I'm trying to add a custom view with some administrative utilities to Spring Boot Admin. The idea is to implement these as endpoints in Springboot Admin and call these endpoints from my custom view, but I don't know how to make a call to the server itself.
When a custom view has parent: 'instances' it will get an axios client for connecting to the current instance, but since the view I'm building isn't tied to a specific instance it doesn't have this. I'm aware I can install axios as a dependency, but I'd like to avoid that if possible to reduce build times. Since SBA itself depends on axios it seems I shouldn't have to install it myself.
Based on this sample, this is what I have right now:
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: {
type: Array,
required: true
}
},
data: () => ({ exampleResponse: "No response" }),
async created() {
const response = await this.axios.get("example");
this.exampleResponse = response.response;
},
};
</script>
ExampleController.kt
#RestController
#RequestMapping("/example")
class ExampleController {
#GetMapping
fun helloWorld() = mapOf("response" to "Hello world!")
}
Console says that it can't read property get of undefined (i.e. this.axios is undefined). Text reads "GET /example: No response"
I'm not sure if this is the best way to do it, but it is a way.
I noticed that I do have access to the desired axios instance within the SBA.use { install(...) { } } block, and learned that this can be passed as a property down to the view.
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry, axios}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
// this is where we pass it down with the props
// first part is the name, second is the value
props: { "axios": axios },
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: { type: Array, required: true },
// this is where we retrieve the prop. the name of the field should
// correspond to the name given above
axios: { type: Object, required: true },
},
data: () => ({
exampleResponse: "No response",
}),
async created() {
// Now we can use our axios instance! And it will be correctly
// configured for talking to Springboot Admin
this.axios.get("example")
.then(r => { this.exampleResponse = r.data.response; })
.catch(() => { this.exampleResponse = "Request failed!" });
},
};
</script>
Based on the code given, it looks like you don't have axios initialized to how you want to use it.
You're calling it via this.axios but it's not in your component i.e
data() {
return {
axios: require("axios") // usually this is imported at the top
}
}
or exposed globally i.e
Vue.prototype.axios = require("axios")
You can simply just import axios and reference it.
<script>
import axios from 'axios';
export default {
created() {
axios.get()
}
}
</script>
Related
I am new to Vue and stuck. I am trying to send user input data from a form into a vuex store. From that vuex store, an action will be called (fetching from API) and I would like that data back into my app and components.
<template>
<div>
<h1>APP NAME</h1>
<form action="submit" #submit.prevent="sendCityName()">
<label for="query"></label>
<input
type="text"
id="query"
v-model="cityName"
>
<button type="submit">Submit</button>
</form>
<h3>{{ lat }}</h3>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
cityName: ''
}
},
computed: {
coordinates () {
return this.$store.state.lat
}
},
methods: {
sendCityName() {
this.$store.commit('fetchCity', this.cityName)
}
},
}
</script>
Here is my index.vue and getting the error "Cannot read properties of undefined (reading 'commit')"
here is my store.js. I want to use the lat and lon across my app.
export const state = () => ({
lat: '',
lon: ''
})
export const mutations = {
SET_LAT(state, payload){
state.lat = payload
},
SET_LON(state, payload){
state.lon = payload
}
}
export const actions = {
async fetchCity({ commit }, cityName) {
// make request
axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "xxxxxxx",
q: cityName,
}
}).then((response) => {
commit('SET_LAT', response.data[0].lat);
commit('SET_LON', response.data[0].lng);
});
},
};
When I button submit I get the error "Cannot read properties of undefined (reading 'commit')"
Here is my working repo with the fixes mentioned below.
There are 3 things in your code:
remove vuex from package.json and run yarn again, that one is already baked into Nuxt as stated in the official documentation, those are the only steps needed
all the files inside of store will be namespaced by default for you, since you do have store/store.js, the proper syntax will be
async sendCityName() {
await this.$store.dispatch('store/fetchCity', this.cityName) // 👈🏻 store prefix
}
since you do use the axios module, you should have the following in your action (using the async/await syntax since it's more modern and preferable)
async fetchCity({ commit }, cityName) {
const response = await this.$axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "3d91ba5b3c11d13158a2726aab902a0b",
q: cityName,
}
})
commit('SET_LAT', response.data[0].lat)
commit('SET_LON', response.data[0].lng)
}
Looking at the browser's console, you also have some errors to fix.
I can also recommend an ESlint + Prettier configuration so that you keep your code error-proof + properly formatted at all times.
I'm currently working on a notification plugin in my project. I want every component to be able to trigger a notification like this:
this.$notification({
msg: 'error message'
});
To achieve that, I created a following plugin with a component:
Notifications.js
import Notifications from './Shared/Components/Notifications';
import Vue from 'vue';
export default {
install: (app, options) => {
app.component('notifications', Notifications)
Vue.prototype.$notification = function (options) {
Notifications.methods.show(options);
}
},
}
Notifications.vue
<template>
<div>
{{ message }}
</div>
</template>
<script>
export default {
name: "Notifications",
data() {
return {
message: '',
}
},
methods: {
show(options) {
this.message = options.msg;
}
}
}
</script>
But now, when another component triggers the plugin the view of the notifications.vue component does not change. But if I log the new value this.message it shows the new value. How do I achieve, that the new message "error message" is shown?
I already tried to watch the property, but it does not get triggered.
I'm having small issue with provide/inject in my project.
In App.vue, I'm pulling data from DB and pushing it into object. With console log I checked and all data it's there.
<template>
<router-view />
</template>
<script>
export default {
provide() {
return {
user: this.user,
};
},
data() {
return {
user: '',
};
},
methods: {
///pulling data from DB
func() {
fetch("url")
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => {
const user = [];
for (const id in data) {
user.push({
id: data[id].user_id,
firstName: data[id].user_firstname,
lastName: data[id].user_lastname,
email: data[id].user_email,
phone: data[id].user_phone,
address1: data[id].user_address_1,
address2: data[id].user_address_2,
address3: data[id].user_address_3,
address4: data[id].user_address_4,
group: data[id].user_group,
});
}
this.user = user;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.func();
},
};
</script>
Console log of object user App.vue
Object { id: "3", firstName: "test", lastName: "test", … }
Next I'm injecting it into component. Object inside component exists, but empty - all data cease to exist.
<script>
export default {
inject: ["user"],
};
</script>
console log of object user in component
<empty string>
While in App.vue data is still there, in any components object appears to be empty, but it is there. Any idea why?
Thanks for help.
In short, this happens because you are reassigning user rather than changing user.
Let's say you have a Child component that consumes your inject data and renders the users in a list:
<template>
<div> Child </div>
<ul>
<li v-for="user in users" :key="user.id"> {{user.name}} </li>
</ul>
</template>
<script>
import {inject} from "vue";
export default {
name: "Child",
setup() {
const users = inject("users");
return {users};
}
}
</script>
To provide the users from parent component, all you need to ensure is that users itself is a reactive object, and you keep changing it from the parent rather than reassigning it.
I am going to use the composition api to illustrate what I mean. Compared to options api, everything in composition api is just plain javascript hence there is a lot less behind-the-scene magic. At the end I will tell you how options api is related to the composition api.
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {reactive, provide, toRefs} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const data = reactive({users: ""});
const generateUsers = () => {
// notice here you are REASSIGNING the users
data.users = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(data.users);
}
// this way of provide will NOT work
provide("users", data.users);
// this way works because of toRefs
const {users} = toRefs(data);
provide("users", users);
return {generateUsers};
}
}
</script>
A few things to note:
the data options in the options api is exactly the same as const data = reactive({users: ""}). Vue will run your data() method, from where you have to return a plain object. And then Vue will automatically call reactive to add reactivity to it.
provide, on the other hand, is not doing any magic - neither in options api, nor in the composition api. It just passes whatever it is given to the consuming component without any massaging.
the reason provide("users", data.users) does not work as you would expect is that the way you populate the users is not a change to the same data.users object (which actually is reactive), but a reassign all together.
the reason toRefs works is because toRefs links to the original parent.
With this understanding in mind, to fix your original code, you just need to ensure you change, instead of reassigning, the users. The simplest way is to define user as an array and push into it when you load data. (in contrast to defining it initially as a string and reassigning it later)
P.S. what also works in composition api, and is a lot simpler, is to:
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {ref, provide} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const users = ref();
const generateUsers = () => {
// notice here you are not reassigning the users
// but CHANGING its value
users.value = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(users.value);
}
provide("users", users);
return {generateUsers};
}
}
</script>
Using Vue TreeSelect Plugin to load a nested list of nodes from firebase backend. It's doc page says,
It's also possible to have root level options to be delayed loaded. If no options have been initially registered (options: null), vue-treeselect will attempt to load root options by calling loadOptions({ action, callback, instanceId }).
loadOptions (in my App.vue) dispatch vuex action_FolderNodesList, fetches (from firebase) formats (as required by vue-treeselect), and mutates the state folder_NodesList, then tries to update options this.options = this.get_FolderNodesList but this does not seems to work.
Here is the loadOptions method (in app.vue)
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
Vue errors out with Invalid prop: type check failed for prop "options". Expected Array, got String with value ""
I am not sure what am I doing wrong, why that does not work. A working Codesandbox demo
Source
App.vue
<template>
<div class="section">
<div class="columns">
<div class="column is-7">
<div class="field">
<Treeselect
:multiple="true"
:options="options"
:load-options="loadOptions"
:auto-load-root-options="false"
placeholder="Select your favourite(s)..."
v-model="value" />
<pre>{{ get_FolderNodesList }}</pre>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Treeselect from "#riophae/vue-treeselect";
import "#riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
data() {
return {
value: null,
options: null,
called: false
};
},
components: {
Treeselect
},
computed: mapGetters(["get_FolderNodesList"]),
methods: {
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
}
};
</script>
Store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
folder_NodesList: ""
},
getters: {
get_FolderNodesList(state) {
return state.folder_NodesList;
}
},
mutations: {
mutate_FolderNodesList(state, payload) {
state.folder_NodesList = payload;
}
},
actions: {
action_FolderNodesList({ commit }) {
fmRef.once("value", snap => {
var testObj = snap.val();
var result = Object.keys(testObj).reduce((acc, cur) => {
acc.push({
id: cur,
label: cur,
children: recurseList(testObj[cur])
});
return acc;
}, []);
commit("mutate_FolderNodesList", result);
});
}
}
});
Any help is appreciated.
Thanks
It seems you are calling this.options which would update the entire element while only the current expanding option should be updated.
It seems loadOptions() is called with some arguments that you can use to update only the current childnode. The first argument seems to contain all the required assets so I wrote my loadTreeOptions function like this:
loadTreeOptions(node) {
// On initial load, I set the 'children' to NULL for nodes to contain children
// but inserted an 'action' string with an URL to retrieve the children
axios.get(node.parentNode.action).then(response => {
// Update current node's children
node.parentNode.children = response.data.children;
// notify tree to update structure
node.callback();
}).catch(
errors => this.onFail(errors.response.data)
);
},
Then I set :load-options="loadTreeOptions" on the <vue-treeselect> element on the page. Maybe you were only missing the callback() call which updates the structure. My installation seems simpler than yours but it works properly now.
I have a basic CRUD rails 5.2 API with a Story model. I am building a Vuejs front end to consume it. Currently, The index view at /stories successfully pulls in data from the server. I can also add stories to the database via NewStory.vue at stories/new. I am trying now to show a single story on a page at stories/:id. The api server currently shows the single result I need at v1/stories/:id.
here is what I have at services/Api.js:
import axios from 'axios'
export default() => {
return axios.create({
baseURL: `http://localhost:3000/v1`
})
}
in StoriesService.js:
import Api from '#/services/Api'
export default {
fetchStories () {
return Api().get('stories')
},
addStory (params) {
return Api().post('stories', params)
},
getStory (params) {
return Api().get('/stories/1')
}
}
in ViewStory.vue:
<template>
<div class="stories">
<h1>Story</h1>
<div v-if="story" class="table-wrap">
<div>
<router-link v-bind:to="{ name: 'NewStory' }" class="">Add
Story</router-link>
</div>
<p>Title: {{story.attributes.title}}</p>
<p>Overview: {{story.attributes.description}}</p>
</div>
<div v-else>
The story with id:{{params}} does not exist <br><br>
<!-- <router-link v-bind:to="{ name: 'NewStory' }"
class="add_story_link">Add Story</router-link> -->
</div>
</div>
</template>
<script>
import StoriesService from '#/services/StoriesService'
export default {
name: 'story',
data () {
return {
title: '',
description: ''
}
},
mounted () {
this.getStory()
},
methods: {
async getStory (params) {
const response = await StoriesService.getStory(params)
this.story = response.data
console.log(this.story)
}
}
}
</script>
With the id of the record hard coded, in the Network tab, I see the request being made to the api and the correct record being retrieved.
However, if I change the getStory call to return Api().get('/stories/', params) I get a 304 response and can't retrieve data.
My question is how to get StoriesService.js to return localhost:3000/v1/stories/params.id, where params.id is the id of the story referenced in the url.
Currently you are not passing in any params to getStory, so you need to get them from the this.$route.params
async getStory () {
const response = await StoriesService.getStory(this.$route.params)
this.story = response.data
console.log(this.story)
}
Beside that axios only supports query string parameters so if your url looks like /stories/someId then you need to build it yourself in getStory:
getStory (params) {
return Api().get(`/stories/${params.id}`)
}
Additionally your data object is missing the story property:
data () {
return {
story: null,
title: '',
description: ''
}
},