Axios POST request reloads page even after e.preventDefault() : React - node.js

So Im building a dummy social media application for my resume with NodeJS, MongoDB and React. As such, I have a NewPost form in which user can attach videos or images as files and enter their post captions. I have used Multer on Backend and I have tested my route on Postman and BackEnd is working fine. The only problem is that my form wont send the data at all. I have tried e.preventDefault() but as soon as I make POST axios request the page reloads and I get Request Aborted error in console and since the data is not sent to BackEnd, no posts are created.
One more odd thing I noticed is that when the content is image, posts are created- maybe because by the time it reloads, the images are uploaded already. In any case, my BackEnd is fine- only problem is with the form.
Here is my NewPost Component
import React from 'react';
import './NewPost.css';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const NewPost = (props) => {
const params = useParams();
const submitHandler = async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
formData.append('user', params.userId);
console.log(formData.get('user'));
console.log(formData.get('content'));
console.log(formData.get('caption'));
const content = formData.get('content').name;
const imgExt = ['jpg', 'jpeg', 'png', 'avif', 'apng', 'pjp', 'webp', 'svg', 'bmp'];
const ext = content.split('.')[1];
let url;
if (imgExt.includes(ext)) url = `/api/v1/posts/create/contentImage`;
else url = `/api/v1/posts/create/contentVideo`;
try {
const res = await axios.post(url, formData);
if (res.data.status === 'success') {
console.log('Post created successfully!!');
}
} catch (err) {
console.log(err.message);
}
};
return (
<div className="container new-post-box">
<div className="login-form new-post-form">
<button
className="btn-mobile-nav new-post-box-close-btn"
>
<ion-icon name="close" class="overlay-close"></ion-icon>
</button>
<h2 className="heading-secondary heading-login ma-bt-lg">Whats on your mind</h2>
<form className="form form--login" id="login-form" onSubmit={submitHandler}>
<div className="form__group">
<label className="form__label" htmlFor="caption">
Caption
</label>
<input
className="form__input"
id="caption"
type="text"
name="caption"
placeholder="This is an example"
required="required"
/>
</div>
<div className="file-input-box">
<label className="form__label" htmlFor="caption">
Upload Content
</label>
<input
className="form__upload"
type="file"
name="content"
accept="video/*, image/*"
id="content"
/>
<label htmlFor="content" className="form__label fileInput">
Choose file
</label>
</div>
<div className="form__group">
<button className="btn btn--green postSubmitBtn">Submit</button>
</div>
</form>
</div>
</div>
);
};
export default NewPost;
NewPost.css
.form__upload {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.form__upload:focus + label {
outline: 3px solid #673ab7;
outline-offset: 3px;
}
.form__upload + label {
color: #673ab7;
display: inline-block;
text-decoration: none;
border-bottom: 2px solid #673ab7;
padding: 3px;
-webkit-transition: all 0.2s;
transition: all 0.2s;
cursor: pointer;
}
.form__upload + label:hover {
background-color: #673ab7;
color: #fff;
-webkit-box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15);
box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15);
-webkit-transform: translateY(-2px);
transform: translateY(-2px);
}
.new-post-box {
margin: 4rem auto;
}
.form__label {
font-size: 1.6rem;
font-weight: 700;
margin-bottom: 0.75rem;
}
.form__label.fileInput {
margin-bottom: 2.5rem;
font-weight: 600;
}
.postSubmitBtn {
margin: 1rem 11rem;
}
.login-form.new-post-form {
max-width: 51rem;
padding: 4.4rem 7rem;
position: relative;
}
.file-input-box {
display: flex;
align-items: baseline;
gap: 2.4rem;
}
.newpost-caption-closebtn-box {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.btn-mobile-nav.new-post-box-close-btn {
position: absolute;
top: 20px;
right: 14px;
}
Run it on your local machine and please tell me what Im doing wrong. I have almost completed my project and this is for my resume. Without thi working, my entire 2 months hard work will go to waste! Any suggestions would be appreciated! Thank you for your time.

Related

Inverted video player's navigation bar when video streaming

I'm building a video chat application using Node, Express, Socket.io, and WebRTC by following this tutorial: Link blog
So on my edge browser, after i enabled the video stream on my application and my webcam is activated, i noticed that when i right click on the video area (video element) and click on show all controls, i found that video player navigation bar is inverted.
Do you have any explication on why the nav bar is inverted? (check theses screenshots for inversed nav bar screenshot 1 & screenshot 2)
Below are my codes:
room.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>videoChatApp</title>
<link rel="stylesheet" href="style.css" />
<script src="/socket.io/socket.io.js"></script>
<script src="https://kit.fontawesome.com/c939d0e917.js"></script>
<script src="https://unpkg.com/peerjs#1.3.1/dist/peerjs.min.js"></script>
<script>
const ROOM_ID = "<%= roomId %>";
</script>
</head>
<body>
<div class="header">
<div class="logo">
<h3>Video Chat</h3>
</div>
</div>
<div class="main">
<div class="main__left">
<div class="videos__group">
<div id="video-grid"></div>
</div>
<div class="options">
<div class="options__left">
<div id="stopVideo" class="options__button">
<i class="fa fa-video-camera"></i>
</div>
<div id="muteButton" class="options__button">
<i class="fa fa-microphone"></i>
</div>
</div>
<div class="options__right">
<div id="inviteButton" class="options__button">
<i class="fas fa-user-plus"></i>
</div>
</div>
</div>
</div>
<div class="main__right">
<div class="main__chat_window">
<div class="messages"></div>
</div>
<div class="main__message_container">
<input
id="chat_message"
type="text"
autocomplete="off"
placeholder="Type message here..."
/>
<div id="send" class="options__button">
<i class="fa fa-plus" aria-hidden="true"></i>
</div>
</div>
</div>
</div>
</body>
<script src="script.js"></script>
</html>
script.js:
let myVideoStream;
const videoGrid = document.getElementById("video-grid");
const myVideo = document.createElement("video");
myVideo.muted = true;
navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
})
.then((stream) => {
myVideoStream = stream;
addVideoStream(myVideo, stream);
});
const addVideoStream = (video, stream) => {
video.srcObject = stream;
video.addEventListener("loadedmetadata", () => {
video.play();
videoGrid.append(video);
});
};
server.js:
const express = require('express')
const app = express()
const { v4: uuidv4 } = require("uuid");
app.set('view engine', 'ejs')
app.use(express.static('public'));
app.get('/', (req, res) => {
res.redirect(`/${uuidv4()}`);
});
app.get("/:room", (req, res) => {
res.render("room", { roomId: req.param.room });
});
app.listen(3030, () => {
console.log('Server is runing on port 3030');
})
style.css:
#import url("https://fonts.googleapis.com/css2?family=Poppins:wght#400;500;600&display=swap");
:root {
--main-darklg: #1d2635;
--main-dark: #161d29;
--primary-color: #2f80ec;
--main-light: #eeeeee;
font-family: "Poppins", sans-serif;
}
* {
margin: 0;
padding: 0;
}
.header {
display: flex;
justify-content: center;
align-items: center;
height: 8vh;
position: relative;
width: 100%;
background-color: var(--main-darklg);
}
.logo > h3 {
color: var(--main-light);
}
.main {
overflow: hidden;
height: 92vh;
display: flex;
}
.main__left {
flex: 0.7;
display: flex;
flex-direction: column;
}
.videos__group {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
background-color: var(--main-dark);
}
video {
height: 500px;
border-radius: 1rem;
margin: 0.5rem;
width: 900px;
object-fit: cover;
transform: rotateY(180deg);
-webkit-transform: rotateY(180deg);
-moz-transform: rotateY(180deg);
}
.options {
padding: 1rem;
display: flex;
background-color: var(--main-darklg);
}
.options__left {
display: flex;
}
.options__right {
margin-left: auto;
}
.options__button {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--primary-color);
height: 50px;
border-radius: 5px;
color: var(--main-light);
font-size: 1.2rem;
width: 50px;
margin: 0 0.5rem;
}
.background__red {
background-color: #f6484a;
}
.main__right {
display: flex;
flex-direction: column;
flex: 0.3;
background-color: #242f41;
}
.main__chat_window {
flex-grow: 1;
overflow-y: scroll;
}
.main__chat_window::-webkit-scrollbar {
display: none;
}
.main__message_container {
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
.main__message_container > input {
height: 50px;
flex: 1;
font-size: 1rem;
border-radius: 5px;
padding-left: 20px;
border: none;
}
.messages {
display: flex;
flex-direction: column;
margin: 1.5rem;
}
.message {
display: flex;
flex-direction: column;
}
.message > b {
color: #eeeeee;
display: flex;
align-items: center;
text-transform: capitalize;
}
.message > b > i {
margin-right: 0.7rem;
font-size: 1.5rem;
}
.message > span {
background-color: #eeeeee;
margin: 1rem 0;
padding: 1rem;
border-radius: 5px;
}
#video-grid {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
#showChat {
display: none;
}
.header__back {
display: none;
position: absolute;
font-size: 1.3rem;
top: 17px;
left: 28px;
color: #fff;
}
#media (max-width: 700px) {
.main__right {
display: none;
}
.main__left {
width: 100%;
flex: 1;
}
video {
height: auto;
width: 100%;
}
#showChat {
display: flex;
}
}
You are applying a 180 degree rotation to the element (i.e. mirroring it)
by using transform: rotateY(180deg) in your CSS file which also rotates the controls.
For WebRTC usage the common approach to solving this is not to use the build-in controls of the video element.

Size of the map when redenring mapbox in React

I trying to use mapbox GL to render a map in React.
I've diveide my screen in 4(2 col/crow) with Grid with the following css:
.container {
font-family: sans-serif;
margin: 0;
display: grid;
grid-template-rows: 1fr 7fr;
grid-template-columns: auto;
gap: 5px;
grid-template-areas: "cr1 cr2"
"cr1 cr4";
height: 100vh;
width: 200vh;
background-color: chartreuse;
}
.table {
grid-area: cr1 ;
background-color: aqua;
display: flex;
justify-content: center;
align-items: center;
}
.tank-select {
grid-area: cr2;
background-color: grey;
}
.map {
grid-area: cr4;
display: flex;
justify-content:space-around;
align-items:stretch;
background-color: blueviolet;
}
i would like to have the map in the box cr4 with the following code:
<div className="container">
<div className="table">test</div>
<div className="tank-select">test2</div>
<div className="map">
<Map />
</div>
</div>
The map is compute this way:
const mapContainerRef = useRef(null)
const [lng, setLng] = useState(5)
const [lat, setLat] = useState(34)
const [zoom, setZoom] = useState(1.5)
// Initialize map when component mounts
useEffect(() => {
const map = new mapboxgl.Map({
container: mapContainerRef.current,
style: 'mapbox://styles/mapbox/streets-v11',
center: [lng, lat],
zoom: zoom,
})
// Add navigation control (the +/- zoom buttons)
map.addControl(new mapboxgl.NavigationControl(), 'top-right')
map.on('move', () => {
setLng(map.getCenter().lng.toFixed(4))
setLat(map.getCenter().lat.toFixed(4))
setZoom(map.getZoom().toFixed(2))
})
// const marker = new mapboxgl.Marker().setLngLat(TankLocationArray).addTo(map)
//
map.on('load', function () {
map.addSource('route', {
type: 'geojson',
data: '../../assets/data/test.geoJSON',
})
map.addLayer({
id: 'route',
type: 'symbol',
source: 'route',
paint: {
'circle-radius': 10,
'circle-color': '#ff0000',
},
})
})
//
// Clean up on unmount
return () => map.remove()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return (
<div>
<div className="sidebarStyle">
<div>
Longitude: {lng} | Latitude: {lat} | Zoom: {zoom}
</div>
</div>
<div className="map-container" ref={mapContainerRef} />
</div>
With the css:
.map-container {
position:relative;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.sidebarStyle {
display: inline-block;
position: absolute;
top: 0;
left: 0;
margin: 12px;
background-color: #404040;
color: #ffffff;
z-index: 1 !important;
padding: 6px;
font-weight: bold;
}
When i change the map-container position from relative to absolute, the map would take the whole screen, otherwise it's not visible.
I do not know how to style to get the map to appear only in the box .map(cr4).
thanks for the help
solved the issue using React Map GL library.
works really fine. https://www.npmjs.com/package/react-map-gl

How to seperate sender and receiver message on basis of their socket ids in angular12 and nodejs

I want to display the sender and receiver message on different side but i dont know how i can do this
this is app.component.ts code
import { Component } from '#angular/core';
import { ChatService } from './chat.service';
import * as moment from 'moment';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'AngularApp';
constructor(private chatService: ChatService){}
newMessage!: string;
messageList: string[] = [];
ngOnInit(){
this.chatService.getNewMessage().subscribe((message: string) => {
let currentTime = moment().format('hh:mm:ss a');
this.messageList.push(message);
})
}
sendMessage() {
this.chatService.sendMessage(this.newMessage);
this.newMessage = '';
}
}
This is chatservice.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { io } from "socket.io-client";
#Injectable({
providedIn: 'root'
})
export class ChatService {
public message$: BehaviorSubject<string> = new BehaviorSubject('');
constructor() {}
socket = io('http://localhost:3000');
public sendMessage(message:any) {
this.socket.emit('message', message);
}
public getNewMessage = () => {
this.socket.on('message', (message) =>{
this.message$.next(message);
});
return this.message$.asObservable();
};
}
this is my app.component.html code
<div class="container">
<div class="chat-box">
<!-- client -->
<div class="client">
<h2>Simple Chat App</h2>
</div>
<!-- Main Chat Section -->
<div class="chats" *ngFor="let message of messageList">
<div *ngIf="message" class="client-chat">{{message}}</div>
<div *ngIf="message" class="my-chat">{{message}}</div>
</div>
<!-- Input Field -->
<div class="chat-input">
<input type="text" [(ngModel)]="newMessage" (keyup)="$event.keyCode == 13 && sendMessage()" placeholder="Enter Message">
<button (click)="sendMessage()" id="send-btn" class="btn btn-info">
Send
</button>
</div>
this is my app.component.css code
.container{
display: flex;
justify-content: center;
align-items: center;
}
/* Chat Box Section */
.chat-box{
width: 500px;
height: 550px;
background-color: #fff;
overflow: hidden;
border-radius: 10px;
position: relative;
overflow-y: scroll;height: XX px
}
.client{
height: 70px;
background-color: #77b3d4;
padding: 15px;
text-align: center;
}
.client, h2{
color: white;
}
/* Chat Section */
.chats{
width: 100%;
padding: 0 17px;
color: #fff;
position: relative;
font-size: 0.9em;
list-style-position: unset;
}
.client-chat{
width: 60%;
word-wrap: break-word;
background-color: #4f5d73c7;
padding: 7px 10px;
border-radius: 10px 10px 10px 0px;
margin: 10px 0px;
}
.my-chat{
width: 60%;
word-wrap: break-word;
background-color: #77b3d4c7;
padding: 7px 10px;
border-radius: 10px 10px 0px 10px;
margin: 5px 0px 5px auto;
}
/* Input Section */
.chat-input{
display: flex;
align-items: center;
width: 500px;
height: 65px;
background-color: #fff;
padding: 15px;
overflow: hidden;
position: fixed;
bottom: 0;
margin-top: 20px;
}
.chat-input input{
width: calc(100% - 40px);
height: 100%;
background-color: #4f5d7321;
border-radius: 45px;
color: #000;
font-size: 1.2em;
padding: 0 15px;
border: none;
}
#send-btn{
width: 50px;
height: 30px;
/* background-color: transparent; */
margin-left: 3px;
cursor: pointer;
}
this is my nodejs backend code
const app = require('express')();
const httpServer = require('http').createServer(app);
const io = require('socket.io')(httpServer, {
cors: {origin : '*'}
});
const port = process.env.PORT || 3000;
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('message', (message) => {
io.emit('message', `${socket.id.substr(0, 2)} said--${message}`);
});
socket.on('disconnect', () => {
console.log('a user disconnected!');
});
});
httpServer.listen(port, () =>
console.log(`listening on port ${port}`));
This is the screenshot of my code response when i display message i shows two time
Please anyone can solve this problem thanks in advance
In your template you have the following
<div class="chats" *ngFor="let message of messageList">
<div *ngIf="message" class="client-chat">{{message}}</div>
<div *ngIf="message" class="my-chat">{{message}}</div>
</div>
so both client-chat and my-chat will always appear since message is always true (it s the content of the message). You will need a way to know if a message come from yourself or from someone else.
Simple solution would be :
Use socket.id and when sending a message to BE, you send the message + the id.
The BE will then broadcast that message with the id.
In your clients, you will need to check
if the message come from the same socketId we are using, then it is our message so use my-chat
if the message come from a socket that is not ours, then display it as client-chat
then just assign a boolean to the message based on this, self:true if it's our message and false if not
and you simply update
<div class="chats" *ngFor="let message of messageList">
<div *ngIf="!message.self" class="client-chat">{{message}}</div>
<div *ngIf="message.self" class="my-chat">{{message}}</div>
</div>
EDIT :
So, this is a quickly made draft, you should really find the best implementation that suit your case.
you could, In your current BE :
socket.on('message', (message) => {
io.emit('message', `${socket.id.substr(0, 2)} said--${message}`);
});
You only send 2 char of the socketId. you can send it all with
socket.on('message', (message) => {
io.emit('message', `${socket.id} said--${message}`);
});
you can then in your FE just recover the sockedId by retrieving it from inside the string.
so you could do use
const socketId = message.split(' said--')[0]
and compare
socked.id === socketId

Stripe creditcard elements not loaded in Safari browser

We use stripe v.3 to add creditcard for a customer. It works on all browsers chrome and firefox. But safari returns the following error.
ReferenceError: Can't find variable: elements
Tested on physical iphone device it seems older version cant create the stripe elements leaving the input field, clumpy and not showing the CC information. Check screenshot.
The bug occurs on iOS version 11.3.1 and down. The iframe is showing in the source elements but dont add the class .StripeElement to the dom.
import {
Component,
Inject,
ViewChild,
ElementRef,
AfterViewInit,
OnDestroy,
ChangeDetectorRef,
} from '#angular/core';
import { NgForm } from '#angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { PaymentService } from '#services/http/payment.service';
import { StateService } from '#services/Logic/state.service';
import { TranslatePipe } from '#app/pipes/translate.pipe';
#Component({
selector: 'app-add-credit-card-dialog',
templateUrl: 'addCreditCard.html',
styleUrls: ['addCreditCard.scss', '../../dialogs.scss'],
})
export class AddCreditCardDialogComponent implements AfterViewInit, OnDestroy {
#ViewChild('cardInfo', { static: true }) cardInfo: ElementRef;
card: any;
cardHandler = this.onChange.bind(this);
error: string;
cardholderName: string;
constructor(
private cd: ChangeDetectorRef,
private toastr: ToastrService,
private translatePipe: TranslatePipe,
private stateService: StateService,
private paymentService: PaymentService,
public dialogRef: MatDialogRef<AddCreditCardDialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: any
) {}
ngAfterViewInit() {
this.card = elements.create('card');
this.card.mount(this.cardInfo.nativeElement);
this.card.addEventListener('change', this.cardHandler);
// Force the Element frame to paint once it is visible on the page
setTimeout(() => {
this.forceReflow('.StripeElement');
}, 1000);
}
ngOnDestroy() {
this.card.removeEventListener('change', this.cardHandler);
this.card.destroy();
}
forceReflow(selector) {
const el = document.querySelector(selector);
if (el) {
const initialDisplay = el.style.display;
el.style.display = 'none';
el.offsetHeight;
el.style.display = initialDisplay;
}
}
onChange({ error }) {
if (error) {
this.error = error.message;
} else {
this.error = null;
}
this.cd.detectChanges();
}
async onSubmit(form: NgForm) {
if (!this.cardholderName || this.cardholderName.length === 0) {
return this.toastr.error(this.translatePipe.transform('TOAST_MESSAGES.ERROR.MISSING_CARDHOLDER'));
}
this.stateService.toggleWaitingPage(true);
// create a Setup Intent for this card
const { setupIntent, error } = await stripe.handleCardSetup(
this.data.client_secret,
this.card,
{
payment_method_data: {
billing_details: { name: this.cardholderName },
},
}
);
if (error) {
this.toastr.error(error.message);
this.stateService.toggleWaitingPage(false);
} else {
// create a Payment Method from the previously created Setup Intent and attach it to the Customer
const paymentMethod = await this.paymentService
.attachPaymentMethod({ payment_method: setupIntent.payment_method })
.toPromise();
this.close(paymentMethod);
}
}
close(data: any = null): void {
this.dialogRef.close(data);
}
}
form {
display: flex;
flex-direction: column;
align-items: center;
width: 95%;
margin: auto;
.form-row {
width: 100%;
margin: 0 0 20px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2);
border-radius: 5px;
padding: 10px;
height: 40px;
#card-errors {
font-size: 12px;
color: #f82e42;
}
}
}
p {
color: #707070;
}
.input_area{
width: 100%;
text-align: center;
padding-bottom: 115px;
}
.body_area{
display: block;
height: 100%;
.attention{
display: flex;
margin: 20px 0px;
height: 100%;
padding: 5px 0px;
background-color: #F6F9FC;
.notranslate {
color: #89b153;
font-size: 18px;
margin-left: 8px;
padding-top: 2px;
padding-right: 5px;
}
p{
text-align: left !important;
margin-bottom: 0px !important;
}
.notranslate{
color: #707070;
place-self: center;
}
}
}
.header_wrapper{
display: block;
height: 100%;
.pinpay{
margin-bottom: unset !important;
font-weight: bold;
font-size: 17px;
color: #4a4a56;
}
.img {
height: 80px;
width: 80px;
margin-top: 10px;
margin: auto;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.16);
border-radius: 500px;
#media (max-width: 320px) and (max-height: 438px) { display: none; }
.image-icon{
height: 60px;
width: 60px;
margin-top: 3px;
margin-left: 8.4px;
}
}
}
#cardholder-name {
width: 95%;
margin-bottom: 20px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2);
background: transparent;
border: none;
}
<div class="dialog_container">
<div class="header_row">
<div class="header_left"></div>
<span class="close" (click)="close()">✕</span>
<h5 id="header_title">{{ 'DIALOGS.ADD_CREDIT_CARD.TITLE' | translate }}</h5>
</div>
<div class="header_wrapper">
<div class="img">
</div>
<div style="display: flex; margin-bottom: unset">
<p class="pinpay">PinployPay</p>
<span></span>™
</div>
<p class="pinpay1">Sikker handel</p>
</div>
<div class="body_area">
<div class="attention">
<mat-icon [matTooltip]="'PAGES.SETTINGS.PROFILE.COMPLETION_RATE_INFO' | translate">info</mat-icon>
<p>{{ 'DIALOGS.ADD_CREDIT_CARD.MESSAGE' | translate }}</p>
</div>
<p>
<mat-icon class="notranslate">remove_shopping_cart</mat-icon>{{
'DIALOGS.ADD_CREDIT_CARD.MESSAGE_1' | translate }}
</p>
</div>
<div class="input_area">
<input
id="cardholder-name"
[(ngModel)]="cardholderName"
[placeholder]="'DIALOGS.ADD_CREDIT_CARD.CARDHOLDER' | translate"
/>
<form #checkout="ngForm" (ngSubmit)="onSubmit(checkout)" class="checkout">
<div class="form-row">
<div id="card-info" #cardInfo></div>
<div id="card-errors" role="alert" *ngIf="error">{{ error }}</div>
</div>
<button type="submit" class="pinploy_button_v2_mobile_green">Ok</button>
</form>
</div>
</div>
index.html
<body>
<app-root></app-root>
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripeKey =
window.location.origin === "https://www.pinploy.com"
? "pk_live_******q"
: "pk_test_1P******";
let stripe = Stripe(stripeKey);
let elements = stripe.elements();
</script>
</body>
</html>

node.js + socket.io + recursively adding replies

I've cobbled together a web page to control an Onkyo receiver via its serial port using parts of several diverse examples I found mostly on stackexchange. It works well, but I've run into the problem highlighted by #user568109 in the first link below where I think I have "initialized connection eventlisteners from within your routes" instead of globally. (Each time the web page is refreshed, the number of replies sent increments by one.) The problem is I can't see how to do initialize it globally. I tried removing the function:
io.sockets.on('connection', function (socket) {......}
but leaving the ....... part, as was done successfully in the second link below, but that didn't work for my case. Is there a simple fix? I'm a complete novice to node and javascript so I'm hoping it is obvious to someone.
Apart from the additional replies each time the web page is refreshed, it works well. (Apart from initializing the on off switch, the radio buttons and volume slider get initialed correctly, but I'll try to address that once I've sorted this sockets thing out).
Thanks!
Socket.io emits duplicate data after browser refresh
node.js + socket.io - duplicate websocket writes?
Here's the code snippet:
var express = require('express');
app = express();
server = require('http').createServer(app);
io = require('socket.io').listen(server);
var SerialPort = require("serialport")
var serialPort = new SerialPort("/dev/ttyUSB0", {
baudRate: 9600,
dataBits: 8,
parity: 'none',
stopBits: 1
}
);
server.listen(8080);
app.use(express.static('public'));
io.sockets.on('connection', function (socket) {
socket.on('toOnkyo', function (data) {
paramVal = data.value;
var buf = new Buffer(16);
buf.write(paramVal, "utf-8");
serialPort.write(buf);
io.sockets.emit('toOnkyo', {value: paramVal});
});
serialPort.on('data', function(data) {
io.sockets.emit('onkyoReply', {value: data.toString().substr(0,7)});
});
});
console.log("running");
body {
text-align: center;
margin-top: 50px;
background: #50D0A0;
}
input[type=range]{
-webkit-appearance: none;
width: 80%;
}
input[type=range]::-webkit-slider-runnable-track {
height: 10px;
background: #ddd;
border: none;
border-radius: 3px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 32px;
width: 32px;
border-radius: 50%;
background: goldenrod;
margin-top: -12px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #ccc;
}
.radioLeft
{
text-align:left;
}
.onoffswitch {
position: relative; width: 90px;
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block; overflow: hidden; cursor: pointer;
border: 2px solid #999999; border-radius: 20px;
}
.onoffswitch-inner {
display: block; width: 200%; margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
box-sizing: border-box;
}
.onoffswitch-inner:before {
content: "ON";
padding-left: 10px;
background-color: #34A7C1; color: #FFFFFF;
}
.onoffswitch-inner:after {
content: "OFF";
padding-right: 10px;
background-color: #EEEEEE; color: #999999;
text-align: right;
}
.onoffswitch-switch {
display: block; width: 18px; margin: 6px;
background: #FFFFFF;
position: absolute; top: 0; bottom: 0;
right: 56px;
border: 2px solid #999999; border-radius: 20px;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 0px;
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Onkyo Controller</title>
<meta name="viewport" content="width=400px" />
<script src="socket.io/socket.io.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
Sent: <span id="sliderVolText"></span><br>
Reply: <span id="replyTextHex"></span>
(Decimal: <span id="replyText10"></span>)<br>
Mode: <span id="modeText"></span><br>
PowerText: <span id="powerText"></span><br>
Power: <span id="power"></span><br>
onoffText: <span id="onoffText"></span><br>
onoff: <span id="onoff"></span>
<script>
function setCheckedValue(radioObj, newValue) {
if(!radioObj)
return;
var radioLength = radioObj.length;
if(radioLength == undefined) {
radioObj.checked = (radioObj.value == newValue.toString());
return;
}
for(var i = 0; i < radioLength; i++) {
radioObj[i].checked = false;
if(radioObj[i].value == newValue.toString()) {
radioObj[i].checked = true;
}
}
}
</script>
<form class="onoffswitch" >
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch" onclick="showOnoff(checked)">
<label class="onoffswitch-label" for="myonoffswitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</form>
<form name="modeForm" method="get" action="" onsubmit="return false;">
<p> <label for="mode0"><input type="radio" value="0x00" name="modeForm" id="mode0" onclick="showMode(this.value)"> Stereo</label>
<label for="mode1"><input type="radio" value="0x01" name="modeForm" id="mode1" onclick="showMode(this.value)"> Direct</label>
<label for="mode2"><input type="radio" value="0x0C" name="modeForm" id="mode2" onclick="showMode(this.value)"> All Ch stereo</label>
<label for="mode3"><input type="radio" value="0x42" name="modeForm" id="mode3" onclick="showMode(this.value)"> THX Cinema</label>
<label for="mode4"><input type="radio" value="0x84" name="modeForm" id="mode4" onclick="showMode(this.value)"> PLllx THX Cinema</label>
<label for="mode5"><input type="radio" value="0x11" name="modeForm" id="mode5" onclick="showMode(this.value)"> Pure</label>
</form>
<form name="slideForm" method="get" action="" onsubmit="return false;">
<input type="range" id= "inputSlider" min="0" max="100" value="vol" step="1" oninput="showVolume(this.value)" />
</form>
<br><br>
<div class="results"></div>
<script type="text/javascript">
var socket = io.connect();
var ctrlType = "";
socket.on('toOnkyo', function (data) {
ctrlType = data.value.toString().substr(2,3);
if (ctrlType == "MVL" && !(data.value.toString().substr(5,4)=="QSTN")){
document.getElementById("inputSlider").value = parseInt(data.value.toString().substr(5,2),16);
document.getElementById("sliderVolText").innerHTML = data.value;
}
if (ctrlType == "LMD" && !(data.value.toString().substr(5,4)=="QSTN")){
document.getElementById("mode").value = parseInt(data.value.toString().substr(5,2),16);
document.getElementById("modeText").innerHTML = data.value;
}
if (ctrlType == "PWR" && !(data.value.toString().substr(5,4)=="QSTN") ){
document.getElementById("power").value = parseInt(data.value.toString().substr(5,2),16);
document.getElementById("powerText").innerHTML = data.value;
}
if (ctrlType == "PWR" && !(data.value.toString().substr(5,4)=="QSTN") ){
document.getElementById("onoff").value = parseInt(data.value.toString().substr(5,2),16);
document.getElementById("onoffText").innerHTML = data.value;
}
});
socket.on('onkyoReply', function (data) {
var done = false;
ctrlType = data.value.toString().substr(2,3);
document.getElementById("replyTextHex").innerHTML = data.value;
document.getElementById("replyText10").innerHTML = parseInt(data.value.toString().substr(5,2),16);
if (ctrlType == "LMD"){
setCheckedValue(document.forms['modeForm'].elements['modeForm'],"0x"+data.value.toString().substr(5,2));
}
if (ctrlType == "PWR"){
var val = parseInt(data.value.toString().substr(5,2),16);
setCheckedValue(document.forms['powerForm'].elements['powerForm'],"0x"+data.value.toString().substr(5,2));
}
if (ctrlType == "MVL" && done == false){
document.getElementById("inputSlider").value = parseInt(data.value.toString().substr(5,2),16);
document.querySelector('.results').innerHTML = parseInt(data.value.toString().substr(5,2),16);
done = true;
}
});
function showVolume(newValue) {
document.getElementById("sliderVolText").innerHTML="\!1MVL"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
socket.emit('toOnkyo', { value: "\!1MVL"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
}
function showMode(newValue) {
document.getElementById("modeText").innerHTML="\!1LMD"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
socket.emit('toOnkyo', { value: "\!1LMD"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
}
function showOnoff(newValue) {
document.getElementById("onoffText").innerHTML="\!1PWR"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
socket.emit('toOnkyo', { value: "\!1PWR"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
}
socket.emit('toOnkyo', { value: "\!1PWRQSTN"+"\r\n" });
socket.emit('toOnkyo', { value: "\!1LMDQSTN"+"\r\n" });
socket.emit('toOnkyo', { value: "\!1MVLQSTN"+"\r\n" });
</script>
</body>
</html>
Found a solution based on Ian Wooten's blog:
http://www.ianwootten.co.uk/2011/07/04/maintaining-references-to-sockets-with-express-and-socket-io/
Great!
server.listen(8080);
app.use(express.static('public'));
var paramVal = 0;
var countRep = 0;
var countSend = 0;
var buf = new Buffer(16);
var global_socket;
io.sockets.on('connection', function (socket) {
global_socket = socket;
global_socket.on('toOnkyo', function (data) {
paramVal = data.value;
buf.write(paramVal, "utf-8");
serialPort.write(buf);
console.log(paramVal.toString().substr(0,7) + " (" + parseInt(paramVal.toString().substr(5,2),16) + ")\r\n");
global_socket.emit('toOnkyo', {value: paramVal});
console.log('new'+paramVal);
countSend=countSend+1;
console.log('count send '+ countSend);
});
});
serialPort.on('data', function(data) {
console.log('data received: ' + data.toString().substr(0,7) + " (" + parseInt(data.toString().substr(5,2),16) + ")");
global_socket.emit('onkyoReply', {value: data.toString().substr(0,7)});
countRep=countRep+1;
console.log('count '+ countRep);
});
console.log("running");

Resources