Angular 9 trim directive - directive

I have a directive that should trim a value in a input box. The directive trim the value in the form but when I submit it, the value is not trimed.
// this code works and the value is trimmed after submitting the form
<input type="text" matInput formControlName="adress" oninput="this.value = this.value.trim()">
// this code trim the value on the form but NOT after submitting the form
<input type="text" matInput formControlName="adress" trimvalue>
I am missing something in the directive ?
Directive:
import { Directive, ElementRef, HostListener, Renderer2 } from '#angular/core';
#Directive({
selector: '[trimvalue]'
})
export class TrimDirective {
constructor(
private renderer: Renderer2,
private elementRef: ElementRef
) { }
#HostListener('input') onInput(event) {
let value = this.elementRef.nativeElement.value;
if (value) {
value = value.trim();
this.elementRef.nativeElement.value = value.trim();
this.renderer.setValue(this.elementRef.nativeElement, this.elementRef.nativeElement.value);
}
}
}

angular source code - setValue()
you can see setValue is woking by node.textContent = value;
setValue(node: RText, value: string): void {
node.textContent = value;
}
and textContent is string|null
export interface RText extends RNode {
textContent: string|null;
}
So, I guess setValue() didn't work as predicted is cause by typescript's type go something wrong in background...
You can trim it by nodeValue.
this.elementRef.nativeElement.childNodes.forEach((x: ChildNode, i: number) => {
if (x.nodeName === '#text') {
this.elementRef.nativeElement.childNodes[i].nodeValue = this.elementRef.nativeElement.childNodes[i].nodeValue.trim();
}
});
}

Related

correctly set params in expo bottomtabs

Having a hard time understanding this newest expo bottom tabs
I dont see an initital params on the node_module for the bottomtabs or any params property... has anyone done this? essentially we have component for two bottom tabs and a different effect depending on that tab.
So 1. Can we pass Params into bottomTabs? 2. if so how?
error we get with TS is:
The expected type comes from property 'initialParams' which is declared here on type 'IntrinsicAttributes & RouteConfig<RootTabParamList, "TabThree", TabNavigationState, BottomTabNavigationOptions, BottomTabNavigationEventMap>'
<BottomTab.Screen
name="Episodes"
component={EpisodesScreen}
initialParams={{
type: "episodes",
}}
options={{
title: 'Episodes',
tabBarIcon: ({ color }) => <TabBarFeatherIcon name="headphones" color={color} />,
}}
/>
<BottomTab.Screen
name="TabThree"
component={EpisodesScreen}
initialParams={{
type: "quickGuides",
displayType: "grid",
}}
from the node_module::
import {
createNavigatorFactory,
DefaultNavigatorOptions,
ParamListBase,
TabActionHelpers,
TabNavigationState,
TabRouter,
TabRouterOptions,
useNavigationBuilder,
} from '#react-navigation/native';
import * as React from 'react';
import warnOnce from 'warn-once';
import type {
BottomTabNavigationConfig,
BottomTabNavigationEventMap,
BottomTabNavigationOptions,
} from '../types';
import BottomTabView from '../views/BottomTabView';
type Props = DefaultNavigatorOptions<
ParamListBase,
TabNavigationState<ParamListBase>,
BottomTabNavigationOptions,
BottomTabNavigationEventMap
> &
TabRouterOptions &
BottomTabNavigationConfig;
function BottomTabNavigator({
initialRouteName,
backBehavior,
children,
screenListeners,
screenOptions,
sceneContainerStyle,
...restWithDeprecated
}: Props) {
const {
// #ts-expect-error: lazy is deprecated
lazy,
// #ts-expect-error: tabBarOptions is deprecated
tabBarOptions,
...rest
} = restWithDeprecated;
let defaultScreenOptions: BottomTabNavigationOptions = {};
if (tabBarOptions) {
Object.assign(defaultScreenOptions, {
tabBarHideOnKeyboard: tabBarOptions.keyboardHidesTabBar,
tabBarActiveTintColor: tabBarOptions.activeTintColor,
tabBarInactiveTintColor: tabBarOptions.inactiveTintColor,
tabBarActiveBackgroundColor: tabBarOptions.activeBackgroundColor,
tabBarInactiveBackgroundColor: tabBarOptions.inactiveBackgroundColor,
tabBarAllowFontScaling: tabBarOptions.allowFontScaling,
tabBarShowLabel: tabBarOptions.showLabel,
tabBarLabelStyle: tabBarOptions.labelStyle,
tabBarIconStyle: tabBarOptions.iconStyle,
tabBarItemStyle: tabBarOptions.tabStyle,
tabBarLabelPosition:
tabBarOptions.labelPosition ??
(tabBarOptions.adaptive === false ? 'below-icon' : undefined),
tabBarStyle: [
{ display: tabBarOptions.tabBarVisible ? 'none' : 'flex' },
defaultScreenOptions.tabBarStyle,
],
});
(
Object.keys(defaultScreenOptions) as (keyof BottomTabNavigationOptions)[]
).forEach((key) => {
if (defaultScreenOptions[key] === undefined) {
// eslint-disable-next-line #typescript-eslint/no-dynamic-delete
delete defaultScreenOptions[key];
}
});
warnOnce(
tabBarOptions,
`Bottom Tab Navigator: 'tabBarOptions' is deprecated. Migrate the options to
'screenOptions' instead.\n\nPlace the following in 'screenOptions' in your code to keep
current behavior:\n\n${JSON.stringify(
defaultScreenOptions,
null,
2
)}\n\nSee https://reactnavigation.org/docs/bottom-tab-navigator#options for more
details.`
);
}
if (typeof lazy === 'boolean') {
defaultScreenOptions.lazy = lazy;
warnOnce(
true,
`Bottom Tab Navigator: 'lazy' in props is deprecated. Move it to 'screenOptions'
instead.\n\nSee https://reactnavigation.org/docs/bottom-tab-navigator/#lazy for more
details.`
);
}
const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder<
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
BottomTabNavigationOptions,
BottomTabNavigationEventMap
>(TabRouter, {
initialRouteName,
backBehavior,
children,
screenListeners,
screenOptions,
defaultScreenOptions,
});
return (
<NavigationContent>
<BottomTabView
{...rest}
state={state}
navigation={navigation}
descriptors={descriptors}
sceneContainerStyle={sceneContainerStyle}
/>
</NavigationContent>
);
}
export default createNavigatorFactory<
TabNavigationState<ParamListBase>,
BottomTabNavigationOptions,
BottomTabNavigationEventMap,
typeof BottomTabNavigator
>(BottomTabNavigator);
only way i found to get my componenet to render on two different routes from the bottom tabs is to use the useNavigationState
import { useNavigationState } from "#react-navigation/native"
made a constant to check the route name and then on use effect we check the case...
const screenName = useNavigationState((state) =>
state.routes[state.index].name)
const type = screenName
useEffect(() => {
switch (type) {
case "Episodes":
setTitle("Episodes")
setIsLoading(false)
break
case "quickGuides":
setTitle("Quick Guides")
setIsLoading(false)
break
}
}, [])

Need angular 2+ 'number only' directive that uses type as number, regex and allows copy paste

I have an input type number. I have a directive that successfully blocks characters other than numbers. And this also allows only single decimal point.
What I need is, the directive should allow copy from field and paste into the field as well.
<input type="number" appNumbersOnly />
import { Directive, ElementRef, HostListener } from "#angular/core";
#Directive({
selector: '[appNumbersOnly]'
})
export class NumbersOnlyDirective {
public text;
private regex: RegExp = new RegExp(/^-?[0-9]+(\.[0-9]*){0,1}$/g);
private specialKeys: Array<string> = ['Backspace', 'Tab'];
constructor(private el: ElementRef) {}
#HostListener('keypress', ['$event']) onKeyDown(event: KeyboardEvent) {
if (this.specialKeys.indexOf(event.key) !== -1) return;
const current: string = this.el.nativeElement.value;
const next: string = current.concat(event.key);
if(next.includes('.')){
if(this.text == next) event.preventDefault();
this.text= next;
}
if ((next && !String(next).match(this.regex))) {
event.preventDefault();
}
}
}
How to make this directive allow copy paste?
I am not sure about your requirenment. But I think this can help you.
copy
Angular 5 - Copy to clipboard
Paste
Angular - On Paste Event Get Content

Host container returns error: "Expression has changed after it was checked" when checking the property of #viewChildren in Angular2

I'm dealing with an error when I try to check the "valid" property of forms in child components via #viewchildren in the container HTML template. I realize this error is caused by timing, strangly the error occurs when I enable prod mode. The relevant documentation has a timer function, but I'm just not sure how that helps. I have looked at several articles here, but nothing has sunk in yet.
I set up the view children here:
#ViewChildren(AddressComponent) addressComponents: QueryList<AddressComponent>"
Here is ngAfterViewInit:
ngAfterViewInit() {
console.log("this is ", this)
this.addressComponents.toArray().forEach((address)=> {
if(address.addressType == "shipper"){
this.shipperForm = address.addressForm
}
else if(address.addressType == "recipient"){
this.recipForm = address.addressForm
}
})
}
And the relevant template:
..[disabled] = "!shipperForm.valid"...causes the error.
The relevant error returned is:
...caused by: Expression has changed after it was checked. Previous
value:...
The entire class:
import {Component, OnInit, Input, AfterViewInit, ViewChild, ViewChildren, QueryList} from '#angular/core';
import {AddressComponent} from '../address'
import {Shipment} from '../../containershipping/shared/shipment'
import any = jasmine.any;
#Component({
selector: 'app-wide-address',
templateUrl: './wide-address.component.html',
styleUrls: ['wide-address.component.scss']
})
export class WideAddressComponent implements OnInit,AfterViewInit {
addressForm: any;
public addressTypes: any = [
'shipper', 'recipient'
]
public shipperForm:any;
public recipForm:any;
#ViewChildren(AddressComponent) addressComponents: QueryList<AddressComponent>
private shippperComponent: AddressComponent;
#ViewChild(AddressComponent)
private recipComponent: AddressComponent;
#Input() addressType: string;
#Input() addresses: any;
#Input() shipment: Shipment;
#Input() useInternalButton:boolean;
public showButton:boolean = false;
constructor() {
}
ngOnInit() {
}
ngAfterViewInit() {
console.log("this is ", this)
console.log("view children is ", this.addressComponents)
//this.addressComponents.changes.subscribe(changes => console.log('changes are ', changes));
this.addressComponents.toArray().forEach((address)=> {
if(address.addressType == "shipper"){
this.shipperForm = address.addressForm
}
else if(address.addressType == "recipient"){
this.recipForm = address.addressForm
}
}
)
}
updateShipmentObject() {
this.addressComponents.toArray().forEach((address)=> {
this.syncFieldsWithObject(address);
}
);
this.shipment.showShippingSummary = true;
}
syncFieldsWithObject(component?: AddressComponent) {
if (component.addressType == "shipper") {
let shipAd = component.addressForm.controls;
this.shipment.shipper = {};
let shipAdObj = this.shipment.shipper
shipAdObj.name = shipAd['name'].value;
shipAdObj.address1 = shipAd['address1'].value;
shipAdObj.address2 = shipAd['address2'].value;
shipAdObj.city = shipAd['city'].value;
shipAdObj.state = shipAd['state'].value;
shipAdObj.country = shipAd['country'].value;
shipAdObj.zip = shipAd['zip'].value;
shipAdObj.phone = shipAd['phone'].value;
shipAdObj.email = shipAd['email'].value;
} else if (component.addressType == "recipient") {
let recipAd = component.addressForm.controls;
this.shipment.recipient = {};
let recipAdObj = this.shipment.recipient;
recipAdObj.name = recipAd['name'].value;
recipAdObj.address1 = recipAd['address1'].value;
recipAdObj.address2 = recipAd['address2'].value;
recipAdObj.city = recipAd['city'].value;
recipAdObj.state = recipAd['state'].value;
recipAdObj.country = recipAd['country'].value;
recipAdObj.zip = recipAd['zip'].value;
recipAdObj.phone = recipAd['phone'].value;
recipAdObj.email = recipAd['email'].value;
}
}
}
and the html template:
<div id="address_paymt_info">
<div class="table_replace table_page" >
<div class="row small-12 columns acsPageTitle">
Contact Information
</div>
<div class="sideByside">
<app-address #shipperComp [addressType]="'shipper'"></app-address>
<app-address #recipientComp [addressType]="'recipient'"></app-address>
</div>
</div>
<button (click)="updateShipmentObject()" class="button-primary" >Get Shipment Information (not enabled by default)</button>
<input type="button" value="Continue" class="button-primary" [disabled] = "!shipperForm.valid" > - this shouldn't be enabled by default
{{shipperForm}}
</div>

How to add a mask 'yyyy/yyyy' in angular2, using pipe and formatter to input field

I'd like to know how can I create a mask with regex expression for input field.
I need to add some masks to a field, such as: yyyy/yyyy. This sounds like a period.
I saw a link that creates a mask using the pipe from Angular2. Here is the link.
So, I'd like to create a pipe with a different regex expression to allow the user writes only this: yyyy/yyyy ; and using transform method from pipe.
Is this possible?
Here is my pipe and formatter:
import { Pipe, PipeTransform } from "#angular/core";
#Pipe({ name: "mypipe" })
export class MyPipe implements PipeTransform {
private SEPARATOR: string;
constructor() {
this.SEPARATOR = "/";
}
transform(value): string {
let integer = (value || "").toString();
// Here is where the code should be, to transform the value
return integer;
}
transform(value): string {
// parse method
}
}
import { Directive, HostListener, ElementRef, OnInit } from "#angular/core";
// import { MyPipe } from ...
#Directive({ selector: "[myFormatter]" })
export class MyFormatterDirective implements OnInit {
private el: HTMLInputElement;
constructor(
private elementRef: ElementRef,
private mypipe: MyPipe
) {
this.el = this.elementRef.nativeElement;
}
ngOnInit() {
this.el.value = this.mypipe.transform(this.el.value);
}
#HostListener("keydown", ["$event.target.value"])
handleKeyboardEvent(value) {
this.el.value = this.mypipe.transform(value);
}
}

CheckBoxList multiple selections: how to model bind back and get all selections?

This code:
Html.CheckBoxList(ViewData.TemplateInfo.HtmlFieldPrefix, myList)
Produces this mark-up:
<ul><li><input name="Header.h_dist_cd" type="checkbox" value="BD" />
<span>BD - Dist BD Name</span></li>
<li><input name="Header.h_dist_cd" type="checkbox" value="SS" />
<span>SS - Dist SS Name</span></li>
<li><input name="Header.h_dist_cd" type="checkbox" value="DS" />
<span>DS - Dist DS Name</span></li>
<li><input name="Header.h_dist_cd" type="checkbox" value="SW" />
<span>SW - Dist SW Name </span></li>
</ul>
You can check multiple selections. The return string parameter Header.h_dist_cd only contains the first value selected. What do I need to do to get the other checked values?
The post method parameter looks like this:
public ActionResult Edit(Header header)
I'm assuming that Html.CheckBoxList is your extension and that's markup that you generated.
Based on what you're showing, two things to check:
The model binder is going to look for an object named Header with string property h_dist_cd to bind to. Your action method looks like Header is the root view model and not a child object of your model.
I don't know how you are handling the case where the checkboxes are cleared. The normal trick is to render a hidden field with the same name.
Also a nit, but you want to use 'label for="..."' so they can click the text to check/uncheck and for accessibility.
I've found that using extensions for this problem is error prone. You might want to consider a child view model instead. It fits in better with the EditorFor template system of MVC2.
Here's an example from our system...
In the view model, embed a reusable child model...
[AtLeastOneRequired(ErrorMessage = "(required)")]
public MultiSelectModel Cofamilies { get; set; }
You can initialize it with a standard list of SelectListItem...
MyViewModel(...)
{
List<SelectListItem> initialSelections = ...from controller or domain layer...;
Cofamilies = new MultiSelectModel(initialSelections);
...
The MultiSelectModel child model. Note the setter override on Value...
public class MultiSelectModel : ICountable
{
public MultiSelectModel(IEnumerable<SelectListItem> items)
{
Items = new List<SelectListItem>(items);
_value = new List<string>(Items.Count);
}
public int Count { get { return Items.Count(x => x.Selected); } }
public List<SelectListItem> Items { get; private set; }
private void _Select()
{
for (int i = 0; i < Items.Count; i++)
Items[i].Selected = Value[i] != "false";
}
public List<SelectListItem> SelectedItems
{
get { return Items.Where(x => x.Selected).ToList(); }
}
private void _SetSelectedValues(IEnumerable<string> values)
{
foreach (var item in Items)
{
var tmp = item;
item.Selected = values.Any(x => x == tmp.Value);
}
}
public List<string> SelectedValues
{
get { return SelectedItems.Select(x => x.Value).ToList(); }
set { _SetSelectedValues(value); }
}
public List<string> Value
{
get { return _value; }
set { _value = value; _Select(); }
}
private List<string> _value;
}
Now you can place your editor template in Views/Shared/MultiSelectModel.ascx...
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<WebUI.Cofamilies.Models.Shared.MultiSelectModel>" %>
<div class="set">
<%=Html.LabelFor(model => model)%>
<ul>
<% for (int i = 0; i < Model.Items.Count; i++)
{
var item = Model.Items[i];
string name = ViewData.ModelMetadata.PropertyName + ".Value[" + i + "]";
string id = ViewData.ModelMetadata.PropertyName + "_Value[" + i + "]";
string selected = item.Selected ? "checked=\"checked\"" : "";
%>
<li>
<input type="checkbox" name="<%= name %>" id="<%= id %>" <%= selected %> value="true" />
<label for="<%= id %>"><%= item.Text %></label>
<input type="hidden" name="<%= name %>" value="false" />
</li>
<% } %>
</ul>
<%= Html.ValidationMessageFor(model => model) %>
Two advantages to this approach:
You don't have to treat the list of items separate from the selection value. You can put attributes on the single property (e.g., AtLeastOneRequired is a custom attribute in our system)
you separate model and view (editor template). We have a horizontal and a vertical layout of checkboxes for example. You could also render "multiple selection" as two listboxes with back and forth buttons, multi-select list box, etc.
I think what you need is how gather selected values from CheckBoxList that user selected and here is my solution for that:
1- Download Jquery.json.js and add it to your view as reference:
2- I've added a ".cssMyClass" to all checkboxlist items so I grab the values by their css class:
<script type="text/javascript" >
$(document).ready(function () {
$("#btnSubmit").click(sendValues);
});
function populateValues()
{
var data = new Array();
$('.myCssClas').each(function () {
if ($(this).attr('checked')) {
var x = $(this).attr("value");
data.push(x);
}
});
return data;
}
function sendValues() {
var data = populateValues();
$.ajax({
type: 'POST',
url: '#Url.Content("~/Home/Save")',
data: $.json.encode(data),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function () { alert("1"); }
});
}
</script>
3- As you can see I've added all selected values to an Array and I've passed it to "Save" action of "Home" controller by ajax 4- in Controller you can receive the values by adding an array as argument:
[HttpPost]
public ActionResult Save(int[] val)
{
I've searched too much but apparently this is the only solution. Please let me know if you find a better solution for it.
when you have multiple items with the same name you will get their values separated with coma