Related
I'm new here so please forgive me if my write isn't good or the code section... still trying to figure it out.
so I've been trying a lot of forms in order to achieve the result that I want to but with no success...
Basically the app is getting a salary (amount) and there is the total sum in the account (currentSalar) both of them are from type string and not number... I need to find a way to rest the salary from the total sum and obviously update it.
Here are some screenshots If anyone could help me please.
**/index.js**
import mutations from './mutations.js';
import actions from './actions.js';
import getters from './getters.js';
export default {
namespaced: true,
state() {
return {
registers: [
{
id: 'user1',
currentSalar: '4,400' ,
saves: '12,000',
currency: '₪',
expense: [{salary: '3,000', category: 'Bar:'}]
},
{
id: 'user2',
currentSalar: '100,000',
saves: '3,000,000',
currency: '$',
expense: []
}
]
}
},
mutations,
actions,
getters
}
``````````````````````
``````````````````````
**/mutations.js**
export default {
addRegister(state, payload) {
const registerToUpdate = state.registers.find(
(register) => register.id === payload.id
);
const expenses = registerToUpdate.expense; //////* find((expense) => expense.salary === payload.salary) */
const salary = +expenses.salary;
let current = +registerToUpdate.currentSalar;
const newCurrnt = current -= salary; // should calculate a new value (UPDATE) for the currentSalar by resting the salary
console.log(newCurrnt);
expenses.unshift(payload);
},
};
```````````````````
**/actions.js**
export default {
addRegister(context, data) {
const RegisterData = {
id: context.rootGetters.userId,
category: data.category,
salary: data.salary
}
context.commit('addRegister', RegisterData);
}
}
```````````````````````
**/component**
<template>
<div class="container">
<h1>Add expense</h1>
<form #submit.prevent="ReturnCat">
<section>
<label for="salary">Amount: </label>
<input type="number" id="salary" v-model.number="salary"/>
</section>
<section>
<label for="category">Category: </label>
<select id="category" v-model.trim="category">
<option value="Bar: " id="bar">
Bar & Restaurants
</option>
<option value="Shopping: " id="shopping">Shopping</option>
<option value="Needs: " id="needs">Needs</option>
</select>
</section>
<base-button class="btn">ADD</base-button>
<h4>Add entery insted</h4>
</form>
</div>
</template>
<script>
export default {
props: ["id"],
data() {
return {
salary: null,
category: '',
};
},
computed: {
expensePath() {
return this.$router.replace("/register/" + this.id);
},
},
methods: {
ReturnCat() {
const form = {
salary: this.salary,
category: this.category
}
this.$store.dispatch('register/addRegister', form);
console.log(this.salary, this.category);
},
}
};
</script>
<style scoped>
#salary {
margin: 1rem auto auto 1rem;
height: 1.5rem;
width: 11rem;
}
select,
option {
margin: 1rem auto 2rem 1rem;
height: 1.5rem;
width: 11rem;
}
.btn {
margin: 0 auto;
width: 11rem;
}
</style>
`
JavaScript doesn't understand numbers as strings.
When you want to do arithmetic operations (as I guess you do, since you're developing an app having to do with accounting), you must work with numbers:
registers: [
{
id: "user1",
currentSalar: 4400,
saves: 12000,
currency: "₪",
expense: [{ salary: 3000, category: "Bar:" }],
},
{
id: "user2",
currentSalar: 100000,
saves: 3000000,
currency: "$",
expense: []
}
]
Even when you're getting this data from a poorly written API which returns numbers as strings, the first thing you want to do is to turn them into numbers, before placing them in the store.
And when you try to cast a string to number (by appending + to it), if the string contains thousand separators as commas (or anything that's not a digit, a dot, or a scientific number notation - e.g: '-3e6'), you're going to get back NaN.
See it here:
const num = '1,000,000'
console.log( +num ) // NaN
// strip commas from it:
console.log( +num.replaceAll(',', '') ) // 1000000
And when you want to display the numbers used in the store to the user, if you want to put thousand and decimal separators on them, use Intl.NumberFormat.
solved:)
here is the code:
// index.js
import mutations from './mutations.js';
import actions from './actions.js';
import getters from './getters.js';
export default {
namespaced: true,
state() {
return {
registers: [
{
id: 'user1',
currentSalar: 4400 ,
saves: 12000 ,
currency: '₪',
expense: []
},
{
id: 'user2',
currentSalar: 100000,
saves: 3000000,
currency: '$',
expense: []
}
]
}
},
mutations,
actions,
getters
}`
`
// mutations.js
export default {
addRegister(state, payload) {
const registerToUpdate = state.registers.find(
(register) => register.id === payload.id
);
let current = registerToUpdate.currentSalar;
const expenses = registerToUpdate.expense;
current -= payload.salary;
expenses.unshift(payload);
registerToUpdate.currentSalar = registerToUpdate.currentSalar -
payload.salary;
},
};
I am quite new to VueJs so forgive me for this question but I am very frustrated.
I have an app pre-configured with vue router and the views I want working fine, but whenever I try to display a card component with properties my site doesnt crash it just goes blank.
// here is my app.vue
[1]: https://i.stack.imgur.com/m7Zl3.png
// here is my router index[2]
[2]: https://i.stack.imgur.com/QGlwQ.png
// here is the component & props ...view farther down
<template>
<div>
<slot>
<b-card
bg-variant="primary"
text-variant="white"
header="Primary"
class="text-center"
>
<section class="header">
<h1>{{ id }} + {{ ipAddress }}</h1>
</section>
<section class="body">
<b-card-text>
<p1> {{ inputs }} + "," + {{ outputs }}</p1>
<p2> {{ publishers }} + "," + {{ subscribers }} </p2>
<p3> {{ frequency }} + "," + {{ messageType }} </p3>>
</b-card-text>
</section>
</b-card>
</slot>
</div>
</template>
<script>
// use camelCase // [ iD, ipAddress, inputs, outputs, publishers,
// subscribers, messageType, isActive, frequency]
export default {
name: nodeCard,
props: {
iD: {
type: Number,
required: true,
},
ipAddress: {
type: Number,
required: true,
// validator: function (value) {
// return value.length >= 8 || value.length <= 12;
// },
},
inputs: {
type: String,
required: false,
default: none,
},
outputs: {
type: String,
required: false,
default: none,
},
publishers: {
type: Object,
required: false,
default: none,
},
subscribers: {
type: Object,
required: false,
default: none,
},
messageType: {
type: Object,
required: true,
},
isActive: {
type: Boolean,
required: false,
default: none,
},
frequency: {
type: String,
required: false,
default: none,
},
},
data() {},
};
</script>
here is the view page
<template>
<div id="card">
<b-card-group deck>
<nodeCard
v-for="topics in NNV"
:key="topics.iD"
:name="topics.ipAddress"
:inputs="topics.inputs"
:outputs="topics.outputs"
:publishers="topics.publishers"
:subscribers="topics.subscribers"
:frequency="topics.frequency"
:messageType="topics.messageType"
>
</nodeCard>
</b-card-group>
</div>
</template>
<script>
import nodeCard from "../components/NodeCard.vue";
export default {
name: NNV,
components: nodeCard,
data() {
return {
topics: [
{
iD: 123,
ipAddress: 1234,
inputs: "Core Vue basics you have to know",
outputs: "Vue",
publishers: "polog",
subscribers: "Migos",
frequency: "offset",
messageType: "quavo",
},
],
};
},
};
</script>
As you are new to Vuejs so it is obvious to make such mistakes, so don't worry.
Replace your code with the below one and try to understand that which things were causing the issue, all were related to spelling mistakes and the difference between variable and String.
For example-
component's name is a String, we should not write it without the single or double comma, like below-
export default { name: "component_name" }
not
export default { name: component_name }
Whenever import any component, write it like this-
components: {component_name}
not
components:component_name
When pass props, use the same name on both sides, i.e if the prop key is "iD", it will be used in another component as iD only, not "id"
Correct code is-
View page-
<template>
<div id="card">
<b-card-group deck>
<nodeCard
v-for="(topic, index) in topics"
:key="index"
:id="topic.iD"
:ipAddress="topic.ipAddress"
:inputs="topic.inputs"
:outputs="topic.outputs"
:publishers="topic.publishers"
:subscribers="topic.subscribers"
:frequency="topic.frequency"
:messageType="topic.messageType"
>
</nodeCard>
</b-card-group>
</div>
</template>
<script>
import nodeCard from "#/components/NodeCard.vue";
export default {
name: "NNV",
components: { nodeCard },
data() {
return {
topics: [
{
iD: 123,
ipAddress: 1234,
inputs: "Core Vue basics you have to know",
outputs: "Vue",
publishers: "polog",
subscribers: "Migos",
frequency: "offset",
messageType: "quavo",
},
],
};
},
};
</script>
NodeCard.vue-
<template>
<div>
<slot>
<b-card
bg-variant="primary"
text-variant="white"
header="Primary"
class="text-center"
>
<section class="header">
<h1>{{ id }} + {{ ipAddress }}</h1>
</section>
<section class="body">
<b-card-text>
<p1> {{ inputs }} + "," + {{ outputs }}</p1>
<p2> {{ publishers }} + "," + {{ subscribers }} </p2>
<p3> {{ frequency }} + "," + {{ messageType }} </p3>>
</b-card-text>
</section>
</b-card>
</slot>
</div>
</template>
<script>
// use camelCase // [ iD, ipAddress, inputs, outputs, publishers,
// subscribers, messageType, isActive, frequency]
export default {
name: "NodeCard",
props: {
id: {
type: Number,
required: true,
},
ipAddress: {
type: Number,
required: true,
// validator: function (value) {
// return value.length >= 8 || value.length <= 12;
// },
},
inputs: {
type: String,
default: null,
},
outputs: {
type: String,
default: null,
},
publishers: {
type: String,
default: null,
},
subscribers: {
type: String,
default: null,
},
messageType: {
type: String,
required: true,
},
isActive: {
type: Boolean,
default: null,
},
frequency: {
type: String,
default: null,
},
},
};
</script>
I wish to filter some text on my component:
<p :v-text="'hello' | prefix"></p>
My filter looks like:
prefix(string) {
return inputPrefix + string;
}
inputPrefix is just a prop....
<my-component input-prefix="some-prefix_" ....
But I get the error inputPrefix is not defined
How can I use a prop in a filter?
In filters you can not access this. Use the filter in this way,
filters:{
prefix: function(inputPrefix, string){
console.log(inputPrefix);
return inputPrefix + string;
}
and access it like:
<p :v-text="'hello' | inputPrefix"></p>
or a computed property.
You can read the docs or refer this for explanation.
Filters should be pure functions which gives output based upon input. In your case you can use a computed property.
Vue.component('mine', {
props: ['prefix'],
data() {
return {
value: ' component'
}
},
template: '<p>{{formattedString}}</p>',
computed: {
formattedString() {
return this.prefix + this.value
}
}
})
new Vue({
el: "#app",
components: ['mine'],
template: '<mine prefix="new" />'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11"></script>
<div id="app"></div>
If you still wish to use a filter for your scenario, please find below
Vue.component('mine', {
props: ['prefix'],
data() {
return {
value: ' component'
}
},
template: '<p>{{value | format(prefix)}}</p>',
filters: {
format(value, prefix) {
return prefix + value
}
}
})
new Vue({
el: "#app",
components: ['mine'],
template: '<mine prefix="new" />'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11"></script>
<div id="app"></div>
In VueJS, is there a way to interpolate a string within a string, either in the template or in the script? For example, I want the following to display 1 + 1 = 2 instead of 1 + 1 = {{ 1 + 1 }}.
<template>
{{ myVar }}
</template>
<script>
export default {
data() {
"myVar": "1 + 1 = {{ 1 + 1 }}"
}
}
</script>
Edit: to better illustrate why I would need this, here's what my actual data looks like:
section: 0,
sections: [
{
inputs: {
user: {
first_name: {
label: "First Name",
type: "text",
val: ""
},
...
},
...
},
questions: [
...
"Nice to meet you, {{ this.section.inputs.user.first_name.val }}. Are you ...",
...
]
},
...
],
this.section.inputs.user.first_name.val will be defined by the user. While I could rebuild the question properties as computed properties, I would rather keep the existing data structure in tact.
I found the solution I was looking for from https://forum.vuejs.org/t/evaluate-string-as-vuejs-on-vuejs2-x/20392/2, which provides a working example on JsFiddle: https://jsfiddle.net/cpfarher/97tLLq07/3/
<template>
<div id="vue">
<div>
{{parse(string)}}
</div>
</div>
</template>
<script>
new Vue({
el:'#vue',
data:{
greeting:'Hello',
name:'Vue',
string:'{{greeting+1}} {{name}}! {{1 + 1}}'
},
methods:{
evalInContext(string){
try{
return eval('this.'+string)
} catch(error) {
try {
return eval(string)
} catch(errorWithoutThis) {
console.warn('Error en script: ' + string, errorWithoutThis)
return null
}
}
},
parse(string){
return string.replace(/{{.*?}}/g, match => {
var expression = match.slice(2, -2)
return this.evalInContext(expression)
})
}
}
})
</script>
<textarea name="" id="" cols="30" rows="10" v-model="$store.state.user.giftMessage | truncate 150"></textarea>
I tried creating a custom filter :
filters: {
truncate(text, stop, clamp) {
return text.slice(0, stop) + (stop < text.length ? clamp || '...' : '')
}
}
but that didn't broke the build when I put it on the v-model for the input...
Any advice?
This is one of those cases, where you really want to use a component.
Here is an example component that renders a textarea and limits the amount of text.
Please note: this is not a production ready, handle all the corner cases component. It is intended as an example.
Vue.component("limited-textarea", {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
template: `
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
`,
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
if (evt.keyCode >= 48 && evt.keyCode <= 90) {
evt.preventDefault()
return
}
}
}
}
})
This component implements v-model and only emits a change to the data if the length of the text is less than the specified max. It does this by listening to keydown and preventing the default action (typing a character) if the length of the text is equal to or more than the allowed max.
console.clear()
Vue.component("limited-textarea", {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
template: `
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
`,
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
if (evt.keyCode >= 48 && evt.keyCode <= 90) {
evt.preventDefault()
return
}
}
}
}
})
new Vue({
el: "#app",
data:{
text: ""
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<limited-textarea v-model="text"
:max="10"
cols="30"
rows="10">
</limited-textarea>
</div>
Another issue with the code in the question is Vuex will not allow you set a state value directly; you have to do it through a mutation. That said, there should be a Vuex mutation that accepts the new value and sets it, and the code should commit the mutation.
mutations: {
setGiftMessage(state, message) {
state.user.giftMessage = message
}
}
And in your Vue:
computed:{
giftMessage:{
get(){return this.$store.state.user.giftMessage},
set(v) {this.$store.commit("setGiftMessage", v)}
}
}
Technically the code should be using a getter to get the user (and it's giftMessage), but this should work. In the template you would use:
<limited-textarea cols="30" rows="10" v-model="giftMessage"></limited-textarea>
Here is a complete example using Vuex.
console.clear()
const store = new Vuex.Store({
state:{
user:{
giftMessage: "test"
}
},
getters:{
giftMessage(state){
return state.user.giftMessage
}
},
mutations:{
setGiftMessage(state, message){
state.user.giftMessage = message
}
}
})
Vue.component("limited-textarea", {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
template: `
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
`,
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
if (evt.keyCode >= 48 && evt.keyCode <= 90) {
evt.preventDefault()
return
}
}
}
}
})
new Vue({
el: "#app",
store,
computed:{
giftMessage:{
get(){ return this.$store.getters.giftMessage},
set(v){ this.$store.commit("setGiftMessage", v)}
}
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.0/vuex.js"></script>
<div id="app">
<limited-textarea v-model="giftMessage"
:max="10"
cols="30"
rows="10">
</limited-textarea>
Message: {{giftMessage}}
</div>
Sorry to break in. Was looking for a solution. Looked at all of them.
For me they look too complicated. I'm always looking for symplicity.
Therefor I like the answer of #Даниил Пронин. But it has the by #J. Rambo noted potential problem.
To stay as close as possible to the native html textelement. The solution I came up with is:
Vue Template
<textarea v-model="value" #input="assertMaxChars()">
JavaScript
let app = new Vue({
el: '#app',
data: {
value: 'Vue is working!',
maxLengthInCars: 25
},
methods: {
assertMaxChars: function () {
if (this.value.length >= this.maxLengthInCars) {
this.value = this.value.substring(0,this.maxLengthInCars);
}
}
}
})
Here is a REPL link: https://repl.it/#tsboh/LimitedCharsInTextarea
The upside I see is:
the element is as close as possible to the native element
simple code
textarea keeps focus
delete still works
works with pasting text as well
Anyway
happy coding
While I agree with the selected answer. You can also easily prevent the length using a keydown event handler.
Vue Template
<input type="text" #keydown="limit( $event, 'myModel', 3)" v-model="myModel" />
JavaScript
export default {
name: 'SomeComponent',
data () {
return {
myModel: ''
};
},
methods: {
limit( event, dataProp, limit ) {
if ( this[dataProp].length >= limit ) {
event.preventDefault();
}
}
}
}
Doing this way, you can also use regular expression to event prevent the type of keys accepted. For instance, if you only wanted to accept numeric values you can do the following.
methods: {
numeric( event, dataProp, limit ) {
if ( !/[0-9]/.test( event.key ) ) {
event.preventDefault();
}
}
}
I have improved on #J Ws answer. The resulting code does not have to define how to react on which keypress, which is why it can be used with any character in contrast to the accepted answer. It only cares about the string-length of the result. It also can handle Copy-Paste-actions and cuts overlong pastes to size:
Vue.component("limitedTextarea", {
props: {
value: {
type: String,
default: ""
},
max: {
type: Number,
default: 25
}
},
computed: {
internalValue: {
get: function () {
return this.value;
},
set: function (aModifiedValue) {
this.$emit("input", aModifiedValue.substring(0, this.max));
}
}
},
template: '<textarea v-model="internalValue" #keydown="$forceUpdate()" #paste="$forceUpdate()"></textarea>'
});
The magic lies in the #keydown and #paste-events, which force an update. As the value is already cut to size correctly, it assures that the internalValue is acting accordingly.
If you also want to protect the value from unchecked script-changes, you can add the following watcher:
watch: {
value: function(aOldValue){
if(this.value.length > this.max){
this.$emit("input", this.value.substring(0, this.max));
}
}
}
I just found a problem with this easy solution: If you set the cursor somewhere in the middle and type, transgressing the maximum, the last character is removed and the cursor set to the end of the text. So there is still some room for improvement...
My custom directive version. Simple to use.
<textarea v-model="input.textarea" v-max-length="10"></textarea>
Vue.directive('maxlength',{
bind: function(el, binding, vnode) {
el.dataset.maxLength = Number(binding.value);
var handler = function(e) {
if (e.target.value.length > el.dataset.maxLength) {
e.target.value = e.target.value.substring(0, el.dataset.maxLength);
var event = new Event('input', {
'bubbles': true,
'cancelable': true
});
this.dispatchEvent(event);
return;
}
};
el.addEventListener('input', handler);
},
update: function(el, binding, vnode) {
el.dataset.maxLength = Number(binding.value);
}
})
Event() has browser compatibility issue.
Unfortunately for me, keydown approach seems not working good with CJK.
there can be side effects since this method fires input event double time.
Best way is to use watch to string length and set old value if string is longer than you want:
watch: {
'inputModel': function(val, oldVal) {
if (val.length > 250) {
this.inputModel = oldVal
}
},
},
Simply use maxlength attribute like this:
<textarea v-model="value" maxlength="50" />
I used your code and broke it out into a .Vue component, thanks!
<template>
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
</template>
<script>
export default {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
evt.preventDefault();
console.log('keydown');
return
}
}
}
}
I did this using tailwind 1.9.6 and vue 2:
<div class="relative py-4">
<textarea
v-model="comment"
class="w-full border border-gray-400 h-16 bg-gray-300 p-2 rounded-lg text-xs"
placeholder="Write a comment"
:maxlength="100"
/>
<span class="absolute bottom-0 right-0 text-xs">
{{ comment.length }}/100
</span>
</div>
// script
data() {
return {
comment: ''
}
}