How to handle v-if with the back button in Vue.js? - javascript

I am using routing with Vue.js and am trying to use v-if to conditional display 2 divs. The first div is a list of items. The second div is the details of an item. A good example of what I am trying to do would be something similar to Gmail. You have your list of emails and when you click the subject, you are taken to a page that shows the whole email.
I have this somewhat working using v-if. In my data, I have a variable called view. When the page loads, this value is set to 'list', which shows the list div. When someone clicks something in the list div, this value switches to 'details'. and the details div shows.
The problem that I am running into is if the user hits the back button in the browser, the view variable does not get updated. This seems like it is a common scenario, so I am wondering if there is a better way to do this.
<div id="app">
{{ view }} //for debugging
<div class="list" v-if="view === 'list'">
<table>
<tr v-for="item in list">
<td><router-link v-on:click.native="doSomething" v-bind:to="'/items/' + item.id">{{ item.title }}</router-link></td>
</tr>
</table>
</div>
<div class="details" v-if="view === 'details'">
<router-view></router-view>
</div>
</div>
Snippets from my Vue declaration.
data: {
view : "list"
},
doSomething: function () {
var app = this;
app.view = "details";
}

Try this:
<div id="app">
{{ view }} //for debugging
<router-view></router-view>
</div>
And then add two routes:
{
path: '/',
name: 'emails',
component: Emails,
},
{
path: '/login',
name: 'email.show',
component: EmailShow,
}
After that Emails component should handle logic of showing all emails and EmailShow should handle showing just one email

Related

Vuejs seems to struggle with v-model when many items on the page

I have a simle page containing a form at the top to submit some data and a list below that form as in the image:
The list is filled by data from an api and each object has 4 properties (not many for a simple list!). Currently the list has 130 elements in total. Now if I try to write something in the textarea, it happens to be very slow (2-10frames/sec). The more items added to the list, the slower it gets. The form at the top has a simple data object called form like below:
form: {
description: '',
expired: '',
applicationId: ''
}
The funny part it that there is no single data shared between the list and the form at the top, so, they should operate independently. When I commend out the list section the textarea is very fast again.
Below is the code to render out the list:
<b-card no-body class="box-shadowed">
<b-card-body v-if="appKeys.length === 0">
<h5>Seems there is no key available.</h5>
</b-card-body>
<b-list-group v-else flush>
<b-list-group-item
v-for="(key, index) in appKeys"
:key="key.id"
>
<div class="d-flex w-100 justify-content-between">
<p class="text-danger h6"><span class="text-dark">{{ index + 1 }} - </span> <i>{{ key.id }}</i></p>
<div>
<b-button variant="primary" title="Edit" #click="openEditModal(key.id, key.description, key.expired)">
<b-icon :icon="'pencil'"/>
</b-button>
<b-button variant="danger" title="Delete" #click="deleteKey(index, key.id)">
<b-icon :icon="'trash'"/>
</b-button>
</div>
</div>
<template v-if="key.expired">
<b-badge variant="info">Expires on: {{ key.expired | formatDate }}</b-badge>
<br/>
</template>
<small>
<b>
<i>Created by: {{ key.createdBy }}</i> on {{ key.created | formatDateTime }}
</b>
<br/>
{{ key.description }}
</small>
</b-list-group-item>
</b-list-group>
If I remove the v-model="form.description" from the textarea the problem goes away again.
I thought that the problem might be the <b-form-textarea> component from bootstrap-vue but the same problem happens with a simple textarea input as well.
I tried checking the vue dev-tools panel and I can see that frames are dropping every time I have many items in the list but I dont know what else to check.
Does anybody have any idea why this might happen ? It is a vuejs limitation on how many items it can handle or I have some bottleneck code somewhere ?
EDIt
I Can use v-once and the page would be fast again but I need that reactivity when I add new elements to update the list below..
There is something what you have faced. Here says:
Any change to a template’s dependency will result of re-render of the
whole virtual dom of that component.
While the list becomes larger and larger, there will be more components to be re-rendered. This results slowness. One solution is that you can create a child component for the html part where the list is. So, any input change in <b-form-textarea> in the parent component will not trigger the child component re-render.

Vue.js - switching from one component to another

I'm quite new to vue.js.
I have a page that lists details of a list of people, with an 'Edit' button against each person.
When I click the Edit button, I want to switch to another page which shows a form for editing the selected person's details.
I'm not sure what is the best way to do this. I'm using Bootstrap and Router in my solution.
Option 1: I thought it would be straightforward to route to '/person/:id' when clicking the Edit button, but not sure how to do this from the click handler method.
Option 2:
Below is the main component, where I'm trying to switch between the two components 'PersonsList' and 'EditPersonData' upon receiving an event from either. PersonsList is emitting the event successfully, but I'm not sure how to listen to it here and switch to EditPesonData component.
Home.vue
<template>
<div class="home">
<h1 class='home-text'>
{{message}}
</h1>
<component v-bind:is="component"/>
</div>
</template>
<script>
import PersonsList from '#/components/PersonsList'
import EditPersonData from '#/components/EditPersonData'
export default {
name: 'Home',
data() {
return {
message: 'Welcome to Person Details!',
component: "PersonsList"
}
},
components: {
PersonsList,
EditPersonData
}
};
</script>
Any help would be very much appreciated.
Thanks
The best way would be to send the id as a param in the route /person/:id.
To do this, you can append the id of each list item, for example:
<div v-for="(user,index) in userList" :key="index">
<a :href="'/person/' + user.id">Edit</a>
</div>
Then, you would get it in the other component as this.$route.params.id in order to get more details about the user of this id.
Make sure you define the route in the router with the id param as follows: person/:id
UPDATE :
If you want to change the route on a click function:
<b-btn #click="editPerson(user.id)">Edit</b-btn>
And in the methods, add the function as follows:
editPerson(userId){
this.$router.push({
name: 'person',
params: { id: userId }
});
}

Right Click and left click options needs comes near to the clicking row

For the product, the mouse click options are wrote for action gear icon only. So if we left click on Task button(action icon), it will show options near to that icon.
and corresponding inspect(F12) html file is here:
<tr id="ember25477" class="ember-view content-row body-row-view container-view" tabindex="0" aria-label="">
<td id="ember25657" class="ember-view content-data item-number item-number-view container-view item-number-view-core_component_data-view-mixin container-view-core_component_data-view-mixin itemNumber">
<div id="ember25660" class="ember-view view">
<div>
</div>
</div>
</td>
<td id="ember25666" class="ember-view container-view">
<rs-icon id="ember25669" class="ember-view action-menu auto-actions-menu-button icon clickable-icon" style="width: 1em; height: 1em; font-size: 20px">
<icon glyph="action" class="action" style="font-size: 20px;">
</icon>
</rs-icon>
<span id="ember25672" class="ember-view view">
</span>
</td>
<td id="ember25678" class="ember-view content-data view view-core_component_data-view-mixin description">
<div class="container">
<div class="content">
<div class="aria-hidden">Empty cell</div>
</div>
</div>
</td>
</tr>
But Now the new requirement is options have have to wherever click (both on right click and left click) on that corresponding row. So I have tried the below code:
click: function (event) {
var eventResult = this.get('tableView').clickRow(event, this.get('object'));
if (eventResult !== false) {
this.get('element').focus();
$('.content-row').bind('contextmenu', function(e) {
e.preventDefault();
var rowParentId = $(this).closest('tr').prop('id');
$('#'+rowParentId).find( ".action-menu" ).click();
});
}
return eventResult;
},
When I'm using the above code, right click option is working fine at the corresponding row. But the options are coming near to the gear icon only. I need the options needs to come near to wherever I click on that row.
Please provide me any other suggestion for this. Your help will be appreciate. Thanks in advance.
I'm not 100% sure exactly what you're trying to achieve here but I will attempt an answer based on what I understand. This example will likely be a more "Ember Way" of doing it instead of breaking out to jQuery so please let me know if you can get it working and if not I will update my answer.
First of all, I would recommend that you use Ember's internal implementations of event binding, there are some strange specifics about interactions of dom events and Ember events so you should be very careful here. You can see a full list of events that can be handled automatically by a component in the Ember Guides.
The first thing that we do is we create a component for a table row so that each row is able to handle its own actions. We then use that component in the main template (either your route's template or a table component):
<table>
<tbody>
{{#each model as |item|}}
{{table-row item=item}}
{{/each}}
</tbody>
</table>
In the component javascript file table-row.js you have the following code:
import Component from '#ember/component';
export default Component.extend({
tagName: 'tr',
showContextForEvent(event) {
this.set('showContextMenu', true);
this.set('x', event.clientX);
this.set('y', event.clientY);
},
contextMenu(event) {
this.showContextForEvent(event);
return false;
},
});
The key part of this is the contextMenu(event) call, this will be called with a dom event that you can use for whatever you need it to do. The showContextMenu boolean and the x and y are unlikely to be used in your setup and were just things that we did to get this working for the demo we were building in the video linked below.
We have recently uploaded a video of us answering your question (or one very similar to it) here https://youtu.be/mG8NbMh7_Ck. You can also see a full code snippet here https://github.com/stonecircle/ember-rightclick-example

Emberjs toggle effect of dynamic data

I have a table loaded with dynamic data from an api. The <td></td> displays a company name and when clicked I want a div that has the companies information to be displayed. I saw a few examples and this is what I did:
Here is my handlebars:
{{#each m in model}}
<tr>
<td >
<p {{ action 'displayDetails' }}>{{m.name}}</p>
{{#if showDetails}}
<div class="company_details">
<p><strong>Wesbite URL: </strong>{{m.website}}</p>
<p><strong>Development Wesbite URL: </strong>{{m.dev_website}}</p>
</div>
{{/if}}
</td>
</tr>
{{/each}}
as you can see when you click on the name of the company the displayDetails action is called
here is my controller:
export default Ember.ArrayController.extend({
showDetails: false,
actions: {
displayDetails: function(){
this.toggleProperty('showDetails');
}
}
});
This works great; however if I click the company name all of the company details are displayed not just the company I clicked on. I obviously just want to display the specific detail of the company I clicked on how can I go about this?
Add a property to each item in the model named something like isDetailsShown.
You can modify you action by passing the item into the method.
<p {{ action 'displayDetails' item}}>{{m.name}}</p>
In the action, just toggle the property of the passed in item.
displayDetails: function(item){
item.toggleProperty('isDetailsShown');
}
Finally modify the if condition in the template to look like
{{#if item.isDetailsShown}}

How to create an Ember JS Master Detail where detail appears between rows of data

I'm trying to create a page that contains master/detail, which is obviously easy using a static {{ outlet }} in ember, however, I'd like the detail to slide down after the row that was selected. For example, on this page: http://jsbin.com/ijejap/1/edit, if the detail of the name was to appear after the currently selected name instead of at the bottom of the page.
The problem I'm trying to solve is that I can't have an outlet within a repeater so that when I click Row 1, I want the outlet to be positioned below Row 1, and when I click Row 2, I want the outlet after Row 2. In other words, I want to dynamically position an outlet, I think, unless there's another way to do it.
Ok, I finally made a conditionnal outlet depending on wether the user is selected or not, but I had to work with DS.Model and its FIXTURES to make this work.
To be brief, I added an action before the link to set a selected property on the user object and record which user is selected to prevent having two users at the same time.
The position of the {{outlet}} then depends on the current selected user.
Here's the template part:
{{#each model}}
<li {{action 'select' this}}>
{{#link-to "user" this}}{{first}}{{/link-to}}
</li>
{{#if selected}}
{{outlet}}
{{/if}}
{{/each}}
And the App.ApplicationController part:
App.ApplicationController = Ember.ArrayController.extend({
currentUser: null,
actions: {
select: function(newUser) {
var oldUser = this.get('currentUser');
if(newUser !== oldUser) {
if(oldUser)
oldUser.toggleProperty('selected');
this.set('currentUser', newUser);
newUser.toggleProperty('selected');
}
}
}
});
Finally, the model definition:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
App.User = DS.Model.extend({
first: DS.attr(),
last: DS.attr(),
avatar: DS.attr()
});
Here's the link to the JSBin: http://jsbin.com/cilari/3/
You can make use of components power here.
I made it on JSBin : http://jsbin.com/zovos/2/
First, you declare your user details as a new component, say user-details :
<script type="text/x-handlebars" id="components/user-details">
<li>{{user.first}}</li>
<h2>
{{user.first}} {{user.last}}
<img {{bindAttr src="user.avatar"}} class="pull-right" width=50 />
</h2>
<dl>
<dt>First</dt>
<dd>{{user.first}}</dd>
<dt>Last</dt>
<dd>{{user.last}}</dd>
</dl>
</script>
Then you call it from your list, using the current user as the user in the component :
<script type="text/x-handlebars">
<div class="container">
<div class="row-fluid">
<ul class="nav nav-list span3 well">
{{#each model}}
{{user-details user=this}}
{{/each}}
</ul>
</div>
</div>
</script>
You add the selected property to your component and an action like expandDetails :
App.UserDetailsComponent = Ember.Component.extend({
selected: false,
actions: {
expandDetails: function() {
this.toggleProperty('selected');
}
}
});
Finally, you add the action to your component template and a conditional display on the selected property :
<script type="text/x-handlebars" id="components/user-details">
<li><a {{action "expandDetails"}}>{{user.first}}</a></li>
{{#if selected}}
<h2>
{{user.first}} {{user.last}}
...
</dl>
{{/if}}
</script>
And, of course, you get rid of your user route.
Here is a link to Ember's Guide that show this on a sample post example : http://emberjs.com/guides/components/handling-user-interaction-with-actions/
Hope this helps.
Are you looking for an accordion?
http://addepar.github.io/#/ember-widgets/accordion
I have an idea but unproved yet, maybe you can try it.
For detail, you only need one template. For master, you need write several named outlet (see: http://emberjs.com/guides/routing/rendering-a-template/) and place each below the corresponding item of master view.
When each master item has been clicked, you need pass a param to the detail route in order to let it know what kind of data should be populated.
The key part is the renderTemplate hook of route (see: http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate). You can take advantage of it because it allows you to specify which named outlet should be used to render the template.
Or you may load all details' data at once and cache it, then pass an identifier when master item got clicked, in this way you can filter the model in advance and get prepared for later renderTemplate use.
This is just an idea and currently don't have spare time to try it, but as your wish, you got the dedicated route, controller and model. I think it is highly doable, let me know it you make any progress.

Categories