vue.js: HTML element show-if not toggling changes - javascript

I'm initially rendering some objects from an API call to my database, they are serialized and look like this initially:
<h3>Messages on Database</h3>
<p v-if="messages.length ===0">No Messages</p>
<div class="msg" v-for="(msg, index) in messages" :key="index">
<p class="msg-index">[{{index}}]</p>
<p class="msg-subject" v-html="msg.subject" v-if="!msg.editing"></p>
<p><input type="text" v-model="msg.subject" v-if="msg.editing" ></p>
<p>{{msg.editing}}</p>
<p class="msg-body" v-html="msg.body" v-show="!messages[index].editing"></p>
<p><input type="text" v-model="msg.body" v-show="messages[index].editing" ></p>
<input type="submit" #click="deleteMsg(msg.pk)" value="Delete" />
<input type="submit" #click="EditMsg(index)" value="Edit" />
<input type="submit" #click="updateMsg(msg.pk)" value="Update" />
</div>
</div>
</template>
<script>
export default {
name: "Messages",
data() {
return {
subject: "",
msgBody: "",
messages: [],
};
},
each message looks like this:
notice that body, pk and subject are the Django model fields. Each item in the array represents a database object.
What I want to do using vue.js, is allow users to edit each item. If the user clicks the edit button for an item, I want to transform its element from p to input, and submit that to the database.
In order to allow editing of individual items, I need an editing field in each item in the array, so I'm doing this in my mounted() property:
mounted() {
this.fetchMessages();
},
methods: {
fetchMessages() {
this.$backend.$fetchMessages().then(responseData => {
this.messages = responseData;
this.messages.forEach(function (value) {
value['editing'] = false;
});
console.log(this.messages);
});
},
Now, when I load up the array in my console, I see this:
So I assumed that now, when the user clicks the Edit button, EditMsg is called, and the fields will transform according to the v-if/v-show directives:
EditMsg(msgIdx) {
this.messages[msgIdx].editing = true;
console.log(this.messages);
},
But that's not happening. What is actually happening is this: the editing flag for the item is changed to true in the console/vue-developer-tools window, but nothing changes in the HTML.
What am I missing?
Full code:
<template>
<div class="hello">
<img src='#/assets/logo-django.png' style="width: 250px" />
<p>The data below is added/removed from the Postgres Database using Django's ORM and Restframork.</p>
<br/>
<p>Subject</p>
<input type="text" placeholder="Hello" v-model="subject">
<p>Message</p>
<input type="text" placeholder="From the other side" v-model="msgBody">
<br><br>
<input type="submit" value="Add" #click="postMessage" :disabled="!subject || !msgBody">
<hr/>
<h3>Messages on Database</h3>
<p v-if="messages.length ===0">No Messages</p>
<div class="msg" v-for="(msg, index) in messages" :key="index">
<p class="msg-index">[{{index}}]</p>
<p class="msg-subject" v-html="msg.subject" v-if="!msg.editing"></p>
<p><input type="text" v-model="msg.subject" v-if="msg.editing" ></p>
<p>{{msg.editing}}</p>
<p class="msg-body" v-html="msg.body" v-show="!messages[index].editing"></p>
<p><input type="text" v-model="msg.body" v-show="messages[index].editing" ></p>
<input type="submit" #click="deleteMsg(msg.pk)" value="Delete" />
<input type="submit" #click="EditMsg(index)" value="Edit" />
<input type="submit" #click="updateMsg(msg.pk)" value="Update" />
</div>
</div>
</template>
<script>
export default {
name: "Messages",
data() {
return {
subject: "",
msgBody: "",
messages: [],
};
},
mounted() {
this.fetchMessages();
},
methods: {
fetchMessages() {
this.$backend.$fetchMessages().then(responseData => {
this.messages = responseData;
this.messages.forEach(function (value) {
value['editing'] = false;
});
console.log(this.messages);
});
},
postMessage() {
const payload = { subject: this.subject, body: this.msgBody };
this.$backend.$postMessage(payload).then(() => {
this.msgBody = "";
this.subject = "";
this.fetchMessages();
});
},
deleteMsg(msgId) {
this.$backend.$deleteMessage(msgId).then(() => {
this.messages = this.messages.filter(m => m.pk !== msgId);
this.fetchMessages();
});
},
EditMsg(msgIdx) {
this.messages[msgIdx].editing = true;
console.log(this.messages);
},
updateMsg(msgId) {
console.log(this.subject, this.msgBody);
const payload = { subject: this.subject, body: this.msgBody };
this.$backend.$putMessage(msgId, payload).then(() => {
this.fetchMessages();
}
)
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
hr {
max-width: 65%;
}
.msg {
margin: 0 auto;
max-width: 30%;
text-align: left;
border-bottom: 1px solid #ccc;
padding: 1rem;
}
.msg-index {
color: #ccc;
font-size: 0.8rem;
/* margin-bottom: 0; */
}
img {
width: 250px;
padding-top: 50px;
padding-bottom: 50px;
}
</style>

According to Vue internals:
Vue observes data by converting properties with Object.defineProperty. However, in ECMAScript 5 there is no way to detect when a new property is added to an Object, or when a property is deleted from an Object.
So, when you bind your response data to this.messages, any mutation to array properties is not considered reactive anymore by Vue.
Instead, if you enrich responseData properties before binding it to the Vue data properties, all the array stays reactive. I mean like this:
fetchMessages() {
this.$backend.$fetchMessages().then(responseData => {
let editableMessages = responseData;
editableMessages.forEach(function (value) {
value['editing'] = false;
});
this.messages = editableMessages;
});
}
Here there is a small example based on your domain.

Related

How to append input elements dynamically in vue

I'm trying to append new input fields based on a condition, I will describe the workflow to help you understand
First stage is to press this button to implement 2 functions(1 is to move to other fieldset in the staged form, second function is to append the inputs:
<input type="button" name="secondBtn" class="next action-button" value="Next" id="secondBtn" #click="nextPlusappend"/>
nextPlusappend:
nextPlusappend() {
this.isNextClicked();
this.appendFields();
}
appendFields:
//this.platform initllized as 'one' so the condition is true.
if(this.platform === 'one'){
this.inputFields.push({
Username: '',
Password: '',
AffiliateID: '',
CI: '',
GI: '',
})
}
And I want to append all the fields from the function to this fieldset:
<div v-if="currentPage === 2">
<fieldset id="fieldset3" v-for="(param, index) in inputFields" :key="index">
<h2 class="fs-title">API Credentials</h2>
<h3 class="fs-subtitle">Step 3- Add any parameter for the integration</h3>
<input v-model="param.Username" type="text" name="`inputFields[${index}[Username]]`" placeholder="userName">
<input type="button" name="previous" class="previous action-button" value="Previous" #click="isPreviousClicked"/>
<input type="submit" name="submit" class="submit action-button" value="Create a Ticket" id="excel"/>
</fieldset>
</div>
How can I append this without hard code all the input fields as I did here:?
<input v-model="param.Username" type="text" name="`inputFields[${index}[Username]]`" placeholder="userName">
This is designated to be dynamic, what do i mean?
I mean that if the this.platform is equal to "one" there will be a unique fields, and if this.platform equal to "two" for example there will be other unique fields.
Don't think like "pushing a form field", rather think like "adding a new item to the dataset" (and of course, its displayed UI is a form field).
Let me give an example:
Vue.component("FormField", {
props: ["label", "value"],
computed: {
val: {
get() {
return this.value
},
set(val) {
this.$emit("update:value", val)
}
},
},
methods: {
handleClickAdd() {
this.$emit("click-add-field")
}
},
template: `
<div>
<label>
{{ label }}: <input type="text" v-model="val" />
</label>
<button
#click="handleClickAdd"
>
+ ADD
</button>
</div>
`,
})
new Vue({
el: "#app",
data() {
return {
formFields: [{
label: "Field 1",
value: null,
}],
}
},
methods: {
handleClickAddField() {
const item = {
label: `Field ${ this.formFields.length + 1 }`,
value: null,
}
this.formFields = [...this.formFields, item]
},
},
template: `
<div
class="container"
>
<div
class="col"
>
<h4>FIELDS:</h4>
<hr>
<form-field
v-for="(field, i) in formFields"
:key="i"
:label="field.label"
:value.sync="field.value"
#click-add-field="handleClickAddField"
/>
</div>
<div
class="col"
>
<h4>FIELD VALUES:</h4>
<hr>
<div
v-for="(field, i) in formFields"
:key="i"
>{{ field.label }}: {{ field.value }}</div>
</div>
</div>
`,
})
.container {
display: flex;
}
.col {
padding: 0 8px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
You can see, that on ADD I just added a new item in the formFields - the values are bound in the template to a child-component, that handles the actual representation of the fields.
On the right side of the snippet, you can see another benefit of decoupling data from UI: I created another representation of the same data source - that immediately reacts to any changes!

Make diffrent V-MODEL on every index that i generate

How can I have different V-MODEL on every object that i generate
I am trying to make an sample cupcake website that can generate multiple forms in one submit.
But when I generate 2 field, the inputs of the 2 generated field bound by each other inputs.
This is the code I am trying to generate:
<template>
<div>
<button #click="cupcakes.push(def)">Add Cup Cake</button>
<div v-for="(cupcake, index) in cupcakes" :key="index">
<input type="text" v-model="cupcakes[index].name">
<input type="text" v-model="cupcakes[index].description">
<input type="text" v-model="cupcakes[index].type">
<input type="text" v-model="cupcakes[index].prize">
<input type="text" v-model="cupcakes[index].color">
</div>
<button #click="onSubmit">Create Cupcate</button>
</div>
</template>
<script>
export default {
data() {
return {
cupcakes: [],
def: {
name: '',
description: 'Originals',
type: 'small',
prize: 500,
color: 'color'
}
}
},
methods: {
onSubmit() {
console.log(this.cupcakes);
}
}
}
</script>
I tried to do other things but it doesn't work.
How can I dis bind the 2 field and when I submit it will take the inputs that I type or input.
You are pushing n times the same objet (def) into your cupcakes Array. def is a reference to an object. So when you update cupcakes[n], you are just updating the def values.
What you need to do is send a copy of that object into the cupcakes object:
<button #click="cupcakes.push(JSON.parse(JSON.stringify(def)))">Add Cup Cake</button>
I think a better pattern would be to make a method that returns you a new cupcake:
<template>
<div>
<button #click="cupcakes.push(getNewCupcake())">Add Cup Cake</button>
<div v-for="(cupcake, index) in cupcakes" :key="index">
<input type="text" v-model="cupcakes[index].name">
<input type="text" v-model="cupcakes[index].description">
<input type="text" v-model="cupcakes[index].type">
<input type="text" v-model="cupcakes[index].prize">
<input type="text" v-model="cupcakes[index].color">
</div>
<button #click="onSubmit">Create Cupcate</button>
</div>
</template>
<script>
export default {
data() {
return {
cupcakes: []
};
},
methods: {
onSubmit() {
console.log(this.cupcakes);
},
getNewCupcake() {
return {
name: "",
description: "Originals",
type: "small",
prize: 500,
color: "color"
}
}
}
};
</script>

How to use new-bind with addeventListeners in ES6 - Javascript?

I am trying to add addEventlistener to DOM element which has #addButton id.I am using new binding method in ES6 but this does not work. How to call add function?
class ToDoApp {
constructor (settings) {
if (!settings) {
throw 'Todo App requires settings object';
}
this.addButtonHandler = document.querySelector(settings.addButtonSelector);
this.addButtonHandler.addEventListener('click', this.add);
}
add = () => {
console.log('heello');
}
}
const myTodo = new ToDoApp({
inputSelector: '#input',
addButtonSelector: '#addButton',
deleteButtonSelector: '#delete',
listContainerSelector: '#list'
});
HTML
<div id="container">
<div id="imputArea">
<form action="index.html" method="POST">
<input type="text" id="input" />
<input type="button" id="addButton" value="Add" />
</form>
</div>
<div id="listArea">
<ul id="list">
<li>
<input type="checkbox" />
<p id="task">Task 1</p>
<input type="button" id="delete" value="X" />
</li>
</ul>
</div>
</div>
I guess you are getting this error due to the fact that element would not exist when you create an instance of newTodo. you might want to put null check for the button you are trying to add event listener to.
class ToDoApp {
constructor(settings) {
if (!settings) {
throw 'Todo App requires settings object';
}
this.addButtonHandler = document.querySelector(settings.addButtonSelector);
if (this.addButtonHandler) {
this.addButtonHandler.addEventListener('click', this.add);
} else {
throw "Element you want to add event listener to doesn't exist";
}
}
add = () => {
console.log('heello');
}
}
const myTodo = new ToDoApp({
inputSelector: '#input',
addButtonSelector: '#addButton',
deleteButtonSelector: '#delete',
listContainerSelector: '#list'
});

How can I add the value of a button to a textarea when clicking the button?

I have a textarea, in which user can write a comment. To make their life easy I also have set of buttons which basically have predefined value. These are quick comments button that can be clicked and it should add the value of button to the textarea. Its almost like StackOverflow's tags search box where you can type to add the tags or you can select the suggested tags by SO, that are outside the search box on the bottom.
If I am able to render/added the comments value on text area with the existing text in the text area that would solve my problem i think. thank you
Picture of what the UI component looks like :
The savePatientComment() is how I save the value of the textArea.
savePatientComment( { target: { name, value } }, data, index ){
this.setState({
patientInfo: this.state.patientInfo.map((patient, i) => {
if (i === index) {
return { ...patient, [name]: value };
}
return patient;
}),
});
}
Patient Row component
<div>
<Button value="quick comments-1" onClick={//add the value to text area}>Quick comments</Button>
<Button value="quick comments-2" onClick={//add the value to text area}>Quick comments</Button>
<Button value="quick comments-3" onClick={//add the value to text area}>Quick comments</Button>
</div>
<textarea
className="patient-comment-textarea"
placeholder="type your comments here"
name="comment"
value={patient.comment}
onChange={(e) => savePatientComment(e, patient, index)} >
</textarea>
Errors: Use array of comments and iterate to display <button />. onClick update state for valueArr. Also, onChange and value property on for displaying comments data.
This is working solution.
class App extends React.Component {
state = {
valueArr: []
};
changeHandler = event => {
this.setState({ valueArr: [event.target.value] });
};
clickHandler = datum => {
this.setState(prevState => ({ valueArr: [...prevState.valueArr, datum] }));
};
render() {
const comments = ["working", "sleeping", "eating", "coding"];
return (
<div>
{comments.map(datum => (
<button
className="comment-btn"
onClick={() => this.clickHandler(datum)}
>
{datum}
</button>
))}
<br />
<textarea
rows="6"
cols="30"
value={this.state.valueArr}
onChange={event => this.changeHandler(event)}
placeholder="type your comment here"
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.comment-btn {
background-color: #fff;
border: solid 1px greenyellow;
height: 50px;
width: 100px;
margin: 20px;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
This should get you on the right track
<script>
function addNewComment(e){
var content = $('#comment').val();
content = content + '. ' + $(e).val();
}
</script>
<div>
<Button value="I'll call you later" onClick='addNewComment(this)'>Quick comments</Button>
<Button value="I'm in a meeting" onClick='addNewComment(this)'>Quick comments</Button>
<Button value="I need some water" onClick='addNewComment(this)'>Quick comments</Button>
</div>
<textarea
className="patient-comment-textarea"
id="comment"
placeholder="type your comments here"
name="comment"
value={patient.comment}
onChange={(e) => savePatientComment(e, patient, index)} >
</textarea>
This is my how I would do it.
<body>
<div>
<input type="button" name="" value="Hello"></input>
<input type="button" name="" value="Good Job"></input>
<input type="button" name="" value="Have a nice day"></input>
</div>
<!-- A form for sending/ saving data -->
<form class="" method="post">
<textarea rows="4" cols="50">this is you current text</textarea>
<input type="submit" name="" value="submit">
</form>
<script type="text/javascript">
const buttons = document.querySelector('div') // Selecting all the buttons
const form = document.querySelector('form textarea') // Selecting my textarea
buttons.addEventListener('click', e => {
// when a button is clicked add the value to the textarea
form.value += ' ' + e.target.value
console.log(form.value, e.target.value);
})
</script>
</body>
Hope it was Helpful ^_^

How can I display required html 5 in vue component?

My vue component like this :
<template>
<div>
...
<form class="form-horizontal" id="form-profile">
...
<input type="number" class="form-control" required>
...
<button type="submit" class="btn btn-primary" #click="submit">Submit</button>
...
</form>
...
</div>
</template>
<script>
export default {
...
methods: {
submit(e) {
e.preventDefault()
if (this.checkForm()) {
// do ajax here
}
},
checkForm() {
let field = true
$('#form-profile :required').each(function(i) {
if(this.checkValidity() == false)
field = false
})
return field
},
}
}
</script>
I using required html5 to validation
I using e.preventDefault() to prevent page redirects. Because I want to using ajax
My problem here is the required validation html5 not show if not filled. Maybe it because I using e.preventDefault()
How can I display the required html5?
In order to work as expected you have to set the v-on:submit method on the form tag, and have a button/input type "submit".
Also, notice the event modifier prevent on the #submit, it's a shorcut to not have to write e.preventDefault() on the method
new Vue({
el: '#app',
data() {
return {
myText: ''
}
},
methods: {
submitForm() {
alert('submited: ' + this.myText)
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.js"></script>
<div id="app">
<form #submit.prevent="submitForm">
<input type="text" v-model="myText" required />
<button type="submit">Submit</button>
</form>
</div>

Categories