Using JavaScript in Angular 12 - javascript

I'm learning to use Angular 12 and trying to build a sidenav. I know I can use angular material, but I don't want to use the css associated with it.
I'd like to use this in my project. But can't understand how to convert the JS to be used in the angular 12 project.
I've placed the javascript in a menu.js under my assets/js folder. But can't understand how it's used with the component.js since it isn't a actual function, but a document.queryselectorall.
let arrow = document.querySelectorAll(".arrow");
for (var i = 0; i < arrow.length; i++) {
arrow[i].addEventListener("click", (e)=>{
let arrowParent = e.target.parentElement.parentElement; //selecting main parent of arrow
arrowParent.classList.toggle("showMenu");
});
}
let sidebar = document.querySelector(".sidebar");
let sidebarBtn = document.querySelector(".bx-menu");
console.log(sidebarBtn);
sidebarBtn.addEventListener("click", ()=>{
sidebar.classList.toggle("close");
});

Your code is old school. You need to get used to the Angular approach.
Basically what your code is doing is toggling a CSS class on an element on click. Here's how you do that in Angular:
In your HTML file:
<button (click)="toggleSidebar()">Toggle Sidebar</button>
<!-- the show-me class is added when showSidebar is true -->
<div class="sidebar" [class.show-me]="showSidebar">I am a sidebar</div>
In your .ts file:
showSidebar = false;
toggleSidebar() {
this.showSidebar = !this.showSidebar;
}
And then add your animation styles in your .styles file:
.sidebar {
// styles
}
.sidebar.show-me {
// styles
}

Related

Cannot appendChild to a custom web component

I have a web-component at root level. The simplified version of which is shown below:
class AppLayout {
constructor() {
super();
this.noShadow = true;
}
connectedCallback() {
super.connectedCallback();
this.render();
this.insertAdjacentHTML("afterbegin", this.navigation);
}
render() {
this.innerHTML = this.template;
}
get template() {
return `
<h1>Hello</h1>
`;
}
navigation = `
<script type="module">
import './components/nav-bar.js'
</script>
`;
}
customElements.define('app-layout', AppLayout);
I want to load a script after this component loads. The script creates html for navigation and tries to add it to the app-layout element shown above. However, even though, it does find the app-layout element, it is unable to append the navBar element. It is, however, able to append the navBar to the body of the html. Any ideas what I'm missing.
const navLinks =
`<ul>
<li>Some</li>
<li>Links</li>
</ul>
`;
const navBar = document.createElement('nav');
navBar.innerHTML = navLinks;
const appLayout = document.querySelector('app-layout'); // works with 'body' but not with 'appLayout'
console.log(appLayout); // Logs correct element
appLayout.appendChild(navBar);
I know that what I'm trying to do here (loading a script inside a web component) is not ideal, however, I would like to still understand why the above doesn't work.
using innerHTML or in your case insertAdjacentHTML to add <script> tags to the document doesn't work because browsers historically try to prevent potential cross site script attacks (https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0)
What you could do is something like:
const s = document.createElement("script");
s.type = "module";
s.innerText = `import './components/nav-bar.js'`;
this.append(s);
// or simply directly without the script: `import('./comp..')` if it is really your only js in the script tag.

Conditional styling on class in Svelte

I'm trying to use Svelte to do some conditional styling and highlighting to equations. While I've been successful at applying a global static style to a class, I cannot figure out how to do this when an event occurs (like one instance of the class is hovered over).
Do I need to create a stored value (i.e. some boolean that gets set to true when a class is hovered over) to use conditional styling? Or can I write a function as in the example below that will target all instances of the class? I'm a bit unclear why targeting a class in styling requires the :global(classname) format.
App.svelte
<script>
// import Component
import Katex from "./Katex.svelte"
// math equations
const math1 = "a\\htmlClass{test}{x}^2+bx+c=0";
const math2 = "x=-\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}";
const math3 = "V=\\frac{1}{3}\\pi r^2 h";
// set up array and index for reactivity and initialize
const mathArray = [math1, math2, math3];
let index = 0;
$: math = mathArray[index];
// changeMath function for button click
function changeMath() {
// increase index
index = (index+1)%3;
}
function hoverByClass(classname,colorover,colorout="transparent")
{
var elms=document.getElementsByClassName(classname);
console.log(elms);
for(var i=0;i<elms.length;i++)
{
elms[i].onmouseover = function()
{
for(var k=0;k<elms.length;k++)
{
elms[k].style.backgroundColor=colorover;
}
};
elms[i].onmouseout = function()
{
for(var k=0;k<elms.length;k++)
{
elms[k].style.backgroundColor=colorout;
}
};
}
}
hoverByClass("test","pink");
</script>
<h1>KaTeX svelte component demo</h1>
<h2>Inline math</h2>
Our math equation: <Katex {math}/> and it is inline.
<h2>Displayed math</h2>
Our math equation: <Katex {math} displayMode/> and it is displayed.
<h2>Reactivity</h2>
<button on:click={changeMath}>
Displaying equation {index}
</button>
<h2>Static math expression within HTML</h2>
<Katex math={"V=\\pi\\textrm{ m}^3"}/>
<style>
:global(.test) {
color: red
}
</style>
Katex.svelte
<script>
import katex from "katex";
export let math;
export let displayMode = false;
const options = {
displayMode: displayMode,
throwOnError: false,
trust: true
}
$: katexString = katex.renderToString(math, options);
</script>
<svelte:head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
</svelte:head>
{#html katexString}
If I understand it correctly you have a DOM structure with arbitrary nested elements and you would want to highlight parts of the structure that share the same class.
So you would have a structure like this:
<div>
<p>This is some text <span class="a">highlight</span></p>
<span class="a">Another highlight</span>
<ul>
<li>Some listitem</li>
<li class="a">Some listitem</li>
<li class="b">Some listitem</li>
<li class="b">Some listitem</li>
</ul>
</div>
And if you select an element with class="a" all elements should be highlighted regardles where they are in the document. This arbitrary placement makes using the sibling selector in css not possible.
There is no easy solution to this, but I will give you my attempt:
This is the full code with some explanation
<script>
import { onMount } from 'svelte'
let hash = {}
let wrapper
onMount(() => {
[...wrapper.querySelectorAll('[class]')].forEach(el => {
if (hash[el.className]) return
else hash[el.className] = [...wrapper.querySelectorAll(`[class="${el.className}"]`)]
})
Object.values(hash).forEach(nodes => {
nodes.forEach(node => {
node.addEventListener('mouseover', () => nodes.forEach(n => n.classList.add('hovered')))
node.addEventListener('mouseout', () => nodes.forEach(n => n.classList.remove('hovered')))
})
})
})
</script>
<div bind:this={wrapper}>
<p>
Blablabla <span class="a">AAA</span>
</p>
<span class="a">BBBB</span>
<ul>
<li>BBB</li>
<li class="a b">BBB</li>
<li class="b">BBB</li>
<li class="b">BBB</li>
</ul>
</div>
<style>
div :global(.hovered) {
background-color: red;
}
</style>
The first thing I did was use bind:this to get the wrapping element (in your case you would put this around the {#html katexString}, this will make that the highlight is only applied to this specific subtree.
Doing a querySelector is a complex operation, so we will gather all the related nodes in a sort of hashtable during onMount (this kind of assumes the content will never change, but since it's rendered with #html I believe it's safe to do so).
As you can see in onMount, I am using the wrapper element to restrict the selector to this section of the page, which is a lot faster than checking the entire document and is probably what you want anyway.
I wasn't entirely sure what you want to do, but for simplicity I am just grabbing every descendant that has a class and make a hash section for each class. If you only want certain classes you could write out a bunch of selectors here instead:
hash['selector-1'] = wrapper.querySelectorAll('.selector-1');
hash['selector-2'] = wrapper.querySelectorAll('.selector-2')];
hash['selector-3'] = wrapper.querySelectorAll('.selector-3');
Once this hashtable is created, we can loop over each selector, and attach two event listeners to all of the elements for that selector. One mouseover event that will then again apply a new class to each of it's mates. And a mouseout that removes this class again.
This still means you have to add hovered class. Since the class is not used in the markup it will be removed by Svelte unless you use :global() as you found out yourself. It is indeed not that good to have global classes because you might have unintended effect elsewhere in your code, but you can however scope it as I did in the code above.
The line
div > :global(.hovered) { background-color: red; }
will be processed into
div.svelte-12345 .hovered { background-color: red; }
So the red background will only be applied to .hovered elements that are inside this specific div, without leaking all over the codebase.
Demo on REPL
Here is the same adapted to use your code and to use a document-wide querySelector instead (you could probably still restrict if wanted by having the bind one level higher and pass this node into the component)
Other demo on REPL

Next.js - Cant apply dynamic class names when using CSS Modules

hoping someone here can help me solve this.
Am trying to build a website through NextJs. One of my pages has some paragraphs and buttons which are styled differently based on states and events. I can get the styling to work as intended when using pure React, and also when using a Global Stylesheet with NextJs; but when I use CSS Modules I cant get it to function as intended.
(Note: I can also get it to work by using a simple ternary like
<h1 className={submitted ? styles.showresult : styles.hideresult}>Correct? {correct}</h1>;
but I have some other scenarios where I need to rely on an multiple ifs and create multiple classes, each with their own styling, so I cant make a simple ternary my final solution.
E.g. this is the file pagex.js
import React from 'react';
import ReactDOM from 'react-dom';
const Pagex = () => {
const [submitted, setSubmitted] = React.useState(false); // whether the submit button was pressed
function calculateScore() {
let correct = 0
let incorrect = 0
//......some scoring logic.....
setSubmitted(true)
}
// function to create a display class based on whether the submit button has been pressed
function displayResult(){
if (submitted === true) {
return "showtheresult"
} else {
return "hidetheresult"
}
}
return (
<section className="results">
<h1 className={displayResult()}>Correct? {correct}</h1>
<h1 className={displayResult()}>Incorrect? {incorrect}</h1>
<button className={displayResult()} onClick={handleMovClick}>An instruction</button>
</section>
</div>
);
};
export default Pagex;
the globals.css file contains
h1.hidetheresult, h3.hidetheresult {
visibility: hidden;
}
h1.showtheresult, h3.showtheresult {
visibility: visible;
}
button.hidetheresult {
border-color: pink;
}
button.showtheresult {
border-color: aqua;
}
Changes when using CSS modules
Add a CSS file in the correct folder with the correct name
(../styles/Pagex.module.css) which contains the same styling shown
above
Additional import in pagex.js import styles from '../styles/Pagex.module.css'
Change reference in the function
within pagex.js
function displayResult(){
if (submitted === true) {
return {styles.showtheresult}
} else {
return {styles.hidetheresult}
}
}
When i do this the '.' in {styles.showtheresult} and {styles.hidetheresult} gets highlighted as an error by vscode with this detail: ',' expected. ts(1005).
Saving the js with a dev server running shows a similar message after trying to compile: Syntax error: Unexpected token, expected "," and the browser shows the same message along with "Failed to compile"
Also tried just passing styles.showtheresult / styles.hidetheresult by removing the curly braces from the displayResult() function. That compiles but nothing happens on the compiled webpage, i.e the class doesnt get updated when the button is pressed and so the styling cant be applied.
Also Tried passing as ${styles.showresult} and ${styles.hideresult} (with `)in the return statement. That also compiles but the page itself gives me an "Unhandled Runtime Error ReferenceError: styles is not defined" message and I cant load the page.
Would highly appreciated if someone could help correct my syntax in the function itself or elsewhere in the code.
Because you asked nicely ;) (just kiddin')
So Next.js is an opinionated framework and uses CSS Modules to enforce component scoped styling.
Basically you define your stylesheet with a name.module.css filename and add regular CSS in it.
.hidetheresult {
visibility: hidden;
}
.showtheresult{
visibility: visible;
}
.btn-hidetheresult {
border-color: pink;
}
.btn-showtheresult {
border-color: aqua;
}
Now to use this, import it like any JS module,
import styles from './styles.module.css'
console.log(styles);
// styles => {
// hidetheresult: 'contact_hidetheresult__3LvIF',
// showtheresult: 'contact_showtheresult__N5XLE',
// 'btn-hidetheresult': 'contact_btn-hidetheresult__3CQHv',
// 'btn-showtheresult': 'contact_btn-showtheresult__1rM1E'
// }
as you can see, the styles are converted to objects and now you can use them
like styles.hidetheresult or styles['btn-hidetheresult'].
Notice the absence of element selector in the stylesheet. That's because CSS Modules rewrite class names, but they don't touch tag names. And in Next.js that is
the default behaviour. i.e it does not allow element tag selectors.
File extensions with *.module.(css | scss | sass) are css modules and they can only target elements using classnames or ids and not using tag names. Although this is possible in other frameworks like create-react-app, it is not possible in next-js.
But you can override it in the next.config.js file. (Beyond the scope of this answer)
There is an article which explains how to override it. - disclaimer: I am the author
Now coming to your use-case, you can do contitional styling like so: (assuming the styles are as per the sample given in the answer)
import React from "react";
import styles from "./styles.module.css";
const PageX = () => {
const [submitted, setSubmitted] = React.useState(false);
const getStyle = () => {
if (submitted) return styles.showtheresult;
else return styles.hidetheresult;
};
const getButtonStyle = () => {
if (submitted) return styles["btn-showtheresult"];
else return styles["btn-hidetheresult"];
};
return (
<div>
<section className="results">
<h1 className={getStyle()}>Correct?</h1>
<h1 className={getStyle()}>Incorrect?</h1>
<button className={getButtonStyle()} onClick={handleMovClick}>
An instruction
</button>
</section>
</div>
);
};
As you add more conditions, the methods do tend to get more complex. This is where the classnames
module comes handy.
import styles from "./styles.module.css";
import clsx from "classnames";
const PageX = () => {
const [submitted, setSubmitted] = React.useState(false);
const headerStyle = clsx({
[styles.showtheresult]: submitted,
[styles.hidetheresult]: !submitted,
});
const btnStyle = clsx({
[styles["btn-showtheresult"]]: submitted,
[styles["btn-hidetheresult"]]: !submitted,
});
return (
<div>
<section className="results">
<h1 className={headerStyle}>Correct?</h1>
<h1 className={headerStyle}>Incorrect?</h1>
<button className={btnStyle} onClick={handleMovClick}>
An instruction
</button>
</section>
</div>
);
};
Here's a CodeSandbox for you to play with:

Angular 12.1 add html element using typescript

I'm learning angular via youtube, but I'm trying to do something new, and I'm getting an error on that, my code is attached below, help me out.
I want to setAttribute like this div.setAttribute('(click)',"popUp($event)"); but I got error.
TypeScript
export class AppComponent {
createEl(){
console.time("timer");
for (let i = 0; i < 10; i++) {
let div = document.createElement("div");
div.textContent = `Hello, World! ${i}`;
div.setAttribute('(click)',"popUp($event)");
document.getElementById('divEl')?.appendChild(div);
};
console.timeEnd("timer");
}
HTML
<div id="divEl"></div>
<button (click)="createEl()">click me</button>
Error
This is not really the angular way of doing things. Try to avoid operations on document such as document.createElement.
A better way to achieve this would be to define what the repeating element would look like in the template and drive it from an array. That way we can keep the template doing display and the typescript doing processing, and Angular handling everything in between.
HTML
<div id="divEl">
<div *ngFor="let row of rows; index as i;" (click)="popUp($event)">
Hello, World! {{i}}
</div>
</div>
<button (click)="createEl()">click me</button>
Typescript
export class AppComponent {
rows: unknown[] = [];
createEl():void {
this.rows.push('something');
}
popUp(event:Event):void {}
}
More reading on loops: https://angular.io/api/common/NgForOf
That's right check below.
div.addEventListener('click', (e) => {
this.popUp(e);
});
Problem is you are trying to do angular stuff with pure javascript.
<div (click)="method()"> is angular.
In javascript you'd do someting like this <button onclick="myFunction()">Click me</button>
Other options are to use event handlers https://www.w3schools.com/js/js_htmldom_eventlistener.asp
Anyhow, angular doesn't recommend changes the DOM because then it won't recognize those changes. Here are multiple examples ho to properly change the dom
Correct way to do DOM Manipulation in Angular 2+
https://medium.com/#sardanalokesh/understanding-dom-manipulation-in-angular-2b0016a4ee5d
`
You can set the click event as shown below instead of using setAttribute
div.addEventListener('click', (e) => {
this.popUp(e);
});
(click) is not an html attribute, it is Angular event binding syntax
This syntax consists of a target event name within parentheses to the left of an equal sign, and a quoted template statement to the right.
You cannot use that with JavaScript. Use
div.onclick = popUp;
export class AppComponent {
createEl(){
console.time("timer");
for (let i = 0; i < 10; i++) {
let div = document.createElement("div");
div.textContent = `Hello, World! ${i}`;
div.addEventListener('click', (e) => {
this.popUp(e);
});
document.getElementById('divEl')?.appendChild(div);
};
console.timeEnd("timer");
}

Q: Add Javascript(By Link To HTML File) To Custom Element With DOM

I have a problem with adding javascript to handle event for a custom element . I defined a custom element in a javascript file called menu.js, by adding this element to DOM directly, like the code below:
customElements.define("custom-menu", class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<header class="header">
<div class="header__menu">
<div class="header__menu_bar"></div>
<div class="header__menu_bar"></div>
<div class="header__menu_bar"></div>
</div>
</header>
<div class="modal2">
// some code HTML
</div>
<div class="modal__details">
// some code HTML
</div>`;
}
});
const header__menu = document.querySelector(".header__menu");
header__menu.addEventListener("click", function () {
document.getElementsByClassName("modal2")[0].style.width = "100%";
$("body").addClass("stop-scrolling");
$("body").removeClass("enable-scrolling");
if (document.body.offsetWidth <= "640") {
document.getElementsByClassName("modal-content")[0].style.width = "100%";
} else {
document.getElementsByClassName("modal-content")[0].style.width = "290px";
}
document.getElementsByClassName("modal-content")[0].style.right = "0";
});
// some code Javascript handle class "modal2" and "modal__details"
I use connectedCallback() function to call everytime custom element is inserted into the DOM. If I add Javascipt code directly like the code above, I have succeeded to add click event to the div with class "header__menu" and handle both the modals. Now I want to put the Javascipt code after customElements.defined(...); to another Javascipt file and link this file to file HTML using this element to do the same task but it doesn't work as when I add directly. Can someone tell me the reason?
Thank you!
You have to make sure the js code get called after the custom element is created.
For example:
$(function () {
const header__menu = document.querySelector(".header__menu");
header__menu.addEventListener("click", function () {
document.getElementsByClassName("modal2")[0].style.width = "100%";
$("body").addClass("stop-scrolling");
$("body").removeClass("enable-scrolling");
if (document.body.offsetWidth <= "640") {
document.getElementsByClassName("modal-content")[0].style.width = "100%";
} else {
document.getElementsByClassName("modal-content")[0].style.width = "290px";
}
document.getElementsByClassName("modal-content")[0].style.right = "0";
});
});
When I style the custom element, I put css code to another file then link to HTML file like I usually work with HTML tag, class or id. This work well, and when I inspect by F12, I can see all html codes of custom element and styles of them.
For example, this is style of the div with class name modal__details:
.modal__details {
position: fixed;
top: 0;
right: 0;
z-index: 2;
width: 0;
height: 100%;
transition: 0.4s;
background-color: black;
}
I don't need to specify anything. It works like any other div. It means my custom element is in HTML DOM, but I just can handle it by put Javascript directly like above, it doesn't work in other JS file. I have also tried the way you instruct, but it has failed. So it's my wondering about that.

Categories

Resources