Moment.js broken when Knockout.js model updated - javascript

I'm using WebApi 2.o together with Knockout.js and Moment.js to parse JSON date/time. Initially Moments.js are working fine when page loaded. But when I did updated Knockout ViewModel, Moment.js can't parse JSON date in proper way and show "Invalid date" error, regardless that WebApi return correct JSON date.
Here is java script:
<script>
function viewModel() {
var self = this;
self.currentPage = ko.observable();
self.pageSize = ko.observable(10);
self.currentPageIndex = ko.observable(0);
self.schedule = ko.observableArray();
self.currentPage = ko.computed(function () {
var pagesize = parseInt(self.pageSize(), 10),
startIndex = pagesize * self.currentPageIndex(),
endIndex = startIndex + pagesize;
return self.schedule.slice(startIndex, endIndex);
});
self.nextPage = function () {
if (((self.currentPageIndex() + 1) * self.pageSize()) < self.schedule().length) {
self.currentPageIndex(self.currentPageIndex() + 1);
}
else {
self.currentPageIndex(0);
}
}
self.previousPage = function () {
if (self.currentPageIndex() > 0) {
self.currentPageIndex(self.currentPageIndex() - 1);
}
else {
self.currentPageIndex((Math.ceil(self.schedule().length / self.pageSize())) - 1);
}
}
self.sortType = "ascending";
self.sortTable = function (viewModel, e) {
var orderProp = $(e.target).attr("data-column")
self.schedule.sort(function (left, right) {
leftVal = left[orderProp];
rightVal = right[orderProp];
if (self.sortType == "ascending") {
return leftVal < rightVal ? 1 : -1;
}
else {
return leftVal > rightVal ? 1 : -1;
}
});
self.sortType = (self.sortType == "ascending") ? "descending" : "ascending";
}
//This must update ViewModel - return data for selected number of days.
self.getDays = function (days) {
var uri = "/api/job?days=" + days;
$.getJSON(uri, function (data) {
ko.mapping.fromJS(data.$values, {}, self.schedule);
})
.error(function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
});
}
}
$(document).ready(function () {
var vm = new viewModel();
$.ajax({
url: "/api/job",
type: "GET",
cache: false,
}).done(function (data) {
vm.schedule(data.$values);
ko.applyBindings(vm);
}).error(function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
});
});
</script>
And this is HTML part:
<div class="well well-sm">
Find schedule for next
<div class="btn-group">
<button type="button" class="btn btn-default" id="7" value="7" data-bind="click: getDays.bind($data, '7')">7 days</button>
<button type="button" class="btn btn-default" id="10" value="10" data-bind="click: getDays.bind($data, '10')">10 days</button>
<button type="button" class="btn btn-default" id="14" value="14" data-bind="click: getDays.bind($data, '14')">14 days</button>
<button type="button" class="btn btn-default" id="30" value="30" data-bind="click: getDays.bind($data, '30')">30 days</button>
<button type="button" class="btn btn-default" id="60" value="60" data-bind="click: getDays.bind($data, '60')">60 days</button>
<button type="button" class="btn btn-default" id="180" value="180" data-bind="click: getDays.bind($data, '180')">180 days</button>
</div>
</div>
<table id="arrival" class="table table-condensed table-striped">
<thead>
<tr data-bind="click: sortTable">
<td></td>
<th data-column="excursion">Name</th>
<th data-column="excursiondate">Date</th>
</tr>
</thead>
<tbody data-bind="foreach: currentPage">
<tr>
<td><a data-bind="attr: { href: '/list/' + $data.kodg }" target="_parent">Tourist list</a></td>
<td data-bind="text: $data.excursion"></td>
<td data-bind="text: moment($data.excursiondate).format('DD.MM', 'ru')"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="pager">
<button data-bind="click: previousPage" class="btn previous"><i class="glyphicon glyphicon-backward"></i></button>
Page
<label data-bind="text: currentPageIndex() + 1" class="badge"></label>
<button data-bind="click: nextPage" class="btn next"><i class="glyphicon glyphicon-forward"></i></button>
</td>
</tr>
</tfoot>
</table>
Here is default data I'm receiving on first call.
{"$id":"1","$values":[{"$id":"2","kodg":1406621521,"excursion":"Big Buddha Tour","excursiondate":"2014-07-31T00:00:00"},{"$id":"3","kodg":-1434407447,"excursion":"City Tour Gems","excursiondate":"2014-07-29T00:00:00"},{"$id":"4","kodg":-23317405,"excursion":"Transfer JSM","excursiondate":"2014-07-29T00:00:00"},{"$id":"5","kodg":-1035617799,"excursion":"Big Buddha Tour","excursiondate":"2014-07-26T00:00:00"},{"$id":"6","kodg":-277944785,"excursion":"City Tour Gems","excursiondate":"2014-07-25T00:00:00"},{"$id":"7","kodg":1405931911,"excursion":"City Tour Gems","excursiondate":"2014-07-22T00:00:00"},{"$id":"8","kodg":1405759887,"excursion":"City Tour Gems","excursiondate":"2014-07-20T00:00:00"},{"$id":"9","kodg":-699185234,"excursion":"Khao Lak","excursiondate":"2014-07-17T00:00:00"},{"$id":"10","kodg":2047068503,"excursion":"City Tour Gems","excursiondate":"2014-07-15T00:00:00"},{"$id":"11","kodg":164879331,"excursion":"City Tour Gems","excursiondate":"2014-07-13T00:00:00"},{"$id":"12","kodg":228070035,"excursion":"Shopping Tour","excursiondate":"2014-07-13T00:00:00"},{"$id":"13","kodg":1978323751,"excursion":"Khao Lak","excursiondate":"2014-07-10T00:00:00"}]}
And here is the set of data I'm receiving after I call getDays with parameter.
{"$id":"1","$values":[{"$id":"2","kodg":1406621521,"excursion":"Big Buddha Tour","excursiondate":"2014-07-31T00:00:00"}]}
I did change WebApi class to be as follow (in order to receive only strings):
public class job
{
public string kodg { get; set; }
public string excursion { get; set; }
public string excursiondate { get; set; }
}
But still has same issue. So it's not depend of data I'm receiving from WebApi controller.

It might help diagnose if you could put in a jsfiddle.
I created a simple view model:
function ViewModel() {
excursionDate = ko.observable('09/22/2014 23:49:35.349');
}
ko.applyBindings(new ViewModel());
and this html:
<span data-bind="text: moment(this.excursionDate()).format('DD.MM', 'ru')"></span>
It works with the above date, returning "22.09". I also works with just 09/22/2014, but with an invalid date like 09/22/2014 23:49.356, it does in fact return "invalid date" on the page. Maybe you could restrict your data to one line or something, scrub the data, debug it or even use a
<textarea data-bind="text: ko.toJSON($data)"></textarea>
to maybe see what's going on and to ensure you're not getting something you're not expecting. HTH

Related

How to Call Partial View with Asp.Net Mvc

I need to search on the database, and load only the View, and not refresh the entire page. A function in Js calls my method on the controller, when clicking on search, and the controller returns the View.
function Pesquisa()
{
let campo = document.getElementsByName("campo");
let pesquisa = document.getElementsByName("EdtPesquisa");
let condicao = document.getElementsByName("pesquisa");
let scampo = Array();
let spesquisa = Array();
let scondicao = Array();
let sNomeGrid = ($(this).find("a").text());
for (var indice = 0; indice < pesquisa.length; indice++)
{
string = pesquisa[indice].value;
if (string.trim() != "")
{
scampo[indice] = campo[indice].id;
scondicao[indice] = condicao[indice].value;
spesquisa[indice] = pesquisa[indice].value;
}
}
window.location.href = "/MenuPrincipal/RetornarView?sNomeGrid=" + "Unidade" + "&listacampo=" + scampo + "&listacondicao=" + scondicao + "&listapesquisa=" + spesquisa;
Controller
public IActionResult RetornarView(string sNomeGrid, List<string> listacampo, List<string> listacondicao, List<string> listapesquisa)
{
var sWhere = "";
if (listacampo.Count > 0)
{
Pesquisa _Pesquisa = new Pesquisa();
sWhere = _Pesquisa.Pesquisar(listacampo, listacondicao, listapesquisa);
}
if (sNomeGrid == "Unidade")
{
var listaunidade = _UnidadeRepositorio.ListarMenu(sWhere);
return View("Unidade", listaunidade);
}
return View("MenuPrincipal");
}
View
#model IEnumerable<ApesWeb.Models.Classes.Unidade>
<div class="tabela-responsive">
<table id="tabela" class="tabela tabela-hover"
data-toggle="table">
<thead>
<tr>
<th id="idunidade" name="campo">#Html.DisplayNameFor(model => model.idunidade)</th>
<th id="sdescricao" name="campo">#Html.DisplayNameFor(model => model.sdescricao)</th>
<th id="sunidade" name="campo">#Html.DisplayNameFor(model => model.sunidade)</th>
<th id="sdigitavolume" name="campo">#Html.DisplayNameFor(model => model.sdigitavolume)</th>
<th id="spadraosistema" name="campo">#Html.DisplayNameFor(model => model.spadraosistema)</th>
</tr>
<tr>
<th>
<div class="inputWithIcon">
<select name="pesquisa" />
<input type="text" name="EdtPesquisa"/>
<i class="fa fa-search" aria-hidden="true" onclick="Pesquisa()"></i>
</div>
</th>
<th>
<div class="inputWithIcon">
<select name="pesquisa"/>
<input type="text" name="EdtPesquisa"/>
<i class="fa fa-search" aria-hidden="true" onclick="Pesquisa()"></i>
</div>
</th>
<th>
<div class="inputWithIcon">
<select name="pesquisa" />
<input type="text" name="EdtPesquisa"/>
<i class="fa fa-search" aria-hidden="true" onclick="Pesquisa()"></i>
</div>
</th>
<th>
<div class="inputWithIcon">
<select name="pesquisa" />
<input type="text" name="EdtPesquisa"/>
<i class="fa fa-search" aria-hidden="true" onclick="Pesquisa()"></i>
</div>
</th>
<th>
<div class="inputWithIcon">
<select name="pesquisa" />
<input type="text" name="EdtPesquisa"/>
<i class="fa fa-search" aria-hidden="true" onclick="Pesquisa()"></i>
</div>
</th>
</tr>
</thead>
<tbody>
#foreach (var Unidade in Model)
{
<tr>
<td>
#Html.DisplayFor(modelitem => Unidade.idunidade)
</td>
<td>
#Html.DisplayFor(modelitem => Unidade.sdescricao)
</td>
<td>
#Html.DisplayFor(modelitem => Unidade.sunidade)
</td>
<td>
#Html.DisplayFor(modelitem => Unidade.sdigitavolume)
</td>
<td>
#Html.DisplayFor(modelitem => Unidade.spadraosistema)
</td>
</tr>
}
</tbody>
</table>
Returns the View with the list to fill the Table, but in this process the entire page is refreshed.
You can use one of the following methods according to your needs:
Method I: If you want to use ViewData, try this:
#Html.Partial("~/PathToYourView.cshtml", null,
new ViewDataDictionary { { "VariableName", "some value" } })
And to retrieve the passed in values:
#{
string valuePassedIn = this.ViewData.ContainsKey("VariableName") ?
this.ViewData["VariableName"].ToString() : string.Empty;
}
Method II: If you just render a partial with just the partial name:
#Html.Partial("_SomePartial", Model)
Method II: Render PartialView using jQuery Ajax call:
Firstly wrap your body content in a div and assign any id to it in _Layout page:
<div id="div-page-content" class="page-content">
#RenderBody()
</div>
Here is the menu item used for rendering PartialView in _Layout page:
<ul class="sub-menu">
<li class="nav-item ">
<a href="#" onclick="renderPartial(event, 'Account', '_Register')" class="nav-link">
<span class="title">Create New User</span>
</a>
</li>
</ul>
Define the javascript function for click event in _Layout page:
function renderPartial(e, controller, action) {
e.preventDefault();
e.stopPropagation();
var controllerName = controller;
var actionName = action;
if (String(actionName).trim() == '') {
return false;
}
if (typeof (controllerName) == "undefined") {
return false;
}
var url = "/" + controllerName + "/" + actionName;
////Open url in new tab with ctrl key press
//if (e.ctrlKey) {
// window.open(url, '_blank');
// e.stopPropagation();
// return false;
//}
$.ajax({
url: url,
data: { /* additional parameters */ },
cache: false,
type: "POST",
dataType: "html",
success: function (data) {
var requestedUrl = String(this.url).replace(/[&?]X-Requested-With=XMLHttpRequest/i, "");
if (typeof (requestedUrl) == "undefined" || requestedUrl == 'undefined') {
requestedUrl = window.location.href;
}
// if the url is the same, replace the state
if (typeof (history.pushState) != "undefined") {
if (window.location.href == requestedUrl) {
history.replaceState({ html: '' }, document.title, requestedUrl);
}
else {
history.pushState({ html: '' }, document.title, requestedUrl);
}
}
$("#div-page-content").html(data);
},
error: function (data) { onError(data); }
});
};
Define your PartialView as shown below:
<div>
... partial view content goes here >
</div>
Add the Action metdod to the Controller as shown below:
[HttpPost]
[AllowAnonymous]
public PartialViewResult _Register(/* additional parameters */)
{
return PartialView();
}

Delete data-id attribute value

I am new on javascript yet. I trying to delete value with data-id button, but data-id value is get always first value on my table. I click on the button with the data-id of "5" but as the id it is "1" (the topmost id) every time. Sorry my bad english.
My table codes:
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Category Name</th>
<th>Status</th>
<th>User</th>
<th>Management</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td><span class="label label-success">#item.ID</span></td>
<td>#item.CategoryName</td>
<td>#if (item.Status == true)
{
<b class="label label-success">Active</b>
}
else
{
<b class="label label-danger">Passive</b>
}
</td>
<td>#item.User.Name</td>
<td>
<button class="btn btn-default btn-sm">Edit</button>
<button class="btn btn-default btn-sm" id="btn-Delete" onclick="DeleteCategory()" data-id="#item.ID">Delete</button>
</td>
</tr>
}
</tbody>
</table>
My javascript codes:
function DeleteCategory() {
var caughtID= $("#btn-Delete").attr("data-id");
$.ajax({
url: "/Category/Delete/" + caughtID,
type: "POST",
dataType: "json",
success: function (response) {
if (response.Success) {
bootbox.alert(response.Message, function () {
location.reload();
});
}
else {
bootbox.alert(response.Message, function () {
//it's null yet
});
}
}
})
}
Id need to be unique, but in your case without id also it can be achievable, what you want to do.
Change:-
onclick="DeleteCategory()"
To:-
onclick="DeleteCategory(this)"
And then:-
function DeleteCategory(ele) {
var caughtID= $(ele).data("id");
.....rest code
DEMO EXAMPLE:-
function DeleteCategory(ele){
alert($(ele).data('id'));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="btn btn-default btn-sm" id="btn-Delete" onclick="DeleteCategory(this)" data-id="1">Delete</button>
<button class="btn btn-default btn-sm" id="btn-Delete" onclick="DeleteCategory(this)" data-id="2">Delete</button>
<button class="btn btn-default btn-sm" id="btn-Delete" onclick="DeleteCategory(this)" data-id="3">Delete</button>
A pure jQuery solution is:-
change id="btn-Delete" to class ="btn-Delete" and remove onclick like below:-
<button class="btn btn-default btn-sm btn-Delete" data-id="#item.ID">Delete</button>
And then change only this line:-
var caughtID= $("#btn-Delete").attr("data-id");
To:-
var caughtID = $(this).data("id");
As Rory commented id attributes must be unique!. You should change the id of the button to class and make click on the static element. Because the button is dynamically created it may cause a problem that you are facing.
<button class="btn btn-default btn-sm btn-Delete" data-id="#item.ID">Delete</button>
$(document).ready(function() {
$('table').on('click', '.btn-Delete', function(event) {
event.preventDefault();
var caughtID = $(this).attr("data-id");
$.ajax({
url: "/Category/Delete/" + caughtID,
type: "POST",
dataType: "json",
success: function(response) {
if (response.Success) {
bootbox.alert(response.Message, function() {
location.reload();
});
} else {
bootbox.alert(response.Message, function() {
//it's null yet
});
}
}
})
});
});

Live search on a web grid table with pagination working only on the actual page

I want to implement a live search on a web grid table which has pagination. However, my search only shows elements which are present in the actual page. I want my search function to do a search on all the element present in the table. Not only the ones actually displayed. Below is my search script:
<script type="text/javascript">
$(document).ready(function () {
$("#filter").keyup(function () {
// Retrieve the input field text and reset the count to zero
var filter = $(this).val(), count = 0;
console.log(filter);
// Loop through each row of the table
$("table tr").each(function () {
// If the list item does not contain the text phrase fade it out
if ($(this).text().search(new RegExp(filter, "i")) < 0) {
$(this).fadeOut();
// Show the list item if the phrase matches and increase the count by 1
} else {
$(this).show();
count++;
}
});
/* var numberItems = count;
$("#filter-count").text("Number of Comments = "+count);*/
});
});
my html page:
<div>
<div id="homeMessage">
Please see below the list of books available.
</div>
<br />
<div id="divCurrentRented">
<form id="live-search" action="" class="styled" method="post">
<fieldset>
<input type="text" class="form-control" id="filter" value="" placeholder="Search by title or author..."/>
<span id="filter-count"></span>
</fieldset>
</form>
</div>
<br />
<div id="divCurrentRented">
#{
WebGrid obj = new WebGrid(Model, rowsPerPage: 5);
}
#obj.Table(htmlAttributes: new
{
id="tableCurrentRented",
#class = "table"
},
headerStyle: "webgrid-header",
footerStyle: "webgrid-footer",
alternatingRowStyle: "webgrid-alternating-row",
rowStyle: "webgrid-row-style",
columns: obj.Columns(
obj.Column("Title", header: "Title"),
obj.Column("Author", header: "Author"),
obj.Column("Avaible", header: "Available", canSort:false),
obj.Column(header: "Rent", format:#<button type="button" class="btn btn-default">Rent it</button>)
))
</div>
<div style="text-align:right">
#obj.Pager(mode: WebGridPagerModes.All)
</div>
<br />
Any idea of how to do this?
The HTML:
<table id="tableCurrentRented" class="table">
<thead>
<tr class="webgrid-header">
<th scope="col">
Title </th>
<th scope="col">
Author </th>
<th scope="col">
Available </th>
<th scope="col">
Rent </th>
</tr>
</thead>
<tbody>
<tr class="webgrid-row-style">
<td>Le Bossu de Notre Dame</td>
<td>Victor Hugo</td>
<td>Yes</td>
<td><button type="button" class="btn btn-default" data-toggle="modal" data-target="#myModal" data-id="9" data-title="Le Bossu de Notre Dame">Yes</button></td>
</tr>
<tr class="webgrid-alternating-row">
<td>Oliver Twist</td>
<td>Charles Dickens</td>
<td>Yes</td>
<td><button type="button" class="btn btn-default" data-toggle="modal" data-target="#myModal" data-id="1" data-title="Oliver Twist">Yes</button></td>
</tr>
<tr class="webgrid-row-style">
<td>Pride and Prejudice</td>
<td>Jane Austen</td>
<td>Yes</td>
<td><button type="button" class="btn btn-default" data-toggle="modal" data-target="#myModal" data-id="5" data-title="Pride and Prejudice">Yes</button></td>
</tr>
<tr class="webgrid-alternating-row">
<td>Sense and Sensibility</td>
<td>Jane Austen</td>
<td>Yes</td>
<td><button type="button" class="btn btn-default" data-toggle="modal" data-target="#myModal" data-id="6" data-title="Sense and Sensibility">Yes</button></td>
</tr>
<tr class="webgrid-row-style">
<td>The Mayor of Casterbridge</td>
<td>Thomas Hardy</td>
<td>Yes</td>
<td><button type="button" class="btn btn-default" data-toggle="modal" data-target="#myModal" data-id="3" data-title="The Mayor of Casterbridge">Yes</button></td>
</tr>
</tbody>
</table>
My controller method:
public ActionResult SearchBooks()
{
var listBook = datamanager.GetAllBooks();
List<ViewBook> listViewBook = new List<ViewBook>();
foreach (Book b in listBook)
{
ViewBook viewBook = new ViewBook();
viewBook.BookID = b.BookId;
viewBook.Title = b.Title;
viewBook.Author = b.Author;
if (b.Rented ?? true)
{
viewBook.Avaible = "No";
}
else
{
viewBook.Avaible = "Yes";
}
listViewBook.Add(viewBook);
}
return View(listViewBook);
}
I have succeeded in passing all the models of my list to the javascript:
var data = #Html.Raw(Json.Encode(Model));
However, when I do this:
$(data).each(function () {
to loop through each element of data, I get this error:
Cannot use 'in' operator to search for 'opacity' in undefined
Any idea how I can solve this?
Javascript
$("#filter").keyup(function () {
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: 'Search?q='+$("#filter").val(),
success:
function (result) {
$("#tableCurrentRented tbody").empty();
$.each(result.Books, function (index, item) {
var cls=(index%2==0)?"webgrid-row-style":"webgrid-alternating-row";
var html = ' <tr class="'+cls+'">'+
'<td>'+item.Title +'</td>'+
'<td>'+item.Author +'</td>'+
'<td>'+item.Avaible +'</td>'+
' <td><button type="button" class="btn btn-default" data-toggle="modal" data-target="#myModal" data-id="'+item.BookID +'" data-title="'+item.Title+'">'+item. +'</button></td></tr>';
$("#tableCurrentRented tbody").append(html);
});
},
error: function (xhr, status, err) {
}
});
});
Add new method for searching in controller
public ActionResult Search(string q)
{
var listBook = datamanager.GetAllBooks().Where(X=> X.Title.Contains(q)).ToList();
List<ViewBook> listViewBook = new List<ViewBook>();
foreach (Book b in listBook)
{
ViewBook viewBook = new ViewBook();
viewBook.BookID = b.BookId;
viewBook.Title = b.Title;
viewBook.Author = b.Author;
if (b.Rented ?? true)
{
viewBook.Avaible = "No";
}
else
{
viewBook.Avaible = "Yes";
}
listViewBook.Add(viewBook);
}
return Json(new { Books = listViewBook }, JsonRequestBehavior.AllowGet);
}
It seems you have a "Model" variable holding the data. Perhaps you should consider filtering the data in this Model directly and passing the filtered Model to the webgrid. The problem, how I understand it, is that you're trying to fetch already rendered values rather than considering the entire collection of data before rendering the filtered data.

Knockout.js attr binding broken when use mapping

I'm trying to update ViewModel via mapping plugin with data sent by WebApi Controller on click. On first load everything is working fine, but when data updated, binding is broken.
I did tried to change attr binding to text binding and it's working nornally, so I suppose that problem is in attr binding. So here is JS:
<script>
function viewModel() {
var self = this;
self.currentPage = ko.observable();
self.pageSize = ko.observable(10);
self.currentPageIndex = ko.observable(0);
self.jobs = ko.observableArray();
self.currentPage = ko.computed(function () {
var pagesize = parseInt(self.pageSize(), 10),
startIndex = pagesize * self.currentPageIndex(),
endIndex = startIndex + pagesize;
return self.jobs.slice(startIndex, endIndex);
});
self.nextPage = function () {
if (((self.currentPageIndex() + 1) * self.pageSize()) < self.jobs().length) {
self.currentPageIndex(self.currentPageIndex() + 1);
}
else {
self.currentPageIndex(0);
}
}
self.previousPage = function () {
if (self.currentPageIndex() > 0) {
self.currentPageIndex(self.currentPageIndex() - 1);
}
else {
self.currentPageIndex((Math.ceil(self.jobs().length / self.pageSize())) - 1);
}
}
self.sortType = "ascending";
self.sortTable = function (viewModel, e) {
var orderProp = $(e.target).attr("data-column")
self.jobs.sort(function (left, right) {
leftVal = left[orderProp];
rightVal = right[orderProp];
if (self.sortType == "ascending") {
return leftVal < rightVal ? 1 : -1;
}
else {
return leftVal > rightVal ? 1 : -1;
}
});
self.sortType = (self.sortType == "ascending") ? "descending" : "ascending";
}
// Here is function that must update ViewModel
self.getDays = function (days) {
var uri = "/api/job?days=" + days;
$.getJSON(uri, function (data) {
ko.mapping.fromJS(data.$values, {}, self.jobs);
})
.error(function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
});
}
$.getJSON("/api/job", function (data) {
self.jobs(data.$values);
});
}
$(document).ready(function () {
ko.applyBindings(new viewModel());
});
</script>
This is how I call update.
<div class="btn-group">
<button type="button" class="btn btn-default" id="7" value="7" data-bind="click: getDays.bind($data, '7')">7 дней</button>
<button type="button" class="btn btn-default" id="10" value="10" data-bind="click: getDays.bind($data, '10')">10 дней</button>
<button type="button" class="btn btn-default" id="14" value="14" data-bind="click: getDays.bind($data, '14')">14 дней</button>
<button type="button" class="btn btn-default" id="30" value="30" data-bind="click: getDays.bind($data, '30')">30 дней</button>
<button type="button" class="btn btn-default" id="60" value="60" data-bind="click: getDays.bind($data, '60')">60 дней</button>
<button type="button" class="btn btn-default" id="180" value="180" data-bind="click: getDays.bind($data, '180')">180 дней</button>
</div>
And this is the table that present data.
<table id="arrival" class="table table-condensed table-striped">
<thead>
<tr data-bind="click: sortTable">
<th></th>
<th data-column="excursion">Название</th>
<th data-column="excursiondate">Дата</th>
</tr>
</thead>
<tbody data-bind="foreach: currentPage">
<tr>
<td><a data-bind="attr: { href: '/list/' + $data.kodg }" target="_parent">Список туристов</a></td>
<%--<td data-bind="text: $data.kodg""></td>--%>
<td data-bind="text: $data.excursion"></td>
<td data-bind="text: $data.excursiondate"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="pager">
<button data-bind="click: previousPage" class="btn previous"><i class="glyphicon glyphicon-backward"></i></button>
Страница
<label data-bind="text: currentPageIndex() + 1" class="badge"></label>
<button data-bind="click: nextPage" class="btn next"><i class="glyphicon glyphicon-forward"></i></button>
</td>
</tr>
</tfoot>
</table>
Any ideas?
Knockout mapping causes all properties to be converted into observables. So in your attr binding the kodg property is a function (ko.observable instance) and you should make a following change:
attr: { href: '/list/' + ko.utils.unwrapObservable($data.kodg) }
or simply
attr: { href: '/list/' + $data.kodg() }
if the kodg is always observable.
Try this
<a
data-bind="
attr: { href: $parent.Link($data.kodg) }"
target="_parent">
Список туристов
</a>
And in your viewmodel
self.Link = function(data){
return '/list/' + data
// Or if you have observable return '/list/' + data()
}

knockout js grid swap input fields if data exists

I have an editable grid in which I populate with data after a user logs in with ajax.
I'm populating it with a device list and shipping information. Inside the device list json I have a Boolean "byod", if the selected row has the device with this data set to "0" I'd like to swap the "MAC Address" text field with the "Ship To" drop down.
fiddle is here http://jsfiddle.net/QTUqD/15/, code is below:
<form id="extMngForm">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Extension</th>
<th>Name</th>
<th>Email</th>
<th>Pin</th>
<th>Device</th>
<th>MAC Address</th>
<th>Ship To</th>
<th style="width: 100px; text-align:right;"></th>
</tr>
</thead>
<tbody data-bind=" template:{name:templateToUse, foreach: pagedList }"></tbody>
</table>
<p class="pull-right addExt"><a class="btn btn-primary" data-bind="click: $root.add" href="#" title="edit"><i class="icon-plus"></i> Add Extension</a></p>
<div class="pagination pull-left" data-bind='visible: pagedList().length > 0'>
<ul><li data-bind="css: { disabled: pageIndex() === 0 }">Previous</li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }">Next</li></ul>
</div>
<br clear="all" />
<script id="extItems" type="text/html">
<tr>
<td style="width:20px;" data-bind="text: extension"></td>
<td data-bind="text: name"></td>
<td data-bind="text: email"></td>
<td style="width:20px;" data-bind="text: vmpin"></td>
<td data-bind="text: device.asObject && device.asObject() && device.asObject().name"></td>
<td data-bind="text: macAddress"></td>
<td data-bind="text: shipTo"></td>
<td class="buttons">
<a class="btn" data-bind="click: $root.edit" href="#" title="edit"><i class="icon-edit"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
<script id="editExts" type="text/html">
<tr>
<td style="width:20px;"><input style="width:65px;min-width: 65px;" data-errorposition="b" class="required" name="extension" data-bind="value: extension" /></td>
<td><input data-errorposition="b" class="required" name="name" data-bind="value: name" /></td>
<td><input data-errorposition="b" class="required" name="email" data-bind="value: email" /></td>
<td style="width:20px;"><input style="width:65px;min-width: 65px;" data-errorposition="b" class="required" name="vmpin" data-bind="value: vmpin" /></td>
<td>
<select data-bind="options: $root.devicesForItem($data), optionsText: 'name', optionsValue: 'id', value: device, valueAsObject: 'asObject'"></select>
</td>
<td><input name="macAddress" data-bind="value: macAddress" /></td>
<td><select style="width:100px;" data-bind="options: $root.addressList, optionsText: 'locationName', optionsValue: 'locationName', value: shipTo"></select></td>
<td class="buttons">
<a class="btn btn-success" data-bind="click: $root.save" href="#" title="save"><i class="icon-ok"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
</form>
window.ExtListViewModel = new function () {
var self = this;
window.viewModel = self;
self.list = ko.observableArray();
self.pageSize = ko.observable(10);
self.pageIndex = ko.observable(0);
self.selectedItem = ko.observable();
self.extQty = ko.observable(20);
self.devices = ko.observableArray([{"id":"gxp2100","name":"Grandstream GXP-2100","qty":"2","byod":"1"}, {"id":"gxp2100","name":"Grandstream GXP-2100 (BYOD)","qty":"1","byod":"0"}, {"id":"pcom331","name":"Polycom 331","qty":"2","byod":"0"}, {"id":"pcom331","name":"Polycom 331 (BYOD)","qty":"1","byod":"1"}]);
self.addressList = ko.observableArray(['addressList']);
self.availableDevices = ko.computed(function() {
var usedQuantities = {};
self.list().forEach(function(item) {
var device = item.device();
if (device) {
usedQuantities[device.id] = 1 + (usedQuantities[device.id] || 0);
}
});
return self.devices().filter(function(device) {
var usedQuantity = usedQuantities[device.id] || 0;
return device.qty > usedQuantity;
});
});
self.devicesForItem = function(item) {
var availableDevices = self.availableDevices();
return self.devices().filter(function(device) {
return device === item.device() || availableDevices.indexOf(device) !== -1;
});
}
self.edit = function (item) {
self.selectedItem(item);
};
self.cancel = function () {
self.selectedItem(null);
};
self.add = function () {
var newItem = new Extension();
self.list.push(newItem);
self.selectedItem(newItem);
self.moveToPage(self.maxPageIndex());
};
self.remove = function (item) {
if (confirm('Are you sure you wish to delete this item?')) {
self.list.remove(item);
if (self.pageIndex() > self.maxPageIndex()) {
self.moveToPage(self.maxPageIndex());
}
}
};
self.save = function () {
self.selectedItem(null);
};
self.templateToUse = function (item) {
return self.selectedItem() === item ? 'editExts' : 'extItems';
};
self.pagedList = ko.dependentObservable(function () {
var size = self.pageSize();
var start = self.pageIndex() * size;
return self.list.slice(start, start + size);
});
self.maxPageIndex = ko.dependentObservable(function () {
return Math.ceil(self.list().length / self.pageSize()) - 1;
});
self.previousPage = function () {
if (self.pageIndex() > 0) {
self.pageIndex(self.pageIndex() - 1);
}
};
self.nextPage = function () {
if (self.pageIndex() < self.maxPageIndex()) {
self.pageIndex(self.pageIndex() + 1);
}
};
self.allPages = ko.dependentObservable(function () {
var pages = [];
for (i = 0; i <= self.maxPageIndex() ; i++) {
pages.push({ pageNumber: (i + 1) });
}
return pages;
});
self.moveToPage = function (index) {
self.pageIndex(index);
};
};
ko.applyBindings(ExtListViewModel, document.getElementById('extMngForm'));
function Extension(extension, name, email, vmpin, device, macAddress, shipTo){
this.extension = ko.observable(extension);
this.name = ko.observable(name);
this.email = ko.observable(email);
this.vmpin = ko.observable(vmpin);
this.device = ko.observable(device);
this.macAddress = ko.observable(macAddress);
this.shipTo = ko.observable(shipTo);
};
ExtListViewModel.addressList = [{"shipping_address_street":"555 Lane","shipping_address_state":"TX","shipping_address_city":"Dallas","shipping_address_postalcode":"75000","locationName":"Preset"}, {"shipping_address_street":"555 Lane","shipping_address_state":"TX","shipping_address_city":"Dallas","shipping_address_postalcode":"75000","locationName":"Home"}];
//Shows device name not value (knockout extension)
ko.bindingHandlers.valueAsObject = {
init: function(element, valueAccessor, allBindingsAccessor) {
var value = allBindingsAccessor().value,
prop = valueAccessor() || 'asObject';
//add an "asObject" sub-observable to the observable bound against "value"
if (ko.isObservable(value) && !value[prop]) {
value[prop] = ko.observable();
}
},
//whenever the value or options are updated, populated the "asObject" observable
update: function(element, valueAccessor, allBindingsAccessor) {
var prop = valueAccessor(),
all = allBindingsAccessor(),
options = ko.utils.unwrapObservable(all.options),
value = all.value,
key = ko.utils.unwrapObservable(value),
keyProp = all.optionsValue,
//loop through the options, find a match based on the current "value"
match = ko.utils.arrayFirst(options, function(option) {
return option[keyProp] === key;
});
//set the "asObject" observable to our match
value[prop](match);
}
};
First of all, like I showed you in your other recent post, don't use optionsValue binding for your selects if you want the value to be the object itself (and then you don't need that valueAsObject stuff). So:
<select data-bind="options: $root.devicesForItem($data), optionsText: 'name', value: device"></select>
Second, your IDs need to be unique (not like in your current example) because your code depends on that.
Then, the rest is easy, you just need a simple computed boolean in your Extension objects that says whether the MAC address or the shipping thing is shown, e.g.:
this.showMac = ko.computed(function() {
if (self.device())
return self.device().byod !== '0';
return true;
});
And use this computed in the bindings, e.g. <!-- ko if: showMac --> or <td data-bind="text: showMac() ? macAddress : shipTo>"
Fiddle: http://jsfiddle.net/antishok/QTUqD/16/

Categories