NUXT pass object data into pages with path - vue.js

I'm really new to Vue and Nuxt so I apologise if this is a simple question.
I'm generating my routes dynamically on making an API call for my data on Index.vue. One API call is enough for me to retrieve all the data i need which is stored in deals_array, so I don't need another API call on my individual page, I just need the data from each deal in deals_array.
<ul>
<li v-for="deal of deals_array" :key="deal.id">
<nuxt-link :to="getSlug(deal)">{{ deal.name }}, {{deal.value}}</nuxt-link>
</li>
</ul>
I'm wondering how do I pass the entire deal object into my pages, so that when I click on the individual nuxt-link I would be able to access that deal object and all its attributes (for each page).
I've taken a look at passing params into nuxt-link but I understand that it only pairs with name attribute and not the path, where I need the path URL in this case.
I may be doing this entirely wrong so I'm hoping to be pointed in the right direction.
Edit - getSlug function
getSlug(deal) {
let name = deal.name;
let dealDetails = deal.details;
let name_hyphen = name.replace(/\s+/g, "-");
let deal_hyphen = dealDetails.replace(/\s+/g, "-");
let nameDealSlug = name_hyphen + "-" + deal_hyphen;
// remove selected special characters from slug
let clean_nameDealSlug = nameDealSlug.replace(
/[&\/\\#,+()$~%.'":*?<>{}]/g,
""
);
let finalSlug = `deals/${clean_nameDealSlug}`;
return finalSlug;
}

I'm assuming you have gone through this: https://router.vuejs.org/api/.
You can just pass the entire object:
<nuxt-link :to="{ path: 'test', query: {a: 1, b: 2}}">Test Page</nuxt-link>
And your URL will become something like this:
http://localhost:3000/test?a=1&b=2
The entire object can be simply passed.
This will be available to your next page in $route object in the url query.
Otherwise if you don't want to get your deal object exposed just use the concepts of vuex. Store the entire deal object in the vuex and pass ids to different pages. And from pages retrieve the deal object through vuex.

Related

How to store long expression in separate file?

I'm building a Vue app where a user reactively generates an HTML page from certain selections. Therefore, there is a very long expression that produces said HTML page. This expression/template is stored in a separate .html file. I would like to have that expression as a computed property in my app, but not sure what's the best way. I want to be able to use either the {{ }} template syntax, or at least the syntax you get inside directives, rather than have to plaster this. in front of every property and method I use, which is what I'd have to do if I move the template to JS-land (e.g. a separate module or just directly define it in the computed property).
Right now I got it working, but it's extremely hacky:
let appSpec = {
/* [snip] */
computed: {
/* other computed properties and… */
html () {
return getAppHTML(this);
}
},
};
let sheetTemplate = await (await fetch("sheet-template.html")).text();
let templateVars = [
...Object.keys(INITIAL_DATA),
...Object.keys(appSpec.computed),
...Object.keys(appSpec.methods)
];
let getAppHTML = new Function(`{${ templateVars.join(", ") }}`, "return `" + sheetTemplate + "`");
/* ... */
I’m thinking there must be a better way to do this.
I don't want to inject the expression directly into the places it's going to be used in my app (e.g. <iframe :srcdoc>), because I want to have a property that corresponds to it (for watchers etc).
Note that I'm using the in-browser API, no build step, and I'd rather keep it that way.

Is there a way to bind a variable number of queries?

I'm coding an app for managing shift work. The idea is pretty simple: the team is shared between groups. In those groups are specific shifts. I want to get something like that:
Group 1
- shift11
- shift12
- shift13
Group 2
- shift21
- shift22
- shift23
I already made a couple of tests, but nothing is really working as I would like it to: everything reactive, and dynamic.
I'm using vue.js, firestore (and vuefire between them).
I created a collection "shiftGroup" with documents (with auto IDs) having fields "name" and "order" (to rearrange the display order) and another collection "shift" with documents (still auto IDs) having fields "name", "order" (again to rearrange the display order, inside the group) and "group" (the ID of the corresponding shiftGroup.)
I had also tried with firestore.References of shifts in groups, that's when I was the closest to my goal, but then I was stuck when trying to sort shifts inside groups.
Anyway, with vuefire, I can easily bind shiftGroup like this:
{
data () {
return {
shiftGroup: [], // to initialize
}
},
firestore () {
return {
shiftGroup: db.collection('shiftGroup').orderBy('order'),
}
},
}
Then display the groups like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">{{group.name}}</li>
</ul>
So now time to add the shifts...
I thought I could get a reactive array of shifts for each of the groups, like that:
{
db.collection('shift').where('group', '==', group.id).orderBy('order').onSnapshot((querySnapshot) => {
this.shiftCollections[group.id] = [];
querySnapshot.forEach((doc) => {
this.shiftCollections[group.id].push(doc.data());
});
});
}
then I'd call the proper list like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">
{{group.name}}
<ul>
<li v-for="(shift, idx2) in shiftCollections[group.id]" :key="idx1+idx2">{{shift.name}}</li>
</ul>
</li>
</ul>
This is very bad code, and actually, the more I think about it, the more I think that it's just impossible to achieve.
Of course I thought of using programmatic binding like explained in the official doc:
this.$bind('documents', documents.where('creator', '==', this.id)).then(
But the first argument has to be a string whereas I need to work with dynamic data.
If anyone could suggest me a way to obtain what I described.
Thank you all very much
So I realize this is an old question, but it was in important use case for an app I am working on as well. That is, I would like to have an object with an arbitrary number of keys, each of which is bound to a Firestore document.
The solution I came up with is based off looking at the walkGet code in shared.ts. Basically, you use . notation when calling $bind. Each dot will reference a nested property. For example, binding to docs.123 will bind to docs['123']. So something along the lines of the following should work
export default {
name: "component",
data: function () {
return {
docs: {},
indices: [],
}
},
watch: {
indices: function (value) {
value.forEach(idx => this.$bind(`docs.${idx}`, db.doc(idx)))
}
}
}
In this example, the docs object has keys bound to Firestore documents and the reactivity works.
One issue that I'm trying to work through is whether you can also watch indices to get updates if any of the documents changes. Right now, I've observed that changes to the Firestore documents won't trigger a call to any watchers of indices. I presume this is related to Vue's reactivity, but I'm not sure.

Event handling after HTML injection with Vue.js

Vue is not registering event handler for HTML injected objects. How do I do this manually or what is a better way to work around my problem?
Specifically, I send a query to my server to find a token in text and return the context (surrounding text) of that token as it exists in unstructured natural language. The server also goes through the context and finds a list of those words that also happen to be in my token set.
When I render to my page I want all of these found tokens in the list to be clickable so that I can send the text of that token as a new search query. The big problem I am having is my issue does not conform to a template. The clickable text varies in number and positioning.
An example of what I am talking about is that my return may look like:
{
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
And the resulting output I am looking for is the sentence looks something like this in psuedocode:
When in the Course of <a #click='search("human events")'>human events</a>, it becomes necessary for <a #click='search("one people")'>one people</a> to dissolve the <a #click='search("political bands")'>political bands</a> which have connected
This is what I have tried so far though the click handler is not registered and the function never gets called:
<v-flex xs10 v-html="addlink(context.context, context.chunks)"></v-flex>
and in my methods section:
addlink: function(words, matchterms){
for(var index in matchterms){
var regquery = matchterms[index].replace(this.regEscape, '\\$&');
var query = matchterms[index];
var regEx = new RegExp(regquery, "ig");
words = words.replace(regEx, '<a href=\'#\' v-on:click.prevent=\'doSearch("'+ query +'")\'>' + query + '</a>');
}
return words;
}
As I said, this does not work and I know why. This is just showing that because of the nature of the problem is seems like regex is the correct solution but that gets me into a v-html injection situation. Is there something I can do in Vue to register the event handlers or can some one tell me a better way to load this data so I keep my links inline with the sentence and make them functional as well?
I've already posted one answer but I've just realised that there's a totally different approach that might work depending on your circumstances.
You could use event delegation. So rather than putting click listeners on each <a> you could put a single listener on the wrapper element. Within the listener you could then check whether the clicked element was an <a> (using event.target) and act accordingly.
Here's one way you could approach it:
<template>
<div>
<template v-for="segment in textSegments">
<a v-if="segment.link" href="#" #click.prevent="search(segment.text)">
{{ segment.text }}
</a>
<template v-else>
{{ segment.text }}
</template>
</template>
</div>
</template>
<script>
export default {
data () {
return {
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
},
computed: {
textSegments () {
const chunks = this.chunks
// This needs escaping correctly
const re = new RegExp('(' + chunks.join('|') + ')', 'gi')
// The filter removes empty strings
const segments = this.context.split(re).filter(text => text)
return segments.map(segment => {
return {
link: segment.match(re),
text: segment
}
})
}
},
methods: {
search (chunk) {
console.log(chunk)
}
}
}
</script>
I've parsed the context text into an array of segments that can then be handled cleanly using Vue's template syntax.
I've used a single RegExp and split, which will not discard matches if you wrap them in a capture group, (...).
Going back to your original example, v-html only supports native HTML, not Vue template syntax. So you can add events using onclick attributes but not #click or v-on:click. However, using onclick wouldn't provide easy access to your search method, which is scoped to your component.

Handle different markets (language / locale) in Angular 2 application using an Web Api

I could use some advice how I should handle different markets in my angular 2 application. By that I mean a new market (like the German market) where the language is in German, as an example. Right now, I have hardcoded the text inside the html (in english ofc) to make it easy for myself.
An example you see here:
<div class="row">
<h2>Booking number: {{bookingNumber}}</h2>
Your changes has been confirmed.
</div>
I have read something about pipes in angular 2, and i guess I should be using something like that. My problem is, that I really don't know where to start.
Already have an Web Api application created in Visual Studio 2015 which I can use and call.
I'm thinking of making two lists in my Web Api project (one for english, one for german), but there should still be some sort of indicator. By that I mean something like:
BOOKING_NUMBER_TEXT, 'the text in english or german'
CONFIRMATION_TEXT, 'the text...'
That list should have two params like, string string or something like that.. any idea how I could make this?
From my angular 2 application, I'm thinking of calling the api and given it an id (number, lets say 1 and 2, where 1 = english, 2 = germany)
My Web Api finds the correct list and sends it back as JSON.
Then I'm guessing of building a pipe my own where I can filter the words I set in the html. I'm thinking of something like:
<div class="row">
<h2>{{BOOKING_NUMBER_TEXT | 'PIPE NAME' }}: {{bookingNumber}}</h2>
{{CONFIRMATION_TEXT | 'PIPE NAME' }}.
</div>
So when it has name BOOKING_NUMBER_TEXT, it should look into the pipe which has the list object, and take out the text from the right one and place it instead.
Is that a good plan or can you maybe give any advice? (I'm don't want to use any translate angular 2 frameworks, because I have to do different things on each market)
Cheers :)
UPDATE
Ok.. I have created some test data and allowed it to be send via my Web Api. Here is how it looks.
public Dictionary<string, string> createEnglishLocaleKeys()
{
Dictionary<string, string> Locale_EN = new Dictionary<string, string>();
// Account Component
Locale_EN.Add("ACCOUNT_LOGIN_TEXT", "Login");
Locale_EN.Add("ACCOUNT_LOGOUT_TEXT", "Logout");
// Booking Component
Locale_EN.Add("BOOKING_ACTIVE_HEADER_TEXT", "ACTIVE BOOKINGS");
Locale_EN.Add("BOOKING_LOADING_TEXT", "Loading bookings");
Locale_EN.Add("BOOKING_NONACTIVE_HEADER_TEXT", "NON ACTIVE BOOKINGS");
Locale_EN.Add("BOOKING_NOPREBOOKING_TEXT", "You currently do not have any previous bookings");
// Booking List Component
Locale_EN.Add("BOOKING_LIST_BOOKINGNUMBER_TEXT", "Booking number");
Locale_EN.Add("BOOKING_LIST_LEAVING_TEXT", "Leaving");
Locale_EN.Add("BOOKING_LIST_RETURNING_TEXT", "Returning");
Locale_EN.Add("BOOKING_LIST_ROUTE_TEXT", "Route");
Locale_EN.Add("BOOKING_LIST_PASSENGERS_TEXT", "Passengers");
Locale_EN.Add("BOOKING_LIST_VEHICLETYPE_TEXT", "Vehicle type");
// Menu Component
// Passenger Component
// DepartureDate Component
// Confirmation Component
Locale_EN.Add("KEY_NOT_FOUND", "KEY NOT FOUND");
return Locale_EN;
}
Have created an LocaleController which takes a string locale "EN" or "DE" as parameter. Then I'm injecting a service for the controller, which will, based on the locale string choose which method to run (For now I'm only sending back the LocaleEN dictionary).
How can I create an value in my Angular 2 application which should be EN as default and should be changeable?
By changeable, you should be able to set it in the URL or some sort of, like:
localhost:3000/amendment?locale=DE
There are several things here:
You could HTTP content negotiation Conneg - See this link: http://restlet.com/blog/2015/12/10/understanding-http-content-negotiation/) and the Accept-Language to tell the server which messages to return.
You need to wait for messages to be there before displaying the screen with for example: <div ngIf="messages">…</div>
I don't think you need to implement a pipe to display messages if they are defined in a map (key / value): {{messages['SOME_KEY']}}
If messages correspond to list a custom filtering pipe can be implemented and used like that: {{messages | key:'SOME_KEY'}}
The implementation of this pipe could be:
#Pipe({name: 'key'})
export class KeyPipe implements PipeTransform {
transform(value, args:string[]) : any {
// Assuming the message structure is:
// { key: 'SOME_KEY', value: 'some message' }
return value.find((message) => {
return (message.key === args[0]);
});
}
}

Pass values to route

I have a list of items. When the user clicks on an item, the user will be taken to item details page.
I want to pass an object containing item details(like item's image URL) to the route. However, I don't want to expose it in the routes url.
If there were a way to do something like <a route-href="route: details; settings.bind({url: item.url})">${item.name}</a> that would be gold.
I have seen properties can be passed to a route if defined in the route configuration. However, I don't know how to change that from the template. Another way could be is to define a singleton and store the values there and inject the object to the destination route.
Is there a way to pass values to routes from view (like angular ui-routers param object)?
Okay so I figured out a way to achieve something closer to what I wanted:
Objective: Pass data to route without exposing them in the location bar.
Let's say, we have a list of users and we want to pass the username to the user's profile page without defining it as a query parameter.
In the view-model, first inject Router and then add data to the destination router:
goToUser(username) {
let userprofile = this.router.routes.find(x => x.name === 'userprofile');
userprofile.name = username;
this.router.navigateToRoute('userprofile');
}
Now when the route changes to userprofile, you can access the route settings as the second parameter of activate method:
activate(params, routeData) {
console.log(routeData.name); //user name
}
For those #Sayem's answer didn't worked, you can put any additional data (even objects) into setting property like this:
let editEmployeeRoute = this.router.routes.find(x => x.name === 'employees/edit');
editEmployeeRoute.settings.editObject = employeeToEdit;
this.router.navigateToRoute('employees/edit', {id: employeeToEdit.id});
So editObject will be delivered on the other side:
activate(params, routeConfig, navigationInstruction) {
console.log(params, routeConfig, navigationInstruction);
this.editId = params.id;
this.editObject = routeConfig.settings.editObject;
}
hopes this helps others encountering same problem as me. TG.