focus out when outside the of the component is clicked - javascript

I have a helper custom component that works as a multiple-choice selection. However, I am facing a problem with the ul list. I need to keep it open when the user selects one or more options. It shouldn't focus out at that time. If outside the component is clicked, it should focusout and close the list. Currently whenever a list is selected the div focuses out. I have tried to patch it but it is a buggy solution, sometimes the focusout event isn't firing. Any sort of suggestion is appreciated.
Thank you!
This is the template part of the component
<template>
<div
class="relative pb-6"
tabindex="0"
:class="{
'has-error': errorText,
success: !errorText
}"
#focusout="handleFocusOut"
>
<label
class="label block pt-0"
>
{{ label }}
</label>
<button
class="btn-primary flex w-full justify-between"
#click.prevent="onClick"
>
<span>
{{ displayedSelectedValue }}
</span>
<span class="ml-auto">
<ChevronUpIcon
v-if="showSelectOptions"
class="h-6 w-6"
/>
<ChevronDownIcon
v-else
class="h-6 w-6"
/>
</span>
</button>
<ul
v-show="showSelectOptions"
class="absolute bottom-16 z-10 max-h-72 w-full space-y-1 overflow-y-scroll
rounded border border-gray-300 bg-white px-4 py-2 shadow-xl"
>
<li
v-for="item in items"
:key="item"
>
<label
:for="item"
class="block cursor-pointer"
>
<input
:id="item"
type="checkbox"
class="cursor-pointer bg-gray-500"
:name="item"
:checked="isItemSelected(item)"
#input="onSelectItem(item)"
>
<span
class="mx-4"
>
{{ item }}
</span>
</label>
</li>
</ul>
<p
class="help-message absolute p-1 text-sm"
>
<template
v-if="errorText"
>
{{ errorText }}
</template>
</p>
</div>
</template>
and this is the script part of the component:
<script lang="ts" setup>
import {
ChevronDownIcon,
ChevronUpIcon,
XIcon,
} from '#heroicons/vue/outline';
import {
ref,
watch,
PropType,
computed,
} from 'vue';
const emits = defineEmits<{
(e: 'blur', value: string[]): void;
(e: 'select', value: string[]): void;
}>();
const props = defineProps({
label: {
type: String,
required: true,
},
multiple: {
type: Boolean,
default: false,
},
value: {
type: Array as PropType<string[]>,
required: true,
},
items: {
type: Array as PropType<string[]>,
required: true,
},
errorText: {
type: String,
default: '',
},
});
watch(() => props.value, () => {
selectedValue.value = props.value;
});
const showSelectOptions = ref(false);
const selectedValue = ref(props.value);
const isListClicked = ref(false);
const displayedSelectedValue = computed(() => selectedValue.value.join(', '));
let inputBlurTimeoutId: number | undefined;
function isItemSelected(item: string) {
return selectedValue.value.includes(item);
}
function onClick(e) {
if (inputBlurTimeoutId) {
window.clearTimeout(inputBlurTimeoutId);
}
showSelectOptions.value = !showSelectOptions.value;
}
function handleFocusOut() {
console.log('focus out');
if (inputBlurTimeoutId) {
window.clearTimeout(inputBlurTimeoutId);
}
inputBlurTimeoutId = window.setTimeout(() => {
if (isListClicked.value) {
isListClicked.value = false;
return;
}
showSelectOptions.value = false;
}, 1 * 500);
}
function onSelectItem(item: string) {
isListClicked.value = true;
const itemIndex = selectedValue.value.indexOf(item);
if (itemIndex !== -1) {
selectedValue.value.splice(itemIndex, 1);
} else {
selectedValue.value.push(item);
}
emits('select', selectedValue.value);
}
</script>

Related

Blur event not working on custom multi-select Vue component

Blur event is not working properly. It works if I click anywhere in the component except when clicking in the input field. If I click in the input field then outside the component, it won't trigger the blur event which closes the options list. How can I make the blur event on the outer div work after clicking on the input field and then clicking outside the component (* blur event should that be triggered if I click on the components list since it is still within the component, therefore I can't just place a blur event on the input field)
<template>
<div class="flex flex-col relative w-full">
<span v-if="label" class="font-jost-medium mb-2">{{ label }}</span>
<div>
<div #blur="showOptions = false" :tabindex="tabIndex">
<div
class="border border-[#EAEAEA] bg-white rounded-md flex flex-col w-full"
>
<div
v-if="selectedOptions.length"
class="flex flex-wrap px-4 py-2 border-b gap-2"
>
<div
v-for="option in selectedOptions"
class="border bg-secondary rounded-full py-1 px-2 flex items-center"
>
<span>{{ option.text }}</span>
<vue-feather
type="x"
class="h-3 w-3 ml-1.5 cursor-pointer"
#click="onDeleteOption(option)"
/>
</div>
</div>
<div
class="flex flex-row justify-end items-center px-4 cursor-pointer"
:class="selectedOptions.length ? 'py-2' : 'p-4'"
#click="showOptions = !showOptions"
>
<MagnifyingGlassIcon class="h-5 w-5 mr-2" />
<input
class="focus:outline-0 w-full"
type="text"
v-model="searchInput"
/>
<vue-feather type="chevron-down" class="h-5 w-5" />
</div>
</div>
<div v-if="showOptions && optionsMap.length" class="options-list">
<ul role="listbox" class="w-full overflow-auto">
<li
class="hover:bg-primary-light px-4 py-2 rounded-md cursor-pointer"
role="option"
v-for="option in optionsMap"
#mousedown="onOptionClick(option)"
>
{{ option.text }}
</li>
</ul>
</div>
<div
id="not-found"
class="absolute w-full italic text-center text-inactive-grey"
v-else-if="!optionsMap.length"
>
No records found
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch } from "vue";
import { IconNameTypes } from "#/types/enums/IconNameTypes";
import { AppIcon } from "#/components/base/index";
import { MagnifyingGlassIcon } from "#heroicons/vue/24/outline";
export default defineComponent({
name: "AppAutocomplete",
components: {
AppIcon,
MagnifyingGlassIcon,
},
props: {
modelValue: {
type: String,
},
label: {
type: String,
default: "",
},
tabIndex: {
type: Number,
default: 0,
},
options: {
type: Array as PropType<{ text: string; value: string }[]>,
required: true,
},
},
setup(props, { emit }) {
const showOptions = ref(false);
const optionsMap = ref(props.options);
const selectedOptions = ref<{ text: string; value: string }[]>([]);
const searchInput = ref("");
watch(searchInput, () => {
optionsMap.value = props.options.filter((option1) => {
return (
!selectedOptions.value.some((option2) => {
return option1.text === option2.text;
}) &&
option1.text.toLowerCase().includes(searchInput.value.toLowerCase())
);
});
sortOptionsMapList();
});
const onOptionClick = (option: { text: string; value: string }) => {
searchInput.value = "";
selectedOptions.value.push(option);
optionsMap.value = optionsMap.value.filter((option1) => {
return !selectedOptions.value.some((option2) => {
return option1.text === option2.text;
});
});
sortOptionsMapList();
emit("update:modelValue", option.value);
};
const onDeleteOption = (option: { text: string; value: string }) => {
selectedOptions.value = selectedOptions.value.filter((option2) => {
return option2.text !== option.text;
});
optionsMap.value.push(option);
sortOptionsMapList();
};
const sortOptionsMapList = () => {
optionsMap.value.sort(function (a, b) {
return a.text.localeCompare(b.text);
});
};
sortOptionsMapList();
document.addEventListener("click", () => {
console.log(document.activeElement);
});
return {
showOptions,
optionsMap,
searchInput,
selectedOptions,
IconNameTypes,
onOptionClick,
onDeleteOption,
};
},
});
</script>
<style scoped lang="scss">
.options-list,
#not-found {
box-shadow: 0 0 50px 0 rgb(19 19 28 / 12%);
#apply border border-[#EAEAEA] rounded-md p-4 mt-1 absolute bg-white z-10 w-full;
}
ul {
#apply max-h-52 #{!important};
}
</style>
blur is not an event that 'bubbles up' to outer elements, so it never reaches the parent div. What you want is focusout
<div #focusout="showOptions = false" :tabindex="tabIndex">

v-if doesn't work when I change to the composition API

I'm new to VueJS and I had some problems when I tried to change my code from vuejs 2 to vuejs3 with composition API.
When I tried to change it to the code below, the v-if statement doesn't work when I clicked the color button to change the active button.
<template>
<div class="relative mt-2 mx-2 text-left">
<div class="w-7/12 md:flex md:flex-wrap" id="color-picker">
<button
v-for="color in colors"
#click="updateActiveColor(color.value)"
class="w-8 h-8 mr-2 rounded-full p-2 mt-2"
:key="color.value"
type="button"
:data-testid="'button-' + color.value"
:style="{ backgroundColor: color.color }"
>
<div v-if="selectColor.value === color.value" class="justify-center w-4 p-0 m-auto text-white">
<CheckIcon />
</div>
</button>
</div>
</div>
</template>
<script lang="ts">
export default defineComponent({
props: {
colors: {
type: Object as() => Array < BaseColorPickerItem > ,
required: true,
},
name: {
type: String,
required: true,
},
selectedColor: {
type: Object as() => Prop < BaseColorPickerItem > ,
require: false,
},
},
setup(props, context) {
let selectColor = ref(props.colors[0]);
const updateActiveColor = (color: any) => {
selectColor.value = color;
console.dir(selectColor);
context.emit('handleSelectColor', selectColor);
};
return {
selectColor,
updateActiveColor,
};
},
});
</script>
You can pass the whole color object from template:
...
#click="updateActiveColor(color)"
...
and update it:
...
const updateActiveColor = (color: any) => {
selectColor.value = color;
...
Please take a look at snippet below:
const { ref } = Vue
const app = Vue.createApp({
setup() {
const colors = ref([{value: 1, color: 'red'}, {value: 2, color: 'blue'}, {value: 3, color: 'orange'}, {value: 4, color: 'green'}, {value: 5, color: 'yellow'}])
const name = ref('aaa')
const selectedColor = ref(null)
const setColor = (color) => {
selectedColor.value = color
}
return { selectedColor, colors, name, setColor }
}
})
app.component('child', {
template: `
<div class="relative mt-2 mx-2 text-left">
<div class="w-7/12 md:flex md:flex-wrap" id="color-picker">
<button
v-for="color in colors"
#click="updateActiveColor(color)"
class="w-8 h-8 mr-2 rounded-full p-2 mt-2"
:key="color.value"
type="button"
:data-testid="'button-' + color.value"
:style="{ backgroundColor: color.color }"
>
<div v-if="selectColor.value === color.value" class="justify-center w-4 p-0 m-auto text-white">
✔
</div>
</button>
</div>
</div>
`,
props: {
colors: {
type: Object ,
required: true,
},
name: {
type: String,
required: true,
},
selectedColor: {
type: Object ,
require: false,
},
},
setup(props, context) {
let selectColor = ref(props.colors[0]);
const updateActiveColor = (color) => {
selectColor.value = color;
console.log(selectColor.value);
context.emit('handleSelectColor', selectColor);
};
return {
selectColor,
updateActiveColor,
};
},
})
app.mount('#demo')
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" integrity="sha512-wnea99uKIC3TJF7v4eKk4Y+lMz2Mklv18+r4na2Gn1abDRPPOeef95xTzdwGD9e6zXJBteMIhZ1+68QC5byJZw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<child :name="name" :selectedColor="selectedColor" :colors="colors" #handle-select-color="setColor"></child>
{{selectedColor}}
</div>

How to render dynamic content "component" with custom tabs VueJs

i have a costume made Tabs and Tab from Laracasts Tabs Tutorial , and it work fine, but i need to load data when the tab is changed and i did that but when the data is loaded,
i need to render another component which have accordion and inside each accordion tab their some charts components need to be render also
so how can i render the accordion with the charts components when the Tabs tab is changed
Tabs Component:
<template>
<div class="tab-container -mt-px w-full">
<div class="tabs">
<ul class="list-reset flex border-b">
<li class="" v-for="(tab, index) in tabs" role="tab">
<a class="bg-white inline-block font-semibold hover:no-underline"
:class="[
{
'active-tab-link text-blue-dark border-l border-r active-tab-link-p': tab.isActive,
'text-blue hover:text-blue-darker non-active-tab-link-p': !tab.isActive
},
]"
:href="tab.href" #click="selectedTab(tab)">
{{tab.name}}
</a>
</li>
</ul>
</div>
<div class="tabs-details px-4 w-full">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "Tabs",
data() {
return {
tabs: []
};
},
created() {
this.tabs = this.$children;
// this.selectFirstTab();
},
methods: {
selectedTab(selectedTab) {
this.$emit('onTabChange', selectedTab);
this.tabs.map((tab, index) => {
tab.isActive = (tab.name === selectedTab.name)
});
},
}
}
</script>
Tab Component:
<template>
<div class="w-full" role="tabpanel" v-show="isActive">
<slot></slot>
</div>
</template>
<script>
import {isEmpty} from "../helpers/Helper";
export default {
name: "Tab",
props: {
name: {
type: String,
required: true
},
selected: {
type: Boolean,
default: false,
},
isFirst: {
type: Boolean,
default: false,
},
},
data() {
return {
// isActive: false,
isActive: true,
// isFirst: this.isFirst
};
},
computed: {
href() {
return this.formatHref(this.name);
},
},
mounted() {
this.selectTabFromURL();
},
methods: {
selectTabFromURL() {
let hash = this.$route.hash;
if (this.selected) {
this.isActive = this.selected;
} else if (!isEmpty(hash)) {
this.isActive = (this.formatHref(this.name) === hash);
} else if (this.isFirst) {
this.isActive = this.isFirst;
} else if (!this.isFirst && this.isActive) {
this.isActive = !this.isActive;
}
},
formatHref(id) {
return `#${id.toLowerCase().replace(/ /g, '-')}`;
}
}
}
</script>
the main component:
<template>
<!--components tabs start-->
<div class="flex flex-col">
<div class="mt-3 border-t-4 border-primary-color border-6 bg-white">
<div class=" border border-gray-400 lg:border-l-0 lg:border-t lg:border-gray-400 rounded-b lg:rounded-b-none lg:rounded-r leading-normal">
<Tabs #onTabChange="handleTabChange">
<!--:name="`${tab.name} - ${tab.component.type}`"-->
<Tab v-for="(tab, index) in page.tabs"
:key="tab.id"
:id="tab.slug"
:name="tab.name"
:slug="tab.slug"
:isFirst="index === 0"
>
<div>
How to render the dynamic accordion with the charts one time only no need to re-render
</div>
</Tab>
</Tabs>
</div>
</div>
</div>
<!--components tabs end-->
</template>
in normal HTML and JQuery, i can load the data and the render the result and append it to the wanted tab how can we do this with vue, dose the dynamic component help in this case ?
I searched for a solution and found and implement the "Creating Vue.js Component Instances Programmatically"

Defining a drag container inside another drag container

This is in relation to implementing drag and drop using vue. I have a draggable div container.
// BoardColumn Component Parent Container
<template>
<div
draggable
#dragover.prevent
#dragenter.prevent
#drop="dropColumn($event, columnIndex)"
#dragstart="setPickColumnInfo($event, columnIndex)"
>
<div class="column bg-grey-light m-4">
<div class="flex items-center mb-2 font-bold">
{{ getColumnName() }}
</div>
<div class="list-reset">
<TaskCard
class="task"
v-for="(task, $taskIndex) of columnData.tasks"
:key="$taskIndex"
v-bind:task="task"
v-bind:taskIndex="$taskIndex"
v-bind:columnIndex="columnIndex"
/>
</div>
<input
type="text"
class="block p-2 w-full bg-transparent"
placeholder="+Enter a new task"
#keyup.enter="createTask($event, columnData.tasks)"
/>
</div>
</div>
</template>
Inside this container is another draggable container TaskCard. i'm able to drag the parent container and the child container without any issues. However, the callback functions related to only parent container BoardColumn gets fired regardless of which container is being dragged.
The function definitions are below. Any help would be greatly appreciated.
// TaskCard.vue Child container
<template>
<div
draggable
#dragover.prevent
#dragenter.prevent
#dragstart="setPickupTaskInfo($event, taskIndex, columnIndex)"
#dragend="dropTask($event, taskIndex, columnIndex)"
#click="goToTask(task)"
v-if="isTaskOpen === false"
>
<span class="w-full flex-no-shrink font-bold">
{{ task.name }}
</span>
<p v-if="task.description" class="w-full flex-no-shrink mt-1 text-sm">
{{ task.description }}
</p>
</div>
<div class="task-bg" #click.self="close" v-else>
<router-view />
</div>
</template>
// BoardColumn JS
import TaskCard from "#/components/TaskCard/TaskCard.vue";
export default {
components: {
TaskCard
},
props: ["columnData", "columnIndex"],
methods: {
getColumnName() {
return this.columnData.name;
},
createTask(e, tasks) {
this.$store.commit("CREATE_TASK", {
tasks,
name: e.target.value
});
e.target.value = "";
},
setPickColumnInfo(e, fromColumnIndex) {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.dropEffect = "move";
e.dataTransfer.setData("from-column-index", fromColumnIndex);
e.dataTransfer.setData("type", "column");
},
moveColumn(fromColumnIndex, toColumnIndex) {
this.$store.commit("MOVE_COLUMN", {
fromColumnIndex,
toColumnIndex
});
},
dropColumn(e, toColumnIndex) {
if (e.dataTransfer.getData("type") === "column") {
this.moveColumn(
e.dataTransfer.getData("from-column-index"),
toColumnIndex
);
console.log(e.dataTransfer.getData("type"));
}
}
}
};
// TaskCard JS
export default {
props: ["task", "taskIndex", "columnIndex"],
data() {
return {
isTaskOpen: false
};
},
methods: {
goToTask(task) {
this.$router.push({ name: "task", params: { id: task.id } });
this.isTaskOpen = true;
},
close() {
this.$router.push({ name: "board" });
this.isTaskOpen = false;
},
setPickupTaskInfo(e, fromTaskIndex, fromColumnIndex) {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.dropEffect = "move";
e.dataTransfer.setData("from-task-index", fromTaskIndex);
e.dataTransfer.setData("from-column-index", fromColumnIndex);
e.dataTransfer.setData("type", "task");
console.log(e);
},
moveTask(e, toTaskIndex, toColumnIndex) {
const fromColumnIndex = e.dataTransfer.getData("from-column-index");
const fromTaskIndex = e.dataTransfer.getData("from-task-index");
this.$store.commit("MOVE_TASK", {
fromTaskIndex,
fromColumnIndex,
toTaskIndex,
toColumnIndex
});
},
dropTask(e, toTaskIndex, toColumnIndex) {
if (e.dataTransfer.getData("type") === "task") {
console.log(e.dataTransfer.getData("type"));
this.moveTask(e, toTaskIndex, toColumnIndex);
}
}
}
};

Vue.JS - How to have a button perform two separate actions on the same parent element?

On parent I have:
<template>
<b-container>
<b-modal id="uw-qb-add-item-modal"
ref="uw-qb-add-item-modal"
title="Enter Item Number"
#ok="handleNewItem">
<form #submit.stop.prevent="handleSubmit">
<b-form-input type="text"
placeholder="Enter the item number" />
</form>
</b-modal>
<div class="row mb-3">
<div class="col">
<nuxt-link to="/">
<i class="fa fa-chevron-left" aria-hidden="true"></i>
Quote Build List
</nuxt-link>
</div>
</div>
<div class="row mb-3">
<div class="col-md"><h2>Quote Build <b-badge :variant="buildBadgeVariant">{{ buildBadgeText }}</b-badge></h2></div>
<div class="col-md text-right text-success"><h3><i class="fa fa-usd" aria-hidden="true"></i> {{ buildTotal | formatDollars }}</h3></div>
</div>
<div class="row mb-3">
<div class="col-md form-group">
<build-customer :buildNumber="buildNumber"/>
</div>
<div class="col-md form-group">
<build-address :buildNumber="buildNumber" />
</div>
<div class="col-md form-group">
<build-contact :buildNumber="buildNumber" />
</div>
<div class="col-md form-group">
<label for="uw-qb-due-date">Due Date</label>
<date-picker v-model="dueDate"/>
</div>
</div>
<div class="row mb-3">
<div class="col-sm form-group">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" v-model="internal" />
Internal Quote
</label>
</div>
<build-requester-id v-if="internal" :buildNumber="buildNumber"/>
</div>
<div class="col-sm form-group">
<label>RFQ Number</label>
<b-form-input type="text" v-model="rfqNumber"/>
</div>
<div class="col-sm form-group">
<div class="form-check">
<label class="form-check-label mb-3">
<input class="form-check-input" type="checkbox" v-model="update" />
Quote Update
</label>
<label class="form-check-label">
<input class="form-check-input" type="checkbox" v-model="correctiveAction" />
Corrective Action
</label>
</div>
</div>
<div class="col-sm form-group">
<label>Request Date</label>
<date-picker v-model="requestDate" />
</div>
</div>
<div class="row mb-3">
<div class="col-md text-right">
<b-btn variant="primary"
v-b-toggle="editId"
#click="newEditItem">
<i class="mr-1 fa fa-plus" aria-hidden="true"></i>
Add Item
</b-btn>
</div>
</div>
<b-collapse :id="editId">
<build-item-edit #doOnEmit="expand" :buildNumber="buildNumber"
:itemNumber="editItemNumber"/>
</b-collapse>
<build-item-list #edit="edit" :buildNumber="buildNumber" />
<build-breakdown-edit :buildNumber="buildNumber" :breakdownNumber="editBreakdownNumber"/>
<build-breakdown-list :buildNumber="buildNumber"/>
</b-container>
</template>
<script>
import BuildCustomer from '#/components/buildCustomer'
import BuildAddress from '#/components/buildAddress'
import BuildContact from '#/components/buildContact'
import BuildRequesterId from '#/components/buildRequesterId'
import BuildItemEdit from '#/components/buildItemEdit'
import BuildItemList from '#/components/buildItemList'
import BuildBreakdownEdit from '#/components/buildBreakdownEdit'
import BuildBreakdownList from '#/components/buildBreakdownList'
import DatePicker from '#/components/datePicker'
export default {
data () {
return {
editItemNumber: null,
editBreakdownNumber: null
}
},
components: {
'build-customer': BuildCustomer,
'build-address': BuildAddress,
'build-contact': BuildContact,
'build-requester-id': BuildRequesterId,
'build-item-edit': BuildItemEdit,
'build-item-list': BuildItemList,
'build-breakdown-edit': BuildBreakdownEdit,
'build-breakdown-list': BuildBreakdownList,
'date-picker': DatePicker
},
computed: {
// nextItemNumber () {
// const itemNumbers = this.$store.getters['build/item/numbers'](this.buildNumber)
// return Math.min.apply(null, itemNumbers) - 1
// },
editId () {
return this.idGen('edit-item-collapse')
},
dueDate: {
get () {
return this.build.dueDate
},
set (value) {
this.$store.commit('build/setDueDate', { buildNumber: this.buildNumber, dueDate: value })
}
},
requestDate: {
get () {
return this.build.requestDate
},
set (value) {
this.$store.commit('build/setRequestDate', { buildNumber: this.buildNumber, requestDate: value })
}
},
internal: {
get () {
return this.build.internal
},
set (value) {
this.$store.commit('build/setInternal', { buildNumber: this.buildNumber, internal: !!value })
}
},
update: {
get () {
return this.build.update
},
set (value) {
this.$store.commit('build/setUpdate', { buildNumber: this.buildNumber, update: !!value })
}
},
correctiveAction: {
get () {
return this.build.correctiveAction
},
set (value) {
this.$store.commit('build/setCorrectiveAction', { buildNumber: this.buildNumber, correctiveAction: !!value })
}
},
requesterId: {
get () {
return this.build.requesterId
},
set (value) {
if (value === null || this.$store.getters.employees.hasOwnProperty(value)) {
this.$store.commit('build/setRequesterId', { buildNumber: this.buildNumber, requesterId: value })
}
}
},
rfqNumber: {
get () {
return this.build.rfqNumber
},
set (value) {
this.$store.commit('build/setRfqNumber', {buildNumber: this.buildNumber, rfqNumber: value })
}
},
employees () {
const res = [{ value: null, text: 'Select requested by ...' }];
for (var empId in this.$store.getters['employee/employees']) {
res.push({ value: empId, text: this.$store.getters.employees[empId] });
}
return res;
},
buildNumber () {
return parseInt(this.$route.params.buildNumber, 10);
},
build () {
return this.$store.getters['build/build'](this.buildNumber);
},
buildBadgeVariant () {
if (this.isNewBuild) { return 'primary'; }
return 'info';
},
buildBadgeText () {
if (this.isNewBuild) { return 'New'; }
return this.buildNumber;
},
isNewBuild () {
return this.buildNumber < 0;
},
buildTotal () {
return this.$store.getters['build/total'](this.buildNumber);
}
},
methods: {
fetchData () {
if (!this.isNewBuild) {
// TODO - waiting on schema changes
console.log('Fetching build data for ' + this.$route.params.buildNumber);
}
},
handleNewItem () {
this.$store.commit('setError', 'Adding items not supported');
},
newEditItem () {
this.editItemNumber = this.nextItemNumber
},
edit(eventPayload) {
this.editItemNumber = eventPayload
},
expand() {
console.log("TEST")
this.idGen('edit-item-collapse')
}
},
fetch (context) {
const buildNumber = parseInt(context.params.buildNumber, 10)
const build = context.store.getters['build/build'](buildNumber)
if (build === null && buildNumber >= 0) {
console.log('load build')
} else if (build === null && buildNumber < 0) {
// if the build doesn't exist and it is a temp build
// number, then just redirect to new
context.app.router.replace('/new')
}
}
}
</script>
buildItemEdit
<template>
<b-container>
<b-row>
<b-col>
<h3>Item</h3>
<p></p>
<b-row></b-row>
<p></p>
</b-col>
</b-row>
<b-row>
<b-col>
<b-row>
<b-col>
<label>Item No.</label>
<b-form-input v-model='itemNum' type="text" :state="itemNoState" onkeypress='return event.charCode >= 48 && event.charCode <= 57'
/>
</b-col>
<b-col>
<label>Item Type</label>
<b-form-select v-model="type" :options="itemTypes" class="mb-3" :state="itemTypeState" />
</b-col>
<b-col>
<label>Part No.</label>
<b-form-input v-model='partNumber' type="text" :state="partNoState" onkeypress='return event.charCode >= 48 && event.charCode <= 57'
/>
</b-col>
<b-col>
<label>Piece Amount</label>
<b-form-input v-model='pieceCount' type="number" :state="pieceAmountState" onkeypress='return event.charCode >= 48 && event.charCode <= 57'
/>
</b-col>
</b-row>
<b-row>
<b-col>
<label>Comments</label>
<b-form-textarea id="comments" v-model="comments" placeholder="Enter comments here" :rows="3" :max-rows="6"></b-form-textarea>
</b-col>
</b-row>
<p></p>
<b-row>
<b-col>
<b-card-group deck class="mb-3">
<b-card bg-variant="primary" text-variant="white" class="text-center">
<p class="card-text">Breakdown 1</p>
</b-card>
<b-card bg-variant="secondary" text-variant="white" class="text-center">
<p class="card-text">Breakdown 2</p>
</b-card>
<b-card bg-variant="success" text-variant="white" class="text-center">
<p class="card-text">Breakdown 3</p>
</b-card>
</b-card-group>
</b-col>
</b-row>
</b-col>
</b-row>
<b-row>
<p></p>
</b-row>
<b-row>
<p></p>
</b-row>
<b-row>
<b-col>
<div v-if="editmsg" class="col-md text-left">
<b-btn size="" #click="editUpdate" variant='success'>
<i class='mr-1 fa fa-plus' aria-hidden="true"></i> Save Edit</b-btn>
</div>
<p></p>
</b-col>
<b-col>
<div class="col-md text-center">
<b-btn size="" #click="addItem" variant='primary'>
<i class='mr-1 fa fa-plus' aria-hidden="true"></i> Break Downs</b-btn>
</div>
</b-col>
<b-col>
<div class="col-md text-right">
<b-btn size="" #click="addItem" variant='primary' v-bind:disabled="!canSave">
<i class='mr-1 fa fa-plus' aria-hidden="true"></i> Save Item</b-btn>
</div>
</b-col>
</b-row>
<div v-if="existmsg">
<p></p>
<b-alert show variant="danger">{{ existmsg }}</b-alert>
</div>
<div v-if="editmsg">
<p></p>
<b-alert show variant="warning">{{ editmsg }}</b-alert>
</div>
</b-container>
</template>
<script>
import Util from '#/lib/util'
export default {
props: ['buildNumber', 'itemNumber'],
data() {
return {
itemNum: "",
type: "",
partNumber: "",
pieceCount: "",
comments: "",
selected: "A",
}
},
watch: {
itemNumber: function (editItemNumber) {
if (editItemNumber == null) {
this.update({})
} else {
const item = this.item(editItemNumber)
this.update(item)
this.itemNum = editItemNumber
}
}
},
computed: {
itemNoState() {
return !isNaN(parseFloat(this.itemNum)) && isFinite(this.itemNum) ? null : false;
},
itemTypeState() {
return (this.type) ? null : false;
},
partNoState() {
return !isNaN(parseFloat(this.partNumber)) && isFinite(this.partNumber) ? null : false;
},
pieceAmountState() {
return !isNaN(parseFloat(this.pieceCount)) && isFinite(this.pieceCount) ? null : false;
},
canSave() {
return this.itemNumber != '' && this.type != '' && this.partNumber != '' && this.pieceCount != ''
},
editmsg: {
get() {
return this.$store.getters["build/item/editmsg"];
},
set(value) {
this.$store.commit("build/item/seteditmsg", value);
}
},
existmsg: {
get() {
return this.$store.getters["build/item/existmsg"];
},
set(value) {
this.$store.commit("build/item/setexistmsg", value);
}
},
itemTypes() {
const iTypes = []
const b = this.$store.getters['itemType/all']
for (var itemValue in b) {
iTypes.push({
value: itemValue,
text: b[itemValue]
})
}
return iTypes
}
},
methods: {
item(itemNumber) {
return this.$store.getters["build/item/item"](
this.buildNumber,
itemNumber
);
},
addItem() {
if (this.item(this.itemNum) == null)
{
this.$store.commit('build/item/add', {
buildNumber: this.buildNumber,
itemNumber: this.itemNum,
item: {
type: this.type,
partNumber: this.partNumber,
pieceCount: this.pieceCount,
comments: this.comments
}
})
this.update({})
}
else
{
this.existmsg = "Item number " + this.itemNum + " already exists on this quote"
}
},
update(item) {
this.itemNum = Util.field(item, 'itemNumber', '')
this.type = Util.field(item, 'type', '')
this.partNumber = Util.field(item, 'partNumber', '')
this.pieceCount = Util.field(item, 'pieceCount', '')
this.comments = Util.field(item, 'comments', '')
this.existmsg = ""
},
editUpdate(item) {
this.$store.commit('build/item/update', {
buildNumber: this.buildNumber,
itemNumber: this.itemNum,
item: {
type: this.type,
partNumber: this.partNumber,
pieceCount: this.pieceCount,
comments: this.comments
}
})
this.update({})
this.editmsg = ""
}
}
}
</script>
Now on the child I already have a button that emits an item number to the parent (editItemNumber) above. But I always want that same button on the child to expand this collapse on the parent only if its not collapsed.
Below is the existing child.
edit(item) {
const payload = {
item
};
this.$emit('edit', item.itemNumber);
this.editmsg = "Edit your item above and then click 'Save Edit'"
}
buildItemList
<template>
<b-container>
<div>
<p></p>
<h5>Items</h5>
<p></p>
<b-table show-empty bordered striped hover :items="itemTableList" :fields="fields">
<template slot="actions" scope="row">
<b-btn variant='success' size="sm" v-on:click="edit(row.item,$event.target)">Edit</b-btn>
<b-btn variant='danger' size="sm" #click.stop="delRow(row.item,row.index,$event.target)">Delete</b-btn>
</template>
</b-table>
</div>
</b-container>
</template>
<script>
import Util from "#/lib/util";
export default {
data() {
return {
fields: [
{ key: "itemNumber", label: "Item No.", sortable: true },
{ key: "type", label: "Item Type", sortable: false },
{ key: "partNumber", label: "Part No.", sortable: false },
{ key: "pieceCount", label: "Piece Amount", sortable: false },
{ key: "comments", label: "Comments", sortable: false },
{ actions: { label: "Actions" } }
]
};
},
props: ["buildNumber"],
computed: {
itemNumbers() {
console.log("DEVELOPER")
console.log(this.buildNumber)
const items = this.$store.getters["build/item/items"](this.buildNumber);
return Util.numSortedKeys(items);
},
itemTableList() {
const itemList = [];
for (var i of this.itemNumbers) {
const item = this.item(i);
itemList.push({
itemNumber: i,
type: item.type,
partNumber: item.partNumber,
pieceCount: item.pieceCount,
comments: item.comments
});
}
return itemList;
},
editmsg: {
get() {
return this.$store.getters["build/item/editmsg"];
},
set(value) {
this.$store.commit("build/item/seteditmsg", value);
}
}
},
methods: {
item(itemNumber) {
return this.$store.getters["build/item/item"](
this.buildNumber,
itemNumber
);
},
edit(item) {
const payload = {
item
};
this.$emit('doOnEmit')
this.$emit('edit', item.itemNumber);
// this.editmsg = "Edit your item above and then click 'Save Edit'"
}
}
};
</script>
So to reiterate, when the button edit is clicked, beyond what it's currently doing I want to expand that toggle collapse if not expanded on the parent component.
Is that possible?
I assume <build-item-edit> is your own component so just add a callback event to it i.e
<build-item-edit #doOnEmit="someFunctionOnTheParent" :buildNumber="buildNumber" :itemNumber="editItemNumber"/>
Then on the parent component, define the method someFunctionOnTheParent and run the code that does your UI stuff.
Then in the build-item-edit component, before you emit the value, call this.$emit('doOnEmit')

Categories

Resources