How do I create a custom list component, allowing child formatting to pass through with loop variable in AngularDart? - angular-dart

I want to create a custom list component (vertical-list) in angular dart so that I can utilise a list and pass through individual items, but also offer additional functionality such as removeItem and reorder list:
<vertical-list [for]="let item of myList" remove order>
<a [routerLink]="item.url()">{{item.title}}</a>
</vertical-list>
I'm not sure how to approach this. I started writing the code below, until I realised I was out of my depth.
#Component(
selector: 'vertical-list',
template: '''<table>
<caption *ngIf="caption != null">{{caption}}</caption>
<tr *ngFor="let item of list">
<td *ngIf="order"><button [class.hidden]="item == list.first" (click)="moveUp(item)">^</button></td>
<td><content></content></td>
<td *ngIf="remove"><button (click)="removeItem(item)">X</button></td>
</tr>
</table>''',
directives: [
coreDirectives
]
)
class VerticalList<T> {
#Input() String caption;
#Input('for') List<T> list;
#Input() bool order;
#Input() bool remove;
void moveUp(T item) {
final index = list.indexOf(item);
list.remove(item);
list.insert(index-1, item);
}
void removeItem(T item) => list.remove(item);
}
I appear to need some way to create an iterator and pass it through to the child, but have access in the parent.
Thanks.

Related

Using drop-down as filter with Blazorise.DataGrid

The Blazorise DataGrid supports textbox filters.
I would like to use a drop-down component to allow filtering by specific values. What do I need to do to make the grid react to the change of the Select value?
Code Example
#page "/ItemList"
#using DataAccessLibrary
#using Blazorise
#using Blazorise.DataGrid
#inject IItemList _items;
<div align="center">
<h3>Item List</h3>
</div>
#if (ItemListItems is null)
{
<p>Loading...</p>
}
else
{
<DataGrid Data="#ItemListItems" TItem="ItemListItem" PageSize="20" ShowPager="true" Filterable="true" Striped="true" Narrow="true" #bind-SelectedRow="#selectedItem">
<EmptyTemplate>
<div class="box">
No items were found!
</div>
</EmptyTemplate>
<DataGridColumns>
<DataGridCommandColumn TItem="ItemListItem" Caption="Action" EditCommandAllowed="true">
<EditCommandTemplate>
<Button Color="Color.Primary" Clicked="#context.Clicked">Edit</Button>
</EditCommandTemplate>
</DataGridCommandColumn>
<DataGridNumericColumn TItem="ItemListItem" Field="#nameof(ItemListItem.ItemID)" Caption="Item ID" Sortable="true" TextAlignment="TextAlignment.Right">
<DisplayTemplate>
#(context.ItemID)
</DisplayTemplate>
</DataGridNumericColumn>
<DataGridSelectColumn TItem="ItemListItem" Field="#nameof(ItemListItem.TypeShortDesc)" Caption="Item Type" Sortable="true">
// This filter should replace the default textbox with a dropdown listing only specific values
<FilterTemplate>
<Select TValue="string" #bind-SelectedValue="#ItemTypeFilter">
#foreach (string type in ItemTypeList)
{
<SelectItem Value="#type">#type</SelectItem>
}
</Select>
</FilterTemplate>
</DataGridSelectColumn>
<DataGridSelectColumn TItem="ItemListItem" Field="#nameof(ItemListItem.Description)" Caption="Item Description" Sortable="true" TextAlignment="TextAlignment.Left" />
<DataGridSelectColumn TItem="ItemListItem" Field="#nameof(ItemListItem.StatusShortDesc)" Caption="Status" Sortable="true">
<FilterTemplate>
// This filter should replace the default textbox with a dropdown listing only specific values
<Select TValue="string" #bind-SelectedValue="#ItemStatusFilter">
#foreach(string status in ItemStatusList)
{
<SelectItem Value="#status">#status</SelectItem>
}
</Select>
</FilterTemplate>
</DataGridSelectColumn>
<DataGridNumericColumn TItem="ItemListItem" Field="#nameof(ItemListItem.ItemPrice)" Caption="Amount" Sortable="false" TextAlignment="TextAlignment.Right" DisplayFormat="{0:C}" />
</DataGridColumns>
</DataGrid>
}
#code {
private List<ItemListItem> ItemListItems;
private ItemListItem selectedItem;
private List<string> ItemStatusList;
private string ItemStatusFilter;
private List<string> ItemTypeList;
private string ItemTypeFilter;
protected override async Task OnInitializedAsync()
{
ItemListItems = await _items.GetItems();
ItemTypeList = await _items.GetItemTypes();
ItemStatusList = await _items.GetItemStatuses();
}
}

How to access DbContext in viewmodel with AspNetCore 2.1?

Using DropDown input fields is very common when you want to display a description while saving an ID in your database.
If I consider my Person model, I have PersonViewModel which has a SelectList used to display a list of possible Job Descriptions
public SelectList SelectJobDescription
{
get
{
MyDbContext _context = new MyDbContext();
var result = new SelectList(_context.Keywords
.Where(k => k.Name == ".JobDescription")
.OrderBy(r => r.Valuename), "Valuecode", "Valuename");
_context.Dispose();
return result;
}
}
and I use this SelectList in my create/edit views like this:
<div class="form-group row">
<label asp-for="JobDescription" class="col-sm-4 col-form-label"></label>
<div class="col-sm-8">
<select asp-for="JobDescription" asp-items="#Model.SelectJobDescription" class="form-control">
<option>Select a Job Description</option>
</select>
</div>
Is it correct to use _context this way in the ViewModel or there is some other way to do the same thing better (maybe DI?)
Set the property after instatiating the view model (or during initialization), for example:
var vm = new YourViewModel()
{
SelectJobDescription = new SelectList( _context... ),
};
return View( vm );

How do I programmatically set focus to dynamically created FormControl in Angular2

I don't seem to be able to set focus on a input field in dynamically added FormGroup:
addNewRow(){
(<FormArray>this.modalForm.get('group1')).push(this.makeNewRow());
// here I would like to set a focus to the first input field
// say, it is named 'textField'
// but <FormControl> nor [<AbstractControl>][1] dont seem to provide
// either a method to set focus or to access the native element
// to act upon
}
How do I set focus to angular2 FormControl or AbstractControl?
I made this post back in December 2016, Angular has progressed significantly since then, so I'd make sure from other sources that this is still a legitimate way of doing things
You cannot set to a FormControl or AbstractControl, since they aren't DOM elements. What you'd need to do is have an element reference to them, somehow, and call .focus() on that. You can achieve this through ViewChildren (of which the API docs are non-existent currently, 2016-12-16).
In your component class:
import { ElementRef, ViewChildren } from '#angular/core';
// ...imports and such
class MyComponent {
// other variables
#ViewChildren('formRow') rows: ElementRef;
// ...other code
addNewRow() {
// other stuff for adding a row
this.rows.first().nativeElement.focus();
}
}
If you wanted to focus on the last child...this.rows.last().nativeElement.focus()
And in your template something like:
<div #formRow *ngFor="let row in rows">
<!-- form row stuff -->
</div>
EDIT:
I actually found a CodePen of someone doing what you're looking for https://codepen.io/souldreamer/pen/QydMNG
For Angular 5, combining all of the above answers as follows:
Component relevant code:
import { AfterViewInit, QueryList, ViewChildren, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
// .. other imports
export class MyComp implements AfterViewInit, OnDestroy {
#ViewChildren('input') rows: QueryList<any>;
private sub1:Subscription = new Subscription();
//other variables ..
// changes to rows only happen after this lifecycle event so you need
// to subscribe to the changes made in rows.
// This subscription is to avoid memory leaks
ngAfterViewInit() {
this.sub1 = this.rows.changes.subscribe(resp => {
if (this.rows.length > 1){
this.rows.last.nativeElement.focus();
}
});
}
//memory leak avoidance
ngOnDestroy(){
this.sub1.unsubscribe();
}
//add a new input to the page
addInput() {
const formArray = this.form.get('inputs') as FormArray;
formArray.push(
new FormGroup(
{input: new FormControl(null, [Validators.required])}
));
return true;
}
// need for dynamic adds of elements to re
//focus may not be needed by others
trackByFn(index:any, item:any){
return index;
}
The Template logic Looks like this:
<div formArrayName="inputs" class="col-md-6 col-12"
*ngFor="let inputCtrl of form.get('phones').controls;
let i=index; trackBy:trackByFn">
<div [formGroupName]="i">
<input #input type="text" class="phone"
(blur)="addRecord()"
formControlName="input" />
</div>
</div>
In my template I add a record on blur, but you can just as easily set up a button to dynamically add the next input field. The important part is that with this code, the new element gets the focus as desired.
Let me know what you think
This is the safe method recommend by angular
#Component({
selector: 'my-comp',
template: `
<input #myInput type="text" />
<div> Some other content </div>
`
})
export class MyComp implements AfterViewInit {
#ViewChild('myInput') input: ElementRef;
constructor(private renderer: Renderer) {}
ngAfterViewInit() {
this.renderer.invokeElementMethod(this.input.nativeElement,
'focus');
}
}
With angular 13, I did it this way:
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, Validators, FormControl, FormControlDirective, FormControlName } from '#angular/forms';
// This setting is required
const originFormControlNgOnChanges = FormControlDirective.prototype.ngOnChanges;
FormControlDirective.prototype.ngOnChanges = function ()
{
this.form.nativeElement = this.valueAccessor._elementRef.nativeElement;
return originFormControlNgOnChanges.apply(this, arguments);
};
const originFormControlNameNgOnChanges = FormControlName.prototype.ngOnChanges;
FormControlName.prototype.ngOnChanges = function ()
{
const result = originFormControlNameNgOnChanges.apply(this, arguments);
this.control.nativeElement = this.valueAccessor._elementRef.nativeElement;
return result;
};
#Component({
selector: 'app-prog-fields',
templateUrl: './prog-fields.component.html',
styleUrls: ['./prog-fields.component.scss']
})
export class ProgFieldsComponent implements OnInit
{
...
generateControls()
{
let ctrlsForm = {};
this.fields.forEach(elem =>
{
ctrlsForm[elem.key] = new FormControl(this.getDefaultValue(elem), this.getValidators(elem));
});
this.formGroup = new FormGroup(ctrlsForm);
}
...
validateAndFocus()
{
if (formGroup.Invalid)
{
let stopLoop = false;
Object.keys(formGroup.controls).map(KEY =>
{
if (!stopLoop && formGroup.controls[KEY].invalid)
{
(<any>formGroup.get(KEY)).nativeElement.focus();
stopLoop = true;
}
});
alert("Warn", "Form invalid");
return;
}
}
}
Reference:
https://stackblitz.com/edit/focus-using-formcontrolname-as-selector?file=src%2Fapp%2Fapp.component.ts
Per the #Swiggels comment above, his solution for an element of id "input", using his solution after callback:
this.renderer.selectRootElement('#input').focus();
worked perfectly in Angular 12 for an element statically defined in the HTML (which is admittedly different somewhat from the OP's question).
TS:
#ViewChild('licenseIdCode') licenseIdCodeElement: ElementRef;
// do something and in callback
...
this.notifyService.info("License Updated.", "Done.");
this.renderer.selectRootElement('#licenseIdCode').focus();
HTML:
<input class="col-3" id="licenseIdCode" type="text" formControlName="licenseIdCode"
autocomplete="off" size="40" />
If you are using Angular Material and your <input> is a matInput, you can avoid using .nativeElement and ngAfterViewInit() as follows:
Component Class
import { ChangeDetectorRef, QueryList, ViewChildren } from '#angular/core';
import { MatInput } from '#angular/material/input';
// more imports...
class MyComponent {
// other variables
#ViewChildren('theInput') theInputs: QueryList<MatInput>;
constructor(
private cdRef: ChangeDetectorRef,
) { }
// ...other code
addInputToFormArray() {
// Code for pushing an input to a FormArray
// Force Angular to update the DOM before proceeding.
this.cdRef.detectChanges();
// Use the matInput's focus() method
this.theInputs.last.focus();
}
}
Component Template
<ng-container *ngFor="iterateThroughYourFormArrayHere">
<input #theInput="matInput" type="text" matInput>
</ng-container>

Play framework 2.3.2: How to render List or Map in scala template

I am trying to display a list of strings as a repeatable input text control in my view. Here is my model:
public class User {
#Required
public String email;
public String password;
public List<String> products;
}
Controller:
public static Result index() {
Form<User> userForm = Form.form(User.class);
Map<String,String> anyData = new HashMap<String,String>();
List<String> listProduct = new ArrayList<String>();
listProduct.add("p1");
listProduct.add("p2");
userForm = userForm.fill(new User("bob#gmail.com", "secret", listProduct));
return ok(views.html.index.render(userForm));
}
View:
#(userForm: Form[models.User])
#import helper._
#import models.User
#import scala._
#main("Welcome to Play") {
<form id="formUser" action="/user/apply" method="post">
#inputText(userForm("email"))
#inputText(userForm("password"))
#for(product <- userForm("products")) {
<input type="text" name="#product" value="#product">
}
<input type="submit" value="submit"/>
</form>
}
Error is:
value map is not a member of play.data.Form.Field
I also tried form helper #repeat. But its just not working.
#repeat(userForm("products"), min = 0) {
product => #inputText(product)
}
Error:
not found: value product
I am using Play 2.3.2 in Java.
Any idea whats going wrong?
Suraj
You just need to remember that view templates are parsed to Scala functions and code is escaped with # character. Your second solution works fine. In this case you just need to format your code in proper way and it works like a charm.
#repeat(userForm("products"), min = 0) { product =>
#inputText(product)
}

Reusing wicket component in a form

I have built a wicket component that contains input/labels and methods to change presentation (required, enabled, etc.). The components render fine, but what happens is when the form submits I see only 1 form parameter 'input', and it's the last InputRow component.
InputRow.html
<html xmlns:wicket="http://wicket.apache.org">
<head>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<wicket:panel>
<label wicket:id="label">abc: <span class="req">*</span></label>
<span class="input">
<input wicket:id="input" type="text" id="name"></input>
</span>
<span wicket:id="input_feedback"></span>
</wicket:panel>
</body>
</html>
InputRow.java
package com.wicket;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
public class InputRow extends Panel{
#SuppressWarnings("unused")
private String id;
public InputRow(String id, String label) {
super(id);
this.id = id;
Label memberIdLabel = new Label("label",label);
memberIdLabel.setEscapeModelStrings(false)
.add(new AttributeAppender("for", new Model<String>(id),""));
add(memberIdLabel);
TextField<String> name = new TextField<String>("input");
name.setType(String.class)
.setMarkupId(id)
.setOutputMarkupId(true);
add(name);
add(new Label("input_feedback",""));
}
public InputRow disable()
{
get("input")
.setEnabled(false)
.add(new AttributeAppender("class", new Model<String>("disabled"),""));
get("label")
.add(new AttributeAppender("class", new Model<String>("disabled"),""));
return this;
}
public InputRow required()
{
Model model = (Model)get("label").getInnermostModel();
StringBuffer label = new StringBuffer((String)model.getObject());
label.append(" <span class=\"req\">*</span>");
model.setObject(label);
((TextField)get("input")).setRequired(true);
return this;
}
#Override
protected void onBeforeRender() {
super.onBeforeRender();
Label feedback = (Label)get("input_feedback");
if (get("input").getFeedbackMessage() != null)
{
feedback.setDefaultModel(new Model<String>("Required"));
}
}
}
Adding to the form component
add(new InputRow("name","Name:").required());
edit
I didn't set up a ListView or repeater since I know what rows / fields I want to add to the form at build time.
Your InputFields are missing their models. This way, wicket doesn't know where to store the formdata. If you add models to the fields they will be populated automatically.
There's not just one form parameter submitted. The submits are of the named like name:input, name2:input, ...
But as Nicktar suggests in the comment you should use a model to bind the value of the form component to your entity object. You have to accept an IModel in the constructor and use it in the constructor of TextField.
A better approach to what you are trying to do is to write a Behavior which adds decorating markup for your FormComponent. That way it works for more than just simple text input fields and you can fully customize the instances of your FormComponents.
It could look like this:
public class FormComponentBehavior extends Behavior {
#Override
public void bind(Component component) {
if (!(component instanceof FormComponent)) {
throw new IllegalArgumentException();
}
}
#Override
public void beforeRender(Component component) {
FormComponent<?> fc = (FormComponent<?>) component;
Response r = component.getResponse();
r.write("<label" + (fc.isRequired() ? " class='required'" : "") + ">");
r.write(fc.getLabel().getObject());
r.write("</label>");
r.write("<span class='input'>");
}
#Override
public void afterRender(Component component) {
component.getResponse().write("</span>");
// if feedback errors write them to markup...
}
}
Then you have to add this behavior to your FormComponent instances.
Maybe the problem with your form is that your input text fields have all the same id. Try using attribute 'name' instead of 'id'