AngularDart: file input model directive - dart

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);
}
}

Related

How to make dynamic tree view every node independently

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

Error uploading file in Dart to Redstone Server

I am trying to upload a file in Dart with this code
Reading the file
dom.InputElement uploadInput = dom.querySelector('#upload');
uploadInput.onChange.listen((dom.Event e)
{
// read file content as dataURL
final files = uploadInput.files;
if (files.length == 1)
{
final file = files[0];
final reader = new dom.FileReader();
reader.onLoad.listen((_)
{
dataRequest('upload', reader.result);
});
reader.readAsDataUrl (file);
}
});
Sending the file
Future<dom.HttpRequest> dataRequest (String path, dynamic data)
{
return dom.HttpRequest.request (path, method: "POST",
sendData: data);
}
But I get this error
POST http://localhost:9090/upload 400 (Bad Request) :9090/upload:1
Instance of '_XMLHttpRequestProgressEvent'
STACKTRACE:
null
I receive it in Redstone like this
#app.Route("/upload", methods: const [app.POST], allowMultipartRequest: true)
#Encode()
upload(#app.Body(app.FORM) Map form)
{
var file = form["file"];
print(file.filename);
print(file.contentType);
print(file.runtimeType);
return new Resp()
..success = (file.filename != null);
}
Any ideas?
Dart: 1.9.1
Redstone: 0.5.21
Let's say you have the following html:
<!DOCTYPE html>
<html>
<head>
<title>send_file.html</title>
</head>
<body>
<form id="read">
user: <input type="text" name='user' value='DefaultValue'>
<input type="file" id="file" name="my_file"/> <br>
<input type="button" id="whole_btn" value="Send whole form!">
<input type="button" id="specific_btn" value="Send specific values!">
</form>
<script type="application/dart" src="send_file.dart"></script>
</body>
</html>
Redstone server file:
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:redstone/server.dart' as app;
import 'package:shelf_static/shelf_static.dart';
#app.ErrorHandler(HttpStatus.NOT_FOUND)
handleNotFoundError() => app.redirect("not_found.html");
#app.Route('/post',methods: const [app.POST], allowMultipartRequest: true)
wholeFormPost(#app.Body(app.FORM) Map form) {
var user = form['user'];
var f = form['my_file'];
print('user: $user \n file: \n ${f.content}');
}
#app.Route('/post1',methods: const [app.POST], allowMultipartRequest: true)
specificPost(#app.Body(app.FORM) Map form) {
var specificField = form['specificField'];
var f = form['my_file'];
print('specificField: $specificField \n file: \n ${f.content}');
}
#app.Interceptor(r'/.*')
interceptor1() {
if (app.request.method == 'OPTIONS') {
app.response = app.response.change(headers: CORS);
app.chain.interrupt();
} else {
app.chain.next(() {
return app.response = app.response.change(headers: CORS );
});
}
}
Map CORS = {
"Access-Control-Allow-Origin" : "*, ",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Content-Disposition"
};
main() {
app.setShelfHandler(createStaticHandler("/home/raz2/dartProjects_linux_1/PR5/cl2/lib",
defaultDocument: 'send_file.html',
serveFilesOutsidePath: true));
app.setupConsoleLog(Level.ALL);
app.start(address: "0.0.0.0", port: 8081);
}
The client dart file: send_file.dart
import 'dart:html';
class UploadFileEx {
FormElement _readForm;
InputElement _fileInput;
File file;
ButtonElement _wholeBtn;
ButtonElement _specificBtn;
UploadFileEx() {
_readForm = document.querySelector('#read');
_fileInput =
document.querySelector('#file')
..onChange.listen(_onFileInputChange);
_wholeBtn =
document.querySelector('#whole_btn')
..onClick.listen((sendForm));
_specificBtn =
document.querySelector('#specific_btn')
..onClick.listen((sendFileAndField));
}
_onFileInputChange(_) {
file = _fileInput.files[0];
}
// Send the whole form
void sendForm(_) {
if(file == null)
return;
FormData fd = new FormData(_readForm);
HttpRequest req = new HttpRequest();
req.open("POST", 'http://127.0.0.1:8081/post');
req.send(fd);
}
// add my own field to FormData
void sendFileAndField(_) {
if(file == null)
return;
FormData fd = new FormData();
fd.append('specificField', 'Lalala');
fd.appendBlob('my_file', file);
HttpRequest req = new HttpRequest();
req.open("POST",'http://127.0.0.1:8081/post1');
req.send(fd);
}
}
void main() {
new UploadFileEx();
}
Should work.
Check out this link for more info: Sending_forms_through_JavaScript

Dart - Model did not stabilize - Component

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.

Component Attribute is none - timing issue

I want to do a HTTPRequest based upon a url, which is set in a component attribute. I tried it like shown below, but the dataUrl is always none. It seems as the constructor of the component is executed before the attributes, which are set in the html, are available to the component.
How can I tell the HTTPRequest to wait until the dataUrl variable is available?
component.dart
class TableData {
static List data = [];
TableData() {}
//GETTERS
List get getData => data;
}
#NgComponent(
selector: 'jstable',
templateUrl: 'jstable/jstable_component.html',
cssUrl: 'jstable/jstable_component.css',
publishAs: 'cmp'
)
class JSTableComponent {
#NgAttr('name')
String name;
#NgAttr('data-url')
String dataUrl;
TableData _table_data = new TableData();
final Http _http;
bool dataLoaded = false;
JSTableComponent(this._http) {
_loadData().then((_) {
dataLoaded = true;
}, onError: (_) {
dataLoaded = false;
});
}
//GETTERS
List get data => _table_data.getData;
//HTTP
Future _loadData() {
print("data url is");
print(dataUrl);
return _http.get(dataUrl).then((HttpResponse response) {
TableData.data = response.data['data'];
});
}
}
.html
<jstable name="myjstablename" data-url="table-data.json"></jstable>
Implement NgAttachAware and put your code in the attach method. The attributes are already evaluated when attach is called.

Why is my TypeScript declared class throwing a Not Declared exception?

I have the following TypeScript module, unnecessary fluff removed for brevity:
/// <reference path="jquery.d.ts" />
module SelectionOtherInputs {
export class SelectionOtherInputDescriptor {
constructor(public selectionId: string, public otherKey: any, public otherInputElementId: string) { }
}
export class SelectionOtherInputHelper {
selectionsWithOther: { [selectionKey: string]: SelectionOtherInputDescriptor; } = {};
getAllSelectionOthers() {
$("[" + ATT_SELECTION_OTHER_FOR + "]").each(function () {
var key = $(this).attr(ATT_DATA_SELECTION_OTHER_KEY);
var desc = new SelectionOtherInputDescriptor("0", key, $(this).attr("id"));
this.selectionsWithOther[key] = desc;
});
}
}
}
Then I try and use the SelectionOtherInputHelper class in a small demo page as follows:
#section scripts
{
<script src="~/Scripts/TypeScript/OtherInput.js"></script>
<script>
var otherManager = new SelectionOtherInputHelper();
otherManager.getAllSelectionOthers();
</script>
}
The `scripts' section is being rendered, and jQuery, but I still get a
Uncaught ReferenceError: SelectionOtherInputHelper is not defined
error on the var otherManager = new SelectionOtherInputHelper() call. What else must I do to properly import and use this class?
I think you forgot the module:
#section scripts
{
<script src="~/Scripts/TypeScript/OtherInput.js"></script>
<script>
var otherManager = new SelectionOtherInputs.SelectionOtherInputHelper();
otherManager.getAllSelectionOthers();
</script>
}

Resources