Knockout JS - update viewModel with AJAX Call - ajax

there are some similar questions but didn't help me to solve my issue. I can't update my results on page / view after updating my viewModel with AJAX. I am getting valid AJAX response that updates the view if I reload the page, but not when I click btnAdvancedSearch
I have simple HTML:
<div>
<input type="button" id="btnAdvancedSearch" data-bind="click: refresh" />
</div>
<div id="resultslist1" data-bind="template: { name: 'rest-template', foreach: restaurants }">
</div>
And I bind in on document load:
$(document).ready(function () {
ko.applyBindings(new RestaurantsListViewModel());
});
My viewModel is like this, and in it I call refresh that is bound with button
// Overall viewmodel for this screen, along with initial state
function RestaurantsListViewModel() {
var self = this;
self.restaurants = ko.observableArray([]);
var mappedRests = $.map($.parseJSON(sessionStorage.getItem('searchResults')), function (item) { return new Restaurant(item) });
self.restaurants = mappedRests;
self.refresh = function () {
updateRestaurantsList(); //Method executes AJAX and saves result to session.
var mappedRests2 = $.map($.parseJSON(sessionStorage.getItem('searchResults')), function (item) { return new Restaurant(item) });
self.restaurants= mappedRests2;
}
}
What am I missing here?
Thanks

I have tried waiting for AJAX to finish like this:
// Overall viewmodel for this screen, along with initial state
function RestaurantsListViewModel() {
var self = this;
self.restaurants = ko.observableArray([]);
var mappedRests = $.map($.parseJSON(sessionStorage.getItem('searchResults')), function (item) { return new Restaurant(item) });
self.restaurants = mappedRests;
self.refresh = function () {
var latitude = sessionStorage.getItem('latitude');
var longitude = sessionStorage.getItem('longitude');
var query = '{"Accepts_Reservations":"' + $('#chkReservation').is(":checked") + '","Accepts_Cards":' + $('#chkAcceptsCards').is(":checked") + '"}';
var searchResults = getRestaurantsAdvancedSearchAJAX(query, latitude, longitude, 40);
searchResults.success(function (data) {
var information = data.d;
var mappedRests2 = $.map($.parseJSON(information), function (item) { return new Restaurant(item) });
self.restaurants = mappedRests2;
});
};
};
Edit 1
Once you have declared your observable like so:
self.restaurants = ko.observableArray([]);
When you want to update restaurants you cannot do this:
self.restaurants = mappedRests2;
Instead, you need to do this:
self.restaurants(mappedRests2);

updateRestaurantsList(); //Method executes AJAX and saves result to session.
The comment after the above line indicates that this method is making an asynchronous call. Therefore, it is likely that the line after it is executing before sessionStorage has been populated. Maybe consider having updateRestaurantsList return a promise. Then you could update your code to something like this:
updateRestaurantsList().then(function() {
var mappedRests2 = $.map($.parseJSON(sessionStorage.getItem('searchResults')), function (item) { return new Restaurant(item) });
self.restaurants= mappedRests2;
});
This way the call to populate your mappedRests2 variable won't happen until after your updateRestaurantsList method has completed.
Edit 1
Be sure to never assign values to an observable using an equal sign.
// Overall viewmodel for this screen, along with initial state
function RestaurantsListViewModel() {
var self = this;
self.restaurants = ko.observableArray([]);
var mappedRests = $.map($.parseJSON(sessionStorage.getItem('searchResults')), function (item) { return new Restaurant(item) });
self.restaurants(mappedRests);
self.refresh = function () {
var latitude = sessionStorage.getItem('latitude');
var longitude = sessionStorage.getItem('longitude');
var query = '{"Accepts_Reservations":"' + $('#chkReservation').is(":checked") + '","Accepts_Cards":' + $('#chkAcceptsCards').is(":checked") + '"}';
var searchResults = getRestaurantsAdvancedSearchAJAX(query, latitude, longitude, 40);
searchResults.success(function (data) {
var information = data.d;
var mappedRests2 = $.map($.parseJSON(information), function (item) { return new Restaurant(item) });
self.restaurants(mappedRests2);
});
};
};

Related

Pushing array to select in knockout gives just one option

I am using MVC and a Razor View I'm trying to bound data received from a controller to a select using a knockout model, If I try to push directly the dynamic array I get only one option like this one
Only one option select:
I'm sure that I'm missing something stupid, I have already tried to return a new SelectList and using optionsText and optionsValue but didn't do the work.
I'm sure the knockout model is correct because if I write
viewModel.dliveryDates.push("option1","option2");
it works as expected
Here's my controller code that reads some data from database and send it back to the view
[HttpPost]
public JsonResult GetDeliveryDates(string code)
{
OrderHeaderPageModel instance = ObjectFactory.Create<OrderHeaderPageModel>();
instance.DeliveryDateRanges = PopulateDeliveryDateRanges(code);
return Json(instance.DeliveryDateRanges.ToArray());
}
Here's is my View code
#Html.DropDownList("deliveryranges", new SelectList(string.Empty, "Code", "Description"), "- Seleziona -", new { #data_bind = "options:dliveryDates" })
And finally my knockout model
function OrderHeaderViewModel() {
var self = this;
self.save = function () {
return true;
}
self.dliveryDates = ko.observableArray([]);
}
var viewModel = new OrderHeaderViewModel();
ko.applyBindings(viewModel, el);
$("#ordertypes").change(function () {
var postUrl = "/checkout/getdeliverydates";
$("#deliveryranges").empty();
$.post(postUrl,
{
code: $("#ordertypes").val(),
__RequestVerificationToken: Sana.Utils.getAntiForgeryToken()
}, function (data) {
var arry = [];
var array = $.map(data, function (value, index) {
return [value];
});
$.each(data, function (i, data) {
arry.push(data.Code);
});
viewModel.dliveryDates.push(arry);
}
);
})
It looks like the code is doing some extra work mapping data that is not used in the ajax callback. Hope the following code helps.
function OrderHeaderViewModel() {
var self = this;
self.getData = function() {
//function to simulate filling the array from the server.
var data = ["Item 1", "Item 2", "Item 3", "Item 4"];
self.dliveryDates(data);
var mappedData = data.map(function(item, index) {
return {
id: index,
description: item
};
});
viewModel.mappedDliveryDates(mappedData);
}
self.save = function() {
return true;
}
//added to put the selected values in
self.selectedValue = ko.observable();
self.selectedMappedValue = ko.observable();
self.mappedDliveryDates = ko.observableArray([]);
self.dliveryDates = ko.observableArray([]);
}
var viewModel = new OrderHeaderViewModel();
ko.applyBindings(viewModel);
$("#ordertypes").change(function() {
var postUrl = "/checkout/getdeliverydates";
$("#deliveryranges").empty();
$.post(postUrl, {
code: $("#ordertypes").val(),
__RequestVerificationToken: Sana.Utils.getAntiForgeryToken()
}, function(data) {
// if the data needs to be transformed and is already an array then you can use
var mappedData = data.map(function(item, index) {
return {
id: index,
description: item
};
});
// If the data is already in the format that you need then just put it into the observable array;
viewModel.mappedDliveryDates(mappedData);
viewModel.dliveryDates(data);
});
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Server Data Values -
<select data-bind="options: dliveryDates, value:selectedValue, optionCaption: 'Choose...'"></select>
<br/> Mapped Values -
<select data-bind="options: mappedDliveryDates, optionsText:'description', value: selectedMappedValue, optionCaption: 'Choose...'"></select>
<br/>
<button data-bind="click: getData">Load Data</button>
<br/>
<br/>
<pre data-bind="text: ko.toJSON($root)"></pre>

Nativescript - Pass array from home-view-model to home.js

I´m having a hard time understanding how to perform this action(as the title says), and maybe someone could help me understand the process, my code is below:
My home-view-model:
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var http = require("http");
function createViewModel() {
http.getJSON("http://myJsonfile").then(function (r) {
var arrNoticias = new ObservableArray(r.data);
return arrNoticias;
}, function (e) {
});
}
exports.createViewModel = createViewModel;
I have done a console.log of the arrNoticias before i have putted it inside a callback function and it returns [object object] etc...and then i have done this:
console.log(arrNoticias.getItem(0).titulo);
and it returns the info i need!.
Then in my home.js file i have this:
var observableModule = require("data/observable")
var ObservableArray = require("data/observable-array").ObservableArray;
var arrNoticias = require('./home-view-model.js');
console.log(arrNoticias.getItem(0).titulo);
and the result in the console is:
TypeError: arrNoticias.getItem is not a function. (In 'arrNoticias.getItem(0)', 'arrNoticias.getItem' is undefined)
My question is, how does this action is perform? passing the data from view-model to the .js file?
Thanks for your time
Regards
As that function send a URL request so probably it's an async function, which is on hold while requesting so that's why you get undefined. Normally, you will want your function that sends a URL request to return a promise. Based on that promise, you will the result as expected after the request is done. So:
function createViewModel() {
return new Promise<>((resolve, reject) => {
http.getJSON("http://myJsonfile").then(function (r) {
var arrNoticias = new ObservableArray(r.data);
resolve(arrNoticias);
}, function(e) {
reject(e);
});
}), (e) => {
console.log(e);
})
}
In home.js:
var homeVM= require('./home-view-model.js');
var arrNoticias;
homeVM.createViewModel().then(function(r) {
arrNoticias = r;
});

Rx Js - create an observer for $.ajax Complete()

I have an ajax call for an insert operation. I need to update a grid (future there might be many) once the ajax call completed. How to do this using Rx Js.
I have done something with event based but it won't work out for my case.
NOTE: save ajax call is in different script file.
var submitButton = document.querySelector('.btn-primary');
var refresh1ClickStream = Rx.Observable.fromEvent(submitButton, 'click');
var requestStream = refresh1ClickStream.startWith('startup click')
.map(function () {
var max = 10;
return 'http://localhost/.../GetPayments';
});
var responseStream = requestStream
.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise($.getJSON(requestUrl));
});
function createSuggestionStream(closeClickStream) {
return closeClickStream.startWith('startup click')
.combineLatest(responseStream,
function (click, data) {
return data;
}
)
.merge(
refresh1ClickStream.map(function () {
return null;
})
)
.startWith(null);
}
var paymentStream = createSuggestionStream(refresh1ClickStream);
//var pfaStream = createSuggestionStream(refresh1ClickStream);
// Rendering ---------------------------------------------------
function renderSuggestion(payments, selector) {
//render grid
}
paymentStream.subscribe(function (payment) {
renderSuggestion(payment, 'payment1');
});
//pfaStream.subscribe(function (pfa) {
// renderSuggestion(pfa, 'tblPayFromAccount');
//});

How can I pass values loaded by ajax to components?

I'm passing a value as a parameter to a component.
<badge-button params="badge: oarBadge"></badge-button>
Here is the viewModel containing oarBadge:
function AppViewModel() {
var self = this;
self.oarBadge = ko.observable();
$.getJSON('/guy.json', function(data) {
var badge = new Badge('wood oar', data.badges.oar, false);
self.oarBadge(badge);
// self.oarBadge().has() returns true so the badge is being properly created with data
// returned by the ajax call
});
} // AppViewModel()
Here is the Badge constructor:
function Badge(name, has, active) {
var self = this;
self.name = ko.observable(name);
self.has = ko.observable(has);
self.active = ko.observable(active);
self.disabled = ko.computed(function() {
return self.has();
});
self.toggleActive = function() {
self.active(!self.active())
};
self.toggleHas = function() {
self.has(!self.has());
};
}
Here is the component's viewModel:
ko.components.register('badge-button', {
viewModel: function(params) {
var self = this;
self.badge = params.badge();
self.open = function() {
self.badge.toggleHas();
self.badge.toggleActive();
}
},
template:
'<img class="ui image" src="http://fakeimg.pl/300/" data-bind="click: open, css: { disabled: badge.disabled }" >'
});
When the page loads, I get an error telling me that badge is undefined.
Full example: https://gist.github.com/guyjacks/5a8763ff71f90e3fe8b4b153ed9a5283
Try setting a default object before the ajax call is completed, also you should assign the observable itself not the evaluation for the observable, so instead of doing this:
self.badge = params.badge();
You should do it like this:
self.badge = params.badge;
Otherwise your variable won't be updated once the ajax request is completed.
Here is a small example: https://jsfiddle.net/b0bdru1u/1/
Note: As far as I know the disable binding won't work in images

Kendo Grid always focus on first cell of Top Row

I have checkbox in Kendo grid. Once i click on Checkbox it always focus the top cell in Kendo Grid. Below is code for Kendo grid that I am binding to checkbox value on checkbox click event in Kendo Grid
$("#contactgrid").on('click', '.chkbx', function () {
var checked = $(this).is(':checked');
var grid = $('#contactgrid').data().kendoGrid;
var rowIdx = $("tr", grid.tbody).index(row);
var colIdx = $("td", row).index(this);
// grid.tbody.find("tr").eq(rowIndex).foucs(); This doesn't work
var dataItem = grid.dataItem($(this).closest('tr'));
dataItem.set('IsSelected', checked);
});
I can get the row index and cell Index in click event but I was not able to figure out to focus the specific cell.
Thanks!
When you want to edit Grid with checkbox then I would suggest you to use the approach from this code library. No matter it uses the MVC extensions open Views/Home/Index.cshtml and see how the template is defined and the javascript used after initializing the Grid.
Here it is
Column template:
columns.Template(#<text></text>).ClientTemplate("<input type='checkbox' #= IsAdmin ? checked='checked':'' # class='chkbx' />")
.HeaderTemplate("<input type='checkbox' id='masterCheckBox' onclick='checkAll(this)'/>").Width(200);
<script type="text/javascript">
$(function () {
$('#persons').on('click', '.chkbx', function () {
var checked = $(this).is(':checked');
var grid = $('#persons').data().kendoGrid;
var dataItem = grid.dataItem($(this).closest('tr'));
dataItem.set('IsAdmin', checked);
})
})
function checkAll(ele) {
var state = $(ele).is(':checked');
var grid = $('#persons').data().kendoGrid;
$.each(grid.dataSource.view(), function () {
if (this['IsAdmin'] != state)
this.dirty=true;
this['IsAdmin'] = state;
});
grid.refresh();
}
</script>
I struggled with this. I essential refocused the cell as shown below. There's plenty of room for improvement in the Kendo grid client-side API. Hopefully my helper methods below will help people out.
var $row = getRowForDataItem(this);
var $current = getCurrentCell($row);
var currentCellIndex = $row.find(">td").index($current);
this.set('PriceFromDateTime', resultFromDate);
$row = getRowForDataItem(this);
var grid = getContainingGrid($row);
//select the previously selected cell by it's index(offset) within the td tags
if (currentCellIndex >= 0) {
grid.current($row.find(">td").eq(currentCellIndex));
}
//Kendo grid helpers
function getColumn(grid, columnName) {
return $.grep(grid.columns, function (item) {
return item.field === columnName;
})[0];
}
function getRowForDataItem(dataItem) {
return $("tr[data-uid='" + dataItem.uid + "']");
}
function getCurrentCell($elem) {
return getContainingGrid($elem).current();
}
function getContainingDataItem($elem) {
return getDataItemForRow(getContainingRow($elem));
}
function getContainingCell($elem) {
return $elem.closest("td[role='gridcell']");
}
function getContainingRow($elem) {
return $elem.closest("tr[role='row']");
}
function getContainingGrid($elem) {
return $elem.closest("div[data-role='grid']").data("kendoGrid");
}
function getGridForDataItem(dataItem) {
return getContainingGrid(getRowForDataItem(dataItem));
}
function getDataItemForRow($row, $grid) {
if (!$grid) $grid = getContainingGrid($row);
return $grid.dataItem($row);
}
function getMasterRow($element) {
return $element.closest("tr.k-detail-row").prev();
}
function getChildGridForDataItem(dataItem) {
return getRowForDataItem(dataItem).next().find("div.k-grid").data("kendoGrid");
}
function getMasterRowDataItem($element) {
var $row = getMasterRow($element);
return getDataItemForRow($row);
}

Resources