I wanted to create my portfolio with Vue. I'm stuck with trying to turn the route params from a string into an id.
In the home page, my portfolio items are listed (doing this static-ally and using data objects and not getting from a database). When you click on them, route params take their id and it directs to /portfolio/1, /portfolio/2, and so on. This page then dynamically changes according to the data of that object.
There's also a pagination in this page (Next Project and Previous Project). Because I use their id as a parameter, I wanted to increment or decrement the ID on click so that it would lead to the next or previous project but I found out that the params are read as a string and not an integer, thus ending up with /portfolio/11 or /portfolio/21.
How can I implement this?
The code I've tried so far is as follows:
<router-link :to="{ name: 'Project', params: { id: `${previousFolio}` } }">
<b-button class="project-nav-button">
<img src="#/assets/icon-arrow-back.svg" alt="previous project button" class="previous"/>
<p>Previous Project</p>
</b-button>
</router-link>
<router-link :to="{ name: 'Project', params: { id: nextFolio } }">
<b-button class="project-nav-button">
<p>Next Project</p>
<img src="#/assets/icon-arrow-next.svg" alt="next project button" class="next"/>
</b-button>
</router-link>
data() {
return {
folioId: this.$route.params.id,
folios: [
{
id: 1,
title: "Project Title 1"
},
{
id: 2,
title: "Project Title 2"
},
{
id: 1,
title: "Project Title 3"
}
]
};
},
computed: {
currentFolio: function() {
let folioId = this.$route.params.id;
return this.folios.filter(function (folio) {
return folio.id == folioId;
})
},
nextFolio: function() {
let currentFolioId = this.$route.params.id;
currentFolioId = parseInt(currentFolioId);
return currentFolioId++;
console.log(currentFolioId);
},
previousFolio: function() {
let folioId = this.$route.params.id;
return folioId--;
},
Simply use the parseInt() to convert it to integer
let folioId = parseInt(this.$route.params.id);
Related
I want to make a text field with typeahead. I have a list of words and when you start typing them, a suggestion appears with the word(s). The thing is, it needs to be able to do it multiple times, every new word you type, it can show you a suggestion.
Anyone know how I can do this?
You can use vue-suggestion to accomplish this easily. Take a look at the demo to see if this suites you.
This is my implementation of App.vue which differs slightly.
<template>
<div>
{{ items }}
<vue-suggestion :items="results"
v-model="item"
:setLabel="setLabel"
:itemTemplate="itemTemplate"
#changed="inputChange"
#selected="itemSelected">
</vue-suggestion>
</div>
</template>
<script>
import itemTemplate from './item-template.vue';
export default {
data () {
return {
item: {},
items: [
{ id: 1, name: 'Golden Retriever'},
{ id: 2, name: 'Flying Squirrel'},
{ id: 3, name: 'Cat'},
{ id: 4, name: 'Catfish'},
{ id: 5, name: 'Squirrel'},
],
itemTemplate,
results: {}
}
},
methods: {
itemSelected (item) {
this.item = item;
},
setLabel (item) {
return item.name;
},
inputChange (text) {
// your search method
this.results = this.items.filter(item => item.name.includes(text));
// now `items` will be showed in the suggestion list
},
},
};
</script>
Please open it in full browser https://stackblitz.com/edit/angular-skzgno?file=src%2Fapp%2Fapp.component.html , Then click button/image of any li tag,The button will be change to different image as like active .Even if you refresh also this active will not be change since we are adding active=true into localstorage.Now the problem is,on page load when you click button of any li,except that button,buttons of other li should be disable and when we refresh also nothing will be change until you clear localstorage.Please find the code below
app.component.html
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<div>
<pre>
</pre>
<ul>
<li *ngFor="let item of statusdata">
<span>{{item.id}}</span>
<span>{{item.name}}</span>
<button style="margin-left:10px" (click)="toggleActive(item, !item.active)">
<img style="width:50px;margin-left:10px" *ngIf="!item?.active || item?.active === false" src ="https://dummyimage.com/qvga" />
<img style="width:50px;margin-left:10px" style="width:50px;margin-left:10px" *ngIf="item?.active === true" src ="https://dummyimage.com/300.png/09f/fff" />
</button>
</li>
</ul>
</div>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
statusdata: any;
ngOnInit() {
this.statusdata = [
{ id: 1, name: "Angular 2" },
{ id: 2, name: "Angular 4" },
{ id: 3, name: "Angular 5" },
{ id: 4, name: "Angular 6" },
{ id: 5, name: "Angular 7" }
];
this.statusdata.forEach(item => {
this.getCacheItemStatus(item);
});
}
toggleActive(item, activeStatus = true) {
if(!this.statusdata.some(d => d.active)){
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
}
getCacheItemStatus(item) {
const cachedItem = localStorage.getItem(`item:${item.id}`);
if (cachedItem) {
const parse = JSON.parse(cachedItem); // Parse cached version
item.active = parse.active; // If the cached storage item is active
}
}
}
I'm so so sorry for not being able to adapt this to the code in the question. I faced this challenge and did not want to forget sharing.
say we have this in the typescript file;
movies: any[] = [
{ name: 'Wakanda', year: 2010, rating: 4.5 },
{ name: 'Super', year: 2000, rating: 5 },
{ name: 'Deli', year: 2002, rating: 3 },
{ name: 'Fury', year: 2020, rating: 4 },
];
isDisabled: boolean = false;
Then this in the HTML...
<div *ngFor="let movie of movies;index as i">
<div class="wrapper">
<button (click)="disableBtn('btn' + i)" [disabled]="isDisabled && isDisabled !== 'btn'+i"
id=btn{{i}}>Vote</button>
</div>
</div>
The disableBtn function takes the current button and assigns it to the isDisabled variable then [disabled] attribute checks if isDisabled is truthy and if isDisabled is strictly not equal to the current button. This will be true for all other buttons except the one clicked.
The function disables(toggles it) all other buttons except the one clicked.
disableBtn(btn) {
if (this.isDisabled === btn) { //also re-enables all the buttons
this.isDisabled = null;
return;
}
this.isDisabled = btn;
}
Have you tried the [disabled] property?
<button (click)="toggleActive(item, !item.active)" [disabled]="shouldDisable(item)">
shouldDisable(item): boolean {
return !item.active && this.statusdata.some((status) => status.active);
}
I have an event handler which updates state when a button is clicked which then renders the state to the page. The way this is rendered is not correct and is breaking my UI. My question is, Is it possible to apply formatting directly to the following event handler?
I have attempted to create a nested array so only 1 state is updated but no joy.
see this video for formatting issue I am having: https://www.screencast.com/t/HksUkk7g3G
I have also posted previously about this with the full code here.
React Nested Array
handleAddTelephone = () => {
this.setState({
telephoneType: this.state.telephoneType.concat([{ name: "" }]),
telephone: this.state.telephone.concat([{ name: "" }])
});
};
I need to format each state update. Something like...
handleAddTelephone = () => {
this.setState({
<span>telephoneType: this.state.telephoneType.concat([{ name: "" }])</span>,
<span>telephone: this.state.telephone.concat([{ name: "" }])</span>
});
};
This is my render function. The call to state needs to be around the MDBRow (Bootstrap row class). JSX will not allow this and Im currently using 2 separate calls, one for telephoneType and another for telephone.
<MDBRow className="grey-text no-gutters my-2">
{this.state.telephoneType.map((tt, ttidx) => (
<MDBCol key={ttidx} md="4" className="mr-2">
<select
defaultValue={tt.name}
onChange={this.handleTelephoneTypeChange(ttidx)}
className="browser-default custom-select">
<option value="Mobile">Mobile</option>
<option value="Landline">Landline</option>
<option value="Work">Work</option>
</select>
</MDBCol>
))}
{this.state.telephone.map((tn, tnidx) => (
<MDBCol key={tnidx} md="7" className="d-flex align-items-center">
<input
value={tn.name}
onChange={this.handleTelephoneChange(tnidx)}
placeholder={`Telephone No. #${tnidx + 1}`}
className="form-control"
/>
<MDBIcon icon="minus-circle"
className="mr-0 ml-2 red-text"
onClick={this.handleRemoveTelephone(tnidx)} />
</MDBCol>
))}
</MDBRow>
and the button...
<div className="btn-add" onClick={this.handleAddTelephone}>
<MDBIcon className="mr-1" icon="plus-square" />Add Telephone</div>
<br />
and array...
class InstallerAdd extends React.Component {
constructor() {
super();
this.state = {
role: "Installer",
name: "",
/* telephone: {
type: [{ name: "" }],
number: [{ name: "" }]
}, */
telephoneType: [{ name: "" }],
telephone: [{ name: "" }],
emailType: [{ email: "" }],
email: [{ email: "" }]
};
}
I was using buefy <b-autocomplete> component and there is one property called v-model which is binding values to the input field
now I wanna bind Full Name into the field but the data consist with list[index].first_name and list[index].last_name, and the index is from a v-for loop.
Since v-model cannot bind a function (it has specific index so I cannot just concat it on computed then pass it on) so it's either v-model="list[index].first_name" or v-model="list[index].last_name"
How do I make it bind's these two?
You need a settable computed for full name, and you can v-model that. You just have to decide on a rule for where extra spaces go and what to do if there is no space.
new Vue({
el: '#app',
data: {
firstName: 'Joe',
lastName: 'Smith'
},
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(newValue) {
const m = newValue.match(/(\S*)\s+(.*)/);
this.firstName = m[1];
this.lastName = m[2];
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
First: {{firstName}}<br>
Last: {{lastName}}<br>
Full Name: <input v-model="fullName">
</div>
I am not sure if i get the question,but i am assuming that you have a list of names and last names and you want to give the ability to user to change those proprties of list.For more See the example in action
The "html" part
<div id="app">
<template v-for="item in list" :key="list.id">
<input type="text" :value="item.name" #input="changeList($event, item.id, 'name')">
<input type="text" :value="item.last_name" #input="changeList($event, item.id, 'last-name')">
Your full name is {{item.name}} {{item.last_name}}
<hr>
</template>
</div>
The "javascript(vue)" part
new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "name1", last_name: 'last_name 1' },
{ id: 2, name: "name2", last_name: 'last_name 2' },
{ id: 3, name: "name3", last_name: 'last_name 3' },
{ id: 4, name: "name4", last_name: 'last_name 4' }
]
},
methods: {
changeList(event, id, property) {
let value = event.target.value
for (item of this.list) {
if (item.id === id) {
if(property === 'name') {
item.name = value
}else if (property === 'last-name') {
item.last_name = value
}
}
}
}
}
})
As it's been said you can't use 2 values in v-model
But if you want to use <b-autocomplete> that means you already have the data and you can compute it in any way you want.
If you have an array of user objects (with firstname and lastname for example) you can do something like:
data() {
return {
users: [
//...
],
computedUsers: [],
}
},
mounted() {
this.users.forEach(user => {
this.computedUsers.push(user.firstname + ' ' + user.lastname)
})
}
and use this new array in the filteredDataArray (from the Buefy autocomplete example)
Fiddle example here
Right now I'm stuck with pagination implementation with React. I have all the neccessary data from JSON, however I got no result.
Here's the code I use:
first, I fetch data from the server:
constructor() {
super();
this.state = {items: {}, totalPages: [], nextPage: []};
}
componentDidMount() {
let url = 'http://localhost:8000/items?json=true';
request.get(url).then((response) => {
this.setState({
items: response.body.items.data,
totalPages: response.body.items.last_page,
nextPage: response.body.items.next_page_url
});
});
}
Thus I get a simple JSON file:
{
"items": {
"total": 26025,
"per_page": 16,
"current_page": 1,
"last_page": 1627,
"next_page_url": "http://localhost:8000/items?page=2",
"prev_page_url": null,
"from": 1,
"to": 16,
"data": [
{
"id": 1,
...
},
{
"id": 2,
...
},
...
]
}
}
I successfully display items data in render method like this:
let items = _.map(this.state.items, (item) => {
return (
<div key={item.id}>
<div className="content">
<span>
{item.type}
</span>
...
</div>
</div>
)
});
and then return it like so:
return (
<div>
{items}
</div>
<div>
<a href={this.state.nextPage}>Next</a>
</div>
)
I can see that URL changes after I press Next button to page2 but there are two issues: I want to change items components based on JSON file when I click Next (i.e first page contains the first set of 16 elements, second page contains the second set) but there is no change and when I click Next button again but on the second page (according to URL) it doesn't get me to the third page and so on.
I know I need to somehow bind these state to page2 URL shows content described on the second page and I ran through tutorials but they seem to be outdated in case I use React 15.2.1.
I would appreciate any help or a thought that'd help me to solve it!
Add a click handler to your link element and pass the url as parameter. In the handler function make the ajax request and update the states using setState (similar to the one u did it on componentDidMount).
constructor() {
super();
this.state = {
items: [],
totalPages: '',
nextPage: ''
};
this._loadData = this._loadData.bind(this);
}
componentDidMount() {
const url = 'http://localhost:8000/items?json=true';
this._loadData(url);
}
_loadData(url) {
request.get(url).then((response) => {
this.setState({
items: response.body.items.data,
totalPages: response.body.items.last_page,
nextPage: response.body.items.next_page_url
});
});
}
render() {
let items = _.map(this.state.items, (item) => {
return (
<div key={item.id}>
<div className="content">
<span>
{item.type}
</span>
...
</div>
</div>
)
});
return (
<div>
{items}
</div>
<div>
<a href="#0" onClick={this._loadData(this.state.nextPage)}>Next</a>
</div>
)
}