I am trying to make a Vue app that lists company offices based on regions. I have a main home view, an offices components, and an office item component. I am using v-for in the offices component to loop through the office items and display them. That works to list them all out. However, I need to sort the office items into separate divs based on the value of "Region". There are 5 regions. I cannot figure out how to loop through them based on that single value.
I know how to import components to one another, but I am trying to loop through all of the office items within the offices component. My guess is to do a loop within a loop, but do I need another component that I'm missing?
office item component:
<div class="office" :class="office.Region">
<p>{{office.Name}}</p>
<p>{{office.Address}}</p>
<p>{{office.Country}}</p>
<p>{{office.Region}}</p>
<p>{{office.Email}}</p>
<p>{{office.Phone}}</p>
</div>
offices component:
<div>
<div v-for="office in offices" :key="office.name">
<div class="office-container global" v-if="office.Region === 'Global'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container north" v-if="office.Region === 'North America'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container europe" v-if="office.Region === 'Europe, Middle East and Africa'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container asia" v-if="office.Region === 'Asia Pacific'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
<div class="office-container latin" v-if="office.Region === 'Latin America'">
<ul>
<li><OfficeItem v-bind:office="office"/></li>
</ul>
</div>
</div>
</div>
a hardcoded array of objects:
offices: [
{
Name: "Corporate Headquarters",
Address: "Suite 500, 698 West 10000 South, South Jordan, Utah 84095",
Country: "USA",
Region: "Global",
Email: "contact#ivanti.com",
Phone: "+1-888-253-6201"
},
{
Name: "EMEA Headquarters",
Address: "First Floor Europa House, Harcourt Street Dublin 2, D02 WR20",
Country: "Ireland",
Region: "Europe, Middle East and Africa",
Email: "contact#ivanti.me",
Phone: "+ 353 1 411 7100"
},
{
Name: "India",
Address: "Bagmane Tech Park, Unit No. 4A, Level 2 , Bangalore",
Country: "India",
Region: "Asia Pacific",
Email: "contact#ivanti.com",
Phone: ""
},
{
Name: "Brazil",
Address: "Borges de Figueiredo, 303 - 4th floor, Bairro Mooca, São Paulo, SP 03110-010",
Country: "Brazil",
Region: "Latin America",
Email: "contact-brazil#ivanti.com",
Phone: "+55 11 9 8136 0343"
},
{
Name: "United States (Seattle)",
Address: "1011 Western Ave SW #700, Seattle, WA 98104",
Country: "United States",
Region: "North America",
Email: "contact#ivanti.com",
Phone: "+1-206-274-4280"
}
]
I want there to be only 5 office-container divs with the list of corresponding offices in each one. however, I get multiple office-container (i.e. two north America divs) and multiple empty divs inside of those
[...new Set(this.offices.map(o => o.Region))] gives you the list of all your regions.
You can loop through this list and and display offices having that region, using a filtering method:
officesOfRegion(region) {
return this.offices.filter(o => o.Region === region)
},
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#hook',
template: '#appTemplate',
data: ({
offices: [{
Name: "Corporate Headquarters",
Address: "Suite 500, 698 West 10000 South, South Jordan, Utah 84095",
Country: "USA",
Region: "North America",
Email: "contact#ivanti.com",
Phone: "+1-888-253-6201"
},
{
Name: "EMEA Headquarters",
Address: "First Floor Europa House, Harcourt Street Dublin 2, D02 WR20",
Country: "Ireland",
Region: "Europe, Middle East and Africa",
Email: "contact#ivanti.me",
Phone: "+ 353 1 411 7100"
},
{
Name: "India",
Address: "Bagmane Tech Park, Unit No. 4A, Level 2 , Bangalore",
Country: "India",
Region: "Asia Pacific",
Email: "contact#ivanti.com",
Phone: ""
},
{
Name: "Brazil",
Address: "Borges de Figueiredo, 303 - 4th floor, Bairro Mooca, São Paulo, SP 03110-010",
Country: "Brazil",
Region: "Latin America",
Email: "contact-brazil#ivanti.com",
Phone: "+55 11 9 8136 0343"
},
{
Name: "United States (Seattle)",
Address: "1011 Western Ave SW #700, Seattle, WA 98104",
Country: "United States",
Region: "North America",
Email: "contact#ivanti.com",
Phone: "+1-206-274-4280"
}
]
}),
computed: {
regions() {
return [...new Set(this.offices.map(o => o.Region))]
}
},
methods: {
officesOfRegion(region) {
return this.offices.filter(o => o.Region === region)
},
displayJson(o) {
return JSON.stringify(o, null, 2);
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/template" id="appTemplate">
<div id="app">
<div class="region" v-for="region in regions" :key="region">
<hr>
<h3 v-text="region"></h3>
<ul>
<li v-for="(office, i) in officesOfRegion(region)" :key="i">
<pre v-html="displayJson(office)"></pre>
</li>
</ul>
</div>
</div>
</script>
<div id="hook"></div>
I didn't look at your markup, as it's irrelevant. You can use any markup you want once the data is properly sorted.
Here it is with your markup:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#hook',
template: '#appTemplate',
data: ({
offices: [{
Name: "Corporate Headquarters",
Address: "Suite 500, 698 West 10000 South, South Jordan, Utah 84095",
Country: "USA",
Region: "North America",
Email: "contact#ivanti.com",
Phone: "+1-888-253-6201"
},
{
Name: "EMEA Headquarters",
Address: "First Floor Europa House, Harcourt Street Dublin 2, D02 WR20",
Country: "Ireland",
Region: "Europe, Middle East and Africa",
Email: "contact#ivanti.me",
Phone: "+ 353 1 411 7100"
},
{
Name: "India",
Address: "Bagmane Tech Park, Unit No. 4A, Level 2 , Bangalore",
Country: "India",
Region: "Asia Pacific",
Email: "contact#ivanti.com",
Phone: ""
},
{
Name: "Brazil",
Address: "Borges de Figueiredo, 303 - 4th floor, Bairro Mooca, São Paulo, SP 03110-010",
Country: "Brazil",
Region: "Latin America",
Email: "contact-brazil#ivanti.com",
Phone: "+55 11 9 8136 0343"
},
{
Name: "United States (Seattle)",
Address: "1011 Western Ave SW #700, Seattle, WA 98104",
Country: "United States",
Region: "North America",
Email: "contact#ivanti.com",
Phone: "+1-206-274-4280"
}
]
}),
computed: {
regions() {
return [...new Set(this.offices.map(o => o.Region))]
}
},
methods: {
officesOfRegion(region) {
return this.offices.filter(o => o.Region === region)
},
propsOf(o) {
return Object.keys(o);
}
},
})
.office p {
display: flex;
}
.office p strong {
width: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/template" id="appTemplate">
<div id="app">
<div class="region" v-for="region in regions" :key="region">
<hr>
<hr>
<h3>{{region}}</h3>
<div v-for="(office, i) in officesOfRegion(region)" :key="i" class="office">
<hr>
<p v-for="prop in propsOf(office)"><strong>{{prop}}:</strong> {{office[prop]}}</p>
</div>
</div>
</div>
</script>
<div id="hook"></div>
It looks like the only thing changing in your template is the classes surrounding to the OfficeItem Component
To keep your code less DRY try applying that conditional logig within the OfficeItem Component like below.
// OfficeItem.vue
<template>
<li :class="['office-container', getRegionClass(office.Region)]">{{office.Region}}</li>
</template>
<script>
const regional_classes = {
A: 'class_a and-another-A-class',
B: 'class_b and-another-B-class',
C: 'class_c and-another-C-class',
D: 'class_d and-another-D-class',
Z: 'class_z and-another-Z-class'
}
export default {
name: "OfficeItem",
props: {
office: Object
},
methods: {
getRegionClass(region) {
return regional_classes[region] || ''
}
}
};
</script>
Alternatively, have a switch statement that takes the Region and returns a String of whatever case is met within the switch.
in this Scenario though i feel a regional_class Object is more readable/maintainable.
And in your Offices component, just pass the office object to your Officeitem like below
// Offices.vue
<template>
<div>
<ul :key="`${regionName}_${index}`" v-for="(region, regionName, index) in officesByRegion">
<h1>Region {{regionName}}</h1>
<OfficeItem v-for="office in region" :key="office.Region" :office="office"/>
</ul>
</div>
</template>
<script>
export default {
name: "Offices",
data() {
return {
offices: [
{ Region: "A" },
{ Region: "B" },
{ Region: "C" },
{ Region: "D" },
{ Region: "Z" },
{ Region: "A" },
{ Region: "B" },
{ Region: "A" },
{ Region: "B" },
{ Region: "A" },
{ Region: "Z" },
{ Region: "C" },
{ Region: "D" },
{ Region: "E" }
]
};
},
computed: {
officesByRegion() {
const obj = {};
this.offices.forEach(o => {
if (o.Region in obj) obj[o.Region].push(o);
else obj[o.Region] = [o];
});
return obj;
}
}
};
</script>
I Hope this helps. Or at least shine's some light on dynamic css class application. :-)
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>
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);
I have this code in my JS
var customer = {
name: "John Jack",
speak: function(){
return "my name is "+name;
},
address:{
street: '123 main st',
city: 'Pittsburgh',
state: 'PA'
}
}
document.write(customer.speak());
In my HTML i expected
my name is John Jack
But instead i got something really weird
my name is Peaks mirroring in a lake below, Stubai Alps, Austria
I have some theories that this is somehow connected to the Chrome extension i'm using called "Pixlr",but i don't see how my js code could connect to that.I tried changing variable name and speak to say,but it still prints the same thing.What's wrong?
replace name with this.name
var customer = {
name: "John Jack",
speak: function() {
return "my name is " + this.name;
},
address: {
street: '123 main st',
city: 'Pittsburgh',
state: 'PA'
}
}
document.write(customer.speak());
I have a mustache file, and I am iterating over the array:
var data = {
sales: [
{
name: "Jim Frost",
region: "USA East",
phone: "212-555-1212",
email: "jfrost#acme-travel.com"
},
{
name: "Jan Smith",
region: "USA West",
phone: "310-555-1212",
},
{
name: "Fred Wesley",
phone: "608-555-1212",
email: "fwesley#acme-travel.com"
},
{
name: "Lisa Moore",
region: "USA South",
phone: "315-555-1212",
email: "lmoore#acme-travel.com"
},
{
name: "Jim Dio",
phone: "+ 44 022-555-1212",
email: "jdio#acme-travel.com"
},
{
name: "Charles Watts",
region: "Spain",
email: "cwatts#acme-travel.com"
},
{
name: "Bert Cooper",
region: "Italy",
email: "bcooper#acme-travel.com"
}
]
};
here is the markup:
<div>
<section>
{{#data.sales}}
<article class="items">
<div class="region">{{{region}}}</div>
</article>
{{/data.sales}}
</section>
</div>
I want to add some special style (like, bold font, color etc) ONLY if the region is USA East.
how can i detect inside this inherent loop in the article element if {{{region}} has a specific value? Given that the comparison will be made against a value that i get from backend, say {{myValue}}, which is manually set to USA East in the backend.
You can add a function in the data which will return the correct class depending on the region value. Something like
data['regionClass'] = function(){
if ( this['region'] == 'USA East' ) {
return "strong green";
}else{
return "";
}
}
And then in the Mustache you can do: <div class="region {{regionClass}}">{{{region}}}</div>