How to dynamically render strings in Vue? - vue.js

I am pulling an array on images from Netlify CMS and passing that to vue-picture-swipe component, but the acutal images don't render, even though the path is correct etc.
Not sure what I am doing wrong?
Template
vue-picture-swipe(:items="items")
Script
<script>
export default {
data: function() {
return {
items: []
};
},
created: function () {
this.imageList()
},
methods: {
imageList: function () {
const files = require.context('~/content/gallery/images/', false, /\.md$/);
let images = files.keys().map(key => ({
...files(key)
}));
let items = images.map(function(value) {
return {
'src': value.attributes.imgSrc,
'thumbnail': value.attributes.imgSrc,
'alt': value.attributes.imgDesc
}
});
return this.items = items
}
}
};
</script>
Rendered HTML
<img src="/assets/uploads/image.jpg" alt="Test Image description" itemprop="thumbnail">

According to your output, if value.attributes.imgSrc renders a relative path like src="/assets/uploads/image.jpg", try to require this path.
I'm assuming your "assets" and "components" folders are directly under "src":
let items = images.map(function(value) {
return {
'src': require('..' + value.attributes.imgSrc), // or require('#' + value.attributes.imgSrc)
'thumbnail': require('..' + value.attributes.imgSrc),
'alt': value.attributes.imgDesc
}
})
Note that vue-picture-swipe items props need w and h attributes for each item.

Related

How Do I Display Product Images In Shopware 6 Administration

I am working on a Shopware 6 Administrative plugin but displaying product images has been a big headache. I went through shopware repositories of 'product_media', 'media', and generally all folder repositories related to media.
I have not been able to decipher how image linking works since I can not get hold of the exact folder names.
How then do I go about this. Note: I have been able to get correct image names and relevant image data like id, folder id etc.
Below is my module/component idex.js file codes
import template from './images-page.html.twig';
const { Component, Context } = Shopware;
const { Criteria } = Shopware.Data;
Component.register('images', {
template,
inject: ['repositoryFactory', 'mediaService', 'acl'],
metaInfo() {
return {
title: 'images'
};
},
computed: {
/**productMediaRepository() {
return this.repositoryFactory.create(this.product.media.entity);
},*/
productRepository() {
return this.repositoryFactory.create('product');
},
mediaFolderRepository() {
return this.repositoryFactory.create('media_folder');
},
mediaRepository() {
return this.repositoryFactory.create('media');
},
rootFolder() {
const root = this.mediaFolderRepository.create(Context.api);
root.name = this.$tc('sw-media.index.rootFolderName');
root.id = null;
return root;
},
logRep(){
console.log(this.productRepository);
// console.log(this.mediaFolderRepository);
// console.log(this.mediaRepository);
// console.log(this.rootFolder);
}
},
methods: {
logProducts(){
const criteria = new Criteria();
this.productRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result[0]);
});
},
logMediaFolders(){
const criteria = new Criteria();
this.mediaFolderRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result);
});
}
},
created(){
this.logMediaFolders();
}
});
here's the twig template (nothing really here)
<sw-card title="Watermark">
<img src="" alt="Product Image" />
</sw-card>
The media elements of the products are associations that are not loaded automatically, but you can configure the criteria, so that those associations will be loaded directly when you fetch the products. Refer to the official docs for detailed infos.
In you case that means to load the cover image / or all product images, you would have to adjust the criteria you use for fetching the products the following way
logProducts(){
const criteria = new Criteria();
criteria.addAssociation('cover.media');
criteria.addAssociation('media.media');
this.productRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result[0]);
});
},
Then to link to the cover image you can use:
<sw-card title="Watermark">
<img src="product.cover.media.url" alt="Product Image" />
</sw-card>
You find all the media elements of the product as an array under product.media and can also use product.media[0].media.url to link to those images.

Vue template fires more than once when used, i think i need a unique key somewhere

I am trying to implement font-awesome-picker to a website that i am making using vue2/php/mysql, but within standard js scripting, so no imports, .vue etc.
The script i am trying to add is taken from here: https://github.com/laistomazz/font-awesome-picker
The problem that i am facing is that i have 3 columns that have a title and an icon picker next it, that will allow the user to select 1 icon for each title. It is kinda working well...but if the same icon is used in 2 different columns then any time the user clicks again any of the 2 icons both instances of the picker will fire up, thus showing 2 popups. I need to somehow make them unique.
I've tried using
:key="list.id"
or
v-for="icon in icons" :icon:icon :key="icon"
but nothing worked. Somehow i have to separate all the instances (i think) so they are unique.
This is the template code:
Vue.component('font-awesome-picker', {
template: ' <div><div class="iconPicker__header"><input type="text" class="form-control" :placeholder="searchPlaceholder" #keyup="filterIcons($event)" #blur="resetNew" #keydown.esc="resetNew"></div><div class="iconPicker__body"><div class="iconPicker__icons"><i :class="\'fa \'+icon"></i></div></div></div>',
name: 'fontAwesomePicker',
props: ['seachbox','parentdata'],
data () {
return {
selected: '',
icons,
listobj: {
type: Object
}
};
},
computed: {
searchPlaceholder () {
return this.seachbox || 'search box';
},
},
methods: {
resetNew () {
vm.addNewTo = null;
},
getIcon (icon) {
this.selected = icon;
this.getContent(this.selected);
},
getContent (icon) {
const iconContent = window
.getComputedStyle(document.querySelector(`.fa.${icon}`), ':before')
.getPropertyValue('content');
this.convert(iconContent);
},
convert (value) {
const newValue = value
.charCodeAt(1)
.toString(10)
.replace(/\D/g, '');
let hexValue = Number(newValue).toString(16);
while (hexValue.length < 4) {
hexValue = `0${hexValue}`;
}
this.selecticon(hexValue.toUpperCase());
},
selecticon (value) {
this.listobj = this.$props.parentdata;
const result = {
className: this.selected,
cssValue: value,
listobj: this.listobj
};
this.$emit('selecticon', result);
},
filterIcons (event) {
const search = event.target.value.trim();
let filter = [];
if (search.length > 3) {
filter = icons.filter((item) => {
const regex = new RegExp(search, 'gi');
return item.match(regex);
});
}else{
this.icons = icons;
}
if (filter.length > 0) {
this.icons = filter;
}
}
},
});
I've setup a fiddle with the problem here:
https://jsfiddle.net/3yxk1ahb/1/
Just pick the same icon in both cases, and then click any of the icons again. You'll see that the popups opens for both columns.
How can i separate the pickers ?
problem is in your #click and v-show
you should use list.id instead of list.icon (i.e #click="addNewTo = list.id")
working fiddle https://jsfiddle.net/q513mhwt/

Getting reactivity from watch in Vue.js

Trying to make a component in Vue.js, which first shows image via thumbnail, loading full image in background, and when loaded, show full image.
The thing which does not work, component does not react on change of showThumb flag in watch section. What is wrong?
Vue.component('page-image',
{
props: ['data'],
template:
'<img v-if="showThumb == true" v-bind:src="thumbSrc"></img>'+
'<img v-else v-bind:src="fullSrc"></img>',
data: function()
{
return { thumbSrc: '', fullSrc: '', showThumb: true };
},
watch:
{
data: function()
{
this.thumbSrc = data.thumbImg.url;
this.fullSrc = data.fullImg.url;
this.showThumb = true;
var imgElement = new Image();
imgElement.src = this.fullSrc;
imgElement.onload = (function()
{
this.showThumb = false; // <<-- this part is broken
} );
}
}
} );
Note: there is a reason why I do it via 2 img tags - this example is simplified.
Your onload callback will have a different scope than the surrounding watch function, so you cannot set your data property like this. Change it to an arrow function to keep scope:
imgElement.onload = () =>
{
this.showThumb = false;
};
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Vue.js - Element UI - HTML message in MessageBox

I'm using vue-js 2.3 and element-ui. This question is more specific to the MessageBox component for which you can find the documentation here
Problem
I'd like to be able to enter html message in the MessageBox
More specifically I would like to display the data contained in dataForMessage by using a v-for loop.
Apparently, we can insert vnode in the message but I have no idea where to find some information about the syntax.
https://jsfiddle.net/7ugahcfz/
var Main = {
data:function () {
return {
dataForMessage: [
{
name:'Paul',
gender:'Male',
},
{
name:'Anna',
gender:'Female',
},
],
}
},
methods: {
open() {
const h = this.$createElement;
this.$msgbox({
title: 'Message',
message: h('p', null, [
h('span', null, 'Message can be '),
h('i', { style: 'color: teal' }, 'VNode '),
h('span', null, 'but I would like to see the data from '),
h('i', { style: 'color: teal' }, 'dataForMessage'),
])
}).then(action => {
});
},
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
I think this is what you want.
methods: {
open() {
const h = this.$createElement;
let people = this.dataForMessage.map(p => h('li', `${p.name} ${p.gender}`))
const message = h('div', null, [
h('h1', "Model wished"),
h('div', "The data contained in dataForMessage are:"),
h('ul', people)
])
this.$msgbox({
title: 'Message',
message
}).then(action => {
});
},
}
Example.
You can also use html directly and convert to vnodes by using domProps:
const html = '<div><h1>Model wished</h1><div>The data contained in dataForMessage are:</div><ul><li>Paul Male</li><li>Anna Female</li></ul></div>'
const message = h("div", {domProps:{innerHTML: html}})
(The above is simplified without the loop. Just to get the idea)
Fiddle

Vuejs2 issue passing data changes between children

Another issue with a project I am working on.
I have the following vuejs2 parent-child structure
Parent app
child product-list
product
product-image
colour-select
Within the product template I initiate the sibling components
The colourSelect component takes a comma delimited string and turns it into a drop down list. Whenever the selected option changes it emits the colour back to the product component which has a data variable "colour"
This appears to work fine.
The product-image component takes the product colour as a prop.
Whenever the colour changes I want the product-image component to detect it and trigger it to go get the relevant image. But its not detecting the change in colour.
Vue.component('product', {
props: ['productID', 'images', 'product'],
data: function () {
return {
colour: 'Navy',
}
},
computed: {
returnColour: function (colour) {
// this.colour = colour
//return colour
}
},
template: '<transition name="list"><li class="moving-item" id="productID">' +
'<product-image :productID="productID" :images="getImage(product.productID)" :colour="colour"></product-image>' +
'<colourSelect :colours="product.colour" :productID="product.productID" v-on:set-colour="setColour(colour)"></colourSelect>' +
'</li></transition>',
methods: {
getImage: function (listItemId) {
var images = this.images.filter(function (item) {
return returnCleanedData(item.Products_x003a_ID) === listItemId
})
},
setColour: function (colour) {
console.log('in main colour emit')
this.colour = colour
console.log(this.colour)
}
}
});
Vue.component('colourSelect', {
props: ['productID', 'colours', 'set-colour'],
template: '<select v-bind:id="getID()" class="form-control input-xs" :disabled=" isActive" v-bind:class="{disabledSelect: isActive}" v-on:click="setColour(productID)">' +
'<colourOption v-for="colourItem in colourArray">{{ colourItem.colour }}</colourOption>' +
'</select>',
data: function() {
return {
isActive: false
}
},
computed: {
colourArray: function () {
//splits data and applies it to the select
}
},
methods: {
getID: function () {
return 'colourSelect' + this.productID;
},
**setColour: function (productID) {**
//yeah used jquery here
var colour = $('#colourSelect' + productID).val()
this.$emit('set-colour', colour)
}
}
});
Vue.component('product-image', {
props: ['productID', 'images', 'colour'],
template: '<p><slot></slot><img :src="getImage(productID, images, colourSelected)" class="productImagePosition img-responsive img-rounded"></img></p>',
data: function () {
return {
isActive: false,
selectedColour: this.colour
}
},
computed: {
colourSelected: function () {
console.log('colour change detected')
return this.colour
}
},
methods: {
getID: function (test) {
return 'colourSelect' + this.productID;
},
getImage: function (listItemId, images, colour) {
console.log('selected colour')
console.log(colour)
//filter images to the specific colour and return link
},
}
});
The issue appears to be related to this line in the product template
v-on:set-colour="setColour(colour)"
When the child component emits the set-colour data back, the product is correctly running this method. But the product-image doesn't detect the change to its prop.
If i change the line to v-on:set-colour="setColour()" it will actually detect the change in the product-image but will error due to no data being passed.
Within the product-image component I have tried referencing a computed value (colourSelected) instead of the prop within the template which has no effect.
Any ideas?
Thanks
On product-image add a watcher to the colour prop:
watch: {
colour(value) {
// make changes here
},
},