Cascading dropdown in knockout.js - javascript

I'm trying to populate another dropdownlist from a dropdown list, i keep getting error "TypeError: Unable to process binding "value: function (){return CompanySelected }" and "http://localhost/xxx/api/Transaction/LoadInsurancePolicies/undefined 400 (Bad Request)". Insurance Policy must be populated when Insurance company is chosen. this is the code below
self.InsuranceCompanyId = ko.observable();
self._companySelected= ko.observable(null);
self.CompanySelected = ko.computed({
read: function () {return this._companySelected() },
write: function (value) {
$.ajax({
url: baseUrl + 'api/Transaction/LoadInsurancePolicies/' +
value.InsuranceCompanyId,
type: 'GET',
headers: { 'Access-Control-Allow-Origin': '*' },
dataType: 'json',
success: function (data) {
if (data.Successfull == 1)
{
self.AllPolicies(data.Model); } },
error: function (request, error) {
}
});
this._companySelected(value);
},
owner: this
});
self.AllInsuranceCompanies = ko.observableArray([]);
self.AllPolicies = ko.observableArray([]);
self.LoadInsuranceCompanies = function () {
$.ajax({
url: baseUrl + 'api/Transaction/LoadInsuranceCompanies',
type: 'GET',
headers: { 'Access-Control-Allow-Origin': '*' },
dataType: 'json',
success: function (data) {
// console.log(data);
if (data.Successfull == 1) {
self.AllInsuranceCompanies(data.Model);
console.log(data);
}
},
error: function (request, error) {
console.log(error);
}
});
}
self.LoadInsuranceCompanies();
this is my view
<div class="form-group" data-bind="visible:(InputOption()==0)">
<label for="InputTxt" class="control-label col-md-4">Insurance
Company</label>
<div class="col-md-8">
<select data-bind="options: AllInsuranceCompanies,
optionsText: 'Name',
optionsValue:'Id',
optionsCaption: 'Choose...',
value:CompanySelected,
valueUpdate:'change'" class="form-control">
</select>
</div>
</div>
<div class="form-group" data-bind="visible: (InputOption()==0)">
<label for="InputTxt" class="control-label col-md-
4">InsurancePolicy</label>
<div class="col-md-8">
<select data-bind="options: AllPolicies,
optionsText: 'Name',
optionsValue:'Id',
value: selectedPolicy,
optionsCaption: 'Choose...'" class="form-control">
</select>
</div>
</div>

The following are probably the problems in your code.
self.CompanySelected is defined before self.AllPolicies. This will cause to have a runtime error since ko.computed automatically runs when it is defined. This is based on knockout documentation. Solution: try defining all ko.observable before all ko.computed or atleast put self.AllPolicies before self.CompanySelected.
Since the ko.computed automatically runs, and the value of self.CompanySelected is undefined, you will also have an undefined InsuranceCompanyId in your api call and this will result in Bad request 400. Solution: try adding a guard before calling your api. if(value){....}
In your html bindings, you put optionsValue: 'Id'. This will result in knockout trying to find an Id property in your model which probably does not exist. Solution: remove optionsValue:'Id' from your bindings so that the value when changing option will be the model object itself and not just the Id.
Here is a sample fiddle: https://jsfiddle.net/przquhcf/1/ which implements the solutions above.
Note: I just substituted setTimeout for your api calls since i dont have access to them. Dont worry about this part.

Your solution gave me an idea.I passed a function(value) and the value will be the selected Id,add it to the api as a parameter and it gets me the results.
self.insuranceCompanyId = ko.observable('');
self.selectedPolicy = ko.observable();
self._companySelected = ko.observable();
self.CompanySelected = ko.pureComputed({
read: function () {
return this._companySelected()
},
write: function (value) {
console.log("inside write", value)
if (value) {
console.log('data');
$.ajax({
url: baseUrl + "api/Transaction/LoadInsurancePolicies/" +
value,
type: 'GET',
headers: { 'Access-Control-Allow-Origin': '*' },
dataType: 'json',
success: function (data) {
if (data.Successfull == 1) {
self.AllPolicies(data.Model);
console.log(value);
}
},
error: function (request, error) {
console.log(error);
}
});
this._companySelected(value);
}
},
owner: this
});
self.LoadInsuranceCompanies();

Related

Request gets overridden with other data

I'm trying to get data for autocomplete using laravel.
Controller:
public function collection_search(Request $request) {
$term = $request->search;
$serveurapObj = new Serveurap();
$result = $serveurapObj->collectionAutocomplete();
return response()->json($result);
}
Model:
public function collectionAutocomplete($term) {
$where= ['m.supprime'=>'0', 's.supprime'=>'0'];
return DB::table('serveuraps AS s')
->select(DB::raw('s.nom as hostname'))
->join('machines AS m','m.id','=','s.machine_id')
->join('typeserveurs AS t','t.id','=','m.typeserveur_id')
->where($where)
->where('hostname','like','%'.$term.'%')
->get();
}
View:
<div class="col-md-12">
<div class="form-group">
<label class="col-md-3 control-label">{!! __('script.serveur') !!}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="serveur" id="relance-serveur" role="textbox" aria-autocomplete="list" aria-haspopup="true">
</div>
</div>
</div>
Jquery/Ajax:
$(document).ready(function () {
// relance serveur autocomplete textbox
$('#relance-serveur').autocomplete({
source: function (request, response) {
alert(request.term)
$.ajax({
url: '/scripts/relanceCollection/collection',
dataType: 'json',
data: {
search: request.term
},
success: function (data) {
response(data);
}
});
},
});
});
I'm getting error when accessing the search from js in controller.
Error:
I printed $request but it showed the json data from model. how would I get the search from js to controller so that I can search data based on that term ?
The key you are sending is search, not term. Either change your controller code to reflect that, or change the ajax data.
$.ajax({
...
data: {
→ search: request.term
},
...
});
public function collection_search(Request $request)
{
$term = $request->search; ←
...
}
$.ajax({
...
data: {
→ term: request.term
},
...
});
public function collection_search(Request $request)
{
$term = $request->term; ←
...
}
You aliased the wrong class. You don't want an instance of a Facade, ever. If you want an instance of something you want the underlying instance, not the facade.
use Illuminate\Http\Request;
Now, $request would be an instance of that class and not the Facade, Illuminate\Support\Facades\Request.
I got the result by adding $.map function in success function in JS. if anyone needs it, please refer below js code:
$(document).ready(function () {
var headers = {
'X-CSRF-TOKEN':'<meta name="csrf-token" content="{{ csrf_token() }}">'
};
// relance serveur autocomplete textbox
$('#relance-serveur').autocomplete({
source: function (request, response) {
$.ajax({
url: '/scripts/relanceCollection/collection',
data: {
term: request.term
},
headers: headers,
dataType: 'json',
success: function (data) {
response($.map(data, function (item) {
return {
label: item.hostname,
value: item.hostname
};
}));
}
});
},
select: function (event, ui) {
$('#relance-serveur').val(ui.item.label); // display the selected text
return false;
},
minLength: 1
});
});

Set data of a Multiple Select2 element through javascript

I'm loading multiple values into a Select2 element through Javascript, ajax and jquery. While the data is loading properly and can be accessed once loaded, I'm unable to set stored data in the Select2 element.
Edit: I'm using Select2 v3.5.
My code:
HTML:
<input class="jsData" style="width: 100%" id="select2Data"></input>
Javascript:
$(".jsData").select2({
ajax: {
minimumInputLength: 4,
contentType: 'application/json',
url: '<%=Url.Action("GetData","Controller")%>',
type: 'POST',
dataType: 'json',
data: function (term) {
return {
sSearchTerm: term
};
},
results: function (data) {
return {
results: $.map(JSON.parse(data), function (item) {
return {
text: item.term,
slug: item.slug,
id: item.Id
}
})
};
}
},
multiple: true
});
So, this creates a Select2 element where I can traverse to and from a database and load data depending on what I've typed. I can also access the data (entered by the user) using the following line:
$('.jsData').select2('val')
The above line returns an array, which I can store in the database. My current objective is to set the stored data back into the Select2 element. Any help in this matter would be most welcome.
Update: A relevant link for what I want to accomplish:
https://select2.github.io/examples.html#programmatic
I want the example of setting multiple elements in Select2. However, the difference would be in the fact that the example in the Select2 documentation brings data at the time of loading the page, while I will be making trips to fetch the data.
The Select2 v3.5.4 docs are a bit confusing around this. I think there's a typo in the docs that's misleading.
First, notice that when I retrieve the data from the remote source I'm returning it as an object in the format of {id: ##, name: NAME}.
The first step is to add the initSelection parameter and pass the function to retrieve the previously selected items.
The next step, where I believe there's a typo, is to define the formatSelection parameter (not the formatResult it states in the docs). This function defines how the previously selected result is displayed. In this case I'm merely showing the name property of the result.
The formatResult parameter defines how newly selected options are displayed. You'll notice formatResult and formatSelection are the same below. I could have reused a single function but felt this was better for demonstration.
$(document).ready(function() {
function formatResult(data) {
return data.name;
};
function formatSelection(data) {
return data.name;
}
$(".jsData").select2({
ajax: {
minimumInputLength: 4,
url: "https://jsonplaceholder.typicode.com/users",
type: "GET",
dataType: "json",
results: function(data) {
return {
results: $.map(data, function(user) {
return {
name: user.name,
id: user.id
};
})
};
}
},
initSelection: function(element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax("https://jsonplaceholder.typicode.com/users/" + id, {
dataType: "json"
}).done(function(data) {
callback(data);
});
}
},
formatResult: formatResult,
formatSelection: formatSelection,
multiple: true
});
});
Here's the full working example:
$(document).ready(function() {
function formatResult(data) {
return data.name;
};
function formatSelection(data) {
return data.name;
}
$(".jsData").select2({
ajax: {
minimumInputLength: 4,
url: "https://jsonplaceholder.typicode.com/users",
type: "GET",
dataType: "json",
results: function(data) {
return {
results: $.map(data, function(user) {
return {
name: user.name,
id: user.id
};
})
};
}
},
initSelection: function(element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax("https://jsonplaceholder.typicode.com/users/" + id, {
dataType: "json"
}).done(function(data) {
callback(data);
});
}
},
formatResult: formatResult,
formatSelection: formatSelection,
multiple: true
});
});
.jsData {
width: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.css" rel="stylesheet"/>
<input class="jsData" style="width: 100%" id="select2Data" value="10"></input>

Add autocomplete input control as part of knockout view model

I have the following markup where any skills that exists as part of my view model are displayed. I have a button in my html that runs the self.create function in my view model to create a new item in the EmployeeSkillsArray IF the user decides to add a new skill. The input control below works as expected when the autocomplete portion is removed, however, my goal is to allow a user to begin typing a skill and if that skill exists allow them to select it from autocomplete. It is THIS autocomplete functionality that seems to be causing the issue within the EmployeeSkillsArray. I am able to see autocomplete working when an input control is placed OUTSIDE the array in my markup.
Is there a way to accomplish this goal of having an input control inside the foreach loop to display an item if it is in the array, otherwise allow the user to use autocomplete?
<tbody data-bind="foreach: EmployeeSkillsArray">
<tr>
<td class="col-xs-2">
<input type="hidden" data-bind="value: EmployeeSkillId, visible: false" />
<div class="input-group">
<input type="text" id="skillName" class="form-control" placeholder="Type a skill..." data-bind="value: SkillName, id: SkillsId, autoComplete: { selected: $root.selectedOption, options: $root.options }" /> <!-- corrected based on answer provided by f_martinez -->
</div>
</td>
</tr>
</tbody>
I am getting an Uncaught ReferenceError: Unable to process binding "autoComplete: function (){return { selected:selectedOption,options:options} }"
Message: selectedOption is not defined
I DO have selectionOption defined as part of my view model....
$(function () {
ko.bindingHandlers.autoComplete = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var settings = valueAccessor();
var selectedOption = settings.selected;
var options = settings.options;
var updateElementValueWithLabel = function (event, ui) {
event.preventDefault();
$(element).val(ui.item.label);
if(typeof ui.item !== "undefined") {
selectedOption(ui.item);
}
};
$(element).autocomplete({
source: options,
select: function (event, ui) {
updateElementValueWithLabel(event, ui);
},
focus: function (event, ui) {
updateElementValueWithLabel(event, ui);
},
change: function (event, ui) {
updateElementValueWithLabel(event, ui);
}
});
}
};
function ActivityViewModel() {
var self = this;
var remoteData;
$.ajax({
url: '#Url.Action("GetAllSkills", "EmployeeSkills")',
data: { },
async: false,
dataType: 'json',
type: 'GET',
contentType: "application/json; charset=utf-8",
success: function (data) {
remoteData = ($.map(data, function (item) {
return {
id: item.SkillId,
name: item.SkillName
};
}));
}
});
self.skills = remoteData;
self.selectedOption = ko.observable('');
self.options = ($.map(self.skills, function (element) {
return {
label: element.name,
value: element.id,
object: element
};
}));
var EmployeeSkill = {
EmployeeSkillId: self.EmployeeSkillId,
EmployeeId: self.EmployeeId,
SkillsId: self.SkillsId,
SkillName: self.SkillName,
SkillLevelId: self.SkillLevelId,
ApplicationUsed: self.ApplicationUsed,
selectedOption: self.selectedOption,
options: self.options
};
self.EmployeeSkill = ko.observable();
self.EmployeeSkillsArray = ko.observableArray();
$.ajax({
url: '#Url.Action("GetAllEmployeeSkills", "EmployeeSkills")',
cache: false,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON({ 'UserId' : employeeId, 'ActivityHistoryId' : activityHistoryId }),
success: function (data) {
self.EmployeeSkillsArray(data); // Put the response in ObservableArray
}
});
self.cloneRow = function () {
$.ajax({
url: '#Url.Action("AddSkill", "EmployeeSkills")',
cache: false,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: {},
success: function (data) {
self.EmployeeSkillsArray.push(data);
}
}).fail(function (xhr, textStatus, err) {
alert(err);
});
}
}
var viewModel = new ActivityViewModel();
ko.applyBindings(viewModel);
});

Binding options and optionsText in Select dropdown in knockout.js

I read everything here and different websites and don't understand my problem. Looks like it should work, but it doesn't. I am getting [Object object] as options.
Html
<select data-bind="options: Cities, optionsText: Cities.CityNameRu"></select>
Knockout
function CityModel(data) {
this.CityId = ko.observable(data.CityId);
this.CityNameRu = ko.observable(data.CityNameRu);
this.CityName = ko.observable(data.CityName);
}
function IndexModel() {
var self = this;
self.Cities = ko.observableArray([]);
self.GetCities = function () {
$.ajax({
type: "GET",
url: '/FetchCities',
dataType: "json",
success: function (data) {
self.SuccessfullyRetrievedModelsFromAjax(data);
},
error: function (err) {
alert(err.status + " : " + err.statusText);
}
});
};
this.SuccessfullyRetrievedModelsFromAjax = function (models) {
ko.utils.arrayForEach(models, function (model) {
self.Cities.push(new CityModel(model));
});
};
self.GetCities();
}
Json Response
[{"CityId":1,"CityName":"philadelphia","CityNameRu":"Филадельфия"},{"CityId":2,"CityName":"new-york","CityNameRu":"Нью Йорк"}
The value of optionsText is the string name of the property within each options array element to use for the text, so you should change your binding to:
<select data-bind="options: Cities, optionsText: 'CityNameRu'"></select>

Knockoutjs foreach n rows check if dropdown has value

I have this html markup:
<!-- ko foreach: Orders -->
<div class="row">
<div>
<select class="form-control" data-bind="attr: { id: 'prefix_' + $index() }, options: TeacherNames, optionsValue: 'TeacherId', optionsText: 'TeacherName', optionsCaption: 'Choose Teacher', event: { change: $root.teacherChanged }">
</select>
</div>
<div>
<a href='#' data-bind="click: $root.RequestImage" class="green-btn blue pull-right">
<span class="glyphicon glyphicon-cloud-download"></span> Download
</a>
</div>
</div>
<!-- /ko -->
There will be n number of items in the foreach loop, that will not be known in the moment of development.
What I want to do is when the $root.RequestImage is clicked, the code needs to check if there is selection made in the respected dropdown for that row, if the selection is made then proceed further, otherwise display alert box with 'error' message.
So in the RequestImage that action should happen, this is the RequestImage function currently:
self.RequestImage = function () {
};
How can I achieve this?
Update
OrdersVM:
var self = this;
self.Orders = ko.observableArray([]);
$.ajax({
type: "POST", url: "/webservices/InfoWS.asmx/GetOrders",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
if (data.d != null) {
var orderIds = [];
ko.utils.arrayForEach(data.d, function (item) {
item._teacherOrders = ko.observable();
$.ajax({
type: "POST",
url: "/webservices/InfoWS.asmx/GetTeachersForMyAccount",
contentType: "application/json; charset=utf-8",
data: "{'orderId': " + JSON.stringify(item.OrderId) + "}",
dataType: "json",
success: function (data) {
if (data) {
return item._teacherOrders(data.d);
}
},
error: function (n) {
alert('Error retrieving teachers for orders, please try again.');
}
});
item.TeacherNames = ko.computed(function () {
return item._teacherOrders();
});
self.Orders.push(item);
orderIds.push(item.OrderId);
});
}
},
error: function (data) {
var response = JSON.parse(data.responseText);
console.log("error retrieving orders:" + response.Message);
}
});
I would do it this way:
add an observable selectedTeacher to every order object
add value: selectedTeacher to your selects:
<select class="form-control" data-bind="attr: { id: 'prefix_' + $index() }, options: TeacherNames, optionsValue: 'TeacherId', ..., value: selectedTeacher"></select>
check that observable in your RequestImage event
if ( !data.selectedTeacher() ) {
alert('Error: select teacher')
} else {
alert('Success')
}
A working demo - Fiddle

Categories