What I've done wrong with dragging columns vuetify - javascript

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'

Related

Repeat the same behaviour on Ant Design Editable Table for React on Ant Design Vue 3?

I'm using Ant Design in Vue 3 and I have a table that I'm able to edit all the cells. The problem is: I want to automatically close the editable cell if the user opens a new one to edit. While I was researching, I noticed that this behaviour happens on Ant Design for React accordingly to the documentation.
My question is, how to do this for Vue 3? On their documentation for Vue, the Ant Design table doesn't show this behaviour that I wanted and I have no idea how to do that. Thanks in advance. :)
This is how my code looks now:
<template>
<a-table bordered :data-source="tableData.data" :columns="tableData.columns" :pagination="false" class="tableEditable">
<template v-for="col in this.editableCells" #[col]="{ column, text, record }" :key="col">
<div class="editable-cell" :ref="(record.key, column.key)">
<div v-if="editableData[record.key + '|' + column.key] !== undefined" class="editable-cell-input-wrapper">
<a-input v-model:value="editableData[record.key + '|' + column.key]" #pressEnter="save(record.key, column.key)" type="number" />
<check-outlined class="editable-cell-icon-check" #click="this.save(record.key, column.key)" />
</div>
<div v-else class="editable-cell-text-wrapper">
{{ text || ' ' }}
<edit-outlined class="editable-cell-icon" #click="this.edit(record.key, column.key)" />
</div>
</div>
</template>
<template #buttonTable="{text, record}">
<div class="editableTableButtonOption">
{{text}}
<Popper arrow :locked="true">
<button class="statusButtonCrud synch" v-if="showEditableButton(record.key)"> {{this.buttonText}} </button>
<template #content="{close}">
<div class="popperOptions">
<li v-for="options in this.optionsToEdit" :key="options" class="popperOptionsList" v-on:click="this.emitOption(options.method), close();">
{{$filters.translate(options.title)}}
</li>
</div>
</template>
</Popper>
</div>
</template>
</a-table>
</template>
<script>
import { CheckOutlined, EditOutlined } from '#ant-design/icons-vue';
export default {
name: 'TableEditable',
props: {
editableCells: Array,
tableData: Object,
buttonText: String,
optionsToEdit: Array,
copyOptionsTable: Boolean
},
emits: ['change', 'editRow', 'editColumn', 'editAllCells'],
components: {
CheckOutlined,
EditOutlined
},
data(){
return {
editableData: {},
selectedRow: '',
selectedColumn: '',
valueSaved: ''
}
},
methods: {
edit(row, column) {
this.editableData[row + '|' + column] = this.tableData.data.filter((item) => row === item.key)[0][column];
},
save(row, column) {
let data = {...this.tableData.data};
if (this.editableData[row + '|' + column] == '') {
data[row][column] = '0'
} else {
data[row][column] = this.editableData[row + '|' + column];
this.valueSaved = data[row][column]
}
delete this.editableData[row + '|' + column];
this.selectedRow = row;
this.selectedColumn = column;
this.$emit('change', data);
if (this.copyOptionsTable) {
this.addClass();
this.editedCell();
}
},
showEditableButton(row) {
if (this.copyOptionsTable && this.selectedRow == row) {
return true
}
},
emitOption(method) {
this.$emit(method, this.selectedRow, this.selectedColumn, this.valueSaved);
},
addClass() {
let columnsHTML = [...document.querySelectorAll('.ant-table-thead > tr > th')];
let columnsData = this.tableData.columns;
for (let idx in columnsHTML) {
columnsHTML[idx].classList.add(columnsData[idx].dataIndex);
}
},
editedCell() {
this.removeCell();
let tableRow = [...document.querySelectorAll('.ant-table-tbody > tr > td')];
let cellRef = this.$refs[this.selectedColumn];
cellRef.classList.add('editedCell');
for (let cell in tableRow) {
let div = tableRow[cell].querySelector('div')
if (div.classList.contains('editedCell')) {
tableRow[cell].classList.add('selectedCell')
}
}
},
removeCell(){
let tableRow = [...document.querySelectorAll('.ant-table-tbody > tr > td')];
for (let cell in tableRow) {
tableRow[cell].classList.remove('selectedCell')
let cellDiv = tableRow[cell].querySelector('div');
cellDiv.classList.remove('editedCell');
}
}
},
}
</script>
It was simpler than I thought it would be. Just changed the edit method to this and now it's working :)
edit(row, column) {
for (let idx in this.editableData) {
delete this.editableData[idx];
}
this.editableData[row + '|' + column] = this.tableData.data.filter((item) => row === item.key)[0][column];
},

Disable a certain text input that has been created dynamically if the length is over a certain amount of chars(VUE.JS)

I have the following Vue template which creates Bootstrap text inputs dynamically. The user can get the values onSubmit from the inputs.
Now, I need to disable an input if the text length is over 10 chars. I am struggling with it since yesterday. Any help is more than welcome
<script>
export default {
data() {
return {
items: [],
inputsAmount: 0,
form: [],
disableInput: false
};
},
methods: {
addInput() {
let theNumberOfInputs = this.inputsAmount++;
if (theNumberOfInputs < 8) {
this.items.push({ value: theNumberOfInputs });
} else {
return;
}
},
getFormsInputs() {
let theFormsInputs = {}, theQuestionnaire = [], overLimit = false;
console.log(this.form);
if (this.form.length) {
this.form.forEach((inputValues, iInputValues) => {
theFormsInputs["answer" + (iInputValues + 3)] = inputValues;
overLimit = this.checkInputLenght(inputValues);
});
}
console.log(overLimit);
if(!overLimit){ theQuestionnaire.push(theFormsInputs); }
return theQuestionnaire;
},
submit() {
const theQuestionnaire = this.getFormsInputs();
},
checkInputLenght(pInputValues) {
if (pInputValues.length > 80) {
console.log("Limited Excist");
this.disableInput = true;
return true;
}
}
}
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div>
<b-container>
<b-row class="mt-2" v-for="(item, iItem) in items" :key="iItem">
<b-form-input v-model="form[iItem]" placeholder="Please, type your answer."></b-form-input>
</b-row>
<button #click="addInput()">Test</button>
<button #click="submit()">Submit</button>
<button #click="resetState()">Reset</button>
</b-container>
</div>
</template>
<script>
//TODO CHECK FOR HOW TO PASS DATA
Create disabledInputs: [] array in your reactive data
data() {
return {
items: [],
inputsAmount: 0,
form: [],
disabledInputs: []
};
},
Add :disabled="disabledInputs.includes(`input_${iItem}`)" to your b-form input attributes
<b-row class="mt-2" v-for="(item, iItem) in items" :key="iItem">
<b-form-input v-model="form[iItem]" placeholder="Please, type your answer." :disabled="disabledInputs.includes(`input_${iItem}`)"></b-form-input>
</b-row>
Pass the index to you check method
this.form.forEach((inputValues, iInputValues) => {
theFormsInputs["answer" + (iInputValues + 3)] = inputValues;
overLimit = this.checkInputLenght(inputValues, iInputValues); //here
});
Add index to disabled array
checkInputLenght(pInputValues, idx) {
if (pInputValues.length > 10) {
this.disabledInputs.push(`input_${idx}`); // add to array
return true;
}
},
Working example:
https://codesandbox.io/s/silly-forest-pmxd8?file=/src/App.vue

How pass result sortablejs to vue data?

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

Using v-for to show dynamic slides in Vuejs

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
}
}
}
}

How to create infinite scroll in Vuetify Autocomplete component?

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.

Categories

Resources