Stop referencing object with vuejs - javascript

I know that the default behaviour of a object when we create new atributes for the same instance is that it reference the old, changing the properties.
I have something like this on my vue data:
export default {
data() {
return {
paragraph: {
text: "",
fontSize: 14,
key: "Paragraph",
align: "left"
}
}
},
methods: {
addParagraph() {
this.$set(this.paragraph, 'key', this.paragraph.key);
this.$set(this.paragraph, 'text', this.paragraph.text);
this.$set(this.paragraph, 'fontSize', this.paragraph.fontSize);
this.$set(this.paragraph, 'align', this.paragraph.align);
this.$store.commit("appendToDocument", this.paragraph)
},
alignment(option) {
this.paragraph.align = option;
}
}
everytime i click a button the data inside the paragraph changes and i want to pas the data to vuex store to add it to a json, so i can have a tree of paragraphs, the problem is, that everttime i create a new paragrapg it changes the values of my other paragraphs created before, is there a way to change it?

#Potray answer is good. But it can be even shorter if you are using Babel with stage-3 (spread operator). Then you can copy all properties with that syntax
addParagraph() {
this.$store.commit("appendToDocument", { ...this.paragraph })
},

Try this:
addParagraph() {
var paragraph = {
key: this.paragraph.key,
text: this.paragraph.text,
fontSize: this.paragraph.fontSize,
align: this.paragraph.alignkey,
}
this.$store.commit("appendToDocument", paragraph)
},

Related

Filtering a stack trace array in vuejs

I am looking for some good ideas on how to filter an array that contains a stack trace. I have a database table that has four columns, one with the stack trace error messages, one that shows the priority of the error, one that shows the date the error was registered and finally a column that displays an custom made error message, which I have placed on multiple try-blocks around my system.
On the frontend I am fetching the data with axios and placing it inside an object called errors. Then in my computed properties I create an array of fields that contain the individual columns from the database and their data. I use the Bootstrap table to output it.
<template>
<b-container>
<b-card class="mt-4">
<h5>{{ $t('events') }}</h5>
<b-table
:items="errors"
:fields="fields"
:per-page="[5, 10]"
sort-desc
primary-key="id"
/>
</b-card>
</b-container>
</template>
<script>
import {errorService} from '#/services/error';
import moment from 'moment';
export default {
components: {
CommonTable,
flapper
},
data() {
return {
errors: null,
};
},
computed: {
fields() {
return [
{
key: 'priority',
label: this.$t('errorLogs.priority'),
sortable: true
},
{
key: 'creationDateTime',
label: this.$t('creationDateTime'),
formatter: date => moment(date).locale(this.$i18n.locale).format('L'),
sortable: true
},
{
key: 'stackTrace',
label: this.$t('errorLogs.stackTrace'),
sortable: true
},
{
key: 'errorMessage',
label: this.$t('message'),
sortable: true
},
]
},
},
methods: {
load(){
errorService.getErrorLogs().then(result => {
this.errors = result.data
})
}
},
created() {
this.load()
}
};
</script>
It works as it should, but the output for the stack trace takes up way too much space in the table column.
Ideally it should only show
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
and then if the user wants more detail they can click on the stack trace and get the full version in a pop up or something.
I am guessing the easiest solution would be to filter the stack trace, so that it does not show any text beyong the : sign.
But how would I implement this in the setup that I currently have?
I am guessing in computed properties I need add a method to the stackTrace field.
So:
{
key: 'stackTrace',
label: this.$t('errorLogs.stackTrace'),
sortable: true
function: this.filteredStackTrace()
},
And then create a new method.
filteredStackTrace(){
this.errors.stackTrace.filter(some filter...)
}
Maybe something like following snippet:
const app = Vue.createApp({
data() {
return {
st: `Exception in thread "main" java.lang.NullPointerException
at com.example.myproject.Book.getTitle(Book.java:16)
at com.example.myproject.Author.getBookTitles(Author.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)`,
expanded: false
};
},
computed: {
firstLine() {
return this.st.split('\n')[0]
},
allLines() {
return this.st.split('\n').filter((item, idx) => idx !== 0).toString()
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
{{ firstLine }}
<button #click="expanded = !expanded">all</button>
<div v-if="expanded">{{ allLines }}</div>
</div>

FormBuilder.js: How to inherit from radio-group control?

I'm trying to create a new control for FormBuilder, it's basically a radio-group control (I mean it has the same config of a radio-group control) but I need to define a custom build() and onRender() method.
I read docs a lot of times but can't get it to work, here is a simple example of what I'm trying to do.
if (!window.fbControls) window.fbControls = new Array();
window.fbControls.push(function (controlClass) {
class controlMultipleObjects extends controlClass {
static get definition() {
return {
icon: '\uD83D\uDD89',
i18n: {
default: 'Control Multiple Items'
}
};
}
configure() {
// this.js = '//cdn.jsdelivr.net/npm/signature_pad#2.3.2/dist/signature_pad.min.js';
}
/**
* build a text DOM element, supporting other jquery text form-control's
* #return DOM Element to be injected into the form.
*/
build() {
this.dom = this.markup('div', null, {class:'multipleObjectsController', id: this.config.name});
return this.dom;
}
onRender() {
}
}
// register this control for the following types & text subtypes
controlClass.register('multipleObjects', controlMultipleObjects);
return controlMultipleObjects;
});
This basically works, the new control 'multipleObjects' is shown in form builder, but when opening the config for the control it only shows the 'Value' item. I need to show multiple values just like the radio-group, select or select-group controls.
Any ideas?
Thanks!
Take a look at https://formbuilder.online/docs/formBuilder/options/typeUserAttrs
you'll need to pass options in your formBuilder like that:
const options = {
typeUserAttrs: {
multipleObjects: {
options: {
label: 'Class',
multiple: true,
options: {
'red form-control': 'Red',
'green form-control': 'Green',
'blue form-control': 'Blue'
},
},
},
},
}
$("#fb-editor").formBuilder(options)

Froala Editor use generic properties and add custom one

I have some Froala Editor inputs and I want to use generic propierties for all of them and then add some custom properties according to the current input.
For assign the froala object I use this code:
new FroalaEditor('.froala-editor-inline-horari', {
toolbarInline: true,
placeholderText: 'Editar',
toolbarButtons: [
['bold', 'italic'],
['textColor', 'backgroundColor']
],
events: {
contentChanged: function () {
guardarFila(this);
}
},
spellcheck: false
});
I want to use some generic properties as a constant like:
const FROALA_PROPERTIES = {
toolbarInline: true,
placeholderText: 'Editar',
toolbarButtons: [
['bold', 'italic'],
['textColor', 'backgroundColor']
],
events: {
contentChanged: function () {
guardarFila(this);
}
},
spellcheck: false
});
and then add to this object some modification like:
events: {
initialized: function () {
this.html.set('some value');
}
so, in this example I want to obtain the first object FROALA_PROPERTIES plus the new events: {...} key.
Is it this possible?
I answer myself because I found the solution. I can add this keys and values or functions by the next code:
FROALA_PROPERTIES.events.initialized = function () {
this.html.set('some value');
}
But if events key doesnt exist, then I need to create it first.
I based my andwer by this web research: http://researchhubs.com/post/computing/javascript/add-a-key-value-pair-to-a-javascript-object.html

tabulator - how to display a loading indicator while data is fetched from db based on a prop?

I created a tabulator component in my vue app and I am passing Tabulator options data and columns via props as following:
// parent component
<template>
<div>
<Tabulator :table-data="materialsData" :table-columns="options.columns" :is-loading="materials.isLoading" />
</div>
</template>
...
// Tabulator component
props: {
tableData: {
type: Array,
default: function() {
return [
{ name: "Billy Bob", age: "12" },
{ name: "Mary May", age: "1" },
];
},
},
tableColumns: {
type: Array,
default: function() {
return [
{ title: "Name", field: "name", sorter: "string", width: 200, editor: true },
{ title: "Age", field: "age", sorter: "number", align: "right", formatter: "progress" },
];
},
},
isLoading: {
type: Boolean,
default: true,
},
},
mounted() {
// instantiate Tabulator
this.tabulator = new Tabulator(this.$refs.table, {
placeholder: "loading data...",
//placeholder: "<font-awesome-icon icon='circle-notch' spin size='2x' />",
data: this.tableData, // link data to table (passed as prop)
columns: this.tableColumns, // define table columns (passed as prop)
});
},
everything works well besides indicating to the user that data is loading. I first tried using the placeholder option (http://tabulator.info/docs/4.7/layout#placeholder) but realized it is not applicable because it is also rendered when no data is displayed.
is there a way to tell Tabulator to preferably render a loading spinner or a text, based on my prop isLoading?
Tabulator has a built in loading overlay when it is loading data via ajax, which can be customised on the ajaxLoaderLoading option
var table = new Tabulator("#table", {
ajaxLoaderLoading:"<span>Loading Data</span>",
});
If you want you want to load your data outside of Tabulator and then load the data in with the setData command, then this is outside of the scope of tabulator.
The easiest approach to this would be to absolutely position an element over the Tabulator that is shown when you trigger your DB load.
In tabulator 5, the new option is called dataLoaderLoading. According to the documentation you can pass an 'html'. In the code, this can be a string or an html component. I think there is a bug when you pass a string and the html is not correctly generated (from what I can tell the way the Template element is used). So to get it to work I had to pass the html element, something in the lines of:
const template = document.createElement('template');
template.innerHTML = '<div style="display:inline-block;" class="d-flex flex-row">' +
'<div>Loading... </div>' +
'<div class="ml-2 activity-sm" data-role="activity" data-type="atom" data-style="dark"></div>' +
'</div>';
const dataLoaderLoading = template.content.firstChild;
...
new Tabulator("#mytable", {
dataLoaderLoading: dataLoaderLoading,
...

How to share a method between two components in Vue.js?

I have an Ag-Grid that has certain action buttons and dynamic data getting filled from a MongoDB database. I have a method on my MasterData.Vue file that refreshes the Grid. Each action button inside my grid's record perform update/delete operations. When I click on those buttons I have designed a customized pop up modal component in another Modal.Vue file. I want to call that RefreshGrid() method in Modal.Vue. I tried using props to share the data but same thing doesn't work on method.
MasterData.Vue Script
<script>
import { AgGridVue } from 'ag-grid-vue';
import { mapGetters } from 'vuex';
import gridEditButtons from '#/components/GridEditButton';
import MasterModal from '#/components/MasterModal';
export default {
name: 'masterData',
data () {
return {
addBtnClick: false,
delBtnClick: false,
editVisible: false,
selected: 'Business Area',
dropdown_tables: [
'Business Area',
'Council',
'Sub Area',
'Type',
'Work Flow Stage'
],
gridOptions: {
domLayout: 'autoHeight',
enableColumnResize: true,
rowDragManaged: true,
animateRows: true,
context: {
vm: null
}
}
};
},
components: {
'ty-master-modal': MasterModal,
'ag-grid-vue': AgGridVue,
gridEditButtons
},
methods: {
// Filter Grid Contents based on Dropdown selection
RefreshGrid: function () {
let cName;
if (this.selected === 'Business Area') {
cName = 'businessarea';
} else if (this.selected === 'Council') {
cName = 'council';
} else if (this.selected === 'Type') {
cName = 'typemaster';
} else if (this.selected === 'Work Flow Stage') {
cName = 'workflowstage';
}
let obj = {
vm: this,
collectionName: cName,
action: 'masterData/setMasterData',
mutation: 'setMasterData'
};
this.$store.dispatch(obj.action, obj);
}
};
</script>
Modal.Vue Script
<script>
import {mapGetters} from 'vuex';
export default {
name: 'MasterModal',
props: {
readOnly: Boolean,
entryData: Object,
addBtnClick: Boolean,
delBtnClick: Boolean,
editVisible: Boolean,
selectedTable: String
},
data () {
return {
fieldAlert: false,
isReadOnly: false,
dialog: false,
dialogDelete: false,
valid: false,
visible: false,
disable: false
};
},
computed: {
...mapGetters('masterData', {
entryState: 'entryState',
// entryData: 'entryData',
columns: 'columns',
selectedRowId: 'selectedRowId'
})
},
watch: {
addBtnClick: function (newValue, oldValue) {
this.setDialog(!this.dialog);
},
editVisible: function (newValue, oldValue) {
this.setVisible(!this.visible);
},
delBtnClick: function (newValue, oldValue) {
this.setDialogDelete(!this.dialogDelete);
}
},
methods: {
setDialog (bValue) {
this.dialog = bValue;
},
setDialogDelete (bValue) {
this.dialogDelete = bValue;
},
}
};
</script>
there are a couple of ways to achieve this.
One is to use the emit
in the MasterModal.vue component run this.$emit('refreshGrid') in the parent MasterData.Vue component use <ty-master-modal #refreshGrid="RefreshGrid" ...>
if you have a direct parent-child relationship, this is likely the best option
Another way is just to pass a function as a prop to the child component.
<ty-master-modal :onRefreshGrid="RefreshGrid" ...>
and add a prop onRefreshGrid to MasterModal.vue, then you can invoke the function.
Another way, using vuex, is to add a watch to MasterData.Vue and watch a variable in the vuex store ie. actionInvoker. when actionInvoker changes, the action executes. To change the value, set it to 0 and increment or toggle between, or set to random value. The advantage is that you can call this from anywhere.
The problem with this (and the previous) solution is that you have functionality tied to a view/component that shouldn't be there. I would recommend a third solution, which is to push the functionality into a vuex action, and then you can call it from anywhere. This would require though that you store the selected variable in vuex too, and if you want to have multiple instances of Modal and Master components, a singular store will prohibit that (unless you add support for multiple instances).

Categories