pui9-components 1.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/demo.html +10 -0
- package/dist/pui9-components.common.js +81953 -0
- package/dist/pui9-components.common.js.map +1 -0
- package/dist/pui9-components.css +5 -0
- package/dist/pui9-components.umd.js +81963 -0
- package/dist/pui9-components.umd.js.map +1 -0
- package/dist/pui9-components.umd.min.js +308 -0
- package/dist/pui9-components.umd.min.js.map +1 -0
- package/package-lock.json +15945 -0
- package/package.json +78 -0
- package/src/App.vue +117 -0
- package/src/components/PuiCheckbox.vue +105 -0
- package/src/components/PuiCodeEditor.vue +123 -0
- package/src/components/PuiDateField.vue +1004 -0
- package/src/components/PuiField.vue +30 -0
- package/src/components/PuiFieldSet.vue +27 -0
- package/src/components/PuiFormFooter.vue +64 -0
- package/src/components/PuiFormFooterBtns.vue +118 -0
- package/src/components/PuiFormHeader.vue +25 -0
- package/src/components/PuiFormLoading.vue +12 -0
- package/src/components/PuiFormMiniAudit.vue +53 -0
- package/src/components/PuiMasterDetail.vue +96 -0
- package/src/components/PuiModalDialog.vue +87 -0
- package/src/components/PuiModalDialogForm.vue +205 -0
- package/src/components/PuiMultiSelect.vue +499 -0
- package/src/components/PuiNumberField.vue +503 -0
- package/src/components/PuiPasswordField.vue +105 -0
- package/src/components/PuiRadioGroup.vue +105 -0
- package/src/components/PuiRichTextEditor.vue +117 -0
- package/src/components/PuiSelect.vue +1638 -0
- package/src/components/PuiSelectDetailDialog.vue +106 -0
- package/src/components/PuiSelectTextService.vue +61 -0
- package/src/components/PuiSpinnerField.vue +484 -0
- package/src/components/PuiSwitch.vue +104 -0
- package/src/components/PuiTextArea.vue +203 -0
- package/src/components/PuiTextField.vue +272 -0
- package/src/dateTimeUtils.js +78 -0
- package/src/index.js +73 -0
- package/src/main.js +33 -0
- package/src/mixins/PuiFormComponentMixin.js +81 -0
- package/src/mixins/PuiMultiSelectMixin.js +106 -0
- package/src/mixins/PuiUtilsNumberMixin.js +19 -0
- package/src/plugins/vuetify.js +32 -0
- package/src/tests/TestAutocomplete.vue +138 -0
- package/src/tests/TestCodeEditor.vue +48 -0
- package/src/tests/TestField.vue +22 -0
- package/src/tests/TestFieldSet.vue +30 -0
- package/src/tests/TestInputCheckbox.vue +53 -0
- package/src/tests/TestInputDate.vue +146 -0
- package/src/tests/TestInputNumber.vue +77 -0
- package/src/tests/TestInputRadioGroup.vue +86 -0
- package/src/tests/TestInputSpinner.vue +77 -0
- package/src/tests/TestInputSwitch.vue +52 -0
- package/src/tests/TestInputText.vue +120 -0
- package/src/tests/TestInputTextArea.vue +73 -0
- package/src/tests/TestMultiSelect.vue +127 -0
- package/src/tests/TestPuiForm.vue +68 -0
- package/src/tests/TestRichTextEditor.vue +54 -0
- package/src/utils.js +148 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-layout wrap v-if="showDialog">
|
|
3
|
+
<v-flex xs10 md8 lg6 xl4>
|
|
4
|
+
<div class="text-center">
|
|
5
|
+
<v-dialog
|
|
6
|
+
:id="`${dialogName}-modal-container`"
|
|
7
|
+
persistent
|
|
8
|
+
v-model="showDialog"
|
|
9
|
+
:width="widthDialog"
|
|
10
|
+
:content-class="getDialogClasses"
|
|
11
|
+
v-bind="allProps"
|
|
12
|
+
>
|
|
13
|
+
<v-card class="pa-2">
|
|
14
|
+
<v-card-title class="pl-3 headline lighten-2" primary-title>{{ titleText }}</v-card-title>
|
|
15
|
+
<v-card-text>
|
|
16
|
+
<pui-form-header v-if="componentHeader && modalData.headerPk" showHeader>
|
|
17
|
+
<component :is="componentHeader" :modelPk="modalData.headerPk" />
|
|
18
|
+
</pui-form-header>
|
|
19
|
+
<v-form action ref="validateForm" class="px-4" @submit.prevent>
|
|
20
|
+
<slot name="message" v-bind:modalData="modalData"></slot>
|
|
21
|
+
</v-form>
|
|
22
|
+
</v-card-text>
|
|
23
|
+
<v-card-actions ref="actions">
|
|
24
|
+
<v-spacer></v-spacer>
|
|
25
|
+
<v-btn v-if="!disableCancel" :id="`${dialogName}-btn-cancel`" depressed @click="cancel()" :disabled="loading">{{
|
|
26
|
+
buttonCancel
|
|
27
|
+
}}</v-btn>
|
|
28
|
+
<v-btn color="primary" :id="`${dialogName}-btn-ok`" depressed @click="ok()" :loading="loading" :disabled="loading">{{
|
|
29
|
+
buttonOk
|
|
30
|
+
}}</v-btn>
|
|
31
|
+
</v-card-actions>
|
|
32
|
+
</v-card>
|
|
33
|
+
</v-dialog>
|
|
34
|
+
</div>
|
|
35
|
+
</v-flex>
|
|
36
|
+
</v-layout>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
export default {
|
|
41
|
+
name: 'PuiModalDialogForm',
|
|
42
|
+
props: {
|
|
43
|
+
value: {
|
|
44
|
+
type: Object
|
|
45
|
+
},
|
|
46
|
+
titleText: {
|
|
47
|
+
type: String,
|
|
48
|
+
required: true
|
|
49
|
+
},
|
|
50
|
+
modelName: {
|
|
51
|
+
type: String,
|
|
52
|
+
required: true
|
|
53
|
+
},
|
|
54
|
+
dialogName: {
|
|
55
|
+
type: String,
|
|
56
|
+
required: true
|
|
57
|
+
},
|
|
58
|
+
dialogClasses: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: ''
|
|
61
|
+
},
|
|
62
|
+
overflow: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: true
|
|
65
|
+
},
|
|
66
|
+
okText: {
|
|
67
|
+
type: String
|
|
68
|
+
},
|
|
69
|
+
cancelText: {
|
|
70
|
+
type: String
|
|
71
|
+
},
|
|
72
|
+
disableCancel: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false
|
|
75
|
+
},
|
|
76
|
+
widthDialog: {
|
|
77
|
+
type: [String, Number],
|
|
78
|
+
default: 'unset'
|
|
79
|
+
},
|
|
80
|
+
onShow: {
|
|
81
|
+
type: Function,
|
|
82
|
+
default: null
|
|
83
|
+
},
|
|
84
|
+
componentHeaderName: {
|
|
85
|
+
type: String
|
|
86
|
+
},
|
|
87
|
+
showDialogProp: {
|
|
88
|
+
type: Boolean,
|
|
89
|
+
default: false
|
|
90
|
+
},
|
|
91
|
+
onOk: {
|
|
92
|
+
type: Function,
|
|
93
|
+
default: null
|
|
94
|
+
},
|
|
95
|
+
onCancel: {
|
|
96
|
+
type: Function,
|
|
97
|
+
default: null
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
data() {
|
|
101
|
+
return {
|
|
102
|
+
modalData: {},
|
|
103
|
+
componentHeader: null,
|
|
104
|
+
headerPk: {},
|
|
105
|
+
showDialog: false,
|
|
106
|
+
loading: false
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
methods: {
|
|
110
|
+
ok() {
|
|
111
|
+
if (this.disableCancel || (!this.loading && this.$refs.validateForm !== undefined && this.$refs.validateForm.validate())) {
|
|
112
|
+
if (this.onOk) {
|
|
113
|
+
this.loading = true;
|
|
114
|
+
// onOk Function handles show/hide dialog on return value
|
|
115
|
+
const returnOnOk = this.onOk(this.modalData);
|
|
116
|
+
if (returnOnOk !== undefined) {
|
|
117
|
+
returnOnOk.then((value) => {
|
|
118
|
+
this.showDialog = !value;
|
|
119
|
+
this.loading = false;
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
this.loading = false;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
if (this.modalData.isAction) {
|
|
126
|
+
this.$puiEvents.$emit(`onPui-action-running-ended-${this.modelName}`);
|
|
127
|
+
}
|
|
128
|
+
this.showDialog = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
cancel() {
|
|
133
|
+
if (this.onCancel) {
|
|
134
|
+
// onCancel Function handles show/hide dialog on return value
|
|
135
|
+
const returnOnCancel = this.onCancel(this.modalData);
|
|
136
|
+
if (returnOnCancel !== undefined) this.showDialog = !returnOnCancel;
|
|
137
|
+
} else {
|
|
138
|
+
this.showDialog = false;
|
|
139
|
+
}
|
|
140
|
+
if (this.modalData.isAction) {
|
|
141
|
+
this.$puiEvents.$emit(`onPui-action-running-ended-${this.modelName}`);
|
|
142
|
+
}
|
|
143
|
+
this.modalData = {};
|
|
144
|
+
},
|
|
145
|
+
loaderDynamicHeader() {
|
|
146
|
+
if (this.headerPk && this.componentHeaderName) {
|
|
147
|
+
this.componentHeader = this.componentHeaderName;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
computed: {
|
|
152
|
+
getOverflow() {
|
|
153
|
+
return this.overflow ? '' : 'pui-modal-dialog-overflow-off';
|
|
154
|
+
},
|
|
155
|
+
buttonOk() {
|
|
156
|
+
return this.okText || this.$t('pui9.accept');
|
|
157
|
+
},
|
|
158
|
+
buttonCancel() {
|
|
159
|
+
return this.cancelText || this.$t('pui9.cancel');
|
|
160
|
+
},
|
|
161
|
+
getDialogClasses() {
|
|
162
|
+
return this.dialogClasses + ' ' + this.getOverflow;
|
|
163
|
+
},
|
|
164
|
+
allProps() {
|
|
165
|
+
return { ...this.$attrs, ...this.$props };
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
mounted() {
|
|
169
|
+
this.loaderDynamicHeader();
|
|
170
|
+
this.$puiEvents.$on(`pui-modalDialogForm-${this.dialogName}-${this.modelName}-show`, (data) => {
|
|
171
|
+
// Clean posible oldData
|
|
172
|
+
this.modalData = {};
|
|
173
|
+
for (const key in data) {
|
|
174
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
175
|
+
// For make the container of the data reactive we use $set vue function
|
|
176
|
+
this.$set(this.modalData, key, data[key]);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// If not loaded from action, disable action events for transfer data, disable styles action button...
|
|
180
|
+
this.modalData.isAction = data.isAction ? data.isAction : false;
|
|
181
|
+
if (data.showDialog === undefined || data.showDialog === true) this.showDialog = true;
|
|
182
|
+
this.onShow && this.onShow(this.modalData);
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
destroyed() {
|
|
186
|
+
this.$puiEvents.$off(`pui-modalDialogForm-${this.dialogName}-${this.modelName}-show`);
|
|
187
|
+
},
|
|
188
|
+
watch: {
|
|
189
|
+
// If some data change from the modalData $emit it in case that they need on the parent component
|
|
190
|
+
modalData: {
|
|
191
|
+
handler(newValue) {
|
|
192
|
+
if (newValue.showDialog !== undefined) {
|
|
193
|
+
this.showDialog = newValue.showDialog;
|
|
194
|
+
}
|
|
195
|
+
this.$emit('input', newValue);
|
|
196
|
+
},
|
|
197
|
+
deep: true
|
|
198
|
+
},
|
|
199
|
+
showDialogProp(newValue) {
|
|
200
|
+
this.loading = false;
|
|
201
|
+
this.showDialog = newValue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
</script>
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-layout wrap>
|
|
3
|
+
<v-flex xs12>
|
|
4
|
+
<v-card :flat="flat" elevation="0">
|
|
5
|
+
<div :class="{ 'pui-multiselect--flat': flat, 'pui-multiselect--disabled': disabled }">
|
|
6
|
+
<v-layout wrap>
|
|
7
|
+
<v-flex xs12 sm6>
|
|
8
|
+
<!-- slot1, TODO MAKE SLOTS IN FILTER AND SORTING DIALOGS-->
|
|
9
|
+
<v-flex xs12 class="pt-2">
|
|
10
|
+
<span class="pui-dialog__title pui-dialog__title--margin">
|
|
11
|
+
{{ $t(textAvailable) }}
|
|
12
|
+
</span>
|
|
13
|
+
</v-flex>
|
|
14
|
+
<v-flex xs12 class="pui-multiselect__searchpanel">
|
|
15
|
+
<pui-text-field
|
|
16
|
+
:placeholder="labelSearch"
|
|
17
|
+
v-model="searchAvailable"
|
|
18
|
+
clearable
|
|
19
|
+
prepend-inner-icon="far fa-search"
|
|
20
|
+
noeditable
|
|
21
|
+
realtime
|
|
22
|
+
></pui-text-field>
|
|
23
|
+
</v-flex>
|
|
24
|
+
<v-flex xs12>
|
|
25
|
+
<v-list
|
|
26
|
+
:style="styleList"
|
|
27
|
+
class="overflow-y-auto pui-multiselect__itemListContainer pui-multiselect__itemListContainer-available"
|
|
28
|
+
>
|
|
29
|
+
<draggable
|
|
30
|
+
v-model="availableItems"
|
|
31
|
+
v-bind="dragOptions"
|
|
32
|
+
@change="onChangeAvailables"
|
|
33
|
+
@end="
|
|
34
|
+
searchSelected = '';
|
|
35
|
+
setSelectedItems();
|
|
36
|
+
"
|
|
37
|
+
class="pui-multiselect__item-draggable"
|
|
38
|
+
:class="{ 'pui-multiselect__item--tall': itemsSearchAvailable.length === 0 }"
|
|
39
|
+
>
|
|
40
|
+
<template v-for="(item, index) in itemsSearchAvailable">
|
|
41
|
+
<!-- He quitado esta líea del v-layout de abajo porque hacía de un tamaño muy grande al elemento
|
|
42
|
+
:class="{'pui-multiselect__item--tall': itemsSearchAvailable.length < 4 && index === itemsSearchAvailable.length -1}"
|
|
43
|
+
-->
|
|
44
|
+
<v-layout :key="item[itemValue]" class="pui-multiselect__item">
|
|
45
|
+
<v-flex xs1 d-flex align-center>
|
|
46
|
+
<v-list-item class="move draggable_point">
|
|
47
|
+
<v-icon small>far fa-ellipsis-v</v-icon>
|
|
48
|
+
<v-icon small>far fa-ellipsis-v</v-icon>
|
|
49
|
+
</v-list-item>
|
|
50
|
+
</v-flex>
|
|
51
|
+
<v-flex xs11>
|
|
52
|
+
<v-list-item class>
|
|
53
|
+
<v-list-item-content @dblclick="selectItemFromEvent(index, item)">
|
|
54
|
+
<v-list-item-title
|
|
55
|
+
class="pui-multiselect--title"
|
|
56
|
+
v-html="item[itemText]"
|
|
57
|
+
:title="item[itemText]"
|
|
58
|
+
></v-list-item-title>
|
|
59
|
+
<v-list-item-subtitle
|
|
60
|
+
v-if="itemDescription"
|
|
61
|
+
v-html="item[itemDescription]"
|
|
62
|
+
></v-list-item-subtitle>
|
|
63
|
+
</v-list-item-content>
|
|
64
|
+
<v-list-item-action>
|
|
65
|
+
<v-btn
|
|
66
|
+
small
|
|
67
|
+
icon
|
|
68
|
+
ripple
|
|
69
|
+
@click.native="selectItemFromEvent(index, item)"
|
|
70
|
+
:disabled="disabled"
|
|
71
|
+
>
|
|
72
|
+
<v-icon>far fa-plus</v-icon>
|
|
73
|
+
</v-btn>
|
|
74
|
+
</v-list-item-action>
|
|
75
|
+
</v-list-item>
|
|
76
|
+
</v-flex>
|
|
77
|
+
</v-layout>
|
|
78
|
+
</template>
|
|
79
|
+
</draggable>
|
|
80
|
+
<v-card v-show="availableItems.length === 0" flat class="pui-multiselect__itemListContainer-cardHelper pl-3 pr-3">
|
|
81
|
+
<div class="pui-multiselect__itemListContainer-cardHelper-text pb-1 pl-2 pr-2">
|
|
82
|
+
<span class="pui-multiselect__itemListContainer-cardHelper-text-title pb-3">
|
|
83
|
+
{{ $t('pui9.components.multiSelect.nothing_to_select') }}
|
|
84
|
+
</span>
|
|
85
|
+
<span class="pui-multiselect__itemListContainer-cardHelper-text-info">
|
|
86
|
+
{{ $t('pui9.components.multiSelect.multiselect_help_to_unselect') }}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
</v-card>
|
|
90
|
+
</v-list>
|
|
91
|
+
</v-flex>
|
|
92
|
+
</v-flex>
|
|
93
|
+
<v-flex xs12 sm6>
|
|
94
|
+
<v-layout wrap>
|
|
95
|
+
<v-flex xs12 class="pt-2">
|
|
96
|
+
<span class="pui-dialog__title pui-dialog__title--margin">
|
|
97
|
+
{{ $t(textSelected) }}
|
|
98
|
+
</span>
|
|
99
|
+
</v-flex>
|
|
100
|
+
<v-flex xs12 class="pui-multiselect__searchpanel">
|
|
101
|
+
<pui-text-field
|
|
102
|
+
:placeholder="labelSearch"
|
|
103
|
+
v-model="searchSelected"
|
|
104
|
+
clearable
|
|
105
|
+
prepend-inner-icon="far fa-search"
|
|
106
|
+
noeditable
|
|
107
|
+
realtime
|
|
108
|
+
></pui-text-field>
|
|
109
|
+
</v-flex>
|
|
110
|
+
<v-flex xs12>
|
|
111
|
+
<v-list
|
|
112
|
+
:style="styleList"
|
|
113
|
+
:two-line="itemDescription !== null"
|
|
114
|
+
class="overflow-y-auto pui-multiselect__itemListContainer"
|
|
115
|
+
:class="compClassItemListSelected"
|
|
116
|
+
>
|
|
117
|
+
<draggable
|
|
118
|
+
v-model="selectedItems"
|
|
119
|
+
v-bind="dragOptions"
|
|
120
|
+
@change="onChangeSelected"
|
|
121
|
+
@end="
|
|
122
|
+
searchAvailable = '';
|
|
123
|
+
setSelectedItems();
|
|
124
|
+
"
|
|
125
|
+
class="pui-multiselect__item-draggable"
|
|
126
|
+
:class="{ 'pui-multiselect__item--tall': itemsSearchSelected.length === 0 }"
|
|
127
|
+
>
|
|
128
|
+
<template v-for="(item, index) in itemsSearchSelected">
|
|
129
|
+
<!-- He quitado esta líea del v-layout de abajo porque hacía de un tamaño muy grande al elemento
|
|
130
|
+
:class="{'pui-multiselect__item--tall': itemsSearchSelected.length < 4 && index === itemsSearchSelected.length -1}"
|
|
131
|
+
-->
|
|
132
|
+
<v-layout :key="item[itemValue]" class="pui-multiselect__item">
|
|
133
|
+
<v-flex xs1 d-flex align-center>
|
|
134
|
+
<v-list-item class="move draggable_point">
|
|
135
|
+
<v-icon small>far fa-ellipsis-v</v-icon>
|
|
136
|
+
<v-icon small>far fa-ellipsis-v</v-icon>
|
|
137
|
+
</v-list-item>
|
|
138
|
+
</v-flex>
|
|
139
|
+
<v-flex xs11>
|
|
140
|
+
<v-list-item>
|
|
141
|
+
<v-list-item-content @dblclick="unselectItemFromEvent(index, item)">
|
|
142
|
+
<v-list-item-title
|
|
143
|
+
class="pui-multiselect--title"
|
|
144
|
+
v-html="item[itemText]"
|
|
145
|
+
:title="item[itemText]"
|
|
146
|
+
></v-list-item-title>
|
|
147
|
+
<v-list-item-subtitle
|
|
148
|
+
v-if="itemDescription"
|
|
149
|
+
v-html="item[itemDescription]"
|
|
150
|
+
></v-list-item-subtitle>
|
|
151
|
+
</v-list-item-content>
|
|
152
|
+
<v-list-item-action>
|
|
153
|
+
<v-btn
|
|
154
|
+
small
|
|
155
|
+
icon
|
|
156
|
+
ripple
|
|
157
|
+
@click.native="unselectItemFromEvent(index, item)"
|
|
158
|
+
:disabled="disabled"
|
|
159
|
+
>
|
|
160
|
+
<v-icon>far fa-times</v-icon>
|
|
161
|
+
</v-btn>
|
|
162
|
+
</v-list-item-action>
|
|
163
|
+
</v-list-item>
|
|
164
|
+
</v-flex>
|
|
165
|
+
</v-layout>
|
|
166
|
+
</template>
|
|
167
|
+
</draggable>
|
|
168
|
+
<v-card
|
|
169
|
+
v-show="selectedItems.length === 0"
|
|
170
|
+
flat
|
|
171
|
+
class="pui9-grey pui-multiselect__itemListContainer-cardHelper pl-3 pr-3"
|
|
172
|
+
>
|
|
173
|
+
<div class="pui-multiselect__itemListContainer-cardHelper-text pb-1 pl-2 pr-2">
|
|
174
|
+
<span class="pui-multiselect__itemListContainer-cardHelper-text-title pb-3">
|
|
175
|
+
{{ $t('pui9.components.multiSelect.nothing_selected') }}
|
|
176
|
+
</span>
|
|
177
|
+
<span class="pui-multiselect__itemListContainer-cardHelper-text-info">
|
|
178
|
+
{{ $t('pui9.components.multiSelect.multiselect_help') }}
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
</v-card>
|
|
182
|
+
</v-list>
|
|
183
|
+
</v-flex>
|
|
184
|
+
</v-layout>
|
|
185
|
+
</v-flex>
|
|
186
|
+
</v-layout>
|
|
187
|
+
<v-card-actions class="ma-0 pb-2 pr-2 pl-2">
|
|
188
|
+
<v-btn
|
|
189
|
+
text
|
|
190
|
+
outlined
|
|
191
|
+
:disabled="disabled || availableItems.length === 0"
|
|
192
|
+
class="puiToolbar-dialog__button"
|
|
193
|
+
@click.native="selectAllItems()"
|
|
194
|
+
>{{ $t('pui9.components.multiSelect.add_all') }}</v-btn
|
|
195
|
+
>
|
|
196
|
+
<v-spacer></v-spacer>
|
|
197
|
+
<span class="pr-2" v-show="selectedItems.length === 1">{{
|
|
198
|
+
`${selectedItems.length} ${$t('pui9.components.multiSelect.selected_item')}`
|
|
199
|
+
}}</span>
|
|
200
|
+
<span class="pr-2" v-show="selectedItems.length > 1">{{
|
|
201
|
+
`${selectedItems.length} ${$t('pui9.components.multiSelect.selected_items')}`
|
|
202
|
+
}}</span>
|
|
203
|
+
<v-btn
|
|
204
|
+
text
|
|
205
|
+
outlined
|
|
206
|
+
:disabled="disabled || selectedItems.length === 0"
|
|
207
|
+
@click.native="unselectAllItems()"
|
|
208
|
+
class="puiToolbar-dialog__button"
|
|
209
|
+
>{{ $t('pui9.components.multiSelect.empty') }}</v-btn
|
|
210
|
+
>
|
|
211
|
+
</v-card-actions>
|
|
212
|
+
</div>
|
|
213
|
+
</v-card>
|
|
214
|
+
</v-flex>
|
|
215
|
+
</v-layout>
|
|
216
|
+
</template>
|
|
217
|
+
|
|
218
|
+
<script>
|
|
219
|
+
import PuiMultiSelectMixin from '../mixins/PuiMultiSelectMixin';
|
|
220
|
+
import PuiTextField from './PuiTextField';
|
|
221
|
+
import draggable from 'vuedraggable';
|
|
222
|
+
|
|
223
|
+
export default {
|
|
224
|
+
name: 'PuiMultiSelect',
|
|
225
|
+
mixins: [PuiMultiSelectMixin],
|
|
226
|
+
components: {
|
|
227
|
+
PuiTextField,
|
|
228
|
+
draggable
|
|
229
|
+
},
|
|
230
|
+
/**
|
|
231
|
+
* !important there are more properties in the selectMixin:
|
|
232
|
+
* items,itemsToSelect,itemText,itemValue
|
|
233
|
+
*/
|
|
234
|
+
props: {
|
|
235
|
+
flat: {
|
|
236
|
+
default: false
|
|
237
|
+
},
|
|
238
|
+
heightList: {
|
|
239
|
+
type: Number,
|
|
240
|
+
default: 300
|
|
241
|
+
},
|
|
242
|
+
itemDescription: {
|
|
243
|
+
type: String,
|
|
244
|
+
default: null
|
|
245
|
+
},
|
|
246
|
+
/**
|
|
247
|
+
* El texto por traducir que se muestra como titulo de los items disponibles
|
|
248
|
+
* Por defecto disponibles
|
|
249
|
+
*/
|
|
250
|
+
textAvailable: {
|
|
251
|
+
type: String,
|
|
252
|
+
default: 'pui9.components.multiSelect.available'
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* El texto por traducir que se muestra como titulo de los items seleccionados
|
|
256
|
+
* Por defecto seleccionados
|
|
257
|
+
*/
|
|
258
|
+
textSelected: {
|
|
259
|
+
type: String,
|
|
260
|
+
default: 'pui9.components.multiSelect.selected'
|
|
261
|
+
},
|
|
262
|
+
disabled: {
|
|
263
|
+
type: Boolean,
|
|
264
|
+
default: false
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
data() {
|
|
268
|
+
return {
|
|
269
|
+
searchAvailable: '',
|
|
270
|
+
searchSelected: '',
|
|
271
|
+
searchingAvailable: true,
|
|
272
|
+
searchingSelected: true,
|
|
273
|
+
dragOptions: {
|
|
274
|
+
group: 'multiselect',
|
|
275
|
+
disabled: this.disabled
|
|
276
|
+
},
|
|
277
|
+
selectedItems: [],
|
|
278
|
+
availableItems: []
|
|
279
|
+
};
|
|
280
|
+
},
|
|
281
|
+
updated() {
|
|
282
|
+
this.searchingAvailable = false;
|
|
283
|
+
this.searchingSelected = false;
|
|
284
|
+
},
|
|
285
|
+
computed: {
|
|
286
|
+
compClassItemListSelected() {
|
|
287
|
+
if (!this.selectedItems) {
|
|
288
|
+
return {
|
|
289
|
+
'pui9-grey': true,
|
|
290
|
+
'pui-multiselect__itemListContainer-selected': true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
'pui9-grey': this.selectedItems.length === 0,
|
|
295
|
+
'pui-multiselect__itemListContainer-selected': this.selectedItems.length === 0
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
itemsSearchAvailable() {
|
|
299
|
+
if (!this.searchAvailable) {
|
|
300
|
+
return this.availableItems;
|
|
301
|
+
}
|
|
302
|
+
//this.searchingAvailable = true;
|
|
303
|
+
return this.availableItems
|
|
304
|
+
.filter((item) => {
|
|
305
|
+
try {
|
|
306
|
+
const match =
|
|
307
|
+
item[this.itemText].toUpperCase().includes(this.searchAvailable.toUpperCase()) ||
|
|
308
|
+
(this.itemDescription && item[this.itemDescription].toUpperCase().includes(this.searchAvailable.toUpperCase()));
|
|
309
|
+
return match;
|
|
310
|
+
} catch (e) {
|
|
311
|
+
//al item le falta el texto o la descripción
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
.sort((a, b) => {
|
|
316
|
+
const aValue = a[this.itemText];
|
|
317
|
+
const bValue = b[this.itemText];
|
|
318
|
+
return aValue.localeCompare(bValue);
|
|
319
|
+
});
|
|
320
|
+
//this.searchingAvailable = false;
|
|
321
|
+
},
|
|
322
|
+
itemsSearchSelected() {
|
|
323
|
+
if (!this.searchSelected) {
|
|
324
|
+
return this.selectedItems;
|
|
325
|
+
}
|
|
326
|
+
//this.searchingSelected = true;
|
|
327
|
+
return this.selectedItems
|
|
328
|
+
.filter((item) => {
|
|
329
|
+
try {
|
|
330
|
+
const match =
|
|
331
|
+
item[this.itemText].toUpperCase().includes(this.searchSelected.toUpperCase()) ||
|
|
332
|
+
(this.itemDescription && item[this.itemDescription].toUpperCase().includes(this.searchSelected.toUpperCase()));
|
|
333
|
+
return match;
|
|
334
|
+
} catch (e) {
|
|
335
|
+
//al item le falta el texto o la descripción
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
.sort((a, b) => {
|
|
340
|
+
const aValue = a[this.itemText];
|
|
341
|
+
const bValue = b[this.itemText];
|
|
342
|
+
return aValue.localeCompare(bValue);
|
|
343
|
+
});
|
|
344
|
+
//this.searchingSelected = false;
|
|
345
|
+
},
|
|
346
|
+
styleList() {
|
|
347
|
+
return `height: ${this.heightList.toString()}px`;
|
|
348
|
+
},
|
|
349
|
+
labelSearch() {
|
|
350
|
+
return this.$t('pui9.components.multiSelect.search');
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
methods: {
|
|
354
|
+
setAvailableItems() {
|
|
355
|
+
this.availableItems = [];
|
|
356
|
+
this.items
|
|
357
|
+
.sort((item1, item2) => {
|
|
358
|
+
if (item1[this.itemText] > item2[this.itemText]) {
|
|
359
|
+
return 1;
|
|
360
|
+
} else if (item1[this.itemText] < item2[this.itemText]) {
|
|
361
|
+
return -1;
|
|
362
|
+
}
|
|
363
|
+
return 0;
|
|
364
|
+
})
|
|
365
|
+
.forEach((item) => this.availableItems.push(item));
|
|
366
|
+
},
|
|
367
|
+
selectItem(item) {
|
|
368
|
+
this.selectedItems.push(item);
|
|
369
|
+
this.availableItems.forEach((availableItem, index) => {
|
|
370
|
+
if (item[this.itemValue] === availableItem[this.itemValue]) {
|
|
371
|
+
this.availableItems.splice(index, 1);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
this.$emit('input', this.selectedItems);
|
|
375
|
+
},
|
|
376
|
+
selectItemById(id, theItem) {
|
|
377
|
+
this.selectedItems.push(theItem);
|
|
378
|
+
for (let index = 0, length = this.availableItems.length; index < length; index++) {
|
|
379
|
+
const availableItem = this.availableItems[index];
|
|
380
|
+
if (availableItem[this.itemValue] === id) {
|
|
381
|
+
//comprobamos por referencia que el item y el availableItem no es lo mismo,
|
|
382
|
+
//en este caso hay que theItem que se acaba de añadir 4 lineas de código atras, y añadimos el bueno
|
|
383
|
+
//esto significa que theItem solo lleva los campos de la pk y no es el item "entero"
|
|
384
|
+
if (availableItem !== theItem) {
|
|
385
|
+
this.selectedItems.pop();
|
|
386
|
+
this.selectedItems.push(availableItem);
|
|
387
|
+
}
|
|
388
|
+
this.availableItems.splice(index, 1);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
setSelectedItems() {
|
|
394
|
+
this.$emit('input', this.selectedItems);
|
|
395
|
+
},
|
|
396
|
+
unselectItem(item) {
|
|
397
|
+
this.availableItems.push(item);
|
|
398
|
+
this.selectedItems.forEach((selectedItem, index) => {
|
|
399
|
+
if (item[this.itemValue] === selectedItem[this.itemValue]) {
|
|
400
|
+
this.selectedItems.splice(index, 1);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
this.$emit('input', this.selectedItems);
|
|
404
|
+
},
|
|
405
|
+
selectItemFromEvent(index, item) {
|
|
406
|
+
if (this.disabled) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
this.searchSelected = '';
|
|
410
|
+
if (this.searchAvailable) {
|
|
411
|
+
this.selectItem(item);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
this.availableItems.splice(index, 1);
|
|
415
|
+
this.selectedItems.push(item);
|
|
416
|
+
this.$emit('input', this.selectedItems);
|
|
417
|
+
},
|
|
418
|
+
unselectItemFromEvent(index, item) {
|
|
419
|
+
if (this.disabled) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
this.searchAvailable = '';
|
|
423
|
+
if (this.searchSelected) {
|
|
424
|
+
this.unselectItem(item);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
this.selectedItems.splice(index, 1);
|
|
428
|
+
this.availableItems.push(item);
|
|
429
|
+
this.$emit('input', this.selectedItems);
|
|
430
|
+
},
|
|
431
|
+
selectAllItems() {
|
|
432
|
+
this.searchSelected = '';
|
|
433
|
+
this.searchingAvailable = true;
|
|
434
|
+
this.searchingSelected = true;
|
|
435
|
+
this.selectedItems = this.selectedItems.concat(this.availableItems);
|
|
436
|
+
this.availableItems = [];
|
|
437
|
+
this.$emit('input', this.selectedItems);
|
|
438
|
+
},
|
|
439
|
+
unselectAllItems() {
|
|
440
|
+
this.searchingAvailable = true;
|
|
441
|
+
this.searchingSelected = true;
|
|
442
|
+
this.searchAvailable = '';
|
|
443
|
+
this.availableItems = this.availableItems.concat(this.selectedItems);
|
|
444
|
+
this.selectedItems = [];
|
|
445
|
+
this.$emit('input', this.selectedItems);
|
|
446
|
+
},
|
|
447
|
+
onChangeAvailables(event) {
|
|
448
|
+
// due to drag and drop, if searchAvailable has value, the item selected by the user is not the item added to
|
|
449
|
+
// selectedItems so:
|
|
450
|
+
// - it has to be removed manually from selectedItems,
|
|
451
|
+
// - it has to be added to the availableItems one more time,
|
|
452
|
+
// - selected item by the user that is in itemsSearchAvailable, has to be added to selectedItems
|
|
453
|
+
// - and selected item by the user has to be removed from availableItems
|
|
454
|
+
if (event.removed && this.itemsSearchAvailable.length > 0 && this.itemsSearchAvailable.length < this.availableItems.length) {
|
|
455
|
+
// remove last from selectedItems because it is not the selected item by user
|
|
456
|
+
const removed = event.removed.element;
|
|
457
|
+
let removedIndex = this.selectedItems.length;
|
|
458
|
+
this.selectedItems.forEach((selectedItem, index) => {
|
|
459
|
+
if (removed[this.itemValue] === selectedItem[this.itemValue]) {
|
|
460
|
+
removedIndex = index;
|
|
461
|
+
this.selectedItems.splice(index, 1);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
// push removed item in availableItems
|
|
465
|
+
this.availableItems.push(removed);
|
|
466
|
+
// add selected item by user to the selectedItems in the same position as removed previously
|
|
467
|
+
const added = this.itemsSearchAvailable[event.removed.oldIndex];
|
|
468
|
+
this.selectedItems.splice(removedIndex, 0, added);
|
|
469
|
+
// remove selected item from availableItems
|
|
470
|
+
this.availableItems.forEach((availableItem, index) => {
|
|
471
|
+
if (added[this.itemValue] === availableItem[this.itemValue]) {
|
|
472
|
+
this.availableItems.splice(index, 1);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
onChangeSelected(event) {
|
|
478
|
+
if (event.removed && this.itemsSearchSelected.length > 0 && this.itemsSearchSelected.length < this.selectedItems.length) {
|
|
479
|
+
const removed = event.removed.element;
|
|
480
|
+
let removedIndex = this.availableItems.length;
|
|
481
|
+
this.availableItems.forEach((availableItem, index) => {
|
|
482
|
+
if (removed[this.itemValue] === availableItem[this.itemValue]) {
|
|
483
|
+
removedIndex = index;
|
|
484
|
+
this.availableItems.splice(index, 1);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
this.selectedItems.splice(event.removed.oldIndex, 0, removed);
|
|
488
|
+
const added = this.itemsSearchSelected[event.removed.oldIndex];
|
|
489
|
+
this.availableItems.splice(removedIndex, 0, added);
|
|
490
|
+
this.selectedItems.forEach((selectedItem, index) => {
|
|
491
|
+
if (added[this.itemValue] === selectedItem[this.itemValue]) {
|
|
492
|
+
this.selectedItems.splice(index, 1);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
</script>
|