We're trying to convert a simple SFC to use the new Vue CompositionAPI. This code works flawless:
export default {
data() {
return {
miniState: true
}
},
methods: {
setMiniState(state) {
if (this.$q.screen.width > 1023) {
this.miniState = false;
} else if (state !== void 0) {
this.miniState = state === true
}
else {
this.miniState = true
}
},
},
watch: {
'$q.screen.width'() {
this.setMiniState()
}
}
};
Converting this to the new CompostionAPI looks like this:
export default defineComponent({
setup() {
const miniState = ref(true)
const setMiniState = (state) => {
if ($q.screen.width > 1023) {
miniState.value = false
} else if (state !== void 0) {
miniState.value = state === true
}
else {
miniState.value = true
}
}
watch('$q.screen.width'(),
setMiniState()
)
return {
miniState, setMiniState
}
}
})
However, every time I try to run this Vue complains that $q.screen.width is not a function. What am I doing wrong here?
You're calling $q.screen.width instead of adding it as a watch source.
Try this:
watch('$q.screen.width', (newVal, oldVal) => setMiniState())
Related
I have the following code, which is very repetitious:
const flags = {
get logged() {
return localStorage.getItem("logged") === "true";
},
set logged(val: boolean) {
if (val) {
localStorage.setItem("logged", "true");
} else {
localStorage.removeItem("logged");
}
},
get notificationsMuted() {
return localStorage.getItem("notifications-muted") === "true";
},
set notificationsMuted(val: boolean) {
if (val) {
localStorage.setItem("notifications-muted", "true");
} else {
localStorage.removeItem("notifications-muted");
}
}
}
As you can see, the get and set for each flag type is identical, save for the property names. I would like to do something like this instead:
function getter(prop: string) {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged: getter("logged")
set logged: setter("logged")
get notificationsMuted: getter("notifications-muted")
set notificationsMuted: setter("notifications-muted")
}
But I'm not sure if Javascript / Typescript has support for this sort of thing. Is such a thing possible, and if so, how? If not, is there any other way I can cut down on the repetition here?
You can use a proxy with get and set traps, use TS types to allow only props you wish to handle (TS playground)):
interface Flags {
logged: boolean,
'notifications-muted': boolean;
}
type Prop = keyof Flags;
const handlers = {
get(_: Flags, prop: Prop) {
return localStorage.getItem(prop) === "true";
},
set(_: Flags, prop: Prop, val: any) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
return true;
}
};
const flags = new Proxy<Flags>({} as Flags, handlers);
All you really need is to use Object.defineProperty with an object with a get and set properties. Or, with multiple properties, use Object.defineProperties to define them all at once.
One approach which will help with code organization is to not use lots of local storage keys, but instead use a single object that gets stored.
const props = ['logged', 'notificationsMuted'] as const;
const defaultStorage = Object.fromEntries(props.map(prop => [prop, false]));
const getStorage = () => JSON.parse(localStorage.getItem('settings') || JSON.stringify(defaultStorage));
const flags = Object.defineProperties(
{},
Object.fromEntries(
props.map(
prop => [
prop,
{
get: () => getStorage()[prop],
set: (newVal: boolean) => {
const store = getStorage();
store.prop = newVal;
localStorage.setItem('settings', JSON.stringify(store));
}
}
]
)
)
) as Record<(typeof props)[number], boolean>;
This is the current "best" solution I can come up with. Open to anyone who can provide an improvement over this:
function getter(prop: string): boolean {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean): void {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged() { return getter("logged") },
set logged(val: boolean) { setter("logged", val) },
get notificationsMuted() { return getter("notifications-muted"); },
set notificationsMuted(val: boolean) { setter("notifications-muted", val); }
}
I'm trying to abstract some repetitive functions to update parts of the Monaco edit from various check boxs in the UI. The example below enables or disables linenumber in the editor.
Works fine when the editor and functions are in the same setup. When I abstract to a composable per below the editor is null, therefore does not work. Any thoughts or ideas?
Note: Some code is removed for brevity
MainTemplate.vue
import * as monaco from 'monaco-editor';
import useCodeEditor from '../composables/codeEditorFunctions';
import { ref, onMounted, reactive, inject, watch } from 'vue';
export default {
setup(props, { emit }) {
let meditor = null;
onMounted(() => {
checkDarkModeIsSet();
checkLineNumbersIsSet();
checkMiniMapIsSet();
const codeEditorDiv = document.getElementById('pf-c-code-editor__code-pre');
meditor = monaco.editor.create(codeEditorDiv, {
value: ['function x() {', '\tconsole.log("If you see this, something went wrong!");', '}'].join('\n'),
language: 'yaml',
lineNumbers: lineNumbers.value,
roundedSelection: false,
scrollBeyondLastLine: true,
readOnly: false,
theme: darkmode.value,
scrollBeyondLastLine: false,
automaticLayout: true,
wordWrap: 'on',
wrappingStrategy: 'advanced',
minimap: {
enabled: true
}
});
if (props.viewstate.editid === 0) {
getDefaultCodeBlock();
} else {
showCodeBlock();
}
getModel(props.viewstate.editid);
});
const { darkmode, checkDarkModeIsSet, toggleEditorDarkMode, lineNumbers, checkLineNumbersIsSet, toggleEditorLineNumbers } = useCodeEditor(monaco, meditor);
return {
darkmode,
lineNumbers,
toggleEditorDarkMode,
toggleEditorLineNumbers,
toggleEditorMinimap,
showConfigFullScreen
};
}
};
codeEditorFunctions.js
import { ref, reactive } from "vue";
export default function useCodeEditor(monaco, meditor) {
/** EDITOR DARKMODE */
const darkmode = ref('vs');
function checkDarkModeIsSet() {
if (localStorage.getItem('editordarkmode') === null) {
darkmode.value = 'vs';
localStorage.setItem('editordarkmode', darkmode.value);
} else {
darkmode.value = localStorage.getItem('editordarkmode');
}
}
function toggleEditorDarkMode(event) {
if (event.target.checked) {
darkmode.value = 'vs-dark';
localStorage.setItem('editordarkmode', darkmode.value);
} else {
darkmode.value = 'vs';
localStorage.setItem('editordarkmode', darkmode.value);
}
monaco.editor.setTheme(darkmode.value);
}
/** EDITOR LINNUMBERS */
const lineNumbers = ref('on');
function checkLineNumbersIsSet() {
if (localStorage.getItem('editorlineNumbers') === null) {
lineNumbers.value = 'on';
localStorage.setItem('editorlineNumbers', lineNumbers.value);
} else {
lineNumbers.value = localStorage.getItem('editorlineNumbers');
}
}
function toggleEditorLineNumbers(event) {
if (event.target.checked) {
lineNumbers.value = 'on';
localStorage.setItem('editorlineNumbers', lineNumbers.value);
} else {
lineNumbers.value = 'off';
localStorage.setItem('editorlineNumbers', lineNumbers.value);
}
meditor.updateOptions({
lineNumbers: lineNumbers.value
});
}
return {
darkmode,
checkDarkModeIsSet,
toggleEditorDarkMode,
lineNumbers,
checkLineNumbersIsSet,
toggleEditorLineNumbers,
};
};
Basically, function toggleEditorLineNumbers(event) throws an error becuase meditor (meditor.updateOptions) is null.
ANy ideas?
This is the case for a ref, which basically is a recipe that allows to pass arbitrary value by reference. Since meditor isn't supposed to be used prior to useCodeEditor call, it makes more sense to define it inside a composable instead of providing it as an argument:
function useCodeEditor(monaco) {
const meditor = ref(null);
...
return {
meditor,
...
}
}
My goal is to create a custom form component called app-form which provides v-model to access the validation. For the input I want to detect is also a custom component called app-input.
Here is my implementation so far.
app-form
<template>
<div>
<slot></slot>
</div>
</template>
const acceptedTags = ['app-input'];
export default {
/*
props: value,
data: isValid
*/
updated() {
// update isValid whenever the view changes
this.checkValidation();
},
methods: {
checkValidation() {
this.isValid = true;
this.checkValidation(this);
this.$emit('input', this.isValid);
},
checkValidationRecursion(node) {
if (acceptedTags.includes(node.$options.name)) {
let result = node.checkValidation();
this.isValid &&= result;
}
node.$children.forEach((child) => this.checkValidationRecursion(child));
},
}
}
app-input
<input
:value="selfValue"
#input="onInput"/>
export default {
name: 'app-input',
/*
props: value,
data: selfValue,
*/
methods: {
checkValidation() {
let valid = true;
/*
this.rules = [(v) => !!v || 'Required'];
*/
for (let f of this.rules) {
let result = f(this.selfValue);
if (typeof result === 'string') {
valid = false;
}
}
return valid;
},
// onInput() => update selfValue and emit
},
// watch: update selfValue
}
In the code above, the app-form have to traverse the whole component tree to get the target inputs every time anything is updated. Is there any better way to achieve it?
For these Kind of things I like to use provide/inject https://v2.vuejs.org/v2/api/#provide-inject. The idea is to "provide" an object in a Parent-Component (your Form-Component) and to "inject" this object in any of your descandant Components. Data here is not reactive, but you can pass a reference to a reactive Object (for example a Vue Object).
If providing a Vue-Instance you can emit an event, like "check-validation" on that instance and your descandant components can listen to that and then emitting an validate-Event with the validation-Result back to the parent Component.
Here is a very basic Example: https://codepen.io/arossbach/pen/xxdxOVZ
Vue.component('my-form', {
provide () {
return {
formBus: this.formBus,
};
},
mounted() {
setTimeout(() => {
this.formBus.$emit("check-validation");
},4000);
this.formBus.$on("validate-element", isValid => {
this.isValidForm &&= isValid;
});
},
data () {
return {
formBus: new Vue(),
isValidForm: true,
};
},
template: '<div><slot></slot></div>',
});
Vue.component('child', {
inject: ['formBus'],
template: '<div></div>',
data() {
return {
isValid: true,
}
},
methods: {
validate() {
this.isValid = Boolean(Math.round(Math.random()));
this.formBus.$emit("validate-element", this.isValid);
}
},
created() {
this.formBus.$on("check-validation", this.validate);
}
});
new Vue({
el: '#app',
data () {
return {
};
},
});
HTML:
<div id="app">
<my-form>
<child></child>
<child></child>
</my-form>
</div>
I am new in the Aurelia community and it was given to me a task to make an entire upgrade of my current platform. (more info at the bottom).
Current problem:
Every time i redirect to the logout.js model a message is prompt
ERROR [app-router] TypeError: "this.view is null"
Questions:
How does a custom component "if-permission" can influence on non-view model?
Conlusions:
- I started to believe that any of the big files bellow are influencing the error at all! After commenting most of the code the error what still showing!
- Removed the noView() logic and added an empty logout.html! Guess what? Works like a charm! The logout will redirect to the login page.
This is my RouteConfig.js
{
route: 'logout',
viewPorts: {
main: {
moduleId: PLATFORM.moduleName('pages/logout/logout')
}
},
nav: false,
sidebar: false,
auth: false,
title: 'Logout',
name: 'logout',
}
This is my logout.js
import { noView } from 'aurelia-framework';
import authService from 'services/authService';
import uiService from 'services/uiService';
#noView()
export class LogoutPage {
activate() {
//THE ERROR PROMPTS EVEN WITH THE ABOVE LINES COMMENTED
uiService.impersonate(null, false);
authService.logout();
}
}
After searching a while i noticed that "this.view" is declared on this 2 files:
if-permission.js
import { inject, customAttribute, templateController, BoundViewFactory, ViewSlot } from 'aurelia-framework';
import userService from 'services/api/userService';
#customAttribute('if-permission')
#inject(BoundViewFactory, ViewSlot)
#templateController
export class IfPermission {
constructor(viewFactory, viewSlot) {
this.viewFactory = viewFactory;
this.viewSlot = viewSlot;
this.showing = false;
this.view = null;
this.bindingContext = null;
this.overrideContext = null;
}
/**
* Binds the if to the binding context and override context
* #param bindingContext The binding context
* #param overrideContext An override context for binding.
*/
bind(bindingContext, overrideContext) {
// Store parent bindingContext, so we can pass it down
this.bindingContext = bindingContext;
this.overrideContext = overrideContext;
this.valueChanged(this.value);
}
valueChanged(newValue) {
if (this.__queuedChanges) {
this.__queuedChanges.push(newValue);
return;
}
let maybePromise = this._runValueChanged(newValue);
if (maybePromise instanceof Promise) {
let queuedChanges = this.__queuedChanges = [];
let runQueuedChanges = () => {
if (!queuedChanges.length) {
this.__queuedChanges = undefined;
return;
}
let nextPromise = this._runValueChanged(queuedChanges.shift()) || Promise.resolve();
nextPromise.then(runQueuedChanges);
};
maybePromise.then(runQueuedChanges);
}
}
_runValueChanged(newValue) {
newValue = userService.hasPermission(newValue);
if (!newValue) {
let viewOrPromise;
if (this.view !== null && this.showing) {
viewOrPromise = this.viewSlot.remove(this.view);
if (viewOrPromise instanceof Promise) {
viewOrPromise.then(() => this.view.unbind());
} else {
this.view.unbind();
}
}
this.showing = false;
return viewOrPromise;
}
if (this.view === null) {
this.view = this.viewFactory.create();
}
if (!this.view.isBound) {
this.view.bind(this.bindingContext, this.overrideContext);
}
if (!this.showing) {
this.showing = true;
return this.viewSlot.add(this.view);
}
}
/**
* Unbinds the if
*/
unbind() {
if (this.view === null) {
return;
}
this.view.unbind();
if (!this.viewFactory.isCaching) {
return;
}
if (this.showing) {
this.showing = false;
this.viewSlot.remove(this.view, true, true);
}
this.view.returnToCache();
this.view = null;
}
}
if-user-role.js
import { inject, customAttribute, templateController, BoundViewFactory, ViewSlot } from 'aurelia-framework';
import userService from 'services/api/userService';
#customAttribute('if-user-role')
#inject(BoundViewFactory, ViewSlot)
#templateController
export class IfUserRole {
constructor(viewFactory, viewSlot) {
this.viewFactory = viewFactory;
this.viewSlot = viewSlot;
this.showing = false;
this.view = null;
this.bindingContext = null;
this.overrideContext = null;
}
/**
* Binds the if to the binding context and override context
* #param bindingContext The binding context
* #param overrideContext An override context for binding.
*/
bind(bindingContext, overrideContext) {
// Store parent bindingContext, so we can pass it down
this.bindingContext = bindingContext;
this.overrideContext = overrideContext;
this.valueChanged(this.value);
}
valueChanged(newValue) {
if (this.__queuedChanges) {
this.__queuedChanges.push(newValue);
return;
}
let maybePromise = this._runValueChanged(newValue);
if (maybePromise instanceof Promise) {
let queuedChanges = this.__queuedChanges = [];
let runQueuedChanges = () => {
if (!queuedChanges.length) {
this.__queuedChanges = undefined;
return;
}
let nextPromise = this._runValueChanged(queuedChanges.shift()) || Promise.resolve();
nextPromise.then(runQueuedChanges);
};
maybePromise.then(runQueuedChanges);
}
}
_runValueChanged(newValue) {
newValue = userService.hasRole(newValue);
if (!newValue) {
let viewOrPromise;
if (this.view !== null && this.showing) {
viewOrPromise = this.viewSlot.remove(this.view);
if (viewOrPromise instanceof Promise) {
viewOrPromise.then(() => this.view.unbind());
} else {
this.view.unbind();
}
}
this.showing = false;
return viewOrPromise;
}
if (this.view === null) {
this.view = this.viewFactory.create();
}
if (!this.view.isBound) {
this.view.bind(this.bindingContext, this.overrideContext);
}
if (!this.showing) {
this.showing = true;
return this.viewSlot.add(this.view);
}
}
/**
* Unbinds the if
*/
unbind() {
if (this.view === null) {
return;
}
this.view.unbind();
if (!this.viewFactory.isCaching) {
return;
}
if (this.showing) {
this.showing = false;
this.viewSlot.remove(this.view, true, true);
}
this.view.returnToCache();
this.view = null;
}
}
With this update i have integrated Aurelia-cli, updated aurelia-webpack and all the dependencies. Which made me switch some code like:
Add PLATFORM.moduleName() to all my platform
Add Require to all modules that were only getting components via < compose >
An external js file that changes the value of a property by a click
event, but how does it detect that this property has changed since the
vue page used this property value?
1.External js
var obj = {},initValue = true;
Object.defineProperty(obj, "newKey", {
get: function () {
return initValue;
},
set: function (value) {
return initValue = value;
}
});
var con=function () {
document.getElementById('btncc').onclick = function () {
setTimeout(() => {
console.log(obj.newKey)
}, 3000)
initValue = false;
}
}
export default {
con,obj
}
> 2.vue page
> Change the style
<input id="color" type="color" :class="obj ? 'transparent': 'transparent2'" :disabled ="obj"/>
import Canvas from '../../Api/js'
export default {
name: 'HelloWorld',
data () {
return {
obj:Canvas.obj.newKey
}
},
mounted () {
Canvas.con()
console.log(this.obj,'我是msg啊');
},
watch: {
[Canvas.obj.newKey] (val) {
if(val){
console.log(this.obj,'我改变了吗,');
}
}
}
}
watch no effect.
Probably like this.
https://jsfiddle.net/qjwanglei/L5afz75t/3/