Using stenciljs dynamically generate nesting unordered <ul><li>...</li></ul> list, so i and giving input as a Obj={} i am getting some issues. Here is my code below Please help me on this...
1. index.html
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
<title>Stencil Component Starter</title>
<script src="/build/mycomponent.js"></script>
</head>
<body>
<list-component list-object='[
{title: "Point", children: [
{title: "Point"},
{title: "Point"},
{title: "Point"},
{title: "Point", children: [
{title: "Point"},
{title: "Point"},
{title: "Point"}
]}
]},
{title: "Point", children: [
{title: "Point"},
{title: "Point", children: [
{title: "Point"},
{title: "Point"},
{title: "Point"}
]},
{title: "Point"}
]},
]' > </list-component>
</body>
</html>
ISSUE:
I am passing nested object to the custom web component.
In this list.tsx file i am facing problem while passing arguments to the function buildList("?","?")...?
2. list.tsx
import { Component, Prop, State, Watch, Element } from '#stencil/core';
#Component({
tag:'list-component',
styleUrl:'./list-component.css'
})
export class ListComponent{
#State() idName: string;
#Prop() listObject: string;
#Element() flashElement: HTMLElement;
private ulContent: HTMLElement;
componentWillLoad() {
this.ulContent = this.flashElement.querySelector('.ul-content');
this.buildList(this.ulContent,this.listObject);
}
#Watch('listObject')
buildList(parentElement, listObject){
console.log(listObject);
var i, l, list, li, ul1;
if( !listObject || !listObject.length ) { return; }
ul1 = document.createElement('ul');
list = parentElement.appendChild(ul1);
for(i = 0, l = listObject.length ; i < l ; i++) {
li = document.createElement('li');
li.appendChild(document.createTextNode(listObject[i].title));
list.appendChild(li);
this.buildList(li, listObject[i].children);
}
}
render() {
return (
<div class="ul-content"></div>
);
}
}
I see two problems:
1: When Stencil calls #Watch methods it always passes the new and old values as arguments, see https://stenciljs.com/docs/properties#prop-default-values-and-validation. This means you cannot define custom arguments.
You could create an additional function which acts as the watcher and then calls buildList:
#Watch('listObject')
listObjectChanged() {
this.buildList(this.ulContent, this.listObject);
}
2: listObject is a string so you need to JSON.parse it before you can loop over it (and rewrite it so it's valid JSON). Then store that parsed list in a local variable and use it to generate the list. See https://medium.com/#gilfink/using-complex-objects-arrays-as-props-in-stencil-components-f2d54b093e85
There is a much simpler way to render that list using JSX instead of manually creating the list elements:
import { Component, Prop, State, Watch, Element } from '#stencil/core';
#Component({
tag: 'list-component',
styleUrl: './list-component.css'
})
export class ListComponent {
#Element() flashElement: HTMLElement;
#State() idName: string;
#Prop() listObject: string;
#State() list: any[];
#Watch('listObject')
listObjectChanged() {
this.list = JSON.parse(this.listObject);
}
componentWillLoad() {
this.listObjectChanged();
}
renderList(list) {
return list.map(list => <ul>
<li>{list.title}</li>
{list.children && this.renderList(list.children)}
</ul>
);
}
render() {
return (
<div class="ul-content">
{this.renderList(this.list)}
</div>
);
}
}
Hope this helps.
Related
I have an app that, among other things, keeps track of items in lists. The following example, a slight extension of the docs example (using props), works properly.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue List Component Test</title>
<script src="https://unpkg.com/vue#3"></script>
</head>
<body>
<div id="app"></div>
<script src="./app.js"></script>
</body>
</html>
app.js (with props)
const MyApp = {
data() {
return {
items: []
}
},
template: `<list-count></list-count><list :itemList="items"></list>`,
methods: {
addItem(i) {
this.items.push(i)
}
},
provide() {
return {
listLength: Vue.computed(() => this.items.length)
}
},
}
const app = Vue.createApp(MyApp)
const ItemListCount = {
inject: ["listLength"],
template: `<p> {{ this.listLength }}</p>`
}
const ItemList = {
props: ["itemList"],
template:`<ul>
<list-item v-for="(item, index) in itemList" :i="item" :idx="index"></list-item>
</ul>`
}
const ItemListItem = {
props: ["i", "idx"],
template: `<li>{{ idx }}, {{ i.message }}</li>`
}
app.component("list", ItemList)
app.component("list-item", ItemListItem)
app.component("list-count", ItemListCount)
const vm = app.mount('#app')
Because the item lists sit in components that are subject to routing, I'd rather use Provide/Inject vs. having to figure out how to pass props down the chain and through the router. The following app.js, which uses provide/inject instead of props for the list, does not work.
app.js (with provide/inject) ... note the removal of the :itemList binding, the addition of the itemList in provide(), and the change in the ItemList component to use inject instead of props.
const MyApp = {
data() {
return {
items: []
}
},
template: `<list-count></list-count><list></list>`,
methods: {
addItem(i) {
this.items.push(i)
}
},
provide() {
return {
listLength: Vue.computed(() => this.items.length),
itemList: Vue.computed(() => this.items)
}
},
}
const app = Vue.createApp(MyApp)
const ItemListCount = {
inject: ["listLength"],
template: `<p> {{ this.listLength }}</p>`
}
const ItemList = {
inject: ["itemList"],
template:`<ul>
<list-item v-for="(item, index) in itemList" :i="item" :idx="index"></list-item>
</ul>`
}
const ItemListItem = {
props: ["i", "idx"],
template: `<li>{{ idx }}, {{ i.message }}</li>`
}
app.component("list", ItemList)
app.component("list-item", ItemListItem)
app.component("list-count", ItemListCount)
const vm = app.mount('#app')
The above raises the following error in console:
Uncaught TypeError: i is undefined
I assume the error is because the v-for loop isn't working properly with the inject: ["itemList"] whereas it seems to be working fine with a props: ["itemList"]. I can't find any relevant docs that would explain why this is the case. How do I fix the provide/inject version?
It looks like you have some unnecessary stuff inside your provide function.
This should do the job:
provide() {
return {
itemList: this.items
};
}
See this working codepen example
Update
Also, I'm assuming that you're using this code as an example of some more complex operation that actually justifies the use of provide/inject. Otherwise it's an overkill for a list component, and you should simplify it like this
const MyApp = {
data() {
return {
items: []
};
},
template: `
<button #click="addItem(Math.floor(Math.random() * 100))">Add Item</button>
<list :items="items"></list>`,
methods: {
addItem(i) {
this.items.push(i);
}
}
};
const app = Vue.createApp(MyApp);
const ItemList = {
props: { items: Array },
template: `
<p>{{ items.length }}</p>
<ul>
<li v-for="(item, index) in items">{{ index + ', ' + item }}</li>
</ul>`
};
app.component("list", ItemList);
app.mount("#app");
having this very bizarre problem with my gatsby project where the SEO component (quite similar to the default on suggested in docs) is effecting my page layout. no matter where i put the SEO component (inside or outside the layout wrapper component my navbar seems to be effected... very strange because the seo component from what i can see has no stylings or jsx or css or anything. its just a way to add meta tags for SEO.. can someone help? here is my page layout (using basic react context and seo compoennt here to inject meta deatail)
<NavActive.Provider value={active}>
<SEO image={logo} />
<Layout active={active} setActive={setActive}>
<div className={`${active&&'body-active'}`}>
<Banner />
<Column />
<Paragraph text={text} header/>
<Blackbar />
<Paragraph text={text} />
<Blackbar button />
<Split />
<div className='c'>
<Blackbar />
</div>
</div>
</Layout>
</NavActive.Provider>
and then here is the way my seo compoennts is structured. have NO idea what could be causing this!
/**
* SEO component that queries for data with
* Gatsby's useStaticQuery React hook
*
* See: https://www.gatsbyjs.org/docs/use-static-query/
*/
import React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
import { useLocation } from "#reach/router"
import { useStaticQuery, graphql } from "gatsby"
function SEO({ description, lang, meta, image, title }) {
const { pathname } = useLocation()
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
author
image
url
}
}
}
`
)
const seo = {
title: title || "Orcawise - Start a willing conversation",
description: site.siteMetadata.description,
image: image || `${site.siteMetadata.url}${site.siteMetadata.image}`,
url: `${site.siteMetadata.url}${pathname}`,
}
return (
<>
<Helmet>
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
{/* <meta property="og:image" content={seo.image} />
<meta property="og:url" content={seo.url} />
<meta property="twitter:image" content={seo.image} />
<meta property="og:description" content={seo.description} />
<meta name="author" content={seo.author} /> */}
</Helmet>
<Helmet
htmlAttributes={{
lang,
}}
title={seo.title}
titleTemplate={`%s | ${seo.title}`}
meta={[
{
property: `og:title`,
content: seo.title,
},
{
name: `description`,
content: seo.description,
},
{
name: `author`,
content: seo.author,
},
{
property: `og:description`,
content: seo.description,
},
{
property: `og:url`,
content: seo.url,
},
{
property: `og:image`,
content: seo.image,
},
{
property: `og:type`,
content: `website`,
},
{
name: `twitter:card`,
content: `summary_large_image`,
},
{
name: `twitter:image`,
content: seo.image,
},
{
name: `twitter:creator`,
content: seo.author,
},
{
name: `twitter:title`,
content: seo.title,
},
{
name: `twitter:description`,
content: seo.description,
},
].concat(meta)}
/>
</>
)
}
SEO.defaultProps = {
lang: `en`,
meta: [],
description: ``,
image: null,
url: ``,
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
image: PropTypes.string,
url: PropTypes.string,
}
export default SEO
very strange because the seo component from what i can see has no
stylings or jsx or css or anything. its just a way to add meta tags
for SEO.. can someone help?
Well, you are adding Bootstrap styles in:
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
Try removing/commenting it to see if it affects the layout. The rest of the component seems quite standard.
I am using the Quill editor in Vue.js and it's working great. I have images, etc.
But...the link isn't working. I tried both the "snow" and "bubble" themes.
I type the text, highlight it and then click on the "link". I get the dialog to set the link, but then the link isn't there.
It's working in the JavaScript version, but not the Vue.
Below is my code.
Vue.component('editor', {
template: '<div ref="editor"></div>',
props: {
value: {
type: String,
default: ''
}
},
data: function() {
return {
editor: null
};
},
mounted: function() {
this.editor = new Quill(this.$refs.editor, {
modules: {
toolbar: [
[{ header: [1, 2, 3, 4, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block', 'link']
]
},
//theme: 'bubble',
theme: 'snow',
formats: ['bold', 'underline', 'header', 'italic', 'link'],
placeholder: "Type something in here!"
});
this.editor.root.innerHTML = this.value;
this.editor.on('text-change', () => this.update());
},
methods: {
update: function() {
this.$emit('input', this.editor.getText() ? this.editor.root.innerHTML : '');
}
}
})
new Vue({
el: '#root',
data: {
//model: 'Testing an editor'
model: '',
isShowing: true
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<link href="https://cdn.quilljs.com/1.3.4/quill.snow.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://cdn.quilljs.com/1.3.4/quill.core.css" rel="stylesheet"/>
<!DOCTYPE html>
<html>
<head>
<title>Trying to use the Quill Editor in Vue</title>
</head>
<body>
<div id="root">
<div v-if="isShowing">
<editor v-model="model"></editor>
</div>
<p>I need the v-html directive: <span v-html="model"></span></p>
<p>Raw data: <pre>{{ model }}</pre></p>
<button #click="isShowing = !isShowing">Toggle</button>
</div>
</script>
</body>
</html>
Any help is greatly appreciated.
Thanks, D
I had to place a 'link' into the "formats" as well:
formats: ['bold', 'underline', 'header', 'italic', 'link'],
I updated my code snippet with the correct answer in case anyone else is having this problem.
Thanks!
I'm trying to pre-process a Vue 2 template and get a list of all of the element bindings. So if I have a file like this:
<html>
<body>
<div id="app">
<p>Here's a message: {{message1}}</p>
<p>Here's an input: <input type="text" v-model="message2"></p>
</div>
<script type="application/javascript" src="vue.js"></script>
<script type="application/javascript">
new Vue({
el: "#app",
data: {
message1: "foo",
message2: "bar"
}
});
</script>
</body>
</html>
Then somewhere (beforeMount?) I could query Vue and it would tell me the bindings are ['message1', 'message2']. Is that possible?
I ended up solving this by getting the text of the render function (by calling vm.$options.render.toString() ) and then parsing the bindings from that.
For instance, the rendering function for a simple list view looks like this:
function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"table",
{ attrs: { border: "1", cellpadding: "5", cellspacing: "0" } },
[
_vm._m(0),
_vm._l(_vm.rows, function(row) {
return _c("tr", [
_c(
"td",
[
_c("router-link", { attrs: { to: "/detail/" + row.ID } }, [
_vm._v(_vm._s(_vm._f("truncate")(row.TITLE, 100)))
])
],
1
),
_c("td", [_vm._v(_vm._s(_vm._f("truncate")(row.DESCRIPTION, 200)))]),
_c("td", [_vm._v(_vm._s(row.TYPE))])
])
})
],
2
)
}
It looks like the bindings are always contained in an _s() element, and optionally a vm.f() instruction when using filters.
<template>
<div id="app" class="phone-viewport">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<router-view></router-view>
<bottomBar v-bind:visibles='show' ></bottomBar>
</div>
</template>
<script>
export default {
name: '',
show: '',
data () {
return {
visibles: [
{name: 'Football', show: true},
{name: 'Basketball', show: true},
{name: 'Hockey', show: true},
{name: 'VolleyBall', show: false},
{name: 'Baseball', show: false},
]
}
}
}
</script>
I'm looking to hide the bottomBar just on VolleyBall and Beisbol .
But I always have this error "Property or method "show" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
"
<script>
export default {
name: 'app',
data () {
return {}
},
computed: {
hideBottom: function () {
if (this.$router.path === '/baseball' || this.$router.path === '/volleyball') return false
else { return true }
}
}
}
Baseball
You are calling method show which does not exist, that's why you are getting that error.
As I understand your question, you want to hide that component on particular routes?
If so, You need to create computed variable which will determine if it should be shown or not. e.g.:
computed: {
toShowOrNotToShow: function () {
if(this.$router.path === '/baseball' || this.$router.path === '/volleyball') return false;
else
return true;
}
}
Just use it: <bottomBar v-if='toShowOrNotToShow' ></bottomBar>