How to filter array values dynamically with two string (searchboxes)? Here's what I've tried so far:
if (!!this.searchCompany.toLowerCase() || !!this.searchName.toLowerCase()) {
this.filter = true
this.newval = this.list.filter(post => {
if (!!this.searchCompany.toLowerCase()) {
return post.profile.company ? post.profile.company.toLowerCase().includes(this.searchCompany.toLowerCase()) : null
} else if (!!this.searchName.toLowerCase()) {
return post.profile.full_name.toLowerCase().includes(this.searchName.toLowerCase())
} else {
return post
}
})
this.searchCompany and this.searchName are the searchboxes in my form.
The array content of this.list
UPD:
Here is your condition checking
if(!!this.searchCompany.toLowerCase())
It does not check whether the value is presented and in lower case. Firstly it will be transformed to lower case by String method and then cast to boolean type. As we know, empty string casts to boolean FALSE and any non empty string to TRUE. Moreover, you can get TypeError in case that searchCompany property is not presented in this.
Here small example snippet how you can search in array of objects
const posts = [{
company: 'Coca-Cola',
name: 'Rocket'
},
{
company: 'Pepsi-Cola',
name: 'Groot'
}
]
let form = document.forms['search-form']
let resultBox = document.querySelector('#result-box')
let searchButton = document.querySelector('#search')
searchButton.addEventListener('click', doSearch, false)
function searchPost(parameter, searchString) {
return posts.find(post => post[parameter].toLowerCase().includes(searchString.toLowerCase()))
}
function isFormEmpty() {
return [...form.children].filter(e => e.type === 'text').reduce((r, c) => r && !c.value.length, true)
}
function doSearch() {
if (isFormEmpty(form)) {
e.preventDefault()
return false
}
let searchNameEl = form.querySelector('[name="searchName"]')
let searchCompanyEl = form.querySelector('[name="searchCompany"]')
let searchParam = searchCompanyEl.value.length ? 'company' : 'name'
let searchText = searchCompanyEl.value.length ? searchCompanyEl.value : searchNameEl.value
let post = searchPost(searchParam, searchText)
resultBox.textContent = JSON.stringify(post)
}
form {
display: block;
}
.result {
margin-top: 10px;
}
<form id="search-form">
<label>Search company</label>
<input type="text" name="searchCompany" />
<label>Search name</label>
<input type="text" name="searchName" />
<button type="button" id="search">Search</button>
</form>
<div class="result">
<pre id="result-box"></pre>
</div>
Related
I have an autocomplite input on my page, but i want to show some values before user begin typing
I read Tagify documentation but find nothing about it
I tried use tagTextProp but it not works
When i set value attribute on my original input then autocomplite stop working
my input on the page
<div class="form-control">
<input id="DetailsAddress" name="address" type="text" class="form-control__control" data-action="form-control" autocomplete="off">
<div class="form-control__label"><?php EP_Lng::_e('Address'); ?></div>
</div>
<div class="form__alert form__alert_error" id="field_address" style="display:none;"></div>
my Tagify js code
cityAutocomplete(input = null) {
input = input || document.getElementById('city_autocomplete')
let url = input.dataset.url || en4.core.baseUrl + 'pets/ajax/city';
let whitelist = input.value ? JSON.parse(input.value) : [];
let tagify = new Tagify(input, {
enforceWhitelist: true,
whitelist: whitelist,
mode: 'select',
value =[{"value":"bar"}],
tagTextProp: 'value',
});
let controller;
tagify.on('input', onInput)
function onInput( e ) {
var value = e.detail.value;
tagify.settings.whitelist.length = 0; // reset the whitelist
controller && controller.abort();
controller = new AbortController();
// show loading animation and hide the suggestions dropdown
tagify.loading(true).dropdown.hide.call(tagify)
let data = new FormData();
data.append('text', value);
fetch(url, {
signal: controller.signal,
method: 'POST',
body: data
})
.then(response => response.json())
.then(whitelist => {
whitelist = whitelist.map(item => {
return {
value: item.label,
id: item.id
}
})
// update inwhitelist Array in-place
tagify.settings.whitelist.splice(0, whitelist.length, ...whitelist)
tagify.loading(false).dropdown.show.call(tagify, value); // render the suggestions dropdown
})
}
tagify.on('change', onChange)
function onChange(e) {
// outputs a String
let location_id = document.getElementById("location_id");
if (!location_id) {
console.warn('Hidden field location_id not found')
return;
}
if (!e.detail.value) {
location_id.value = '';
} else {
let value = JSON.parse(e.detail.value);
location_id.value = value[0].id;
}
}
return tagify
}
How I use it
const addressInput = document.getElementById('DetailsAddress')
this.cityAutocomplete(addressInput)
Is there a way to create a validation on an input field based on a server side response with a vuelidate helper? I used a different approach by creating some data fields and then in my form submit check what code is being returned from the server and then displaying the error message from the data. This "kind of" works but now when the user types in the field again the error message remains until the server returns a success code.
My end result / goal would be just display the same error message for each response scenario and when the error is showing from server code responses and user begins to type again the error message is removed until another request is made.
<div class="form-field">
<label for="">
Enter your patient ID
</label>
<input type="text" class="form-input" name="isPatient" id="isPatient" v-model="$v.formData.isPatient.$model" />
<label v-if="($v.formData.isPatient.$dirty && !$v.formData.isPatient.validInput) || invalidPatient"
class="error error--invalidIsPatient">
We're sorry, this patient does not exist.
</label>
</div>
<div class="form-field">
<label for="">
Enter your Childs ID
</label>
<input type="text" class="form-input" name="isChild" id="isChild" v-model="$v.formData.isChild.$model" />
<label v-if="($v.formData.isChild.$dirty && !$v.formData.isChild.validInput) || invalidChild"
class="error error--invalidIsChild">
We're sorry, this child does not exist.
</label>
</div>
<script>
import { validationMixin } from 'vuelidate'
const { required } = require('vuelidate/lib/validators')
const VueScrollTo = require('vue-scrollto')
export default {
name: 'ActivateCard',
mixins: [validationMixin],
data() {
return {
formData: {
isPatient: null,
isChild: null
},
showDemographics: false,
showIdErrors: false,
showConfirmation: false,
invalidPatient: false,
invalidChild: false
}
},
validations: {
formData: {
isPatient: {
validInput: (value, vm) => {
const isValidValue = value != null && value.trim().length
const altValueIsValid = vm.isChild !== null && vm.isChild.trim().length
return isValidValue ? true : altValueIsValid
}
},
isChild: {
validInput: (value, vm) => {
const isValidValue = value !== null && value.trim().length
const altValueIsValid = vm.isPatient !== null && vm.isPatient.trim().length
return isValidValue ? true : altValueIsValid
}
}
}
},
methods: {
submitCardId() {
this.$v.$reset()
this.$v.$touch()
if (!this.$v.$invalid) {
const formData = new FormData()
formData.append('method', 'activate')
formData.append('isPatient', this.formData.isPatient)
formData.append('isChild', this.formData.isChild)
this.$services
.post('', formData)
.then(response => {
const responseCode = response.data.trim()
const responseSuccess = responseCode === 'success'
const responseHdBad = responseCode === 'hd-bad'
const responseCardBad = responseCode === 'card-bad'
const responseBothBad = responseCode === 'both-bad'
console.log(`activate response: ${responseCode}`)
if (responseSuccess) {
this.showDemographics = true
this.invalidPatient = false
this.invalidChild = false
}
if (responseBothBad) {
this.invalidPatient = true
this.invalidChild = true
}
if (responseHdBad) {
this.invalidPatient = true
}
if (responseCardBad) {
this.invalidChild = true
}
})
.catch(err => {
console.log(err + ' Activate Card Error')
})
}
}
}
}
</script>
I have form where user can add as much as he wants documents. Each document have several inputs.
And I'm trying to get each document inputs values and put it to state as array of objects.
State should look like:
[
{
id: 'UGN2WP68P1',
name: 'passport',
placeIssue: 'paris'
},
{
id: 'TD0KUWWIM6',
name: 'shengen visa',
placeIssue: 'paris'
}
...
]
So I write a function which is called on inputs change. He check are there object with same id, if there is no object with same id he creates new and add to array, if object exist with same id then he update object:
const addNewDocumentObj = (id, type, val) => {
// Get object with same id
let docObj = addedDocsArr.filter( el => el.id === id)
// If there is no object with same id, creates new one
if (docObj.length === 0) {
if (type === 'name'){
let obj = {
id: id,
docId: val.id
}
setAddedDocsArr(addedDocsArr.concat(obj))
} else if (type === 'placeIssue') {
let obj = {
id: id,
placeIssue: val
}
setAddedDocsArr(addedDocsArr.concat(obj))
}
// If object exist with same id then updates with new value and adds to array
} else {
if (type === 'name'){
let newObject = Object.assign(docObj, {name: val.id})
let newArray = addedDocsArr.filter(el => el.id !== id)
setAddedDocsArr(newArray.concat(newObject))
} else if (type === 'placeIssue') {
let newObject = Object.assign(docObj, {placeIssue: val})
let newArray = addedDocsArr.filter(el => el.id !== id)
setAddedDocsArr(newArray.concat(newObject))
}
}
}
But it doesn't work, and I can't understand why, maybe my logic is bad and there is better practise?
UPDATE:
In React debugger I noticed how state changes. If I add select document name, in state object looks like that:
{name: 'passport', id: 'UGN2WP68P1'}
If I enter document place of issue value. Then object changes and show data like that:
{placeIssue: 'paris', id: 'UGN2WP68P1'}
But result should be:
{name: 'passport', placeIssue: 'paris', id: 'UGN2WP68P1'}
So it looks like that object not updated but created new one
Maybe you need something like:
const addNewDocumentObj = (id, type, val) => {
// Get object with same id
let docObj = addedDocsArr.find(el => el.id === id)
// If there is no object with same id, creates new one
if (!docObj) {
docObj = { id, placeIssue: val }
// and pushes it to addedDocsArray
addedDocsArr.push(docObj)
}
if (type === 'name') {
docObj.name = val.id
} else if (type === 'placeIssue') {
docObj.placeIssue = val
}
setAddedDocsArr(addedDocsArr)
}
First of all, why are you using filter if you are actually try to find something in array? Just use find.
Second, if object with given id is already exists, there is no need to filter your array and then put that object back... Just find that object in array and update it! It is already in your array! Remember that Array contains references to your objects, so when you grab your object from the Array and edit it, your edit the same object that Array have.
Last one, Idk what logic your setAddedDocsArr function have. In my example I assume that the only thing it does is set its argument (newArray) to the variable named addedDocsArr. So instead of that, in situation where object with given id is not present, I just push it in old array.
Finished App:
Implementation of Handle submit:
const handleSubmit = (event) => {
event.preventDefault();
if (!uid) {
alert("Please enter the ID");
return;
}
let existingRecords = docs.filter((doc) => doc.id === uid);
if (!existingRecords.length) {
let newRecord = {
id: uid,
name: name,
issuePlace: place
};
setDocs([...docs, newRecord]);
setId("");
setName("");
setPlace("");
} else {
let unmodifiedRecords = docs.filter((doc) => doc.id !== uid);
if (name) {
existingRecords[0].name = name;
}
if (place) {
existingRecords[0].issuePlace = place;
}
unmodifiedRecords.push(existingRecords[0]);
setDocs(unmodifiedRecords);
setId("");
setName("");
setPlace("");
}
};
And Here is the full finished example:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [docs, setDocs] = useState([
{ id: "1", name: "passport", issuePlace: "delhi" }
]);
const [uid, setId] = useState("");
const [name, setName] = useState("");
const [place, setPlace] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
if (!uid) {
alert("Please enter the ID");
return;
}
let existingRecords = docs.filter((doc) => doc.id === uid);
if (!existingRecords.length) {
let newRecord = {
id: uid,
name: name,
issuePlace: place
};
setDocs([...docs, newRecord]);
setId("");
setName("");
setPlace("");
} else {
let unmodifiedRecords = docs.filter((doc) => doc.id !== uid);
if (name) {
existingRecords[0].name = name;
}
if (place) {
existingRecords[0].issuePlace = place;
}
unmodifiedRecords.push(existingRecords[0]);
setDocs(unmodifiedRecords);
setId("");
setName("");
setPlace("");
}
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<table>
<tr>
<td>
<label>ID: </label>
</td>
<td>
<input
value={uid}
onChange={(e) => {
setId(e.target.value);
}}
/>
</td>
</tr>
<tr>
<td>
<label>Name: </label>
</td>
<td>
<input
value={name}
onChange={(e) => {
setName(e.target.value);
}}
/>
</td>
</tr>
<tr>
<td>
<label>Isuue Place: </label>
</td>
<td>
<input
value={place}
onChange={(e) => {
setPlace(e.target.value);
}}
/>
</td>
</tr>
<tr>
<td>
<button type="submit">Submit</button>
</td>
</tr>
</table>
</form>
{docs.map((doc) => (
<div className="records">
<span>{"ID:" + doc.id + " "}</span>
<span>{"Name:" + doc.name + " "}</span>
<span>{"Issue Place:" + doc.issuePlace + " "}</span>
</div>
))}
</div>
);
}
Check out finished example with source code at: Codesandbox Link
I find a pretty easy way how to solve this problem. I read documentations of react forms and find multiple inputs idea React Forms
So I changed my code to:
// Update or add information of added documents inputs
const addNewDocumentObj = (id, e, name) => {
const newInput = addedDocsArr.map(el => {
if(id === el.id) {
if(name === 'name'){
el[name] = e.target.value
} else if (name === 'placeIssue'){
el[name] = e.target.value
}
}
return el
})
setAddedDocsArr(newInput);
}
// Add new document inputs
const addNewDocument = () => {
let blockId = randomNumber(10, true, false)
setAddedDocsArr([...addedDocsArr, {id: blockId, name: '', placeIssue: ''}])
}
And it works perfectly!
On initial render, the input components acts as normal. However after it is unfocused and refocused again, it will only accept one character. This also happens if I reset the form.
I created a FormValidationStateManager class to reuse Joi validation logic that is needed.
Form.jsx
class Form extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.validation = new FormValidationStateManager();
}
handleSubmit(e) {
e.preventDefault();
var result = this.validation.validateAll();
this.setState({
// need to do this, trigger render after validations
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
}, function() {
if (result.valid) {
this.props.onSubmit(result.data);
}
}.bind(this));
}
resetForm(e) {
e.preventDefault();
this.validation.clearForm();
this.setState({
// need to do this, trigger render after validations,
// we could eventually encapsulate this logic into FormValidationSM.
submit: Date.now()
});
}
render() {
// TODO implement logic for rendering name from API call
let name = "John Doe";
return (
<form class='info-form' onSubmit={this.handleSubmit} autocomplete='off'>
<div class='info-block-section-title'>
<h2>Welcome {name}</h2>
<h4>Some nicely worded few sentences that let's the user know they just need to fill out a few bits of information to start the process of getting their student loans paid down!</h4>
</div>
<br />
<div class='info-block-section-body'>
<InfoFieldset validation={this.validation} />
</div>
<div class='info-form-button'>
<FormButton label="Continue" />
<FormButton type="button" onClick={this.resetForm.bind(this)} label="Reset" />
</div>
</form>
)
}
}
export default Form;
Fieldset
class Fieldset extends Component {
constructor(props) {
super(props);
this.state = {
secondaryEmailVisible: false
};
this.handleToggle = this.handleToggle.bind(this);
this.validation = this.props.validation || new FormValidationStateManager();
this.validation.addRules(this.validatorRules.bind(this));
}
validatorRules() {
var _rules = {
// TODO validate format of date, address and zip
date_of_birth: Joi.date().label("Date of Birth").required(),
address_street: Joi.string().label("Street Address").required(),
address_zip: Joi.string().label("Zip Code").required(),
password_confirmation: Joi.any()
.valid(Joi.ref('password'))
.required()
.label('Password Confirmation')
.messages({
"any.only": "{{#label}} must match password"
}).strip(),
password: Joi.string()
.pattern(/\d/, 'digit')
.pattern(/^\S*$/, 'spaces')
.pattern(/^(?!.*?(.)\1{2})/, 'duplicates')
.pattern(/[a-zA-z]/, 'alpha')
.required()
.min(CONSTANTS.PASSWORD_MIN)
.label('Password')
.messages({
"string.min": PASSWORD_HINT,
"string.pattern.name": PASSWORD_HINT
})
}
if (this.state.secondaryEmailVisible) {
_rules['secondary_email'] = Joi.string()
.label("Secondary Email")
.email({ tlds: false })
.required();
}
return _rules;
}
handleToggle() {
this.setState(state => ({
secondaryEmailVisible: !state.secondaryEmailVisible
}));
}
render() {
return(
<div class='info-fieldset'>
{/* for skipping chrome browser autocomplete feature
<input type='text' style={{ display: 'none '}} />
<input type='password' style={{ display: 'none '}} />
*/ }
<strong>Primary email for login: work#example.com</strong>
<FormElementToggle
field="secondary"
label={"Add another email for notifications?"}
onChange={this.handleToggle} />
<div class='info-form-element'>
<DisplayToggle remove={true} show={this.state.secondaryEmailVisible}>
<FormElementInput
field='secondary_email'
label="Secondary Email"
validation={this.validation}
placeholder='john#example.com' />
</DisplayToggle>
<FormElementInput
field='password'
type='password'
label={(
<div>
Password
<Tooltip
placement='top'
trigger={['hover']}
overlay={PASSWORD_HINT}>
<span><i className='fa fa-info-circle'></i></span>
</Tooltip>
</div>
)}
placeholder='********'
validation={this.validation} />
<FormElementInput
key={1}
field='password_confirmation'
type='password'
label="Password Confirmation"
placeholder='********'
validation={this.validation} />
<FormElementInput
field='date_of_birth'
label="Date of Birth"
validation={this.validation}
placeholder='MM/DD/YYYY' />
<FormElementInput
field='address_street'
label="Street Address"
validation={this.validation}
placeholder='123 Example St' />
<FormElementInput
field="address_zip"
label="Zip Code"
validation={this.validation}
placeholder='01234' />
</div>
</div>
)
}
}
export default Fieldset;
FormElementInput.jsx
class FormElementInput extends Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.cachedErrors = {};
this.cachedValue = null;
this.state = { focused: false };
this.validation = this.props.validation || new FormValidationStateManager();
}
componentDidUpdate(prevProps, prevState) {
}
shouldComponentUpdate(nextProps, nextState) {
var filter = function(val, key, obj) {
if (_.isFunction(val)) {
return true;
}
// Certain props like "label" can take a React element, but those
// are created dynamically on the fly and they have an random internal UUID,
// which trips the deep-equality comparision with a false-positive.
// This is wasted cycles for rendering.
if (React.isValidElement(val)) {
return true;
}
// validation is a complex obj that we shouldn't be caring about
if (_.contains(['validation'], key)) {
return true;
}
};
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (!_.isEqual(this.cachedErrors, this.getErrors())) {
return true;
}
// if the errors after a validation got updated, we should re-render.
// We have to compare in this manner because the validation object is not shallow-copied
// via props since it is managed at a higher-component level, so we need to
// do a local cache comparision with our local copy of errors.
if (this.cachedValue !== this.getDisplayValue()) {
return true;
}
return !_.isEqual(_.omit(nextProps, filter), _.omit(this.props, filter)) || !_.isEqual(this.state, nextState);
}
setValue(value) {
console.error('deprecated - can not set value directly on FormElementInput anymore');
throw 'deprecated - can not set value directly on FormElementInput anymore';
}
isDollarType() {
return this.props.type === 'dollar';
}
isRateType() {
return this.props.type === 'rate';
}
getType() {
if (this.isDollarType() || this.isRateType()) {
return 'text';
} else {
return this.props.type;
}
}
getPrefix() {
if (this.isDollarType()) {
return _.compact([this.props.prefix, '$']).join(' ');
} else {
return this.props.prefix;
}
}
getPostfix() {
if (this.isRateType()) {
return _.compact([this.props.postfix, '%']).join(' ');
} else {
return this.props.postfix;
}
}
getDisplayValue() {
var value = this.props.value || this.validation.getFormValue(this.props.field);
// while DOM input is focused, just return the same value being typed
if (this.state.focused) {
return value;
}
if (this.isDollarType()) {
if (_.isUndefined(value)) {
return value;
}
// keep in sync with the validation check in getOnChange()
// even if this is different - it wont break - cause accounting.js handles gracefully
else if (_.isNumber(value) || !value.match(/[a-z]/i)) {
return accounting.formatMoney(value, '', 2);
} else {
return value;
}
} else if (this.isRateType()){
if (_.isUndefined(value)) {
return '';
}
return accounting.formatNumber(value, 3)
} else {
return value;
}
}
getErrors() {
return this.props.errors || this.validation.getValidationMessages(this.props.field);
}
onBlur(event) {
this.validation.validate(this.props.field);
this.setState({ focused: false });
}
onFocus(event) {
this.setState({ focused: true });
}
onChange(event) {
if (this.isDollarType()) {
// only accept if the input at least resembles a currency
// accounting.parse already strips alot of characters and does this silently
// we want to be a little less strict than accounting.parse since we need to allow more
// different types of inputs +-,. and also let accounting.parse do its job
// very basic check here for a-z characters
if (!event.target.value.match(/[a-z]/i)) {
var parsedValue = accounting.parse(event.target.value);
event._parsedValue = parsedValue;
}
}
let _value = event._parsedValue || event.target.value;
console.log(this.input)
this.validation.setFormValue(this.props.field, _value);
}
_eventHandlers(field) {
return {
onChange: this.onChange.bind(this),
onBlur: this.onBlur.bind(this),
onFocus: this.onFocus.bind(this)
};
}
_mergeEventHandlers(p1, p2) {
var events = [
'onChange',
'onBlur',
'onFocus'
];
var r1 = {};
events.map(function(e) {
r1[e] = FuncUtils.mergeFunctions(p1[e], p2[e]);
}.bind(this));
return r1;
}
render() {
let _errors = this.cachedErrors = this.getErrors();
let _value = this.cachedValue = this.getDisplayValue();
let attrs = {
id: this.props.htmlId || ('field_' + this.props.field),
className: classnames({
'error': _errors && _errors.length
}),
type: this.getType(),
placeholder: this.props.placeholder,
autoComplete: "false",
disabled: this.props.disabled,
readOnly: this.props.disabled,
value: _value,
ref: this.input,
};
attrs = _.extend(attrs, this._mergeEventHandlers(this.props, this._eventHandlers()));
const prefix = this.getPrefix();
const postfix = this.getPostfix();
return (
<FormElementWrapper
type="input"
field={this.props.field}
errors={_errors}
label={this.props.label}>
<div className="form-element-input">
<DisplayToggle remove={true} hide={!prefix}>
<span className="prefix">{prefix}</span>
</DisplayToggle>
<div className={classnames({
"has-prefix": !!prefix,
"has-postfix": !!postfix
})}>
<input {...attrs} />
</div>
<DisplayToggle remove={true} hide={!postfix}>
<span className="postfix">{postfix}</span>
</DisplayToggle>
</div>
<div className="form-error">
{_errors.map((msg, idx) => {
return (<FormError key={idx}>{msg}</FormError>)
})}
</div>
</FormElementWrapper>
)
}
};
FormElementInput.PropTypes = {
field: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
value: PropTypes.any,
label: PropTypes.any,
errors: PropTypes.array,
type: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onFocus: PropTypes.func,
onKeyUp: PropTypes.func,
tooltip: PropTypes.string,
prefix: PropTypes.string,
postfix: PropTypes.string,
disabled: PropTypes.bool,
disableTrimming: PropTypes.bool
}
FormElementInput.defaultProps = {
type: 'text',
placeholder: 'Enter Value',
tooltip: undefined,
prefix: undefined,
postfix: undefined,
disabled: false,
disableTrimming: false
}
export default FormElementInput;
FormValidationStateManager.js
class FormValidationStateManager {
// default state, validation ref obj
constructor(defaultState) {
this.rules = [];
this.state = defaultState || {};
this.errors = {};
this.dirty = false;
}
getFormValue(key) {
return this.state[key];
}
setFormValue(key, value) {
this.dirty = true;
this.state[key] = value;
}
setFormState(newState) {
this.state = _.extend({}, newState);
}
addRules(rules) {
this.rules.push(rules);
}
isFormDirty(){
return this.dirty;
}
/**
* Clear all previous validations
*
* #return {void}
*/
clearValidations() {
this.dirty = false;
this.errors = {};
}
clearForm() {
this.state = _.mapObject(this.state, function(v, k) {
return '';
});
this.clearValidations();
}
/**
* Check current validity for a specified key or entire form.
*
* #param {?String} key to check validity (entire form if undefined).
* #return {Boolean}.
*/
isValid(key) {
return _.isEmpty(this.getValidationMessages(key));
}
// call within submit
validateAll() {
return this.validate();
}
/**
* Method to validate single form key or entire form against the component data.
*
* #param {String|Function} key to validate, or error-first containing the validation errors if any.
* #param {?Function} error-first callback containing the validation errors if any.
*/
validate(key, callback) {
if (_.isFunction(key)) {
callback = key;
key = undefined;
}
var schema = this._buildRules();
var data = this.state;
var result = this._joiValidate(schema, data, key);
if (key) {
this.errors[key] = result.errors[key];
} else {
this.errors = result.errors;
}
return {
valid: this.isValid(key),
errors: this.errors,
data: result.data
};
}
_buildRules() {
var allRules = this.rules.map(function(r) {
// Rules can be functions and be dynamically evalulated on-the-fly,
// to allow for more complex validation rules.
if (_.isFunction(r)) {
return r();
} else {
return r;
}
});
// collapse all the rules into one single object that will
// be evaluated against `this.state`
return Joi.object(_.extend({}, ...allRules));
}
/**
* Get current validation messages for a specified key or entire form.
*
* #param {?String} key to get messages, or entire form if key is undefined.
* #return {Array}
*/
getValidationMessages(key) {
let errors = this.errors || {};
if (_.isEmpty(errors)) {
return [];
} else {
if (key === undefined) {
return _.flatten(_.keys(errors).map(error => {
return errors[error] || []
}));
} else {
return errors[key] ? errors[key].map(he.decode) : [];
}
}
}
_joiValidate(joiSchema, data, key) {
joiSchema = joiSchema || Joi.object();
data = data || {};
const joiOptions = {
// when true, stops validation on the first error, otherwise returns all the errors found. Defaults to true.
abortEarly: false,
// when true, allows object to contain unknown keys which are ignored. Defaults to false.
allowUnknown: true,
// remove unknown elements from objects and arrays. Defaults to false
stripUnknown: true,
errors: {
escapeHtml: true,
// overrides the way values are wrapped (e.g. [] around arrays, "" around labels).
// Each key can be set to a string with one (same character before and after the value)
// or two characters (first character before and second character after), or false to disable wrapping:
label: false,
wrap: {
label: false,
array: false
}
},
messages: {
"string.empty": "{{#label}} is required",
"any.empty": "{{#label}} is required",
"any.required": "{{#label}} is required"
}
};
const result = joiSchema.validate(data, joiOptions);
let errors = this._formatErrors(result);
if (key) {
errors = _.pick(errors, key);
}
return {
errors: errors,
data: result.value
};
}
_formatErrors(joiResult) {
if (joiResult.error) {
return _.reduce(joiResult.error.details, (memo, detail) => {
// According to docs:
// - "detail.path": ordered array where each element is the accessor to the value where the error happened.
// - "detail.context.key": key of the value that erred, equivalent to the last element of details.path.
// Which is why we get the last element in "path"
var key = _.last(detail.path);
if (!Array.isArray(memo[key])) {
memo[key] = [];
}
if (!_.contains(memo[key], detail.message)) {
memo[key].push(detail.message);
}
return memo;
}, {});
} else {
return {};
}
}
}
export default FormValidationStateManager;
I think I got it. I changed value: _value to defaultValue: _value for the attributes in FormElementInput.jsx
saw it here
Anyone know why this fixed it under the hood?
I have a problem: I need to check if the username typed into an input form is equal to the username that is in my array of users. I do the same thing for password. I want to make a login form.
var app = angular.module('myApplication', []);
app.controller('UserListCtrl', function ($scope) {
$scope.usersList = [
{
name: 'Alex',
pass: 2200201
},
{
name: 'Michael',
pass: 1231215
},
{
name: 'John',
pass: 1232116
}
];
$scope.checkInputs = function () {
$scope.searchUser = {
name: $scope.yourName,
pass: $scope.yourPass
};
if ($scope.searchUser.name === $scope.usersList.name) {
console.log($scope.searchUser.name);
} else {
console.log('You are not registered');
}
};
});
And the HTML:
<form ng-submit="checkInputs()">
<input type="text" ng-model="searchUser.yourName"><br>
<input type="text" ng-model="searchUser.yourPass">
<input type="submit" class="button">
</form>
Important note : this answer deals with the different mistakes that are present in the specific code posted in the question, as
check if a string is contained in an array of object
angularjs databinding between the view and the model
The code exposed here should not be considered as a valid authentication solution
I need to check if username that the user types in input form is equal to username that is in the array of users
To achieve that, you wrote $scope.searchUser.name === $scope.usersList.name. That will fail, as userList is not a string but an array of strings. So as to check if a name is in the userList array, you can use the function indexOf.
The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.
1) Replace :
if ($scope.searchUser.name === $scope.usersList.name) {
// ...
}
with
Solution 1 : assuming you use a library like lodash or underscore with a pluck function :
var userNames = _.pluck($scope.usersList, 'name');
if (userNames.indexOf($scope.searchUser.name) >= 0) {
// ...
}
Solution 2 : define yourself a pluck function :
var myPluck = function (propertyName, array) {
return array.map(function(obj) {
return obj[propertyName];
})
}
var userNames = myPluck('name', $scope.usersList);
if (userNames.indexOf($scope.searchUser.name) >= 0) {
// ...
}
2) Replace also :
$scope.searchUser = {
name: $scope.yourName,
pass: $scope.yourPass
};
with
$scope.searchUser = {
name: '',
pass: ''
};
3) Finally, in the html template, replace :
<input type="text" ng-model="searchUser.yourName"><br>
<input type="text" ng-model="searchUser.yourPass">
with
<input type="text" ng-model="searchUser.name"><br>
<input type="text" ng-model="searchUser.pass">
Here is your working jsfiddle
<form ng-submit="checkInputs(searchUser)">
<input type="text" ng-model="searchUser.yourName"><br>
<input type="text" ng-model="searchUser.yourPass">
<input type="submit" class="button">
</form>
function formCtrl($scope){
$scope.usersList = [
{
name: 'Alex',
pass: 2200201
},
{
name: 'Michael',
pass: 1231215
},
{
name: 'John',
pass: 1232116
}
];
$scope.checkInputs = function (credentials) {
var auth = false;
angular.forEach($scope.usersList, function(value){
if(value.name == credentials.yourName && value.pass == credentials.yourPass){
auth = true;
}
if(!auth){
$scope.message= "You are not authorized";
}
else
{
$scope.message= "Authorized";
}
});
}
}