Knockout.js: Get options text from select populated with ajax call - javascript

I'm working with a web app using knockout.js as front-end data manipulator, and fetching de data from the server with AJAX calls.
I have a series of drop-downs which are populated via AJAX, using id as values and strings as optionText, and I need to catch, in different divs, the optionText of the selected option of these drop-downs. So far, using different methods from other Stack Overflow answers, I've not been able to do it.
Here is the code:
View
<div>
<select data-bind="options: provinces,optionsText:'name',optionsValue:'id',value: provincesSelected "></select>
<select data-bind="options: regions,optionsText:'name',optionsValue:'id',value: regionsSelected"></select>
<select data-bind="options: cities,optionsText:'name',optionsValue:'id',value:citiesSelected"></select>
</div>
<div>
<div>
<div>
<div>
<span data-bind="text: citiesSelected().name"></span>
</div>
</div>
</div>
</div>
Viewmodel
function getProvinces(provinces) {
$.ajax({
url: "resturl1",
type: "POST",
success: function (data) {
var dats = JSON.parse(data);
provinces(dats);
return provinces;
}
});}function getRegionsByProvince(regions, province) {
$.ajax({
url: "resturl2",
type: "POST",
data: {province: province},
success: function (data) {
var dats = JSON.parse(data);
regions(dats);
return regions;
}
});}function getCitiesByRegion(cities, region) {
$.ajax({
url: "resturl3",
type: "POST",
data: {region: region},
success: function (data) {
var dats = JSON.parse(data);
cities(dats);
return cities;
}
});}
function AppViewModel() {
var self = this;
self.provinces = ko.observableArray();
self.provincesSelected = ko.observable();
self.regions = ko.observableArray([]);
self.regionsSelected = ko.observable();
self.cities = ko.observableArray([]);
self.citiesSelected = ko.observable();
self.provinces(getProvinces(self.provinces));
self.provincesSelected.subscribe(function (val) {
self.regions(getRegionsByProvince(self.regions, val));
});
self.regionsSelected.subscribe(function (val) {
self.cities(getCitiesByRegion(self.cities, val));
});
}ko.applyBindings(new AppViewModel());
This is the last thing I tried (get a "name" property that comes in the array of objects from the server response), but the console throws the error:
Uncaught TypeError: Unable to process binding "text: function (){return citiesSelected().name }"
Message: Cannot read property 'name' of undefined
Not using the property in the div data-bind shows the value, but I need the text.
I'm quite new at Knockout, so maybe I'm making a huge newbie mistake, but I would apreciate a lot your help.

If you remove the below, it works:
optionsValue:'id'
When you set the optionsValue attribute, you are telling Knockout to set selectedCities equal to the id of the selected city. That is why you got the error stating that the name property doesn't exist. You were essentially trying to bind to selectedCities.id.name which definitely doesn't exist. By removing the optionsValue attribute, you will be capturing the entire selected city object, and the binding will evaluate the name property as expected.
Working fiddle: https://jsfiddle.net/dw1284/dqzb3zwn/1/

Related

How can ajax based Select2 pre-population be formatted?

We've found several examples of pre-populating selected option for Select2, however none of them we could find deal with formatted list and selection options. We have a JS fiddle at https://jsfiddle.net/gpsx62de/26/ that illustrates the issue. In that fiddle, you can type and L or whatever into the select search and the data is returned, the list is formatted, and if you select something, the selection is formatted.
However if you click the button in that JS Fiddle which is intended to simulate pre-population per https://select2.org/programmatic-control/add-select-clear-items#preselecting-options-in-an-remotely-sourced-ajax-select2 the data is returned (you can uncomment the console.log to see it), but the formatted selection shows undefined for the intended values. Does anyone know of a way to get the formatted values for pre-populated data to display correctly?
// Set up the Select2 control
$('#mySelect2').select2({
ajax: {
url: '/api/students'
}
});
// Fetch the preselected item, and add to the control
var studentSelect = $('#mySelect2');
$.ajax({
type: 'GET',
url: '/api/students/s/' + studentId
}).then(function (data) {
// create the option and append to Select2
var option = new Option(data.full_name, data.id, true, true); //**** DOES IT MATTER WHAT IS PASSED HERE BECAUSE WE ARE NOT DISPLAY THE OPTION TEXT?? ***
studentSelect.append(option).trigger('change');
// manually trigger the `select2:select` event
studentSelect.trigger({
type: 'select2:select',
params: {
data: data //**** THIS DOES NOT SEEM TO SUPPORT FORMATTED SELECTIONS, SO HOW CAN THIS BE DONE? ***
}
});
});
The problem is in format_selection function. The format of the object it receives depends on how it was created. When you use new Option(text, value) it receives only the properties of this Option object, not your original object containing all user info.
A workaround is to check of either possible values in the fuction:
function format_selection(obj) {
let name = obj.name || obj.element.text;
let email = obj.email || obj.element.email;
return $(`<div><b>${name}</b></div><div>(${email})</div>`);
}
For this to work you should append the de property on you Option object:
var option = new Option(data.name, data.id, true, true);
option.email = data.email;
$('#sel').append(option).trigger('change');
The problem, in https://jsfiddle.net/gpsx62de/26/ is with the
function format_selection(obj) {
// Just add this to see the obj
console.log(obj);
return $(`<div><b>${obj.text}</b></div><div>(${obj.id})</div>`);
}
The obj object just contains the Option class data, so:
id: "1",
selected: true,
text: "Leanne Graham",
title: ""
So you have to find a way to pass "data.email" to the "format_selection" method
EDIT
This could be a solution
$('#btn').on('click', function() {
$.ajax({
type: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/1'
})
.then(function(data) {
console.log(data)
// create the option and append to Select2
$('#sel').append($('<option />') // Create new <option> element
.val(data.id) // Set value
.text(data.name) // Set textContent
.prop('selected', true)
.attr('data-name', data.name) // Don't know why the .data(key, value) isn't working...
.attr('data-email', data.email))
.trigger('change');
}); //then
}); //click
And
function format_selection(obj) {
return $(`<div><b>${obj.element.dataset.name}</b></div><div>(${obj.element.dataset.email})</div>`);
}
This is the fiddle https://jsfiddle.net/947jngtu/

mdb_autocomplete drop down does not close on click event

In certain cases, usually after values have been cached from an initial search, I cannot get the mdb_autocomplete drop down to close on a single click. I often have to double click on my selection to get it to close. I am dynamically populating the values in the drop down via an Ajax call to a service method via and controller action method that searches the active directory for the first 30 name values. No call back occurs until a user enters at least 3 values into the mdb_autocomplete selection box. The callback and population of the drop down is working very well, but the click event following the selection of a value often will not close the dropdown. Additionally, we are being forced to use mdb boostrap over the normal bootstrap library. I have included the view, service, and typescript code that is being used to generate and populate the drop down.
View:
<div class="form-group">
<label class="font-weight-bold" for="RequestorSearchInput">Requestor</label>
<input type="text" id="RequestorSearchInput" name="RequestorSearchInput" class="form-control mdb-autocomplete" />
<span id="loading_data_icon"></span>
</div>
Service Method:
public List<string> GetRequestor(string aRequestor)
{
IEnumerable<Dictionary<string, string>> requestorNames;
using (ActiveDirectoryService service = new ActiveDirectoryService(<LDAP Domain Name>))
{
string[] propertiesToLoad = new string[] { "displayName" };
//requestorNames = service.SearchByPartialUsername(aRequestor, propertiesToLoad).Take(30).Union(service.SearchBySurname(aRequestor, propertiesToLoad).Take(30));
requestorNames = service.SearchBySurname(aRequestor, propertiesToLoad).Take(30);
}
return requestorNames.SelectMany(x => x.Values).Where(y => y.IndexOf(",", StringComparison.Ordinal) >= 0).Distinct().ToList();
}
TypeScript:
private handleRequestorSearch() {
let options: string[] = new Array();
// #ts-ignore
let searchval = $("#RequestorSearchInput").val().length;
if (searchval >= 3) {
$.ajax({
url: main.getControllerHREF("GetRequestor", "Search"),
method: "POST",
data: {
requestor: $("#RequestorSearchInput").val()
},
async: false,
dataType: "json",
success: function (names) {
$.each(names, function () {
options.push(this);
});
// #ts-ignore
$("#RequestorSearchInput").mdb_autocomplete({
data: options
}).select(function () {
$("#RequestorSearchInput").focus;
});
}
});
}
}

How to add content via ajax using the popover boostrap

I tried to view different sources and also looked into the forums posting similar question, but it didnt quite help me with the issue that im facing.
I have a text input filed to which I'm adding a popover to show similar a list of names in the database. The inout field checks for validation, to see if the name entered is unique, if not it displays similar names available in the database that could be re-used.
here is the popover snippet:
$("#account_name_create").popover({
title: 'Twitter Bootstrap Popover',
content: function (process) {
this.accountCollection = new ipiadmin.collections.AccountCollection();
var newName = $("#new-account-form #account_name_create").val();
var userFilter = "accountName~'" + newName + "'";
this.accountCollection.fetch({
data: { "f": userFilter,
"sortby": null,
"type":"ipi",
"pageno":0,
"pagesize":2,
"reversesort" : true
},
cache: false,
success: function(model, response, options) {
var states = [];
map = {};
$.each(model.aDataSet, function (i, state) {
map[state.accountName] = state;
states.push(state.accountName);
});
process(states); //gives an error saying 'undefined is not a function (says process is undefined)'
},
error: function(model, response, options) {
console.log('error');
}
});
},
});
here is the html:
<input type="text" id="account_name_create" name="account_name" class="" size="40" />
I'm not sure how why it says 'process' as undefined. Also not sure if this would be the correct way of displaying the data in the popover.
Any ideas??
Thanks!
process doesn't have scope in the success function, only in the content function. If you want to call the process function from within the success function, you could define it somewhere outside of the jQuery call.

Having issues tying together basic javascript chat page

I have the skeleton of a chat page but am having issues tying it all together. What I'm trying to do is have messages sent to the server whenever the user clicks send, and also, for the messages shown to update every 3 seconds. Any insights, tips, or general comments would be much appreciated.
Issues right now:
When I fetch, I append the <ul class="messages"></ul> but don't want to reappend messages I've already fetched.
Make sure my chatSend is working correctly but if I run chatSend, then chatFetch, I don't retrieve the message I sent.
var input1 = document.getElementById('input1'), sendbutton = document.getElementById('sendbutton');
function IsEmpty(){
if (input1.value){
sendbutton.removeAttribute('disabled');
} else {
sendbutton.setAttribute('disabled', '');
}
}
input1.onkeyup = IsEmpty;
function chatFetch(){
$.ajax({
url: "https://api.parse.com/1/classes/chats",
dataType: "json",
method: "GET",
success: function(data){
$(".messages").clear();
for(var key in data) {
for(var i in data[key]){
console.log(data[key][i])
$(".messages").append("<li>"+data[key][i].text+"</li>");
}
}
}
})
}
function chatSend(){
$.ajax({
type: "POST",
url: "https://api.parse.com/1/classes/chats",
data: JSON.stringify({text: $('input1.draft').val()}),
success:function(message){
}
})
}
chatFetch();
$("#sendbutton").on('click',chatSend());
This seems like a pretty good project for Knockout.js, especially if you want to make sure you're not re-appending messages you've already sent. Since the library was meant in no small part for that sort of thing, I think it would make sense to leverage it to its full potential. So let's say that your API already takes care of limiting how many messages have come back, searching for the right messages, etc., and focus strictly on the UI. We can start with our Javascript view model of a chat message...
function IM(msg) {
var self = this;
self.username = ko.observable();
self.message = ko.observable();
self.timestamp = ko.observable();
}
This is taking a few liberties and assuming that you get back an IM object which has the name of the user sending the message, and the content, as well as a timestamp for the message. Probably not too far fetched to hope you have access to these data elements, right? Moving on to the large view model encapsulating your IMs...
function vm() {
var self = this;
self.messages = ko.observableArray([]);
self.message = ko.observable(new IM());
self.setup = function () {
self.chatFetch();
self.message().username([user current username] || '');
};
self.chatFetch = function () {
$.getJSON("https://api.parse.com/1/classes/chats", function(results){
for(var key in data) {
// parse your incoming data to get whatever elements you
// can matching the IM view model here then assign it as
// per these examples as closely as possible
var im = new IM();
im.username(data[key][i].username || '');
im.message(data[key][i].message || '');
im.timestamp(data[key][i].message || '');
// the ([JSON data] || '') defaults the property to an
// empty strings so it fails gracefully when no data is
// available to assign to it
self.messages.push(im);
}
});
};
}
All right, so we have out Javascript models which will update the screen via bindings (more on that in a bit) and we're getting and populating data. But how do we update and send IMs? Well, remember that self.message object? We get to use it now.
function vm() {
// ... our setup and initial get code
self.chatSend = function () {
var data = {
'user': self.message().username(),
'text': self.message().message(),
'time': new Date()
};
$.post("https://api.parse.com/1/classes/chats", data, function(result) {
// do whatever you want with the results, if anything
});
// now we update our current messages and load new ones
self.chatFetch();
};
}
All right, so how do we keep track of all of this? Through the magic of bindings. Well, it's not magic, it's pretty intense Javascript inside Knockout.js that listens for changes and the updates the elements accordingly, but you don't have to worry about that. You can just worry about your HTML which should look like this...
<div id="chat">
<ul data-bind="foreach: messages">
<li>
<span data-bind="text: username"></span> :
<span data-bind="text: message"></span> [
<span data-bind="text: timestamp"></span> ]
</li>
</ul>
</div>
<div id="chatInput">
<input data-bind="value: message" type="text" placeholder="message..." />
<button data-bind="click: $root.chatSend()">Send</button>
<div>
Now for the final step to populate your bindings and keep them updated, is to call your view model and its methods...
$(document).ready(function () {
var imVM = new vm();
// perform your initial search and setup
imVM.setup();
// apply the bindings and hook it all together
ko.applyBindings(imVM.messages, $('#chat')[0]);
ko.applyBindings(imVM.message, $('#chatInput')[0]);
// and now update the form every three seconds
setInterval(function() { imVM.chatFetch(); }, 3000);
});
So this should give you a pretty decent start on a chat system in an HTML page. I'll leave the validation, styling, and prettifying as an exercise to the programmer...

ObservableArray not reflecting data update

I'm creating an app using the very slick KnockoutJS library, but I've run into a snag. On the html page, I have a plain <select> control that I want to load with JSON data returned from a web service.
I define the observable array as follows:
var laborRow = function () {
this.positions = ko.observableArray([]);
};
When the page loads, the ajax call is made and the data is returned. In the callback, I do the following:
success: function (msg) {
laborRow.positions = msg;
}
based on the KO docs, I would expect that I would set the result like this:
laborRow.positions(msg);
However, that just throws an error stating that "laborRow.positions in not a function"
The template in the html is as follows:
<tbody data-bind='template: {name: "laborRowTemplate", foreach: laborLine}'> </tbody>
</div>
<script type="text/html" id="laborRowTemplate">
<tr>
<td><select data-bind='options: positions, optionsText: "Title", optionsCaption: "select", value: selectedPosition '></select></td>
</tr>
</script>
The laborRow object is a property on the ViewModel which is bound to the page. For whatever reason, this does not work. To add another wrinkle, if I add code to peek into the observableArray and print out some piece of data, the data is in there. So it is being loaded successfully.
Any thoughts would be greatly appreciated.
The full code for my example case:
var laborRow = function () {
this.positions = ko.observableArray([]);
};
var projectEstimate = function () {
this.laborLine = ko.observableArray([new laborRow()]);
};
var projectViewModel = new projectEstimate();
ko.applyBindings(projectViewModel);
//and the code in the callback function on ajax success
success: function (msg) {
laborRow.positions = msg;
//laborRow.positions(msg); **this does not work - error is laborRow.positions is not a function**
},
And the html:
<tbody data-bind='template: {name: "laborRowTemplate", foreach:
laborLine}'> </tbody>
<script type="text/html" id="laborRowTemplate">
<tr>
<td><select data-bind='options: positions, optionsText:
"Title", optionsCaption: "select", value: selectedPosition '></
select></td>
</tr>
</script>
Finally, thanks to Sean's comments below, I was able to get it working by modifying the code in the callback as follows:
success: function (msg) {
projectViewModel.laborLine()[(projectViewModel.laborLine().length-1)].positionList(msg);
}
The problem is that you haven't actually created your model:
var laborRow = function () {
this.positions = ko.observableArray([]);
// will only be called if you call var some_var = new laborRow()
};
Change your function to a bare object (as shown in the Knockout docs):
var laborRow = {
positions: ko.observableArray([])
};
And you'll be able to call laborRow.positions(msg); and have it work.
EDIT
Based on the new code, laborRow is still not instantiated -- if you are setting var laborRow somewhere else in your code (around the ajax request, perhaps) then you'll want to make sure that your call stack looks like this:
projectViewModel.laborLine()[0].positions()
// This will return the array you're looking for.
// The key is that laborLine is a `getter` not an attribute
I've been bitten by the "ko variables are getters not attributes" bug on several occasions ... might that be happening with your code?

Categories