I have some fields where the validation works fine and functionality where items get moved from an unselected array to a selected array. Im trying to validate the length of the selected array before submitting the form. It is required to select at least 1 item.
I'm trying to achieve the onSubmit() method is not fired when the "selected" array is empty. At the moment I'm trying to achieve this with vue's "watch()" method and vee-validates "validate()" method, but to no avail.
Thanks in advance!
I am using:
vue 3
vee-validate 4
Code
<template>
<Form #submit="onSubmit">
//some other fields where the validation works
//functionality that puts an array item from the unselected to the selected array
<Button>Submit</Button>
</Form>
</template>
<script setup>
import {ref, watch} from "vue/dist/vue";
import {validate, Form} from "vee-validate";
const onSubmit = () => {
//submit form if all fields are valid
}
const selected = ref([])
const unselected = ref([
{title: 'Bram janssen', id: 1, value: 1},
{title: 'Bert Bertsma', id: 2, value: 2},
{title: 'Kees Keesma', id: 3, value: 3},
{title: 'Jan Janssen', id: 4, value: 4},
{title: 'Pieter Pietersma', id: 5, value: 5},
{title: 'Lars Klaver', id: 6, value: 6},
])
watch(selected.value, () => {
validate(selected.value, {arrayLength: true})
});
</script>
//this rule lives in another file, confirmed to work
defineRule('arrayLength', (array: any) => {
if (array.length < 1) {
return 'Array is empty, add at lest 1 item';
}
return true;
});
I guess just not with a watch. You can use the validate method directly in your "add to selected". Working example: here
Code looks like this:
<template>
<form #submit="onSubmit">
<p>
<button
#click.prevent.stop="addToArray"
>
Add to Array
</button>
</p>
<p>Array Length: {{ myArray.length }} - array error: {{ arrayError }}</p>
<p>
<button type="submit">Submit</button>
</p>
</form>
</template>
<script setup lang="ts">
import { useField, useForm } from 'vee-validate';
const { handleSubmit } = useForm();
const onSubmit = handleSubmit((values) => {
alert(JSON.stringify(values, null, 2));
});
var {
value: myArray,
errorMessage: arrayError,
validate,
} = useField('myArray', (value) =>
value.length > 0 ? true : 'Please add to the array'
);
myArray.value = [];
function addToArray(){
myArray.value.push('abc');
validate();
}
</script>
Related
I have a question regarding Storybook and Vue components with v-models. When writing a story for let's say an input component with a v-model i want a control reflecting the value of this v-model. Setting the modelValue from the control is no problem, but when using the component itself the control value stays the same. I am searching the web for a while now but i can't seem to find a solution for this.
A small example:
// InputComponent.vue
<template>
<input
type="text"
:value="modelValue"
#input="updateValue"
:class="`form-control${readonly ? '-plaintext' : ''}`"
:readonly="readonly"
/>
</template>
<script lang="ts">
export default {
name: "GcInputText"
}
</script>
<script lang="ts" setup>
defineProps({
modelValue: {
type: String,
default: null
},
readonly: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
</script>
In Storybook:
Does anyone have a solution to make this working?
Thanks in advance!
In my case, I have a custom select input that uses a modelValue prop.
I tried this and worked for me:
at my-component.stories.js:
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
export default {
title: 'Core/MyComponent',
component: MyComponent,
argTypes: { }
}
const Template = (args) => ({
components: { MyComponent },
setup() {
let model = ref('Javascript')
const updateModel = (event) => model.value = event
return { args, model, updateModel }
},
template: '<my-component v-bind="args" :modelValue="model" #update:modelValue="updateModel" />'
})
export const Default = Template.bind({})
Default.args = {
options: [
'Javascript',
'PHP',
'Java'
]
}
The question is simple, how to use select2 in nuxt3?
select2 from https://select2.org/
nuxt3 from v3.nuxtjs.org
I have spent few days to do this, but failed. Finally I made my own npm package. Think might be useful for others, so here is how to do it:
1 npm install
npm install nuxt3-select2 --save
2 create a plugin
// plugins/select2.client.ts
import Select2 from 'nuxt3-select2';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component("Select2", Select2, {});
});
3 use it in template
<template>
<div>
<Select2 v-model="myValue" :options="myOptions" :settings="{ settingOption: value, settingOption: value }" #change="myChangeEvent($event)" #select="mySelectEvent($event)" />
<h4>Value: {{ myValue }}</h4>
</div>
</template>
<script setup lang="ts">
const myChangeEvent = (event) => {
console.log("myChangeEvent: ", event);
}
const mySelectEvent = (e) => {
console.log("mySelectEvent: ", event);
}
const myOptions = [
{id: 1, text: 'apple'},
{id: 2, text: 'berry'},
{id: 3, text: 'cherry'},
]
const myValue = ref();
</script>
you can read more documentation here: nuxt3-select2
I am having a hard time to access the values inside the test property.
When I do current.room.test I get the "Proxy { : {…}, : {…} }" as a value, but I want to read the array like shown in isButtonDisabled().
Not sure exactly what the trick is to do this inside the setup/isButtonDisabled function.
I can do current.room.title and no problems but the moment I have an Object I don't know how to get it's value
Thank you
export default defineComponent({
name: "App",
setup() {
const roomId = ref(5);
const current = reactive({
room: {_id: 1, title: 'title', test: [{_id: 4}, {_id: 3}]},
});
function isButtonDisabled(optionIdx: number) {
console.log(current.room.test);
console.log(current.room.test[optionIdx]);
}
return {
current,
};
},
});
The key here was to return your method into the template (if you want to call it from there). Your syntax to access the items was right.
const { createApp, reactive, ref } = Vue;
const app = createApp({
setup() {
const roomId = ref(5);
const current = reactive({
room: {_id: 1, title: 'title', test: [{_id: 4}, {_id: 3}]},
});
function isButtonDisabled(optionIdx) {
console.log(current.room.test[optionIdx]);
console.log(current.room.test[optionIdx]._id, ' value');
}
return {
isButtonDisabled
}
}
});
app.mount("#app");
<script src="https://unpkg.com/vue#next"></script>
<div id="app">
<button #click="isButtonDisabled(0)">test: _id: 0</button>
<button #click="isButtonDisabled(1)">test: _id: 1</button>
</div>
I use Rails and React-Table to display tables. It works fine so far. But How can one add an edit/delete column to the React-Table?
Is it even possible?
return (
<ReactTable
data={this.props.working_hours}
columns={columns}
defaultPageSize={50}
className="-striped -highlight"
/>
)
All you need to do is turn columns into a component state. You can see a working example https://codesandbox.io/s/0pp97jnrvv
[Updated 3/5/2018] Misunderstood the question, here's the updated answer:
const columns = [
...
{
Header: '',
Cell: row => (
<div>
<button onClick={() => handleEdit(row.original)}>Edit</button>
<button onClick={() => handleDelete(row.original)}>Delete</button>
</div>
)
}
]
where handleEdit and handleDelete will be the callbacks how you want to handle the actions when the buttons are clicked.
You can actually add buttons using the accessor prop of the columns in react-table.
here is code example:
{
Header: 'Action',
accessor: (originalRow, rowIndex) => (
<div>
<button onClick={() => handleEdit(originalRow)}>Edit</button>
<button onClick={() => handleDelete(originalRow)}>Delete</button>
</div>
),
id: 'action',
},
ReactTable v7
Add a column into a table can be achieved by inserting a column object into columns definitions that pass into useTable hook. Basically, the number of column objects that resides in the columns definitions array represents the number of columns that will be rendered by react-table.
Usually, a minimal column object consists of Header and accessor which at the Header we pass the name of the column and at the accessor, we pass the key that will be used by the react-table to look up the value from the data passed into useTable hook.
{
Header: "Column Name",
accessor: "data key", // can be a nested key
}
Here, to render other than a string inside a cell, which is, in this case, is custom button JSX, we can use either accessor or Cell option and pass to it a Function that returns a valid JSX.
accessor
The document says that accessor accept either string or Function Here we use Function to render a JSX button.
accessor: String | Function(originalRow, rowIndex) => any
One of the benefits of using accessor options is the rowIndex is directly available. The rowIndex represents the index number of rows inside the data array that is currently managed by react-table on the client-side and the originalRow is the raw object of row.
Here the rowIndex can be used as a reference to select and modify row objects in the columns data array.
Cell
Cell option accept Function that return either JSX or React.Component. Cell option is usually used for formatting a cell value, but here we use to render our button.
Cell: Function | React.Component => JSX
The Function receives tableInstance that is quite similar to the result of useTable hook with additional cell, row, and column object.
Cell: (tableInstance) => JSX
Here we can get the row index information too by destructuring the row object:
Cell: (tableInstance) => {
const { row: index } = tableInstance;
return (
...
)
}
So, it depends on your requirement in determining whether accessor or Cell will be the chosen one for rendering your edit/add button. But if you need to get more data/information from tableIntance, then Cell is the correct option to go.
Note: If you choose accessor, please make sure that the id is included in the column properties due to the id option being required as the document said so.
Required if accessor is a function.
This is the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.
Now, we already have the column. The next is the button. Commonly the button is either a normal button that will either call a handler for updating a state or triggering a dialog popup or a link button that will redirect the app to the detail page.
So, the code will be:
// accessor
{
Header: 'Action',
id: 'action',
accessor: (originalRow, rowIndex) => {
return (
// you can pass any information you need as argument
<button onClick={() => onClickHandler(args)}>
X
</button>
)
}
}
// or Cell
{
Header: 'Action',
accessor: "action",
Cell: (tableInstance) => {
const { row: index } = tableInstance;
return (
// you can pass any information you need as argument
<button onClick={() => onClickHandler(args)}>
X
</button>
)
}
}
Example:
const { useCallback, useEffect, useMemo, useState } = React;
const { useTable } = ReactTable;
// table data
const data = [
{
name: "John",
workingHours: 40
},
{
name: "Doe",
workingHours: 40
}
];
const AddEmployee = ({ onSubmit }) => {
const [name, setName] = useState("");
const [workingHours, setWorkingHours] = useState("");
const handleSubmit = (e) => {
onSubmit(e);
setName("");
setWorkingHours("");
}
return (
<fieldset style={{ width: "200px" }}>
<legend>Add Employee:</legend>
<form onSubmit={(e) => handleSubmit(e)}>
<input
type="text"
name="name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<br />
<input
type="text"
name="workingHours"
placeholder="Working Hours"
value={workingHours}
onChange={(e) => setWorkingHours(e.target.value)}
/>
<br />
<button type="submit">Add</button>
</form>
</fieldset>
)
}
const EditEmployee = ({ row, onSave }) => {
const { originalRow, rowIndex } = row;
const [name, setName] = useState(originalRow.name);
const [workingHours, setWorkingHours] = useState(originalRow.workingHours);
return (
<fieldset style={{ width: "200px" }}>
<legend>Edit Employee:</legend>
<input
type="text"
name="name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<br />
<input
type="text"
name="workingHours"
placeholder="Working Hours"
value={workingHours}
onChange={(e) => setWorkingHours(e.target.value)}
/>
<br />
<button onClick={() => onSave({ name, workingHours }, rowIndex)}>Save</button>
</fieldset>
)
}
function App() {
const [tableData, setTableData] = useState(data);
const [editingRow, setEditingRow] = useState();
const handleDelete = useCallback((index) => {
setTableData(tableData.filter((v, i) => i !== index));
},[tableData]);
const tableColumns = useMemo(() => [
{
Header: 'Name',
accessor: 'name',
},
{
Header: 'Working Hours',
accessor: 'workingHours'
},
{
Header: 'Action',
id: 'action',
accessor: (originalRow, rowIndex) => {
return (
<div>
<button onClick={() => setEditingRow({ originalRow, rowIndex })}>
Edit
</button>
<button onClick={() => handleDelete(rowIndex)}>
Delete
</button>
</div>
)
}
}
], [handleDelete]);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = useTable({
columns: tableColumns,
data: tableData,
});
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const newData = {};
formData.forEach((value, key) => newData[key] = value);
setTableData((prevData) => {
return [...prevData, newData];
});
};
const handleEdit = useCallback((row, rowIndex) => {
const editedData = tableData.map((rowData, index) => {
if (index === rowIndex) {
return row;
}
return rowData;
});
setTableData(editedData);
setEditingRow();
},[tableData])
return (
<div>
<h3>React-table v.7</h3>
<br />
{ editingRow ? <EditEmployee row={editingRow} onSave={handleEdit} /> : <AddEmployee onSubmit={handleSubmit} /> }
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-table#7.8.0/dist/react-table.development.js"></script>
<div class='react'></div>
I am trying to fetch the documents from the db in an order from most likes to least and I keep running into an error. I created a few documents with likes of 1, 2 and 3 and the order that is returned is 2, 3, 1. It is really strange because when I first start up the server, it works fine, but I found that after around 20 mins of working on my project(not touching the code I am about to post), I realized that it wasn't returning the docs in proper order. Could this be a bug in Meteor? Or is it a problem on my side? Anyway here is the code where I am trying to fetch the docs in order.
renderNotesByLike.js
import React from "react";
import { Tracker } from "meteor/tracker";
import { Link, withRouter } from "react-router-dom"
import { Notes } from "./../../methods/methods";
class RenderNotesByLike extends React.Component{
constructor(props){
super(props);
this.state = {
notes: []
};
}
renderNotes(notes){
return notes.map((note) => {
return(
<div key={note._id} className="note-list" onClick={() => {this.props.history.push(`/fullSize/${note._id}`)}}>
<div className="left inline">
<p><strong>{note.title}</strong></p>
<span className="removeOnSmallDevice">{note.userEmail}</span>
</div>
<div className="right inline">
<span>Subject: <strong>{note.subject}, {note.unit}</strong></span>
<br />
<span className="removeOnSmallDevice">⬆ {note.likes.length} ⬇ {note.dislikes.length}</span>
</div>
</div>
)
})
}
componentDidMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
const notes = Notes.find({subject: this.props.subject}, {sort: {likes: -1}}).fetch();
notes.map((note) => {console.log(note.likes.length)})
this.setState({ notes })
});
}
componentWillReceiveProps(nextProps) {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
const notes = Notes.find({subject: nextProps.subject}, {sort: {likes: -1}}).fetch();
this.setState({ notes });
});
}
componentWillUnmount() {
this.tracker.stop()
}
render(){
return(
<div className="center">
{this.renderNotes(this.state.notes)}
</div>
)
}
}
export default withRouter(RenderNotesByLike);
The publication for notes is pretty basic:
Meteor.publish('notes', function () {
return Notes.find()
});
I do realize that a possible problem would be because I am publishing all the notes and I have to publish the ones I want to be filtered. But I did it the exact same way with the CreatedAt property and that works just fine.
Example Data
cloudinaryData:
{data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
createdAt:
1506224240000
description:""
dislikes:[]
imageURL:["AImageURL.jpg"]
likes:["d#d"]
subject:"Food"
title:"a"
unit:"a"
userEmail:"d#d"
userId:"rSGkexdzzPnckiGbd"
_id:"GPJa8qTZyDHPkpuYo"
__proto__:Object
Notes Schema:
"notes.insert"(noteInfo){
noteInfo.imageURL.map((url) => {
const URLSchema = new SimpleSchema({
imageURL:{
type:String,
label:"Your image URL",
regEx: SimpleSchema.RegEx.Url
}
}).validate({ imageURL:url })
})
Notes.insert({
title: noteInfo.title,
subject: noteInfo.subject,
description: noteInfo.description,
imageURL: noteInfo.imageURL,
userId: noteInfo.userId,
userEmail: noteInfo.userEmail,
unit: noteInfo.unit,
likes: [],
dislikes: [],
createdAt: noteInfo.createdAt,
cloudinaryData: noteInfo.cloudinaryData
})
console.log("Note Inserted", noteInfo)
}
You're sorting based on an array, not the length of the array. {sort: {likes: -1}} is not going to give you predictable results. Try explicitly sorting the fetched array of documents using underscore.js' _.sortBy() function.
componentDidMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
let notes = Notes.find({subject: this.props.subject}).fetch();
notes = _.sortBy(notes,(n) => { return n.likes.length});
this.setState({ notes })
});
}