I'm not understanding something about getting firebase data into vue... I'm happy to delete the question or quit my job as a programmer once I get an answer ;)
I get that the data is async, so vue is trying to render the component before the data has been received. But, I don't see how to make it wait. Examples would be greatly appreciated. I've tried reading the docs, but I'm just not understanding.
It's a tiny tiny app that displays our company's parking lot and saves the user's tap (car) location to firebase (we're tired of forgetting where we parked). Grateful for any help!
Oh, and I'm using set() because only one car location needs to be saved at a time, so it's ok that the data is overwritten each time the user taps a new place on the screen.
<template>
<div id="app">
<span id="marker" class="fas fa-times" :style="{ left: leftCoord, top: topCoord }"></span>
<img #click="saveCoordinates($event)" src="./assets/parking-lot.jpg">
</div>
</template>
<script>
import firebase from 'firebase'
const config = {
apiKey: 'MY-API-KEY',
authDomain: 'MY-APP.firebaseapp.com',
databaseURL: 'https://MY-APP.firebaseio.com',
projectId: 'MY-APP',
storageBucket: 'MY-APP.appspot.com',
messagingSenderId: '1234567890'
}
const app = firebase.initializeApp(config)
const db = app.database()
const locationRef = db.ref('location')
export default {
name: 'app',
firebase: {
location: locationRef
},
mounted () {
this.leftCoord = this.location.leftCoord
this.topCoord = this.location.topCoord
},
data () {
return {
leftCoord: '',
topCoord: ''
}
},
methods: {
saveCoordinates: function (clickEvent) {
const coordinates = {
leftCoord: clickEvent.layerX,
topCoord: clickEvent.layerY
}
this.location.set(coordinates)
}
}
}
</script>
This will do the trick:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://www.gstatic.com/firebasejs/4.11.0/firebase.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuefire/dist/vuefire.js"></script>
</head>
<body>
<div id="app">
<span id="marker" class="fas fa-times" :style="{ left: leftCoord, top: topCoord }"></span>
<img #click="saveCoordinates($event)" src="./assets/parking-lot.jpg">
<img #click="readCoords" src="./assets/parking-lot.jpg">
</div>
<script>
var config = {
apiKey: ".........",
authDomain: ".........",
databaseURL: ".........",
projectId: ".........",
storageBucket: "........."
};
/* global Vue, firebase */
var db = firebase.initializeApp(config).database()
var todosRef = db.ref('todos')
const locationRef = db.ref('location')
new Vue({
el: '#app',
name: 'app',
firebase: {
location: {
source: locationRef,
asObject: true
}
},
mounted() {
locationRef.once('value', snapshot => {
this.leftCoord = this.location.leftCoord
this.topCoord = this.location.topCoord
})
},
data() {
return {
leftCoord: '',
topCoord: ''
}
},
methods: {
saveCoordinates: function (clickEvent) {
console.log(clickEvent.layerX)
const coordinates = {
leftCoord: clickEvent.layerX,
topCoord: clickEvent.layerY
}
locationRef.set(coordinates)
},
readCoords: function (clickEvent) {
console.log(this.location.leftCoord);
console.log(this.location.topCoord);
}
}
})
</script>
</body>
</html>
Apparently there is no "native" way in vuefire to wait into a mounted hook that the data is loaded, see https://github.com/vuejs/vuefire/issues/69
I've added a second image link with a new function readCoords just for testing purpose.
Note that you could also do in the mounted hook
locationRef.once('value', snapshot => {
this.leftCoord = snapshot.val().leftCoord
this.topCoord = snapshot.val().topCoord
})
I add a second answer, which is not based anymore on the mounted hook but on the callback function of the object binding.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://www.gstatic.com/firebasejs/4.11.0/firebase.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuefire/dist/vuefire.js"></script>
</head>
<body>
<div id="app">
<span id="marker" class="fas fa-times" :style="{ left: leftCoord, top: topCoord }"></span>
<img #click="saveCoordinates($event)" src="./assets/parking-lot.jpg">
</div>
<script>
var config = {
apiKey: ".........",
authDomain: ".........",
databaseURL: ".........",
projectId: ".........",
storageBucket: "........."
};
/* global Vue, firebase */
var db = firebase.initializeApp(config).database()
var todosRef = db.ref('todos')
const locationRef = db.ref('location')
new Vue({
el: '#app',
name: 'app',
firebase: {
location: {
source: locationRef,
asObject: true,
readyCallback: function () {
this.leftCoord = this.location.leftCoord
this.topCoord = this.location.topCoord
}
}
},
data() {
return {
leftCoord: '',
topCoord: ''
}
},
methods: {
saveCoordinates: function (clickEvent) {
console.log(clickEvent.layerX)
const coordinates = {
leftCoord: clickEvent.layerX,
topCoord: clickEvent.layerY
}
locationRef.set(coordinates)
}
}
})
</script>
</body>
</html>
If location is an object, you need to bind like this:
export default {
name: 'app',
firebase: {
location: {
source: db.ref('location'),
asObject: true,
readyCallback: function () {}
}
},
mounted () {
this.leftCoord = this.location.leftCoord
this.topCoord = this.location.topCoord
},
data () {
return {
leftCoord: '',
topCoord: ''
}
},
methods: {
saveCoordinates: function (clickEvent) {
const coordinates = {
leftCoord: clickEvent.layerX,
topCoord: clickEvent.layerY
}
this.$firebaseRefs.location.set(coordinates)
}
}
}
Note that when setting, we need to use this.$firebaseRefs.location.set(coordinates)
Document https://github.com/vuejs/vuefire
Related
I have the following working code for a search input using options API for component data, watch and methods, I am trying to convert that to the composition api.
I am defining props in <script setup> and also a onMounted function.
<template>
<label for="search" class="hidden">Search</label>
<input
id="search"
ref="search"
v-model="search"
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm h-9 w-1/2"
:class="{ 'transition-border': search }"
autocomplete="off"
name="search"
placeholder="Search"
type="search"
#keyup.esc="search = null"
/>
</template>
<script setup>
import {onMounted} from "vue";
const props = defineProps({
routeName: String
});
onMounted(() => {
document.getElementById('search').focus()
});
</script>
<!--TODO convert to composition api-->
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
// page.props.search will come from the backend after search has returned.
search: this.$inertia.page.props.search || null,
};
},
watch: {
search() {
if (this.search) {
// if you type something in the search input
this.searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
this.$inertia.get(route(this.routeName));
}
},
},
methods: {
searchMethod: _.debounce(function () {
this.$inertia.get(
route(this.routeName),
{ search: this.search }
);
}, 500),
},
});
</script>
What I am trying to do is convert it to the composition api. I have tried the following but I can't get it to work at all.
let search = ref(usePage().props.value.search || null);
watch(search, () => {
if (search.value) {
// if you type something in the search input
searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
Inertia.get(route(props.routeName));
}
});
function searchMethod() {
_.debounce(function () {
Inertia.get(
route(props.routeName),
{search: search}
);
}, 500)
}
Any help or pointers in how to convert what is currently in <script> into <script setup> would be greatly appreciated thanks.
I managed to get this working with the below!
<script setup>
import {onMounted, ref} from "vue";
import {Inertia} from "#inertiajs/inertia";
const props = defineProps({
route_name: {
type: String,
required: true
},
search: {
type: String,
default: null
}
});
const search = ref(props.search);
onMounted(() => {
search.value.focus();
search.value.addEventListener('input', () => {
if (search.value.value) {
searching();
} else {
Inertia.get(route(props.route_name));
}
});
});
const searching = _.debounce(function() {
Inertia.get(route(props.route_name), {search: search.value.value});
}, 500);
</script>
I am currently using quasar v2(vuejs3) and the gold-layout 2.5.0 version.
(I also tried vue-golden-layout, but I gave up because it was difficult to use in quasar v2)
project structure
components
- TestCom.vue : simple component with only div and h2
- WebEditor.vue : part where the monaco editor is defined, and it works normally when the golden layout is not used.
layouts
- GoldenLayout.vue : The page where I want to use the Golden-layout package.
App.vue
I imported other components in Golden Layout.vue and registered in Golden-layout.
There is no error message when running, but the contents of the components are not visible.
Is there a right way to register the component in Golden layout?
layouts/GoldenLayout.vue
<template>
<div>
<link type="text/css" rel="stylesheet" href="//golden-layout.com/assets/css/goldenlayout-base.css" />
<link type="text/css" rel="stylesheet" href="//golden-layout.com/assets/css/goldenlayout-light-theme.css" />
<div ref ="test"></div>
</div>
</template>
<script>
import {shallowRef , ref, onMounted, onUnmounted, h } from "vue";
import { GoldenLayout } from "golden-layout/src/index";
import WebEditor from "components/WebEditor.vue";
import TestCom from "components/TestCom.vue"
export default {
name: 'App',
components: {
},
setup(props) {
let c = () => h(WebEditor, {
code : "import java.util.*;\nimport java.io.*;\n\npublic class Main{\n public static void main(String[] args) throws IOException {\n BufferedReader re = new BufferedReader(new InputStreamReader(System.in));\n \n int a = Integer.parseInt(re.readLine());\n int b = Integer.parseInt(re.readLine());\n\n System.out.println(a+b);\n re.close();\n }\n}",
language : "java",
readonly : false
});
let d = () => h(TestCom);
const test = ref(undefined);
let goldenLayout;
const config = {
content:
[
{
type: 'row',
content:
[
{
type: 'component',
componentName: 'WebEditor',
componentType : 'WebEditor'
},
{
type: 'component',
componentName: 'TestCom',
componentType : 'TestCom'
}
]
}
]
}
onMounted(() => {
goldenLayout = new GoldenLayout(test);
goldenLayout.registerComponent('WebEditor', c);
goldenLayout.registerComponent('TestCom', d);
goldenLayout.init();
goldenLayout.loadLayout(config);
});
onUnmounted(() => {
goldenLayout.destroy();
});
return {
test
};
}
};
</script>
<style scoped>
</style>
components/TestCom.vue
<template>
<div>
<h2 style="height:200px">Test</h2>
</div>
</template>
<script>
export default {
setup () {
return {}
}
}
</script>
<style lang="scss" scoped>
</style>
componets/WebEditor.vue
<template>
<div>
<div ref="editorDiv" style="height: 100%; width:100%"></div>
<div><h2 #click="updateEditor">refresh</h2></div>
</div>
</template>
<script>
// package.json
// "monaco-editor": "^0.33.0",
// "monaco-editor-webpack-plugin": "^7.0.1",
// quasar.confg
// const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
// module.exports = configure(function (/* ctx */) {
// return {
// plugins: [new MonacoWebpackPlugin()],
import { ref, onMounted } from "vue";
import * as monaco from 'monaco-editor';
export default {
// example
//
// <web-editor code="import java.util.*" language="java" :readOnly="false"></web-editor>
name : 'WebEditor',
props :{
code : String, // "import java.util.*;\nimport java.io.*;\n\npublic class Main{\n public static void main(String[] args) throws IOException {\n BufferedReader re = new BufferedReader(new InputStreamReader(System.in));\n \n int a = Integer.parseInt(re.readLine());\n int b = Integer.parseInt(re.readLine());\n\n System.out.println(a+b);\n re.close();\n }\n}"
language : String, // "java", "c", "python"
readOnly : Boolean, // "false"
},
setup (props) {
const editorDiv = ref(undefined);
let monacoEditor;
let editorCode = JSON.parse(JSON.stringify(props.code));
let editorLanguage = JSON.parse(JSON.stringify(props.language));
let editorReadOnly = JSON.parse(JSON.stringify(props.readOnly));
onMounted(() => {
monacoEditor = monaco.editor.create(editorDiv.value,{
// model: null,
readOnly: editorReadOnly,
value: editorCode,
language: editorLanguage,
// theme: 'vs', //light version
theme: 'vs-dark',
tabSize: 2,
fontFamily: "Consolas",
// fontFamily: 'D2Coding',
// fontFamily: 'IBM Plex Mono',
fontSize: 12,
});
});
const updateEditor = () => {
editorCode = monacoEditor.getValue();
monacoEditor.dispose();
monacoEditor = monaco.editor.create(editorDiv.value,{
// model: null,
readOnly: editorReadOnly,
value: editorCode,
// c,cpp,java,javascript,python
language: editorLanguage,
// theme: 'vs', //light version
theme: 'vs-dark',
tabSize: 2,
fontFamily: "Consolas",
fontSize: 12,
});
};
return {
editorDiv,
monacoEditor,
editorCode,
editorLanguage,
editorReadOnly,
updateEditor
};
}
}
</script>
<style lang="scss" scoped>
</style>
This is my first time asking a question in stackoverflow. If you feel that I made a mistake or lack explanation for the problem, please let me know right away.
In the following example, {{test}} doesn't get updated according to the input of the component. What am I doing wrong?
<html>
<body>
<Component v-model="test"></Component>
{{test}}
<script type="module">
import {createApp} from './node_modules/vue/dist/vue.esm-browser.prod.js';
const Component = {
props: {
modelValue: String,
},
emits: [
'update:modelValue',
],
template: `<input #keyup="updateValue">`,
methods: {
updateValue(event) {
this.$emit('update:modelValue', event.target.value);
},
},
};
const app = createApp({});
app.component('Component', Component);
app.mount('body');
</script>
</body>
</html>
You forget to declare test variable in data options.
const app = createApp({
data: () => {
return {
test: ''
}
}
});
when I use rollup configure vue3 environment ,i run it then use the component in the html file,it happen error
the code below
rollup.config.js this is rollup config file
const path = require('path')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const babel = require('rollup-plugin-babel')
const json = require('rollup-plugin-json')
const vue = require('rollup-plugin-vue')
const postcss = require('rollup-plugin-postcss')
const inputPath = path.resolve(__dirname,'./src/index.js')
const outputUmdPath = path.resolve(__dirname,'./dist/imooc.datav.js')
const outputEsPath = path.resolve(__dirname,'./dist/imooc.datav.es.js')
module.exports = {
input:inputPath,
output:[{
file:outputUmdPath,
format:'umd',
name:'imoocDatav',
globals: {
vue: 'Vue'
}
},{
file:outputEsPath,
format:'es',
globals: {
vue: 'Vue'
}
}],
plugins:[
vue(),
babel({
exclude:'node_modules/**',
presets: ["#vue/babel-preset-jsx"]
}),
resolve(),
commonjs(),
json(),
// vue(),
postcss({
plugins:[]
})
],
external:['vue']
}
Test.vue this is vue3 component
<template>
<div class="text">{{ aa }}</div>
</template>
<script>
export default {
name: 'Test',
setup() {
const aa = 'hello';
return {
aa,
};
},
};
</script>
<style lang="scss" scoped>
.text {
color: red;
}
</style>
index.js the code make an global component
import Test from './Test.vue'
export default function(Vue){
Vue.component(Test.name,Test);
}
index.html this is html file
<!DOCTYPE html>
<html>
<head>
<title>imooc datav libs example</title>
<script src="https://cdn.jsdelivr.net/npm/vue#3.0.0-beta.6/dist/vue.global.js"></script>
<script src="../dist/imooc.datav.js"></script>
</head>
<body>
<div id="app">
{{message}}
<test></test>
</div>
<script>
Vue.createApp({
setup(){
var message = 'hello world';
return {
message
}
}
}).use(imoocDatav).mount('#app');
</script>
</body>
</html>
vue.global.js:4877 TypeError: Cannot read property 'aa' of undefined
package.json dependencies:
"vue": "^3.0.0"
Change:
external:['vue']
to
external:['#vue']
This works for me
I'm trying to sort my data by using .orderByChild, but the data are filter by the name of the node.
I added this on Firebase :
"concerts": {
".indexOn": "expires"
}
And here is my code in my index.js file :
loadConcerts ({commit}) {
commit('setLoading', true)
firebase.database().ref("concerts").orderByChild("expires").startAt(Date.now()/1e3).limitToFirst(7).once("value")
.then((data) => {
const concerts = []
const obj = data.val()
for (let key in obj) {
concerts.push({
expires: obj[key].expires,
...
})
}
commit('setLoadedConcerts', concerts)
commit('setLoading', false)
})
I also add this :
getters: {
loadedConcerts (state) {
return state.loadedConcerts.sort((concertA, concertB) => {
return concertA.expires > concertB.expires
})
},
loadedConcert (state) {
return (concertId) => {
return state.loadedConcerts.find((concert) => {
return concert.id === concertId
})
}
Maybe someone has a clue ? Thank you
In order to list the value in the correct order (by expires) you should use the forEach() method and not use directly data.val().
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://www.gstatic.com/firebasejs/5.5.8/firebase.js"></script>
</head>
<body>
<script>
// Initialize Firebase
var config = {
apiKey: '......',
authDomain: '......',
databaseURL: '......',
projectId: '......',
storageBucket: '......'
};
firebase.initializeApp(config);
firebase
.database()
.ref('past')
.orderByChild('expires')
.startAt(Date.now() / 1e3)
.limitToFirst(7)
.once('value')
.then(data => {
const concerts = []
data.forEach(element => {
console.log(element.val());
concerts.push({ id: element.key, expires: element.val().expires});
});
});
</script>
</body>
</html>
PS: if you want to order with the reverse order, just store in another field the value of -1*expires