Create new KO.observable array and populate it from an array object - javascript

Is possible to create an ko.observable array and populate it using an array object?
My goal here is to create a ko.observable array with all the description/objects that are with the original array.
//Sample data the original data is coming from an socket query and being push on the array("people")
var people = [{
name: "Contact 1",
address: "1, a street, a town, a city, AB12 3CD",
tel: "0123456789",
email: "anemail#me.com",
type: "family"
},
{
name: "Contact 2",
address: "1, a street, a town, a city, AB12 3CD",
tel: "0123456789",
email: "anemail#me.com",
type: "friend"
}
];.
var people = [{
name: "Contact 1",
address: "1, a street, a town, a city, AB12 3CD",
tel: "0123456789",
email: "anemail#me.com",
type: "family"
},
{
name: "Contact 2",
address: "1, a street, a town, a city, AB12 3CD",
tel: "0123456789",
email: "anemail#me.com",
type: "friend"
}
];
var quotesarray = function(items) {
this.items = ko.observableArray(items);
this.itemToAdd = ko.observable("");
this.addItem = function() {
if (this.itemToAdd() != "") {
this.items.push(this.itemToAdd());
this.itemToAdd("");
}
}.bind(this);
};
ko.applyBindings(new quotesarray(people));
console.log(people);

You just needed to make it items instead of quotesarray
var people = [
{ name: "Contact 1", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "anemail#me.com", type: "family" },
{ name: "Contact 2", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "anemail#me.com", type: "friend" }
];
var quotesarray = function(items){
this.items = ko.observableArray(items);
this.itemToAdd = ko.observable("");
this.addItem = function(){
if (this.itemToAdd() != ""){
this.items.push(this.itemToAdd());
this.itemToAdd("");
}
}.bind(this);
};
ko.applyBindings(new quotesarray(people));
console.log(people);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr><th>name</th><th>address</th></tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: address"></td>
</tr>
</tbody>
</table>

You can create an observableArray to which the socket writes messages. You subscribe to the array to be automatically notified when the contents change (i.e. after every write by the socket).
In the subscribe callback you empty the array and add the items to your viewmodel's property.
If you expect to receive many rapidly succeeding messages, you can rateLimit the array to which you write to ensure you don't update the DOM too many times.
Here's an example. The explanations are in the code comments.
const UPDATE_EVERY_MS = 500;
// The observable array the socket writes to
const received = ko.observableArray([])
// Use a rateLimit extension if you expect to
// receive many updates from your socket
.extend({ rateLimit: UPDATE_EVERY_MS });
// The observable array in your viewmodel
const rendered = ko.observableArray([]);
received.subscribe(items => {
// Write "inbox" to viewmodel's list
rendered(rendered().concat(items));
// Clear received without triggering notification
items.length = 0;
});
ko.applyBindings({ items: rendered });
// Mock a socket that writes to `received`
setInterval(() => received.push(Math.random()), 200);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: rendered">
<li data-bind="text: $data"></li>
</ul>

Related

Trying to map elements from object of nested subarrays to a cell in an HTML table, unsure how to reach deepest sub arrays

I have a React/TypeScript component I'm building that features an HTML table for contact details.
I'm trying to map the API response to cells in the table and dynamically populate rows for each contact and their details. There are two arrays for phone numbers and addresses that are nested deep within the object and I can't figure out how to iterate over them along with the rest of the data all in one go.
I initially tried nested for loops but I hit a wall when I got to those two elements because of their position in the data object.
I then tried to use .map() in the middle of the for loops, but I hit a TypeScript error stating the element I'm trying to map over could possibly be null.
I thought about iterating over phone number and address arrays separately and then inserting them into the appropriate cells per contact but I can't figure out how to do when I'm using separate for loops to populate the other cells.
Expected Output:
Name | Member | Telephone | Email | Addresses
Ben B| Friend | 610-535-1234 | ben#gmail.com | 123 Fiction Drive,Denver
215-674-6789 234 Dreary Ln,Seattle
Alice | Family| 267-333-1234 | ally#aim.com | 437 Chance St, Pitts.
I made a CodeSandbox and dropped the current component and example data structure below. For the CodeSandbox it currently loads but as soon as you uncomment these lines you'll see the error
<td>{contacts.contactGroups[i].contacts[j].phoneNumbers}</td>
<td>{contacts.contactGroups[i].contacts[j].addresses}</td>
Current Component
import React from "react";
import { Contacts } from "./contact-types";
type Props = {
contacts: Contacts;
};
export const ContactsGrid = (props: Props) => {
const { contacts } = props;
const rows = [];
for (let i = 0; i < contacts.contactGroups.length; i++) {
rows.push(
<tr>
<td>{contacts.contactGroups[i].contactGroup}</td>
</tr>
);
for (let j = 0; j < contacts.contactGroups[i].contacts.length; j++) {
rows.push(
<tr>
<td>{contacts.contactGroups[i].contacts[j].fullName}</td>
<td>{contacts.contactGroups[i].contacts[j].member}</td>
{/* <td>{contacts.contactGroups[i].contacts[j].phoneNumbers}</td> */}
<td>{contacts.contactGroups[i].contacts[j].email}</td>
{/* <td>{contacts.contactGroups[i].contacts[j].addresses}</td> */}
</tr>
);
}
}
return (
<table>
<thead>
<tr>
<td>Name</td>
<td>Member Type</td>
<td>Telephone</td>
<td>Email</td>
<td>Address</td>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
};
Current Data Structure
export default {
count: 1,
contactGroups: [
{
contactGroup: "Family",
count: 1,
contacts: [
{
member: "Uncle",
fullName: "BENJAMIN BILLIARDS",
lastName: "BILLIARDS",
firstName: "BENJAMIN",
email: "shark#billiards.com",
phoneNumbers: [
{
telephoneNumber: "123-456-7899",
type: "mobile"
},
{
telephoneNumber: "610-555-7625",
type: "work"
}
],
addresses: [
{
addressLine1: "123 FAMILY ST",
addressLine2: "APT 1208",
city: "ATLANTA",
state: "GEORGIA",
zipCode: "12345"
},
{
addressLine1: "456 WORKING BLVD",
addressLine2: "",
city: "ATLANTA",
state: "GEORGIA",
zipCode: "12345"
}
]
}
]
},
{
contactGroup: "Friends",
count: 1,
contacts: [
{
member: "School Friend",
fullName: "HANS ZIMMER",
lastName: "ZIMMER",
firstName: "HANS",
email: "hans#pirates.com",
phoneNumbers: [
{
telephoneNumber: "267-455-1234",
type: "mobile"
}
],
addresses: [
{
addressLine1: "789 FRIEND ST",
addressLine2: "",
city: "SAN DIEGO",
state: "CALIFORNIA",
zipCode: "67890"
},
{
addressLine1: "234 CANARY ST",
addressLine2: "",
city: "SEATTLE",
state: "WASHINGTON",
zipCode: "67890"
}
]
}
]
}
]
};
Use a nested map:
const rows = contacts.contactGroups.map(group => <tr>
<td>{group.contactGroup}</td>
<td>
<table>
{group.contacts.map(contact => <tr>
<td>{contact.fullName}
</tr>}
</table>
</td>
</tr>;

data subscribed to Knockout ObservableArray but displaying empty array

I'm new to javascript. i'm having difficulty printing the data from the location ObservableArray. The data - bind works and i could list out the data from the location ObservableArray at the view but can't print it out on the console. i have been on it for hours now, any help would be appreciated. thank you
Here is the ViewModel
let MapViewModel = function() {
let map
let geocoder;
let self = this;
self.location = ko.observableArray([]);
for (let i = 0; i < locationList.length; ++i) {
self.location.push(new Location(locationList[i]));
}
console.log(this.location()); // Location, Location, Location, Location, Location, Location, Location]
console.log(this.location()[0].name); // Location {name: ƒ, address: ƒ} ...
console.log(this.location().length); //length is 7
}
let Location = function(data) {
this.name = ko.observable(data.name);
this.address = ko.observable(data.address);
}
ko.applyBindings(new MapViewModel());
Here is the Binding Code`
<div class="menu_item_container">
<h1>Neighborhood Map</h1>
<input type="text" id="search" data-bind= 'value:filterLocations, valueUpdate: 'afterKeyDown',value:filterLocations' placeholder="Search Locations...">
<hr>
<nav id=nav>
<ul data-bind='foreach:location'>
<li data-bind="text:name"></li>
</ul>
</nav>
</div>
LocationList
let locationList = [{
name: 'Brooklyn Museum',
address: '200 Eastern Pkwy, Brooklyn, NY 11238'
}, {
name: 'Empire State Building',
address: '350 5th Ave, New York, NY 10118'
}, {
name: 'Statue of liberty',
address: 'New York, NY 10004'
}, {
name: 'Rockefeller Center',
address: '45 Rockefeller Plaza, New York, NY 10111'
},
{
name: 'Brooklyn Bridge',
address: 'Brooklyn Bridge, New York, NY 10038'
},
{
name: 'Time Square',
address: '45 Rockefeller Plaza, New York, NY 10111'
},
{
name: 'World Trade Center',
address: '285 Fulton St, New York, NY 10007'
},
];
This can unwrap observable to regular js and convert this to single string (if needed) and then u can print it console :
let locationsJSON = ko.toJS(self.location);
let locationsString = JSON.stringify(locationsJSON);

JS: If statement to check if var exists in array

I have a AngularJS app where I have an array defined that has a group of dealers. Something like this:
$scope.dealers = [{
name: "Dealer Name",
address: "Address goes here",
website:"site.com",
lat: "latitude",
lng: "longitude"
territory: ['County1', 'County2', 'County3']
},
{
name: "Dealer Name",
address: "Address goes here",
website:"site.com",
lat: "latitude",
lng: "longitude",
territory: ['County1', 'County2', 'County3']
},
];
A user will input their zip code, and then using the Google Geocode API, I convert their zip code to lat/long coordinates and find their closest dealer based off of coordinates between them, and all of the dealers.
That is working fine.
Here is where I need help. Each dealer has a territory (in the array as counties) that needs to be checked first, before finding the closest dealer, because some dealers have counties in their territories that are actually geographically closer to another dealer.
I have a var that stores the users County based on their zip. So I need to make an IF statement that checks the userZip variable against the dealers array to see if that county exists anywhere in the array. If it does, then I need to return the name of that dealer. If it does not, I will have an ELSE statement that just runs the function I already have, which will just find the closest dealer to their location.
You can use Array.prototype.find()
let dealers = [{
name: "Dealer Name",
address: "Address goes here",
website: "site.com",
lat: "latitude",
lng: "longitude",
territory: ['County1', 'County2', 'County3']
},
{
name: "Dealer Name",
address: "Address goes here",
website: "site.com",
lat: "latitude",
lng: "longitude",
territory: ['County1', 'County2', 'County3']
},
];
let country = 'County2';
let found = dealers.find(d => d.territory.includes(country));
if(found)
console.log(found);
else
console.log("..find closest...");
//another case
country = 'NotAnywhere';
found = dealers.find(d => d.territory.includes(country));
if(found)
console.log(found);
else
console.log("..find closest...");

Javascript error trying to perform a search filter using Angular JS

In my app Im trying perform a search filter on names by partial match from the beginning. Heres an example of what Im trying to do:
Lets say I have a list of names:
Ben
James
Adam
Judy
Andy
and enter the text "a" in my search field, it would return
Adam
Andy
if I further enter "an" in my search field, it would return
Andy
In my app.js, I have the code:
var myApp = angular.module("myApp", []);
myApp.controller("myController", function ($scope) {
var employees = [
{ name: "Ben", gender: "Male", salary: 55000, city: "London" },
{ name: "Jane", gender: "Female", salary: 62000, city: "Albany" },
{ name: "Rick", gender: "Male", salary: 65000, city: "Los Angeles" },
{ name: "Pam", gender: "Female", salary: 60000, city: "Paris" },
{ name: "Josh", gender: "Male", salary: 68000, city: "Brussels" },
];
$scope.employees = employees;
$scope.filtered = function (item) {
if ($scope.searchName == undefined) {
return true;
} else {
if (item.name.toLowerCase().startsWith($scope.searchName.toLowerCase()) != -1) {
return true;
}
}
return false;
}
});
And in my html page, I have the following line which displays the list of employees:
<tr ng-repeat="employee in employees | filter: filtered">
And the following line which the user inputs the search text:
<input type="text" placeholder="Search Name" ng-model="searchName.name"> <br><br>
However, when I attempt to test this, I get the error:
Error: $scope.searchName.toLowerCase is not a function. (In '$scope.searchName.toLowerCase()', '$scope.searchName.toLowerCase' is undefined)
As your ng-model is set to searchName.name, $scope.searchNameis an object, therefore it has no .toLowerCase() function.
You need to adjust your if-case like this:
if (item.name.toLowerCase().startsWith($scope.searchName.name.toLowerCase()) !== -1) {}
Furthermore, it is advisable to use identity operators instead of equality operators, unless strict identity is explicitly not needed.
The ng-model is set to searchName.name, so you may need to call on $scope.searchName.name.toLowerCase() instead of $scope.searchName.toLowerCase()

Search by name and family but display account number in jquery autocomplete

I'm working on a piece of code which has used jquery ui autocomplete component in order filter searched items. I have to configure it in order to be available to search based on multi ple factors, here is my sample search array:
var availableTags = [{
name: "Smith",
family: "Johnson",
account_number: "10032",
nick_name: "Friend account",
}, {
name: "Susan",
family: "Rice",
account_number: "343243",
nick_name: "Mom Account",
}, {
name: "Joe",
family: "Austin",
account_number: "3434",
nick_name: "Partner Account",
}, {
}];
the auto complete should display name, family, account number and nick_name when displaying the suggestion box. but when each item is selected only the account_number must be inserted into the text box. user must also be able to search through name, family, account number and nick name all of them. How can i achieve this target?
You need to:
Revise the data array to contain the value parameter (this allows autocomplete to fill the input upon focus/select)
Write a custom source function that filters the data based on what user has typed
Write a custom _renderItem function that displays the data formatted to your liking
So you have:
var userData = [{
name: "Smith",
family: "Johnson",
value: "10032",
nick_name: "Friend account"
}, {
name: "Susan",
family: "Rice",
value: "343243",
nick_name: "Mom Account"
}, {
name: "Joe",
family: "Austin",
value: "3434",
nick_name: "Partner Account"
}];
$("#autocomplete").autocomplete({
source: function (request, response) {
var search = $.trim(request.term.toLowerCase());
var array = $.grep(userData, function (val) {
return
val.name.toLowerCase().indexOf(search) >= 0 ||
val.family.toLowerCase().indexOf(search) >= 0 ||
val.value.toLowerCase().indexOf(search) >= 0 ||
val.nick_name.toLowerCase().indexOf(search) >= 0;
});
response(array);
}
})
.data("ui-autocomplete")._renderItem = function (ul, item) {
var $a = $("<a></a>").text(item.name + " " + item.family);
$("<br />").appendTo($a);
$("<small></small>").text(item.nick_name).appendTo($a);
$("<br />").appendTo($a);
$("<small></small>").text(item.value).appendTo($a);
return $("<li></li>").append($a).appendTo(ul);
};
Demo here

Categories