Binding problems with complex data and autocomplete - javascript

I'm using Knockout JS 3.2 and I'd like to use it with autocomplete dropdown. I'm not able to get around two problems.
I simplified the data and code so this runs stand-alone:
<script type="text/javascript">
var data = [
{ "User": { "Id": 1, "DisplayName": "john a" }, "Roles": [{ "Id": 1, "Name": "admins" }, { "Id": 2, "Name": "users" }] },
{ "User": { "Id": 2, "DisplayName": "john b" }, "Roles": [] },
{ "User": { "Id": 3, "DisplayName": "john c" }, "Roles": [{ "Id": 1, "Name": "admins" }] },
{ "User": { "Id": 4, "DisplayName": "john d" }, "Roles": [] },
{ "User": { "Id": 5, "DisplayName": "john e" }, "Roles": [{ "Id": 2, "Name": "users" }] }
];
$(function () {
$("#searchTerm").autocomplete({
source: data,
minLength: 1,
select: function (event, ui) {
if (ui.item) {
var viewModel = ko.mapping.fromJS(ui.item);
ko.cleanNode($("#userDetails")[0]);
ko.applyBindings(viewModel, $("#userDetails")[0]);
}
}
})
.autocomplete("instance")._renderItem = function (ul, item) {
return $("<li>")
.append("<a>" + item.User.DisplayName + "</a>")
.appendTo(ul);
};
});
</script>
<div>Select User: <input id="searchTerm" name="searchTerm" type="text" /></div>
<div id="userDetails">
<div>User: <span data-bind="text: User.DisplayName"></span></div>
<div data-bind="foreach: Roles, visible: Roles().length > 0">
<div><span data-bind="text: Name"></span></div>
</div>
</div>
Problems:
I'd like to show the userDetails div only when it's bound -- hide it on page load. I tried setting style="display:none" and then data-bind="if:User" or data-bind="if:User.Id". Setting display attribute hides the element on load, but it doesn't change on bind.
Roles element binding doesn't work right. On first time that user is selected, the roles show, but they fail to show after changing the user selection.

Instead of always rebinding you need to have a proper view model with a selectedUser property and just update that one in the automcomplete handler.
var viewModel = {
selectedUser: ko.observable()
}
ko.applyBindings(viewModel, $("#userDetails")[0]);
$(function () {
$("#searchTerm").autocomplete({
source: data,
minLength: 1,
select: function (event, ui) {
if (ui.item) {
var user = ko.mapping.fromJS(ui.item);
viewModel.selectedUser(user);
}
}
})
.autocomplete("instance")._renderItem = function (ul, item) {
return $("<li>")
.append("<a>" + item.User.DisplayName + "</a>")
.appendTo(ul);
};
});
With this approach you can use the with binding and it will also solve both of your problems:
<div id="userDetails" data-bind="with: selectedUser">
<div>User: <span data-bind="text: User.DisplayName"></span></div>
<div data-bind="foreach: Roles, visible: Roles().length > 0">
<div><span data-bind="text: Name"></span></div>
</div>
</div>
Demo JSFiddle.

Related

How can I avoid options if those options are previously selected in vue js html?

My json data for getting all inventories is
{
"status": true,
"data": [
{ "inv_id": 1, "name": "Arts" },
{ "inv_id": 2, "name": "web" },
{ "inv_id": 3, "name": "mobileapp" },
{ "inv_id": 4, "name": "ws" },
{ "inv_id": 5, "name": "aop" },
{ "inv_id": 6, "name": "bin" },
{ "inv_id": 7, "name": "webs" },
{ "inv_id": 8, "name": "hell" }
]
}
My json data which is selected already by user will be in the following format
{
"data": {
"pid": 109,
"contact": {
"email": "ew98#gmail.com",
"phone": 85998472,
"address": { "country": "India", "state": "Kerala" }
},
"about": "hello how are you",
"is_featured": false,
"avg_rating": 0,
"total_reviews": 0,
"reviews": [],
"inventory": [
{
"item": {
"name": "Arts",
"category": { "name": "Education", "id": 1 },
"id": 1
}
}
],
"review": null
},
"status": true
}
Here arts is already selected, so I need to avoid the same when I am giving an edit option. How can I able to achieve the same.
mounted() {
var self = this
data = {}
data["auth-token"] = this.authType
data["pid"] = this.pid
$.ajax({
url: "http://127.0.0.1:8000/get/post/",
data: data,
type: "POST",
dataType: "json",
success: function(e) {
if (e.status == 1) {
self.inventory = e.data.inventory
data = {}
data["category"] = self.catid
data["cat_id"] = self.catid
$.ajax({
url: "http://127.0.0.1:8000/alpha/get/inventory/",
data: data,
type: "POST",
dataType: "JSON",
success: function(e) {
if (e.status == 1) {
self.inventoryall = e.data
}
},
})
}
},
})
}
I have all inventories in inventoryall[] and inventory that is already added in inventory[].
My html code is
<div class="form-group">
<label>Inventory
<small>(required)</small>
</label>
<select
id="basic"
class="selectpicker"
data-live-search="true"
data-live-search-style="begins"
title="Select Your Subcategory"
v-model="inv" name="inv[]" multiple required="required"
>
<option v-for="sop in inventoryall" v-bind:value="sop.inv_id">{{sop.name}}</option>
</select>
</div>
So, when I display the inventories here, I need to avoid the once that is already selected. Please help me to have a solution.
Here you can use the array filter method:
// filter loops through every item in the array and returns a new array
// if the filter function returns true, the item stays in the new array
// if the filter function returns false, the item is removed
self.inventoryall = self.inventoryall.filter(item => {
// loop through all of the currently selected items
for (const selectedItem of self.inventory) {
// if we find a current item with the same ID as the selected one,
// return false, so we don't keep the item
if (selectedItem.id === item.inv_id) {
return false
}
}
// if we never find an item with a matching ID, return true to keep it
return true
})
Note that this method is only available in browsers that support ES6, so use the polyfill on the MDN page if you need to support older browsers.
Another note, since you're using Vue, this would probably be a good use case for a computed property.

How to show values in suggestion instead of label jquery autocomplete and search should be process on the basic of label field

Here is the image of current working operation I am using jquery auto complete . But my problem is when i type item1 it shows item1 as suggestion. And i want to show when i type item1 is should be show zzz1 inside suggestion box.
My code is here.
<input name="jobCat" id="jobCat" value="" type="text" placeholder="Search category by keyword"/>
<script>
$(function() {
var datasource = [
{ "label": "item1", "value": "zzz1", "id": 1 },
{ "label": "item2", "value": "zzz2", "id": 2 },
{ "label": "item3", "value": "zzz3", "id": 3 }];
$("#jobCat").autocomplete({
source: datasource,
select: function (event, ui) { }
});
</script>
Edit, I found something usefull in the documentation... For having the values in the suggestions, instead of the labels.
var datasource = [
{ "label": "item1", "value": "zzz1", "id": 1 },
{ "label": "item2", "value": "zzz2", "id": 2 },
{ "label": "item3", "value": "zzz3", "id": 3 }];
$("#jobCat").autocomplete({
source: datasource,
}).autocomplete( "instance" )._renderItem = function( ul, item ) {
return $( "<li>" )
.append( "<div>" + item.value + " - ID: " + item.id + "</div>" )
.appendTo( ul );
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css" rel="stylesheet"/>
<input name="jobCat" id="jobCat" value="" type="text" placeholder="Search category by keyword"/>

How to filter a nested ko.observableArray from another array in knockout.js with checkboxes

I'm trying and failing to filter a list from another array with checkboxes. Essentially I have an array of jobs which inlcude an array of locations in each job. I have checkboxes of the options and have created an array of chosenLocations.
All of the above is working but I can't achieve the filtered list I require. The filtered list is displaying and is exactly the same as hiringManagerJobs, but the filtering isn't working.
I've posted my code below. Issue is in the viewModel.filteredJobs function.
KnockoutJs
var Search = function(){
var self = this;
self.hiringManagerJobs = ko.observableArray();
self.hiringManagerFilterSearchTerm = ko.observable();
self.hiringManagerFilterId = document.getElementById('hiringManagerId').value
self.LocationsFacets = ko.observableArray();
self.chosenLocations = ko.observableArray();
self.filteredJobs = ko.observableArray();
//self.filteredJobs = ko.observableArray();
searchByHiringManager = function () {
$.get('/...
}).done(function (data) {
self.hiringManagerJobs(data.Results);
self.LocationsFacets(data.Facets.Locations);
})
}
searchByHiringManager();
}
var viewModel = new Search();
viewModel.filteredJobs = ko.computed(function () {
var chosenLocations = ko.utils.arrayFilter(viewModel.LocationFacets,
function (p) {
return p.selected();
});
var jobs = viewModel.hiringManagerJobs();
if (chosenLocations.length == 0) //if none selected return all
return jobs;
else { //other wise only return selected jobs
return ko.utils.arrayFilter(jobs, function(job){
return ko.utils.arrayFilter(job.Locations, function(location){
return ko.utils.arrayFilter(chosenLocations,
function(chosenLocation) {
return location == chosenLocation.Value;
}).length > 0;
})
})
}
})
ko.applyBindings(viewModel);
HTML
<!-- ko foreach: LocationsFacets -->
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.chosenLocations" />
<span data-bind="text: Value"></span>
<!-- /ko -->
<ul class="list-group" data-bind="foreach: filteredJobs">
<li class="list-group-item" data-bind="with: Document">
<p class="job-title media-heading" data-bind="text: JobTitle"></p>
</li>
</ul>
The JSON Arrays in the knockout View Model are like below
"chosenLocations": [
{
"Type": 0,
"From": null,
"To": null,
"Value": "London",
"Count": 1
},
{
"Type": 0,
"From": null,
"To": null,
"Value": "Glasgow",
"Count": 1
}
],
"hiringManagerJobs": [
{
"Score": 1,
"Highlights": null,
"Document": {
"id": "1b41ce24-280d-4fe7-8488-d4babd522bc9",
"JobTitle": "Test HiringManagerFilterId",
"CompanyName": "Test Company",
"ExtUrl": "https://.....",
"Locations": [
"London",
"Manchester",
"New York"
],
"JobSummary": "blah blah blah",
"OgLogo": null,
"HiringManagerFilterId": "xjifu9fdasjkf985ed4"
}
},
{
"Score": 1,
"Highlights": null,
"Document": {
"id": "853880b3-fbae-4271-8034-7868c4de63a8",
"JobTitle": "Senior Manager - Software Development",
"CompanyName": "Test Company",
"ExtUrl": "https:......",
"Locations": [
"London",
"Glasgow",
"Edinburgh"
],
"JobSummary": "blah blah blah ",
"OgLogo": null,
"HiringManagerFilterId": "xjifu9fdasjkf985ed4"
}
}
],
"LocationsFacets": [
{
"Type": 0,
"From": null,
"To": null,
"Value": "Edinburgh",
"Count": 1
},
{
"Type": 0,
"From": null,
"To": null,
"Value": "Glasgow",
"Count": 1
},
{
"Type": 0,
"From": null,
"To": null,
"Value": "London",
"Count": 1
}
],
The main problem is that you're not referring to your data in the way that you've structured it. You've got a local definition of chosenLocations that seems to be left over from a previous iteration of the program. You are looking at job.Locations, but Locations is inside job.Document.
I've used arrayFirst as an analogue of Array.find, which is more like what you want inside the filter, rather than additional filters.
var Search = function() {
var self = this;
self.hiringManagerJobs = ko.observableArray();
self.hiringManagerFilterSearchTerm = ko.observable();
self.hiringManagerFilterId = 'xjifu9fdasjkf985ed4';
self.LocationsFacets = ko.observableArray();
self.chosenLocations = ko.observableArray();
self.filteredJobs = ko.observableArray();
//self.filteredJobs = ko.observableArray();
searchByHiringManager = function() {
self.hiringManagerJobs([{
"Score": 1,
"Highlights": null,
"Document": {
"id": "1b41ce24-280d-4fe7-8488-d4babd522bc9",
"JobTitle": "Test HiringManagerFilterId",
"CompanyName": "Test Company",
"ExtUrl": "https://.....",
"Locations": [
"London",
"Manchester",
"New York"
],
"JobSummary": "blah blah blah",
"OgLogo": null,
"HiringManagerFilterId": "xjifu9fdasjkf985ed4"
}
},
{
"Score": 1,
"Highlights": null,
"Document": {
"id": "853880b3-fbae-4271-8034-7868c4de63a8",
"JobTitle": "Senior Manager - Software Development",
"CompanyName": "Test Company",
"ExtUrl": "https:......",
"Locations": [
"London",
"Glasgow",
"Edinburgh"
],
"JobSummary": "blah blah blah ",
"OgLogo": null,
"HiringManagerFilterId": "xjifu9fdasjkf985ed4"
}
}
]);
self.LocationsFacets([{
"Type": 0,
"From": null,
"To": null,
"Value": "Edinburgh",
"Count": 1
},
{
"Type": 0,
"From": null,
"To": null,
"Value": "Glasgow",
"Count": 1
},
{
"Type": 0,
"From": null,
"To": null,
"Value": "London",
"Count": 1
}
]);
}
searchByHiringManager();
}
var viewModel = new Search();
viewModel.filteredJobs = ko.computed(function() {
const chosenLocations = viewModel.chosenLocations();
const jobs = viewModel.hiringManagerJobs();
if (chosenLocations.length == 0) //if none selected return all
return jobs;
else { //other wise only return selected jobs
const result = ko.utils.arrayFilter(jobs, function(job) {
return ko.utils.arrayFirst(job.Document.Locations, function (location) {
return ko.utils.arrayFirst(chosenLocations, function (cl) {
return cl.Value === location;
});
});
});
return result;
}
})
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko foreach: LocationsFacets -->
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.chosenLocations" />
<span data-bind="text: Value"></span>
<!-- /ko -->
<ul class="list-group" data-bind="foreach: filteredJobs">
<li class="list-group-item" data-bind="with: Document">
<p class="job-title media-heading" data-bind="text: JobTitle"></p>
</li>
</ul>
The self.filteredJobs = ko.observableArray(); is redundant, don't use viewModel.filteredJobs either.
instead directly in your model have the computed.
Note: self.LocationFacets() this is an observable. You had it as viewModel.LocationFacets.
self.filteredJobs = ko.computed(function () {
var chosenLocations = ko.utils.arrayFilter(self.LocationFacets(), function (p) {
return p.selected();
});
var jobs = self.hiringManagerJobs();
if (chosenLocations.length == 0) {
// if none selected return all
return jobs;
}
else {
// other wise only return selected jobs
return ko.utils.arrayFilter(jobs, function(job) {
return ko.utils.arrayFilter(job.Locations, function(location){
return ko.utils.arrayFilter(chosenLocations, function(chosenLocation) {
return location == chosenLocation.Value;
}).length > 0;
})
})
}
})
Note that you state filteredJobs is working, it is just that when you select a location facet it does not trigger the filteredJobs. The selected observable is updating but this would not trigger the filteredJobs to mutate, only a change to LocationFacets or hiringManagerJobs would update this.
If you are expecting the array to mutate on select of facet, you will need this:
self.selected.subscribe(function (newValue) {
if (viewModel != null)
viewModel.LocationFacets.valueHasMutated();
});

Angular Select. Add element and automatically select it

Look ex. jsfiddle example
HTML
<div ng-app="app" ng-controller="mCtrl">
<select size="20" ng-model="selectedType" ng-init="selectedType=types[0]"
ng-options="type.name for type in types">
</select>
<button ng-click="addElem()">add elem in array</button>
</div>
Controller
$scope.types = [ my Array of objects ];
$scope.addElem = function() {
element = {
"id": 999,
"name": "xxx"
};
$scope.types.push(element);
}
I want to add element in ng-options array, and automatically select it in select Box with model changing.
I tried
$scope.selectedType = $scope.types[$scope.types.lenght - 1]
, but it doesn't work.
You have misspelled length.
Here is solution which works:
app = angular.module('app', []);
app.controller('mCtrl', function($scope) {
$scope.types = [
{
"id": 0,
"name": "Zaj"
},
{
"id": 1,
"name": "Emoltra"
},
{
"id": 2,
"name": "Malathion"
},
{
"id": 3,
"name": "Pyramax"
},
{
"id": 4,
"name": "Boink"
},
{
"id": 5,
"name": "Savvy"
},
{
"id": 6,
"name": "Accruex"
},
{
"id": 7,
"name": "Zappix"
}
];
$scope.addElem = function() {
element = {
"id": 7,
"name": "Zappix"
};
$scope.types.push(element);
$scope.selectedType = $scope.types[$scope.types.length - 1];
}
})
DEMO
In your code, there is a typo you have misspelled length
$scope.selectedType = $scope.types[$scope.types.length- 1]
DEMO
OR
You can set it using $scope.selectedType = element
$scope.addElem = function () {
element = {
"id": $scope.types.length + 1,
"name": "Zappix" + $scope.types.length + 1
};
$scope.types.push(element);
$scope.selectedType = element;
}
DEMO

nested foreach with knockout binding?

My JSON object and observablarray are like :
self.myComplexsObject= ko.observableArray([{
"TupleArray": [{
"OptInfo": {
"Version": "B",
"Name": "csk_profile"
},
"Parameter": [{
"Value": "1",
"Name":"min SampleCopunt"
}]
},
{
"OptInfo": {
"Version": "A",
"Name": "Dml_profile"
},
"Parameter": [{
"Value": "2",
"Name":"min SampleCopunt"
}]
}]
}]);
and I want to access to Name and Version like below: But its not working Could you please help me??
<div data-bind='template: { foreach: myComplexsObject,
beforeRemove: hideElement,
afterAdd: showElement }'>
<div data-bind="foreach: OptInfo">
<div data-bind='attr: { "class": "complexObject" + Name,"title":Name},
text: Version'></div>
</div>
</div>
You can't access "OptInfo" this way because of the structure of your JSON. You are passing an array of one object to the observableArray. Here is the structure of this object:
{
"TupleArray":[
{
"OptInfo":{
"Version":"B",
"Name":"csk_profile"
},
"Parameter":[
{
"Value":"1",
"Name":"min SampleCopunt"
}
]
},
{
"OptInfo":{
"Version":"A",
"Name":"Dml_profile"
},
"Parameter":[
{
"Value":"2",
"Name":"min SampleCopunt"
}
]
}
]
}
So, this object, which becomes the context in your for each loop does not have a "OptInfo" property. The object only contains an array of two objects ("TupleArray").

Categories

Resources