I met a problem while I implemented a angular application with material tree view.
thie tree view is dyniamic, that means that you can add child node if you click add button.
the HTML code looks like this
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-checkbox class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="todoLeafItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-form-field>
<input matInput #itemValue placeholder="New item...">
</mat-form-field>
<mat-form-field appearance="legacy">
<input matInput type="text" [formControl]="locationField" [(ngModel)]="node.field" name="node" [matAutocomplete]="auto" placeholder="Field"/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let filteredFieldResult of locationFieldResults" [value]="filteredFieldResult">
{{filteredFieldResult}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<button mat-button (click)="saveNode(node, itemValue.value)">Save</button>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename">
<mat-icon class="mat-icon-rtl-mirror">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
<mat-checkbox [checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
<button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
</mat-tree-node>
</mat-tree>
you can see that I have a input field with autocomplet.
and the ts code is as follow:
import {SelectionModel} from '#angular/cdk/collections';
import {FlatTreeControl} from '#angular/cdk/tree';
import {Component, Injectable} from '#angular/core';
import {MatTreeFlatDataSource, MatTreeFlattener} from '#angular/material/tree';
import {BehaviorSubject} from 'rxjs';
import { FormControl } from '#angular/forms';
/**
* Node for to-do item
*/
export class TodoItemNode {
children: TodoItemNode[];
item: string;
}
/** Flat to-do item node with expandable and level information */
export class TodoItemFlatNode {
item: string;
level: number;
expandable: boolean;
}
/**
* The Json object for to-do list data.
*/
const TREE_DATA = {
Groceries: {
'Almond Meal flour': null,
'Organic eggs': null,
'Protein Powder': null,
Fruits: {
Apple: null,
Berries: ['Blueberry', 'Raspberry'],
Orange: null
}
},
Reminders: [
'Cook dinner',
'Read the Material Design spec',
'Upgrade Application to Angular'
]
};
/**
* Checklist database, it can build a tree structured Json object.
* Each node in Json object represents a to-do item or a category.
* If a node is a category, it has children items and new items can be added under the category.
*/
#Injectable()
export class ChecklistDatabase {
dataChange = new BehaviorSubject<TodoItemNode[]>([]);
get data(): TodoItemNode[] { return this.dataChange.value; }
constructor() {
this.initialize();
}
initialize() {
// Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested
// file node as children.
const data = this.buildFileTree(TREE_DATA, 0);
// Notify the change.
this.dataChange.next(data);
}
/**
* Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object.
* The return value is the list of `TodoItemNode`.
*/
buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[] {
return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => {
const value = obj[key];
const node = new TodoItemNode();
node.item = key;
if (value != null) {
if (typeof value === 'object') {
node.children = this.buildFileTree(value, level + 1);
} else {
node.item = value;
}
}
return accumulator.concat(node);
}, []);
}
/** Add an item to to-do list */
insertItem(parent: TodoItemNode, name: string) {
if (parent.children) {
parent.children.push({item: name} as TodoItemNode);
this.dataChange.next(this.data);
}
}
updateItem(node: TodoItemNode, name: string) {
node.item = name;
this.dataChange.next(this.data);
}
}
/**
* #title Tree with checkboxes
*/
#Component({
selector: 'tree-checklist-example',
templateUrl: 'tree-checklist-example.html',
styleUrls: ['tree-checklist-example.css'],
providers: [ChecklistDatabase]
})
export class TreeChecklistExample {
/** Map from flat node to nested node. This helps us finding the nested node to be modified */
flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>();
/** Map from nested node to flattened node. This helps us to keep the same object for selection */
nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>();
public locationField: FormControl = new FormControl();
public locationFieldResults = ["abc", "asdfa", "asdfasd"]
/** A selected parent node to be inserted */
selectedParent: TodoItemFlatNode | null = null;
/** The new item's name */
newItemName = '';
treeControl: FlatTreeControl<TodoItemFlatNode>;
treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;
dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;
/** The selection for checklist */
checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */);
constructor(private _database: ChecklistDatabase) {
this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel,
this.isExpandable, this.getChildren);
this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
_database.dataChange.subscribe(data => {
this.dataSource.data = data;
});
this.locationField.valueChanges.subscribe(inputField => {
this.filterField(inputField);
}
);
}
getLevel = (node: TodoItemFlatNode) => node.level;
isExpandable = (node: TodoItemFlatNode) => node.expandable;
getChildren = (node: TodoItemNode): TodoItemNode[] => node.children;
hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable;
hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '';
/**
* Transformer to convert nested node to flat node. Record the nodes in maps for later use.
*/
transformer = (node: TodoItemNode, level: number) => {
const existingNode = this.nestedNodeMap.get(node);
const flatNode = existingNode && existingNode.item === node.item
? existingNode
: new TodoItemFlatNode();
flatNode.item = node.item;
flatNode.level = level;
flatNode.expandable = !!node.children;
this.flatNodeMap.set(flatNode, node);
this.nestedNodeMap.set(node, flatNode);
return flatNode;
}
/** Whether all the descendants of the node are selected. */
descendantsAllSelected(node: TodoItemFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
const descAllSelected = descendants.every(child =>
this.checklistSelection.isSelected(child)
);
return descAllSelected;
}
/** Whether part of the descendants are selected */
descendantsPartiallySelected(node: TodoItemFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
const result = descendants.some(child => this.checklistSelection.isSelected(child));
return result && !this.descendantsAllSelected(node);
}
/** Toggle the to-do item selection. Select/deselect all the descendants node */
todoItemSelectionToggle(node: TodoItemFlatNode): void {
this.checklistSelection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
// Force update for the parent
descendants.every(child =>
this.checklistSelection.isSelected(child)
);
this.checkAllParentsSelection(node);
}
/** Toggle a leaf to-do item selection. Check all the parents to see if they changed */
todoLeafItemSelectionToggle(node: TodoItemFlatNode): void {
this.checklistSelection.toggle(node);
this.checkAllParentsSelection(node);
}
/* Checks all the parents when a leaf node is selected/unselected */
checkAllParentsSelection(node: TodoItemFlatNode): void {
let parent: TodoItemFlatNode | null = this.getParentNode(node);
while (parent) {
this.checkRootNodeSelection(parent);
parent = this.getParentNode(parent);
}
}
/** Check root node checked state and change it accordingly */
checkRootNodeSelection(node: TodoItemFlatNode): void {
const nodeSelected = this.checklistSelection.isSelected(node);
const descendants = this.treeControl.getDescendants(node);
const descAllSelected = descendants.every(child =>
this.checklistSelection.isSelected(child)
);
if (nodeSelected && !descAllSelected) {
this.checklistSelection.deselect(node);
} else if (!nodeSelected && descAllSelected) {
this.checklistSelection.select(node);
}
}
/* Get the parent node of a node */
getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null {
const currentLevel = this.getLevel(node);
if (currentLevel < 1) {
return null;
}
const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;
for (let i = startIndex; i >= 0; i--) {
const currentNode = this.treeControl.dataNodes[i];
if (this.getLevel(currentNode) < currentLevel) {
return currentNode;
}
}
return null;
}
/** Select the category so we can insert the new item. */
addNewItem(node: TodoItemFlatNode) {
const parentNode = this.flatNodeMap.get(node);
this._database.insertItem(parentNode!, '');
this.treeControl.expand(node);
}
/** Save the node to database */
saveNode(node: TodoItemFlatNode, itemValue: string) {
const nestedNode = this.flatNodeMap.get(node);
this._database.updateItem(nestedNode!, itemValue);
}
private filterField(value: string): string[] {
if (value) {
this.locationFieldResults = this.locationFieldResults.filter((searchFieldResult) => {
return searchFieldResult.indexOf(value) !== -1;
});
} else {
this.locationFieldResults = this.locationFieldResults;
}
return this.locationFieldResults;
}
}
the problem is: If I tipped some content in input field, by adding new child node, the tipped content in old input field disappeared.
any solutions are expected.
this is example from angular material, you can copy the html and ts file into it,
https://stackblitz.com/angular/nnxeaxmrdob?file=src%2Fapp%2Ftree-checklist-example.ts
Related
I am migrating a Laravel app to Node app using TypeORM. Has anyone been able to implement something similar to Laravel's Polymorphic Relations in TypeOrm?
Example schema I am trying to reproduce:
export class Notification {
id: string;
attachable_id: number;
attachable_type: string;
}
I want to be able to to have a notification.attachable relation that could be of any type. Then, ideally, I can eager load a user with their last x notifications, with the attachable on each notification.
EDIT:
So I done a refactor/rewrite and put it all in a repo https://github.com/bashleigh/typeorm-polymorphic
So, I've been thinking of trying to implement something for this for a while. I had 2 days to implement something in a hurry so I made this crud thing.
import {
FindManyOptions,
DeepPartial,
ObjectID,
FindConditions,
UpdateResult,
Repository,
SaveOptions,
} from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
export interface PolymorphicInterface {
entityId: string;
entityType: string;
}
export type PolyMorphicType<K> = PolymorphicInterface & DeepPartial<K>;
export const POLYMORPHIC_RELATIONSHIP = 'POLYMORPHIC_RELATIONSHIP';
export interface PolymorphicOptions {
type: Function;
parent: Function;
property: string | Symbol;
}
export const PolyMorphic = (type: Function): PropertyDecorator => (
target: Object,
propertyKey: string | Symbol,
): void =>
Reflect.defineMetadata(
`${POLYMORPHIC_RELATIONSHIP}::${propertyKey}`,
{
type,
parent: target.constructor.name,
property: propertyKey,
},
target,
);
export class PolymorphicRepository<T extends DeepPartial<T>> extends Repository<T> {
private getMetadata(): Array<PolymorphicOptions> {
let keys = Reflect.getMetadataKeys((this.metadata.target as Function)['prototype']);
if (!Array.isArray(keys)) {
return [];
}
keys = keys.filter((key: string) => {
const parts = key.split('::');
return parts[0] === POLYMORPHIC_RELATIONSHIP;
});
if (!keys) {
return [];
}
return keys.map(
(key: string): PolymorphicOptions =>
Reflect.getMetadata(key, (this.metadata.target as Function)['prototype']),
);
}
async find(findOptions?: FindConditions<T> | FindManyOptions<T>): Promise<T[]> {
const polymorphicMetadata = this.getMetadata();
if (Object.keys(polymorphicMetadata).length === 0) {
return super.find(findOptions);
}
const entities = await super.find(findOptions);
return this.hydratePolymorphicEntities(entities);
}
public async hydratePolymorphicEntities(entities: Array<T>): Promise<Array<T>> {
const metadata = this.getMetadata();
metadata.forEach(
async (data: PolymorphicOptions): Promise<void> => {
await Promise.all(
entities.map(
async (entity: T): Promise<void> => {
const repository = this.manager.getRepository(data.type);
const property = data.property;
const parent = data.parent;
if (!repository) {
throw new Error(
`Repository not found for type [${
data.type
}] using property [${property}] on parent entity [${parent}]`,
);
}
const morphValues = await repository.find({
where: {
//#ts-ignore
entityId: entity.id, // TODO add type AbstractEntity
entityType: this.metadata.targetName,
},
});
//#ts-ignore
entity[property] = morphValues;
},
),
);
},
);
return entities;
}
public async update(
criteria:
| string
| string[]
| number
| number[]
| Date
| Date[]
| ObjectID
| ObjectID[]
| FindConditions<T>,
partialEntity: QueryDeepPartialEntity<T>,
): Promise<UpdateResult> {
const polymorphicMetadata = this.getMetadata();
if (Object.keys(polymorphicMetadata).length === 0) {
return super.update(criteria, partialEntity);
}
const result = super.update(criteria, partialEntity);
// TODO update morphs
throw new Error("CBA I'm very tired");
return result;
}
public async save<E extends DeepPartial<T>>(
entity: E | Array<E>,
options?: SaveOptions & { reload: false },
): Promise<E & T | Array<E & T>> {
const polymorphicMetadata = this.getMetadata();
if (Object.keys(polymorphicMetadata).length === 0) {
return Array.isArray(entity) ? super.save(entity, options) : super.save(entity);
}
const result = Array.isArray(entity)
? await super.save(entity, options)
: await super.save(entity);
Array.isArray(result)
? await Promise.all(result.map((res: T) => this.saveMorphs(res)))
: await this.saveMorphs(result);
return result;
}
private async saveMorphs(entity: T): Promise<void> {
const metadata = this.getMetadata();
await Promise.all(
metadata.map(
async (data: PolymorphicOptions): Promise<void> => {
const repository: Repository<PolymorphicInterface> = this.manager.getRepository(
data.type,
);
const property = data.property;
const parent = data.parent;
const value: Partial<PolymorphicInterface> | Array<Partial<PolymorphicInterface>> =
//#ts-ignore
entity[property];
if (typeof value === 'undefined' || value === undefined) {
return new Promise(resolve => resolve());
}
if (!repository) {
throw new Error(
`Repository not found for type [${
data.type
}] using property [${property}] on parent entity [${parent}]`,
);
}
let result: Array<any> | any;
if (Array.isArray(value)) {
//#ts-ignore
result = await Promise.all(
value.map(val => {
// #ts-ignore
val.entityId = entity.id;
val.entityType = this.metadata.targetName;
return repository.save(
value instanceof data.type ? value : repository.create(value),
);
}),
);
} else {
// #ts-ignore
value.entityId = entity.id; // TODO resolve AbstractEntity for T
value.entityType = this.metadata.targetName;
result = await repository.save(
value instanceof data.type ? value : repository.create(value),
);
}
// #ts-ignore
entity[property] = result;
},
),
);
}
}
It's pretty rough but that's what I implemented to tackle this. Essentially I've implemented is my own methods to handle saving of entities that are defined within the metadata by creating my own repository.
Then you can use it like so
#Entity()
export class TestEntity {
#PolyMorphic(SomeOtherEntity)
property: SomeOtherEntity[];
}
The typings are really bad but that's only because I've had 1 days to implement this feature and I did it on the plane
I have weird issue when trying to create a custom autocomplete editor.
Basicly what I've done is I've pulled the built-in AutocompleteEditor class and refactored it to plain ES6, and renamed the class to ProductSelectEditor. No modifications to the code logic.
When I try to use it, I'm getting error "Cannot read property 'onCommit' of undefined" when handleChange() is called:
handleChange() {
this.props.onCommit(); // props undefined
}
Now if i replace the editor with the real built-in AutocompleteEditor, it works just fine. I can't see any straight reason, why my custom version does not work, when only alterations I'm doing are refactoring the code away from TypeScript, renaming the class, and eventually exporting the class out as default?
Any clues on what I'm not understanding here?
Below is the whole refactored code
import React from 'react'
import ReactDOM from 'react-dom'
import ReactAutocomplete from 'ron-react-autocomplete';
import PropTypes from 'prop-types';
import '../css/ron-react-autocomplete.css'
const { shapes: { ExcelColumn } } = require('react-data-grid')
let optionPropType = PropTypes.shape({
id: PropTypes.required,
title: PropTypes.string
});
export default class ProductSelectEditor extends React.Component {
static propTypes = {
onCommit: PropTypes.func,
options: PropTypes.arrayOf(optionPropType),
label: PropTypes.any,
value: PropTypes.any,
height: PropTypes.number,
valueParams: PropTypes.arrayOf(PropTypes.string),
column: PropTypes.shape(ExcelColumn),
resultIdentifier: PropTypes.string,
search: PropTypes.string,
onKeyDown: PropTypes.func,
onFocus: PropTypes.func,
editorDisplayValue: PropTypes.func
};
static defaultProps = {
resultIdentifier: 'id'
};
handleChange() {
this.props.onCommit();
}
getValue() {
let value;
let updated = {};
if (this.hasResults() && this.isFocusedOnSuggestion()) {
value = this.getLabel(this.autoComplete.state.focusedValue);
if (this.props.valueParams) {
value = this.constuctValueFromParams(this.autoComplete.state.focusedValue, this.props.valueParams);
}
} else {
value = this.autoComplete.state.searchTerm;
}
updated[this.props.column.key] = value;
return updated;
}
getEditorDisplayValue() {
let displayValue = {title: ''};
let { column, value, editorDisplayValue } = this.props;
if (editorDisplayValue && typeof editorDisplayValue === 'function') {
displayValue.title = editorDisplayValue(column, value);
} else {
displayValue.title = value;
}
return displayValue;
}
getInputNode() {
return ReactDOM.findDOMNode(this).getElementsByTagName('input')[0];
}
getLabel(item) {
let label = this.props.label != null ? this.props.label : 'title';
if (typeof label === 'function') {
return label(item);
} else if (typeof label === 'string') {
return item[label];
}
}
hasResults() {
return this.autoComplete.state.results.length > 0;
}
isFocusedOnSuggestion() {
let autoComplete = this.autoComplete;
return autoComplete.state.focusedValue != null;
}
constuctValueFromParams(obj, props) {
if (!props) {
return '';
}
let ret = [];
for (let i = 0, ii = props.length; i < ii; i++) {
ret.push(obj[props[i]]);
}
return ret.join('|');
}
render() {
let label = this.props.label != null ? this.props.label : 'title';
return (<div height={this.props.height} onKeyDown={this.props.onKeyDown}>
<ReactAutocomplete search={this.props.search} ref={(node) => this.autoComplete = node} label={label} onChange={this.handleChange} onFocus={this.props.onFocus} resultIdentifier={this.props.resultIdentifier} options={this.props.options} value={this.getEditorDisplayValue()} />
</div>);
}
}
Alright, after few hours of poking and mangling found the reason for the props to be undefined. Apparently after stripping out the Typescripts, I needed to re-bind 'this' in order to get the correct context:
<ReactAutocomplete ... onChange={this.handleChange.bind(this)} ... />
I am using https://pub.dartlang.org/packages/drails_validator to validate a data model. The errors generated are then displayed by the error-message property of a paper-input element.
Given the following codes
.dart
#PolymerRegister('language-form')
class LanguageForm extends PolymerElement
{
#Property( notify: true )
Language language = new Language( );
String topic = '';
IronIcon validationIcon;
IronCollapse collapse;
#property
String errorMsg = '';
LanguageForm.created() : super.created();
#reflectable
void onInputHandler(event, [_]) {
//String property = toLowerCamelCase( event.currentTarget.id );
String id = event.currentTarget.id;
switch ( property ) {
case 'first':
updateValidationErrors(language, $[id] as PaperInput, id);
break;
case 'second':
updateValidationErrors(language, $[id] as PaperInput, property);
break;
}
}
void updateValidationErrors( dynamic data, var element, String property ) {
//PaperInput element = elemen as PaperInput;
print( 'property| $property' );
print( 'element | $element' );
print( 'element runtime | ${element.runtimeType}' );
switch ( element.runtimeType ) {
case PaperInput:
var validationErrors = doGetValidationResult( data ).errors;
print( 'valErrors ${validationErrors[property]}' );
if ( validationErrors[property] != null ) {
element
..errorMessage = '${validationErrors[property].join( ' | ' )}'
..invalid = true;
} else {
element
..errorMessage = ''
..invalid = false;
break;
}
break;
case PaperItem:
//elem = element as PaperItem;
break;
}
}
#reflectable
void toggler( event, [_] ) {
toggleCollapse( collapse );
}
void ready( ) {
topic = this.dataset['topic'];
collapse = $['collapse'] as IronCollapse;
validationIcon = $['fe-icon'] as IronIcon;
}
}
void updateValidationErrors( dynamic data, var element, String property ) {
//PaperInput element = elemen as PaperInput;
print( 'property| $property' );
print( 'element | $element' );
print( 'element runtime | ${element.runtimeType}' );
switch ( element.runtimeType ) {
case PaperInput:
var validationErrors = doGetValidationResult( data ).errors;
print( 'valErrors ${validationErrors[property]}' );
if ( validationErrors[property] != null ) {
element
..errorMessage = '${validationErrors[property].join( ' | ' )}'
..invalid = true;
} else {
element
..errorMessage = ''
..invalid = false;
break;
}
break;
case PaperItem:
//elem = element as PaperItem;
break;
}
}
#validable
class Language extends JsProxy {
bool isValid = false;
#reflectable
#Length( min: 2, customDescription: 'At least 2 letters required' )
#Matches( r"^[a-zA-Z][a-zA-Z '-]*$", customDescription: 'First language is invalid' )
String first = '';
#reflectable
#Matches(
r"^\s*$|^[a-zA-Z][a-zA-Z '-]*$", customDescription: 'Second language is invalid' )
String second = '';
}
.html
<dom-module id = "language-form">
<style include = "reg-styles">
</style>
<template>
<div class = "layout vertical main-container">
<div class = "layout horizontal center-justified">
<required-icon></required-icon>
<paper-button
raised
active
toggles
on-tap = "toggler"
class = "bold">Language
<iron-icon icon = ""
id = "fe-icon"></iron-icon>
</paper-button>
</div>
<iron-collapse id = "collapse">
<div class = "layout vertical body">
<paper-input
required
auto-validate
label = "First *"
value = "{{language.first}}"
on-input="onInputHandler"
id = "first"></paper-input>
<paper-input
required
auto-validate
label = "Second"
pattern = "[[regex]]"
id = "second"
value = "{{language.second}}"
on-input="onInputHandler"></paper-input>
</div>
</iron-collapse>
</div>
</template>
</dom-module>
Validating First language displays the red error messages, but as soon as focus is achieved in Second language the error message from First language disappears.
Please see graphics below:
I would like the that the error message in First be displayed even when focus is lost.
Thanks
Remove the auto-validate property from each element.
i'm expanding angular.dart tutorial for some simple CRUD operations. I'm trying to do nice, smart combobox (dropdown box) that is taking List of KeyValuePairs and returning Selected KeyValyePair.
Unfortunatly i'm getting this error.
Model did not stabilize in 10 digests. Last 3 iterations:
itemsMap: collection: Instance of 'KeyValuePair'[null -> 0], Instance of 'KeyValuePair'[null -> 1]...
There is code.
dropdown_box.dart
library dropdown_box;
import 'package:angular/angular.dart';
import 'dart:core';
import 'package:tutorial/service/KeyValuePair.dart';
import 'dart:async';
#Component(selector: 'dropdownbox', templateUrl: 'dropdown_box.html', publishAs: 'dropCtrl')
class DropDownComponent {
// #NgOneWay('items-map')
// Map<String, String> itemsMap;
#NgOneWay('items-map')
List<KeyValuePair> itemsMap;
#NgTwoWay('selected-keyvalue')
KeyValuePair selectedKeyValue;
// void printit(item) {
// new Future(() {
// print("${item.value + ' '+ item.key}");
// SelectedKeyValue = new KeyValuePair(item.key, item.value);
// });
String selectedKey;
void setKeyAsSelected() {
new Future(() {
Iterable keyvaluepairs = itemsMap.where((i) => i.key == selectedKey);
if (keyvaluepairs.length > 0) {
selectedKeyValue = keyvaluepairs.elementAt(0);
}
});
}
}
dropdown_box.html
<div class=dropdownbox">
<select ng-model="selectedKey" ng-change="setKeyAsSelected()">
<option ng-value=""></option>
<option ng-repeat="item in itemsMap" ng-value="item.key">
{{item.value}}
</option>
</select>
</div>
use in recipe_book.html
<dropdownbox items-map="categorieKvList" selected-keyvalue="selectedKV"></dropdownbox>
recipe_book.dart (important part only)
KeyValuePair selectedKV;
List<KeyValuePair> _categorieKvList = new List<KeyValuePair>();
List<KeyValuePair> get categorieKvList {
_categorieKvList.clear();
categories.forEach((f)=>
_AddToList(f, f));
return _categorieKvList;
}
_AddToList(key, value)
{
KeyValuePair kvpair = new KeyValuePair.fromValues(key, value); //new KeyValuePair().AddValues(key, value);
_categorieKvList.add(kvpair);
}
keyvaluepair.dart
library KeyValuePair;
import 'package:angular/angular.dart';
#Injectable()
class KeyValuePair {
dynamic key;
dynamic value;
// AddValues(key, value) {
// this.key = key;
// this.value = value;
// }
KeyValuePair();
KeyValuePair.fromValues(dynamic this.key, dynamic this.value);
}
The getter of categorieKvList returns a changed _categorieKvList with new KeyValu pairs every time it is called. This is interpreted by angular as change.
Alter the logic to so _categorieKvList is modified only when its data source (categories) changes.
I'm trying to make a directive similar to ng-model to work with file inputs. I am new to AngularDart so don't really know how to go about doing this.
Here is some code I managed to write by looking at the ng-model source files but it (obviously) doesn't work.
#Decorator(
selector: 'input[type=file][file-model]',
map: const {'file-model': '&filesSelected'})
class FileModel {
final InputElement inputElement;
String expression;
final Scope scope;
String _inputType;
List<File> files;
var filesSelected;
FileModel(this.inputElement, this.scope, NodeAttrs attrs) {
expression = attrs["file-model"];
inputElement.onChange.listen((event) {
files = inputElement.files;
filesSelected(files);
});
}
}
Any suggestions?
I've got it working.
#Decorator(
selector: 'input[type=file][file-model]',
map: const {'file-model': '&filesSelected'})
class FileModel {
Element inputElement;
String expression;
final Scope scope;
String _inputType;
List<File> files;
var listeners = {};
FileModel(this.inputElement, this.scope) {
}
initListener(var stream, var handler) {
int key = stream.hashCode;
if (!listeners.containsKey(key)) {
listeners[key] = handler;
stream.listen((event) => handler({r"files": (inputElement as InputElement).files}));
}
}
set filesSelected(value) => initListener(inputElement.onChange, value);
}
Usage: <input type="file" multiple file-model="cmp.filesSelected(files)">
For getting the files upon the change event, you can also just pass them via $event.target.files. Example:
import 'package:angular2/core.dart';
#Component(
selector: 'my-app',
template: r'''
<h1>{{title}}</h1>
<input class="btn" type="file" (change)="onFileUpload($event.target.files)">
''')
class AppComponent {
String title = 'File transfer';
onFileUpload(List<File> files) {
if (files == null) return;
// read file content as dataURL
final File file = files[0];
final FileReader reader = new FileReader();
reader.onLoad.listen((_) {
print(reader.result);
});
reader.readAsDataUrl(file);
}
}