Custom hit template with Algolia in Shopify - javascript

When installing the Algolia plugin in Shopify, they add a file algolia_instant_search.js.liquid and in that file, there is a code part;
// Hits
instant.search.addWidgets([
hits({
container: '.ais-hits-container',
templates: {
empty: instant.templates.empty,
item: instant.templates.product,
},
cssClasses: {
list: 'row',
item: 'col-6 col-sm-6 col-lg-3 col-md-4 col-sm-6 mb-45'
},
transformItems: function(products) {
return products.map(function(product) {
return algolia.assign({}, product, {
_distinct: instant.distinct,
can_order:
product.inventory_management !== 'shopify' ||
product.inventory_policy === 'continue' ||
product.inventory_quantity > 0,
translations: algolia.translations,
queryID: product.__queryID,
productPosition: product.__position,
index: instant.search.mainIndex.getIndexName(),
});
});
},
}),
]);
I want to change the hit template so normally I will add some HTML like this;
// Hits
instant.search.addWidgets([
hits({
container: '.ais-hits-container',
templates: {
empty: instant.templates.empty,
item: `
<div class="single-product">
<div class="single-product__image"><!-- Product Image Lazyload with Retina -->
<a class="image-wrap" href="/collections/all/products/grey-tool">
<img class="responsive-image__image popup_cart_image" src="https://cdn.shopify.com/s/files/1/0559/2632/5423/products/2_540x.jpg?v=1617194828">
</a>
<div class="single-product__floating-badges">
<span class="soldout-title">Soldout</span>
</div>
<div class="single-product__content ">
<div class="title"><h3 class="popup_cart_title"> 1. Side Ottoman</h3>
<div class="product-cart-action">
<button class="cart-disable">
<span class="cart-text">Soldout</span>
</button>
</div>
</div>
</div>
<div class="price">
<span id="product_current_price" class="discounted-price">€110,00</span>
<span class="main-price discounted ">€130,00</span>
</div>
</div>
</div>
`,
},
cssClasses: {
list: 'row',
item: 'col-6 col-sm-6 col-lg-3 col-md-4 col-sm-6 mb-45'
},
transformItems: function(products) {
return products.map(function(product) {
return algolia.assign({}, product, {
_distinct: instant.distinct,
can_order:
product.inventory_management !== 'shopify' ||
product.inventory_policy === 'continue' ||
product.inventory_quantity > 0,
translations: algolia.translations,
queryID: product.__queryID,
productPosition: product.__position,
index: instant.search.mainIndex.getIndexName(),
});
});
},
}),
]);
But because it is a .liquid file I can't add the Algolia JS tags like {{ product_image }}, any help on how to do this, and maybe some can tell me where I can find the template the originally put in with instant.templates.product

You're using tics (`) for your HTML. Have you tried it with single quotes (') instead?

Related

React DONUT not working properly when using twice on the same page

I'm using donut twice on the same page but somehow it's appearing in one div only whereas I defined it in two different Divs
Library : React-donut
<div className={this.state.selected == 2 ? "col-xs-6 col-sm-6 selected linkpointer" : "col-xs-6 col-sm-6 linkpointer"} onClick={this.programsClickEvent}>
<div className="card p_15 program">
<div className="mahroon-block">
<strong>Programs</strong>
</div>
<div id = "program-donut">
<Donut
chartData={[
{ name: `${this.state.programCompleted} Completed`, data: this.state.programCompleted },
{ name: `${this.state.programStarted} Started`, data: this.state.programStarted },
{ name: `${this.state.programNotStarted} Not Started`, data: this.state.programNotStarted },
]}
chartWidth={100}
chartHeight={300}
title=""
chartThemeConfig={{
series: {
colors: ['#28B463', '#F39C12 ', '#E74C3C'],
},
}}
/>
</div>
</div>
</div>
<div className={this.state.selected == 3 ? "col-xs-6 col-sm-6 selected linkpointer" : "col-xs-6 col-sm-6 linkpointer"} onClick={this.coursesClickEvent} >
<div className="card p_15 course">
<div className="mahroon-block">
<strong>Courses</strong>
</div>
<div id = "course-donut">
<Donut
chartData={[
{ name: `${this.state.courseCompleted} Completed`, data: this.state.courseCompleted },
{ name: `${this.state.courseStarted} Started`, data: this.state.courseStarted },
{ name: `${this.state.courseNotStarted} Not Started`, data: this.state.courseNotStarted },
]}
chartWidth={200}
chartHeight={300}
title=""
chartThemeConfig={{
series: {
colors: ['#28B463', '#F39C12 ', '#E74C3C'],
},
}}
/>
</div>
</div>
</div>
I'm using two different divs for both the donuts but they are appearing in first div only.

How can I get Vue to correctly update data dynamically?

The component I have below allows a user to view a products macro nutrient info and then also modify the serving size which in return updates the macro nutrient amounts.
The issue I'm having is that Im not getting the values to be updated correctly even when using vue set.
I'm using a watcher to run the calcNewNutriValues function.
<template>
<div class="product">
<div class="topbar">
<div class="left">
<p class="left__name">{{ product.name }}</p>
<p class="left__energy">{{ product.energy }}</p>
</div>
<div class="right">
<button class="cancel" #click="removeItem">
<inline-svg
:src="require('../assets/svg/addition-icon.svg')"
></inline-svg>
</button>
</div>
</div>
<div class="details">
<div class="macros">
<p class="details__heading">Macros</p>
<div class="macros__container container">
<div class="wrapper" v-for="(macro, name, index) in product.macros" :key="index">
<p>{{ name }}</p>
<p>{{ product.macros[name] }}</p>
</div>
</div>
</div>
<div class="serving">
<p class="details__heading">Serving Size (g)</p>
<input type="number" placeholder="40" v-model.number="productServSize">
</div>
</div>
</div>
</template>
export default {
data () {
return {
productServSize: 0,
ogServSize: 0,
macros: {
protein: '',
carbs: '',
fats: '',
fibre: ''
},
micros: {},
energy: ''
}
},
props: [
'product',
],
methods: {
serving () {
const num = this.product.servingSize.split(' ')[0]
this.productServSize = parseFloat(num)
this.ogServSize = parseFloat(num)
},
removeItem () {
this.$emit('removeProduct', this.product)
},
calcNewNutriValues () {
Object.keys(this.product.macros).forEach(key => {
let num = parseFloat(this.product.macros[key].split(' ')[0])
let perGram = parseFloat(num / this.ogServSize)
let newTotal = `${(perGram * this.productServSize).toFixed(1)} g`
this.$set(this.macros, key, newTotal)
})
}
},
mounted () {
this.serving()
Object.assign(this.macros, this.product.macros)
this.energy = this.product.energy
},
watch: {
productServSize: {
handler () {
this.calcNewNutriValues()
this.$emit('updatedNutriValues', this.product)
}
}
}
}
It only seems like macros isn't updating because your template displays product.macros instead of macros:
<div class="wrapper" v-for="(macro, name, index) in product.macros" :key="index">
<p>{{ name }}</p>
<!-- <p>{{ product.macros[name] }}</p> DON'T DO THIS -->
<p>{{ macros[name] }}</p>
</div>
demo

How can I work around the limitation of multiple root elements in Vue.js? [duplicate]

This question already has answers here:
A way to render multiple root elements on VueJS with v-for directive
(6 answers)
Closed 2 years ago.
hopefully someone here will be able to help me with this problem.
I have the following data:
[
{
title: 'Header',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
{
title: 'Container',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
]
I want to render this in a list of <div> like this:
<div class="sortable-item" data-depth="1" data-index="0">Header</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
<div class="sortable-item" data-depth="1" data-index="1">Container</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
I have built a component that would be recursive, this is what I have so far:
<template>
<template v-for="(item, index) in tree">
<div
class="sortable-item"
:data-depth="getDepth()"
:data-index="index"
:key="getKey(index)"
>
{{ item.title }}
</div>
<Multi-Level-Sortable
:tree="item.children"
:parent-depth="getDepth()"
:parent-index="index"
:key="getKey(index + 0.5)"
></Multi-Level-Sortable>
</template>
</template>
<script>
export default {
name: 'MultiLevelSortable',
props: {
tree: {
type: Array,
default() {
return [];
},
},
parentDepth: {
type: Number,
},
parentIndex: {
type: Number,
},
},
methods: {
getDepth() {
return typeof this.parentDepth !== 'undefined' ? this.parentDepth + 1 : 1;
},
getKey(index) {
return typeof this.parentIndex !== 'undefined' ? `${this.parentIndex}.${index}` : `${index}`;
},
},
};
</script>
As you can see not only I have a <template> as the root element I also have a v-for, two "no no" for Vue.js. How can I solve this to render the list of elements like I pointed out above?
Note: I have tried vue-fragment and I was able to achieve the structure I wanted, but then when I tried using Sortable.js it didn't work, as if it wouldn't recognise any of the .sortable-item elements.
Any help will be greatly appreciated! Thank you!
Thanks to #AlexMA I was able to solve my problem by using a functional component. Here is what it looks like:
import SortableItemContent from './SortableItemContent.vue';
export default {
functional: true,
props: {
tree: {
type: Array,
default() {
return [];
},
},
},
render(createElement, { props }) {
const flat = [];
function flatten(data, depth) {
const depthRef = typeof depth !== 'undefined' ? depth + 1 : 0;
data.forEach((item, index) => {
const itemCopy = item;
itemCopy.index = index;
itemCopy.depth = depthRef;
itemCopy.indentation = new Array(depthRef);
flat.push(itemCopy);
if (item.children.length) {
flatten(item.children, depthRef);
}
});
}
flatten(props.tree);
return flat.map((element) => createElement('div', {
attrs: {
'data-index': element.index,
'data-depth': element.depth,
class: 'sortable-item',
},
},
[
createElement(SortableItemContent, {
props: {
title: element.title,
indentation: element.indentation,
},
}),
]));
},
};
The SortableItemContent component looks like this:
<template>
<div class="item-content">
<div
v-for="(item, index) in indentation"
:key="index"
class="item-indentation"
></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">{{ title }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'SortableItemContent',
props: {
title: String,
indentation: Array,
},
};
</script>
Given the data I have posted on my question, it now renders the HTML elements like I wanted:
<div data-index="0" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Header</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
<div data-index="1" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Container</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
Thank you again #AlexMA for the tip on Functional Components.

How do I fetch JSON data with Vue and Axios

I'm trying to fetch product data from a JSON file, but can't get it to work.
I've tried several things and searched the internet for a solution but none of the examples on the internet equals my situation.
I'm new to both vue and axios, so please excuse my ignorance.
This is what I have so far:
Vue.component('products',{
data: {
results: []
},
mounted() {
axios.get("js/prods.json")
.then(response => {this.results = response.data.results})
},
template:`
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
<div class="">
<div class="mkcenter" style="position:relative">
<a class="item">
<img class="productImg" width="120px" height="120px" v-bind:src="'assets/products/' + product.image">
<div class="floating ui red label" v-if="product.new">NEW</div>
</a>
</div>
</div>
<div class="productItemName" >
<a>{{ product.name }}</a>
</div>
<div class="mkdivider mkcenter"></div>
<div class="productItemPrice" >
<a>€ {{ product.unit_price }}</a>
</div>
<div v-on:click="addToCart" class="mkcenter">
<div class="ui vertical animated basic button" tabindex="0">
<div class="hidden content">Koop</div>
<div class="visible content">
<i class="shop icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
`
})
new Vue({
el:"#app",
});
The json file is as follows
{
"products":[
{
"name": "Danser Skydancer",
"inventory": 5,
"unit_price": 45.99,
"image":"a.jpg",
"new":true
},
{
"name": "Avocado Zwem Ring",
"inventory": 10,
"unit_price": 123.75,
"image":"b.jpg",
"new":false
}
]
}
The problem is only with the fetching of the data from a JSON file, because the following worked:
Vue.component('products',{
data:function(){
return{
reactive:true,
products: [
{
name: "Danser Skydancer",
inventory: 5,
unit_price: 45.99,
image:"a.jpg",
new:true
},
{
name: "Avocado Zwem Ring",
inventory: 10,
unit_price: 123.75,
image:"b.jpg",
new:false
}
],
cart:0
}
},
template: etc.........
As the warnings suggest, please do the following:
Rename the data array from results to products since you are referencing it by the latter one as a name during render.
Make your data option a function returning an object since data option must be a function, so that each instance can maintain an independent copy of the returned data object. Have a look at the docs on this.
Vue.component('products', {
data() {
return {
products: []
}
},
mounted() {
axios
.get("js/prods.json")
.then(response => {
this.products = response.data.products;
});
},
template: `
//...
`
}
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
...
Also, since you're not using CDN (I think), I would suggest making the template a component with a separate Vue file rather than doing it inside template literals, something like that:
Products.vue
<template>
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
<!-- The rest of the elements -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Products',
data() {
return {
products: []
}
},
mounted() {
axios
.get("js/prods.json")
.then(response => {
this.products = response.data.products;
});
}
}
</script>
And then in your main JS file or anywhere else requiring this component:
import Products from './components/Products.vue';
new Vue({
el: '#app',
data() {
return {
//...
}
},
components: {
Products
}
})
<div id="app">
<Products />
</div>

Refresh Boostrap-Vue table after deleting a row

I'm using Bootstrap-Vue for my datatables and got the following table within my dashboard:
I can succesfully delete items by clicking on the trash icon. It sends an AJAX request using Axios. However, after deletion it still displays the item until I manually refresh the web page. How do I solve this? I don't want to make another AJAX request to load in the updated version, I think the best way to solve it is just remove the deleted item row from the datatable.
I tried giving my table a ref tag and call a refresh function using this.$refs.table.refresh(); but with no success.
My code:
<template>
<div>
<b-modal ref="myModalRef" hide-footer title="Delete product">
<div class="container">
<div class="row">
<p>Are you sure you want to delete this item?</p>
<div class="col-md-6 pl-0">
Confirm
</div>
<div class="col-md-6 pr-0">
Cancel
</div>
</div>
</div>
</b-modal>
<div id="main-wrapper" class="container">
<div class="row">
<div class="col-md-12">
<h4>Mijn producten</h4>
<p>Hier vind zich een overzicht van uw producten plaats.</p>
</div>
<div class="col-md-6 col-sm-6 col-12 mt-3 text-left">
<router-link class="btn btn-primary btn-sm" :to="{ name: 'create-product'}">Create new product</router-link>
</div>
<div class="col-md-6 col-sm-6 col-12 mt-3 text-right">
<b-form-input v-model="filter" class="table-search" placeholder="Type to Search" />
</div>
<div class="col-md-12">
<hr>
<b-table ref="table" show-empty striped hover responsive :items="posts" :fields="fields" :filter="filter" :current-page="currentPage" :per-page="perPage">
<template slot="title" slot-scope="data">
{{ data.item.title|truncate(30) }}
</template>
<template slot="description" slot-scope="data">
{{ data.item.description|truncate(50) }}
</template>
<template slot="public" slot-scope="data">
<i v-if="data.item.public === 0" title="Unpublished" class="fa fa-circle false" aria-hidden="true"></i>
<i v-else title="Published" class="fa fa-circle true" aria-hidden="true"></i>
</template>
<template slot="date" slot-scope="data">
{{ data.item.updated_at }}
</template>
<template slot="actions" slot-scope="data">
<a class="icon" href="#"><i class="fas fa-eye"></i></a>
<a v-on:click="editItem(data.item.id)" class="icon" href="#"><i class="fas fa-pencil-alt"></i></a>
<i class="fas fa-trash"></i>
</template>
</b-table>
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage" class="my-0 pagination-sm" />
</div>
</div><!-- Row -->
</div><!-- Main Wrapper -->
</div>
<script>
export default {
data() {
return {
posts: [],
filter: null,
currentPage: 1,
perPage: 10,
totalRows: null,
selectedID: null,
fields: [
{
key: 'title',
sortable: true
},
{
key: 'description',
},
{
key: 'public',
sortable: true,
},
{
key: 'date',
label: 'Last updated',
sortable: true,
},
{
key: 'actions',
}
],
}
},
mounted() {
this.getResults();
},
methods: {
// Our method to GET results from a Laravel endpoint
getResults() {
axios.get('/api/products')
.then(response => {
this.posts = response.data;
this.totalRows = response.data.length;
});
},
getID: function(id){
this.selectedID = id;
this.$refs.myModalRef.show();
},
deleteItem: function (id) {
axios.delete('/api/products/' + id)
.then(response => {
this.$refs.myModalRef.hide();
this.$refs.table.refresh();
});
},
editItem: function (id){
this.$router.push({ path: 'products/' + id });
}
},
}
</script>
The deleteItem method should be like this:
deleteItem(id) {
axios.delete('/api/products/' + id)
.then(response => {
const index = this.posts.findIndex(post => post.id === id) // find the post index
if (~index) // if the post exists in array
this.posts.splice(index, 1) //delete the post
});
},
So basically you don't need any refresh. If you remove the item for posts array Vue will automatically handle this for you and your table will be "refreshed"
try to remove that post with the given id after the successful delete :
axios.delete('/api/products/' + id)
.then(response => {
this.posts= this.posts.filter(post=>post.id!=id)
});
axios.delete('/api/products/' + id)
.then(response => {
this.getResults();
});

Categories