I am using a modal to display information about a selected park. In this modal, I display things such as park name, address and user given tags.
HTML Modal code
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="park-img" src="">
</div>
<hr>
<!-- section containing park details -->
<div class="park-details">
<p id="park-address">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-geo-alt" viewBox="0 0 16 16">
<path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A31.493 31.493 0 0 1 8 14.58a31.481 31.481 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94zM8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10z"/>
<path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
</p>
<div id="park-opening">
</div>
<div>
<table class="timetable table">
<thead class="thead">
<tr>
<th scope="col">Weekdays</th>
<th scope="col">Opening Hours</th>
</tr>
</thead>
<tbody>
<tr>
<td>Monday</td>
<td id="day1"></td>
</tr>
<tr>
<td>Tuesday</td>
<td id="day2"></td>
</tr>
<tr>
<td>Wednesday</td>
<td id="day3"></td>
</tr>
<tr>
<td>Thursday</td>
<td id="day4"></td>
</tr>
<tr>
<td>Friday</td>
<td id="day5"></td>
</tr>
<tr>
<td>Saturday</td>
<td id="day6"></td>
</tr>
<tr>
<td>Sunday</td>
<td id="day7"></td>
</tr>
</tbody>
</table>
</div>
<hr>
<div class="fs-4">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tag" viewBox="0 0 16 16">
<path d="M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0z"/>
<path d="M2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1zm0 5.586 7 7L13.586 9l-7-7H2v4.586z"/>
</svg>
Tags
</div>
<div id="tags-p" class="mb-3 mt-2">
<p>Check all user given tags to this park or add your own.</p>
<p>You can also upvote tags, by clicking on them.</p>
</div>
<div id="park-tags">
</div>
<div id="tags-input">
<input id="tags-input-form" type="text" class="form-control mt-2">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
Add to Favourites
</button>
</div>
</div>
</div>
JS code
function showModal() {
const marker = this;
places.getDetails(
{ placeId: marker.placeResult.place_id },
(place, status) => {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
return;
}
// display tags
$('div#park-tags').empty();
var place_id = place['place_id'];
fetch(`/tags/${place_id}`)
.then(response => response.json())
.then(park_tags => {
park_tags.forEach(park => {
possible_colors = [
'primary', 'secondary', 'success',
'danger', 'warning', 'info'
];
var i = Math.floor(Math.random() * (7 - 1) + 1);
var tagSpan = document.createElement("button");
tagSpan.setAttribute("class", `btn btn-${possible_colors[i]} position-relative`);
tagSpan.setAttribute("id", `park_tag_${park["id"]}`);
// number of tag upvotes
var tagUps = document.createElement("span");
tagUps.setAttribute("class", "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-success");
tagSpan.innerHTML = park['tag'];
tagUps.innerHTML = park['upvotes'];
tagSpan.appendChild(tagUps);
document.getElementById('park-tags').appendChild(tagSpan);
// if user clicks button -> upvotes tag
const selected_tag = document.getElementById(`park_tag_${park["id"]}`);
selected_tag.addEventListener('click', () => {
// update DB table
fetch(`/sancheck/upvote_tag/${park["id"]}`, {
method: 'PUT'
})
.then(response => response.json())
.then(response => {
console.log(response);
// display changes immediately
if (response['message'] == 'Upvote removed.') {
// decrease count by 1
tagUps.innerHTML = response['upvotes'];
//TODO: if num_upvotes = 0, remove tag from display
selected_tag.remove();
} else {
// increase count by 1
tagUps.innerHTML = response['upvotes'];
}
});
});
});
});
// add new tag
var input = document.getElementById('tags-input-form');
input.value = "";
input.addEventListener('keypress', createTag);
function createTag(e){
if (e.key === 'Enter') {
// remove wanted spaces
let tag = e.target.value.trim();
console.log(tag);
if (tag.length > 1) {
tag.split(',').forEach(tag => {
console.log(place_id, tag);
// save new tags to DB
fetch(`/sancheck/create_tag/${place_id}/${tag}`, {
method: 'PUT'
})
.then(response => response.json())
.then(response => {
var tag_exists = response['message'] == 'Tag already exists.';
if (!tag_exists) {
//TODO: display changes immediately
possible_colors = [
'primary', 'secondary', 'success',
'danger', 'warning', 'info'
];
var i = Math.floor(Math.random() * (7 - 1) + 1);
var tagSpan = document.createElement("button");
tagSpan.setAttribute("class", `btn btn-${possible_colors[i]} position-relative`);
tagSpan.setAttribute("id", `park_tag_${response["new_id"]}`);
// number of tag upvotes
var tagUps = document.createElement("span");
tagUps.setAttribute("class", "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-success");
tagSpan.innerHTML = tag;
tagUps.innerHTML = 1;
tagSpan.appendChild(tagUps);
document.getElementById('park-tags').appendChild(tagSpan);
}
});
});
}
}
}
}
);
}
Problems found:
When I first open a modal I can correctly create a tag and remove them. After I create a tag (modal still open), if I try to remove nothing happens.
After opening a second modal, if I create a tag it automatically creates multiple (it's creating for all modals I've opened). I can't remove tags in this case.
Please help me fix this issue.
Related
I'm having issues loading two modals (openModalEdit and openModalDetail method) on my Angular 9 project. When I open it, it automaticly navigates to the root route.
I have another instance (openModalCreate method) of a modal in the same component, apparently both are the same changing only a couple of parameters like the modal title, but the first navigates and the other stays in the modal.
You get to see the modal appearing just before the navigation moves to the root route and the OnInit method of the modal component doesn't have any code, so the modal component doesn't have any functionality that can provoke the navigation in any point.
My bootstrap installed version is "#ng-bootstrap/ng-bootstrap": "^6.0.3".
Does anyone know how to prevent navigation on NgbModal load?
Codebehind:
emitIconButtonClick (action, i, content) {
switch (action) {
case 'edit':
this.openModalEdit(i);
break;
case 'delete':
this.onDeleteRow(i);
break;
case 'detail':
this.openModalDetail(i, content);
break;
default:
break;
}
}
openModalCreate () {
this._formsService.editing = false;
const modalRef = this.modalService.open(DynamicModalComponent, {
size: 'lg',
});
modalRef.componentInstance.title = 'Nuevo ' + this.config.label;
modalRef.componentInstance.fields = this.config.fields;
modalRef.componentInstance.close.subscribe(() => {
modalRef.close();
});
modalRef.componentInstance.save.subscribe((event) => {
this._formsService.setSavedStatusForm(false);
this.rows.push(event);
this.bindToForm();
modalRef.close();
});
}
openModalEdit (index: number) {
const modalRef = this.modalService.open(DynamicModalComponent, {
size: 'lg',
});
modalRef.componentInstance.title = 'Editar ' + this.config.label;
modalRef.componentInstance.fields = this.config.fields;
modalRef.componentInstance.data = this.rows[index];
modalRef.componentInstance.close.subscribe(() => {
modalRef.close();
});
modalRef.componentInstance.save.subscribe((event) => {
this.rows[index] = event;
this._formsService.setSavedStatusForm(false);
this.bindToForm();
modalRef.close();
});
}
openModalDetail (i: number, content: any) {
this.detailArray = [];
Object.entries(this.rows[i]).forEach((e) => {
const entry = {
name: e[0],
value: e[1],
};
this.detailArray.push(entry);
});
this.modalService.open(content).result.then(
(result) => { debugger },
(reason) => { debugger }
);
}
HTML
<div class="form-group dynamic-group field" [formGroup]="group">
<div class="add-btn">
<app-button (click)="openModalCreate()" clasesBtn="btn-primary" icono="plus-circle"> </app-button>
</div>
<div [attr.id]="config.name" [attr.name]="config.name" class="master-table">
<table class="table table-striped">
<thead>
<tr>
<th *ngFor="let header of config.fields" scope="col">
{{ (header.header ? header.header : header.name) | translate }}
</th>
<th *ngIf="config.actions">
{{ 'actions' | translate }}
</th>
</tr>
</thead>
<tbody [ngSwitch]="true">
<tr *ngFor="let row of rows; index as i">
<td *ngFor="let header of config.fields">
<div class="ellipsis max-width-cell">
{{ showDataTable(row[header?.name], header.name) }}
</div>
</td>
<td *ngIf="config.actions">
<div class="table-body_row_actions">
<a *ngFor="let action of config.actions" href="" (click)="emitIconButtonClick(action.name, i, content)" [ngClass]="{
'table-body_row_actions-container': true,
delete: action.name === 'delete'
}">
<i-feather name="{{ action.icon }}" class="feather-icon"></i-feather>
</a>
</div>
</td>
<ng-template #content let-modal>
<div class="modal-header">
<h4 class="modal-title">
{{ 'detail' | translate }}
</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body custom-modal-body">
<div class="flex-container">
<div class="dataCell" *ngFor="let field of detailArray">
<div class="header">
{{ field.name | translate }}
</div>
<div class="data">
{{ showDataTable(field.value, field.name) }}
</div>
</div>
</div>
</div>
</ng-template>
</tr>
</tbody>
</table>
</div>
</div>
RESOLVED by #zainhassan
--> Remove href="" from a tag
I'm creating two modals on a single page and I'm having problem on how can I make it work. I just copied the example here and I converted it into a function like so:
function isDialogOpen() {
return {
modal: false,
open() { this.modal = true;document.body.classList.add("modal-open"); },
close() { this.modal = false;document.body.classList.remove("modal-open"); },
isOpen() { return this.modal === true },
}
}
These two modals is wrapped inside <main> element.
<main class="assignedquestion" x-data="isDialogOpen()" #keydown.escape="close">
I've tried to do it something like this:
<main class="assignedquestion" x-data="...isDialogOpen(), ...reassignDialog()" #keydown.escape="close">
And create another function with different variables and functions.
function reassignDialog() {
return {
reassignmodal: false,
openReassign() { this.reassignmodal = true;document.body.classList.add("modal-open"); },
closeReassign() { this.reassignmodal= false;document.body.classList.remove("modal-open"); },
isOpenReassign() { return this.reassignmodal=== true },
}
}
Unfortunately it doesn't work. Only the first modal is showing up.
These modal will be triggered by 2 different buttons.
Below is the markup of my modals:
First Modal
<!-- overlay -->
<div
class="overflow-auto"
style="background-color: rgba(0, 0, 0, 0.75);display:none"
x-show="isOpen()"
:class="{ 'user-history-modal': isOpen() }"
>
<!-- dialog -->
<div
class="bg-white shadow-2xl"
x-show="isOpen()"
#click.away="close"
>
<div class="modal-header">
<h3>Reassign Ticket</h3>
<button type="button" #click="close"><svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 1L1 13M1 1l12 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
</div>
<div class="modal-content">
</div>
</div><!-- /dialog -->
</div><!-- /overlay -->
Second Modal
<!-- overlay reassignDialog -->
<div
class="overflow-auto"
style="background-color: rgba(0, 0, 0, 0.75);display:none"
x-show="isOpenReassign()"
:class="{ 'reassign-history-modal': isOpenReassign() }"
>
<!-- dialog -->
<div
class="bg-white shadow-2xl"
x-show="isOpenReassign()"
#click.away="closeReassign"
>
<div class="modal-header">
<h3>Reassign Ticket</h3>
<button type="button" #click="closeReassign"><svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 1L1 13M1 1l12 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
</div>
<div class="modal-content">
</div>
</div><!-- /dialog -->
</div><!-- /overlay -->
The solution here is to make each modal a separate component and to dispatch custom events to them to open/close them. That way, they'll be separate and you'll be able to close them independently. If only one of the modals can be open at a time, you should add
#toggle-reassign-modal.window="close()" to the dialog modal (1st modal) and #toggle-modal.window="close()" to the reassign modal (2nd modal) so that they always close when the other modal is opened.
<div
x-data="isDialogOpen()"
#toggle-modal.window="modal = !modal"
x-init="$watch('modal', (val) => { if (val) document.body.classList.add('modal-open') } )"
>
<!-- contents of modal as per the question -->
</div>
<div
x-data="reassignDialog()"
#toggle-reassign-modal.window="reassignmodal = !reassignmodal"
x-init="$watch('reassignmodal', (val) => { if (val) document.body.classList.add('modal-open') } )"
>
<!-- contents of reassign modal as per the question -->
</div>
To trigger the modals to open you can use the following:
<!-- can remove x-data if these are inside of an Alpine component -->
<button x-data #click="$dispatch('toggle-modal')">Toggle Modal</button>
<button x-data #click="$dispatch('toggle-reassign-modal')">Toggle Reassign Modal</button>
You can see it working as a Codepen.
Modals (among other components) are now part of official AlpineJS documentation. Use them for perfect out of the box modals!
I have a rails app that creates a standardized card-box for every db record. It creates a button in each box so there are multiples of the same button class. I want to pass the text card-box to send an AJAX request. So each box has a different variable to send. Currently when I click any button it only passes the last element in the card-text array. I'll include a screenshot to show what I'm talking about. Below is the JavaScript snippet that handles the button click.
let track = document.getElementsByClassName('btn btn-sm btn-outline-dark');
let getPackages = document.getElementsByClassName('card-text');
let tNumber = "";
for(let i=0; i<track.length; i++){
track[i].addEventListener('click', function(){
for (let j=0; j<getPackages.length; j++){
console.log(i);
console.log(j);
if (i == j){
console.log(tNumber = getPackages[j].innerText);
tNumber = getPackages[j].innerText;
console.log(tNumber);
sendRequest(tNumber);
}
}
});
}
Entire card html:
<div class="col-md-4">
<div class="card mb-4 shadow-sm">
<svg class="bg-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="placeholder: Thumbnail">
<title>Package</title>
<rect width="100%" height="100%" fill="#55595c"></rect>
<image href="https://volumeintegration.com/wp-content/uploads/PackageIcon.png" width="100%" height="225"/>
</svg>
<div class="card-body">
<p class="card-text">
<%= package.tracking %>
</p>
<div class="d-flex justify-content-between alight-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-dark" id="trackButton">Track</button>
<%= link_to 'Delete', package, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-sm btn-danger btn-outline-dark"%>
</div>
</div>
</div>
</div>
</div>
I figured it out I used the .closest() method then got the child
let track = document.getElementsByClassName('btn btn-sm btn-outline-dark');
let tNumber = "";
for(let i=0; i<track.length; i++){
track[i].addEventListener('click', function(){
let parent = track[i].closest('.card-body');
tNumber = parent.getElementsByClassName('card-text')[0].innerText;
console.log(tNumber);
sendRequest(tNumber);
});
}
I have a view which polls an endpoint every 30 seconds and updates if there are changes. In the same view I have a div which is shown/hidden every 7 seconds. The timers are started at the same time.
The first few times it runs, it runs perfectly. It's not until the first 30s timer polls again that it basically starts flickering back and forward every 2 seconds - which it shouldn't.
component.ts
getScreen(tapCode) {
timer(0, 30000)
.pipe(mergeMap(() => this.dataService.get_screen(tapCode)))
.subscribe(objectResp => {
this.callResp = objectResp.status;
this.polledObj = objectResp.items;
this.landing = false;
this.loaded = true;
this.unavailable = objectResp.items.unavailable;
localStorage.setItem('ontapCode', tapCode);
this.getAds(this.polledObj);
});
}
getAds(polledObj) {
this.showAd = false;
this.adTimer = timer(0, 7000);
this.adSubscription = this.adTimer.subscribe(() => {
if(this.timerCount >= this.polledObj.adverts.length) {
this.timerCount = 0;
}
if(this.timerCount <= this.polledObj.adverts.length) {
this.adImage = this.polledObj.adverts[this.timerCount].filename;
this.timerCount++;
}
this.showAd = !this.showAd;
});
}
component.html
<div *ngIf="callResp === 'OK'" class="tap">
<div *ngIf="unavailable === '1' || unavailable === 1" class="object-unavailable">
<img src="./assets/unavailable.png" class="unavailable-img">
</div>
<div *ngIf="unavailable === '0' || unavailable === 0">
<img src={{polledObj.img}} class="img-object">
<div class="container">
<div *ngIf="showAd === true">
<div class="advertisement">
<img src={{adImage}}" class="img-ad">
</div>
</div>
<div *ngIf="showAd === false">
<div class="object-detail-container">
<h3 (click)="resetStorage()" class="object-name text-white">{{polledObj.object_name}}</h3>
<h4 class="text-white object-brewery">{{polledObj.brewery}}</h4>
<h4 class="text-white object-style">{{polledObj.style}} - {{polledObj.abv}}%</h4>
<div class="object-sizes" *ngFor="let size of polledObj.sizes">
<span class="object-size-name">{{size.name}}: </span>
<span class="object-size-volume"> {{size.volume*1000}}ml </span>
<span class="object-size-price"> ${{getPrice(size.price)}} </span>
<span class="object-size-std"> {{getSizeStd(size)}} </span>
<span class="object-size-std-drinks"> std drinks</span>
</div>
</div>
</div>
</div>
</div>
So I'm trying to make a team comparison on my web app to compare their stats, the problem is that after I selected two teams then remove either one of the team, at first I succeed but when I try to remove the last one its doing nothing the last team logo is still showing up. Below is my code.
On the console it shows that selectedTeams values are undefined after remove-first and remove-second are clicked
undefined (2) [undefined, "TeamB", __ob__: Observer] 0
undefined (2) [undefined, undefined, __ob__: Observer] 1
Display Team Logo
<div class="col-md-6 first-selected">
<img id="firstteamlogo" :src="selectedTeams[0] | spacetodash | useLGLogo" v-if="selectedTeams[0] || selectedTeams[0] != undefined">
</div>
<div class="col-md-6 second-selected">
<img id="secondteamlogo" :src="selectedTeams[1] | spacetodash | useLGLogo" v-if="selectedTeams[1] || selectedTeams[1] != undefined">
</div>
Remove Team Logo
<div class="add-area">
<i class="fa fa-times remove-first" aria-hidden="true" v-if="selectedTeams[0]" v-on:click="removeTeams"></i>
<i class="fa fa-plus select-first" aria-hidden="true" v-else></i>
<span v-if="selectedTeams[0]">vs</span>
<span v-else>Comparison</span>
<i class="fa fa-times remove-second" aria-hidden="true" v-if="selectedTeams[1]" v-on:click="removeTeams"></i>
<i class="fa fa-plus select-second" aria-hidden="true" v-else></i>
</div>
Team Selection
<div class="team-selection" v-if="showTeamSelection">
<div class="team-row">
<div class="col-md-3" v-for="(team, index) in teams" v-if="index < 4">
<div class="team-logo">
<img class="team" :src="team.clubName | spacetodash | useMDLogo" :id="team.clubName | removespace" :data-team-name="team.clubName" :data-team-id="team.teamId" v-on:click="selectTeams">
</div>
</div>
</div>
<div class="team-row">
<div class="col-md-3" v-for="(team, index) in teams" v-if="index > 3">
<div class="team-logo">
<img class="team" :src="team.clubName | spacetodash | useMDLogo" :id="team.clubName | removespace" :data-team-name="team.clubName" :data-team-id="team.teamId" v-on:click="selectTeams">
</div>
</div>
</div>
</div>
VueJs Code
export default {
data: function(){
return {
teams: {},
isTeamsSelected: true,
isPlayersSelected: false,
showTeamSelection: true,
selectedTeams: [],
selectedPlayers: [],
}
},
mixins: [
filters,
methods
],
methods: {
selectTeams(e) {
if(this.selectedTeams.length < 2){
this.selectedTeams.push(e.target.dataset.teamName);
if(this.selectedTeams.length == 2){
this.showTeamSelection = false;
}
console.log(this.selectedTeams);
}
return false;
},
removeTeams(e) {
let removeTeam = e.target.classList.value;
this.showTeamSelection = true;
if(removeTeam.indexOf('remove-first') >= 0){
this.selectedTeams[0] = undefined;
console.log(this.selectedTeams[0], this.selectedTeams, 0);
}
if(removeTeam.indexOf('remove-second') >= 0){
this.selectedTeams[1] = undefined;
console.log(this.selectedTeams[1], this.selectedTeams, 1);
}
},
},
mounted: function() {
let self = this;
this.getCurrentSeasonTeams().then(function(response){
if( response.status == 200 && response.data.length > 0 ) {
self.teams = response.data;
}
});
}
}
Just pass the team you want to remove.
<i class="fa fa-times" aria-hidden="true" v-if="selectedTeams[0]" v-on:click="removeTeams(selectedTeams[0])"></i>
And change your removeTeam method.
removeTeams(team) {
this.selectedTeams.splice(this.selectedTeams.indexOf(team), 1)
this.showTeamSelection = true;
}