Restrict SortableJS Elements Clone + Nesting Problem - javascript

I am looking to allow only certain elements to be nested inside others and I did not managed to do so.
Example: Application element can contain only Container elements. Container only Queries, and Queries different types of Components.
Codepen - Code
HTML:
<div class="container">
<div id="cloning" class="row">
<div id="example3Left" class="list-group col">
<div class="list-group-item nested-sortable" name="container">Container</div>
<div class="list-group-item nested-1 nested-sortable" name="query">Query</div>
<div class="list-group-item nested-2" name="component text">Component Text</div>
<div class="list-group-item nested-2" name="component bool">Component Bool</div>
</div>
<div class="list-group col">
<div id="example3Right" class="list-group-item tinted nested-sortable" name="application">Application
</div>
</div>
<div style="padding: 0" class="col-12">
</div>
</div>
</div>
JAVASCRIPT:
<script>
createNesting();
var example3LeftSortable = new Sortable(example3Left, {
group: {
name: 'shared',
pull: 'clone',
put: false
},
animation: 150,
onEnd: function (/**Event*/evt, /**Event*/originalEvent) {
createNesting();
}
});
var example3RightSortable = new Sortable(example3Right, {
group: {
name: 'shared',
pull: false
},
animation: 150,
sort: false
});
function createNesting() {
// Nested demo
const container = document.querySelector("#example3Right");
var nestedSortables = [].slice.call(container.querySelectorAll('.nested-sortable'));
// Loop through each nested sortable element
for (var i = 0; i < nestedSortables.length; i++) {
new Sortable(nestedSortables[i], {
group: {
name: 'shared',
pull: false
},
animation: 150,
sort: false,
fallbackOnBody: true,
swapThreshold: 0.50
});
}
}
</script>

Related

Custom hit template with Algolia in Shopify

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?

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.

Can't succeed in dragging and dropping elements from one div to the other separately

I wish to create two containers with the first containing elements to be dragged and dropped to the second when the user wishes. I actually succeed in displaying the two containers with the respective elements, but when I try dragging one of them to the second container separately, the whole block moves on.
Below is my code :
$(document).ready(function () {
$('.box-item').draggable({
cursor: 'move',
helper: "clone"
});
$("#container1").droppable({
drop: function (event, ui) {
var itemid = $(event.originalEvent.toElement).attr("itemid");
$('.box-item').each(function () {
if ($(this).attr("itemid") === itemid) {
$(this).appendTo("#container1");
}
});
}
});
$("#container2").droppable({
drop: function (event, ui) {
var itemid = $(event.originalEvent.toElement).attr("itemid");
$('.box-item').each(function () {
if ($(this).attr("itemid") === itemid) {
$(this).appendTo("#container2");
}
});
}
});
});
/* Styles go here */
.box-container {
height: 200px;
}
.box-item {
width: 100%;
z-index: 1000
}
<div class="row">
<div class="col-lg-11">
<div class="row">
<div class="col-lg-12">
#using (Html.BeginForm("", "", FormMethod.Post, new { id = "formAssignVehicles" }))
{
<fieldset>
<legend>Affecter des voitures par glisser-déposer</legend>
#{ }
<div class="col-xs-3">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Liste des véhicules</h3>
</div>
<div id="container1" class="panel-body box-container">
#foreach (var voiture in Model.ListeVoituresAffectees)
{
<div itemid="itm-1" class="btn btn-default box-item">
#Html.DisplayFor(model => voiture.MarqueVoiture.Libelle)
</div>
}
</div>
</div>
</div>
<div class="col-xs-3">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Liste des véhicules affectées</h3>
</div>
<div id="container2" class="panel-body box-container"></div>
</div>
</div>
<input type="button" value="Affecter les voitures" />
</fieldset>
}
</div>
</div>
</div>
</div>
Can't figure out where's the typo. Kindly help, please!
In your #foreach statement, all the elements are being given the same itemid "itm-1" so they are all affected the same way. Find a way to give each a unique itemid and that should solve the problem.

Infinite loop VueJS API calls

I got infinite api calls when this method setCurrentDateFilter called after clicking on radio button. It looks like my filtered list rerenders every time because of reactivity. But actually i don't understand the reason why. Everything was fine before i did this additional api call on button click.
index.html partially
<div class="row align-items-center justify-content-center">
<b-form-group>
<b-form-radio-group buttons v-model="selected_date" :change="setCurrentDateFilter()" name="date_filter">
<b-form-radio value="today" button-variant="outline-success">Сегодня</b-form-radio>
<b-form-radio value="tomorrow" button-variant="outline-success">Завтра</b-form-radio>
<!-- <b-form-radio value="specific" button-variant="outline-success" disabled>Выбрать дату</b-form-radio> -->
</b-form-radio-group>
</b-form-group>
</div>
<div class="container">
<div class="section-top-border" v-for="(event, i) in filteredEvents" :key="event.id">
<div class="row">
<div class="col-md-6">
<div class="country">
<div class="grid">
<div class="row">
<div class="col-sm-3 event date">{{event.start_date.day}}</div>
<div class="col-sm-6 event month">{{event.start_date.month}}</div>
</div>
<div class="row event">
<div class="col-sm-1">{{event.start_date.time}} </div>
<div class="col-sm-11" v-if="event.place"> 📌 {{event.place.name}} </div>
</div>
</div>
</div>
</div>
</div>
<h3 class="mb-30">{{event.title}}</h3>
<div class="row">
<div class="col-md-3">
<b-img v-bind:src="event.poster_link" alt="" width="200" height="200" fluid>
</div>
<div class="col-md-9 mt-sm-20">
<p>{{event.short_description}}</p>
<b-btn variant="outline-success" v-on:click="currentEvent=event;modalShow=true"> 👁 Подробнее</b-btn>
</div>
</div>
</div>
main.js
var app = new Vue({
el: '#app',
data: {
api: 'http://127.0.0.1:8000/api/events',
show: true,
events: [],
currentEvent: Object,
modalShow: false,
loading: true,
errored: false,
selected_filter: ["1", "2", "3"],
selected_date: "today",
},
computed: {
filteredEvents() {
var sel_filter = this.selected_filter
var objs = this.events.filter(function(event) {
return sel_filter.indexOf(event.type.id.toString()) >= 0
})
console.log(objs.length, sel_filter)
return objs;
}
},
mounted() {
this.getEventsFromServer();
},
methods: {
getEventsFromServer(date = (new Date).toString()) {
axios
.get(this.api)
.then(response => {
this.events = handleResponse(response)
})
.catch(error => {
console.log(error);
this.errored = true;
})
.finally(() => (this.loading = false));
},
setCurrentDateFilter: function(e) {
console.log(e)
console.log(this.selected_date)
this.getEventsFromServer();
},
},
filters: {}
})
function handleResponse(response) {
var events = []
for (let i = 0; i < response.data.length; i++) {
let e = response.data[i]
let start_date = new Date(e.start_date)
let el = {
start_date: {
day: start_date.getDate(),
time: start_date.getHours() + ":" + (start_date.getMinutes() < 10 ? '0' : '') + start_date.getMinutes(),
month: getMonthWord(start_date)
},
title: e.id,
title: e.title,
description: e.description,
short_description: e.short_description,
poster_link: e.poster_link,
source_link: e.source_link,
type: getStyledType(e.type),
phone: e.phone,
email: e.email,
place: e.place
}
events.push(el)
}
return events;
}

Vue.JS using class for element

I try to give v-if="seen" for some class but it doesn't work obviously...
My code:
<div class="item" v-if="seen">item 1</div>
<div class="item" v-if="seen">item 2</div>
<div class="item" v-if="seen">item 3</div>
var item = new Vue({
el: 'div.item',
data: {
seen: true
}
});
In JavaScript I could do :
var item = document.getElementsByClassName('item');
for(var i = 0; i < item.length; i++){
item[i].style.display = "none";
}
How must I do in Vue.js ? Thanks
You can try using a v-for and setup the data to use an array.
Try something like this:
<div id="app">
<div v-for="myItem in items" class="item" v-if="myItem.seen">{{myItem.name}}</div>
</div>
var app = new Vue({
el: '#app',
data: {
items : [{
seen: true,
name: 'item 1'
},
{
seen: false,
name: 'item 2'
},
{
seen: false,
name: 'item 3'
}]
}
});
The way Vue is designed, it should be ideally done like so:
<div id="app">
<div class="item" v-if="seen">item 1</div>
<div class="item" v-if="seen">item 2</div>
<div class="item" v-if="seen">item 3</div>
</div>
var app = new Vue({
el: '#app',
data: {
seen: true
}
});
There is many ways to achieve what you want. Example of one of them:
<div is="app">
<div class="item" v-if="!seen.includes('item1')">
item 1
</div>
<div class="item" v-if="!seen.includes('item2')">
item 2
</div>
<div class="item" v-if="!seen.includes('item3')">
item 3
</div>
</div>
var item = new Vue({
el: '#app',
data: {
seen: [
'item1',
'item3'
]
}
})
var item = new Vue({
el: 'div.item',
data: {
isSeen: true
},
computed:{
seen:()=>this.isSeen;
}
});
or you could use v-show or v-class

Categories