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>
Related
I am using asp.net mvc5 in which i am using Ienumerable in View and also in Controller to pass the mutiple values for which i need to use Foreach loop.
After using all this i am getting NULL values in controller from Model
For controller:
public ActionResult ManageTimeExpense(IEnumerable<TimeExpense> Timemodel, IEnumerable<ExpenseBook> Expensemodel)
{
var get = Request.Files.Count;
try
{
{
//for(var i = Timemodel)
foreach(var r in Timemodel)
for view:
#model IEnumerable<Tuple<TimeSheet.Models.TimeExpense , TimeSheet.Models.ExpenseBook>>
#{
Layout = null;
}
#using (Html.BeginForm("ManageTimeExpense", "TimeExpense", FormMethod.Post,new { enctype = "multipart/form-data" }))
{ <div class="col-lg-3 col-md-3 col-sm-12 col-xs-12">
<label>Hours:</label>
<input id="Hours" name="Hours" type="number" class="form-control hours" placeholder="24 Hours" />
#* #Html.TextBoxFor(h => h.Hours, new {#type="number",#class = "form-control",#placeholder="24 Hours", Required = "required" })*#
</div>
<div class="col-md-3 col-sm-12 col-xs-12">
<label></label>
<input type="submit" value="Save" class="form-control btn btn-primary">
</div>
$("#Save").click(function () {
var TimeExpense = [];
var ExpenseBook = [];
var Date = $("#Date").val();
var TimeExpenseMasterId = $("#TimeExpenseMasterId").val();
$('.invoicerow1').each(function () {
var TaskMasterId = $(this).closest('tr').find('.task').val();
var Hours = $(this).closest('tr').find('.hours').val();
var TimeDescription = $(this).closest('tr').find('. timedes').val();
TimeExpense.push({
TimeExpenseMasterId: TimeExpenseMasterId,
Date: Date,
TaskMasterId: TaskMasterId,
Hours: Hours,
TimeDescription: TimeDescription,
});
});
$('.invoicerow').each(function () {
var ExpenseDescription = $(this).closest('tr').find('.exp').val();
var Amount = $(this).closest('tr').find('.amt').val();
var Currency = $(this).closest('tr').find('.currency').val();
var BillStatus = $(this).closest('tr').find('.bill').val();
ExpenseBook.push({
ExpenseDescription: ExpenseDescription,
Amount: Amount,
Currency: Currency,
BillStatus: BillStatus,
});
});
var data = {
TimeExpense: TimeExpense,
ExpenseBook: ExpenseBook
};
});
</script>
I am implementing a calendar and I have encountered some difficulties because I am learning JavaScript alone and I have not mastered yet.
My problem is this: I can create the event and save it in the database, but the event and the color of it ... do not appear on the calendar ... can someone find out the solution or where is the problem?
If possible ... to create events ... being the color of this event always in random color?
View
<div id="calender"></div>
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><span id="eventTitle"></span></h4>
</div>
<div class="modal-body">
<button id="btnDelete" class="btn btn-default btn-sm pull-right">
<span class="glyphicon glyphicon-remove"></span> Remover
</button>
<button id="btnEdit" class="btn btn-default btn-sm pull-right" style="margin-right:5px;">
<span class="glyphicon glyphicon-pencil"></span> Editar
</button>
<p id="pDetails"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fechar</button>
</div>
</div>
</div>
</div>
<div id="myModalSave" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Salvar Evento</h4>
</div>
<div class="modal-body">
<form class="col-md-12 form-horizontal">
<input type="hidden" id="hdID_Reserva" value="0" />
<div class="form-group">
<label>Cliente</label>
<input name="Nome" class="form-control" type="text" placeholder="Introduza o Nome" id="txtCliente">
<input type="hidden" id="txtID_Cliente" name="ID_Cliente" />
</div>
<div class="form-group">
<label>Data de Entrada</label>
<div class="input-group date" id="dtp1">
<input type="text" id="txtDataEntrada" name="DataEntrada" class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
<div class="form-group">
<label>Data de Saida</label>
<div class="input-group date" id="dtp1">
<input type="text" id="txtDataSaida" name="DataSaida" class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
<div class="form-group">
<label>Número Quarto</label>
#Html.DropDownList("ID_Quarto", null, "Selecione o Quarto", htmlAttributes: new { #class = "form-control" })
</div>
<div class="form-group">
<label>Número Pessoas</label>
<input id="txtNumeroPessoas" name="NumeroPessoas" class="form-control" />
</div>
<div class="form-group">
<label>Número Noites</label>
<input id="txtNumeroNoites" name="NumeroNoites" class="form-control" />
</div>
<div class="form-group">
<label>Preço</label>
<input id="txtPreço" name="Preço" class="form-control" />
</div>
<div class="form-group">
<label>Observações</label>
<input id="txtObservaçoes" name="Observaçoes" class="form-control" />
</div>
<button type="button" id="btnSave" class="btn btn-success">Salvar</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Fechar</button>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link href="~/AdminLTE/plugins/fullcalendar-3.9.0/fullcalendar.min.css" rel="stylesheet" />
<link href="~/AdminLTE/plugins/fullcalendar-3.9.0/fullcalendar.print.min.css" rel="stylesheet" media="print" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" rel="stylesheet" />
#section Scripts{
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="~/AdminLTE/plugins/fullcalendar-3.9.0/fullcalendar.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
<script>
$(document).ready(function () {
var events = [];
var selectedEvent = null;
FetchEventAndRenderCalendar();
function FetchEventAndRenderCalendar() {
events = [];
$.ajax({
type: "GET",
url: "/CalendárioReservas/GetEvents",
success: function (data) {
$.each(data, function (i, v) {
events.push({
eventID: v.ID_Reserva,
clienteID: v.ID_Cliente,
quartoID: v.ID_Quarto,
inicio: moment(v.DataEntrada),
fim: v.DataSaida != null ? moment(v.DataSaida) : null,
noites: v.NumeroNoites,
pessoas: v.NumeroPessoas,
preço: v.Preço,
obs: v.Observaçoes
});
})
GenerateCalender(events);
},
error: function (error) {
alert('failed');
}
})
}
function GenerateCalender(events) {
$('#calender').fullCalendar('destroy');
$('#calender').fullCalendar({
contentHeight: 400,
defaultDate: new Date(),
timeFormat: 'h(:mm)a',
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay,agenda'
},
eventLimit: true,
eventColor: '#378006',
events: events,
eventClick: function (calEvent, jsEvent, view) {
selectedEvent = calEvent;
$('#myModal #eventTitle').text(calEvent.clienteID);
var $quartoID = $('<div/>');
$quartoID.append($('<p/>').html('<b>DataEntrada:</b>' + calEvent.inicio.format("DD-MMM-YYYY HH:mm a")));
if (calEvent.fim != null) {
$quartoID.append($('<p/>').html('<b>DataSaida:</b>' + calEvent.fim.format("DD-MMM-YYYY HH:mm a")));
}
$quartoID.append($('<p/>').html('<b>ID_Quarto:</b>' + calEvent.quartoID));
$('#myModal #pDetails').empty().html($quartoID);
$('#myModal').modal();
},
selectable: true,
select: function (inicio, fim) {
selectedEvent = {
eventID: 0,
clienteID: '',
quartoID: '',
inicio: inicio,
fim: fim,
pessoas: '',
noites: '',
preço: '',
obs: ''
};
openAddEditForm();
$('#calendar').fullCalendar('unselect');
},
editable: true,
eventDrop: function (event) {
var data = {
ID_Reserva: event.eventID,
ID_Cliente: event.clienteID,
DataEntrada: event.inicio.format('DD/MM/YYYY HH:mm A'),
DataSaida: event.fim != null ? event.fim.format('DD/MM/YYYY HH:mm A') : null,
ID_Quarto: event.quartoID,
NumeroNoites: event.noites,
NumeroPessoas: event.pessoas,
Preço: event.preço,
Observaçoes: event.obs
};
SaveEvent(data);
}
})
}
$('#btnEdit').click(function () {
//Open modal dialog for edit event
openAddEditForm();
})
$('#btnDelete').click(function () {
if (selectedEvent != null && confirm('Are you sure?')) {
$.ajax({
type: "POST",
url: '/CalendárioReservas/DeleteEvent',
data: { 'eventID': selectedEvent.eventID },
success: function (data) {
if (data.status) {
//Refresh the calender
FetchEventAndRenderCalendar();
$('#myModal').modal('hide');
}
},
error: function () {
alert('Failed');
}
})
}
})
$('#dtp1,#dtp2').datetimepicker({
format: 'DD/MM/YYYY HH:mm A'
});
//$('#chkDiaTodo').change(function () {
// if ($(this).is(':checked')) {
// $('#divDataFim').hide();
// }
// else {
// $('#divDataFim').show();
// }
//});
function openAddEditForm() {
if (selectedEvent != null) {
$('#hdID_Reserva').val(selectedEvent.eventID);
$('#txtID_Cliente').val(selectedEvent.clienteID);
$('#txtDataEntrada').val(selectedEvent.inicio.format('DD/MM/YYYY HH:mm A'));
//$('#chkDiaTodo').prop("checked", selectedEvent.allDay || false);
//$('#chkDiaTodo').change();
$('#txtDataSaida').val(selectedEvent.fim != null ? selectedEvent.fim.format('DD/MM/YYYY HH:mm A') : '');
$('#ID_Quarto').val(selectedEvent.quartoID);
$('#txtNumeroNoites').val(selectedEvent.noites);
$('#txtNumeroPessoas').val(selectedEvent.pessoas);
$('#txtPreço').val(selectedEvent.preço);
$('#txtObservaçoes').val(selectedEvent.obs);
}
$('#myModal').modal('hide');
$('#myModalSave').modal();
}
$('#btnSave').click(function () {
//Validation/
if ($('#txtID_Cliente').val().trim() == "") {
alert('Introduza um Título');
return;
}
if ($('#txtDataEntrada').val().trim() == "") {
alert('Introduza uma Data de Início');
return;
}
//if ($('#chkDiaTodo').is(':checked') == false && $('#txtDataFim').val().trim() == "") {
// alert('Introduza uma Data de Fim');
// return;
//}
else {
var startDate = moment($('#txtDataEntrada').val(), "DD/MM/YYYY HH:mm A").toDate();
var endDate = moment($('#txtDataSaida').val(), "DD/MM/YYYY HH:mm A").toDate();
if (startDate > endDate) {
alert('Data de Fim Inválida');
return;
}
}
var data = {
ID_Reserva: $('#hdID_Reserva').val(),
ID_Cliente: $('#txtID_Cliente').val().trim(),
DataEntrada: $('#txtDataEntrada').val().trim(),
DataSaida: $('#txtDataSaida').val().trim(),
ID_Quarto: $('#ID_Quarto').val(),
NumeroPessoas: $('#txtNumeroPessoas').val(),
NumeroNoites: $('#txtNumeroNoites').val(),
Preço: $('#txtPreço').val(),
Observaçoes: $('#txtObservaçoes').val()
}
SaveEvent(data);
// call function for submit data to the server
})
function SaveEvent(data) {
$.ajax({
type: "POST",
url: '/CalendárioReservas/SaveEvent',
data: data,
success: function (data) {
if (data.status) {
//Refresh the calender
FetchEventAndRenderCalendar();
$('#myModalSave').modal('hide');
}
},
error: function () {
alert('Failed');
}
})
}
})
</script>
controller
namespace WebApplication.Controllers
{
public class CalendárioReservasController : Controller
{
private HotelEntities db = new HotelEntities();
// GET: CalendárioReservas
public ActionResult Index()
{
ViewBag.ID_Quarto = new SelectList(db.Quarto, "ID_Quarto", "ID_Quarto");
return View();
}
public JsonResult GetEvents()
{
try
{
var events = db.Reserva.Select(p => new
{
ID_Reserva = p.ID_Reserva,
ID_Cliente = p.ID_Cliente,
ID_Quarto = p.ID_Quarto,
DataEntrada = p.DataEntrada,
DataSaida = p.DataSaida,
NumeroNoites = p.NumeroNoites,
NumeroPessoas = p.NumeroPessoas,
Preço = p.Preço,
Observaçoes = p.Observaçoes
}).ToList();
return Json(events, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(ex.Message);
}
}
[HttpPost]
public JsonResult SaveEvent(Reserva e)
{
var status = false;
using (HotelEntities dc = new HotelEntities())
{
if (e.ID_Reserva > 0)
{
//Update the event
var v = dc.Reserva.Where(a => a.ID_Reserva == e.ID_Reserva).FirstOrDefault();
if (v != null)
{
v.ID_Cliente = e.ID_Cliente;
v.DataEntrada = e.DataEntrada;
v.DataSaida = e.DataSaida;
v.ID_Quarto = e.ID_Quarto;
v.NumeroPessoas = e.NumeroPessoas;
v.NumeroNoites = e.NumeroNoites;
v.Preço = e.Preço;
v.Observaçoes = e.Observaçoes;
}
}
else
{
dc.Reserva.Add(e);
}
dc.SaveChanges();
status = true;
}
return new JsonResult { Data = new { status = status } };
}
[HttpPost]
public JsonResult DeleteEvent(int eventID)
{
var status = false;
using (HotelEntities dc = new HotelEntities())
{
var v = dc.Reserva.Where(a => a.ID_Reserva == eventID).FirstOrDefault();
if (v != null)
{
dc.Reserva.Remove(v);
dc.SaveChanges();
status = true;
}
}
return new JsonResult { Data = new { status = status } };
}
}
}
Model
public partial class Reserva
{
public int ID_Reserva { get; set; }
public int ID_Cliente { get; set; }
public int ID_Quarto { get; set; }
public System.DateTime DataEntrada { get; set; }
public Nullable<System.DateTime> DataSaida { get; set; }
public int NumeroPessoas { get; set; }
public Nullable<int> NumeroNoites { get; set; }
public Nullable<decimal> Preço { get; set; }
public string Observaçoes { get; set; }
public virtual Cliente Cliente { get; set; }
public virtual Quarto Quarto { get; set; }
}
Your event objects do not conform to the specification laid out at https://fullcalendar.io/docs/event-object .
Here is the problem:
$.each(data, function (i, v) {
events.push({
eventID: v.ID_Reserva,
clienteID: v.ID_Cliente,
quartoID: v.ID_Quarto,
inicio: moment(v.DataEntrada),
fim: v.DataSaida != null ? moment(v.DataSaida) : null,
noites: v.NumeroNoites,
pessoas: v.NumeroPessoas,
preço: v.Preço,
obs: v.Observaçoes
});
});
FullCalendar doesn't speak Portuguese. The event property names have to match the ones in the documentation otherwise it will ignore them. It doesn't magically know that inicio means start, for example. This means it cannot read any start time from your event, and therefore it doesn't know how to display it on the calendar.
Again as per the documentation, your events are also required to have a title, which you didn't appear to include at all.
Try this instead:
$.each(data, function (i, v) {
events.push({
id: v.ID_Reserva,
clienteID: v.ID_Cliente,
quartoID: v.ID_Quarto,
title: '[You need to choose something to put here]',
start: moment(v.DataEntrada),
end: v.DataSaida != null ? moment(v.DataSaida) : null,
noites: v.NumeroNoites,
pessoas: v.NumeroPessoas,
preço: v.Preço,
obs: v.Observaçoes
});
});
I have just changed the properties which matter for displaying the event. For other custom properties, you can call them anything you like, but the ones which have a defined purpose in fullCalendar must be named correctly.
P.S. You may need to change the code in your eventDrop function in a similar way.
I'm trying integrate ko on one of my razor views. I've researched quite a bit yesterday but haven't been able to find a similar solution for my problem.
I have a record model:
public class Record
{
public Int Request Id {get;set;}
public string RecordName {get;set;}
public Person Person {get;set;}
public IList<Person> People { get; set; }
}
And a Person Model:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IList<Alias> Aliases { get; set; }
}
My View:
#model Record
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("PostRequest", "Request", FormMethod.Post))
{
#Html.LabelFor(m => m.Person.FirstName)
#Html.TextBoxFor(m => m.Person.FirstName)
<h5>People</h5>
<table>
<tbody data-bind="foreach: People">
<tr>
<td>#Html.LabelFor(m => m.Person.FirstName)</td>
<td><input type="text" data-bind="value: FirstName, attr: {name: 'People[' + $index() + '].FirstName'}" /></td>
<td>#Html.LabelFor(m => m.Person.LastName)</td>
<td><input type="text" data-bind="value: LastName, attr: {name: 'People[' + $index() + '].LastName'}" /></td>
</tr>
<tr>
<td><button id="removePerson" data-bind="click: $root.removePerson">Remove Person</button></td>
</tr>
<tr>
<td>
<button id="addAlias" data-bind="click: addAlias">Add Alias</button>
</td>
</tr>
<!-- ko foreach: Aliases -->
<tr>
<td>#Html.LabelFor(m => m.Alias.FirstNameAlias)</td>
<td><input type="text" data-bind="value: FirstNameAlias, attr: {name: 'Aliases[' + $index() + '].FirstNameAlias'}" /></td>
<td>#Html.LabelFor(m => m.Alias.LastNameAlias)</td>
<td><input type="text" data-bind="value: LastNameAlias, attr: {name: 'Aliases[' + $index() + '].LastNameAlias'}" /></td>
<td><button id="removeAlias" data-bind="click: $root.removeAlias">Remove Alias</button></td>
</tr>
<!-- /ko -->
</tbody>
</table>
<button id="addPerson" data-bind="click: addPerson">Add Person</button>
<button>Submit</button>
<input id="clickMe" type="button" value="clickme" onclick="submit();" />
}
And Script:
#section scripts
{
<script type="text/javascript">
$(function () {
var personItem = function () {
var self = this;
self.LastName = ko.observable();
self.FirstName = ko.observable();
self.Aliases = ko.observableArray();
};
var model = ko.mapping.fromJS(#Html.Raw(Model.ToJson()));
alert('The length of the array is ' + model.People().length);
alert('The first element is ' + model.People()[0].Aliases().length);
alert(ko.toJSON(model));
model.addPerson = function () {
model.People.push(new personItem());
};
model.removePerson = function (person) {
model.People.remove(person);
};
ko.applyBindings(model);
})
function submit() {
$.ajax({
url: '/Request/PostRequest',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON(model),
success: function (status) {
alert(status);
}
});
};
</script>
}
The Controller action that GETs this view pre initializes some data for the record model, so the binding appears to be working properly, however when I post data, the data for Alias List comes back Null.
Controller:
public ActionResult CreateRequest(Record viewModel)
{
var list = new Record
{
People = new List<Person> {
new Person {FirstName = "My First Person", Aliases = new List<Alias> { new Alias{FirstNameAlias = "firstnamealias",LastNameAlias = "lastnamealias1"}},},
new Person {FirstName = "My Second Person", Aliases = new List<Alias> { new Alias{FirstNameAlias = "firstnamealias2", LastNameAlias ="lastnamealias2"}},}
},
Person = new Person()
{
FirstName = "me"
}
};
return View(list);
}
[HttpPost]
public JsonResult PostRequest(Record viewModel)
{
return Json(String.Format("'Success':'false','Error':'"));
}
I feel that I am missing something with ko.mapping. But so close, as the data populates from the GET controller action, but fails to POST.
SOLUTION
I updated my KO script, and the way I was rendering the ko data in the view. This has solved my problem.
Updated Script
<script>
var initialData = #Html.Raw(Serialize(Model.People));
var BackgroundModel = function(people) {
var self = this;
self.people = ko.mapping.fromJS(people);
self.addPerson = function() {
self.people.push({
FirstName: "",
LastName: "",
Aliases: ko.observableArray()
});
};
self.removePerson = function(person) {
self.people.remove(person);
};
self.addAlias = function(person) {
person.Aliases.push({
//todo
FirstNameAlias: "",
LastNameAlias: ""
});
};
self.removeAlias = function(Alias) {
$.each(self.people(), function() { this.Aliases.remove(Alias) })
};
self.save = function() {
self.lastSavedJson(JSON.stringify(ko.toJS(self.people), null, 2));
var subModel = JSON.stringify(ko.toJS(self));
$.ajax({
url: '/Request/PostRequest',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: subModel,
success: function (status) {
alert(status);
}
});
};
self.lastSavedJson = ko.observable("")
};
ko.applyBindings(new BackgroundModel(initialData));
</script>
Updated View
<div data-bind="foreach: people">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"> <a data-bind="attr:{href:'#collapseOne'+$index()}, text:FirstName() +' '+ LastName()" #*href="#collapse_One"*# class="accordion-toggle" data-toggle="collapse" data-parent="#accordion"></a> </h4>
</div>
<div data-bind="attr:{id:'collapseOne'+$index()}" class="panel-collapse collapse in" #*data-bind="attr:{id:_collapseId, class:_collapsedIn}"*#>
<div class="panel-body">
<table>
<tbody>
<tr>
<td>#Html.DisplayNameFor(x => x.Person.FirstName)</td>
<td>#Html.DisplayNameFor(x => x.Person.LastName)</td>
</tr>
<tr>
<td><input data-bind="value: FirstName, name: FirstName" /></td>
<td><input data-bind="value: LastName, name: LastName" /></td>
</tr>
<tr>
<td><a href='#' data-bind='click: $root.removePerson'>Remove Person</a></td>
</tr>
</tbody>
</table>
<!-- ko foreach: Aliases -->
<table>
<tbody>
<tr>
<td>#Html.DisplayNameFor(x => x.Alias.FirstNameAlias)</td>
<td>#Html.DisplayNameFor(x => x.Alias.LastNameAlias)</td>
</tr>
<tr>
<td><input data-bind="value: FirstNameAlias, name: FirstNameAlias" /></td>
<td><input data-bind="value: LastNameAlias, name: LastNameAlias" /></td>
</tr>
<tr>
<td><a href='#' data-bind='click: $root.removeAlias'>Remove Alias</a></td>
</tr>
</tbody>
</table>
<!-- /ko -->
<a href='#' data-bind='click: $root.addAlias'>Add Alias</a>
</div>
</div>
</div>
</div>
The variable model in your submit funtion is going to be undefined it's not the model you're expecting it to be.
Move the submit method to in to your model...
$(function () {
var personItem = function () {
var self = this;
self.LastName = ko.observable();
self.FirstName = ko.observable();
self.Aliases = ko.observableArray();
};
var model = ko.mapping.fromJS(#Html.Raw(Model.ToJson()));
alert('The length of the array is ' + model.People().length);
alert('The first element is ' + model.People()[0].Aliases().length);
alert(ko.toJSON(model));
model.addPerson = function () {
model.People().push(new personItem());
};
model.removePerson = function (person) {
model.People().remove(person);
};
model.submit = function() {
$.ajax({
url: '/Request/PostRequest',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON(model),
success: function (status) {
alert(status);
}
});
};
ko.applyBindings(model);
});
and update the binding on your button to
<input id="clickMe" type="button" value="clickme" data-bind="click: submit" />
Also
Controller.Json expects an object to serialize rather than a string
[HttpPost]
public JsonResult PostRequest(Record viewModel)
{
return Json(String.Format("'Success':'false','Error':'"));
}
Should be:
[HttpPost]
public JsonResult PostRequest(Record viewModel)
{
return Json(new { success = false, error: String.Empty });
}
Update
Also noticed another issue with updating the model.People array
model.addPerson = function () {
model.People.push(new personItem());
};
model.removePerson = function (person) {
model.People.remove(person);
};
In these functions model.People should be model.People(), i.e.
model.addPerson = function () {
model.People().push(new personItem());
};
model.removePerson = function (person) {
model.People().remove(person);
};
Notice the () after model.People - included in the code block above.
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;
});
I want to populate multiple inputs inside a template after select of an autocomplete item. I'm following http://jsfiddle.net/rniemeyer/MJQ6g/ but not sure how to apply this to multiple inputs.
Model:
<script>
var ContactModel = function (contactsInfo) {
var self = this;
self.Company = ko.observable();
self.ContactsInformation = contactsInfo;
self.Name = ko.observable();
};
var ContactsInformationModel = function () {
var self = this;
self.Address1 = ko.observable();
};
var viewModel;
var ViewModel = function () {
var self = this;
self.Contact1 = new ContactModel(new ContactsInformation);
self.Contact2 = new ContactModel(new ContactsInformation);
};
$(function () {
viewModel = new ViewModel();
ko.applyBindings(viewModel);
});
function getContacts(searchTerm, sourceArray) {
$.getJSON("web_service_uri" + searchTerm, function (data) {
var mapped = $.map(data, function (item) {
return {
label: item.Name,
value: item
};
});
return sourceArray(mapped);
});
}
</script>
Template:
<script type="text/html" id="contact-template">
<div>
Name
<input data-bind="uniqueName: true,
jqAuto: { autoFocus: true, html: true },
jqAutoSource: $root.Contacts,
jqAutoQuery: getContacts,
jqAutoValue: Name,
jqAutoSourceLabel: 'label',
jqAutoSourceInputValue: 'value',
jqAutoSourceValue: 'label'"
class="name" />
</div>
<div>
Company
<input data-bind="value: Company, uniqueName: true" class="company" />
</div>
<div>
Address
<input data-bind="value: ContactsInformation.Address1, uniqueName: true" class="address1" />
</div>
</script>
Html:
<div data-bind="template: { name: 'contact-template', data: Contact1 }">
</div>
<hr/>
<div data-bind="template: { name: 'contact-template', data: Contact2 }">
</div>
If you leave out the jqAutoSourceValue option from your data-bind, then it will set the value equal to the actual object. Then, you can read properties off of that object.
Usually, you would have an observable like: mySelectedPerson and then bind a section (possibly with the with binding) against it and access the individual properties/observables off of that object.
Here is the sample modified to leave out the jqAutoSourceValue option, bind jqAutoValue against an observable called mySelectedPerson and use the with binding to display some properties from the selected object. http://jsfiddle.net/rniemeyer/YHvyL/