Custom Elements rendering once only - javascript

I want to be able to create reusable custom elements. With my current implementation, each of the custom elements renders only once. All the elements (all 4) are injected into the DOM, but only the first instance of each is rendered.
I have tried with both using ShadowDOM and not using it. Any ideas?
Screenshot from dev tools:
index.html (extract from <body>):
<body>
<funky-header></funky-header>
<funky-content></funky-content>
<funky-header></funky-header>
<funky-content></funky-content>
<script src="index.js" defer></script>
</body>
I have created a generic script to create custom elements from a .html file:
index.js:
const elements = [
{ name: 'funky-header', shadowDom: false },
{ name: 'funky-content', shadowDom: false }
]
async function registerCustomElement(elementName, shadowDom) {
console.log(`Registering ${elementName}`)
await fetch(`./${elementName}.html`)
.then(stream => stream.text())
.then(async markup => {
const doc = new DOMParser().parseFromString(markup, 'text/html');
const template = doc.querySelector('template[alpine]')
const templateContent = template.content
const styles = doc.querySelector('style[scoped]')
const styleContent = styles.textContent.toString()
const elements = templateContent.querySelectorAll('[class]')
class CustomElement extends HTMLElement {
constructor() {
super()
}
connectedCallback() {
console.log(`inserting ${elementName}`)
if (shadowDom) {
const shadowRoot = this.attachShadow({ mode: 'closed' })
shadowRoot.appendChild(template.content)
} else {
this.setAttribute(`data-x-${elementName}`, '')
elements.forEach(element => {
element.setAttribute(`data-x-${elementName}`, '')
})
const scopedStyles = styleContent.replaceAll(' {', '{').replaceAll('{', `[data-x-${elementName}] {`)
const styleTag = document.createElement('style')
styleTag.type = 'text/css'
styleTag.append(document.createTextNode(scopedStyles))
this.append(styleTag)
this.append(templateContent)
}
}
}
customElements.define(elementName, CustomElement)
})
.catch(err => {
console.log('ERROR:', err)
})
}
elements.forEach(element => registerCustomElement(element.name, element.shadowDom))
An example template file:
funky-header.html:
<template alpine>
<h1 class="font-black text-blue-800">This is my header</h1>
<p class="font-thin text-xs text-blue-600 my-text">This is a paragraph with a longer text, to simualte a descritpion.</p>
</template>
<style scoped>
.my-text {
color: purple;
}
</style>

The solution was as simple as replacing the this.append(templateContent) with this.innerHTML = template.innerHTML.

Related

How to get class name from current binded element using Vue

I currently have this div:
<div class="center-align" :class="setDisplay()">
<div class="lds-hourglass"></div>
</div>
I need to check if "this" div contains "center-aligh" to execute code in setDisplay:
In my Vue 3 setup I have:
setup() {
const setDisplay = () => {
console.log(this.$el);
}
}
return {
setDisplay
}
this.$el returns as undefined. From what I've read I would return it like so:
this.$el.className based on this answer
But it's not working. Any idea what I'm doing wrong?
You can use ref in setup function and with nextTick get class in mounted hook with value.classList :
const { ref, onMounted, nextTick } = Vue
const app = Vue.createApp({
setup() {
const el = ref(null)
const myClass = ref(null)
const setDisplay = () => {
myClass.value = 'myclass'
}
onMounted(async () => {
await nextTick()
console.log(Array.from(el.value.classList))
Array.from(el.value.classList).includes('center-align') && setDisplay()
})
return { setDisplay, el, myClass }
}
})
app.mount('#demo')
.myclass {
color: red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="center-align xxx" ref="el" :class="myClass">
<div class="lds-hourglass"></div>
class applied
</div>
</div>

How Can I use components using CDN in vue.js?

I am using vue.js as CDN. I need help with a schematic of how I can build an application to display the component in index.html. Currently, I have the following structure:
<div id="app">
</div>
<script>
const { createApp } = Vue
createApp({
data() {
return {
}
}).mount('#app')
</script>
component.js:
<template>
<div>
Test
</div>
</template>
export default {
data: () => ({
}),
}
You can try to define Vue and use .component
//in other file
const component1 = {
template: `<div> {{ item }} <p>{{ prop }}</p></div>`,
props: ['prop'],
data: () => ({ item: 'test' }),
}
const app = Vue.createApp({
data: () => ({ someData: 'prop' }),
})
app.component('test', component1)
app.mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<test :prop="someData" />
</div>
component_one.html
<p>component one</p>
component_two.html
<p>component {{two}}</p>
<input type="text" v-model="two"/>
component_three.html
<p>component three</p>
app.html
<router-link to="/">one</router-link> |
<router-link to="/two">two</router-link>
<component_three/>
<router-view />
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.41/vue.global.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/4.1.6/vue-router.global.js"></script>
<title>test</title>
</head>
<body>
<div id="app"/>
<script type="text/javascript" src="index.js"> </script>
</body>
</html>
index.js
const one = async () => {
let template = await fetch("component_one.html")
template = await template.text()
return ({
template: template,
setup() {/*...*/ }
})
}
const two = async () => {
let template = await fetch("component_two.html")
template = await template.text()
return ({
template: template,
setup() {
return {
two: Vue.ref("TWO"),
}
}
})
}
const three = async () => {
let template = await fetch("component_three.html")
template = await template.text()
return ({
template: template,
setup() {/*...*/ }
})
}
const app = async () => {
let template = await fetch("app.html")
template = await template.text()
return ({
template: template,
components: {
"component_three" : await three(),
},
setup() {/*...*/ }
})
}
const init = async () => {
const index = Vue.createApp(await app());
const routings = VueRouter.createRouter({
history : VueRouter.createWebHashHistory(),
routes : [
{path:'/', component: await one()},
{path:'/two', component: await two()}
]
})
index.use(routings)
index.mount("#app")
}
init()
html files are read as string. maybe put them in backend/database server. For faster loading, use Promise.all([]) to all await components.
working example: www.julven.epizy.com/vuetest
Here is what I have tried with vue router. We can separate the logic to components.
Here is the example in replit
In the index.html, we can use the <script type="module"> so we can use the import the js files.
In the components/home/home.js,
importTemplate function accepts a html file path and return the html file as string.
Other vue properties like methods, data can be put in the export default {}.
If sub-components are needed, we can use app.component('bottomb', BottomBar) like the one in index.html. <bottomb /> can then be used in all other components like the home.html, not just in index.html.

How can i add and remove a class from an specific element?

i'm making a to do list with only javascript and css. When i add the task, it creates an article with a h1 and 2 icons. The check icon and delete icon. When i click the check icon, it add the class that changes the text style to line-through. The problem is that it's applying to all h1, and i want it to apply only to one specific h1.
function TaskName() {
window.taskName = {};
window.taskName.handdleClick = () => {
const $Task = document.querySelectorAll("h1");
$Task.forEach((e) => {
e.classList.toggle("task-check");
};
window.taskName.delete = () => {
ItemRemoval();
};
let $Input = document.getElementById("input-item");
let $task = $Input.value;
return /*html*/ `
<article id='task-article'>
<h1 id='task-name'>${$task}</h1>
<img src='images/check 1.png' alt='Completar tarefa' onclick='taskName.handdleClick()' id='check-icon'>
<img src='images/delete.svg' alt='Deletar tarefa' onclick='taskName.delete()' id='delete-icon'>
</article>
`;
}
Your problem starts at the structure of your code:
there's a function called TaskName that does a lot of things: creating HTML, deleting something from somewhere, handling a click event
you use the global namespace (window) to handle things
What do you need, if you want a Todo app?
a list of todos (probably an array)
a function to add a new Todo to the list of todos
a function to remove a Todo item from the list of todos
a function that sets 1 Todo item to done (OK, usually toggle between done and not done)
Here's a snippet that does this:
// list of todos & list manipulation functions
let todos = []
const addTodo = (newTodo, todos) => [...todos, newTodo]
const removeTodo = (idToRemove, todos) => todos.filter(({ id }) => idToRemove != id)
const toggleTodoDone = (idToToggle, todos) => todos.map(({ id, done, ...rest }) => id == idToToggle ? { id, done: !done, ...rest } : { id, done, ...rest })
const getTodoItem = (label) => ({
id: Date.now(),
done: false,
label,
})
// DOM manipulation & event handling
const input = document.getElementById("input-add-todo")
const btnAdd = document.getElementById("btn-add-todo")
const container = document.getElementById("container")
const resetInput = () => input.value = ''
btnAdd.addEventListener('click', function() {
const label = input.value
if (label) {
const newTodo = getTodoItem(label)
todos = addTodo(newTodo, todos)
updateContainer(container, todos)
resetInput()
}
})
const getTodoHtml = (todo) => {
const doneClass = todo.done ? ' done' : ''
return `
<div
class="todo-item${doneClass}"
data-id="${todo.id}"
>
${todo.label} - ${todo.done}
<button class="remove-todo" data-id="${todo.id}">X</button>
</div>
`
}
const getTodoListHtml = (todos) => todos.map(getTodoHtml).join('')
const registerEventHandlers = (container) => {
const els = document.querySelectorAll('.todo-item')
els.forEach(el => el.addEventListener('click', function() {
const id = el.dataset.id
todos = toggleTodoDone(id, todos)
updateContainer(container, todos)
}))
const btns = document.querySelectorAll('.remove-todo')
btns.forEach(btn => btn.addEventListener('click', function(e) {
e.stopPropagation()
const id = btn.dataset.id
todos = removeTodo(id, todos)
updateContainer(container, todos)
}))
}
const updateContainer = (container, todos) => {
container.innerHTML = getTodoListHtml(todos)
registerEventHandlers(container)
}
.todo-item {
cursor: pointer;
}
.todo-item.done {
text-decoration: line-through;
}
<div>
<input type="text" id="input-add-todo" />
<button id="btn-add-todo">ADD TODO</button>
</div>
<div id="container"></div>

Gatsby Develop: cannot destructure property `frontmatter` of undefined

I'm new to Gatsby and GraphQL. I was trying to build a blog-like website. I'm using a template for the blog post page which leads to the use of gatsby-node.js and I think this is causing the issue. It runs fine in development mode but as I try to build it, it gives me an error. I'm on the latest version of everything.
Command: Gatsby build
Error:
Also, here are my files attached.
gatsby-node.js
exports.createPages = async ({ actions, graphql, reporter, ...props }) => {
console.log(actions, graphql, reporter, props)
const { createPage } = actions
const blogPostTemplate = path.resolve(`src/pages/lead-generation.js`)
const result = await graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: blogPostTemplate,
context: {}, // additional data can be passed via context
})
})
}
lead-generation.js
export default class Template extends React.Component {
render() {
const { markdownRemark: { frontmatter = {}, html } = {} } =
this.props.data;
return (
<div className="p-3">
<div>
<h1>{frontmatter.title}</h1>
<h6 className="pb-5">{frontmatter.articleHeading}</h6>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
<div
id="container"
className="p-5"
style={{ height: 500, width: 400 }}
>
{/* widget*/}
</div>
</div>
)
}
}
export const pageQuery = graphql`
query($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
title
articleHeading
categoryID
}
}
}
`
markdown-file.md
---
path: "/works/remaining-URL"
title: "some title?"
articleHeading: "This is some heading"
categoryID: 10056
---
This is the further text
I have simplified the question out. Please let me know if you need further elaboration.

Converting contents from html to ReactJS file

Apologies if the question is confusing. Basically I have this html code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A Gentle Introduction</title>
<script
src="https://rawgit.com/flatiron/director/master/build/director.min.js">
</script>
<script>
var author = function () { console.log("author"); };
var books = function () { console.log("books"); };
var viewBook = function (bookId) {
console.log("viewBook: bookId is populated: " + bookId);
};
var routes = {
'/author': author,
'/books': [books, function() {
console.log("An inline route handler.");
}],
'/books/view/:bookId': viewBook
};
var router = Router(routes);
router.init();
</script>
</head>
<body>
<ul>
<li>#/author</li>
<li>#/books</li>
<li>#/books/view/1</li>
</ul>
</body>
</html>
which is clearly in a .html file. I want to change this to a .js file so that I can put html within the js so that when the different links are clicked, what is routed/returned is different.
I dont really know how to directly put this into a javascript file and then get the router to work. This is where the html file came from https://github.com/flatiron/director#client-side-routing and I am trying to use this flatiron/director router.
Any help would be great!
I was able to make it work with react and jsx and the routing code outside react itself.
Written with es6/es2015
app.js
const author = () => { console.log("author"); };
const books = () => { console.log("books"); };
const viewBook = (bookId) => { console.log("viewBook: bookId is populated: " + bookId); };
const routes = {
'/author': author,
'/books': [books, () => { console.log("An inline route handler."); }],
'/books/view/:bookId': viewBook
};
const router = Router(routes);
router.init();
class SampleRouting extends React.Component {
render() {
return (
<ul>
<li>#/author</li>
<li>#/books</li>
<li>#/books/view/1</li>
</ul>
)
}
}
React.render( <SampleRouting/> , document.getElementById('root'));
index.html
<div id="root"></div>
sample: http://s.codepen.io/oobgam/debug/vNoogO
_edited app.js to reflect the updating of state and page header
class App extends React.Component {
constructor(props) {
super(props);
this.state = { currentPage: 'author' }
}
componentDidMount() {
const author = () => { this.setState({currentPage: 'author'}) };
const books = () => { this.setState({currentPage: 'Books'}); };
const viewBook = (bookId) => { this.setState({currentPage: 'Book ' + bookId }); };
const routes = {
'/author': author,
'/books': books,
'/books/view/:bookId': viewBook
};
const router = Router(routes);
router.init();
}
render() {
return (
<div>
<h1>{ this.state.currentPage }</h1>
<SampleRouting />
</div>
);
}
}
// stateless function
const SampleRouting = () => {
return (
<ul>
<li>#/author</li>
<li>#/books</li>
<li>#/books/view/1</li>
< /ul>
)
}

Categories

Resources