I want to show only one input clicked (on vue.js) - javascript

I would like to show an input on the click, but being in a for loop I would like to show only the clicked one
<div v-for="(todo, n) in todos">
<i class="fas fa-minus ml-2"></i>
<li class="mt-2 todo">
{{ todo }}
</li>
<li class="button-container">
<button class="ml-1 btn btn-primary rounded-circle btn-sm" v-if="isHidden" v-on:click="isHidden = false"><i
class="THIS-CLICK"></i></button>
<input class="ml-5 border border-primary rounded" v-if="!isHidden" v-model="todo">
<button class="ml-1 btn btn-success rounded-circle btn-sm" v-if="!isHidden" v-on:click="isHidden = true"
#click="modifyTodo(n, todo)"><i class="far fa-save"></i></button>
</li>
</div>
I would like that on clicking on THIS-CLICK, only one input (that of the button clicked) is visible, but being in a for loop I don't know if it can be done

I would suggest to change the structure a bit in your app. You can clean up your code a lot by using v-if inside the button instead of two different buttons.
Also, moving as much javascript out from the markup as possible is a good practice.
I have an example below where this is done.
Regarding your question, you would have to add the key to each todo item.
new Vue({
el: "#app",
data() {
return {
todos: [{
name: 'wash hands',
isHidden: true
},
{
name: 'Stay home',
isHidden: true
}
],
};
},
methods: {
toggleTodo(n, todo) {
const hidden = todo.isHidden;
if (hidden) {
this.modifyTodo(n, todo);
todo.isHidden = false;
} else {
todo.isHidden = true;
}
},
modifyTodo(n, todo) {
//Some logic...
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div v-for="(todo, n) in todos">
<i class="fas fa-minus ml-2"></i>
<li class="mt-2 todo">
{{ todo.name }}
</li>
<li class="button-container">
<input class="ml-5 border border-primary rounded" v-if="!todo.isHidden" v-model="todo.name">
<button #click="toggleTodo(n, todo)">
<i v-if="todo.isHidden" class="THIS-CLICK">click-this</i>
<i v-else class="far fa-save">save</i>
</button>
</li>
</div>
</div>
If you cannot do this, you could go with adding a new key to data like: hiddenTodos that would be an array of ids/a unique identifier to the todo you selected to hide.
in the template, it would be something like this:
<button #click="hiddenTodos.push(todo)">
...
<div v-if="hiddenTodos.includes(todo)"

Related

How to add a 'Sort By' dropdown to sort a list in Vuejs?

I've got a pretty straight forward setup.
Trying to display a list of users with a search box at the top (for actively filtering the search results).
If I use just that the page works and displays fine.
I'm trying to add in an additional dropdown to pick an attribute to sort by (and hopefully add in another dropdown to indicate ascending/descending once I get the first dropdown working).
My current code (with a non-working version of the sort) looks like this:
<div id="app">
<section class="mb-3">
<div class="container">
<h2>Person Search</h2>
<h3>
<small class="text-muted">Filter people based on the name, location or job</small>
</h3>
<h3>
<small class="text-muted">
Examples: “Jane Doe”, “ABC Building”, or “Math”
</small>
</h3>
<input type="text" class="form-control" v-model="search_term" placeholder="Begin typing to filter by name, location or job...">
<!-- THIS WOULD CAUSE THE LIST TO FILTER ALPHABETICALLY BY SELECTED ATTRIBUTE -->
<select id="sortFilterSelect" name="sort_filter" v-model="sort_filter">
<option value="">Sort By...</option>
<option value="first_name">First Name</option>
<option value="last_name">Last Name</option>
</select>
</div>
</section>
<section>
<div class="container">
<h3>People List : ([[ people_count ]] People)</h3>
<!-- I ADDED 'sortedPeople' HERE - WHICH BROKE IT -->
<div v-for="person in filteredPeople | sortedPeople">
<div class="card mb-4" :class="person.has_summative_past_due ? 'alert-warning' : ''">
<div class="card-body row" >
<div class="col">
<h4 class="card-title" v-bind:person='person.full_name'><a v-bind:href="'{% url 'commonground:index' %}' + 'users/' + person.id">[[ person.full_name ]]</a></h4>
<p v-if="person.active_summative" class="card-text">
Active Summative Due Date: [[ person.active_summative.due_date ]]
<span v-show="!person.active_summative.past_due" v-bind:past_due='person.active_summative.past_due' class="badge badge-success">[[ person.active_summative.due_date_status ]]</span>
<span v-show="person.active_summative.past_due" class="badge badge-danger">[[ person.active_summative.due_date_status ]]</span>
<ul class="list-group list-group-flush">
<li class="list-group-item justify-content-between align-items-center" v-for="summary in person.summative_evaluations_summary">
<span class="badge badge-secondary badge-pill">[[summary.evaluation_type__count]]</span> [[ summary.evaluation_type__name ]]
</li>
</ul>
</p>
<p v-if="!person.active_summative" class="card-text">
No Active Unlocked Summatives
</p>
<a v-if="person.active_summative" :href="person.active_summative.absolute_url" class="btn btn-primary"><i class="far fa-edit"></i> View / Edit Active Summative</a>
</div>
<div class="col-auto float-right text-right">
<p class="h5">
[[ person.base_location ]]
<div v-if="person.multiple_locations" class="small text-muted"><i class="fal fa-info-circle"></i> User has multiple locations</div>
</p>
<p class="h5">
[[ person.assignment_job ]]
<div v-if="person.multiple_jobs" class="small text-muted"> <i class="fal fa-info-circle"></i> User has multiple jobs</div>
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- END OF VUE -->
</div>
The actual Vue code looks like this:
<script>
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
data: {
people: [],
people_count: 0,
search_term: "",
sort_filter: "",
},
computed: {
filteredPeople:function()
{
var search = this.search_term.toLowerCase();
return this.people.filter(function(person){
return Object.values(person).some( val => String(val).toLowerCase().includes(search))
})
},
sortedPeople:function()
{
var sort_filter = this.sort_filter.toLowerCase();
console.log('triggered')
return this.people.filter(function(person){
return Object.values(person).some( val => String(val).toLowerCase().includes(sort_filter))
})
},
},
async created () {
var response = await fetch("{% url 'user-list' %}");
this.people = await response.json();
this.people_count = await this.people.length
}
})
</script>
Fairly new to Vue, but I am building this to learn. All help is appreciated!
Check out the simple sample I made: Link
filteredPeople() {
return this.people.filter(
(person) =>
person.firstname
.toLowerCase()
.includes(this.search.toLowerCase().trim()) ||
person.lastname
.toLowerCase()
.includes(this.search.toLowerCase().trim())
);
},
sortedPeople() {
return this.filteredPeople.sort((a, b) =>
a[this.sortby].localeCompare(b[this.sortby])
);
},
Added asc/dec order: Link
sortedPeople() {
return this.filteredPeople.sort((a, b) =>
(this.sort == 'asc') ? a[this.sortby].localeCompare(b[this.sortby]) : b[this.sortby].localeCompare(a[this.sortby])
);
},

Cannot Update Bootstrap Modal Forms

I have a CRUD reminder app, and when users right click on a reminder, a context menu pops up. Options include edit, delete, etc. Each reminder has an id, and the context menu button updates based on the reminders id, and passes it to a bootstrap modal form:
JS:
//part of the context menu script
//this function is called when the user right clicks inside of any element with class task
function toggleMenuOn(e) {
if ( menuState !== 1 ) {
menuState = 1;
menu.classList.add( contextMenuActive );
}
let reminder_id = e.target.id
$.ajax({
url: '/ajax/get_reminder/',
data: {
'reminder_id': reminder_id
},
dataType: 'json',
success: function (data) {
let context_btn1 = document.getElementById("context_menu_btn1");
let reminder_pk = data.serialized_reminder.toString();
let reminder_url = "{% url 'update-reminder' 0 %}".replace(/0/, reminder_pk)
context_btn1.className = "update-reminder context-menu__link btn btn-light";
context_btn1.setAttribute('data-id', reminder_url);
change_var(reminder_url)
}
});
}
let context_btn_id = "/update_reminder/0/";
function change_var(new_name) {
context_btn_id = new_name;
}
$(document).ready(function() {
$("#context_menu_btn1").click(function () {
$(this).modalForm({formURL: context_btn_id});
});
});
HTML:
<nav id="context-menu" class="context-menu">
<ul class="context-menu__items">
<li class="context-menu__item">
<button type="button" class="context-menu__link btn btn-light" id="context_menu_btn1" data-id=""><i class="fa fa-edit"></i> Edit</button>
</li>
<li class="context-menu__item">
<button type="button" class="context-menu__link btn btn-light" id="context_menu_btn2" data-id=""><i class="fa fa-times"></i> Delete</button>
</li>
<li class="context-menu__item">
<button type="button"class="context-menu__link btn btn-light" id="context_menu_btn3" data-id=""><i class="fa fa-eye"></i> Share</button>
</li>
</ul>
</nav>
Reminder HTML:
<div>
<h3 class="ml-4">Long-Term Assignments:</h3>
<div class="events-content-section ml-4 mr-3">
{% get_reminder_id user.username as id_reminders %}
{% for reminder_id, long_term_reminder in id_reminders.items %}
<div class="task mb-2" style="display: flex;">
<button type="button" class="view-reminder btn btn-light" data-id="{% url 'view-reminder' long_term_reminder.pk %}" id="{{ reminder_id }}" style="width: 100%;">
<h4 style="float: left;">{{ long_term_reminder.title }}</h4>
</button>
</div>
{% endfor %}
</div>
</div>
As you can see, I have one navigation menu and have buttons that change data-id every time it is shown. The code works the first time, but when I go to edit another reminder it just brings up the modal form for the first reminder. For example, if I were to update a reminder named Test1, and then try to edit Test2, it would bring up the edit form form for Test1.
I think it has something to do with the code not creating a new modalForm every time context_btn_id is updated. As a reference, the code below works, but an individual button is created for each reminder.
$(".view-reminder").each(function () {
$(this).modalForm({formURL: $(this).data('id')});
});
How would I solve this issue? Any help would be appreciated!
You can add a data-id attribute for each reminder in reminders list or table and on navigation click you can get the selected reminder id.

Vue.js 2 dynamically adding and removing classes in looped elements

I have a project here where I need to add and remove layers of images, using vue.js 2. I am building up a pizza where I need to add toppings. My current solution has a flaw - it removes all other elements/ pizza toppings when I add a new one.
The toppings are generated from an array which I loop through.
Can you please help, I am sure this is easy but me being a rookie in vue.js I have already struggled for hours... Thanks!
<div id="app" class="container-fluid">
<div class="row">
<div class="left-container">
<h2>add your ingredients:</h2>
<div v-for="(item, index) in pizzas" v-bind:key="index">
<button class="btn btn-primary" v-on:click="show == index ? show = -1 : show = index">{{ item.pizza }}</button>
</div>
<div class="submit-buttons">
<button class="btn btn-primary reset-pizza" v-on:click="show = -1">Reset pizza</button>
<button class="btn btn-primary submit-pizza">Share pizza</button>
</div>
</div>
<div class="right-container">
<ul class="pizza-layers">
<li v-for="(item, index) in pizzas" class="pizza-canvas" v-bind:class="item.class" v-if="show == index"></li>
<li class="pizza-canvas pizza-canvas--topping-base"></li>
</ul>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
pizzas: [
{ pizza: 'Salami', class: 'pizza-canvas--topping-salami' },
{ pizza: 'Rucolla', class: 'pizza-canvas--topping-rucolla' },
{ pizza: 'Cheese', class: 'pizza-canvas--topping-cheese' }
],
show: {},
},
})
</script>
To allow for multiple toppings, this.show should be an array, instead of an object.
Once you change that, you'd need to modify the click event handler to add/remove the topping based on whether or not the topping is already part of show.
Also, while displaying the topping, you'd need to check its existence using show.includes(index) instead of show == index - since this.show is an array.
In the snippet below, I've applied these changes, and added some background colors to visualize how the toppings are added or removed.
new Vue({
el: '#app',
data: {
pizzas: [{
pizza: 'Salami',
class: 'pizza-canvas--topping-salami'
},
{
pizza: 'Rucolla',
class: 'pizza-canvas--topping-rucolla'
},
{
pizza: 'Cheese',
class: 'pizza-canvas--topping-cheese'
}
],
show: [],
},
methods: {
addTopping(event, item, index) {
if(this.show.includes(index)) {
this.show.splice(this.show.indexOf(index),1);
} else {
this.show.push(index);
}
}
}
})
.pizza-canvas--topping-salami {
background-color: red;
}
.pizza-canvas--topping-rucolla {
background-color: yellow;
}
.pizza-canvas--topping-cheese {
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app" class="container-fluid">
<div class="row">
<div class="left-container">
<h2>add your ingredients:</h2>
<div v-for="(item, index) in pizzas" v-bind:key="index">
<button class="btn btn-primary" v-on:click="addTopping(event, item, index)">{{ item.pizza }}</button>
</div>
<div class="submit-buttons">
<button class="btn btn-primary reset-pizza" v-on:click="show = []">Reset pizza</button>
<button class="btn btn-primary submit-pizza">Share pizza</button>
</div>
</div>
<div class="right-container">
<ul class="pizza-layers">
<li v-for="(item, index) in pizzas" class="pizza-canvas" v-bind:class="item.class" v-if="show.includes(index)"></li>
<li class="pizza-canvas pizza-canvas--topping-base"></li>
</ul>
</div>
</div>
</div>

Vue 2 - Uncaught TypeError: cloned[i].apply is not a function at HTMLInputElement.invoker (vue.esm.js?65d7:1810) error

I am getting the error from the title:
Uncaught TypeError: cloned[i].apply is not a function
at HTMLInputElement.invoker (vue.esm.js?65d7:1810)
Made standard setup with vue-cli (simple webpack), and this is my component:
<template>
<div class="column is-4">
<nav class="panel">
<p class="panel-heading">
Authors in our library
</p>
<div class="panel-block">
<p class="control has-icons-left">
<input class="input is-small" type="text" placeholder="Search"
v-model="search"
#keyup="filterAuthors">
<span class="icon is-small is-left">
<i class="fa fa-search"></i>
</span>
</p>
</div>
<a class="panel-block is-active" v-for="author in filterAuthors">
<span class="panel-icon">
<i class="fa fa-book"></i>
</span>
{{ author }}
</a>
</nav>
</div>
</template>
<script>
export default {
data () {
return {
'search' : ''
}
},
computed: {
filterAuthors() {
let search = this.search.toLowerCase();
return this.$store.state.authors.filter((author) => {
return author.toLowerCase().indexOf(search) >= 0;
})
}
}
}
</script>
Strange part is that the filter is working, but every time I type into the input field, I get this error. Anyone have any idea what can it be?
Computed properies are reactive by default, and in fact you can't attach them to event handler.
Removing the keyup event handler that calls computed property should fix the problem.
<p class="control has-icons-left">
<input class="input is-small" type="text" placeholder="Search">
<span class="icon is-small is-left">
<i class="fa fa-search"></i>
</span>
</p>
For some reason, I had a property name under data and a function with the same name under methods.
Removing the property fixed a similar error.
new Vue({
el: '#elem',
data: {
function_name: null, // <- removed this
},
methods: {
function_name: function() {
// ...
}
}
});

Short HTML Code from Json will display the HTML code instead of the rendered version

I'm retrieving an array filled with user data. They should be displayed in a ul. One of the fields is the prefix for every user as an html code like this <span class="red-text">[Admin]</span>. But when I try to append the li element it renders the html code instead of the red version of the span.
My json code:
{
"users":[
{
"id":"1",
"usrname":"YannickFelix",
"email":"example#gmail.com",
"lvl":"4",
"prefix":"<span class=\"red-text\">[Admin]<\/span>"
}
]
}
And my javascript:
listElemTmplt = `
<li class="collection-item avatar">
<i class="material-icons circle {"{{color}}"}">person</i>
<span class="title">{"{{usrname}}"}</span>
<p>{"{{prefix}}"} {"{{usrname}}"} | {"{{email}}"}
</p>
<span class="secondary-content">
<a class="waves-effect waves-circle" href="users.php?action=edit&uID={"{{id}}"}">
<i class="material-icons grey-text text-darken-1">create</i>
</a>
<a class="waves-effect waves-circle waves-red modal-trigger" href="#modal{"{{id}}"}">
<i class="material-icons grey-text text-darken-1">delete</i>
</a>
</span>
<div id="modal{"{{id}}"}" class="modal">
<div class="modal-content black-text">
<h4>Löschen</h4>
<p>Möchtest Du den Benutzer "{"{{usrname}}"}" wirklich löschen?</p>
</div>
<div class="modal-footer">
Abbrechen
Löschen
</div>
</div>
</li>
`;
template = Handlebars.compile(listElemTmplt);
finishedString = [];
$.getJSON("**url**", function (data) {
console.log(data);
$("ul#users").html("");
data["users"].forEach(function (element, index, array) {
html = template({"{"}id: element["id"], usrname: element["usrname"], email: element["email"], prefix: element["prefix"]{"}"});
$("ul#users").append(html);
});
});
The example item in the List. [Admin] should be red and without the html code
Handlebars escapes the values you give to it when using {{prefix}}. When you want to use raw HTML you have to use {{{prefix}}} to tell it not to escape it.

Categories