I'm trying to implement a component where, if a field changes, it may trigger a code path that necessitates user feedback. I'm using another, fairly widely used component (ViewDialogue), to render and return that feedback.
The problem I am experiencing is, if multiple changes occur, I only receive the dialogue for the final one. For example: In the AChange function, I will only receive the dialogue for HandleA() even if UpdateB and UpdateC are triggered.
PARENT COMPONENT:
<template>
<v-card>
<v-text-field
v-model="valueC"
#change="CChange"
></v-text-field>
<v-text-field
v-model="valueB"
#change="BChange"
></v-text-field>
<v-text-field
v-model="valueA"
#change="AChange"
></v-text-field>
<v-dialog v-model="qdialog" width="500">
<ViewDialogue :question="question" #HandleAnswer="HandleAnswer" />
</v-dialog>
</v-card>
</template>
<script>
export default {
data: () => ({
qdialog: false,
question: '',
valueA: 0,
valueB: 0,
valueC: 0,
answerA: false,
answerB: false,
answerC: false,
BChanged: false,
CChanged: false,
}),
methods: {
HandleAnswer(x) {
if (x === true) {
if (this.answerA) {
this.DoA()
} else if (this.answerB) {
this.DoB()
} else if (this.answerC) {
this.DoC()
}
} else {
this.answerA = false
this.answerB = false
this.answerC = false
this.question = ''
this.qdialog = false
}
},
BChange() {
this.BChanged = true
},
CChange() {
this.CChanged = true
},
DoA() {
this.valueA = this.valueB
this.answerB = false
this.qdialog = false
},
DoB() {
this.valueB = this.valueA
this.answerB = false
this.qdialog = false
},
DoC() {
this.valueC = this.valueA
this.answerC = false
this.qdialog = false
},
UpdateB() {
if (this.valueB !== this.valueA) {
this.question = 'Update B to A?'
this.answerB = true
this.qdialog = true
}
},
UpdateC() {
if (this.valueC !== this.valueA) {
this.question = 'Update C to A?'
this.answerC = true
this.qdialog = true
}
},
HandleC() {
if (this.BChanged) {
this.UpdateB()
}
if (this.CChanged) {
this.UpdateC()
}
},
HandleA() {
if (this.valueA < this.valueB + this.valueC) {
this.question = 'Update A to B?'
this.answerA = true
this.qdialog = true
}
},
AChange() {
this.HandleC()
this.HandleA()
},
},
}
</script>
CHILD COMPONENT: ViewDialogue
<template>
<v-card>
<v-card-title class="text-h5 grey lighten-2">
{{ question }}
</v-card-title>
<v-divider></v-divider>
<v-card-actions>
<v-btn color="primary" text #click="HandleAnswer(false)"> No </v-btn>
<v-spacer></v-spacer>
<v-btn color="primary" text #click="HandleAnswer(true)"> Yes </v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: {
question: { type: String, default: '' },
},
emits: ['HandleAnswer'],
data: () => ({}),
methods: {
HandleAnswer(a) {
this.$emit('HandleAnswer', a)
},
},
}
</script>
I'm not thrilled about this answer, as it seems to hook into the nuxt instance, but here we are. Used [this][1] Dialog component.
[1]: https://gist.github.com/eolant/ba0f8a5c9135d1a146e1db575276177d
In my Plugins/core-components.js:
import ConfirmDialog from '#/components/Test/ConfirmDialog.vue'
Vue.component('ConfirmDialog', ConfirmDialog)
In my layouts/default.vue:
<template>
<v-main>
<v-app id="app">
<ConfirmDialog ref="confirm"></ConfirmDialog>
<nuxt />
</v-app>
</v-main>
</template>
<script>
export default {
async mounted() {
this.$nuxt.$root.$confirm = this.$refs.confirm
},}
</script>
Finally, my Parent Component (no child involved now)
<script>
export default {
data: () => ({
qdialog: false,
question: '',
valueA: 0,
valueB: 0,
valueC: 0,
answerA: false,
answerB: false,
answerC: false,
BChanged: false,
CChanged: false,
}),
methods: {
HandleAnswer(x) {
return new Promise((resolve, reject) => {
if (x === true) {
if (this.answerA) {
this.DoA()
} else if (this.answerB) {
this.DoB()
} else if (this.answerC) {
this.DoC()
}
resolve(true)
} else {
this.answerA = false
this.answerB = false
this.answerC = false
this.question = ''
this.qdialog = false
}
resolve(false)
}}),
BChange() {
this.BChanged = true
},
CChange() {
this.CChanged = true
},
DoA() {
this.valueA = this.valueB
this.answerB = false
},
DoB() {
this.valueB = this.valueA
this.answerB = false
},
DoC() {
this.valueC = this.valueA
this.answerC = false
},
async UpdateB() {
if (this.valueB !== this.valueA) {
if (
await this.$nuxt.$root.$confirm.open(
'Update B to A?',
'Are you sure?',
{ color: 'red',}
)
) {
this.answerB= true
await this.HandleAnswer(true)
} else {
await this.HandleAnswer(false)
}
}
},
async UpdateC() {
if (this.valueC !== this.valueA) {
if (
await this.$nuxt.$root.$confirm.open(
'Update C to A?',
'Are you sure?',
{ color: 'red',}
)
) {
this.answerC = true
await this.HandleAnswer(true)
} else {
await this.HandleAnswer(false)
}
}
},
async HandleC() {
if (this.BChanged) {
await this.UpdateB()
}
if (this.CChanged) {
await this.UpdateC()
}
},
async HandleA() {
if (this.valueA < this.valueB + this.valueC) {
if (
await this.$nuxt.$root.$confirm.open(
'Update A to B?',
'Are you sure?',
{ color: 'red',}
)
) {
this.answerA = true
await this.HandleAnswer(true)
} else {
await this.HandleAnswer(false)
}
}
},
async AChange() {
await this.HandleC()
await this.HandleA()
},
},
}
</script>
Related
I have a form that uses dropdowns to allow the user to select their configuration and when they click apply a highchart is rendered. At first glance this works perfectly.
My problem is that if after the chart is rendered you open a dropdown again to make a change without closing the dropdown and click apply we get the chart with the previous configuration. I have to click apply a second time to get the new configuration to show up.
What am I missing?
Here is part of the complete code:
<template>
<div class="dropdown multiple-select">
<GlobalEvents v-if="open" #click="handleClick" #keydown.esc="close" />
<button
ref="button"
class="btn btn-block btn-multiple-select"
type="button"
:disabled="disabled"
#click="toggle"
>
<span v-if="internalValue.length === 0"> {{ emptyLabel }} </span>
<span v-else> {{ internalValue.length }} Selected </span>
<b-icon :icon="open | icon" :scale="0.5" />
</button>
<div ref="dropdown" class="dropdown-menu" :class="{ show: open }">
<template v-if="!loading">
<div class="px-4 pt-1">
<div class="form-group">
<b-form-input
v-model="search"
debounce="500"
placeholder="Search"
/>
</div>
</div>
<div class="scroll px-4">
<b-form-checkbox v-model="selectAll" class="py-1" #change="toggleAll">
Select all
</b-form-checkbox>
<b-form-checkbox
v-for="item in filtered"
:key="item.id"
v-model="internalValue"
:value="item"
class="py-1"
#input="checkSelectAllStatus"
>
{{ item.name }}
</b-form-checkbox>
<p v-if="filtered.length === 0">No results.</p>
</div>
</template>
<div v-else class="text-center my-2">
<b-spinner />
</div>
</div>
</div>
</template>
<script>
import { createPopper } from '#popperjs/core';
import GlobalEvents from 'vue-global-events';
export default {
components: {
GlobalEvents
},
filters: {
icon(item) {
return item ? 'caret-up-fill' : 'caret-down-fill';
}
},
model: {
prop: 'value',
event: 'change'
},
props: {
emptyLabel: {
type: String,
default: () => 'None Selected'
},
disabled: {
type: Boolean,
default: () => false
},
loading: {
type: Boolean,
default: () => false
},
options: {
type: Array,
default: () => []
},
value: {
type: Array,
default: () => []
}
},
data() {
return {
internalValue: this.value,
open: false,
popper: null,
search: '',
selectAll: false
};
},
computed: {
filtered() {
return this.options.filter((item) =>
item.name.toLowerCase().includes(this.search.toLowerCase())
);
},
showAll() {
return (
this.internalValue.length > 0 &&
this.internalValue.length === this.options.length
);
}
},
watch: {
options() {
this.checkSelectAllStatus();
},
internalValue() {
this.checkSelectAllStatus();
},
value(value) {
this.internalValue = value;
this.$emit('change', this.internalValue);
}
},
methods: {
checkSelectAllStatus() {
this.selectAll = this.internalValue.length === this.options.length;
},
close() {
this.open = false;
this.search = '';
this.$emit('change', this.internalValue);
},
create() {
this.popper = createPopper(this.$refs.button, this.$refs.dropdown, {
placement: 'bottom-start',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 10]
}
}
]
});
},
destroy() {
if (this.popper) {
this.popper.destroy();
this.popper = null;
}
},
handleClick(event) {
if (!this.$el.contains(event.target)) {
this.close();
}
},
toggle() {
this.open = !this.open;
if (this.open) {
this.$emit('open');
this.create();
} else {
this.destroy();
}
},
toggleAll(checked) {
if (checked) {
this.internalValue = this.options;
} else {
this.internalValue = [];
}
}
}
};
</script>
Found a solution that works with what we already have. My MultipleSelect component was invoking #input="checkSelectAllStatus"
I added this.$emit('change', this.internalValue); to the checkSelectAllStatus method and it worked.
You seem to be using vuelidate - is there a good reason why you aren't accessing your form values directly but are going through your validation model instead?
Without knowing your config, your code should look more like this:
async apply() {
try {
this.chartOptions.utilityMetric = this.form.metric;
this.chartOptions.title = this.getTitle();
this.loading = true;
this.$v.$touch()
if (this.$v.$error) throw new Error('form not valid')
const response = await await this.$api.organizations.getAnnualWidget(
this.organizationId,
this.parentUtility,
this.requestParams
);
this.chartOptions.categories = response.data.categories;
this.chartOptions.series = response.data.series;
this.chartOptions.yAxis = response.data.yAxis;
this.$forceUpdate();
} catch (error) {
const message = getErrorMessage(error);
this.$bvToast.toast(message, {
title: 'Error',
toaster: 'b-toaster-top-center',
variant: 'danger'
});
} finally {
this.loading = false;
}
}
and
requestParams() {
return {
calendarization: this.form.calendarization,
metrics: this.form.metric.map((metric) => metric.id),
meterIds: this.form.meterIds,
Years: this.form.fiscalYears.map((year) => year.value),
waterType: this.form.waterType,
includeBaseline: this.form.baseLine,
showDataLabels: this.form.showDataLabels
};
},
Again, without knowing your complete code it's difficult to tell, but I am guessing it's the pointing to vuelidate that's causing the issues
I would like to override Quasar's QTree in a single file component from the code pen
I have created the component below but it does not override the method __onTickedClick
<script>
import QTree from 'quasar';
export default {
name: 'MyTree',
mixins: [QTree],
data() {
return {
ticked: [],
};
},
props: {
tickSubStrategy: {
type: String,
default: 'none',
validator: v => ['leaf'].includes(v),
},
tree: {
type: Array,
required: true,
},
},
methods: {
__onTickedClick(meta, state) {
if (meta.indeterminate === true) {
state = meta.indeterminateNextState;
}
if (meta.strictTicking) {
const keys = [];
if (this.tickSubStrategy === 'leaf') {
const travel = meta => {
if (meta.isParent) {
keys.push(meta.key);
meta.children.forEach(travel);
} else if (
meta.noTick !== true &&
meta.tickable === true &&
(meta.leafFilteredTicking !== true || meta.matchesFilter === true)
) {
keys.push(meta.key);
}
};
travel(meta);
} else {
keys.push(meta.key);
}
this.setTicked(keys, state);
} else if (meta.leafTicking) {
const keys = [];
const travel = meta => {
if (meta.isParent) {
if (state !== true && meta.noTick !== true && meta.tickable === true) {
keys.push(meta.key);
}
if (meta.leafTicking === true) {
meta.children.forEach(travel);
}
} else if (
meta.noTick !== true &&
meta.tickable === true &&
(meta.leafFilteredTicking !== true || meta.matchesFilter === true)
) {
keys.push(meta.key);
}
};
travel(meta);
this.setTicked(keys, state);
}
},
},
};
</script>
<template>
<q-tree
:nodes="tree"
ref="tree"
node-key="id"
tick-strategy="strict"
:tick-sub-strategy="tickSubStrategy"
:ticked.sync="ticked"
>
<template v-slot:default-header="prop">
<div class="row items-center">
<div class="text-weight-bold text-primary">{{ prop.node.title }}</div>
</div>
</template>
</q-tree>
</template>
<style lang="stylus" scoped></style>
The code pen override works as expected.
I am having a callback function which updates my array in the SetState and it works fine as I can see in the console log in the handleDaysValueChange all is good. But when I try to access it in another function i.e handleValuesChange the values are not up to date. I am missing a async or something.
import React from 'react';
import PropTypes from 'prop-types';
import AppService from 'services/app-service';
import Enum from 'enum';
import { ApiData } from 'data';
import { isEmpty, boolValue } from 'core/type-check';
import { notifySuccess } from 'core/errors';
import { withDrawerForm } from 'components/_hoc';
import { ExtendedAvatar } from 'components/extended-antd';
import { Tabs } from 'antd';
import { FormTemplateAudit } from '../templates';
import ShiftRosterFormDetails from './shiftroster-form-details';
import Item from 'antd/lib/list/Item';
const { TabPane } = Tabs;
class ShiftRosterForm extends React.Component {
constructor(props) {
super(props);
this.formInputRef = React.createRef();
this.state = {
daysValue:[]
};
this.handleDaysValueChange = this.handleDaysValueChange.bind(this);
}
handleDaysValueChange(daysValues) {
this.setState({ daysValue: daysValues }, () => {
console.log("Data" + this.props.customData);
console.log("Hello World " + JSON.stringify(this.state.daysValue));
});
}
componentDidMount() {
// Update the parent form state with a reference to the
// formInputRef so we can call this later for validation
// before saving the data to the server.
const { onFormStateChange } = this.props;
onFormStateChange({ formInputRef: this.formInputRef.current });
}
//Shift Roster Detail
handleValuesChange = (props, changedValues, allValues) => {
// Update the parent form state with the changed item
// details and mark the form as dirty
const { itemData, onFormStateChange } = this.props;
console.log("Hey" + this.state.daysValue);
onFormStateChange({
isFormDirty: true,
itemData: {
...itemData,
...changedValues,
...this.state.daysValue
}
});
}
render() {
const { itemData, customData } = this.props;
const isSR = (!isEmpty(itemData) && itemData.id > 0);
return (
<Tabs className="bhp-tabs" defaultActiveKey="1" animated={false}>
<TabPane key="1" tab="Details">
<ShiftRosterFormDetails
ref={this.formInputRef}
dataSource={itemData}
onValuesChange={this.handleValuesChange}
handleDaysValueChange={this.handleDaysValueChange}
/>
</TabPane>
<TabPane key="2" tab="Audit" disabled={!isSR}>
<FormTemplateAudit
itemData={itemData}
/>
</TabPane>
</Tabs>
);
}
}
ShiftRosterForm.propTypes = {
itemId: PropTypes.number, // Passed in by the HOC. The loaded Shift Roster id
itemData: PropTypes.object, // Passed in by the HOC. The loaded Shift Roster data
customData: PropTypes.object, // Temporary store to hold the changed Shift Roster
isFormDirty: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is dirty
isLoading: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is loading
daysValue: PropTypes.object,
onFormStateChange: PropTypes.func // Passed in by the HOC. Callback to update the parent form state.
};
ShiftRosterForm.defaultProps = {
itemId: -1,
itemData: {},
customData: {},
isFormDirty: false,
isLoading: false,
daysValue: {},
onFormStateChange() { }
};
const ShiftRosterFormTitle = ({ data }) => {
const name = (!isEmpty(data) && data.id > 0) ? `${data.name}` : 'New Shift Roster';//`${data.name}`
return isEmpty(data)
? <ExtendedAvatar type="icon" size="large" />
: <span><ExtendedAvatar name={name} type="letter" size="large" />{name}</span>
}
const saveShiftRoster = (shiftrosterId, shiftroster, rosterdays) => {
return ApiData.saveShiftRoster(shiftrosterId, shiftroster, rosterdays)
.then(response => {
notifySuccess('Save Successful', 'Site Work Package saved successfully');
return response;
})
.catch(error => {
throw error;
});
}
const saveForm = (formState, setFormState) => {
const { isFormDirty, itemData, customData, formInputRef } = formState;
const typeName = "Dynamic";
const actualType = itemData.type;
let rosterdays = [];
if (actualType !== typeName) {
rosterdays = GetDaysForRoster(itemData);
console.log("My Values" + JSON.stringify(rosterdays));
}
const shiftRosterId = itemData.id;
const isExistingShiftRoster = shiftRosterId > 0;
return new Promise((resolve, reject) => {
if (isExistingShiftRoster && !isFormDirty) {
// No Changes
notifySuccess('Save Successful', 'Site Work Package saved successfully');
resolve(itemData);
}
else {
// Validate and Save
formInputRef.validateFields((error, values) => {
if (!error) {
// Form validated successfully.
// Save form changes
const shiftrosterRecord = saveShiftRoster(shiftRosterId, values, rosterdays);
resolve(shiftrosterRecord);
}
else {
// Form validation error.
// Return data as is.
resolve(itemData);
}
});
}
});
}
const GetDaysForRoster = (itemsData) => {
const result = [];
const keys = Object.keys(itemsData);
for (const k in keys) {
if (Number(k) == k) {
result[k] = itemsData[k]
}
}
return result.filter(function (el) { return el != null });
}
const WrappedShiftRosterForm = withDrawerForm({
containerClassName: 'bhp-equipment-type-form',
title: (record) => <ShiftRosterFormTitle data={record} />,
onLoad: (itemId, setFormState) => ApiData.getShiftRoster(itemId),
onSave: (formState, setFormState) => { return saveForm(formState, setFormState); },
canView: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Read] }),
canCreate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Create] }),
canUpdate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Update] })
})(ShiftRosterForm);
WrappedShiftRosterForm.propTypes = {
containerClassName: PropTypes.string,
itemId: PropTypes.number,
visible: PropTypes.bool,
onSave: PropTypes.func,
onClose: PropTypes.func
};
WrappedShiftRosterForm.defaultProps = {
containerClassName: null,
itemId: -1,
visible: false,
onSave() { },
onClose() { }
};
export default WrappedShiftRosterForm;
//ShiftRosterFormDetails
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ApiData } from 'data';
import { Form, Input, Select, Button, space, InputNumber } from 'antd';
import ShiftDays from './shiftdays'
const ShiftRosterFormDetails = ({ form, dataSource, onValueChange, handleDaysValueChange }) => {
const { getFieldDecorator } = form;
const formLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
console.log("My datasource" + dataSource.shiftRosterDays);
//const daysRoster = dataSource.shiftRosterDays || [{ daysIn: 1, daysOut: 1, category: "Day Shift" }];
const [inputList, setInputList] = useState([{ daysIn: 1, daysOut: 1, category: "Day Shift" }]);
const [selectedType, setSelectedType] = useState(dataSource.type || 'Fixed Single');
const [isTotalDaysRequired, SetTotalDaysRequired] = useState(dataSource.type === 'Dynamic' ? true : false);
const [isTotalDaysRequiredMessage, setIsTotalDaysRequiredMessage] = useState(dataSource.type === 'Dynamic' ? 'Please enter the Total Days' : '');
const handleTypeChanged = (value, e) => {
setSelectedType(value);
if (value === "Dynamic") {
SetTotalDaysRequired(true);
setIsTotalDaysRequiredMessage('Please enter the Total Days');
}
if (value === "Fixed Single") {
if (inputList.length > 1) {
const list = [...inputList];
console.log("Total" + inputList.length);
list.splice(1, inputList.length);
setInputList(list);
console.log("My List" + JSON.stringify(list));
console.log("Input List" + JSON.stringify(inputList));
handleDaysValueChange(list);
}
}
else {
SetTotalDaysRequired(false);
setIsTotalDaysRequiredMessage('');
}
};
return (
<div className='bhp-equipment-type-form-details bhp-content-box-shadow'>
<Form {...formLayout}>
<Form.Item label='Name' hasFeedback>
{getFieldDecorator('name', {
initialValue: dataSource.name,
rules: [{
required: true,
message: 'Please enter the Name'
}],
})(
//disabled={dataSource.id > 0}
<Input placeholder='Name' />
)}
</Form.Item>
<Form.Item label='Status' hasFeedback>
{getFieldDecorator('status', {
initialValue: dataSource.status,
rules: [{
required: true,
message: 'Please enter the Status'
}],
})(
<Select>
<Select.Option value="Active">Active</Select.Option>
<Select.Option value="InActive">InActive</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Type' hasFeedback>
{getFieldDecorator('type', {
initialValue: dataSource.type || 'Fixed Single',
rules: [{
required: true,
message: 'Please select the Type'
}],
})(
<Select onChange={handleTypeChanged}>
<Select.Option value="Fixed Single">Fixed Single</Select.Option>
<Select.Option value="Fixed Multiple">Fixed Multiple</Select.Option>
<Select.Option value="Dynamic">Dynamic</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Total Days' hasFeedback style={selectedType === 'Dynamic' ? { display: '' } : { display: 'none' }}>
{getFieldDecorator('totaldays', {
initialValue: dataSource.totalDays,
rules: [{
required: isTotalDaysRequired,
message: isTotalDaysRequiredMessage
}],
})(
<InputNumber min={1} max={365} />
)}
</Form.Item>
<ShiftDays inputList={inputList} setInputList={setInputList} selectedType={selectedType} handleDaysValueChange={handleDaysValueChange} getFieldDecorator={getFieldDecorator} />
</Form>
</div>
)};
const onFieldsChange = (props, changedFields, allFields) => {
if (props.onFieldsChange) {
props.onFieldsChange(props, changedFields, allFields);
}
};
const onValuesChange = (props, changedValues, allValues) => {
if (props.onValuesChange) {
props.onValuesChange(props, changedValues, allValues);
}
};
ShiftRosterFormDetails.propTypes = {
form: PropTypes.object,
dataSource: PropTypes.object,
onFieldsChange: PropTypes.func,
onValuesChange: PropTypes.func
};
ShiftRosterFormDetails.defaultProps = {
form: {},
dataSource: {},
onFieldsChange() { },
onValuesChange() { }
};
export default Form.create({
onValuesChange,
onFieldsChange
})(ShiftRosterFormDetails);
I got an object from API resource and put it inside the property, then children component can't access the props's object inside created method to assign it values inside my data properties as arrays and strings
when i try to console the props from child component i found my items object inside it
"This is my parent component"
<template>
<v-container grid-list-xl fill-height>
<v-layout row wrap>
<v-flex xs6 offset-xs3>
<message-box :items="source" v-if="source.length > 0"></message-box>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
<script>
import MessageBox from './MessageBox'
export default {
components:{MessageBox},
data() {
return {
room_id: 1,
source: {},
};
},
created(){
var app = this;
axios.get(`/api/room/${app.room_id}/message`)
.then(res => app.source = res.data.data);
}
};
</script>
</script>
"This is my child component"
<template>
<div>
<beautiful-chat
:participants="participants"
:titleImageUrl="titleImageUrl"
:onMessageWasSent="onMessageWasSent"
:messageList="messageList.messageList"
:newMessagesCount="newMessagesCount"
:isOpen="isChatOpen"
:close="closeChat"
:icons="icons"
:open="openChat"
:showEmoji="true"
:showFile="true"
:showTypingIndicator="showTypingIndicator"
:colors="colors"
:alwaysScrollToBottom="alwaysScrollToBottom"
:messageStyling="messageStyling"
#onType="handleOnType"
#edit="editMessage"
/>
</div>
</template>
<script>
import CloseIcon from "vue-beautiful-chat/src/assets/close-icon.png";
import OpenIcon from "vue-beautiful-chat/src/assets/logo-no-bg.svg";
import FileIcon from "vue-beautiful-chat/src/assets/file.svg";
import CloseIconSvg from "vue-beautiful-chat/src/assets/close.svg";
export default {
props: ['items'],
data() {
return {
room_id: 1,
participants:[],
messageList: [],
limit: 7,
busy: false,
auth_uid: User.id(),
titleImageUrl:
"https://a.slack-edge.com/66f9/img/avatars-teams/ava_0001-34.png",
newMessagesCount: 0,
isChatOpen: false,
alwaysScrollToBottom: false, // when set to true always scrolls the chat to the bottom when new events are in (new message, user starts typing...)
messageStyling: true,
showTypingIndicator: "",
icons: {
open: {
img: OpenIcon,
name: "default"
},
close: {
img: CloseIcon,
name: "default"
},
file: {
img: FileIcon,
name: "default"
},
closeSvg: {
img: CloseIconSvg,
name: "default"
}
},
colors: {
header: {
bg: "#4e8cff",
text: "#ffffff"
},
launcher: {
bg: "#4e8cff"
},
messageList: {
bg: "#ffffff"
},
sentMessage: {
bg: "#4e8cff",
text: "#ffffff"
},
receivedMessage: {
bg: "#eaeaea",
text: "#222222"
},
userInput: {
bg: "#f4f7f9",
text: "#565867"
}
}
};
},
methods: {
sendMessage(text) {
if (text.length > 0) {
this.newMessagesCount = this.isChatOpen
? this.newMessagesCount
: this.newMessagesCount + 1;
this.onMessageWasSent({
author: "support",
type: "text",
data: { text }
});
axios
.post(`/api/room/${this.room_id}/message`, { body: text })
.then(res => console.log("message sent"));
}
},
onMessageWasSent(message) {
// called when the user sends a message
this.messageList = [...this.messageList, message];
},
openChat() {
// called when the user clicks on the fab button to open the chat
this.isChatOpen = true;
this.newMessagesCount = 0;
},
closeChat() {
// called when the user clicks on the botton to close the chat
this.isChatOpen = false;
},
handleScrollToTop() {
// called when the user scrolls message list to top
// leverage pagination for loading another page of messages
},
handleOnType() {
console.log("Emit typing event");
},
editMessage(message) {
const m = this.messageList.find(m => m.id === message.id);
m.isEdited = true;
m.data.text = message.data.text;
},
},
created(){
// console.log(this.$props.items)
Array.prototype.forEach.call(this.$props.items, child => {
this.participants = child.participants;
// console.log(this.participants)
this.messageList = child.body;
// console.log(this.messageList)
});
},
computed:{
itemarr(){
return this.$props.items
}
}
};
</script>
"The console error is TypeError: Array.prototype.forEach called on null or undefined"
"The output of my items object is {__ob__: Observer}"
You can use v-if to solve your problem. You need to wait for ajax response to render child component
<template>
<v-container grid-list-xl fill-height>
<v-layout row wrap>
<v-flex xs6 offset-xs3>
<message-box v-if="sourceLength > 0" :items="source"></message-box>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
<script>
import MessageBox from './MessageBox'
export default {
components:{MessageBox},
data() {
return {
room_id: 1,
source: {},
};
},
created(){
var app = this;
axios.get(`/api/room/${app.room_id}/message`)
.then(res => app.source = res.data.data);
},
get sourceLength() {
return Object.keys(this.source).length;
}
};
</script>
</script>
setState is in use but DataTables doesn't refresh after Add or Edit function.
Please focus on:
listOfCurrency
Function ws
Function onClickSubmitAddCurrency
Component JSTable
Tag in JSTable.js
Currency.js
componentWillMount() {
this.setState(
{
listOfCurrency: [],
currency: { currency: '', symbol: '', status: '' },
myCurrencyRate: { exchangeRate: 0, adminFee: 0 },
currencyRateRequest:{exchangeRate:'',processFee:'',earnrate:'',earnAdminFee:''},
myCurrency: { currency:''},
isAdmin:false,
message: '',
snackbar: {
open: false,
vertical: null,
horizontal: null,
},
}
);
var checkAccessRightUrl=properties.domain+properties.checkAccessRightUrl;
this.wsCheckUrlAccess(checkAccessRightUrl,'Currency');
var wsListUrl = properties.domain + properties.currencyList;
this.ws(wsListUrl, false, false, 'get', null, false,'list');
var populateMyMembershipCurrencyRate = properties.domain + properties.populateMembeshipCurrencyRate;
this.wsbind(populateMyMembershipCurrencyRate,'currencyRate');
var myMembershipCurrency = properties.domain + properties.showMyMembershipCurrency;
this.wsbind(myMembershipCurrency,'myMembershipCurrency');
var checkIsAdminUrl = properties.domain + properties.populateCheckIsAdmin;
this.wsbind(checkIsAdminUrl,'checkIsAdmin');
}
ws(wsurl, jsonLibrary, toggle, process, senditem, snackbar,processName) {
var accessToken = sessionStorage.getItem('accessToken');
var reqs;
if (process === 'post') {
reqs = Request.post(wsurl)//wsurl
} else if (process === 'get') {
reqs = Request.get(wsurl)//wsurl
}
reqs.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/json')
if (senditem != null) {
reqs.send(senditem)
}
reqs.end((err, res) => {
if (res.status === 200) {
if(processName === 'currencyRate'){
this.setState(
{
isLoading:false,
currencyRateRequest: res.body,
message: (res.body===null?'':res.body.message),
snackbar: {
vertical: 'top',
horizontal: 'right',
open: snackbar
},
}
);
}else{
this.setState(
{
isLoading:false,
listOfCurrency: (jsonLibrary ? JSON.parse(JSON.stringify(res.body.responses)) : res.body),
message: (res.body===null?'':res.body.message),
snackbar: {
vertical: 'top',
horizontal: 'right',
open: snackbar
},
}
);
}
if (toggle) {
this.togglePrimary();
}
} else {
this.checkSession(res);
console.log('do logout function here');
console.log(err);
}
});
}
onClickSubmitAddCurrency() {
var wsListUrl = properties.domain + properties.addCurrency;
this.ws(wsListUrl, true, true, 'post', this.state.currency, true);
};
render() {
return (
<div className="animated fadeIn">
<CardBody>
<JSTable id='#currencyTable'
dataSet={this.state.listOfCurrency} columns={columns}
onEdit={this.onClickGotoEdit.bind(this)} onDelete={this.onClickSubmitDeleteCurrency.bind(this)} />
</CardBody>
</div>
);
}
JSTable.js
import React, { Component } from 'react';
import { Card, CardBody, CardHeader, Col, Row,Button } from 'reactstrap';
import '../../css/jquery.dataTables.css';
import '../../css/dataTables.responsive.css';
const $ = require('jquery');
var ReactDOM = require('react-dom');
$.Datatable = require('datatables.net-responsive');
class JSTable extends Component {
constructor(props) {
super(props);
this.state = {
//ajaxurl : props.ajaxurl,
id : props.id,
dataSet : props.dataSet,
columns : props.columns,
onEdit : props.onEdit,
onDelete: props.onDelete,
onTopUp : props.onTopUp,
render : {
edit :props.onEdit==null?{display:'none'}:{display:''},
delete :props.onDelete==null?{display:'none'}:{display:''},
topup :props.onTopUp==null?{display:'none'}:{display:''},
},
indicator:{
edit :props.onEdit==null?true:false,
delete :props.onDelete==null?true:false,
topup :props.onTopUp==null?true:false,
}
};
}
componentDidMount() {
this.$el = $(this.el);
this.$el.DataTable({
// ajax: this.state.ajaxurl,
data: this.state.dataSet,
columns: this.state.columns,
responsive: true,
columnDefs: [{
targets: 0,
createdCell: (td, cellData, rowData, row, col) =>
ReactDOM.render(
<div>
<Button color="primary" style={this.state.render.edit}
onClick={this.state.indicator.edit?this.empty:this.state.onEdit.bind(this.celldata,this,rowData,this.row,this.col)}
className="mr-1">Edit</Button>
<Button color="primary" style={this.state.render.delete}
onClick={this.state.indicator.delete?this.empty:this.state.onDelete.bind(this.celldata,this,rowData,this.row,this.col)}
className="mr-1">Delete</Button>
<Button color="primary" style={this.state.render.topup}
onClick={this.state.indicator.topup?this.empty:this.state.onTopUp.bind(this.celldata,this,rowData,this.row,this.col)}
className="mr-1">Top Up</Button>
</div>, td
),
}
],
});
}
// componentWillUnmount() {
// this.$el.DataTable.destroy(true);
// }
empty(){
console.log('===========no function=================');
}
render() {
return (
<div className="animated fadeIn">
<table data={this.state.dataSet} className="display" width="100%" ref={el => this.el = el}>
</table>
</div>
);
}
}
export default JSTable;
I can get the update data. However, it doesn't update the table. Unless I refresh the page.
Please advise.
The simplest way to refresh or rerender component is a key.
Your key can be your value in your state
state={counter:null}
Then when data is loaded from your API
Use setState and increment your counter
<table key={this.state.counter}>
//your code
</table>
React Current Image in Image Gallery
You can use this.forceUpdate() for rerender in reactJs.