This should be simple but despite searching I was unable to find any solution to this. How do you use vue template tags within a liquid file? Since both Vue and liquid use the same curly brackets, I'm unable to render any of my view data:
<img src="{{ product.featured_image }}" />
results in:
<img src>
There are 36 products in my parent view component.
When I try to use custom delimiters:
new Vue({
delimiters: ['#{{', '}}'],
It won't parse with Vue:
GET https://inkkas.com/collections/# 404 (Not Found)
UPDATE: I'm able to access Vue data with v-bind: but I still need to be able to use delimiters also.
Apparently with Shopify, you can't put these delimiters in the tag attributes at all so for those use v-bind: (the shorthand won't work). Also you have to use a single curly brace for your custom delimiter or it will still try to parse with liquid, for example:
delimiters: ['${', '}']
will work with:
<span class="title">${ product.title }</span>
Adding on a bit from where Kevin Compton left off, this is where you put the "delimiters" parameter:
const ConditionalRendering = {
data() {
return {
seen: true,
someMessage: "My message"
}
},
delimiters: ['${', '}']
}
Vue.createApp(ConditionalRendering).mount('#conditional-rendering')
Related
I'm using vue.js (v2.6.12) components in laravel blade templates.
For the project, I'm also using MathML in which I need to use the open attribute of <mfenced> tag to be set to some custom values. Here is the example of the math expressing in mathml.
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>f</mi>
<mfenced close="]" open="[">
<mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
</mfenced>
</math>
But as soon as the page renders, the open attribute is converted into this open="open". I'm 100% sure there is no other library or script is loaded that updates like so, just plain vue. This actually breaks the math expression. So it looks like this:
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>f</mi>
<mfenced close="]" open="open">
<mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
</mfenced>
</math>
Later I realized that not only in math expression, litaratily any tag, be it <div open="anything">...</div>, <span open="anything">...</span>, <custom-element open="something">...</custom-element> having open attribute behaves the same. even if I use v-pre attribute to exclude it from vue js templete compiler.
And this do not happen, as soon I disable the vue app initialization.
The question here are:
Why vue is changing the open attribute like so?
How can I stop this behaviour, to the entire page within the vue application area or at least where I choose (something like using v-pre), is there ary config or any other way around?
Why
In HTML spec there are some attributes called boolean attributes. Spec dictates what can be a value of such attribute:
If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute's canonical name, with no leading or trailing whitespace.
The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.
open is one of the boolean attributes - it is defined for the <details> element
Problem with Vue 2 is, that it treats most of the boolean attributes as global - without considering the element it is placed on. Result is that open attribute is always rendered with value "open" or removed if the value is falsy (when v-binding). This is fixed in Vue 3 as shown in 2nd example...
How
The use of v-pre is the way to go but unfortunately for you there is a bug.
See this issue. The bug was already fixed with this commit(Sep 21, 2020) but it was not released yet...
example - the "With v-pre" should work in Vue version > 2.6.12
const vm = new Vue({
el: '#app',
data() {
return {
message: 'Hi!',
html: `<div open="[" close="]">Hi from html</div>`
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<div open="[" close="]">{{ message }}</div>
<div v-html="html"></div>
<div v-pre>
<p open="[" close="]">With v-pre</p>
</div>
</div>
example - it works in Vue 3 - open is treated as boolean attribute only if placed on <details>
const app = Vue.createApp({
data() {
return {
message: 'This works in Vue 3!',
}
},
})
app.mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.11/vue.global.js" integrity="sha512-1gHWIGJfX0pBsPJHfyoAV4NiZ0wjjE1regXVSwglTejjna0/x/XG8tg+i3ZAsDtuci24LLxW8azhp1+VYE5daw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="app">
<div open="[" close="]">{{ message }}</div>
<details open="[">
<summary>Details</summary>
open attribute on details element is treated as boolean (renders empty value)
</details>
</div>
One workaround is to create a directive (named "attr") that sets the attribute:
Vue.directive('attr', (el, binding) => el.setAttribute(binding.arg, binding.value || ''))
Then use it in your template like v-bind but with v-attr:
<mfenced v-attr:open="'['">
Vue.directive('attr', (el, binding) => el.setAttribute(binding.arg, binding.value || ''))
new Vue({ el: '#app' })
<script src="https://unpkg.com/vue#2.6.12"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<div id="app">
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>f</mi>
<mfenced close="]" v-attr:open="'['">
<mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
</mfenced>
</math>
</div>
I've found a simple hack to solve this problem.
Why hack?
Because it is eventually going to be fixed in the comming release as pointed by #Michal, so just a quick & dirty hack is enough for now to go for it.
What I did is I placed the math content in the content and also added it to the data attribute and replacing the original content after vue has done its bad work (sorry just using blade syntax here, but it will make sense). I keep it in both places just for SEO purposes.
The template where I need math expression to be displayed.
...
<div class="proxy-content" data-proxy-content="{{ $article->content }}">
{!! $article->content !!}
</div>
...
I was using it along with jQuery, but you can easily substitute with vue.js' $el. This is what it looks in my app.js file.
...
const app = new Vue({
el: '#app',
methods: {
proxyContent() {
// Set Proxy Content.
jQuery('.proxy-content').each((i, el) => {
const $el = jQuery(el);
$el.html( jQuery('<textarea />').html( $el.data('proxy-content')).text() );
});
}
loadMathJax() {
// Load & Initialize MathJax Library.
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://cdn.jsdelivr.net/npm/mathjax#3/es5/tex-mml-chtml.js";
document.getElementsByTagName("head")[0].appendChild(script);
}
}
mounted(){
// Enable proxy content after mount, so we are sure no more rendering issue for templates.
this.proxyContent();
// Load MathJax library with a little delay to make sure everything is ready before loading the library.
setTimeout(() => this.loadMathJax(), 10);
}
});
...
One might argue, that I'm mixing up things outside of the scope of the vue application. For me that is not an issue, as the whole page is using vue.js and also the single thing don't make any harm even if there is another scope that is using mathml (though it depends on actual implementation).
In that case, if you want to scope it well, just use $el of vue.
I am currently using the bootstrap table component to display a list (bootstrap-table.com)
This component (or jQuery plugin wrapped within a vue component) has a formatter for each column.
example with source code: https://examples.bootstrap-table.com/#welcomes/vue-component.html#view-source
I need to build a more complex set of links which involve some js processing that could easily be done with vue.
Instead of the formatter returning the <a> tag I would like to return the contents of a <component>. I have tried this but haven't been able to render a component.
I'm almost sure this is impossible because the child component is not previously binded which means the component won't be "reactive".
As an alternative I can build a js function that generates all the required links.
Another phrasing for this question would be: Is there any possibility to generate a vue component from vanilla js?
The above was not possible the way was tried because the components cannot be simply rendered like html tags as they can have methods and computed data therefore they must be compiled.
It looks like the solution for the above situation is using the Vue.compile() method:
const template = `
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>`;
const compiledTemplate = Vue.compile(template);
new Vue({
el: '#app',
data() {
return {
items: ['Item1', 'Item2']
}
},
render(createElement) {
return compiledTemplate.render.call(this, createElement);
}
});
Demo:
https://codepen.io/couellet/pen/ZZNXzy
Demo reference:
https://snipcart.com/blog/vue-render-functions
Vue docs:
https://v2.vuejs.org/v2/api/#Vue-compile
I have a simple Vue.js app, with this template:
<h1>{{ name || '<New Document>' }}</h1>
My goal is that if name is falsy, to use the text <New Document>. This is not intended to be a custom markup tag. I want Vue.js to insert this into the document:
<h1><New Document></h1>
Instead, I get this warning on the console:
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
According to the documentation, using a pair of curly brackets, {{ and }}, means text interpolation, and the text value of that expression will be used. Instead, Vue.js seems to want to treat it as HTML.
Why is that happening? How can it be resolved?
This is a great question. Like you, I assumed that everything between the curly braces would be evaluated as an expression and injected into the template. That's what the documentation implies, and in all cases I've encountered this appears to be true... except when there could be an HTML tag in a string literal. At least, that's why my experiments seem to indicate.
Take the following example:
<h1>{{ name || '<span>hello</span>' }}</h1>
There are two ways the parser could read this:
An h1 tag containing curly braces - within those, an expression we need to evaluate later.
An h1 tag followed by the string {{ name || ', then a span, then another string ' }}.
In this case, the parser is built to choose (2), and this explains why you received a compile-time error. The parser indicated that you have a tag starting with <New Document>, but it didn't have a corresponding closing tag.
If this seems like an odd design choice, consider this code:
<h1>{{'<span>hello</span>'}}</h1>
What did the user intend here? Did he/she mean to surround a span with curly braces and quotes? Probably.
As for a solution, you could manually escape the string:
{{ name || '<New Document>' }}
Alternatively, you could solve this with a computed property, which will eschew the template parser altogether:
<template>
<h1>{{ nameOrNew }}</h1>
</template>
<script>
export default {
data() {
return {
name: false,
};
},
computed: {
nameOrNew() {
return this.name || '<New Document>';
},
},
};
</script>
Vue's template parser parses HTML first (split into tags and their content) and only then parses tags attributes and texts.
For your template it will be something like:
tag: <h1>
|
+- text: "{{ name || '"
|
+- tag: <New> (attributes: ["Document"])
|
+- text: "' }}"
You should think about template as a valid HTML first, with Vue's interpolations added later.
Documentation also states that:
All Vue.js templates are valid HTML that can be parsed by spec-compliant browsers and HTML parsers.
Ref: compiler/parser/html-parser.js
you can include all possible ignored elements in this config Vue.config.ignoredElements
Vue.config.ignoredElements = ['New Document'];
i hope it helps
The reason for the behaviour isn't clear to me, but here are two possibilities if you just need it to work:
<h1 v-if="name">{{ name }}</h1>
<h1 v-else><New Document></h1>
Or, as already pointed out in another answer:
<h1>{{ name || '<New Document>' }}</h1>
I got this error
Interpolation inside attributes has been removed. Use v-bind or the
colon shorthand instead. For example, instead of <div id="{{ val }}">,
use <div :id="val">.
on this line
<a href="/Library/#Model.Username/{{myVueData.Id}}">
It works in Angular 1. How do you do it in Vue?
In your template:
<a :href="href">
And you put href in data:
new Vue({
// ...
data: {
href: 'your link'
}
})
Or use a computed property:
new Vue({
// ...
computed: {
href () {
return '/foo' + this.someValue + '/bar'
}
}
})
Just complementing ... solve the interpolation error (simple solution, I am Junior front-end developer):
Example post object in a loop:
instead of
<a href="{{post.buttonLinkExt}}">
try this way
<a v-bind:href="post.buttonLinkExt">
Use javascript code inside v-bind (or shortcut ":") :
:href="'/Library/#Model.Username' + myVueData.Id"
and
:id="'/Library/#Model.Username' + myVueData.Id"
Update Answer
Some directives can take an “argument”, denoted by a colon after the directive name. For example, the v-bind directive is used to reactively update an HTML attribute:
<a v-bind:href="url"></a>
Here href is the argument, which tells the v-bind directive to bind the element’s href attribute to the value of the expression url. You may have noticed this achieves the same result as an attribute interpolation using href="{{url}}": that is correct, and in fact, attribute interpolations are translated into v-bind bindings internally.
Found in Google this topic when searching $attrib.
Question don't specify what value is used (maybe not defined before)
For ANY parent attribute or to FILTER it, use something like that:
<template>
<component
is="div"
v-bind="$attrs"
class="bg-light-gray"
>
EXAMPLE
</component>
</template>
This instruct to create specific, dynamic and context aware, wrapper:
v-bind="$attrs" instruct to take all sended params. Not needed to declare as param object in script.
Work even with valid html attribute like class
example above mix static class with parent and join it. Use ternary operator (x=1?x:y) to choose proper one.
bonus: by "is" you can dynamically set tag like header or secion instead of div
$attrs can be binded to any tag in component so this easily enable simple transmission for one tag dynamic attributes like you define class for <input /> but wrapper and actions are added in component
Source with description: https://youtu.be/7lpemgMhi0k?t=1307
you can either use the shorthand : or v-bind
<div>
<img v-bind:src="linkAddress">
</div>
new Vue({
el: '#app',
data: {
linkAddress: 'http://i3.kym-cdn.com/photos/images/newsfeed/001/217/729/f9a.jpg'
}
});
or for when you need more than just binding an attribute you can also do:
new Vue({
el: '#app',
data: {
finishedLink: ' Google '
}
});
I had the following in Vue 1.x
<tr v-for="product in products">
<td><img src="{{ product.image_url | thumbnail }}" class="thumbnail"></td>
</tr>
But in Vue 2 I tried:
<tr v-for="product in products">
<td><img :src="product.image_url | thumbnail" class="thumbnail"></td>
</tr>
and got "Property or method "thumbnail" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option"
note: The regular mustache interpolation filter doesn't work when I'm assigning it to a property on a html element (ie: {{ data | filter }} works fine as plain text but not when trying to do src="{{ data | filter }}".
I tried a computed property but it didn't work as the element I'm trying to get a computed value is each element within an array (and I'm looping through each element in the array).
All thumbnail does is do some regex and fancy text replacement. Not sure the best way to do this in vue2.
Vue.js 2.0
Filters can now only be used inside text interpolations ({{}} tags). In the past we've found using filters with directives such as v-model, v-on etc. led to more complexity than convenience, and for list filtering on v-for it is more appropriate to move that logic into JavaScript as computed properties.
Using computed property:
new Vue({
el: '#app',
data: {
products: [],
},
computed: {
filterProducts() {
return this.products.filter(function(product) {
...
})
}
}
})
Vue.js expects you to define your filters before you use them in templates. Here is an example of how you would define a date formatting filter:
// Define the date time format filter
Vue.filter("formatDate", function(date) {
return moment(date).format("MMMM D, YYYY")
})
Once you have that, you are allowed to use it in code as follows:
<div class="due-date">{{dueDate | formatDate}}</div>
Can you tell me what the thumbnail filter is supposed to do? For images, I don't think there is any processing you can do on the client side. You can show thumbnails in some pre-defined size, which is something you would do in CSS, not using filters.