Pass object to Angular2 Component using Router - javascript

I'm poking Angular2 and it's Routing system. I'm creating 'Project Wizard' #Component with 'child' #Components using #RouteConfig and it looks like this:
const enum State {
welcome, basicData, groupsData, overview
}
const enum Condition {
back
}
#Component({
selector: 'router-outlet',
templateUrl: '/app/templates/wizard/project/project-wizard-container.html',
directives: [
ROUTER_DIRECTIVES,
],
})
#RouteConfig([
{ path: '/', name: 'ProjectWizardWelcome', component: ProjectWizardWelcomeComponent, useAsDefault: true },
{ path: '/step2', name: 'ProjectWizardStep2', component: ProjectWizardStep2Component },
{ path: '/step3', name: 'ProjectWizardStep3', component: ProjectWizardStep3Component },
{ path: '/overview', name: 'ProjectWizardOverview', component: ProjectWizardOverviewComponent },
])
export class ProjectWizardComponent {
mock: Mock = new Mock();
private mapping: {key: State, value: string}[] = [
{ key: State.welcome, value: 'ProjectWizardWelcome' },
{ key: State.basicData, value: 'ProjectWizardStep2' },
{ key: State.groupsData, value: 'ProjectWizardStep3' },
{ key: State.overview, value: 'ProjectWizardOverview' },
];
private transitions: FSM.Transition<State, Condition>[] = [
{ from: State.welcome, conditions: [], to: State.basicData },
{ from: State.basicData, conditions: [Condition.back], to: State.welcome },
{ from: State.basicData, conditions: [], to: State.groupsData },
{ from: State.groupsData, conditions: [Condition.back], to: State.basicData },
{ from: State.groupsData, conditions: [], to: State.overview },
{ from: State.overview, conditions: [Condition.back], to: State.groupsData },
];
private fsm: FSM<State, Condition> = new FSM(State.welcome, this.transitions);
constructor(
private _router: Router,
private _routeParams: RouteParams) {
}
onPrev(): void {
var prevState = this.fsm.apply([Condition.back]).get();
var prevRoute = this.mapping[prevState].value;
this._router.navigateByInstruction(this._router.generate([prevRoute]), true);
}
onNext(): void {
var nextState: State = this.fsm.apply([]).get();
var nextRoute = this.mapping[nextState].value;
this._router.navigateByInstruction(this._router.generate([nextRoute]), true);
}
onCancel(): void {
this._router.navigate(['Welcome']);
}
}
I need to share a Mock object across 'child' components and I want to understand what my options are. My current understanding is that:
it can be shared using container object which is #Injectable like some Service.
using RouterData. In this case, I would need to unmarshal data from url.
But are there any other ways to pass this object to #Components directly using router?

No, these two are the available options. I'd suggest a shared service.

Related

Testing component - Error in mounted hook: "TypeError: Cannot read property 'dispatch' of undefined"

I am trying to write a simple test for my vue component. Since the vue component makes an async call on mount and updates the vuex store, dispatch is called during mount, which breaks my existing unit tests. Any idea how to overcome this? Since I am mocking table data, I don't need the mounted() function to be called when running the tests.
MyTable.spec.js
const wrapper = shallowMount(MyTable, {
propsData: {
tableData: [
{
"product_id":10826345236,
"name":"T-Shirt"
}
],
columns: ['product_id', 'name'],
headings: ['Product ID', 'Name'],
actionType: 'loadProducts'
}
});
...
MyTable.vue
...
data() {
return {
options: {
...
}
};
},
methods: {
getHeadings() {
let headings = {};
this.columns.map((key, i) => headings[key] = this.headings[i]);
return headings;
},
setColumnClasses() {
let classes = {};
this.columns.map((key) => classes[key] = key);
return classes;
},
loadRecords(actionType) {
this.$store.dispatch(actionType);
}
},
props: {
tableData: {
type: Array,
required: true
},
columns: {
type: Array,
required: true
},
actionType: {
type: String,
required: true
},
headings: {
type: Array,
required: true
},
...
},
mounted() {
this.loadRecords(this.actionType);
}
You are getting this error message because Vue (when mounted) is expecting that the this.$store is defined, and while it might be within your application, you are not importing it, nor are you mocking it.
Here is your test function code you provided:
const wrapper = shallowMount(MyTable, {
propsData: {
tableData: [
{
"product_id":10826345236,
"name":"T-Shirt"
}
],
columns: ['product_id', 'name'],
headings: ['Product ID', 'Name'],
actionType: 'loadProducts'
}
});
Here is what you need to add:
import store from '../path/to/store.js';
import { createLocalVue, shallowMount } from '#vue/test-utils';
// You will want to create a local Vue instance for testing purposes: https://vue-test-utils.vuejs.org/api/#createlocalvue
const localVue = createLocalVue();
// This tells the local Vue instance to use Vuex for the store mechanism.
localVue.use(Vuex);
const wrapper = shallowMount(MyTable, {
localVue, // Bind the local Vue instance when we shallow-mount the component.
store, // Bind the store so all of the methods are available when invoked.
propsData: {
tableData: [
{
"product_id":10826345236,
"name":"T-Shirt"
}
],
columns: ['product_id', 'name'],
headings: ['Product ID', 'Name'],
actionType: 'loadProducts'
}
});

Dynamically add navigation items in Vue CoreUI

In my project, I want to add some Ajax loaded menu items to my CoreUI sidebar in Vue. I already found a working solution, but it's kind of hacky and might have timing issues. Therefore I want to ask you, if there is a proper or at least better solution.
I also found this question from a few days ago, but it doesn't have an answer yet.
// main.js
new Vue({
el: '#app',
router,
icons,
template: '<App/>',
components: {
App
},
data: {
clientConfiguration: null
},
created: async function () {
let svcResult = await this.$http.get('Picking/ViewerSettings');
this.clientConfiguration = svcResult.data;
this.$children[0].$children[0].$children[0].$data.nav[0]._children[0].items =
svcResult.data.map(vc => ({
name: vc.name,
to: 'test/' + vc.name,
icon: 'cil-spreadsheet'
}));
}
})
// _nav.js
export default [
{
_name: 'CSidebarNav',
_children: [
{
_name: 'CSidebarNavDropdown',
name: 'Lists',
to: '/test',
icon: 'cil-list-numbered',
items: []
},
// ...
]
}
]
The _nav.js file is just an example of data structure that can be rendered by CRenderFunction component docs
The idea behind CRenderFunction is that you can render components from the Array/Object.
In your case, you have two options:
generate CRenderFunction object on backend,
generate CRenderFunction object on frontend by computed properties, based on data you got from the backend
Here is the example of the second approach:
in template
<CRenderFunction flat :content-to-render="navItems"/>
in script:
//example array that you receive from backend
const menuItems = [
{
name: 'first item',
to: '/first',
icon: 'cil-user'
},
{
name: 'second item',
to: '/second'
},
{
name: 'third item',
to: '/third'
}
]
export default {
computed: {
navItems () {
return [
{
_name: 'CSidebarNav',
_children: this.sidebarNavChildren
}
]
},
sidebarNavChildren () {
return menuItems.map(menuItem => {
return {
_name: 'CSidebarNavItem',
name: menuItem.name,
to: menuItem.to,
icon: menuItem.icon || 'cil-spreadsheet'
}
})
}
}
}
navItems computed property result:
[{"_name":"CSidebarNav","_children": [
{"_name":"CSidebarNavItem","name":"first item","to":"/first","icon":"cil-user"},
{"_name":"CSidebarNavItem","name":"second item","to":"/second","icon":"cil-spreadsheet"},
{"_name":"CSidebarNavItem","name":"third item","to":"/third","icon":"cil-spreadsheet"}
]
}]

How to create a tree as extension for Vs code

I want to create a tree in VS code, but my problem is how to manually add a node to my tree. I am not sure from where to start. I tried to review all the projects that created a tree for VScode as an extension.
My problem is that I am not an expert in Typescript and the examples are not so clear or I am not sure how it is working.
Would you mind helping me to understand how to create the tree in VS code? My problem is with creating a node and then adding the node to tree.
I reviewed these projects:
vscode-code-outline
vscode-extension-samples
vscode-git-tree-compare
vscode-html-languageserver-bin
vscode-mock-debug
vscode-tree-view
Update1:
I managed to use "vscode-extension-samples" and generate the below code examples; now I don't know what I should do, or in other words, how to fill the tree. I tried to use mytree class to fill the data but it didn't work. Would you mind advising me what is next?
extension.ts
'use strict';
import * as vscode from 'vscode';
import { DepNodeProvider } from './nodeDependencies'
import { JsonOutlineProvider } from './jsonOutline'
import { FtpExplorer } from './ftpExplorer.textDocumentContentProvider'
import { FileExplorer } from './fileExplorer';
//mycode
import { SCCExplorer } from './sccExplorer';
export function activate(context: vscode.ExtensionContext) {
// Complete Tree View Sample
new FtpExplorer(context);
new FileExplorer(context);
//mycode
new SCCExplorer(context);
// Following are just data provider samples
const rootPath = vscode.workspace.rootPath;
const nodeDependenciesProvider = new DepNodeProvider(rootPath);
const jsonOutlineProvider = new JsonOutlineProvider(context);
vscode.window.registerTreeDataProvider('nodeDependencies', nodeDependenciesProvider);
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () => nodeDependenciesProvider.refresh());
vscode.commands.registerCommand('nodeDependencies.addEntry', node => vscode.window.showInformationMessage('Successfully called add entry'));
vscode.commands.registerCommand('nodeDependencies.deleteEntry', node => vscode.window.showInformationMessage('Successfully called delete entry'));
vscode.commands.registerCommand('extension.openPackageOnNpm', moduleName => vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`https://www.npmjs.com/package/${moduleName}`)));
vscode.window.registerTreeDataProvider('jsonOutline', jsonOutlineProvider);
vscode.commands.registerCommand('jsonOutline.refresh', () => jsonOutlineProvider.refresh());
vscode.commands.registerCommand('jsonOutline.refreshNode', offset => jsonOutlineProvider.refresh(offset));
vscode.commands.registerCommand('jsonOutline.renameNode', offset => jsonOutlineProvider.rename(offset));
vscode.commands.registerCommand('extension.openJsonSelection', range => jsonOutlineProvider.select(range));
}
sccExplorer.ts
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';
//#region Utilities
interface Entry {
uri: vscode.Uri,
type: vscode.FileType
}
//#endregion
export class FileSystemProvider implements vscode.TreeDataProvider<Entry> {
getTreeItem(element: Entry): vscode.TreeItem | Thenable<vscode.TreeItem> {
throw new Error("Method not implemented.");
}
onDidChangeTreeData?: vscode.Event<Entry>;
getChildren(element?: Entry): vscode.ProviderResult<Entry[]> {
throw new Error("Method not implemented.");
}
getParent?(element: Entry): vscode.ProviderResult<Entry> {
throw new Error("Method not implemented.");
}
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
constructor() {
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
}
}
export class SCCExplorer {
private fileExplorer: vscode.TreeView<any>;
constructor(context: vscode.ExtensionContext) {
const treeDataProvider = new myTree().directories;
this.fileExplorer = vscode.window.createTreeView('scc_Explorer', { treeDataProvider });
vscode.commands.registerCommand('scc_Explorer.openFile', (resource) => this.openResource(resource));
}
private openResource(resource: vscode.Uri): void {
vscode.window.showTextDocument(resource);
}
}
export class myTree{
directories: any;
constructor()
{
this.directories = [
{
name: 'parent1',
child: [{
name: 'child1',
child: []
},
{
name: 'child2',
child: []
}]
},
{
name: 'parent2',
child: {
name: 'child1',
child: []
}
},
{
name: 'parent2',
child: [{
name: 'child1',
child: []
},
{
name: 'child2',
child: []
}]
}];
}
}
I finally got it working. It took me very long and I had it right all the time. My issue was I never did explicitly expand the items, so I would never see nested results and only the top level.
Basic working example
import * as vscode from "vscode";
export class OutlineProvider
implements vscode.TreeDataProvider<any> {
constructor(private outline: any) {
console.log(outline);
}
getTreeItem(item: any): vscode.TreeItem {
return new vscode.TreeItem(
item.label,
item.children.length > 0
? vscode.TreeItemCollapsibleState.Expanded
: vscode.TreeItemCollapsibleState.None
);
}
getChildren(element?: any): Thenable<[]> {
if (element) {
return Promise.resolve(element.children);
} else {
return Promise.resolve(this.outline);
}
}
}
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand(
"outliner.outline",
async () => {
vscode.window.registerTreeDataProvider(
"documentOutline",
new OutlineProvider([dataObject])
);
}
);
context.subscriptions.push(disposable);
}
const dataObject = {
label: "level one",
children: [
{
label: "level two a",
children: [
{
label: "level three",
children: [],
},
],
},
{
label: "level two b",
children: [],
},
],
}
And of course in package.json
"contributes": {
"commands": [
{
"command": "outliner.outline",
"title": "Outline"
}
],
"views": {
"explorer": [
{
"id": "documentOutline",
"name": "Document Outline"
}
]
}
},
Types
note the type for treeDataProvider is not neccecarly what you return. Only the getTree item has to return a tree item or a class that extends it.
interface CustomType {
label: string
children?: CustomType[]
}
export class TypeExample
implements vscode.TreeDataProvider<CustomType> {
constructor(private data: CustomType[]) { }
getTreeItem(element: CustomType): vscode.TreeItem {
return new vscode.TreeItem(
element.label,
(element.children?.length ?? 0) > 0
? vscode.TreeItemCollapsibleState.Expanded
: vscode.TreeItemCollapsibleState.None
);
}
getChildren(element?: CustomType): Thenable<CustomType[]> {
return element && Promise.resolve(element.children ?? [])
|| Promise.resolve(this.data);
}
}
I thought at first the type of the data provider should be the return type of the tree item, this doesnt make much sense of course and I was trying to wrap my head around the reasoning. Now I understand that you pass your custom type in and all other methods inherit this type and expect this type as its argument. Only the getTreeItem method has to return a valid tree item that can be rendered.

dat.GUI and Complex Variables

I'm fairly new to dat.GUI and I'm having problems getting it to work with a more complex variable.
I have the following:
let complexVariable= {
topLayer:
{
deeperLayer:
{
deepestLayer: 20,
//Other stuff
}
}
}
let gui = new dat.GUI();
gui.add(complexVariable, "topLayer.deeperLayer.deepestLayer", 10, 40);
This gives me the following error:
Uncaught Error: Object "[object Object]" has no property "topLayer.deeperLayer.deepestLayer"
Any help here would be appreciated.
It currently doesn't seem possible by looking at the source code. They use bracket notation with the single property you pass in on the object.
function add(gui, object, property, params) {
if (object[property] === undefined) {
throw new Error(`Object "${object}" has no property "${property}"`);
}
...
So what you are telling dat.GUI to do is find a top level property "topLayer.deeperLayer.deepestLayer" and that obviously does not exist on your object. It seems like more code would have to be written to support nested properties.
dat.gui would have to do something like if (object[property1][property2][...] === undefined) or in your case - complexVariable["topLayer"]["deeperLayer"]["deepestLayer"];
It's obvious that question is ~4 years old. But I was searching for the same and I found your question before realizing that it's possible. Below is E2E sample in Angular.
import { AfterViewInit, Component, ViewChild, ViewContainerRef } from '#angular/core';
import { GUI, GUIController, GUIParams } from 'dat.gui';
export type EditorMetadata = Array<IEditorFolder>;
export interface IDatState {
state: EditorMetadata;
instance: GUI;
metadata: EditorMetadata;
}
export interface IEditorFolder {
title: string;
property?: string;
tooltip: string;
elements?: Array<IEditorElement>;
folders?: Array<IEditorFolder>;
folderRef?: GUI;
}
export interface IEditorElement {
title: string;
tooltip: string;
options?: any;
elementRef?: GUIController;
target?: Object;
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit {
#ViewChild('container', { read: ViewContainerRef, static: true })
container: ViewContainerRef;
constructor() { }
ngAfterViewInit(): void {
this.composeEditor(this.container.element.nativeElement, options, foldersMetadata);
}
composeEditor(container: HTMLElement, editorOptions: GUIParams, editorMetadata: EditorMetadata): IDatState {
const
addFolder = (folder: IEditorFolder, parent: GUI): GUI => {
const
{ title, tooltip } = folder,
folderController = parent.addFolder(title);
folderController.domElement.setAttribute('title', tooltip);
return folderController;
},
addElement = (folder: GUI, element: IEditorElement, target: Object): GUIController => {
const
{ title, options, tooltip } = element,
elmController = folder
.add(target, title, options)
.name(title)
.onChange((value) => {
console.log(model);
});
elmController.domElement.setAttribute('title', tooltip);
return elmController;
},
createDat = (options: GUIParams, metadata: EditorMetadata): IDatState => {
const
state: IDatState = {
instance: new GUI(options),
state: JSON.parse(JSON.stringify(metadata)), // Don't touch original metadata, instead clone it.
metadata: metadata, // Return original metadata
};
return state;
},
process = (folders: EditorMetadata, parent: GUI, model: Object): void => {
folders.forEach(folder => {
const target: Object = folder.property ? model[folder.property] : model; // Root level or object nested prop?
folder.folderRef = addFolder(folder, parent);
folder.elements && folder.elements.forEach(element => element.elementRef = addElement(folder.folderRef, element, target));
folder.folders && process(folder.folders, folder.folderRef, target);
});
},
{ state, instance, metadata } = createDat(editorOptions, editorMetadata);
process(state, instance, model);
container.appendChild(instance.domElement);
return { state, instance, metadata };
}
}
const options: GUIParams = {
hideable: false,
autoPlace: false,
closeOnTop: false,
width: 250,
name: 'DAT Sample'
};
const model = {
rtl: true,
renderer: 'geras',
theme: 'dark',
toolbox: 'foundation',
toolboxPosition: 'left',
debug: {
alert: false
}
};
const foldersMetadata: EditorMetadata = [
{
title: 'Options',
tooltip: 'Options AA',
elements: [
{
title: 'rtl',
tooltip: 'RTL',
},
{
title: 'renderer',
tooltip: 'Renderer',
options: ['geras', 'thrasos', 'zelos']
},
{
title: 'theme',
tooltip: 'Theme',
options: ['classic', 'dark', 'deuteranopia', 'tritanopia', 'highcontrast']
},
{
title: 'toolbox',
tooltip: 'Toolbox',
options: ['foundation', 'platform', 'invoice']
},
{
title: 'toolboxPosition',
tooltip: 'Toolbox Position',
options: ['left', 'right', 'top', 'bottom']
},
],
folders: [
{
title: 'Debug',
property: 'debug',
tooltip: 'Debug mode',
elements: [
{
title: 'alert',
tooltip: 'Alert Tooltip',
},
]
}
]
},
];

How to properly write a for loop using the request from an axios request in js?

I am trying to write a for loop that builds a dictionary for each object returned in the axios request.
My understanding is that the logic to "do stuff" with the returned object must be contained with axios '.then' block.
Here is my attempt at trying this (coming from a python background, so probably lots of errors here)
var jsonPayload = {}
axios.get('http://localhost:8080/api/tools')
.then(function (response) {
jsonPayload = response.data
console.log(jsonPayload)
const children = [];
jsonPayload.forEach(item => {
const dict = {
name: item.name,
path: item.path,
meta: {
label: item.label,
link: item.link,
},
component: lazyLoading('testitem/basic'),
}
children.push(dict);
});
})
.catch(function (error) {
console.log(error)
})
THIS is what I'm trying to accomplish:
const state = {
items: [
{
name: 'android',
path: '/android',
meta: {
icon: 'fa-tachometer',
link: 'android/index.vue'
},
children: [
{
name: 'jsonResponse_name',
path: 'jsonResponse_path',
meta: {
label: 'jsonResponse_label',
link: 'jsonResponse_link'
},
component: lazyLoading('test')
},
{
name: 'jsonResponse_name',
path: 'jsonResponse_path',
meta: {
label: 'jsonResponse_label',
link: 'jsonResponse_link'
},
component: lazyLoading('test')
},
{
name: 'jsonResponse_name',
path: 'jsonResponse_path',
meta: {
label: 'jsonResponse_label',
link: 'jsonResponse_link'
},
component: lazyLoading('test')
},
]
},
not sure if this is helpful: router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import menuModule from 'vuex-store/modules/menu'
Vue.use(Router)
export default new Router({
mode: 'hash', // Demo is living in GitHub.io, so required!
linkActiveClass: 'is-active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
name: 'Home',
path: '/',
component: require('../views/Home')
},
{
name: 'Login',
path: '/login',
component: require('../views/auth/Login')
},
...generateRoutesFromMenu(menuModule.state.items),
{
path: '*',
redirect: '/'
}
]
})
// Menu should have 2 levels.
function generateRoutesFromMenu (menu = [], routes = []) {
for (let i = 0, l = menu.length; i < l; i++) {
let item = menu[i]
if (item.path) {
routes.push(item)
}
if (!item.component) {
generateRoutesFromMenu(item.children, routes)
}
}
return routes
}

Categories