How to declare computed values in SFC/Composition in Vue? - vue.js

I'm trying to use computed fields in VueJs.
Previously it was achievable with:
{
props: {
left: 5,
right: 100,
},
computed: {
width: () {return this.right.value - this.left.value};
}
}
But with the new SFC/Composition syntax how do I do it?
<script setup>
defineProps({left: 5, right: 100});
defineComputed( //??
)
</script>
I've read this link but it doesn't explain it:
https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md

In the new setup syntax, you use computed to create a computed property:
import { computed } from 'vue'
const props = defineProps({left: 5, right: 100});
const width = computed(() => props.right - props.left);

Related

Add Vue component to canvas with fabric.js library

Instead of creating custom object shape, I already have it as a Vue component. Here is what I have tried:
<template>
<div>
<canvas ref="canvas"></canvas>
<button type="button" #click="addVueComponent()">Add to canvas</button>
</div>
</template>
<script>
import { fabric } from 'fabric';
import CustomComponent from "CustomComponent.vue";
export default {
mounted() {
this.canvas = new fabric.Canvas(this.$refs.canvas);
},
methods:
{
addVueComponent() {
var vueComponent = new Vue({
template: '<custom-component ref="customComp"></custom-component>',
components: { 'custom-component': CustomComponent},
});
vueComponent.$mount();
var customObj = new fabric.Rect({
width: 100,
height: 100,
left: 50,
top: 50,
});
customComponent._element = vueComponent.$refs.customComponent.$el;
this.canvas.add(customComponent);
}
}
}
but instead of a rendered component I am just getting black rectangle as a result.
Is it even possible to add Vue component to canvas and what is the way to do it?

How to change background color of a div using Vue 3?

I have this following line inside the template:
<div class="card" #click="!state.clicked" :style="state.style">
And inside the script this code:
<script lang="ts">
import { ref, computed, reactive } from "vue";
export default {
name: "Card",
setup() {
const state = reactive({
clicked: false,
style: computed(() => {
backgroundColor: state.clicked ? "red" : "white";
})
});
return {
state
};
}
};
</script>
But my color doesn't change. My clicked flag is toggling correctly, but I just can't applied the background color.
Not sure if this is a reactivity issue or just the way I set the background color.
Any ideas ?
Your computed propertie doesnt return anything and returning nothing means it automatically returns undefined. Wrap it in parenthesis
style: computed(() => ({
backgroundColor: state.clicked ? "red" : "white"
}))

Using custom model objects as provider for v-for in Vue?

new to Vue - learning as I go about exploring.
I'm trying to use a custom object as data source for a v-for situation. So far, not working - I admit I'm not sure how to access data by name in a Vue component.
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import VueKonva from "vue-konva";
Vue.use(VueKonva);
import RoomBuilder from "#/model/RoomBuilder";
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App),
data: () => ({
roomBuilder: new RoomBuilder()
})
}).$mount("#app");
The custom object, which may not be wired right into the data provider, is roomBuilder.
Then, in the component where I want to reference this object, I have this code:
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer>
<v-rect ref="background" :config="backgroundConfig"></v-rect>
<v-rect
v-for="room in roomBuilder.getRooms()"
:key="room.id"
:config="room.config"
></v-rect>
</v-layer>
</v-stage>
</template>
<script>
export default {
name: "RoomGrid",
data() {
return {
stageSize: {
width: 1000,
height: 1000
},
backgroundConfig: {
width: 50,
height: 50,
fill: "#2B2B2B"
}
};
},
mounted() {
this.$nextTick(function() {
window.addEventListener("resize", this.fitStageIntoParentContainer);
this.fitStageIntoParentContainer();
});
},
methods: {
fitStageIntoParentContainer() {
const parentElement = this.$parent.$el;
const background = this.$refs.background.getNode();
background
.setAttr("width", parentElement.clientWidth)
.setAttr("height", parentElement.clientHeight);
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss"></style>
The two POJOs are:
RoomBuilder.js
import Room from "#/model/Room";
class RoomBuilder {
construct() {
this.rooms = [new Room(1)];
}
drawOnto(konvaStage) {
console.log(konvaStage);
}
getRooms() {
return this.rooms;
}
}
export default RoomBuilder;
and
Room.js
class Room {
construct(id) {
this.id = id;
this.config = {
x: 10,
y: 10,
fill: "white",
width: 10,
height: 10
};
}
}
export default Room;
Thanks, and go easy on me. I'm typically a server-side guy! I clearly have wired something wrong... have tried
v-for="room in this.$data.roomBuilder.getRooms()"
v-for="room in roomBuilder.getRooms()"
etc.
Clearly there's some magic afoot I don't understand!
Thank you!

How to import a Vue component that is packaged into a JavaScript library?

I am attempting to package a Vue component into a JavaScript library and then use it in another project using vue-sfc-rollup.
I am able to package the component just as the README says to do. Then when I copy the .min.js file into another project and attempt to use the component, I always get the error:
Vue warn handler: Failed to mount component: template or render function not defined.
The way I'm trying to use the component from inside another Vue component is this:
import Vue from 'vue'
import MyComponent from '../lib/my-components.min.js'
Vue.use(MyComponent)
Then in the components section:
components: {
'my-component': MyComponent
}
Then in the template:
<my-component></my-component>
What am I missing here? What is the correct way to use the component in another project?
EDIT: Adding component code in response to comment.
<template>
<div class="my-component">
<p>The counter was {{ changedBy }} to <b>{{ counter }}</b>.</p>
<button #click="increment">
Click +1
</button>
<button #click="decrement">
Click -1
</button>
<button #click="increment(5)">
Click +5
</button>
<button #click="decrement(5)">
Click -5
</button>
<button #click="reset">
Reset
</button>
</div>
</template>
<script>
export default {
name: 'MyComponent', // vue component name
data() {
return {
counter: 5,
initCounter: 5,
message: {
action: null,
amount: null,
},
};
},
computed: {
changedBy() {
const {
message
} = this;
if (!message.action) return 'initialized';
return `${message?.action} ${message.amount ?? ''}`.trim();
},
},
methods: {
increment(arg) {
const amount = (typeof arg !== 'number') ? 1 : arg;
this.counter += amount;
this.message.action = 'incremented by';
this.message.amount = amount;
},
decrement(arg) {
const amount = (typeof arg !== 'number') ? 1 : arg;
this.counter -= amount;
this.message.action = 'decremented by';
this.message.amount = amount;
},
reset() {
this.counter = this.initCounter;
this.message.action = 'reset';
this.message.amount = null;
},
},
};
</script>
<style scoped>
.my-component {
display: block;
width: 400px;
margin: 25px auto;
border: 1px solid #ccc;
background: #eaeaea;
text-align: center;
padding: 25px;
}
.my-component p {
margin: 0 0 1em;
}
</style>
I found one way to do this at this Stack Overflow question: Register local Vue.js component dynamically.
I got it to work by implementing a simpler version of the solution shown there. I removed the component section from the outer component, then added this created() lifecycle hook:
created() {
console.log('pages/PageHome.vue: created(): Fired!')
// From https://stackoverflow.com/questions/40622425/register-local-vue-js-component-dynamically
// "This is how I ended up importing and registering components dynamically to a component locally"
const componentConfig = require('../lib/components/my-component.js')
console.log('pages/PageHome.vue: created(): componentConfig.default = ')
console.log(componentConfig.default)
const componentName = 'my-component'
console.log('pages/PageHome.vue: componentName = ' + componentName)
this.$options.components[componentName] = componentConfig.default
}
The component is imported using a require() call, then registered locally by adding it to the this.$options.components dictionary. The secret sauce is to add .default to the componentConfig expression. This doesn't seem to be formally documented anywhere.
Editorial comment: I'm surprised the Vue documentation pays such little attention to distribution patterns for re-usability. As great as the Vue docs are, this is a glaring omission.

Share constants between Vue components

I have a collection of static data that I want to access in some of my Vue components. Example:
COLORS = Object.freeze({
RED: 1,
GREEN: 2,
BLUE: 3,
})
FLAVOURS = Object.freeze({
VANILLA: 'vanilla',
CHOCOLATE: 'chocolate',
})
I'm working with single file components.
I want to access those constants both in component template and in JS code (i.e. in data()).
I don't want them to be reactive.
If possible, I want them to be instantiated only once (not copying each constant into each component instance).
I don't currently use Vuex, but I'll consider it if it leads to more elegant solution.
I tried to solve my problem using mixin:
// ColorMixin.js
export const COLORS = Object.freeze({
RED: 1,
GREEN: 2,
})
export const ColorMixin = {
created() {
this.COLORS = COLORS
}
}
Then, in my component I have to use that mixin and also the constants:
<template>
<input name="red" :value="COLORS.RED" />
<input name="green" :value="COLORS.GREEN" />
</template>
<script>
import {COLORS, ColorMixin} from './ColorMixin.js'
export default {
mixins: [ColorMixin],
data() {
return {
default_color: COLORS.RED,
}
}
}
</script>
This works, but it seems kind of repetitive. Is there a more elegant solution for my problem?
How about just using a global mixin ?
// import your constants
Vue.mixin({
created: function () {
this.COLORS = COLORS;
}
})
You do not just import the exported var from your Color.js file into correct SFC ?
COLORS = Object.freeze({
RED: 1,
GREEN: 2,
BLUE: 3,
});
FLAVOURS = Object.freeze({
VANILLA: 'vanilla',
CHOCOLATE: 'chocolate',
});
export {COLORS, FLAVOURS};
and then in your SFC
<template>
<input name="red" :value="default_color" />
</template>
<script>
import {COLORS, FLAVOURS} from './Color.js';
export default {
data() {
return {
default_color: COLORS.RED,
default_flavour: FLAVOURS.CHOCOLATE,
}
}
}
</script>
Or just create a Vuex store to save this datas and use it directly from each SFC