Vue-draggable, nesting, and groups - vue.js

Hey guys I need some assistance with vue draggable used in a nested context with groups. Some back story; I'm building a landing page creator, that said my layout follows standard bootstrap layout
section
container
row
col
col
That said I have a toolbox with 2 types of modules: Component modules and Layout Modules.
Layout modules look like the following:
layoutToolbox: [
{
name: "2 column layout",
icon: "font",
tagName: "div",
attributes: {
id: Math.random().toString(36).substring(7),
class: "row",
style: {},
},
textNode: null,
children: [
{
tagName: "div",
attributes: {
id: Math.random().toString(36).substring(7),
class: "col",
style: {},
},
textNode: null,
children: [],
},
{
tagName: "div",
attributes: {
id: Math.random().toString(36).substring(7),
class: "col",
style: {},
},
textNode: null,
children: [],
},
],
},
],
And then component modules:
toolbox: [
{
name: "paragraph",
icon: "font",
tagName: "div",
attributes: {
id: "toolbox-p",
class: "p",
style: {},
},
textNode: "This is my paragraph from the toolbox",
children: [],
},
]
That said I need layout to only be draggable to the top parent level, and then components I need draggable inside the column drag groups only!
Using just groups I can't get this behavior to work, my top level in recursion has the group of section:
<draggable
v-model="elements"
:group="{ name: 'section', put: true }"
handle=".editor-ui-drag-handle"
>
Then all my children inside of this are rendered using the group row:
<draggable
v-bind="dragOptions"
:list="list"
:value="value"
tag="div"
:group="{ name: 'row', put: !child }"
handle=".editor-ui-drag-handle"
#input="emitter"
#change="onChange"
>
That said I have what I think is a solution but I want to know why groups aren't working. When I have a group set to section on 2 draggable groups(toolbar, and parent) why can I drag it into its children? And why can I drag components with the group of row into the parent with the group of section?
My idea to solve this right now is using the drag start call backs and set the store to a variable that says if its a parent type or child type and then enable / disable put in the sections accordingly but I want to know if there's a better way.
Thanks!

Related

how to make a treeview without having to duplicate the collection vue js vuetify

As the title indicates I want to display a different collection without having to duplicate it
for example :
const items: IRoute[] = [{
id: 1,
name: 'Administrateur',
children: [
{
id: 2,
name: 'Catalogue',
routeName: RouteName.CATALOGUE
},
{
id: 5,
name: 'Catalogue',
routeName: RouteName.CATALOGUE
}]
const itemWithoutAdmin: IRoute[] = [{
id: 5,
name: 'Catalogue',
routeName: RouteName.CATALOGUE
}]
I make the verification on the role and according to the role I display the admin part otherwise I display only the catalog,
in the template for now I have :
<v-navigation-drawer
app
permanent
>
<v-treeview
v-if="role === 'Admin'
:items="items"
activatable>
</v-treeview>
<v-treeview
v-else
:items="items"
activatable />
</v-navigation-drawer>
Thank you for your help

Dynamic item template slots within v-data-table with custom components & helpers

Say I have a custom component that uses Vuetify's v-data-table within.
Within this component, there's multiple other custom components such as loaders and specific column-based components for displaying data in a certain way.
I found myself using the same code for filtering, retrieving data, loaders etc. across the project - so not very DRY.
The things that vary are:
API request url to retrieve data from (which I can pass to this generic component)
headers for v-data-table (which I pass to this generic component)
specific item slot templates!
(One file using this same code would need a column modification like the below, requiring different components sometimes too):
<template v-slot:[`item.FullName`]="{ item }">
<router-link class="black--text text-decoration-none" :to="'/users/' + item.Id">
<Avatar :string="item.FullName" />
</router-link>
</template>
Where another would have for example:
<template v-slot:[`item.serial`]="{ item }">
<copy-label :text="item.serial" />
</template>
There are many more unique "column templates" that I use obviously, this is just an example.
modifying items passed to v-data-table in a computed property (to add "actions" or run cleanups and/or modify content before displaying it - not related to actual HTML output, but value itself)
computed: {
items () {
if (!this.data || !this.data.Values) {
return []
}
return this.data.Values.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
hwVersion: this.$getItemHwVersion(item),
swVersion: this.$getItemSwVersion(item),
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
}
there are some unique methods that I can use on certain template slot item modifications, such as dateMoreThan24HoursAgo() below:
<template v-slot:[`item.LastLogin`]="{ item }">
<span v-if="dateMoreThan24HoursAgo(item.LastLogin)">{{ item.LastLogin | formatDate }}</span>
<span v-else>
{{ item.LastLogin | formatDateAgo }}
</span>
</template>
I can always make this global or provide them as a prop so this point should not be a big issue.
So my questions are:
What is the best way to use one component with v-data-table within but dynamically pass template slots and also allow item modification prior to passing the array to the v-data-table (as per point 3 and 4 above)
is there a better way to approach this since this seems too complex (should I just keep separate specific files)? It does not feel very DRY, that's why I'm not very fond of the current solution.
Basically I would be happy to have something like:
data: () => {
return {
apiPath: 'devices',
headers: [
{ text: 'Device', align: 'start', value: 'device', sortable: false, class: 'text-none' },
{ text: 'Serial Number', sortable: false, value: 'serial', class: 'text-none' },
{ text: 'Status', value: 'Status', class: 'text-none' },
{ text: 'Calibration', value: 'NextCalibrationDate', class: 'text-none' },
{ text: '', sortable: false, align: 'right', value: 'actions' }
],
itemsModify: (items) => {
return items.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
},
columnTemplatesPath: '/path/to/vue/file/with/templates'
}
}
And then I'd just call my dynamic component like so:
<GenericTable
:api-path="apiPath"
:headers="headers"
:items-modify="itemsModify"
:column-templates-path="columnTemplatesPath"
/>
Relevant but not exactly a solution to my question:
Is it possible to use dynamic scoped slots to override column values inside <v-data-table>?
Dynamically building a table using vuetifyJS data table

How to do cell editing in vue-good-table?

I am trying to create a table where the data can be edited directly in the cells. However, I don't understand how to do this.
I am using Vue.js, Vuexy and vue-good-table 2.21.1. The data is contained in the mediaPlanData variable and is reactive. The table is successfully populating with data and can be modified. However, when I change the data in the cells, this variable does not change. I did not find in the documentation vue-good-table how to do this. Please tell me how can I achieve the desired result?
<vue-good-table
:columns="columns"
:rows="mediaPlanData"
:select-options="{
enabled: true,
selectOnCheckboxOnly: true, // only select when checkbox is clicked instead of the row
selectionInfoClass: 'custom-class',
selectionText: 'rows selected',
clearSelectionText: 'clear',
disableSelectInfo: true, // disable the select info panel on top
selectAllByGroup: true, // when used in combination with a grouped table, add a checkbox in the header row to check/uncheck the entire group
}"
#on-selected-rows-change="onRowClick"
>
<template slot="table-row" slot-scope="props">
<span>
<b-form-input v-model="props.row[props.column.field]"
type="text"
></b-form-input>
</span>
</template>
</vue-good-table>
data() {
return {
pageLength: 10,
columns: [
{
label: 'Channel Code',
field: 'channelCode',
},
{
label: 'Product',
field: 'product',
}
],
mediaPlanData: [] //[ {"channelCode": "P230", "product": "Test"}, {"channelCode": "P230", "product": "Test4"}, {"channelCode": "P230", "product": "Test45"}, {"channelCode": "Р230", "product": "Test2"}]
}
}
methods: {
onRowClick(params) {
console.log('onRowClick' + JSON.stringify(params))
this.$toast({
component: ToastificationContent,
props: {
title: `Hello user! You have clicked on row ${params.selectedRows.product}`,
icon: 'UserIcon',
variant: 'success',
},
})
}
}
I have solved this problem.
Added to b-form-input
#change="changeCell(props.row[props.column.field], props.column.field, props.row.originalIndex)"
And adden method
changeCell(changedData, column, row) {
this.mediaPlanData[row][column] = changedData
},

Vue.js. Render of different kinds of child components according to a list of conditions

I'm new to not OOP, and VUE.JS especially.
I have a list of conditions, according to them I should show on the page several different kinds of components.
How can I render, for example, 2 TextInput components (or 3.. 10) dynamically and read the entered text in parent after clicking a button?
Thank you in advance.
You didn't provide any code, so I'm not sure what exactly you are trying to do.
If you want to display multiple components, just use v-for and specify conditions in v-if, which will detemine whether this particular component will be rendered:
<input
v-for="input in inputs"
v-if="input.show"
v-model="input.model"
:placeholder="input.label"
type="text"
>
<button #click="handleButtonClick()">Button text</button>
data: () => ({
inputs: [
{
label: 'input 1',
model: '',
show: true
},
{
label: 'input 2',
model: '',
show: true
}
]
}),
methods: {
handleButtonClick () {
console.log(this.inputs)
}
}
If you don't know the type of component you need to display you can use dynamic components.
In a nutshell this defers the type of component used at runtime based on a value.
Let's assume you have 2 different type of components
TextComponent
ImageComponent
You can have a list of items
data () {
return {
items: [
{
id: 1,
val: 'something',
type: 'TextComponent'
},
{
id: 2,
val: 'something',
type: 'ImageComponent'
}
]
}
}
Now you can iterate over the list and display the component based on type
<component v-for="item in items" :key="item.id" :is="item.type" v-model="item.value />
If type is not the exact name of the component, you can translate it inside the :is condition. Something like this.
:is="getComponentFromTag(item.type)"
And then write the conversion method
methods: {
getComponentFromTag (tag) {
switch (tag) {
case 'text':
return 'TextComponent'
case 'img':
return 'ImageComponent'
}
}
}
For the example above I'm assuming that items look like this:
items: [
{
id: 1,
val: 'something',
type: 'text'
},
{
id: 2,
val: 'something',
type: 'img'
}
]

Creating a custom component for alerts?

I'm trying develop a custom component like that shown in my mockup. I found an example component on the web (might have been in Sencha's docs), and now I'm trying to adapt it for my purposes. I have two questions:
Is this the right approach?
How do I drive my data dynamically from my AlertStore. The example was hard-code with a data: [] value. This can't be bound to a Store?
What I need is like a scrollable list view but with a different type of view. Sort of like the balloons in Apple's iPhone Messages app.
Sample code that I found on the Internet and I'm in the middle of adapting:
Ext.define("Sencha.view.ComponentView", {
extend: 'Ext.Component',
xtype: 'custom-component',
config: {
xtype: 'container',
scrollable: true,
layout: {type: 'vbox', pack: 'start', align: 'stretch'},
cls: ['view1'],
data: {
items: [
{name: 'Congestion near tunnel', n: 100},
{name: 'Car fore near exit 10', n: 21},
{name: 'Broken down vehicle in tunnel', n: 24},
{name: 'Slow traffic next 20 miles', n: 24},
{name: 'Drive carefully', n: 26}
]
},
store: 'AlertStore',
tpl: new Ext.XTemplate(
'<tpl for="items">',
'{% if(xindex % this.getPerRow() == 1) {%}',
'<div class="view-container">',
'{% } %}',
'<div class="alert-row">',
'<div class="name">{[xindex]} - {name}</div>',
'</div>',
'{% if(xindex % this.getPerRow() == 0 || xindex == xcount){ %}',
'</div>',
'{% } %}',
'</tpl>',
{
getPerRow: function () {
return 2;
}
})
},
initialize: function () {
this.callParent(arguments);
}
});
You should just be able to use a list and a css class to add rounded corners to your list items.
Here is a basic fiddle: http://new.senchafiddle.com/#/vZ4fT/
I implemented this chat for this application with Sencha Touch 2:
This is a list with an XTemplate. As #kevhender suggested in his comment, you should let your component inherit from Ext.dataview.DataView (or Ext.dataview.List if you don't need listitems made by more than one component).
Of course you can drive your component with a store, checkout Sencha Docs section on stores. You can basically retrieve your data from a proxy attached to the store, or you can get it from any other source, for example with Ext.Ajax or Ext.data.JsonP, then use setData() on the store. Once you have configured correctly the store the list will automatically update itself when changing its contents.
Here's what I came up with from the provided answers.
Ext.define("SF.view.SampleDataView", {
extend: 'Ext.Container',
xtype: 'sample-view',
id: 'sample-view-id',
requires: [],
config: {
cls: ['class1', 'class2'],
items: [
{
xtype: 'dataview',
cls: 'myclass',
itemTpl: '{name}',
store: {
fields: ['name'],
data: [
{name: 'Congestion near tunnel'},
{name: 'Broken down vehicle in tunnel'},
{name: 'The conference is over. See you next year.'},
{name: 'Slow traffic next 20 miles'},
{name: 'Drive carefully'},
{name: 'Congestion near tunnel'},
{name: 'Broken down vehicle in tunnel'},
{name: 'The conference is over. See you next year.'},
{name: 'Slow traffic next 20 miles'},
{name: 'Drive carefully'}
]
}
}
]
},
initialize: function () {
this.callParent(arguments);
}
});
I also added some margin and padding to #bwags' css.
.customRound {
border:2px solid;
border-radius:25px;
margin: 30px;
padding: 10px;
}