Is is possible to define a custom mapping deriving from NgAttr in AngularDart? - angularjs-directive

I'd like to define a new mapping annotation. For instance, wouldn't it be nice if #NgAttr added the mustache (the braces {{ and }}) automatically if the attribute looks like a controller value? So that it doesn't matter whether I write
<pui-input pattern="{{ctrl.pattern}}" ng-model="ctrl.value">
or
<pui-input pattern="ctrl.pattern" ng-model="ctrl.value">
Is it possible to add a custom mapping to AngularDart? My first approach was to simply derive from NgAttr, but it doesn't work because annotations must not contain methods in Dart:
/**
* When applied as an annotation on a directive field specifies that
* the field is to be mapped to DOM attribute with the provided [attrName].
* The value of the attribute to be treated as a string, equivalent
* to `#` specification.
* If the value of the attribute looks like a property of a controller, it
* is surrounded by a mustache ({{ }}) if it is missing.
*/
class PuiAttr extends AttrFieldAnnotation {
final mappingSpec = '#';
const PuiAttr(String attrName) : super(attrName);
String get attrName => addMustache(super.attrName);
String addMustache(String attrName)
{
if (attrName.indexOf("{{") == 0) // Todo: find a nice regexp
if (attrName.indexOf("\.")>0)
return "{{$attrName}}";
return attrName;
}
}
Please note the mustaches are only an example. My question is whether it's possible to add custom mappings to the standard set #NgAttr, #NgOneWay, #NgTwoWay, #NgOneWayOneTime and #NgCallback.
Any ideas or suggestions?

Related

How to render nested routes with multiple optional params and path placeholders?

I've been trying to use react-router to define a series of nested components/routes w/ optional params, but also separated by path placeholders.
i.e.
/list
/list/1
/list/items
/list/1/items
/list/1/items/1
I would assume the two <Route> paths would be something like:
/list/:listId?
and
`${match.url}`/items/:itemId?`
But alas.. "items" always ends up being accepted as the listId param, and therefore, the sub-routing for /items never matches.
I have a generic example I've coded here (doesn't accomplish a solution): https://stackblitz.com/edit/nesting-w-optional-params-react-router
I see examples all over the internet for /root/:id1?/:id2? but nothing for what I'm trying to do where I have a placeholder between the params: /root/:id1/placeholder/:id2.
Is this possible to do w/ react-router v4+ ??
Figured out a solution utilizing RR's children pattern, to always render the nested route/component, but then inside the component use path-to-regexp directly to do route matching and to grab relevant route params from props.location.pathname.
Relevant bit of code:
render() {
const pathRe = new PathToRegexp('/foo/:fooId?/bar/:barId?')
const match = pathRe.match(this.props.location.pathname)
if (!match) {
return null
}
const barId = (this.props.match && this.props.match.params.barId) || match.barId
return <h1>Bar <em>{barId}</em></h1>
}
https://stackblitz.com/edit/nesting-w-optional-params-react-router-solution

Access filter result Angular 6

How can I access filteredArray in my .ts component? Because right now it is accessible only inside ng-container.
<ng-container *ngIf="(userList | filter: 'name' : value) as filteredArray">
<tr *ngFor="let user of filteredArray">
<td>{{user.name}}</td>
<td>{{user.group}}</td>
</tr>
<div>Count: {{ filteredArray.length }}</div>
</ng-container>
How can I modify the code in order to obtain what I want? Thank you for your time!
To answer your question directly: it's not possible the way you describe it. But read on.
Pipes (sometimes still called "filters") should be used only to format data, i.e. prepare it in a human-readable form. For example, the build-in date pipe can be used to transform an ISO string to a string such as "March 21st, 1995", which is how a human from the USA might expect to read the date.
The way you're using pipes is not recommended, precisely because of the question you have. You've essentially put application logic inside a template, which is an anti-pattern and beats the purpose of having easy-to-read declarative templates, which Angular uses in order to figure out how to update DOM.
You should move the filtering logic back to the class. For example, instead of setting this.userList = xxx, you could have a function which you call every time, such as this.changeUserList(xxx).
changeUserList (list) {
this.userList = list
this.filteredArray = list.filter(...)
}
You can put this logic in a setter as well, which allows you to run custom code when you write the usual this.userList = list, but you'll need a separate (usually prefixed private) property on the class where you'd actually store the value. It's not really a limitation since you can also have a trivial getter, so you can still us this.userList normally as a getter without having to remember to use this._userList, essentially tucking this away as the get/set pair's implementation detail.
private _userList
public set userList (list) {
this._userList = list
this.filteredArray = list.filter(...)
}
public get userList (list) { return this._userList }
Observables could really come in handy here as well, since you could just rx.map the userList$ to filteredArray$ with an Array#filter.
public userList$
public filteredArray$ = this.userList$.pipe(map(arr => arr.filter(...))
Then in the template, you can use the async pipe.
*ngIf="filteredArray$ | async as filteredArray"
Avoid doing the following.... but it works for demo purposes 😃
Create a component (e.g. demo-element.component.ts) that takes a single #Input() value:any
Add this new component as the first child of the <ng-container>, and give it a template reference #containerRef e.g.:
<ng-container *ngIf="(userList | filter: 'name' : value) as filteredArray">
<demo-element #containerRef [value]="filteredArray"></demo-element>
In your main component, add
#ViewChild('containerRef') ref;
ngAfterViewInit() {
this.filteredArray = this.ref.value; // Terrible pattern, but answers the question:-)
}
I hope this below code will help you.
<div class="rsrvtn_blk" *ngIf="(items | fSearch:firstname) as filteredItems">
<div class="col-md-3 pl-0" *ngFor="let item of filteredItems">
// you can display the filtered content here
</div>
</div>

angular 2+ component with attribute name and no parameters

I want to allow a user to provide a list of one-word attributes without parameter values. For example,
<container row crosscenter wrap spacearound ...>
which results in something like this in container.html
<div [ngClass]="{ 'flexDisplay': true, 'directionRow': isRow, 'directionCol': isCol, 'contentSpaceAround': isSpaceAround}" ...>
What I'm missing is how to set
#Input('row') isRow = false;
to true if 'row' was present in the container line.
Any ideas?
Thanks in advance.
Yogi
This can be handled in ngOnChanges. The value can be assigned either back to input property or to some object that will be passed to ngClass
ngOnChanges(changes: SimpleChanges) {
if ('foo' in changes) {
this.options.foo = true;
}
}
Since there's no way how inputs can become unassigned, there's no reason to provide bindings for them. #Attribute can be used instead:
constructor(#Attribute('foo') public foo: boolean|null) {
this.foo = (foo != null);
}
Using attributes for regular options isn't a good decision, design-wise. This prevents from setting them dynamically. Instead, it is always preferable to accept options input. If all options are supposed to be flags, it can be a string input that will be split and processed in ngOnChanges, like:
<container options="row crosscenter wrap spacearound">
or
<container [options]="'row crosscenter wrap spacearound'">
I think the answer to my question is to create directives for each of the "one-word" tags (attributes) I want to use.
:-)

Dojo validation that depends on another widget value

I have a widget with validation params such as "min" and "max". I want "min" to be set dynamically, because it depends on value contained in another widget.
<input id="test" type="text"
data-dojo-type="dijit/form/NumberTextBox"
name= "elevation"
required="true"
value="3000"
data-dojo-props="constraints:{min:-20000,max:20000,places:0},
invalidMessage:'Invalid elevation.'" />
How can I do something like min: testWidget.getValue()
Thanks.
All you need to do is use the _WidgetBase#set method. Here is the description from Dojo's API documentation:
Set a property on a widget
Sets named properties on a widget which may potentially be handled by a setter in the widget.
For example, if the widget has properties "foo" and "bar" and a method named _setFooAttr(), calling myWidget.set("foo", "Howdy!") would be equivalent to calling widget._setFooAttr("Howdy!") and myWidget.set("bar", 3) would be equivalent to the statement widget.bar = 3;
set() may also be called with a hash of name/value pairs, ex:
So with your widget reference you can simply do:
var elevationInput = dijit.byId("test");
var constraints = {
min: testWidget.getValue(); // or testWidget.get("value")
max: elevationInput.constraints.max
};
elevationInput.set("constraints", constraints);

AS3 regular expressions make html tags uppercase

I would like to transform a string containing HTML tags such that all tags and properties will be in upper case (using regular expressions preferably).
For example, the following string:
'<bla>something <br/>bar</bla>'
should be changed to
'<BLA>something <BR/>bar</BLA>'
Is this possible using a single string replace with a regular expression? If so, how?
Changing just the HTML element tag names to uppercase is pretty easy. Use the callback function version of the String replace method like so:
function HtmlTagName2Upper(text) {
return text.replace(/(<\/?\w+\b)([^<>]*>)/g,
function(m0, m1, m2) {
return m1.toUpperCase() + m2;});
}
The above regex captures the tag name in $1 and all the attributes (if any) in $2. The above regex will work pretty well but does not handle attribute values having angle brackets and does not uppercase the attribute names.
Handling HTML elements having angle brackets in their attribute values and making the element attribute names uppercase is a bit more involved:
function HtmlTagAndAttributeNames2Upper(text) {
return text.replace(/(<\/?\w+\b)((?:[^<>'"]+|"[^"]*"|'[^']*')*>)/g,
function(mo0, mo1, mo2) {
mo2 = mo2.replace(/(\s+[\w\-.:]+)((?:\s*=\s*(?:"[^"]*"|'[^']*'|[\w\-.:]+))?)/g,
function(mi0, mi1, mi2) {
return mi1.toUpperCase() + mi2});
return mo1.toUpperCase() + mo2;});
}
Honestly I'm not an expert of AS3. However a work-around could be the following:
Find all the html tags through a regexp like this: <[^>]+>
Substitute all the html tags found with their upper case version (by using the toUpperCase() method).