Vue - Load External Components From Another Site - vue.js

I am trying to retrieve a vue component from an external site and load it in my app and cannot for the life of me figure this out.
Here is the full scenario;
Site A (Internally Hosted Site) loads a VueJS application
Site A requests a VueJS component from Site B (Internally Hosted Site, different sub domain)
Ideally, Site B would consist of .vue files either in their native .vue format or precompiled (maybe vue-cli for this?)
Site A loads and renders the components in the application
Let's say I have a very simplistic component I want to load from Site B;
{
"template": "<div><span>{{message}}</span></div>",
"data": function() {
return { "message": "Hello World" };
}
}
I've tried the following this with mixed results;
Inside my index.ts (using TypeScript)
Vue.component('my-external-component', () => (Vue as any).http.get("http://test.local/test.js"));
and inside my .vue file
<my-external-component></my-external-component>
This results in this comment being added to the DOM
<!--function (a, b, c, d) { return createElement(vm, a, b, c, d, true); }-->
however adding
<component :is="my-external-component"></component>
Results in an empty comment being added to the DOM
<!---->
I have also done a couple variations of this including using the cli to compile the template into a js file and attempting to load that, weird error about not being able to access template.trim or something along those lines.

Given that your component definition is (or was) JSON and component data should be a function, you should transform the result before resolving the async component.
For example (using fetch because I don't know what Vue.http is)
Vue.component('MyExternalComponent', () =>
fetch('http://test.local/test.json').then(res => res.json()).then(defn => ({
...defn,
data () { // transform defn.data into a function
return defn.data
}
}))
)
JSFiddle demo here using their /echo/json service to simulate the external component definition.
Exporting a component definition from a remote .js file would be tricky.

Related

Nuxt.js footer component is not executing code to retrieve data

We have a nuxt.js application which retrieves data from strapi. this works for all the other pages that we've created, but when we try to retrieve data for the <Footer /> it appears that the code is not executing.
This is the code that we use for retrieving on the index page:
export default {
async asyncData({ $strapi }) {
return {
homepage: await $strapi.find("homepage"),
};
},
}
All we change from page to page is the variable name and the value its finding.
This works on 10 - 12 pages.
On main pages we are able to retrieve the data of the footer with this code:
export default {
async asyncData({ $strapi }) {
return {
footer: await $strapi.find("footer")
};
},
}
However when we put this code in our footer component it doesn't appear to execute, as no variable was shown in the view explorer, and if we try an render anything form the {{footer}} then we get an error saying we've referenced something that doesn't exist.
Is there any reason why this code isn't executing in the footer component?
The asyncData hook can only be used on page components. The official documentation explains how you can work around this issue:
asyncData is only available for pages and you don't have access to this inside the hook.
Use the new fetch hook that is available in Nuxt 2.12 and later versions.
Make the API call in the mounted hook and set data properties when loaded. Downside: Won't work for server side rendering.
Make the API call in the asyncData method of the page component and pass the data as props to the sub components. Server rendering will work fine. Downside: the asyncData of the page might be less readable because it's loading the data for other components.

how do I open a file when Vue.js router path is /apple-app-site-association

I have a Vuejs project with the following folder structure
https://ibb.co/dpGPBNw
I would like to open the file named apple-app-site-association inside the path static/config which is outside my src folder, using vue-router routing.
That means when a user redirects to https://myDomain/apple-app-site-association, the file static/config/apple-app-site-association should open up instead of routing to a component (which is how vue-router usually handles routing)
I require this for something called as deep-linking, i.e when a user navigates to https://myDomain/apple-app-site-association, the mobile app should open instead of navigating to the browser.
I do not know exactly how deep-linking works, but my mobile-app developer instructed that this is how we can achieve it.
As far as I know, Vue router, which is in charge of all routes within application, can't redirect you outside of it. You can, however, create component, that will only contain this specific document, and then you can point specific route to this component.
Regarding deep-linking it only means you get straight at some point within your app and not to the home page.
This is how I solved:
Option 1:
Create a component that will open the file:
<template>
<div></div>
</template>
<script>
export default {
name: 'WellKnown',
props: {
file: String
},
mounted () {
window.open(this.file)
}
}
</script>
Create a route in your vue router that uses that component and passes the file url:
{
path: '/.well-known/apple-app-site-association',
component: WellKnown,
props: {
file: '<URL to your file>'
}
}
Deploy your app, whenever you go to <yourapp>/.well-known/apple-app-site-association you should be promp to download that file (the expected result)
Thats it.
Notes:
The component created in step 1 receives the file URL as prop. That prop can be passed from the route definition it self.
The file can be hosted whereever you want, what's important is that can be accesable from the url you defined in the router.
Option 2:
This one is actually more straight forward. Place the file in your public directory:
And for the apple-app-site-association file change the content-type header to application/pkcs7-mime. More info here
Hope this makes sense and help others.

Prevent JSON file from becoming part of chunk.js/app.js when I build my vue app

I have a JSON file that I included in my vue app. But the problem is that when I build the app, the JSON file disappears and becomes a part from a js file.
How can I keep it as a JSON file after build, since I want to manipulate it?
If you are using Vue CLI 3, you can place your static files in public directory and reference them by absolute paths.
For more information you can visit the CLI documents.
Update
Place your JSON file in public directory
Import axios or any similar tools you use for AJAX calls.
You need a .env file in your project root. And store your base url in BASE_URL variable. (Read More!)
Add a data property containing your base URL:
data() {
return {
baseUrl: process.env.BASE_URL,
}
},
And then you can access you JSON value with an ajax call:
axios.get(this.baseUrl + 'test.json').then(response => {
console.log(response);
})
I think you want to configure the externals option.

Load AJAX in Framework7, vue

I'm very new to Framework7, and want to build a fairly simple mobile app -- a list of places, detail pages of those places (where some murals are displayed), and a map and about page. My current plan is to publish it via PhoneGap Build, since that seems like a fast and easy way to deploy.
I created my app using the phonegap-framework7-vue template. Perhaps overkill for such a simple app, but seems better than building it from scratch.
I want to load a list of places via AJAX (eventually via sqlite), and can't figure out how/when to do this, and how to access the main app. My Murals.vue file has the template and the following script, but doesn't load because app.request is undefined. I've tried "framework7", "Framework7", and moving it outside of the mounted() call, but feel like I'm just guessing. Any suggested? Thanks!
<script>
import F7List from "framework7-vue/src/components/list";
let dataURL = 'https://jsonplaceholder.typicode.com/posts'; // returns some json
export default {
name: 'Murals',
components: {F7List},
mounted() { // when the Vue app is booted up, this is run automatically.
app.request.get(dataURL, function (data) {
console.log(data);
});
},
data () {
return {
title: 'Murals'
};
}
};
</script>
You're code is almost right !
To access to F7 app instance with vue, you have to use this.$f7.request rather the app.request

Vue-multiselect inconsistent reactive options

So I'm building an application using Laravel Spark, and therefore taking the opportunity to learn some Vue.js while I'm at it.
It's taken longer for me to get my head around it than I would have liked but I have nearly got Vue-multiselect working for a group of options, the selected options of which are retrieved via a get request and then updated.
The way in which I've got this far may well be far from the best, so bear with me, but it only seems to load the selected options ~60% of the time. To be clear - there are never any warnings/errors logged in the console, and if I check the network tab the requests to get the Tutor's instruments are always successfully returning the same result...
I've declared a global array ready:
var vm = new Vue({
data: {
tutorinstruments: []
}
});
My main component then makes the request and updates the variable:
getTutor() {
this.$http.get('/get/tutor')
.then(response => {
this.tutor = response.data;
this.updateTutor();
});
},
updateTutor() {
this.updateTutorProfileForm.profile = this.tutor.profile;
vm.tutorinstruments = this.tutor.instruments;
},
My custom multiselect from Vue-multiselect then fetches all available instruments and updates the available instruments, and those that are selected:
getInstruments() {
this.$http.get('/get/instruments')
.then(response => {
this.instruments = response.data;
this.updateInstruments();
});
},
updateInstruments() {
this.options = this.instruments;
this.selected = vm.tutorinstruments;
},
The available options are always there.
Here's a YouTube link to how it looks if you refresh the page over and over
I'm open to any suggestions and welcome some help please!
Your global array var vm = new Vue({...}) is a separate Vue instance, which lives outside your main Vue instance that handles the user interface.
This is the reason you are using both this and vm in your components. In your methods, this points to the Vue instance that handles the user interface, while vm points to your global array that you initialized outside the Vue instance.
Please check this guide page once more: https://v2.vuejs.org/v2/guide/instance.html
If you look at the lifecycle diagram that initializes all the Vue features, you will notice that it mentions Vue instance in a lot of places. These features (reactivity, data binding, etc.) are designed to operate within a Vue instance, and not across multiple instances. It may work once in a while when the timing is right, but not guaranteed to work.
To resolve this issue, you can redesign your app to have a single Vue instance to handle the user interface and also data.
Ideally I would expect your tutorinstruments to be loaded in a code that initializes your app (using mounted hook in the root component), and get stored in a Vuex state. Once you have the data in your Vuex state, it can be accessed by all the components.
Vuex ref: https://vuex.vuejs.org/en/intro.html
Hope it helps! I understand I haven't given you a direct solution to your question. Maybe we can wait for a more direct answer if you are not able to restructure your app into a single Vue instance.
What Mani wrote is 100% correct, the reason I'm going to chime in is because I just got done building a very large scale project with PHP and Vue and I feel like I'm in a good position to give you some advice / things I learned in the process of building out a PHP (server side) website but adding in Vue (client side) to the mix for the front end templating.
This may be a bit larger than the scope of your multiselect question, but I'll give you a solid start on that as well.
First you need to decide which one of them is going to be doing the routing (when users come to a page who is handling the traffic) in your web app because that will determine the way you want to go about using Vue. Let's say for the sake of discussion you decide to authenticate (if you have logins) with PHP but your going to handle the routing with Vue on the front end. In this instance your going to want to for sure have one main Vue instance and more or less set up something similar to this example from Vue Router pretending that the HTML file is your PHP index.php in the web root, this should end up being the only .php file you need as far as templating goes and I had it handle all of the header meta and footer copyright stuff, in the body you basically just want one div with the ID app.
Then you just use the vue router and the routes to load in your vue components (one for each page or category of page works easily) for all your pages. Bonus points if you look up and figure using a dynamic component in your main app.vue to lazy load in the page component based on the route so your bundle stays small.
*hint you also need a polyfill with babel to do this
template
<Component :is="dynamicComponent"/>
script
components: {
Account: () => import('./Account/Account.vue'),
FourOhFour: () => import('../FourOhFour.vue')
},
computed: {
dynamicComponent() {
return this.$route.name;
}
},
Now that we are here we can deal with your multiselect issue (this also basically will help you to understand an easy way to load any component for Vue you find online into your site). In one of your page components you load when someone visits a route lets say /tutor (also I went and passed my authentication information from PHP into my routes by localizing it then using props, meta fields, and router guards, its all in that documention so I'll leave that to you if you want to explore) on tutor.vue we will call that your page component is where you want to call in multiselect. Also at this point we are still connected to our main Vue instance so if you want to reference it or your router from tutor.vue you can just use the Vue API for almost anything subbing out Vue or vm for this. But the neat thing is in your main JS file / modules you add to it outside Vue you can still use the API to reference your main Vue instance with Vue after you have loaded the main instance and do whatever you want just like you were inside a component more or less.
This is the way I would handle adding in external components from this point, wrapping them in another component you control and making them a child of your page component. Here is a very simple example with multiselect pretend the parent is tutor.vue.
Also I have a global event bus running, thought you might like the idea
https://alligator.io/vuejs/global-event-bus/
tutor.vue
<template>
<div
id="user-profile"
class="account-content container m-top m-bottom"
>
<select-input
:saved-value="musicPreviouslySelected"
:options="musicTypeOptions"
:placeholder="'Choose an your music thing...'"
#selected="musicThingChanged($event)"
/>
</div>
</template>
<script>
import SelectInput from './SelectInput';
import EventBus from './lib/eventBus';
export default {
components: {
SelectInput
},
data() {
return {
profileLoading: true,
isFullPage: false,
isModalActive: false,
slackId: null,
isActive: false,
isAdmin: false,
rep: {
id: null,
status: '',
started: '',
email: '',
first_name: '',
},
musicTypeOptions: []
};
},
created() {
if (org.admin) {
this.isAdmin = true;
}
this.rep.id = parseInt(this.$route.params.id);
this.fetchData();
},
mounted() {
EventBus.$on('profile-changed', () => {
// Do something because something happened somewhere else client side.
});
},
methods: {
fetchData() {
// use axios or whatever to fetch some data from the server and PHP to
// load into the page component so say we are getting the musicTypeOptions
// which will be in our selectbox.
},
musicThingChanged(event) {
// We have our new selection "event" from multiselect so do something
}
}
};
</script>
this is our child Multiselect wrapper SelectInput.vue
<template>
<multiselect
v-model="value"
:options="options"
:placeholder="placeholder"
label="label"
track-by="value"
#input="inputChanged" />
</template>
<script>
import Multiselect from 'vue-multiselect';
export default {
components: { Multiselect },
props: {
options: {
type: [Array],
default() {
return [];
}
},
savedValue: {
type: [Array],
default() {
return [];
}
},
placeholder: {
type: [String],
default: 'Select Option...'
}
},
data() {
return {
value: null
};
},
mounted() {
this.value = this.savedValue;
},
methods: {
inputChanged(selected) {
this.$emit('selected', selected.value);
}
}
};
</script>
<style scoped>
#import '../../../../../node_modules/vue-multiselect/dist/vue-multiselect.min.css';
</style>
Now you can insure you are manging the lifecycle of your page and what data you have when, you can wait until you get musicTypeOptions before it will be passed to SelectInput component which will in turn set up Multiselect or any other component and then handle passing the data back via this.$emit('hihiwhatever') which gets picked up by #hihiwhatever on the component in the template which calls back to a function and now you are on your way to do whatever with the new selection and pass different data to SelectInput and MultiSelect will stay in sync always.
Now for my last advice, from experience. Resist the temptation because you read about it 650 times a day and it seems like the right thing to do and use Vuex in a setup like this. You have PHP and a database already, use it just like Vuex would be used if you were making is in Node.js, which you are not you have a perfectly awesome PHP server side storage, trying to manage data in Vuex on the front end, while also having data managed by PHP and database server side is going to end in disaster as soon as you start having multiple users logged in messing with the Vuex data, which came from PHP server side you will not be able to keep a single point of truth. If you don't have a server side DB yes Vuex it up, but save yourself a headache and wait to try it until you are using Node.js 100%.
If you want to manage some data client side longer than the lifecycle of a page view use something like https://github.com/gruns/ImmortalDB it has served me very well.
Sorry this turned into a blog post haha, but I hope it helps someone save themselves a few weeks.