AngularJS: ng-repeat across multilple divs - javascript

I would like to have part of my website be a collage of images and information as shown below:
I can do this manually as a series of divs:
<div class="options">
<div class="option">
<img class="opt_img" src="images/hotels/BBH.jpg">
<h3 class="centered" id="hotel_info">Bailbrook House Hotel<br>BA1 7JD<br>Approx Price: £xx</h3>
</div>
<div class="option">
<img class="opt_img" src="images/hotels/bath_central_travelodge.jpg">
<h3 class="centered" id="hotel_info">Bath Central Travelodge<br>BA1 2EB<br>Approx Price: £xx </h3>
</div>
<div class="option">
<img class="opt_img" src="images/hotels/bath_waterside_travelodge.jpg">
<h3 class="centered" id="hotel_info">Bath Waterside Travelodge<br>BA2 4JP<br>Approx Price: £xx</h3>
</div>
</div>
<div class="options">
<div class="option">
<img class="opt_img" src="images/hotels/Bath_premier_inn.jpg">
<h3 class="centered" id="hotel_info">Bath City Centre Premier Inn<br>BA1 2BX<br>Approx Price: £xx</h3>
</div>
<div class="option">
<img class="opt_img" src="images/hotels/YHA.jpg">
<h3 class="centered" id="hotel_info">YHA Bath<br>BA2 6LA <br>Approx Price: £xx </h3>
</div>
<div class="option">
<img class="opt_img" src="images/hotels/bailbrook_lodge.jpg">
<h3 class="centered" id="hotel_info">Bailbrook Lodge<br>BA1 7HZ<br>Approx Price: £xx</h3>
</div>
and the following CSS:
*{
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif
}
#hotel_info{
background-color: rgba(130, 130, 130,0.7);
width: 80%;
}
.centered {
position: absolute;
width: 100%;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
}
.options{
width: 100%;
overflow: hidden;
}
.option {
position: relative;
text-align: center;
width: 33%;
float:left;
}
.opt_img{
width: 100%;
opacity: 0.7;
}
I have now started playing with AngularJS as a way to more easily display this information.
I have MainController.js
app.controller('MainController', function($scope){
$scope.title = 'Hotels';
$scope.hotels = [
{
name: 'Bailbrook House Hotel',
postcode: "BA1 7JD",
price: 0.00,
website: "https://www.handpickedhotels.co.uk/bailbrookhouse",
img: "images/hotels/BBH.jpg"
},
{
name: "Bath Central Travelodge",
postcode: "BA1 2EB",
price: 0.00,
website: "https://www.travelodge.co.uk/hotels/75/Bath-Central-hotel",
img: "images/hotels/bath_central_travelodge.jpg"
},
{
name: "Bath Waterside Travelodge",
postcode: "BA2 4JP",
price: 0.00,
website: "https://www.travelodge.co.uk/hotels/361/Bath-Waterside-hotel",
img: "images/hotels/bath_waterside_travelodge.jpg"
},
//... [and so on]
]
});
I have then used
<div ng-controller="MainController">
<div class = "options" ng-repeat="hotel in hotels">
<div class = "option">
<a ng-href={{hotel.website}}><img class="opt_img" ng-src={{hotel.img}}></a>
<h3 class="centered" id="hotel_info">{{hotel.name}}<br>{{hotel.postcode | uppercase}}<br>Approx Price: {{hotel.price | currency:'£'}}</h3>
</div>
</div>
</div>
This puts all the options one underneath each other. I can see why this is, as it doesn't create the options div around each set of 3 as I had done when making it manually. Is it possible to achieve this with the ng-repeat or do I need to rethink how I am doing it?
I am very new to web development so please make your answers beginner friendly, and bear with me if I have to ask for clarification. I am also open to people suggesting a better or simpler way of doing things, I merely used Codecademy's AngularJS course as a starting point.

This can easily be achieved by using css flex property
<div ng-controller="MainController" class="hotel-container">
<div class = "options" ng-repeat="hotel in hotels">
<div class = "option">
<a ng-href={{hotel.website}}><img class="opt_img" ng-src={{hotel.img}}></a>
<h3 class="centered" id="hotel_info">{{hotel.name}}<br>{{hotel.postcode | uppercase}}<br>Approx Price: {{hotel.price | currency:'£'}}</h3>
</div>
</div>
</div>
.hotel-container {
display: flex;
}
.options {
width : 33.33%;
}
Read more flex property here : https://css-tricks.com/almanac/properties/f/flex/

JS solution
A really simple solution is to group your list into sublists of 3 elements:
$scope.hotels = [
[{
name: 'Bailbrook House Hotel',
postcode: "BA1 7JD",
price: 0.00,
website: "https://www.handpickedhotels.co.uk/bailbrookhouse",
img: "images/hotels/BBH.jpg"
}, {
name: "Bath Central Travelodge",
postcode: "BA1 2EB",
price: 0.00,
website: "https://www.travelodge.co.uk/hotels/75/Bath-Central-hotel",
img: "images/hotels/bath_central_travelodge.jpg"
}, {
name: "Bath Waterside Travelodge",
postcode: "BA2 4JP",
price: 0.00,
website: "https://www.travelodge.co.uk/hotels/361/Bath-Waterside-hotel",
img: "images/hotels/bath_waterside_travelodge.jpg"
}],
//...
]
and then just add one more ng-repeat directive
<div ng-controller="MainController">
<div ng-repeat="group in hotels">
<div class = "options" ng-repeat="hotel in group">
<div class = "option">
<a ng-href={{hotel.website}}><img class="opt_img" ng-src={{hotel.img}}></a>
<h3 class="centered" id="hotel_info">{{hotel.name}}<br>{{hotel.postcode | uppercase}}<br>Approx Price: {{hotel.price | currency:'£'}}</h3>
</div>
</div>
</div>
</div>
CSS solution
Another simple solution with CSS is to use flex-direction: row and then set the flex property accordingly on the elements. Here's a code pen using the bootstrap library.

Related

how to create milky overlay in vue js

I'm working with Vue 3 and Bootstrap 5.
I have a button and two inputs. When I click the button I want to have a "milky" overlay like following:
How can I achieve this?
Code to work with:
<template>
<div class="row">
<button class="btn btn-dark" #click="overlayMilky()">Button</button>
</div>
<div class="row mt-2">
<div class="col-12">
<span>Input 2</span>
<input class="form-control" />
</div>
<div class="col-12">
<span>Input 3</span>
<input class="form-control" />
</div>
</div>
</template>
<script>
export default {
methods: {
overlayMilky() {
//set overlay to milky
}
}
}
</script>
First, you need to surround the inputs with a container element and give this element position: relative and add to it a child which will be the overlay, this should have position: absolute to be absolute to the container element, also should have width: 100%; height: 100%; top: 0px; left: 0px; to take the full size of the container element and then conditionally with v-if you can show/hide it with a state
<div v-if="showOverlay" class="overlay"></div>
methods: {
overlayMilky() {
this.showOverlay = !this.showOverlay;
//set overlay to milky
},
},
This is a full example of code.
<template>
<div>
<div class="row">
<button class="btn btn-dark" #click="overlayMilky()">Button</button>
</div>
<div class="container">
<div v-if="showOverlay" class="overlay"></div>
<div class="row mt-2">
<div class="col-12">
<span>Input 2</span>
<input class="form-control" />
</div>
<div class="col-12">
<span>Input 3</span>
<input class="form-control" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showOverlay: false,
};
},
methods: {
overlayMilky() {
this.showOverlay = !this.showOverlay;
//set overlay to milky
},
},
};
</script>
<style>
.container {
position: relative;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
background-color: rgba(255, 255, 255, 0.4);
}
</style>
You need to add a data field that will describe the "milky" overlay state.
So all you need to do in the overlayMilky method is to set this.milkyOverlay = true.
Then use this milkyOverlay property to add the milky css class or show milky div on top.

How to dynamically set an Image object src in javascript and Vue

I'm trying to teach myself javascript and Vue.js. I was following the documentation on Vue's site and modifying their demonstrations as an exercise. I wanted to change their looping directive example to dynamically add images to the list from a specified url. I can't seem to get the image to show despite setting the image properties src field. I have verified that everything runs and the field is in fact getting set. I assume I must be misunderstanding something related to the DOM or ordering of events.
Thanks in advance.
HTML
<script src="https://unpkg.com/vue#next"></script>
<div id="list-rendering" class="demo">
<input v-model="imgsrc"></input>
<button v-on:click="setImage"> Set</button>
<ol>
<li v-for="todo in todos">
{{ todo.text }} {{todo.image}}
</li>
</ol>
</div>
CSS
.demo {
font-family: sans-serif;
border: 1px solid #eee;
border-radius: 2px;
padding: 20px 30px;
margin-top: 1em;
margin-bottom: 40px;
user-select: none;
overflow-x: auto;
}
Javascript
const ListRenderingApp = {
data() {
return {
todos: [
{ text: 'Learn JavaScript',
image: new Image(16,16)},
{ text: 'Learn Vue',
image: new Image(16, 16)},
{ text: 'Build something awesome',
image: new Image(16, 16)}
],
imgsrc: ""
}
},
methods:{
setImage(){
this.todos.map(todo => todo.image.src = this.imgsrc)
}
}
}
Vue.createApp(ListRenderingApp).mount('#list-rendering')
When using v-for make sure to add :key. Docs
Also pay attention to your html elements. <img> & <input>.
Also return the result of .map() to this.todos. Docs
new Vue({
el: "#app",
data: {
imgSrc: 'https://via.placeholder.com/150/FF0000',
todos: [
{ text: "Learn JavaScript", image: 'https://via.placeholder.com/150/0000FF' },
{ text: "Learn Vue", image: 'https://via.placeholder.com/150/0000FF' },
{ text: "Play around in JSFiddle", image: 'https://via.placeholder.com/150/0000FF' },
{ text: "Build something awesome", image: 'https://via.placeholder.com/150/0000FF' }
]
},
methods: {
setImage: function(todo){
this.todos = this.todos.map(todo => ({ ...todo, image: this.imgSrc }))
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<small>image url</small>
<input v-model="imgSrc" />
</div>
<br />
<div>
<button #click="setImage()">
Click to set Image
</button>
</div>
<ol>
<li v-for="(todo, i) in todos" :key="i">
<label>
{{ todo.text }}
</label>
<img :src="todo.image" alt="img" width="100" height="100">
</li>
</ol>
</div>
try running this snippet & click the "Click to set Image"
try this.
<div id="list-rendering" class="demo">
<input v-model="imgsrc"></input>
<button v-on:click="setImage"> Set</button>
<ol>
<li v-for="(todo, index) in todos :key="index"">
{{ todo.text }}
<img :src="todo.image"/>
</li>
</ol>
</div>

How to loop through a block of Div element N number of times?

I am trying to display Cards next to one another (4 cards per row). Here is my my html for a Card:
<div class="HelloWorldCard">
<div class="cardwithlink">
<div class="row">
<div class="Hellocard cardwithlink style="height: 170px;">
<a href="//www.https://www.google.com/" title="Google home page" target="">
<div class="content">
<div class="HelloTopSection" style="height: 110px;">
<div class="HelloCardTitle">{{ title }}</div>
<div class="HelloCardExcerpt">{{ description }}</div>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<Script>
export default{
name: "HelloWordCard",
props:{
title: String,
description: String
},
};
I have about 100 cards that I want to display on my page. I can't just copy and past this html 100 times since that would be a waste of time. Is it possible to print out this block of html in a loop 100 times ?
The real issue I am having is that the cards are displaying 1 card on each row. I am trying to get them to display 4 on each row.
Your row should not be inside your card component.
It should be in a parent component holding the card, where you can apply #Srijan Katuwal's CSS. For example:
<template>
<div id="app">
<div class="row">
<HelloWorldCard
v-for="index in 100"
:key="index"
title="Test"
description="Test description"
/>
</div>
</div>
</template>
<script>
import HelloWorldCard from "./components/HelloWorldCard";
export default {
name: "App",
components: {
HelloWorldCard,
},
};
</script>
<style>
.row {
display: grid;
grid-template-columns: repeat(4, auto);
gap: 30px;
}
</style>
Your component now is:
<template>
<div class="Hellocard cardwithlink" style="height: 170px">
<a href="//www.https://www.google.com/" title="Google home page" target="">
<div class="content">
<div class="HelloTopSection" style="height: 110px">
<div class="HelloCardTitle">{{ title }}</div>
<div class="HelloCardExcerpt">{{ description }}</div>
</div>
</div>
</a>
</div>
</template>
<script>
export default {
name: "HelloWordCard",
props: {
title: String,
description: String,
},
};
</script>
You can see it in action here: https://codesandbox.io/s/hungry-hodgkin-2sklq?file=/src/App.vue:0-476
You can use v-for directive to show a div block for n number of times.
Vue offical documentation has similar example v-for
Also, to display 4 cards in a single row, you can use CSS grid or flex. A grid implementation can be done as below
.row {
display: grid;
grid-template-columns: repeat(4, auto);
gap: 30px;
}
You need to write following CSS to display cards next to each other
* {
box-sizing: border-box;
}
.row {
display: block;
width: 100%;
}
.card {
display: block;
float: left;
width: 25%;
padding: 10px;
border: 1px solid grey;
}
<div class="row">
<div class="card">Card Data</div>
<div class="card">Card Data</div>
<div class="card">Card Data</div>
<div class="card">Card Data</div>
<div class="card">Card Data</div>
<div class="card">Card Data</div>
</div>

Vue.js send data to a child component and display them

Hi I'm beginning with vue.js and I don't understand why my infos are not sent to the child component. I tried several differents things and it's not working. I think I'm not that far but I can't figure it out :
App.vue
<template>
<div class="app">
<Header/>
<div class="row">
<div class="col-3">
<Services v-bind:services="services"></Services>
</div>
</div>
</div>
</template>
<script>
import Header from "./components/Header.vue";
import Services from "#/components/Service";
export default {
nam: 'App',
components: {
Services,
Header,
},
data: function () {
return {
services: [{
title: "Logo flatdesign",
description: "Je fais de super flatdesign",
image: "https://fiverr-res.cloudinary.com/t_gig_cards_web_x2,q_auto,f_auto/gigs/22527722/original/3b5876ffb817872561d9eba6788dced76cb78224.jpg",
price: 6.7,
rate: 4,
id:1
},{
title: "Logo rapide",
description: "Je fais vite un logo",
image: "https://fiverr-res.cloudinary.com/t_gig_cards_web_x2,q_auto,f_auto/gigs/142024147/original/2abc0f9433df4f98790707a591772e56bf8777a1.jpg",
price: 5.5,
rate: 3,
id:2
},{
title: "Logo flatdesign",
description: "Je fais de super flatdesign",
image: "https://fiverr-res.cloudinary.com/t_gig_cards_web_x2,q_auto,f_auto/gigs/22527722/original/3b5876ffb817872561d9eba6788dced76cb78224.jpg",
price: 6.7,
rate: 4,
id:3
}]
}
}
}
</script>
<style>
</style>
Service.vue
<template>
<div class="home">
<div class="row">
<div class="col-3" v-for="service in services" v-bind:key="service.id">
<div class="jumbotron">
<img :src="service.image" height="100%" width="100%">
<h4>
{{service.title}}
</h4>
<div class="row">
<div class="col rate">
{{service.rate}} ★
</div>
<div class="col price">
{{service.price}} €
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
</script>
<style scoped>
.rate{
color: yellow;
}
.price{
font-weight: bold;
}
</style>
Thank you so much for your help :) !
Add this to your child component's script to define the passed prop before using it:
export default {
name: "Service",
props: ['services']
};

Angular click event: how to select a checkbox within an a element?

In my Angular app I have the following menu:
As you can see I have items (that are a elements) and a checkbox and a label in each of them:
<span class="caption">RAM</span>
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden">
<label>4 GB</label>
</div>
</a>
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden">
<label>8 GB</label>
</div>
</a>
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden">
<label>16 GB</label>
</div>
</a>
How should I add (click) to every item to correctly handle event capturing so if user click on label or on whole item I get the related checkbox selected?
...Or do you know a better way to reach what I mean?
To make sure that clicking on the label toggles the checkbox, include the input element inside of the label (as explained in MDN):
<a class="sub-item item">
<div class="item-checkbox">
<label><input type="checkbox" tabindex="0" class="hidden">4 GB</label>
</div>
</a>
If you also want the label to fill the anchor element, define the CSS as shown below. With this styling in place, clicking on the anchor will toggle the checkbox.
.ui.secondary.menu a.item {
padding: 0px;
}
div.item-checkbox {
width: 100%;
height: 100%;
}
div.item-checkbox > label {
display: block;
padding: .78571429em .92857143em;
}
div.item-checkbox > label > input {
margin-right: 0.25rem;
}
See this stackblitz for a demo.
If you can place all the checkboxes in a container, you can set a single click event listener on the container, and event.target will give you the clicked element and previousElementSibling will select the sibling input.
function doSomething() {
const selectedInput = event.target.previousElementSibling;
selectedInput.checked = selectedInput.checked? false : true;
}
But in case there is a probability that you document structure changes in the future, say for example, if other elements get in between the input and the label, or their order changes, then the sibling selector will fail. To solve this you can use a parent selector instead and select the chechbox input element inside of it.
document.getElementById('container').addEventListener('click', doSomething);
function doSomething() {
const selectedElement = event.target.parentElement.querySelector('input[type="checkbox"]');
selectedElement.checked = selectedElement.checked? false:true;
}
<div id= 'container'>
<span class="caption">RAM</span>
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden" value="4 GB">
<label>4 GB</label>
</div>
</a>
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden" value="8 GB">
<label>8 GB</label>
</div>
</a>
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden" value="16 GB">
<label>16 GB</label>
</div>
</a>
</div>
First of all you should use *ngFor to list all your options:
html:
<a class="item" *ngFor="let choice of checks; let i=index">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden" [value]="choice.value" (change)="onCheckChange($event)">
<label>{{choice.title}}</label>
</div>
</a>
component:
public checks: Array<choices> = [
{title: '4 GB', value: '4'},
{title: "8 GB", value: '8'},
{title: "16 GB", value: '16'}
];
public onCheckChange(event) {
// do some things here
}
Further, you should use Reactive Forms to validate your choices:
https://angular.io/guide/reactive-forms
You have to create a boolean array and bind this to your checkboxes.
.ts file
myArray: any[] = [
{
"size": "2GB",
"value":false
},
{
"size": "4GB",
"value":false
},
{
"size": "8GB",
"value":false
}
]
.html file
<div *ngFor="let data of myArray">
<a class="item">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden" [(ngModel)]="data.value" (ngModelChange)="changeHandler()">
<label>{{data.size}}</label>
</div>
</a>
</div>
Working demo : link
You could do something like that:
const ramOptions = [
{
id: 1,
size: '4 GB'
},
{
id: 2,
size: '8 GB'
},
{
id: 3,
size: '16 GB'
}
]
And in html:
<a class="item" *ngFor="let option of ramOptions">
<div class="item-checkbox">
<input type="checkbox" tabindex="0" class="hidden" (change)="onSelect(option.id)">
<label>{{option.size}}</label>
</div>
</a>
If in the .js/.ts file, you can create an interface to define the handler. Just as:
onSelect(id: string) {
...
}

Categories