How i pass result to data vue? I don't understand, how tide vue example and sortablejs.
Project use boostrap-vue
<div>
<b-table v-sortable="sortableOptions" striped hover :items="items"></b-table>
</div>
</template>
<script>
import Sortable from "sortablejs";
const createSortable = (el, options, vnode) => {
return Sortable.create(el, {
...options,
});
};
const sortable = {
name: "sortable",
bind(el, binding, vnode) {
const table = el;
table._sortable = createSortable(
table.querySelector("tbody"),
binding.value,
vnode
);
},
};
I stumbled upon such a task in one of my projects. I know that there are many similar solutions here on stackoverflow, but they didn't work in my case. The thing was that no one tells you need to provide Sortable-object with dataIdAttr property and set a proper attribute in children of sortable-dom-element. At least you need to do it working with bootstrap-vue table. That will help to resort rows backwards and reset vue-reactive-data according to changed drag indexes.
I hope that will be useful for someone.
<template>
<b-table
v-draggable-rows="{ data: items, orderColumn: '№' }"
:items="items"
>
<template #cell(Dragger)>
<IconDragHandle />
</template>
</b-table>
</template>
<script>
import IconDragHandle from '~/components/icons/table/IconDragHandle.vue';
export default {
components: {
IconDragHandle,
},
data() {
return {
items: [],
};
},
created() {
this.items = [...Array(8).keys()].map(x => ({
'Dragger': '',
'№': x + 1,
'Cell1': x + 1,
'Cell2': x + 1,
'Cell3': x + 1,
}));
},
};
</script>
import Vue from 'vue';
import Sortable from 'sortablejs';
Vue.directive('draggable-rows', {
bind(el, binding, vnode) {
const table = el;
table._sortable = createSortable(
table.querySelector('tbody'),
binding.value,
vnode
);
},
});
const createSortable = (el, options, vnode) => {
let order = [];
const data = options.data;
const orderColumn = options.orderColumn;
for (let i = 0; i < el.children.length; i++) {
el.children[i].setAttribute('sortable-id', i);
}
return Sortable.create(el, {
dataIdAttr: 'sortable-id', // default: data-id
animation: 150,
easing: 'cubic-bezier(0.25, 1, 0.5, 1)',
handle: '.custom-table-draggable-handle',
ghostClass: 'custom-draggable-rows-ghost',
chosenClass: 'custom-draggable-rows-chosen',
dragClass: 'custom-draggable-rows-drag',
onStart() {
order = [...this.toArray()];
},
onEnd(evt) {
this.sort(order);
data.splice(evt.newIndex, 0, ...data.splice(evt.oldIndex, 1));
if (!orderColumn) return;
data.forEach((o, i) => {
o[orderColumn] = i + 1;
});
},
});
};
Draggable Draggable.Vue Bootstrap-Vue SortableJS v-sortable Reorder
Related
What is sortable? Why I got problem with it
sortTheHeadersAndUpdateTheKey(evt) {
const headersTmp = this.headers;
const oldIndex = evt.oldIndex;
const newIndex = evt.newIndex;
if (newIndex >= headersTmp.length) {
let k = newIndex - headersTmp.length + 1;
while (k--) {
headersTmp.push(undefined);
}
}
headersTmp.splice(newIndex, 0, headersTmp.splice(oldIndex, 1)[0]);
this.table = headersTmp;
this.anIncreasingNumber += 1;
},
},
directives: {
'sortable-table': {
inserted: (el, binding) => {
el.querySelectorAll('th').forEach((draggableEl) => {
// Need a class watcher because sorting v-data-table rows asc/desc removes the sortHandle class
watchClass(draggableEl, 'sortHandle');
draggableEl.classList.add('sortHandle');
});
Sortable.create(el.querySelector('tr'), binding.value ? { ...binding.value, handle: '.sortHandle' } : {});
},
},
},
<v-data-table
:headers="showHeaders"
:page="page"
:pageCount="numberOfPages"
:options.sync="options"
:loading="loading"
:server-items-length="totalItems"
:items="items"
:items-per-page="15"
class="mainTable"
#dblclick:row="editItem"
v-sortable-table="{onEnd:sortTheHeadersAndUpdateTheKey}"
:key="anIncreasingNumber"
:footer-props="{
showFirstLastPage: true,
firstIcon: 'mdi-arrow-collapse-left',
lastIcon: 'mdi-arrow-collapse-right',
prevIcon: 'mdi-minus',
nextIcon: 'mdi-plus'
}"
>
I'm just try to do this Dragging columns in a Vuetify v-data-table
but have a mistake I did all what he say and still got a problem I've installed and import vuedraggable
maybe import is wrong ---> import 'vuedraggable'
<template>
<el-row>
<el-col :span="24">
<el-image
v-for="(card, index) in randomCards"
:key="card.indexOf"
:src="card.url"
:id="(card.id = index)"
fit="fill"
#click="flipCards(index)"
></el-image>
</el-col>
</el-row>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
selectedCardsId: [],
randomCards: null,
data: [],
};
},
computed: {},
methods: {
getResults() {
axios
.get("https://api.thecatapi.com/v1/images/search", {
params: { limit: 36 },
})
.then((response) => {
this.randomCards = response.data
.concat(response.data)
.sort(() => 0.5 - Math.random());
});
},
flipCards(cardIndex) {
this.randomCards[cardIndex].url = "1";
},
},
async mounted() {
await this.getResults();
},
};
</script>
When I load the page and click the image, I see changes in two elements in the array because they have the same value but a different index. I think that's because I pushed the same array. I also tried Array.from() and the spread operator.
In response.data.concat(response.data), you're appending the original objects by reference, so changes to one instance affect the other as they refer to the same data.
Assuming the data items are all shallow, a quick way to clone the data is to map them into new objects:
this.randomCards = response.data.concat(response.data.map(x => ({ ...x })))
demo
The method this.fillForm() of my Vue component C (EditComment) is called twice, but I'm having trouble understanding why. I tried using uuid, but don't know how it helps knowing that beforeCreate is called twice.
There are 3 components. Here are the relevant parts:
Component A:
showCommentDialog: function(recordNumber) {
this.$modal.show(
ShowComment,
{
commentRecId: recordNumber
},
{
draggable: true,
width: 400,
height: 250
},
{
closed: function(event) {}
}
);
Component B:
<EditComment v-bind:comment-rec-id="commentRecId" v-if="showEdit"></EditComment>
</div>
</template>
<script>
import * as $ from "jquery";
import EditComment from "./EditComment.vue";
export default {
props: ["commentRecId"],
data: function() {
with this function
editItem: function(){
this.showEdit = true;
console.log("editItem function() called!");
var playerID = this.$store.state.selectedPlayer.ID;
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
}
Component C:
<script>
import * as $ from "jquery";
import DatePicker from "vue2-datepicker";
let uuid = 0;
export default {
props: ["text", "commentRecId"],
beforeCreate() {
this.uuid = uuid.toString();
uuid += 1;
console.log("beforeCreate() uuid: " + this.uuid);
},
components: { DatePicker },
data: function() {
return {
commentData: {
comment: "",
customDate: ""
},
selectedCategory: "",
lang: {
default: "en"
},
}
},
mounted: function() {
// console.log("this._uid: " + this._uid);
this.fillForm();
},
methods: {
fillForm: function(){
Any help is appreciated.
If I understand correctly your problem, you fired component C with this section of editItem method:
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
if I'm right, you have a mistake in your method:
when you use v-if, vue fires your component and it resets all values which you passed it before, like props, data values (except your uuid because it's not a data property)
so in your method you fire your component twice with :
this.showEdit = true;
anyway...for solution, please try this way:
first, use "v-show" instead of "v-if"
then show your component by this.$modal.show()
I hope can help
Having some trouble with a custom Vue component using Vue Carousel(https://ssense.github.io/vue-carousel/) to build a slide from a node list in the index file. Some of the carousel data is read in through a data.json file, but I want to create a new slide based on an object containing a node list. So I believe I need to us v-for and then iterate through that object creating a <slide></slide> for each instance in the node list.
The NodeList is made in the mounted() lifehook, but Im not sure how to tie it to the component, ALSO still very new to Vue and I didn't build out this templating system. Any help would be appreciated!
import { Carousel, Slide } from 'vue-carousel';
let articles={};
export default {
name: 'ProductCarousel',
props: {
dnode: Object,
value: Object
},
components: {
Carousel,
Slide
},
data() {
return {
carousel: this.dnode,
product: articles
}
},
mounted() {
var _self = this;
var rowName = ".js-ep--" + _self.carousel.anchor;
let e4pRow = document.querySelector(rowName);
var productRows;
if (e4pRow && !window.isEditMode) {
productRows = e4pRow.querySelectorAll(".product-grid");
if (productRows) {
for (var len = productRows.length, i = 0; i < len; i++) {
articles[i] = productRows[i].querySelectorAll("article");
//console.log(articles[i]);
}
//console.log(articles);
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<section :id="carousel.anchor" :class="carousel.classList" >
<h1 v-if="carousel.header" v-text="carousel.header" />
<h2 v-if="carousel.subheader" v-text="carousel.subheader"/>
<p v-if="carousel.paragraphText" v-text="carousel.paragraphText" />
<carousel
:navigationEnabled="carousel.navigation"
:paginationEnabled="carousel.pagination"
:navigationNextLabel="carousel.navigationNextIcon"
:navigationPrevLabel="carousel.navigationPrevIcon"
:navigationClickTargetSize="carousel.arrowClickSize"
:perPageCustom="[
[640, carousel.numSlidesSmall],
[1024, carousel.numSlidesMedium],
[1920, carousel.numSlidesLarge]]">
<slide></slide>
</carousel>
</section>
</template>
import { Carousel, Slide } from 'vue-carousel';
let articles={};
export default {
name: 'ProductCarousel',
props: {
dnode: Object,
value: Object
},
components: {
Carousel,
Slide
},
data() {
return {
carousel: this.dnode,
product: []
}
},
mounted() {
var _self = this;
var rowName = ".js-ep--" + _self.carousel.anchor;
let e4pRow = document.querySelector(rowName);
var productRows;
if (e4pRow && !window.isEditMode) {
productRows = e4pRow.querySelectorAll(".product-grid");
if (productRows) {
for (var len = productRows.length, i = 0; i < len; i++) {
articles[i] = productRows[i].querySelectorAll("article");
//console.log(articles[i]);
}
//console.log(articles);
this.product = articles; //loop through product in your template
}
}
}
}
I have a page with Vuetify Autocomplete component, and REST API backend with '/vendors' method. This method takes limit, page and name parameters and returns JSON with id and name fields.
I made some code with lazy list load on user input event. But now I want to add the ability to load this list on user scroll event.
For example, by default there is a list of 100 vendors. User scrolled this list until the end, then "some event" is called and loads next 100 of vendors. Then user keeps scrolling and the action is repeated.
Is it possible to made this with Vuetify Autocomplete component, or should i use another library?
Example code of current component is shown below:
<template>
<v-autocomplete
:items="vendors"
v-model="selectedVendorId"
item-text="name"
item-value="id"
label="Select a vendor"
#input.native="getVendorsFromApi"
></v-autocomplete>
</template>
<script>
export default {
data () {
return {
page: 0,
limit: 100,
selectedVendorId: null,
vendors: [],
loading: true
}
},
created: function (){
this.getVendorsFromApi();
},
methods: {
getVendorsFromApi (event) {
return new Promise(() => {
this.$axios.get(this.$backendLink
+ '/vendors?limit=' + this.limit
+ '&page=' + this.page
+ '&name=' + (event ? event.target.value : ''))
.then(response => {
this.vendors = response.data;
})
})
}
}
}
</script>
I was able to get auto-loading going with the Vuetify AutoComplete component. It's a bit of a hack, but basically the solution is to use the v-slot append item, the v-intersect directive to detect if that appended item is visible, and if it is, call your API to fetch more items and append it to your current list.
<v-autocomplete
:items="vendors"
v-model="selectedVendorId"
item-text="name"
item-value="id"
label="Select a vendor"
#input.native="getVendorsFromApi"
>
<template v-slot:append-item>
<div v-intersect="endIntersect" />
</template>
</v-autocomplete>
...
export default {
methods: {
endIntersect(entries, observer, isIntersecting) {
if (isIntersecting) {
let moreVendors = loadMoreFromApi()
this.vendors = [ ...this.vendors, ...moreVendors]
}
}
}
}
In my use case, I was using API Platform as a backend, using GraphQL pagination using a cursor.
<component
v-bind:is="canAdd ? 'v-combobox' : 'v-autocomplete'"
v-model="user"
:items="computedUsers"
:search-input.sync="search"
item-text="item.node.userProfile.username"
hide-details
rounded
solo
:filter="
(item, queryText, itemText) => {
return item.node.userProfile.username.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
} "
:loading="loading"
item-value="username"
class="text-left pl-1"
color="blue-grey lighten-2"
:label="label"
>
<template v-slot:selection="{ item }">
<v-chip v-if="typeof item == 'object'">
<v-avatar left>
<v-img v-if="item.node.userProfile.image" :src="item.node.userProfile.image" />
<v-icon v-else>mdi-account-circle</v-icon>
</v-avatar>
{{ item.node.userProfile.firstName }} {{ item.node.userProfile.lastName }}
</v-chip>
<v-chip v-else-if="typeof item == 'string'">
{{ item }}
</v-chip>
</template>
<template v-slot:item="{ item: { node } }">
<v-list-item-avatar >
<img v-if="node.userProfile.avatar" :src="node.userProfile.avatar" />
<v-icon v-else>mdi-account-circle</v-icon>
</v-list-item-avatar>
<v-list-item-content class="text-left">
<v-list-item-title>
{{ $t('fullName', { firstName: node.userProfile.firstName, lastName: node.userProfile.lastName } )}}
</v-list-item-title>
<v-list-item-subtitle v-html="node.userProfile.username"></v-list-item-subtitle>
</v-list-item-content>
</template>
<template v-slot:append-item="">
<div v-intersect="endIntersect" >
</div>
</template>
</component>
import { VCombobox, VAutocomplete } from "vuetify/lib";
import debounce from "#/helpers/debounce"
import { SEARCH_USER_BY_USERNAME } from "#/graphql/UserQueries";
const RESULTS_TO_SHOW = 5
export default {
props: {
canAdd: {
type: Boolean,
default: false,
},
value: [Object, String],
label: String,
},
components: { VCombobox, VAutocomplete },
apollo: {
users: {
query: SEARCH_USER_BY_USERNAME,
variables() {
return {
username: this.search,
numberToShow: RESULTS_TO_SHOW,
cursor: null,
}
},
watchLoading(isLoading) {
this.loading = isLoading
},
skip() {
if (this.search) {
return !(this.search.length > 1)
}
return true
},
},
},
data() {
return {
user: this.value,
search: "",
cursor: null,
loading: false,
};
},
watch: {
user(newValue) {
let emit = newValue
if (newValue) {
emit = newValue.node
}
this.$emit("input", emit);
},
value(newValue) {
if (this.user && this.user.node != newValue) {
if (newValue == null) {
this.user = null
}
else {
this.user = { node: newValue };
}
}
},
search(newValue) {
this.debouncedSearch(newValue)
},
},
methods: {
endIntersect(entries, observer, isIntersecting) {
if (isIntersecting && this.users && this.users.pageInfo.hasNextPage) {
let cursor = this.users.pageInfo.endCursor
this.$apollo.queries.users.fetchMore({
variables: { cursor: cursor},
updateQuery: (previousResult, { fetchMoreResult }) => {
let edges = [
...previousResult.users.edges,
...fetchMoreResult.users.edges,
]
let pageInfo = fetchMoreResult.users.pageInfo;
return {
users: {
edges: edges,
pageInfo: pageInfo,
__typename: previousResult.users.__typename,
}
}
}
})
}
},
debouncedSearch: debounce(function (search) {
if (this.users) {
this.$apollo.queries.users.refetch({
username: search,
numberToShow: RESULTS_TO_SHOW,
cursor: null,
});
}
}, 500),
filter(item, queryText) {
return item.node.userProfile.username.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
}
},
computed: {
computedUsers() {
if (this.users){
return this.users.edges
}
return []
},
skip() {
if (this.search) {
return this.search.length > 1
}
return false
}
}
};
</script>
Update June 12, 2021:
If you are using Vuetify 2.X, use Brettins' solution based on append-item slot and v-intersect directive.
Old answer:
Looks like it's not possible with default v-autocomplete component (at least in vuetify 1.5.16 or lower).
The component that provides the most similar functionality is VueInfiniteAutocomplete.
But keep in mind that in this case there may be problems with styles, validation, etc.
There is an example with this library.
<template>
<div>
<vue-infinite-autocomplete
:data-source="getAsyncOptions"
:fetch-size="limit"
v-on:select="handleOnSelect"
:value="autocompleteViewValue"
>
</vue-infinite-autocomplete>
</div>
</template>
<script>
export default {
data () {
return {
selectedVendorId : null,
limit: 100,
autocompleteViewValue: null
}
},
methods: {
getAsyncOptions(text, page, fetchSize) {
return new Promise((resolve, reject) => {
resolve(
this.$axios.get(this.$backendLink
+ '/vendors?limit=' + fetchSize
+ '&page=' + page
+ '&name=' + text)
.then(response => {
//Response MUST contain 'id' and 'text' fields, and nothing else.
//If there are other fields, you should remove it here
//and create 'id' and 'text' fields in response JSON by yourself
return response.data;
})
)
});
},
handleOnSelect(selectedItem) {
this.autocompleteViewValue = selectedItem.text;
this.selectedVendorId = selectedItem.id;
}
}
}
</script>
P.S.: If you just want to use v-autocomplete component with server-side pagination, you could create a "Load more..." button using append-item slot, as suggested in this issue.