I have a type
export type LocationQuery = {
showWarnings: boolean;
showErrors: boolean;
startDate: string;
endDate: string;
quickDate: number;
}
Now, I want to convert location.query from the history module to be converted into this type.
The inconvenient way of doing this is manual:
let query: LocationQuery;
query.showWarnings = location.query['showWarnings'];
query.showErrors = location.query['showErrors'];
...
But is there a more convenient, one liner way? Bare in mind location.query may have other fields that I would not care about (so if there is location.query['someOtherField'], that should not get into query
There is too many ways to do it.
this could be one of them
import * as assert from "assert";
/**
* will copy all blueprint.keys from source
* or default to blueprint (default) value;
* #returns a copy of blueprint with source values
*/
const copy = <TSource extends {}, TTarget extends {}>
(source: TSource, bluePrint: TTarget): TTarget => {
let result: any = {};
// see: Object.getOwnPropertyNames, its shallow
for (let key of Object.getOwnPropertyNames(bluePrint)) {
result[key] = source.hasOwnProperty(key)
? source[key]
// default to blueprint prop, will be undefined
// but they key will exists
: bluePrint[key];
}
return result;
}
export type LocationQuery = {
showWarnings: boolean;
showErrors: boolean;
startDate: string;
endDate: string;
quickDate: number;
}
interface History {
showWarnings: boolean;
showErrors: boolean;
startDate: string;
endDate: string;
quickDate: number;
somethingElse: any;
}
/**
* Default Value, blueprint, skeleton, shape,etc
*/
const empty: LocationQuery = {
showWarnings: undefined,
showErrors: undefined,
startDate: undefined,
endDate: undefined,
quickDate: undefined,
};
/**
* test's source subject
*/
const history: History = {
showWarnings: false,
showErrors: false,
startDate: '2016-12-01',
endDate: '2016-12-31',
quickDate: 1,
somethingElse: false
}
/**
* LocationQuery it's 'Partial'History
*/
const expected: LocationQuery = {
showWarnings: false,
showErrors: false,
startDate: '2016-12-01',
endDate: '2016-12-31',
quickDate: 1,
}
describe("copy", () => {
it("shallow copy, all desired members", () => {
let result = copy(history, empty);
assert.deepEqual(expected, result);
// All Key Present?
// somethingElse shoudl be missing...
assert.equal(
"showWarnings, showErrors, startDate, endDate, quickDate",
Object.keys(result).reduce( (a,b)=> a+ ", "+b));
});
it("shallow copy, all desired members and defaults to missing props", () => {
// doesn't provide all memebers
const historyLike = {
showWarnings: true,
showErrors: true,
}
let result = copy(historyLike, empty);
const expected_result_with_defaults = {
showWarnings: true,
showErrors: true,
startDate: undefined,
endDate: undefined,
quickDate: undefined,
};
// Values Ok?
assert.deepEqual(expected_result_with_defaults, result);
// All Key Present?
assert.equal(
"showWarnings, showErrors, startDate, endDate, quickDate",
Object.keys(result).reduce( (a,b)=> a+ ", "+b)
)
});
})
Another one for Typescript 2.1+
/**
* Requires Typescript 2.1 up
* copy specified key from derived type
* where TSource is superset of TResult
*/
const copy = <TSource extends TResult, TResult extends {}>(source: {}, ...keys: (keyof TResult)[]): TResult => {
let result: any = {};
for(let key of keys){
result[key] = source.hasOwnProperty(key) ? (<any>source)[key] : null;
}
return result
}
describe("copy", () => {
it("copy specified members ", () => {
let result = copy<History, LocationQuery>(
/*from*/ history,
"showWarnings" ,
"showErrors",
"startDate",
"endDate",
"quickDate");
assert.deepEqual(expected, result);
assert.equal(
"showWarnings, showErrors, startDate, endDate, quickDate",
Object.keys(result).reduce( (a,b)=> a+ ", "+b)
)
});
})
With a list of field names:
function toLocationQuery(source) {
const fields = ['showWarnings', 'showErrors', 'startDate', 'endDate', 'quickDate']
let res = {}
for (let k of fields) {
if (source[k] === undefined)
throw new Error(`Missing field "${k}"`)
res[k] = source[k]
}
return res as LocationQuery
}
let query = toLocationQuery(location.query)
Or the same code but without to redeclare the list of fields for each call:
const toLocationQuery = (function () {
const fields = ['showWarnings', 'showErrors', 'startDate', 'endDate', 'quickDate']
return function (source) {
let res = {}
for (let k of fields) {
if (source[k] === undefined)
throw new Error(`Missing field "${k}"`)
res[k] = source[k]
}
return res as LocationQuery
}
})()
let query = toLocationQuery(location.query)
Related
I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});
I have an API that returns the following:
{
'data' : {
players: [
{id: 1, name: 'harry'},
{id: 2, name: 'barry'} ...
],
},
'paging': {
current: 1,
totalPages: 10,
}
}
How do I return this correctly with players in the object and also paging info so they are separate keys?
getPlayers(type: string): Observable<Player[]> {
return this.get(myApiURL).pipe(
map(result => {
'players': result.data.map(player => {
player._dead = !!player.deathDate;
return player;
}),
}
);
);
Interfaces
interface Player {
id;
name;
}
interface Paging {
current;
totalPages
}
interface HttpPlayer {
data: {
players: Player[]
};
paging: Paging;
}
interface UiPlayer {
id;
name;
current;
totalPages;
}
Mapping to merge players and paging (regarding your question)
const mergePlayersWithPaging = (data: HttpPlayer): UiPlayer [] => data.players.map(player => Object.assign({}, player, data.paging))
Mapping to separate players and paging (regarding your comment)
const mapHttpPlayerToPlayers = (httpPlayer: HttpPlayer): Player[] => httpPlayer.data.players;
const mapHttpPlayerToPaging = (httpPlayer: HttpPlayer):
Use the mapping (merge)
getPlayers(type: string): Observable<Player[]> {
return this.get(myApiURL).pipe(
map(mergePlayersWithPaging)
)
}
Use the mapping (separate)
getPlayers(type: string): Observable<Player[]> {
return this.get(myApiURL).pipe(
map(mapHttpPlayerToPlayers)
)
}
getPaging(type: string): Observable<Player[]> {
return this.get(myApiURL).pipe(
map(mapHttpPlayerToPaging)
)
}
I have the following:
...
type RepairsState = {
data: Property[] /* Property is an object coming from another file */
}
type RepairsPropertyLoadAction = {
type: typeof REPAIRS_PROPERTY_LOAD
payload: { models: Property[] } /* the payload will return an object that has a models property of an array of objects that match my property type */
}
/* Reducer */
export const initialState: RepairsState = {
data: [
{
id: '',
jobNo: '',
trade: '',
priority: '',
status: '',
raisedDate: new Date(),
appointmentDate: new Date(),
completedDate: new Date(),
description: ''
}
]
}
export default function reducer(state = initialState, action: RepairsPropertyLoadAction): RepairsState {
switch (action.type) {
case REPAIRS_PROPERTY_LOAD:
console.log(action.payload)
return {
...state,
data: action.payload
}
default:
return state
}
}
export const getRepairsProperty = (state: AppState) => state.repairs.data
...
Property class:
export default class Property {
id: string = ''
jobNo: string = ''
trade: string = ''
priority: string = ''
status: string = ''
raisedDate: Date = new Date()
appointmentDate: Date = new Date()
completedDate: Date = new Date()
description: string = ''
}
however I am getting the following error:
Type '{ models: Property[]; }' is missing the following properties from type 'Property[]': length, pop, push, concat, and 28 more. TS2740
The action returns an {models: Property []} object for the data, but the state has data: Property []
return {
...state,
data: action.payload.model
}
You are missing the models property, which is defined as part of RepairsPropertyLoadAction. The reducer should be returning this instead:
return {
...state,
data: action.payload.models,
}
I have a class "House" like :
class House{
constructor(params){
this.clear();
// this = {...params} // I know that don't work !!!
//--
// if(params.address !== undefined) this.address = {...params.address}
//...
}
clear(){
this.address = {
number: null,
street: null,
zipcode: null,
ton: null,
}
this.access = {
doorcode: null,
stair: null,
}
}
}
I want to create a new instance of House and inject in constructor multiple json like :
const h = new House({address: { /* json */ }, access: { /* json */});
Or only one like :
const h = new House({access: { /* json */});
In constructor, am i obliged to check all values in "params" to insert in good properties (nested object)
I would like to avoid to create other classes like address and access and in the house constructor create new instance of each.
What's the best practice ?
Regards
Using Object.assign() and object destructuring with default parameters in the constructor, you can achieve this quite easily:
class House {
static get defaultAddress () {
return {
number: null,
street: null,
zipcode: null,
town: null
}
}
static get defaultAccess () {
return {
doorcode: null,
stair: null
}
}
constructor({ address = House.defaultAddress, access = House.defaultAccess } = {}) {
this.clear()
Object.assign(this.address, address)
Object.assign(this.access, access)
}
clear () {
const { defaultAddress, defaultAccess } = House
Object.assign(this, { address: defaultAddress, access: defaultAccess })
}
}
// no object
console.log(new House())
// empty object
console.log(new House({}))
// partial object
console.log(new House({ address: { number: 1, street: 'street', zipcode: 12345, town: 'town' } }))
// empty sub-objects
console.log(new House({ address: {}, access: {} }))
// partial sub-objects
console.log(new House({ address: { number: 1, street: 'street' }, access: { doorcode: 321 } }))
// complete object
console.log(new House({ address: { number: 1, street: 'street', zipcode: 12345, town: 'town' }, access: { doorcode: 321, stair: 3 } }))
.as-console-wrapper{min-height:100%!important}
You can loop through the parameters and set them manually. Then, to clear, remove all own properties (properties that aren't inherited).
class House {
constructor(params) {
// set data
Object.assign(this, params);
}
clear() {
for (let key in this) {
if (this.hasOwnProperty(key))
this[key] = undefined; // or `delete this[key];`
}
}
}
let house = new House({type: "normal", height: 40});
console.log(house, house instanceof House);
Of course, you probably want to limit the input keys to a predefined set. You could store those keys in a static class variable and use them to loop through the properties in constructor and clear.
class House {
constructor(params) {
// check for invalid properties
Object.keys(params).forEach(key => {
if (!House.keys.includes(key))
throw `Invalid paramater ${key}`;
});
// set data
Object.assign(this, params);
}
clear() {
for (let key in House.keys) {
if (this.hasOwnProperty(key))
this[key] = undefined; // or `delete this[key];`
}
}
}
House.keys = ['type', 'height'];
let house = new House({type: 'normal', height: 40});
console.log(house, house instanceof House);
let error = new House({helloWorld: true});
I think you want a common namespace for your instance properties - similar to React's props pattern - you can also specify defaults for each instance you are creating:
const defaultProps = { address: {}, access: {} };
class House {
constructor(props = {}) {
this.props = {...defaultProps, ...props};
}
clear() {
this.props = {...defaultProps};
}
}
Using rollup, buble, flow-remove-types,
Is it possible to create an ENUM of classes instances for chess board representation, as types, like this:
// a Ref is a class or a type
class Ref { /* ... */ }
// Refs is an ENUM
Refs.forEach((ref: Ref, key: string) => {
console.log(key) // outputs: "a1", ..., "h8" successively
})
// type checking should work
typeof Refs.a1 === Ref // true
// etc...
typeof Refs.h8 === Ref // true
// move(ref) --> ref will work
Refs.a1.move(7, 7) === Refs.h8 // true
Refs.h8.move(-7, -7) === Refs.h8 // true
// with...
Refs.a1.move(0, 0) === Refs.a1 // true
// void reference
Refs.a1.move(-1, -1) === null
// or
Refs.a1.move(-1, -1) === Refs.EMPTY
A possible modular implementation would be packing the Ref class and the Refs collection in the same file, with a initialization code, like Enuify lib does... But how to make the Ref#move method working properly ??
The same as :
TicTacToe.X.us =TicTacToe.X
TicTacToe.X.them =TicTacToe.O
TicTacToe.O.us =TicTacToe.O
TicTacToe.O.them =TicTacToe.X
something like this, is perfectible, but works fine for me...
type TF = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'
type TR = '1'|'2'|'3'|'4'|'5'|'6'|'7'|'7'
type TRefDef = {
file: TF,
fidx: number,
rank: TR,
ridx: number
}
interface IRef {
move (df: number, dr: number) : IRef
}
const FILES: Array <TF> = 'abcdefgh'.split('')
const RANKS: Array <TR> = '12345678'.split('')
const all: {
[key:string] : IRef
} = {}
const compute = function(fidx: number, ridx: number): IRef {
const file: TF = FILES[fidx]
const rank: TR = RANKS[ridx]
return all[file + rank]
}
const select = function(key: string) : IRef {
return all[key]
}
const get = function(arg1: string | number, arg2: ?number) : IRef {
if(arguments.length === 1) {
return select (arg1)
}
if(arguments.length === 2) {
return compute (arg1, arg2)
}
}
const each = function (callback) {
Object.keys(all).forEach((key, idx) => {
callback.call(this, all[key], idx)
})
}
class Ref implements IRef {
constructor (refdef: TRefDef) {
this.file = refdef.file
this.fidx = refdef.fidx
this.rank = refdef.rank
this.ridx = refdef.ridx
this.key = this.file + this.rank
}
toString() : string {
return 'Ref: ' + '(' + this.fidx + ',' + this.ridx + ')' + ' ' + this.file + this.rank
}
move (df: number, dr: number) : Ref {
let f = FILES.indexOf(fidx)
let r = RANKS.indexOf(ridx)
f += df
r += dr
return all[FILES[f] + RANKS[r]]
}
}
FILES.forEach((file, fidx) => {
RANKS.forEach( (rank, ridx) => {
const key: string = file + rank
const ref: Ref = new Ref({ file, fidx, rank, ridx })
all[key] = ref
})
})
Ref.empty = new Ref('', -1, '', -1)
const Refs = { compute, select, each, get }
// let f = { compute, each, selection }
// console.log(f)
// export { compute, each, select, Ref }
export { Refs, Ref }