I want to make a universal handler for input event and I need somehow to change v-model value in it. I tried this:
function validateInput(event) {
let input = event.target.value;
input = parseInt(input);
if(isNaN(input)) {
event.target.value = '';
} else {
event.target.value = input;
}
}
<input #input="validateInput($event)"
v-model="inputRef">
But inputRef doesn't react on event.target.value changes.
As Boussadjar mentioned, modifying event.target.value is no option.
just return the value from the event handler:
function onlyNumbersFilter(event) {
let input = event.target.value;
input = parseInt(input);
return isNaN(input) ? '' : input;
}
and use it:
<input #input="inputRef = onlyNumbersFilter($event)" :value="inputRef">
Or if you want to stick with v-model:
// setup
import { computed, ref } from 'vue'
// val is not DOM Event but value itself unwrapped from $event.target.value
function onlyNumbersFilter(val) {
const n = parseInt(val);
return isNaN(n) ? '' : val;
}
const filteredModel = function(valueRef, filterFn) {
return computed({
get() { return valueRef.value }
set(newVal) { valueRef.value = filterFn(newVal)) }
})
}
const input1Value = ref('')
const input1Model = filteredModel(input1Value, onlyNumbersFilter)
return {
input1Model
}
and usage:
<input v-model="input1Model">
In this case you should use value instead of v-model since v-model is the equivalent of #input ant value :
function validateInput(event) {
let input = event.target.value;
input = parseInt(input);
if(isNaN(input)) {
inputRef.value = '';
} else {
inputRef.value = input;
}
}
<input #input="validateInput($event)"
:value="inputRef">
Related
In my <vue-numeric>
<vue-numeric
currency="RMB"
separator=","
v-bind:minus="false"
v-model="amount"
v-bind:precision="2"
class="form-control form-control-lg bg-secondary border-0 text-white"
></vue-numeric>
By using this code it can convert the user input to number type even the input contains string inside, but what I want is user only can insert numberand when alphabet pressed, it will display nothing
You could try adding a keydown event listener and test the keycode to see if it's a number:
#keydown="testNumber"
methods: {
testNumber({ keyCode }) {
if(keyCode < 48 || keyCode > 57) {
event.preventDefault()
}
}
}
What you can do is extend/ overwrite the component options - e.g.:
import VueNumeric from "vue-numeric";
const inputHanlder = VueNumeric.methods["onInputHandler"];
VueNumeric.methods["onInputHandler"] = function(val) {
// here the sanitizer
this.amount = this.amountNumber;
inputHanlder.bind(this)();
};
VueNumeric.watch["valueNumber"] = function(newValue) {
console.log(newValue);
if (this.$refs.numeric !== document.activeElement) {
this.amount = this.format(newValue);
} else {
this.amount = newValue;
}
};
export default VueNumeric;
class UserStore {
#observable rules = {
data:[],
isFeatching: false,
error:false,
};
#observable rooms = {
data:[],
isFeatching: false,
error:false,
};
#observable money = {
data:[],
isFeatching: false,
error:false,
};
#action
async getRules() {
try {
this.rules.isFeatching = true;
const data = await api.getRules();
this.rules.isFeatching = false;
}
}
#action
async getRooms() {
try {
this.rooms.isFeatching = true;
const data = await api.getRooms();
this.rooms.isFeatching = false;
}
}
#action
async getMoney() {
try {
this.money.isFeatching = true;
const data = await api.getMoney();
this.money.isFeatching = false;
}
}
}
Help me please.
Conditions of the problem:
1) I need to get three types of data in one Store.
Task Objective:
1) How to make it so that "isFeatching" is automatically placed?
Is there any way to automate?
I had the following thought:
Create a global array (or a class from which I will inherit):
const globalManagerFetching = {UserStore: {
getRules: {isFeatching:false}
getRooms: {isFeatching:false}
getMoney: {isFeatching:false}
}
But how to do it?
How can I get the name action?
my pseudocode:
#action
async getMoney() {
const methoneName = 'getMoney'; // how to get it automatically?
try {
globalManagerFetching[this.constructor.name][methoneName] = false;
const data = await api.getMoney();
globalManagerFetching[this.constructor.name][methoneName] = true;
}
}
my pseudocode other:
#action
async getMoney() {
try {
setFetching(true);//how to do this?
const data = await api.getMoney();
setFetching(false);//how to do this?
}
}
Tell me please.
Sorry for bad english
If I understand the context of your question correct - you would like to avoid code duplication. I would recommend to solve you this by restructuring your code in such a way:
const { getRules, getRooms, getMoney} = api;
class UserStoreResource {
#observable data = [];
#observable isFetching = false;
#observable error;
constructor(fetchFunction) {
this.fetchData = async ( ) => {
this.isFetching = ture;
await fetchFunction();
this.isFetching = false;
}
}
}
class UserStore {
rules = new UserStoreResource(getRules);
rooms = new UserStoreResource(getRooms);
money = new UserStoreResource(getMoney);
#action
async fetchAllData() {
try {
await Promise.all([
this.rules.fetchData(),
this.rooms.fetchData(),
this.money.fetchData(),
])
}
}
}
If in your components you will use any observable from UserStoreResource - you will get correct rerendering.
Answering your question about getting a function name - its possible by requesting a property arguments.callee.name - but this is deprecated functionality. More here. Most of all - if you need this property - this is an indicator that code requires restructure.
you can get function name like this
const methodName = arguments.callee.name
i'm am attempting to pull in data through VueX and iterate over an array of objects to get the menu_code. I am successfully get the data i need but i need to show/hide a button according to these conditions:
if ALL of the data in menu_code is NULL, don't show the button.
if one or more of the menu_code data is !== NULL, show the button.
unsure if i'm linking the hasCode data correctly to the button.
// MenuPage.vue
<button v-show="hasMenuCode">hide me if no menu code</button>
<script lang="ts">
import {
Vue,
Component,
Watch,
Prop
} from 'vue-property-decorator';
import {
namespace
} from 'vuex-class';
import MenuItem from "../../models/menu/MenuItem";
export default class MenuPage extends Vue {
#namespace('menu').State('items') items!: MenuItem[];
hasCode = true;
hasMenuCode() {
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].menu_code !== null) {
this.hasCode = true;
} else {
this.hasCode = false;
}
}
}
}
</script>
// MenuItem.ts
import AbstractModel from "../AbstractModel";
import Image from '../common/Image';
import MenuRelationship from "./common/MenuRelationship";
import Availability from "../common/Availability";
import Pricing from "../common/Pricing";
interface MenuItemMessages {
product_unavailable_message: string;
}
interface ItemOrderSettings {
min_qty: number;
max_qty: number;
}
export default class MenuItem extends AbstractModel {
name: string;
menu_code: string;
description: string;
image: Image;
has_user_restrictions: boolean;
availability: Availability;
pricing: Pricing;
ordering: ItemOrderSettings;
messages: MenuItemMessages;
prompts: MenuRelationship[];
setMenus: MenuRelationship[];
constructor(props: any) {
super(props);
this.name = props.name;
this.menu_code = props.menu_code;
this.description = props.description;
this.image = props.image;
this.has_user_restrictions = props.has_user_restrictions;
this.availability = new Availability(props.availability);
this.pricing = new Pricing(props.pricing);
this.ordering = props.ordering;
this.messages = props.messages;
this.prompts = props.prompts;
this.setMenus = props.setMenus;
}
}
Base on your requirement, you need to return if there is any item has menu_code, otherwise the loop continues and it only take the value of the final item in this.items
hasMenuCode() {
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].menu_code !== null) {
this.hasCode = true;
return
} else {
this.hasCode = false;
}
}
}
Shorter implementation
hasMenuCode() {
return this.items.some(item => item.menu_code !== null)
}
In my RN project, I want to achieve this.
// componentWillReceiveProps
componentWillReceiveProps = async (nextProps) => {
let { searchText, peopleTab } = this.props;
let params = {};
if (peopleTab !== nextProps.peopleTab) {
params.peopleTab = nextProps.peopleTab;
}
// here i want to pass nextProps.searchText without a condition with the params like this.
// params.searchText = nextProps.searchText
if (Object.keys(params).length > 0) {
await this.props.fetchUnfollowedPeople(params);
}
}
I want to send nextProps.searchText with params object, if there is a new value. Otherwise I want to send this.props.searchText with the params object.
The above code, if I uncomment
// params.searchText = nextProps.searchText
it gives the infinity loop. How can I achieve this?
Setting the let { searchText, peopleTab } = this.props; in componentWillReceiveProps causes the new value to be pasted
componentWillMount() {
this.searchText = this.props.searchText ;
this.peopleTab = this.props.peopleTab ;
}
componentWillReceiveProps = async (nextProps) => {
const params = [];
if (this.peopleTab !== nextProps.peopleTab) {
params['peopleTab'] = nextProps.peopleTab ;
}
if (Object.keys(params).length > 0) {
await this.props.fetchUnfollowedPeople(params);
}
}
Let's say I have the following component:
class TagsFilter extends React.Component {
static propTypes = {
tags: React.PropTypes.arrayOf(React.PropTypes.string),
onChange: React.PropTypes.func
}
onSelectTag = (tag)=>{
this.props.onChange(this.props.tags.concat([tag]))
}
render () {
return (
<div className='tags-filter'>
<SelectedTags tags={this.props.tags}/>
<TagsSelector tags={this.props.tags} onSelect={this.onSelectTag}/>
</div>
)
}
}
Now, I know that when I call the parent with the new tags it's going to trigger a chain reaction, hit the store, update the list filter there, and cascade the new changes until it comes back again as new props to this component.
The issue is that when it hits the store it triggers server queries and other components updates that make the app stutter for a second, and the update of the TagsFilter component feels sluggish.
So I came up with doing the following:
class TagsFilter extends React.Component {
static propTypes = {
tags: React.PropTypes.arrayOf(React.PropTypes.string),
onChange: React.PropTypes.func
}
componentWillMount () { this.componentWillReceiveProps(this.props) }
componentWillReceiveProps (np) {
this.setState({tags: np})
}
onSelectTag = (tag)=>{
let newTags = this.props.tags.concat([tag])
this.setState({tags: newTags})
setTimeout(()=>{
this.props.onChange({tags: newTags})
}, 50)
}
render () {
return (
<div className='tags-filter'>
<SelectedTags tags={this.state.tags}/>
<TagsSelector tags={this.state.tags} onSelect={this.onSelectTag}/>
</div>
)
}
}
Now, this definitively feels snappier. But I'm not sure if it's the appropriate way to handle these situations. Any insight is appreciated.
Do a comparison in componentWillReceiveProps and only setState if different. You don't need componentWillMount. Here is an example, this is the standard pattern for input.
import React from 'react';
class JInputRender extends React.Component {
render() {
let inputSty = this.props.input.style ? this.props.input.style : {color: 'red'};
let textValue = this.state.textValue;
let colorValue = this.props.input.colorValue ? this.props.input.colorValue : '#1A3212';
let checkedValue = (this.props.input.checkedValue != null) ? this.props.input.checkedValue : false;
let numberValue = this.props.input.numberValue ? this.props.input.numberValue : 0;
let radioValue = this.props.input.radioValue ? this.props.input.radioValue : '';
let radioChecked = (this.props.input.radioChecked != null) ? this.props.input.radioChecked : false;
let min = this.props.input.min ? this.props.input.min : 0;
let max = this.props.input.max ? this.props.input.max : 100;
let step = this.props.input.step ? this.props.input.step : 1;
let inputType = this.props.input.type ? this.props.input.type : 'text';
let returnRadio = (
<input
ref="inputRef"
type={inputType}
style={inputSty}
checked={radioChecked}
value={radioValue}
onChange={this.handleValueChange} />
)
let returnChecked = (
<input
ref="inputRef"
type={inputType}
style={inputSty}
checked={checkedValue}
onChange={this.handleCheckedChange} />
)
let returnColor = (
<input
type={inputType}
ref="inputRef"
style={inputSty}
value={colorValue}
onChange={this.handleValueChange} />
)
let returnNumber = (
<input
type={inputType}
ref="inputRef"
style={inputSty}
value={numberValue}
min={min} max={max} step={step}
onChange={this.handleValueChange} />
)
let returnText = (
<input
type={inputType}
ref="inputRef"
style={inputSty}
value={textValue}
onChange={this.handleTextValueChange} />
)
let returnIt = {};
switch (inputType) {
case 'checkbox': returnIt = returnChecked; break;
case 'radio': returnIt = returnRadio; break;
case 'color': returnIt = returnColor; break;
case 'number':
case 'range': returnIt = returnNumber; break;
default: returnIt = returnText; break;
}
return (returnIt);
}
}
export default class JInput extends JInputRender {
constructor() {
super();
this.state = {textValue: ''};
}
componentDidMount = () => {
if (this.props.input.textValue) this.setState({textValue: this.props.input.textValue});
if (this.props.input.focus) this.refs.inputRef.focus();
}
componentWillReceiveProps = (nextProps) => {
if (nextProps.input.textValue && (this.state.textValue != nextProps.input.textValue))
{this.setState({textValue: nextProps.input.textValue});}
}
handleCheckedChange = (event) => { this.props.handleChange(this.props.input.name, event.target.checked); }
handleTextValueChange = (event) => {
let newValue = event.target.value;
this.setState({textValue: newValue});
this.props.handleChange(this.props.input.name, newValue);
}
handleValueChange = (event) => { this.props.handleChange(this.props.input.name, event.target.value); }
}