Displaying(adding, editing) and Deleting list items in multiple columns in Vue.js without v-if and v-for - list

I am beginner in Vue.js. I want to add new items in list, but these items need to be displayed in separate divs according to their "category" property. Also, every item has option to be edited (haven't made that yet) or deleted. I have read that it is not recommended to use v-if inside v-for, so inspired by second answer here I used Computed Properties to do that. I needed to add index for every list item, because I haven't found any way to delete list item in Vue.js without index. The problem is that we iterate over two lists from computed properties and basically we have repeating indexes (check out print of list items in my code and you will understand) so it deletes items from wrong category. This problem would make editing item names harder, too. I was thinking of way to solve this, but I would have to use v-for and v-if together which is not recommended.
Also, this is not very good solution for me, because I would probably need to generate these divs dynamically from given list of categories (there could be a lot of them), and I don't know how would I be able to generate computed properties dynamically for each category. For this I would need to use v-if inside v-for, which is not recommended.
So basically I have two problems:
1. Deleting items from wrong category
2. Dynamically generating divs for each category if I keep using this method with computed properties.
Here is my code: fiddle
Do you have any advice or solutions? Thanks in advance!

The index in the v-for has nothing to do with that of the object in the amenities array.
Since you don't want to make one loop for both categories, I can this solution:
A better approach would be to autogenerate a unique id for each object on addition, and then deleting according to it:
new Vue({
el: '#app',
data: function() {
return {
Amenity: {
name: '',
category: ''
},
amenities: [],
nextId:0
}
},
computed: {
familyCategory: function () {
return this.amenities.filter(i => i.category === 'family')
},
facilitiesCategory: function () {
return this.amenities.filter(i => i.category === 'facilities')
}
},
methods: {
addAmenity: function(e) {
this.amenities.push({
id: this.nextId,
name: this.Amenity.name,
category: this.Amenity.category
});
this.Amenity = {
name: '',
category: 'family'
};
this.nextId = this.nextId+1;
},
removeElement : function (id) {
console.log(id);
this.amenities=this.amenities.filter(e => e.id!=id);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<br /> Amenity name:
<input type="text" v-model="Amenity.name" placeholder="Amenity name">
<label for="category">Category:</label>
<select id="cetegory" v-model="Amenity.category">
<option value="family">Family</option>
<option value="facilities" >Facilities</option>
</select>
<input type="button" #click="addAmenity" value="Submit" class="btn btn-info">
<div>
<h3>Family</h3>
<ol>
<li v-for="(item, index) in familyCategory">
{{ index }} - {{ item.name }}
<button>
Edit
</button>
<button v-on:click="removeElement(item.id)">
Delete
</button>
</li>
</ol>
</div>
<div>
<h3>Facilities</h3>
<ol>
<li v-for="(item, index) in facilitiesCategory">
{{ index }} - {{ item.name }}
<button>
Edit
</button>
<button v-on:click="removeElement(item.id)">
Delete
</button>
</li>
</ol>
</div>
</div>

Related

Bootstrap-Select not updating on mobile

I am using Bootstrap-select for multi-select items with Django. It works fine on desktop, but when the native mobile drop-down is enabled, the selected values of the dropdown do not populate.
HTML
<!-- start product brand info -->
<div class="row">
<div class="col-md-6 col-xs-*">
<div>
<label for="id_brand-name" class="form-label">Products</label>
</div>
<select multiple class="form-control selectpicker mb-3" id="id_brand-name" name="brand-name" mobile="true" multiple required>
{% for product in products %}
<option value="{{product.id}}" id="optiion">{{ product.brand.name | title }} - {{product.name | title}}</option>
{%endfor%}
</select>
<div class="invalid-feedback">
Select at least one product.
</div>
</div>
</div>
<!-- end product info -->
<script>
//Enble native drop down on mobile
window.onload = function () {
$('#id_brand-name').selectpicker({
container: 'body'
});
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
$('#id_brand-name').selectpicker('mobile')
}};
</script>
No matter how many items are selected, the selector always shows Nothing selected. When the data is posted, it is apart of the form though.
I like to use bootstrap-select, but this problem has been bothering me for a long time
Similar questions can be found on github, but no perfect answer.
The reason is that it will always refresh his title, no matter how remove() is done externally, and you can't change it's style on iphone Safari and Firefox.
So my solution is: if you can't remove it, then join it.
You have to change the original file: bootstrap-select.js
Search for: bs-title-option, next line you will find:
this.selectpicker.view.titleOption.value = '';
And add two lines down:
this.selectpicker.view.titleOption.value = '';
// new add
this.selectpicker.view.titleOption.disabled='true';
this.selectpicker.view.titleOption.textContent=this.options.title;
//
Done
When you use $('#id_brand-name').selectpicker('mobile')
The title text show on first empty option, and it can't be choose.
try it!

Using different content object passed from Views to Template depending on what button user presses

I currently have one model called Comments.
After entering in a youtube Channel in a form, the user is taken to the index template that shows all the comments on that youtube channels videos that includes one of three key words (Keyword A, Keyword B , Keyword C).
I would like to add a feature so there are three links/buttons on the top of the page (each for one of the keywords).
The user can press that link and without page reload (does this mean I will need AJAX?) see the comments with that keyword, instead of all comments with any of the three keywords.
I am currently sending four content variable objects from views to the template (one with all the comments and three other objects each that just contain the comment objects for that keyword).
So the template has access to the data I need, I just need to make it so that when one of the links/buttons are clicked, it only shows that content.
Views
def addTodo(request):
new_item =Channel(channel=request.POST['channel'])
#if channel exists render page with comments
if Channel.objects.filter(channel=new_item.channel).exists():
channel_obj=Channel.objects.get(channel=request.POST['channel'])
comments_object=Comments.objects.filter(channel=channel_obj)
comments_objectA=Comments.objects.filter(channel=channel_obj, key="keywordA")
comments_objectB=Comments.objects.filter(channel=channel_obj, key="keywordB")
comments_objectC=Comments.objects.filter(channel=channel_obj, key="keywordC")
return render(request, 'todo/index.html', {'comments_all': comments_object, 'commentsa': comments_objectA,'commentsb': comments_objectB,'commentsc': comments_objectC})
Index Template
#three buttons/links on top to allow user to sort..the part Im not sure how to do:
<button type="button"onclick="justshowrelatedcomment>KeywordA!</button>
<button type="button"onclick="justshowrelatedcomment>KeywordB</button>
<button type="button" onclick="justshowrelatedcomment>KeywordC</button>
#the comment structure, would want to replace comments_all with whatever button is clicked on.
<div class="new_comment">
<!-- build comment -->
{%for a in comments_all%}
<ul class="user_comment">
<!-- current #{user} avatar -->
<!-- the comment body --><div class="comment_body">
<p>{{ a.question }}</p>
</div>
</ul>
{% endfor %}
</div>
</div>
I'm quite stuck.. Is this possible without Ajax?
If Ajax is my only/best option, how should I go about that?
I was using this solution to avoid ajax, as I couldn't figure out how to use ajax.
Thanks and cheers.
You can do it without ajax, but it does not reduce the size of loaded page. The non-ajax client-side solution is to assign each comment element a class or attribute based on its keyword - here you have used a <ul> element with user_comment class already.
So try to put the keyword as an attribute or class to your comment divs. Then you can select and then hide or show each keyword class of comments using javascript coding.
It can be done in different ways but it would be easy using pure js so I am putting the template for the implementation:
<!-- toggling comments -->
<script>
function toggle_keyword(keyword) {
document.querySelectorAll('.user_comment').forEach(function (e) {
e.style.display = 'none';
});
document.querySelectorAll('.' + keyword).forEach(function (e) {
e.style.display = 'block';
});
}
</script>
<button type="button" onclick="toggle_keyword('KeywordA');">hide/show KeywordA!</button>
<button type="button" onclick="toggle_keyword('KeywordB');">hide/show KeywordB</button>
<button type="button" onclick="toggle_keyword('KeywordC');">hide/show KeywordC</button>
#the comment structure, would want to replace comments_all with whatever button is clicked on.
<div class="new_comment">
<!-- build comment -->
{%for a in comments_all%}
<ul class="user_comment {{ a.key }}">
<!-- current #{user} avatar -->
<!-- the comment body -->
<!-- here I added a.key as a class to the div -->
<div class="comment_body">
<p>{{ a.question }}</p>
</div>
</ul>
{% endfor %}
</div>
</div>
Definitely Ajax will be best option for you. You just need to post one flag while clicking button with using ajax. On that flag basis you can decide which data you want to pass for your html
HTML
<button onclick="justshowrelatedcomment('A')">KeywordA</button>
<button onclick="justshowrelatedcomment('B')">KeywordB</button>
<button onclick="justshowrelatedcomment('C')">KeywordC</button>
<script>
function justshowrelatedcomment (flag) {
$.ajax({
url: 'addTodo',
type: 'POST',
data: {
flag: flag
},
success: function(data){
return data;
}
});
}
</script>
View
def addTodo(request):
flag = request.POST['flag']
new_item =Channel(channel=request.POST['channel'])
Hopefully this will work for you.

How to set semantic-ui-ember dropdown value?

I have a model, called assessmentTestModel, that is a list of assessmentTests. Each assessmentTest contains a unique identifier id, and form that is a reference to another object called form. Form also has a unique identifier, id.
For every assessmentTest in assessmentTestModel, I want to render a drop-down on the screen that's menu-items are all of the forms (I've already accomplished this). I also want them to have the selected default value of assessmentTest.form (This is the issue). Currently, when each drop-down is rendered, it displays the default text 'Select an Assessment Test'.
Here's an example of assessmentTestModel:
{ assessmentTestModel: [
{
id: '1029',
form: '85'
},
{
id: '1030',
form: '87'
},
{
id: '1031',
form: '85'
}
]
}
Here is my current .hbs file. I've tried setting the value attribute of the ui-dropdown to the id of the form, and the form object itself. Neither have worked.
<div class="field">
<label>Assessment Tests</label>
<ol>
{{#each assessmentTestModel as |assessment|}}
<li>
{{#ui-dropdown value=assessment.form class="selection" onChange=(action 'selectForm' assessment)}}
<div class="default text">Select an Assessment Test</div>
<i class="dropdown icon"></i>
<div class="menu">
<div data-value="" class="item">All Items</div>
{{#each formModel as |form|}}
<div data-value="{{form.id}}" class="item">
{{form.name}}
</div>
{{/each}}
</div>
{{/ui-dropdown}}
</li>
{{/each}}
</ol>
</div>

Using dynamic templates in meteor

I'm really stuck trying to use dynamic templates in meteor, and applying filters to the data which populates them. I'd be really grateful for some help.
To give a simple example, I'd like to be able to input tasks into a to do list, with a tag, for whether it is a big or a little task. Then I'd like to be able to display a table of big tasks, and a table of little tasks, separately.
Here are some code extracts, adapted from the official meteor blaze tutorial, so you can see in a general sense what I'm trying to do.
body.html extract
<body>
<div class="container">
<header>
<h1>Todo List</h1>
<form class="new-task">
<input type="text" name="text" placeholder="Type to add new tasks" />
<input type="text" name="taskType" placeholder="Big/little" />
<button type = "submit">submit</button>
</form>
</header>
<h1>big tasks</h1>
<ul>
{{> Template.dynamic template="task" data=bigTasks}}
</ul>
<h1>little tasks</h1>
<ul>
{{> Template.dynamic template="task" data=littleTasks}}
</ul>
</div>
</body>
task.html extract
<template name="task">
<li class="{{#if checked}}checked{{/if}}">
<button class="delete">×</button>
<input type="checkbox" checked="{{checked}}" class="toggle-checked" />
<span class="text">{{text}}</span>
</li>
</template>
task.js extract
Template.task.helpers({
bigTasks() {
return Tasks.find({}, { taskType: Big, sort: { createdAt: -1 } });
},
});
Template.task.helpers({
littleTasks() {
return Tasks.find({}, { taskType: Little, sort: { createdAt: -1 } });
},
});
I realise this is perhaps a little more a request for a tutorial, than a specific question, but would be grateful for anything which you can offer
There are numerous issues with your code.
First, you're using Template.dynamic wrong way. It's supposed to include template which name is dynamic.
Second, using Template.dynamic you're using data=<helper name>. That's correct form, but you're using it in Template.body, so these helpers also should be in Template.body, not in Template.task.
And, third issue is that you forgot to wrap your task template content in {{#each}}...{{/each}}.
As your question is about using Template.dynamic, here is how it should be (though there is an another, better way to implement this):
Template.body.helpers({
bigTasks() {
const tasks = Tasks.find({}, { taskType: Big, sort: { createdAt: -1 } }).fetch();
return { tasks };
},
littleTasks() {
const tasks = Tasks.find({}, { taskType: Little, sort: { createdAt: -1 } }).fetch();
return { tasks };
},
});
task.html:
<template name="task">
{{#each tasks}}
<li class="{{#if checked}}checked{{/if}}">
<button class="delete">×</button>
<input type="checkbox" checked="{{checked}}" class="toggle-checked" />
<span class="text">{{text}}</span>
</li>
{{/each}}
</template>
For anyone encountering the same challenges, Styx's answer above works, but I found that I needed to also make a few changes to the syntax of the helper function:
Template.body.helpers({
bigTasks() {
// Show newest tasks at the top
const tasks = Tasks.find({taskType: "Big"}, {sort: { createdAt: -1 } }).fetch();
return { tasks };
},
littleTasks() {
// Show newest tasks at the top
const tasks = Tasks.find({taskType: "Little"}, {sort: { createdAt: -1 } }).fetch();
return { tasks };
},
});
Styx - thanks so much. I really appreciate you taking the time to help this newbie find my way. All the best!

Ember Object, with a nested array

To learn Ember, I've been trying to make a simple app that computes timezones.
When a person enters their city, and the other person's city, I make a GET request to my API, which returns the dates like so --
great_times: [array]
good_for_me: [array]
good_for_them: [array]
In handlebars, I have
<div class="container">
<div class="col-md-6 col-md-offset-3">
<header>
<h2>We found <span class="twenty-four-header">{{totalTimes}}</span>
great meeting {{pluralize totalTimes 'time' 'times'}} for you!</h2>
</header>
<div class="main-content">
<div class="row">
<div class="col-md-6">
<div class="form-group">
{{view RightTime.AutoCompleteAddressView value=myCity placeholder="What's your city?"
class="form-control input-lg" latLng=myLatLng}}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
{{view RightTime.AutoCompleteAddressView value=theirCity placeholder="What their city?"
class="form-control input-lg" latLng=theirLatLng}}
</div>
</div>
</div>
{{#each meetingTime in greatTimes}}
{{render 'meetingTime' meetingTime }}
{{/each}}
</div><!--main-content-->
</div>
</div>
This works, but what happens is that when I update the city, It no longer updates this each loop.
I do know however that the model was updated, because the {{totalTimes}} computed property does update.
This is what my meeting Object looks like:
RightTime.Meeting = Ember.Object.extend({
meetingTimes: null,
myCity: null,
theirCity: null,
myLatLng: null,
theirLatLng: null,
totalTimes: function() {
if (this.meetingTimes) {
return this.meetingTimes.great_times.length;
}
}.property('meetingTimes'),
locationsData: function() {
return {
myLatLng: [this.myLatLng.k, this.myLatLng.A],
theirLatLng: [this.theirLatLng.k, this.theirLatLng.A]
}
}.property('myLatLng', 'theirLatLng'),
findTimes: function() {
var meeting = this;
if (this.myLatLng && this.theirLatLng) {
$.ajax({
url: 'api/meetings',
type: 'GET',
data: meeting.get('locationsData')
}).done(function(response){
meeting.set('meetingTimes', Ember.A(response));
});
}
}.property('myLatLng', 'theirLatLng')
});
I have a feeling that the problem lies in
.done(function(response){
meeting.set('meetingTimes', Ember.A(response));
});
I'm resetting the whole meetingtimes array, which may be the wrong way to go about it.
How would you go about making the meetingTimes arrray update and reflect in handlebars?
I'd probably just move great_times into a computed property that depends on meetingTimes and isn't chained.
something like
greatTimes: function() {
return Em.get(this, 'meetingTimes.great_times') || [];
}.property('meetingTimes'),
With a template like this
{{#each meetingTime in greatTimes}}
{{render 'meetingTime' meetingTime }}
{{/each}}