Knockout + Select2 -- setting default values? - javascript

TL;DR: I need to set a default value for display on a select2 field, bound via knockout, but the select2 binding keeps overriding my viewmodel value to "" instead of accepting the value.
The Ingredients
I am utilizing the following:
KnockoutJS
Select2 on input fields
Custom knockout binding to select2
An ajax call to load an object (an invoice) as part of a Start() method on my viewmodel.
The Goal
Load my values as part of the initial viewmodel's Start() function
Bind the select2 default values to the values of my VM at the time it loads the invoice.
Allow users to select other options as they choose
the default values wold be included in the select2 options as well due to the way we're bringing down the select2 values, so no need to
The Problem
Select2 would be working entirely fine if I was starting from a blank form. It loads my values from an Ajax call upon dropdown, etc.
However, when I load the invoice for display, the viewmodel values aren't set on the select2 controls.
It appears the select2 control actually loads the data and overwrites my viewmodel's value with "" when it loads, as a value hasn't been selected yet -- rather than letting me show a default item based on my bound value..
Thoughts so far on Trying to Solve it
I'll be investigating all of these:
I might not be properly using the knockout binding to allow for a default element choice that isn't a part of its values.
If there is a way I could verify that the select2 boxes are loaded and then trigger an element update, that would be fine, too.
The Code
Document Load
$(document).ready(function () {
'use strict';
console.log("creating viewmodel");
vm = new invoiceDetailsPage.ViewModel();
vm.Start();
console.log("applying bindings");
ko.applyBindings(vm);
});
The invoiceDetailsPage NameSpace(some irrelevant parts removed)
var invoiceDetailsPage = invoiceDetailsPage || {
InvoiceDetailItem: function () {
'use strict';
var self = this;
self.DatePayable = new Date();
self.Fees = 0.00;
self.Costs = 0.00;
self.Adjustments = ko.observable();
self.AdjustmentNote = ko.observable();
self.Total = ko.computed(function () {
});
self.hasAdjustments = ko.computed(function () {
});
},
Invoice: function (invoiceID, documentTypeID, firmID, invoiceNumber, invoicePeriod, datePayable, privateComment, statusID, vendorFirmID) {
'use strict';
var self = this;
self.TypeID = ko.observable(documentTypeID);
self.PrivateComment = ko.observable(privateComment);
self.Status = ko.observable(statusID);
self.FirmID = ko.observable(firmID);
self.VendorFirmID = ko.observable(vendorFirmID);
self.InvoicePeriod = ko.observable(invoicePeriod);
self.DatePayable = ko.observable(datePayable);
self.InvoiceNumbers = ko.observable(invoiceNumber);
self.DetailItems = ko.observableArray([]);
self.isFinalized = ko.computed(function () {
//finalized if it has the appropriate status (anything except)
});
self.hasPrivateComments = ko.computed(function () {
// if self.privatecomment isn't null or empty, true
});
self.TotalFees = ko.computed(function () {
//foreach item in detailitems, sum of fees.
});
self.TotalCosts = ko.computed(function () {
//foreach item in detailitems, sum of Costs.
});
self.TotalAdjustments = ko.computed(function () {
//foreach item in detailitems, sum of adjustments.
});
self.GrandTotal = ko.computed(function () {
//foreach item in detailitems, sum of totals.
});
},
LoadInvoice: function (clientSiteID, invoiceID, callbackFunction, errorFunction) {
'use strict';
var self = this;
self.clientSiteID = clientSiteID;
self.invoiceID = invoiceID;
$.ajax({
url: '/api/DefenseInvoice/GetDefenseInvoice?ClientSiteID=' + self.clientSiteID + "&InvoiceID=" + invoiceID,
type: 'GET',
processData: false,
contentType: 'application/json; charset=utf-8',
dataType: "json",
data: null,
success: function (data) {
console.log(data);
callbackFunction(data);
},
error: function (jqXHR, textStatus, errorThrown) {
errorFunction(jqXHR, textStatus, errorThrown);
}
});
},
ViewModel: function () {
'use strict';
var self = this;
self.InvoiceLoaded = ko.observable();
self.Invoice = ko.observable(new invoiceDetailsPage.Invoice()); // load blank invoice first
self.clientSiteID = -1;
self.invoiceID = -1;
self.SaveInvoiceDetails = function () {
// can only save the details prior to approval / rejection
// should update only general invoice fields, not private comments or adjustments
};
self.LoadInvoice = function() {
self.InvoiceLoaded(false);
invoiceDetailsPage.LoadInvoice(self.clientSiteID, self.invoiceID, function(result) {
//success
vm.Invoice(new invoiceDetailsPage.Invoice(
result.InvoiceInfo.DefenseInvoiceID,
result.InvoiceDocumentTypeID,
result.InvoiceInfo.FirmID,
result.InvoiceInfo.InvoiceNumber,
result.InvoiceInfo.InvoicePeriod,
result.InvoiceInfo.DatePayable,
result.InvoiceInfo.PrivateComment,
result.InvoiceInfo.StatusID,
result.InvoiceInfo.VendorFirmID
));
self.InvoiceLoaded(true);
}, function() {
//error
toastr.error("We're sorry, but an error occurred while trying to load the invoice. Please contact support or refresh the page to try again.", "Invoice Approval");
console.log("LoadInvoice -- ERROR");
console.log(" error: " + errorThrown);
toastr.clear(notifier);
});
};
self.Start = function () {
self.LoadInvoice();
};
},
utils: {
GetSelect2Options: function (placeholder, url) {
'use strict';
var options = {
allowClear: false,
placeholder: placeholder,
query: function (query) {
var dto = {
query: query.term,
filters: {
ClientSiteID: Number(vm.clientSiteID)
}
};
$.ajax({
type: "POST",
url: url,
data: JSON.stringify(dto),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
query.callback(msg);
}
});
}
};
return options;
}
}
};
The Knockout Binding we're using
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor(),
lookupKey = allBindings.lookupKey;
$(element).select2(obj);
if (lookupKey) {
var value = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('data', ko.utils.arrayFirst(obj.data.results, function (item) {
return item[lookupKey] === value;
}));
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
});
},
update: function (element) {
$(element).trigger('change');
}
};
The HTML Element and its bindings
<input type="text" id="ddlInvoiceType" placeholder="Invoice Type" class="select2-container" data-bind="select2: invoiceDetailsPage.utils.GetSelect2Options('Invoice Type', '/api/DefenseInvoiceType/Post'), value: Invoice().TypeID"/>

Not sure I understood the question properly, at first I see the possible issue in the update part of the custom binding:
update: function (element, valueAccessor) {
//added next row to update value
$(element).val(ko.utils.unwrapObservable(valueAccessor()));
$(element).trigger("change");
}

I got it working and I think the difference is in the init with an select like
<input type="hidden" class=""
data-bind="value: observedValue, select2: --your options--">
Here is mine:
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).select2('destroy');
});
select2 = ko.utils.unwrapObservable(allBindings().select2);
$(element).select2(select2);
},

Related

Select options are loaded and disapear after

I'm trying to dynamically load partial view with a different model instances inside of main view. Everything is working fine, until I have to reload partial view content. Then I have to reload 1 select list options, that is not in model. I have js method for that. Problem is, sometimes options are added, and disapear instantly. What am I doing wrong?
There are no any methods, that are changind this select control.
Main view:
<div id="tableContainer">
#{Html.RenderPartial("_TableView", Model.Tables[0]);}
</div>
<script type="text/javascript">
function loadTable () {
var selectedElement = $("#tableSelect").children("option:selected").val();
$("#tableContainer").load("/Home/UpdateTable/" + selectedElement);
getDishes();
};
window.onload = function () {
getDishes();
};
</script>
getDishes function:
function getDishes() {
$.ajax({
type: "GET",
url: "/GetDishes",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: "",
success: function (result) {
if (result.success) {
fillDishList(result.message);
}
else
alert("Błąd podczas pobierania dostępnych dań! Treść błędu: " + result.message);
}
})
}
And fillDishList function:
function fillDishList(list) {
for (var a = 0; a < list.length; a++) {
var element = { val: a, text: list[a].Name };
$("#dishes").append($("<option>").attr('value', element.val).text(element.text));
}
updateDishPrice();
}
EDIT
Ok I got it working. There was timing issue. Function that should append elements to select list were launched before partial view were completely loaded. Here is solution:
<script type="text/javascript">
function loadTable () {
var selectedElement = $("#tableSelect").children("option:selected").val();
$("#tableContainer").html("");
$("#tableContainer").load("/Home/UpdateTable/" + selectedElement);
window.requestAnimationFrame(ready);
};
var ready = function () {
var dishes = $("#dishes");
if (dishes) {
getDishes();
if (dishes.children().length != 0)
return;
};
window.requestAnimationFrame(ready);
};
window.onload = function () {
getDishes();
};
</script>

Knockout customer binding valueAccessor() is not a function

I created a customer binding for icheck plug , i want update the binding object when checkbox checked .
This is my viewModel
var userViewModel = function () {
var self = this;
self.UserFullData = ko.observable();
self.loadObject = function () {
$.ajax({
type: 'GET',
contentType: 'application/json',
url: '../UserData.do/GetUser?id=1',
success: function (result) {
var data = JSON.parse(result);
if (data) {
self.UserFullData(data); //data from ajax
}
}
});
};
Ajax data:
{"User":{"ID":1,"HumanID":1,"LoginName":"cccc","LoginPass":"eeee","PermissionID":1,"DepartmentID":8,"Name":"cz","Sex":1,"SexStr":null,"EName":"cheo","NickName":"b","Company":null,"Address":null,"Email":null,"Country":0,"Province":0,"City":0,"Area":0,"PersonType":0,"Mobile":null,"PhoneNumber":"123456","MSN":null,"Fax":null,"Extension":null,"LastLoginTime":"0001-01-01T00:00:00","Position":null},"PermissionGroup":[{"ID":1,"Title":"a","View":false,"Update":true}]}
HTML :
'View' is a property in Array of FullUserData.PermissionGroup
<div class="box" data-bind="with:UserFullData">
<div data-bind="foreach:PermissionGroup">
<input type="checkbox" class="minimal" style="position: absolute; opacity: 0;" data-bind="iCheckBinding:View">
following is customer binding function :
ko.bindingHandlers.iCheckBinding = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).iCheck({
checkboxClass: 'icheckbox_minimal-blue',
radioClass: 'iradio_minimal-blue'
});
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
if (valueUnwrapped) {
$(element).iCheck('check');
}
else {
$(element).iCheck('uncheck');
}
$(element).on('ifChanged', function () {
var newValue = this.checked;
var observable = valueAccessor();
//observable always not a function
observable(newValue);
});
}
};
now everything others works fine to me .. just observable(newValue) is not a function stucked me , i can't update my object .
anyone knows ?
You're expecting View to be an observable, but you're doing nothing to make it an observable. The only observable in that code is UserFullData, which you set up here:
self.UserFullData = ko.observable();
The code in your success function does nothing to make anything inside that observable:
success: function (result) {
var data = JSON.parse(result);
if (data) {
self.UserFullData(data); //data from ajax
}
}
If you want View in that structure to be observable, you have to make it so.

Two ViewModels Binding Using KnockOut JS in a MVC4 View

I am using knockoutJs 3.3.0. In My application I have common javascript file which is reffered all over the application. apart from that I'll be having individual Js files for each page.
So I have two view Models, one is in common and another is page-level viewModel page level one view model. and all the functions of my both Js files are ajax.
I need to bind them in view.
This is my Common ViewModel
var App = function () {
var self = {};
self.FP = ko.observable($("#fpTag").val());
self.UserName = ko.observable($("#StoreValues").val());
self.Enterprise = ko.observable($("#fpIdentifier").val());
self.UpdateFP = function () {
$.ajax({
url: "/Home/createDeal",
type: "POST",
data: { tag: self.FP() },
success: function (data) {
self.FpData($.parseJSON(data));
//return result;
},
error: function (data) {
//return result;
}
});
}
return self;
}
ko.applyBindings(new App());
and this is my PageLevel Js
var Discovery = function() {
var self = {};
var application = new App();
self.KeyWords = ko.observable();
self.GetSearchKeywords = ko.computed(function () {
var data = application.FpData();
if (data != null) {
$.ajax({
url: "/Home/GetSearchKeywords",
type: "POST",
data: { tag: application.UserName(), EnterpriseId: application.Enterprise(), StoreType: "SINGLE", offset: 1 },
success: function (res) {
self.KeyWords($.parseJSON(res));
},
error: function (res) {
}
});
}
});
return self;};ko.applyBindings(new Discovery());
I My view How can I refer the Value as I need all values from both ViewModels.
In My view:
<tbody data-bind="foreach: $root.KeyWords()">
<tr>
<td data-bind="text: keyword"></td>
<td data-bind="text: App().FormatDate(createdOn)"></td>
<td data-bind="text: ip"></td>
</tr>
</tbody>
<input data-bind="value: App().FP()"/>
How can I achieve this..?
UPDATE
Here is the link which I found Helpful. multiple viewmodels communication
This is (IMHO) not possible, one can bind only one model to a specific element.
I would try to make one model object a property of the other one.
You should 'composite' the two viewModels together:
JS:
var App = function (pageVm) {
var self = {};
if (typeof pageVm !== 'undefined') {
self.pageVm = pageVm;
}
self.FP = ko.observable($("#fpTag").val());
self.UserName = ko.observable($("#StoreValues").val());
self.Enterprise = ko.observable($("#fpIdentifier").val());
self.UpdateFP = function () {
$.ajax({
url: "/Home/createDeal",
type: "POST",
data: { tag: self.FP() },
success: function (data) {
self.FpData($.parseJSON(data));
//return result;
},
error: function (data) {
//return result;
}
});
}
return self;
}
var pageVm = new Discovery();
ko.applyBindings(new App(pageVm));
Now your App bindings work without modification, and you can access your page-specific VMs through the App.pageVm attribute.
I prefer this, typically, rather than binding each VM to a different DOM node (also an option) because often one node may be nested inside the other which can make that difficult.

Knockout ObserableArray dropdown binding issue

I am trying to fetch data from restful wcf service in page load and bind to drop down which is not working.
function CreateItem(name, value) {
var self = this;
self.itemName = ko.observable(name);
self.itemValue = ko.observable(value);
};
function AppViewModel() {
var self = this;
self.titleList = ko.observableArray();
self.load = function () {
alert("click fired");
$.ajax({
url: "https://mydomain/RestfulService/Service1.svc/CreateData?name=venkat",
type: "POST",
cahce: false,
async: false,
data:'',
dataType: "jsonp",
success: function (data) {
for (var i = 0; i < data.length; i++) {
self.titleList().push(new CreateItem(data[i].Description, data[i].TitleID));
}
alert("success " + data);
},
error: function (error) {
alert("failed " + error);
}
});
};
};
<div>
<select data-bind="options: titleList(), optionsText: 'itemName', optionsValue: 'itemValue', value: selectedTitleValue, optionsCaption: 'Please choose'"></select>
</div>
<script type="text/javascript">
$(document).ready(function() {
var model = new AppViewModel();
model.load();
ko.applyBindings(model);
});
</script>
The issue is, knockout array is populating in load function correctly but drop down is not refreshing updated data. I am not understanding where is the problem. Please give inputs.
Replace:
self.titleList().push(new CreateItem(data[i].Description, data[i].TitleID));
with
self.titleList.push(new CreateItem(data[i].Description, data[i].TitleID));
The reason is self.titleList() returns the underlying array, when you push data to it, Knockout is unaware of the changes and does not notify the views.

ASP.NET MVC - Javascript array always passed to controller as null

I'm having some problem with passing a javascript array to the controller. I have several checkboxes on my View, when a checkbox is checked, its ID will be saved to an array and then I need to use that array in the controller. Here are the code:
VIEW:
<script type="text/javascript">
var selectedSearchUsers = new Array();
$(document).ready(function () {
$("#userSearch").click(function () {
selectedSearchUsers.length = 0;
ShowLoading();
$.ajax({
type: "POST",
url: '/manage/searchusers',
dataType: "json",
data: $("#userSearchForm").serialize(),
success: function (result) { UserSearchSuccess(result); },
cache: false,
complete: function () { HideLoading(); }
});
});
$(".userSearchOption").live("change", function () {
var box = $(this);
var id = box.attr("dataId");
var checked = box.attr("checked");
if (checked) {
selectedSearchUsers.push(id);
}
else {
selectedSearchUsers.splice(selectedSearchUsers.indexOf(id), 1);
}
});
$("#Send").click(function () {
var postUserIDs = { values: selectedSearchUsers };
ShowLoading();
$.post("/Manage/ComposeMessage",
postUserIDs,
function (data) { }, "json");
});
});
</script>
When the "Send" button is clicked, I want to pass the selectedSearchUsers to the "ComposeMessage" action. Here is the Action code:
public JsonResult ComposeMessage(List<String> values)
{
//int count = selectedSearchUsers.Length;
string count = values.Count.ToString();
return Json(count);
}
However, the List values is always null. Any idea why?
Thank you very much.
You might try changing the controller's action method to this:
[HttpPost]
public JsonResult ComposeMessage(string values)
{
JavaScriptSerializer jass = new JavaScriptSerializer;
AnyClass myobj = jass.Deserialize<AnyClass>((string)values);
...
...
}
I believe that you have to take the JSON data in as a string and do the conversion
manually. Hope it helps. Cheers.

Categories