I'm working on a personal project using Vue3, Nuxt3 and Element Plus. I'm a beginner with JS/TS and Vue3 and I'm having a problem with the Element Plus TreeSelect Component. I am trying to have the TreeSelect Components selected value be an object but it's always the label field of the object that is selected. Here is my components code.
<template>
<div>
<ClientOnly>
<el-select v-model="selectedEntityDefinitionRef">
<el-option v-for="entityDefinition in scheme.schemeDefinition.entityDefinitions"
:key="entityDefinition.label" :label="entityDefinition.label" :value="entityDefinition">
</el-option>
</el-select>
<el-tree-select check-strictly :render-after-expand="false" show-checkbox check-on-click-node
value-key="label" :data="schemeEntitiesRef" v-model="selectedParentEntityRef"/>
</ClientOnly>
<el-button style="margin-top: 12px" #click="printValues">Add</el-button>
</div>
</template>
<script lang="ts" setup>
import { ElTreeSelect, ElSelect, ElOption, ElButton } from 'element-plus';
import { ref, reactive } from 'vue';
class SchemeDefinition {
label: string
entityDefinitions: Array<EntityDefinition> = new Array<EntityDefinition>()
constructor(label: string) {
this.label = label
}
}
class EntityDefinition {
label: string
constructor(label: string) {
this.label = label
}
}
class Scheme {
schemeDefinition: SchemeDefinition
entities: Array<Entity> = new Array<Entity>()
}
class Entity {
label: string
parent: Entity
children: Array<Entity> = new Array<Entity>()
constructor(label: string) {
this.label = label;
}
}
const schemeDefinition: SchemeDefinition = new SchemeDefinition('Scheme Definition');
const fondsEntityDefinition: EntityDefinition = new EntityDefinition('Entity Definition')
schemeDefinition.entityDefinitions.push(fondsEntityDefinition)
const scheme: Scheme = new Scheme()
scheme.schemeDefinition = schemeDefinition
scheme.entities.push(new Entity('Entity'))
const selectedEntityDefinitionRef = ref<EntityDefinition>()
const selectedParentEntityRef = ref<Entity>()
const schemeEntitiesRef = reactive(scheme.entities)
const printValues = () => {
console.log(selectedEntityDefinitionRef.value)
console.log(selectedParentEntityRef.value)
}
</script>
My question is if I am doing something fundamentally wrong here or is my issue a limitation of the component itself? As a newbie any advice or guidance would be greatly appreciated. Thanks
Related
I am currently working on a bokeh application with continuous AND categorical data in the same Dataframe. Since the PointDrawTool of bokeh only comes with support for continuous data, I need to write a custom PointDrawTool myself. I followed the custom tools tutorial (https://docs.bokeh.org/en/2.4.1/docs/user_guide/extensions_gallery/tool.html) and changed the TypeScript code. I just copied the source code of the PointDrawTool (https://github.com/bokeh/bokeh/blob/branch-3.0/bokehjs/src/lib/models/tools/edit/point_draw_tool.ts) and fixed all the imports so that I do not run into any TypeScript Errors anymore. However, I always get a weird console error when loading the page. Additionally, the page remains completely blank and none of my widgets are showing.
Error in console: "Failed to repull session TypeError: this.properties[e] is undefined"
Code in draw_tool.py:
from bokeh.core.properties import Instance
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, Tool
from bokeh.plotting import figure
from bokeh.util.compiler import TypeScript
output_file('tool.html')
TS_CODE = """
import {Keys} from "core/dom"
import {PanEvent, TapEvent, KeyEvent} from "core/ui_events"
import * as p from "core/properties"
import {GlyphRenderer} from "models/renderers/glyph_renderer"
import {EditTool, EditToolView, HasXYGlyph} from "models/tools/edit/edit_tool"
import {tool_icon_point_draw} from "styles/icons.css"
export class CustomDrawToolView extends EditToolView {
override model: CustomDrawTool
override _tap(ev: TapEvent): void {
const renderers = this._select_event(ev, this._select_mode(ev), this.model.renderers)
if (renderers.length || !this.model.add) {
return
}
const renderer = this.model.renderers[0]
const point = this._map_drag(ev.sx, ev.sy, renderer)
if (point == null)
return
// Type once dataspecs are typed
const glyph: any = renderer.glyph
const cds = renderer.data_source
const [xkey, ykey] = [glyph.x.field, glyph.y.field]
const [x, y] = point
this._pop_glyphs(cds, this.model.num_objects)
if (xkey) cds.get_array(xkey).push(x)
if (ykey) cds.get_array(ykey).push(y)
this._pad_empty_columns(cds, [xkey, ykey])
const {data} = cds
cds.setv({data}, {check_eq: false}) // XXX: inplace updates
}
override _keyup(ev: KeyEvent): void {
if (!this.model.active || !this._mouse_in_frame)
return
for (const renderer of this.model.renderers) {
if (ev.keyCode === Keys.Backspace) {
this._delete_selected(renderer)
} else if (ev.keyCode == Keys.Esc) {
renderer.data_source.selection_manager.clear()
}
}
}
override _pan_start(ev: PanEvent): void {
if (!this.model.drag)
return
this._select_event(ev, "append", this.model.renderers)
this._basepoint = [ev.sx, ev.sy]
}
override _pan(ev: PanEvent): void {
if (!this.model.drag || this._basepoint == null)
return
this._drag_points(ev, this.model.renderers)
}
override _pan_end(ev: PanEvent): void {
if (!this.model.drag)
return
this._pan(ev)
for (const renderer of this.model.renderers)
this._emit_cds_changes(renderer.data_source, false, true, true)
this._basepoint = null
}
}
export namespace CustomDrawTool {
export type Attrs = p.AttrsOf<Props>
export type Props = EditTool.Props & {
add: p.Property<boolean>
drag: p.Property<boolean>
num_objects: p.Property<number>
renderers: p.Property<(GlyphRenderer & HasXYGlyph)[]>
}
}
export interface CustomDrawTool extends CustomDrawTool.Attrs {}
export class CustomDrawTool extends EditTool {
override properties: CustomDrawTool.Props
override __view_type__: CustomDrawToolView
override renderers: (GlyphRenderer & HasXYGlyph)[]
constructor(attrs?: Partial<CustomDrawTool.Attrs>) {
super(attrs)
}
static {
this.prototype.default_view = CustomDrawToolView
this.define<CustomDrawTool.Props>(({Boolean, Int}) => ({
add: [ Boolean, true ],
drag: [ Boolean, true ],
num_objects: [ Int, 0 ],
}))
}
override tool_name = "Point Draw Tool XX"
tool_icon = tool_icon_point_draw
override event_type = ["tap" as "tap", "pan" as "pan", "move" as "move"]
override default_order = 2
}
"""
class CustomDrawTool(Tool):
__implementation__ = TypeScript(TS_CODE)
source = Instance(ColumnDataSource)
Usage in main.py
from custom_tools.draw_tool import CustomDrawTool
tools = config.TOOLS + [CustomDrawTool(source=data_model.data_X)]
p = figure(height=config.PLOT_HEIGHT, width=config.PLOT_WIDTH, tools=tools, output_backend="webgl",
**kw_figure)
Note: data_model.data_X is just a ColumnDataSource.
Hopefully someone can give me a hint on how to fix the problem and continue with writing my own Tool.
Thanks & best regards
I wrote a vue3 component which uses the VirtualScroller from PrimeVue and I would like to scroll to the end of the scroller each time I'm adding new elements. For that, there is scrollInView method which is defined on the component and documented here
My code looks like this (it's typescript with vue-class-component and single file syntax):
<template>
...
<VirtualScroller :items="content" :itemSize="50" class="streamscroller" ref="streamscroller">
<template v-slot:item="{ item }">
<pre>{{ item }}</pre>
</template>
</VirtualScroller>
...
</template>
<script lang="ts">
...
import { ref, ComponentPublicInstance } from "vue";
import VirtualScroller from "primevue/virtualscroller";
...
#Options({
components: {
VirtualScroller,
...
},
})
export default class StreamResultViewer extends Vue {
streamscroller = ref<ComponentPublicInstance<VirtualScroller>>();
content: string [] = [ "No output" ];
...
mounted(): void {
...
console.debug("scroller mounted: ", this.streamscroller.value); // <=== here, already the value is indefined
}
onData(msg: string): void {
const lines = msg.split('\n');
const content = [...this.content, ...lines];
this.content = content;
console.debug("scroller: ", this.streamscroller.value); // <== always undefined
this.streamscroller.value?.scrollInView(this.content.length, 'to-end', 'smooth'); // <== so never called
}
...
The virtual scroller works well (I can add lines each time they arrives and the scroll bar moves...) but I can never call the scroll method because the ref is undefined...
I'd be very grateful for any clue...
Thank you
The only workaround I found is too use $refs like this:
onData(msg: string): void {
const lines = msg.split('\n');
const content = [...this.content, ...lines];
this.content = content;
const scroller = this.$refs.streamscroller as VirtualScroller;
scroller.scrollInView(this.content.length, 'to-end', 'smooth');
}
This way, I am able to call the scrolling method and it works fine.
If someone can explain how it should work normally with ref<T>() in the vue-class-component + typescript mode, I'd be glad to hear that.
I have 3 classes derived from Record. Definitions of first two classes are below.
// Base.js
import {Record} from 'immutable';
import * as uuid from 'uuid';
export const Base = defaultValues => {
return class extends Record({
key: null,
...defaultValues,
}) {
constructor(props) {
super(Object.assign({}, props, {key: (props && props.key) || uuid.v4()}));
}
};
};
// LOBase.js
import {Base} from './BaseModel';
export const LOBase = defaultValues => {
return class extends Base({
created_at: new Date(null),
updated_at: new Date(null),
deleted_at: new Date(null),
isActive: new Boolean(),
isDeleted: new Boolean(),
publishState: new String(),
...defaultValues,
}) {};
};
And this is my last class derived from LOBase and where my problem is.
// Question.js
import {List, Record, fromJS} from 'immutable';
import _ from 'lodash';
import {LOBase} from './base/LOBaseModel';
export class Question extends LOBase({
id: '',
name: 'test',
description: '',
questionType: 1,
title: 'title',
version: new String(),
customData: {},
//...
}) {
insertOption() {
let index = this.customData.options.length;
this.updateIn(['customData', 'options'], options => {
return options.splice(index, 0, {
someGenericStuff: [],
// ...
});
});
return this;
}
static MultipleChoice() {
let defaultCustomData = {
options: [],
//...
};
let question = new Question()
.set('questionType', QUESTION_TYPE_MULTIPLE_CHOICE)
.set('customData', new Record(defaultCustomData)())
//...
.insertOption()
.insertOption()
.insertOption();
return question;
}
// ...
}
I use let question = Question.MultipleChoice() to create a new Question instance. And when i use question.insertOption() it works fine. But when I do this in the reducer on the state I get an error saying "A state mutation was detected inside a dispatch".
How can I achieve to change question object in the state? Should I clone original Record before doing that? What is the Immutablejs way to do that?
Thanks in advance.
insertOption uses this.updateIn but does not return or store the result.
When you return this at the end of the function you actually return the same immutable Record without the changes.
So, unless I'm missing something here, you should probably go with:
insertOption() {
let index = this.customData.options.length;
return this.updateIn(['customData', 'options'], options => {
return options.splice(index, 0, {
someGenericStuff: [],
// ...
});
});
}
The updateIn will return a new instance of the Record with the updated values.
You did not add your state structure and reducer (if you can please do), but you should be sure to return a new state object every time and not just changing the question field.
BTW, you are doing a sequence of mutation methods one after the other (set, set, updateIn). This is not suggestable from a performance perspective. I'd suggest replacing it with withMutations in the following manner:
static insertOption(record) {
let index = record.customData.options.length;
return record.updateIn(['customData', 'options'], options => {
return options.splice(index, 0, {
someGenericStuff: [],
// ...
});
});
}
static MultipleChoice() {
// ...
let question = new Question();
question.withMutations(record => {
record.merge({
questionType: QUESTION_TYPE_MULTIPLE_CHOICE,
customData: new Record(defaultCustomData)()
})
Question.insertOption(record);
})
return question;
}
I am using Nativescript with Angular and have code written that succesfully calls an onQueryEvent from the nativescript-firebase-plugin for data set upon first building the application. However after following a route to a second component containing the exact same onQueryEvent the data succeeds to retreive a complete list but skips the onQueryEvent.
In all honesty I don't know best practices for queries in any situation let alone this one, so I hope it is just a matter of manipulating how I call the onQueryEvent.
I believe the problem to be in the firebase.query inside the getMyTransactionList() function of the firebase.service.ts file.
the overview.component.html page has a transaction | async RadListView that successfully filters upon running tns run android. Then clicking any link directing to the deal-summary.component.html page where the function is re-iterated refuses to query by the static storage variable set in the firebase.service
Here is my code:
firebase.service.ts
export class FirebaseService {
private _allItems: Array<any> = [];
items: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
public storage: any = '-KomUSGcX-j6qQmY4Wrh'; // set statically to test different routes
constructor(
private ngZone: NgZone,
){}
// fetch data
getMyDealList(): Observable<any> {
return new Observable((observer: any) => {
let path = `deals/${BackendService.token}`;
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
getMyTransactionList(): Observable<any> {
return new Observable((observer: any) => {
let path = `transactions/${BackendService.token}`;
// this is a merge of jen loopers giftler code combined with nativescrip-firebase-plugins standard onQueryEvent. It works on the first load but routing to a second instance of the same function retrieves all the data without queryEvent
let onQueryEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
observer.next(results);
});
};
firebase.query(
onQueryEvent,
`/transactions/${BackendService.token}`,
{
singleEvent: true,
orderBy: {
type: firebase.QueryOrderByType.CHILD,
value: 'dealId' // mandatory when type is 'child'
},
range:
{
type: firebase.QueryRangeType.EQUAL_TO,
value: `${this.storage}` // this calls a static variable for testing consistency
}
,
}
);
firebase.addValueEventListener(onQueryEvent, `/${path}`);
console.log("transaction Listener added");
}).share();
}
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (<any>Object).assign({id: id}, data[id]);
this._allItems.push(result);
}
this.publishUpdates();
}
return this._allItems;
}
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
}
app.component.ts
<page-router-outlet></page-router-outlet>
overview.component.ts
export class OverviewComponent implements OnInit {
public deals: Observable<any>;
public transactions: Observable<any>;
constructor(private router: Router,
private firebaseS: FirebaseService,
){ }
ngOnInit() {
this.deals = <any>this.firebaseS.getMyDealList();
this.transactions = <any>this.firebaseS.getMyTransactionList();
}
viewDealSumm(id){
this.router.navigate(['dashboard/deal-summary', id]);
}
}
overview.component.html
<RadListView [items]="deals | async ">
<ng-template tkListItemTemplate let-item="item">
<StackLayout (tap)="viewDealSumm(item.id)">
<Label [text]="item.dealName"></Label>
</StackLayout>
</ng-template>
</ListViewGridLayout>
</RadListView>
<RadListView [items]="transactions | async " >
<ng-template tkListItemTemplate let-item="item">
<GridLayout>
<Label [text]="item.transName"></Label>
</GridLayout>
</ng-template>
</RadListView>
deal-summary.component.ts
export class DealSummaryComponent implements OnInit {
public transactions: Observable<any>;
constructor(
private firebaseS: FirebaseService,
){ }
ngOnInit() {
this.transactions = <any>this.firebaseS.getMyTransactionList();
}
deal-summary.component.html
<RadListView [items]="transactions | async " >
<ng-template tkListItemTemplate let-item="item">
<GridLayout >
<Label col="1" [text]="item.transName"></Label>
</GridLayout>
</ng-template>
</RadListView>
I'm using aurelia-validate and my validation works fine if I use variables, but I need it to validate properties of an object rather than a variable:
Here's what works:
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
#ensure(function(it){
it.isNotEmpty()
.hasLengthBetween(3,10);
})
name = '';
#ensure(function(it){
it.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/); //no spaces
})
url = '';
constructor(validation, service) {
this.validation = validation.on(this);
this.service = service;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.name = res.content.name; //populate
this.url = res.content.url;
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.name,
url: this.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Here's what I'm trying to do (but doesn't work)...also I'm not sure if it's better to keep the properties on the class or have a property called this.item which contains the properties (this is the typical angular way):
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
#ensure(function(it){
it.isNotEmpty()
.hasLengthBetween(3,10);
})
this.item.name; //no assignment here should happen
#ensure(function(it){
it.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/); //no spaces
})
this.item.url; //no assignment?
constructor(validation, service) {
this.validation = validation.on(this);
this.service = service;
this.item = null;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.item = res.content; //populate with object from api call
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.item.name,
url: this.item.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Can someone give me some guidance here on how to use a validator against an existing object (for an edit page)?
The validation works in all kinds of situations, but using the #ensure decorator can only be used to declare your rules on simple properties (like you found out).
Hence...
Option a: replace the ensure decorator with the fluent API 'ensure' method, this supports 'nested' or 'complex' binding paths such as:
import {Validation} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
constructor(validation, service) {
this.validation = validation.on(this)
.ensure('item.url')
.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/)
.ensure('item.name')
.isNotEmpty()
.hasLengthBetween(3,10);
this.service = service;
this.item = null;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.item = res.content; //populate with object from api call
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.item.name,
url: this.item.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Note: you can set up your validation even before item is set. Cool, no?
Option b: Since the validation rules are specific to the item, you could move your validation rules inside your item class using the #ensure decorator inside that class instead.
You can then set up validation in your VM after you've retrieved the item: this.validation = validation.on(this.item); or, your service can set up the validation when it returns your item to your VM and make it an intrinsic part of the model: item.validation = validation.on(item);
Option a is easiest and seems to match your experience. Option b is more maintainable, as the validation rules for your model will live on the model, not on the view-model. However if you go with option b, you might have to adjust your HTML a bit to make sure validation hints appear.
Use the .on method of the validator to apply your rules to object properties.
The example below is called after I retrieve an object named stock, it validates that the quantity is not empty and is numeric only. Hope this helps...
let stock = {
name: 'some name'
minimumQuantity: '1'
};
applyRules() {
ValidationRules
.ensure((m: EditStock) => m.minimumQuantity)
.displayName("Minimum Quantity")
.required()
.withMessage(`\${$displayName} cannot be blank.`)
.matches( /^[0-9]*$/)
.withMessage(`\${$displayName} must be numeric only.`)
.on(this.stock);
}