Knockout.js Update ViewModel on button click - javascript

Well, that's not the best situation description... Anyway, I'm trying to update my ViewModel but it's not working. By default I'm getting data from controller function and by button click - from another function in same contoller, but ViewModel contain only data received after first ViewModel initialization.
<script>
function viewModel () {
var self = this;
self.currentPage = ko.observable();
self.pageSize = ko.observable(10);
self.currentPageIndex = ko.observable(0);
self.salesdata = ko.observableArray();
self.newdata = ko.observable();
self.currentPage = ko.computed(function () {
var pagesize = parseInt(self.pageSize(), 10),
startIndex = pagesize * self.currentPageIndex(),
endIndex = startIndex + pagesize;
return self.salesdata.slice(startIndex, endIndex);
});
self.nextPage = function () {
if (((self.currentPageIndex() + 1) * self.pageSize()) < self.salesdata().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.salesdata().length / self.pageSize())) - 1);
}
}
//Here I'm trying to update ViewModel
self.request = function (uri) {
$.ajax({
url: uri,
contentType: 'application/json',
data: [],
type: 'GET',
cache: false,
success: function (data) {
ko.mapping.fromJS(data.$values, {}, self.salesdata);
}
});
}
}
$(document).ready(function () {
$.ajax({
url: "/api/sales",
type: "GET",
cache: false,
}).done(function (data) {
var vm = new viewModel();
vm.salesdata(data.$values);
ko.applyBindings(vm);
}).error(function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
});
//Here i'm calling for ViewModel update
$(".btn-default").click(function () {
days = $(this).val();
var uri = "/api/sales?days=" + days;
new viewModel().request(uri);
});
});
</script>
UPDATE.
I chaged block of code where I'm getting new data to be as follow:
self.request = function (uri) {
$.getJSON(uri, function (data) {
ko.mapping.fromJS(data.$values, {}, viewModel);
});
}
Unfortunately this is not working as well. Here is no any JS errors, controller return proper portion of updated data.

I'm new to all of this, but if I'm reading your code correctly, you are calling the request function on a new instance of the view model and not the one that was bound to the html document. You need to make the request call on the view model that you created after the initial get call completed.
Update:
Sorry, I should have been more specific about the code I was referring to. At the end of your code block you have the following code:
$(".btn-default").click(function () {
days = $(this).val();
var uri = "/api/sales?days=" + days;
new viewModel().request(uri);
});
In this code, it appears that each time the default button is clicked, a new view model is created and the request function is called on that view model.
In the document ready function where you are defining what happens after the sales data is loaded, you have the following code which is what creates the view model that the html document is actually bound to:
var vm = new viewModel();
vm.salesdata(data.$values);
ko.applyBindings(vm);
Nothing ever calls the request function on this view model. I wonder if what you really want is to somehow bind the request function in this view model to the default button.

I would try updating the viewmodel salesdata observable, by giving context: self and using the following success method:
self.request = function (uri) {
$.ajax({
url: uri,
contentType: 'application/json',
context: self,
data: [],
type: 'GET',
cache: false,
success: function (data) {
this.salesdata(data.$values);
}
});
}
EDIT:
I can see you attached a click event with jQuery.
You should use knockout clck binding instead:
<button data-bind="click: clickEvent" value="1">Click me!</button>
And in the viewmodel
clickEvent: function (data, event) {
days = event.target.value;
var uri = "/api/sales?days=" + days;
data.request(uri);
}
This way you can retrieve your viewmodel instead of creating a new one as you did with new viewModel().request(uri);
For more on click binding see http://knockoutjs.com/documentation/click-binding.html

Building slightly on the answer from #brader24 here:
In your update button's click event, you use this line of code:
new viewModel().request(uri);
What that is doing is creating a new viewModel (separate from the one that you already have instantiated and have applied bindings for) and filling it's observable array with data via your request function. It isn't affecting your original viewModel at all (the one that has it's bindings applied on the DOM!). So you won't see any errors, but you also won't see anything happening on the page because all you did was create a new viewModel in memory, fill it with data, and do nothing with it.
Try this code (everything in your viewModel function looks fine).
$(document).ready(function () {
var vm = new viewModel(); // declare (and instantiate) your view model variable outside the context of the $.ajax call so that we have access to it in the click binding
$.ajax({
url: "/api/sales",
type: "GET",
cache: false,
}).done(function (data) {
vm.salesdata(data.$values);
ko.applyBindings(vm);
}).error(function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
});
//Here i'm calling for ViewModel update
$(".btn-default").click(function () {
days = $(this).val();
var uri = "/api/sales?days=" + days;
vm.request(uri); // don't use a new instance of a view model - use the one you have already instantiated
});
});
Using a Knockout click binding instead of attaching a click event handler using jQuery is usually the recommended route, but it is not necessary - so your existing code (with the modifications above) should work fine. For more info on that, see Using unobtrusive event handlers in the Knockout docs

Well. Final solution based on #GoTo answer is:
Here is the way to call function in viewmodel via click databind.
<button type="button" class="btn btn-default" id="7" value="7" data-bind="click: getDays.bind($data, '7')">7</button>
Here is the function. As you can see I'm calling self.salesdata instead of viewModel. This solution is working fine but somehow now I have problem with data format that is binded this way -
<td data-bind="text: moment($data.whensold).format('DD.MM', 'ru')"></td>.
self.getDays = function (days) {
var uri = "/api/sales?days=" + days;
$.getJSON(uri, function (data) {
ko.mapping.fromJS(data.$values, {}, self.salesdata);
});
}

Related

Data binding not happening when the binded data is edited/updated in UI5

I have a master-detail page which has a clone button. On clicking the clone button, the data of the first list item is put into a model (detailModel - which is declared in component.js) and a new screen is opened. The data of that list item is binded to some input fields. When any of that input field is updated/erased and then user navigates back without saving it and comes again on that page, that updated/erased field stays blank. I want that data to come in that field which was coming earlier but it is coming blank. Data in my model is also not changing. Below is the code :
Component.js :
this.detailModel = new sap.ui.model.json.JSONModel();
this.detailModel.setDefaultBindingMode(sap.ui.model.BindingMode.OneWay);
sap.ui.getCore().setModel(this.detailModel, "detailModel");
Controller.js :
/*eslint-disable no-console, no-alert */
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"hmel/TravelandGuestHouse/controller/formatter",
"sap/m/MessageToast",
"sap/m/MessageBox",
"sap/m/Button",
"sap/m/Dialog",
"sap/m/Text",
'sap/m/Label',
'sap/m/TextArea'
], function (Controller, JSONModel, formatter, MessageToast, MessageBox, Button, Dialog, Text, Label, TextArea) {
"use strict";
return Controller.extend("hmel.TravelandGuestHouse.controller.CloneTravelRequest", {
onInit: function () {
this.router = sap.ui.core.UIComponent.getRouterFor(this);
//for cloning request
this.detailModel = sap.ui.getCore().getModel("detailModel");
this.getView().setModel(this.detailModel, "detailModel");
//for sending data to guest house screen
this.getView().byId("date").setDateValue(new Date());
this.router.attachRoutePatternMatched(this._handleRouteMatched, this);
},
_handleRouteMatched: function (evt) {
if (evt.getParameter("name") !== "CloneTravelRequest") {
return;
}
this.getView().invalidate();
var that = this;
//fetching Train Names
$.ajax({
url: "/Non_sap_create_requests/odata/TravelPrpTrainDetails",
method: "GET",
dataType: "json",
success: function (data) {
that.getView().getModel("Model").setProperty("/TravelPrpTrainDetails", data.value);
var tempModel = that.getView().getModel("detailModel");
var train = tempModel.getData().TrainName;
var trainKey = formatter.pickTrainKeyFromModel(data.value, train);
that.getView().byId("trainName").setSelectedKey(trainKey);
that.setLocations();
var nameofPass = tempModel.getData().NameOfPsngr;
if (nameofPass === "" || nameofPass === null) {
that.getView().byId("nameOfP").setValue(tempModel.getData().Requestee);
}
},
error: function (err) {
console.log(err.message);
}
});
var fdate = new Date();
this.getView().byId("date").setDateValue(new Date());
this.getView().byId("date").setMinDate(fdate);
}
});
});
If you can change to an oDataModel you can use the resetChanges method.
If you cant, you `ll have to do it a little bit more manually. Once the new windows pops up save the data the user will change and if he cancels it restore the saved data on the jsonDataModel.

Editable resolves to undefined mixing KnockOutJS and plain Javascript

I tried to create a drop-down menu using options binding in KnockOut JS (ko.plus to be precise). Things were running as expected until I mixed my solution up with this jsfiddle: http://jsfiddle.net/jnuc6y05/ in order to place a default option in the list. The problem lies in "HERE" (please see the code) where I get
error message
"TypeError: this.fieldStreetApallou is not a function"
As I said I had no problem, and I think mixing plain javascript with KO caused the situation. I tried to unwrap the editable with no luck since it resolves to undefined. Even ko.toJS does not do the trick (undefined again).
I don't have any serious experience with KO and furthermore with Javascript, and any help would be greatly appreciated.
PS: Reduced code provided
/////// HTML
<input data-bind="value: fieldStreetApallou, enable: fieldStreetApallou.isEditing" />
Rename
<div data-bind="visible: fieldStreetApallou.isEditing">
Confirm
Cancel
</div>
/////// Javascript
<script type="text/javascript">
ko.observableArray.fn.find = function(prop, data) {
var valueToMatch = data[prop];
return ko.utils.arrayFirst(this(), function(item) {
return item[prop] === valueToMatch;
});
};
var availableCompanies = [{
offset: 1,
name: "Company1"
}, {
offset: 2,
name: "Company2"
}
// ...more pairs here
];
//Default pairs for the drop-down menus
var selectedCompanyApallou = {
offset: 1,
name: "Company1"
};
var ViewModel = function(availableCompanies, selectedCompanyApallou) {
this.availableCompaniesApallou = ko.observableArray(availableCompanies);
this.selectedCompanyApallou = ko.observable(this.availableCompaniesApallou.find("offset", selectedCompanyApallou));
this.fieldStreetApallou = ko.editable("Initial value");
postStreetFieldToServerForApallou = function() {
$.ajax({
type: "PUT",
url: "http://www.san-soft.com/goandwin/addresses/" + 15,
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "Address_id=15&Street=" + this.fieldStreetApallou() //<---- HERE!
}).done(function(data) {
alert("Record Updated Successfully " + data.status);
}).fail(function(err) {
alert("Error Occured, Please Reload the Page and Try Again " + err.status);
});
};
};
ko.applyBindings(new ViewModel(availableCompanies, selectedCompanyApallou));
</script>
I think you linked to the wrong JSFiddle.
Looks like this is not what you are expecting when postStreetFieldToServerForApallou is called by the button click. this in JavaScript is based on who called the function.
To work around it in this case, I like to set var self = this; at the top of the view model so self always points to the view model, then I replace all instances of this with self. This is really only needed on your HERE line, but it simplifies to use self throughout.
The fixed view model code:
var ViewModel = function(availableCompanies, selectedCompanyApallou) {
var self = this;
self.availableCompaniesApallou = ko.observableArray(availableCompanies);
self.selectedCompanyApallou = ko.observable(self.availableCompaniesApallou.find("offset", selectedCompanyApallou));
self.fieldStreetApallou = ko.editable("Initial value");
postStreetFieldToServerForApallou = function() {
$.ajax({
type: "PUT",
url: "http://www.san-soft.com/goandwin/addresses/" + 15,
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "Address_id=15&Street=" + self.fieldStreetApallou() //<---- HERE!
}).done(function(data) {
alert("Record Updated Successfully " + data.status);
}).fail(function(err) {
alert("Error Occured, Please Reload the Page and Try Again " + err.status);
});
};
};

Issue with setting value to select dropdown in MVC

I am using MVC.
I am having two drop down and one change of 'primaryspec' the 'primarysubspec' should get loaded.
Everything is working fine for passing values to controller and it got saved to DB.
When I am trying to retrieve the saved details,'primarysubspec' saved values are not getting displayed.
But displaying save data for 'primarySpec'.
Here is my .cshtml code:
#Html.DropDownListFor(m => m.PSpec, Model.PSpec, new { id = "ddUserSpec", style = "width:245px;height:25px;", data_bind = "event: {change: primaryChanged}" }, Model.IsReadOnly)
#Html.DropDownListFor(m => m.PSubspec, Model.PSubspec, new { id = "ddUserSubSpec", style = "width:245px;height:25px;", data_bind = "options: primarySubSpec,optionsText: 'Name',optionsValue: 'Id'" }, Model.IsReadOnly)
Here is my JS Code to retrieve the values for :
this.primarySubSpec = ko.observableArray([]);
this.primarySpecChanged = function () {
var val = $("#ddetailsPrimarySpec").val();
primarySubStartIndex = 0;
primarySubSpecialityUrl = '/PlatformUser/GetSpecandSubSpec?primarySpe=' + val+//model.primarySpecID() +'&secondarySpec=';
loadPrimarySubSpec();
};
function loadPrimarySubSpec() {
$.ajax({
type: 'GET',
url: primarySubSpecUrl,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
processdata: false,
cache: false,
success: function (data) {
primarySubSpec = [];
model.primarySubspec('0');
try {
if (data.length == 0) {
primarySubSpeacId.empty();
}
model.primarySubSpec(data);
},
error: function (request, status, error) {
primarySubSpeacId.prop("disabled", true);
}
});
}
Everything is working fine,but facing issue only while displaying the saved values from the DB.
Showing fine for 'primarySpec'
The values showing empty for 'PrimarySubSpec' instead of saved values in dropdown.
Please let me know what is the issue how can i show the saved value as selected value in 'primarySubSpec'dropdown.
The Problem:
when you load the page to view saved values, the change event is never called.
Why:
When your page is loaded with saved values, the select box has the saved value selected before knockout knows anything about it. Hens the change event isn't called.
Simplest solution:
change the primarySpecilaityChanged as follows
this.primarySpecilaityChanged = function () {
var val = $("#ddUserDetailsPrimarySpeciality").val();
if(val){
primarySubStartIndex = 0;
primarySubSpecialityUrl = '/' + NMCApp.getVirtualDirectoryName() + '/PlatformUser/GetSpecialitiesandSubSpecilaities?primarySpeciality=' + val+//model.primarySpecialityUID() +'&secondarySpeciality=';
loadPrimarySubSpecilaities();
}
};
then call primarySpecilaityChanged function after you call ko.applyBindings.
var viewModel = new YourViewModel();
ko.applyBindings(viewModel);
viewModel.primarySpecilaityChanged();

How to unbind or turn off all jquery function?

I have constructed an app with push state. Everything is working fine. However in some instances my jquery function are fireing multiple times. That is because when I call push state I bind the particular js file for each page I call. Which means that the same js functions are binded many times to the html while I surf in my page.
Tip: I am using documen.on in my jquery funciton because I need my function to get bound to the dynamical printed HTML through Ajax.
I tried to use off in the push state before printing with no success!
Here is my code:
var requests = [];
function replacePage(url) {
var loading = '<div class="push-load"></div>'
$('.content').fadeOut(200);
$('.container').append(loading);
$.each( requests, function( i, v ){
v.abort();
});
requests.push( $.ajax({
type: "GET",
url: url,
dataType: "html",
success: function(data){
var dom = $(data);
//var title = dom.filter('title').text();
var html = dom.find('.content').html();
//alert(html);
//alert("OK");
//$('title').text(title);
$('a').off();
$('.push-load').remove();
$('.content').html(html).fadeIn(200);
//console.log(data);
$('.page-loader').hide();
$('.load-a').fadeIn(300);
}
})
);
}
$(window).bind('popstate', function(){
replacePage(location.pathname);
});
Thanks in advance!
simple bind new function with blank code
$( "#id" ).bind( "click", function() {
//blank
});
or
used
$('#id').unbind();
Try this,
var requests = [];
function replacePage(url) {
var obj = $(this);
obj.unbind("click", replacePage); //unbind to prevent ajax multiple request
var loading = '<div class="push-load"></div>';
$('.content').fadeOut(200);
$('.container').append(loading);
$.each(requests, function (i, v) {
v.abort();
});
requests.push(
$.ajax({
type: "GET",
url: url,
dataType: "html",
success: function (data) {
var dom = $(data);
//var title = dom.filter('title').text();
var html = dom.find('.content').html();
//alert(html);
//alert("OK");
//$('title').text(title);
obj.bind("click", replacePage); // binding after successfulurl ajax request
$('.push-load').remove();
$('.content').html(html).fadeIn(200);
//console.log(data);
$('.page-loader').hide();
$('.load-a').fadeIn(300);
}
}));
}
Hope this helps,Thank you

Convert function into plugin

I have a function that I call multiple times in my projects:
function fillSelect(select) {
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "Data.asmx/Status",
dataType: "json",
async: true,
success: function(data) {
$.each(data.d, function(i) {
select.append('<option value=' + data.d[i].value + '>' + data.d[i].name + '</option>');
});
},
error: function(result) {
alert("Error occured. Contact admin");
}
});
}
Then in my code I'm using this like so:
fillSelect($('select#status1'));
fillSelect($('select#status2'));
fillSelect($('select#status3'));
What I would like to do is to convert my function into plugin, so I would be able to call it as so:
$('select#status1, select#status2, select#status3').fillSelect();
Using http://starter.pixelgraphics.us/ I've generated empty schema:
(function($) {
$.ajaxSelect = function(el, select, options) {
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("ajaxSelect", base);
base.init = function() {
base.select = select;
base.options = $.extend({}, $.ajaxSelect.defaultOptions, options);
// Put your initialization code here
};
// Sample Function, Uncomment to use
// base.functionName = function(paramaters){
//
// };
// Run initializer
base.init();
};
$.ajaxSelect.defaultOptions = {
clear: false //append to select or replace current items
};
$.fn.ajaxSelect = function(select, options) {
return this.each(function() {
(new $.ajaxSelect(this, select, options));
});
};
})(jQuery);
but I don't know how to fill it.
What I would like to do is to call sever ones and then fill as many select items as I put in parameters.
Is all that code really necessary for such a small plugin?
I know that there are probably some plugins that this functionality, but I would like to create my own plugin, just to learn a bit more :)
You don't need all that boiler plate you could do as below
$.fn.fill = function fillSelect(options) {
var self = this;
options = $.extend({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "Data.asmx/Status",
dataType: "json",
async: true,
success: function(data) {
var list = "";
$.each(data.d, function(i) {
list += '<option value='
+ data.d[i].value + '>'
+ data.d[i].name
+ '</option>';
});
self.filter("select").each(function(){
$(this).append(list);
});
},
error: function(result) {
alert("Error occured. Contact admin");
}
},options);
$.ajax(options);
return this;
}
the first thing to notice that the function is added to the jQuery prototype/$.fn. Then the success handler have been changed so that all selected elements will be handled and lastly the selection is returned to make chaining possible, as this is usually expect when using jQuery.
The above code will append the same options to all selected "select" elements only. If you select something else the options will not be appended to those elements.
I've changed the signature to accept an options element. In the above version there's default vesrion equaling your ajax options. If other values are supplied, they will override the default ones if a default exist. If a default does not exist the values will be added to the options object
You just need to add your method to the $.fn object, as described here: http://docs.jquery.com/Plugins/Authoring
The this keyword will evaluate to the jQuery selector that was used to invoke your function's code, so instead of using the select parameter in your code, just use this

Categories

Resources