It is assumed that you have already used the Angular CLI to install and instantiate an Angular application. Refer to the Angular documentation for help setting up an Angular project.
npm install --save @my-ul/ng-porcelain@14.0.2 \
@fortawesome/angular-fontawesome@* \
@fortawesome/fontawesome-svg-core@* \
@fortawesome/free-solid-svg-icons@* \
@w11k/angular-sticky-things@* \
angular-mydatepicker@* \
lodash-es@* \
luxon@* \
sprintf-js@*
import {
ApplicatorModule,
DateRefinerModule,
FooterModule,
InterpolateModule,
RefinersModule,
SearchInputModule
SimpleRefinerModule,
SpinnerModule,
TruncateModule
} from '@my-ul/ng-porcelain';
// YourModule.ts
@Module({
imports: [
ApplicatorModule,
DateRefinerModule,
FooterModule,
InterpolateModule,
RefinersModule,
SearchInputModule,
SimpleRefinerModule,
SpinnerModule,
TruncateModule
]
})
export class AppModule {}
The checkbox provides an accessible, labeled and styled checkbox functionality that can be used almost exactly like a standard HTML checkbox. The checkbox does not support indeterminate states.
Boolean representing whether or not the checkbox is in the checked state. Can be split into [checked]
and (checkedChange)
.
Boolean that can be used to disable the checkbox. When disabled, the checkbox will not respond to the user's attempts to change the value. The value can still be changed programatically. Can be split into [disabled]
and (disabledChange)
.
Allow a user to search and select pre-populated values.
export class MyComponent {
fruits: ['Apple', 'Banana', 'Cherry', 'Durian'];
currentFruit: 'Apple';
}
<porcelain-combobox [items]="fruits" [(value)]="currentFruit"></porcelain-combobox>
Use an array of objects as items. Selecting a value returns the entire object, not just the label.
export class MyComponent {
fruits: [
{
color: 'red';
name: 'Apple';
},
{
color: 'yellow';
name: 'Banana';
},
{
color: 'darkred';
name: 'Cherry';
},
{
color: 'lightyellow';
name: 'Durian';
}
];
currentFruit: 'Apple';
}
<porcelain-combobox
[items]="fruits"
[isObjectArray]="true"
[labelProp]="name"
[(value)]="currentFruit"
></porcelain-combobox>
The Expando can be used to create content areas that can be opened or closed with a disclosure triangle.
The Expando's expand/collapse behavior supports animations. These actions will be animated if you import BrowserAnimationsModule
in the root of your app.
Used to change the expando's icon position relative to the expando title.
The title to be shown next to the icon.
You can use non-bracketed syntax to define the title. If you do this, you must use string interpolation to use variables.
The following bindings are equivalent:
<p-expando [title]="myVariable"></p-expando> <p-expando title="{{myVariable}}"></p-expando>
Accepts string values "before" or "after" as defined by ExpandoIconPosition
Accepts any Font Awesome icon definition, from the @fortawesome/free-*-svg-icons
packages. By default, this is faCaretDown
.
Boolean used to control the current state of the expando. Set true to reveal included content, otherwise, false to hide.
<p-expando
title="My Expando Title"
[iconPosition]="ExpandoIconPosition"
[icon]="faIconDefinition"
[isOpen]="trueOrFalse"
>
<p>Your content goes here.</p>
</p-expando>
<!-- or -->
<porcelain-expando
title="My Expando Title"
[iconPosition]="ExpandoIconPosition"
[icon]="faIconDefinition"
[isOpen]="trueOrFalse"
>
<p>Your content goes here.</p>
</porcelain-expando>
If the [title]
input is omitted or set to the empty string ''
, the Expando will use Expando Body and Expando Header components to populate these regions.
<p-expando>
<p-expando-header>
<h2>My Header Area</h2>
</p-expando-header>
<p-expando-body>
<p>My content area</p>
</p-expando-body>
</p-expando>
<!-- or -->
<porcelain-expando>
<porcelain-expando-header>
<h2>My Header Area</h2>
</porcelain-expando-header>
<porcelain-expando-body>
<p>My content area</p>
</porcelain-expando-body>
</porcelain-expando>
Shows the UL footer.
<porcelain-footer></porcelain-footer>
You can customize the footer links by passing an array of [url, label] entries to the links
input.
<porcelain-footer
[links]="[
['https://example.com/link1', 'Link 1'],
['https://example.com/link2', 'Link 2'],
['https://example.com/link3', 'Link 3']
]"
></porcelain-footer>
You can also provide the links as content children. This is useful when you want to use Angular components as links. Ensure the porcelain-link
directive is used as a Structural Directive (include the asterisk, i.e. *porcelain-link
).
<porcelain-footer>
<a *porcelain-link href="https://example.com/link1">Link 1</a>
<a *porcelain-link href="https://example.com/link2">Link 2</a>
<a *porcelain-link href="https://example.com/link3">Link 3</a>
</porcelain-footer>
Since 1.4.0
Deprecated Use the new p-search-input component that supports two-way binding.
The Search Sort Component provides keyword search function as needed.
Install Porcelain 1.4 and its dependencies
npm install --save @my-ul/ng-porcelain@^1.4.0
Import the LegacySearchInputModule
import { LegacySearchInputModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [YourComponent],
imports: [CommonModule, LegacySearchInputModule],
exports: []
})
class YourModule {}
Write a function to handle new values from the component...
@Component()
class YourComponent {
handleNewValue(newValue: string): void {
this.value = newValue;
}
}
Place the component in your template, with reference to the handler...
<porcelain-search-input (submitHandler)="handleNewValue($event)"></porcelain-search-input>
Change the Placeholder Text to change the displayed text. Useful for i18n/translation.
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[placeholderLabel]="'Volume'"
></porcelain-search-input>
Just use uservalue to assign value to it in the HTML
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[userValue]="searchTerm"
></porcelain-search-input>
For Search box border Toggle set borders for
1.)true for enabling border 2.)false for disabling border
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[borders]="false"
></porcelain-search-input>
For getting just empty value when search cancel is clicked, use emptyhandler
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[borders]="false"
[emptyHandler]="clearSearchClick($event)"
></porcelain-search-input>
Alternative Font Awesome icons can be used instead of the defaults for 'Clear' and 'Submit'. See Font Awesome for Angular docs for more information.
<porcelain-search-input
(submitHandler)="..."
[submitIcon]="mySubmitIcon"
[clearIcon]="myClearIcon"
></porcelain-search-input>
The Simple Refiner component provides an interface for the user to pick many options to begin refining a search.
The behavior of the Simple Refiner is best defined by the SimpleRefinerDefinition
class in lib/shared
.
<porcelain-simple-refiner
[refiner]="mySimpleRefiner"
(onRefinerChange)="myRefinerUpdateCallbackFunction($event)"
></porcelain-simple-refiner>
Inside the component, use the SimpleRefiner
class to define your refiner's appearance. Write a callback function to be called when the value is updated.
import { SimpleRefiner } from '@my-ul/ng-porcelain';
class MyComponent {
// Provided to the <porcelain-simple-refiner> component
mySimpleRefiner = new SimpleRefiner({
slug: 'mySimpleRefiner',
title: 'Select three options',
options: {
optionKey1: 'Option Value One',
optionKey2: 'Option Value Two',
optionKey3: 'Option Value Three'
}
});
// called when the refiner has a new value.
myRefinerUpdateCallbackFunction([refinerKey, refinerValue]: [string, string[]]) {
console.log('SimpleRefiner change', refinerKey, refinerValue);
// => 'simple refiner values have changed', 'mySimpleRefiner', ['optionKey1', 'optionKey2', 'optionKey3']
}
}
Incase some options needs to enabled by default then use preSelectedValues property of SimpleRefinerDefinition while creating refiner object or in the created refiner object update preSelectedValues before loaded onto applicator component. Only slugs must be sent and should properly be mapped.
import { SimpleRefiner } from '@my-ul/ng-porcelain';
class MyComponent {
// Provided to the <porcelain-simple-refiner> component
mySimpleRefiner = new SimpleRefiner({
slug: 'mySimpleRefiner',
title: 'Select three options',
preSelectedValues: ['optionKey1', 'optionKey2'],
options: {
optionKey1: 'Option Value One',
optionKey2: 'Option Value Two',
optionKey3: 'Option Value Three'
}
});
// called when the refiner has a new value.
myRefinerUpdateCallbackFunction([refinerKey, refinerValue]: [string, string[]]) {
console.log('SimpleRefiner change', refinerKey, refinerValue);
// => 'simple refiner values have changed', 'mySimpleRefiner', ['optionKey1', 'optionKey2', 'optionKey3']
}
//optionKey1 and optionKey2 are enabled by default. NOTE SEND ONLY PROPER MAPPED SLUGS IN THE preSelectedValues!! preSelectedValues property takes array of strings i.e slugs
}
The Date Refiner component provides an interface for the user to specify an applicable date range for refining a search.
The behavior of the Simple Refiner is defined by the DateRefinerDefinition
class in lib/shared
.
<!-- When using callback API -->
<porcelain-date-refiner
[refiner]="myDateRefiner"
(onRefinerChange)="myRefinerUpdateCallbackFunction($event)"
></porcelain-date-refiner>
<!-- When using subscription API -->
<porcelain-date-refiner [refiner]="myDateRefiner"></porcelain-date-refiner>
Write a refiner definition and a callback function that will receive the updated refiner value.
import { DateRefinerValue } from '@my-ul/ng-porcelain';
class MyComponent implements OnInit {
myDateRefiner = new DateRefiner({
slug: 'modifiedRange',
title: 'Modified'
// options: null // OMIT to use the default ranges
});
ngOnInit() {
/**
* Subscription API Option
* --
* Add one or many subscriptions to perform tasks when the Refiner value changes
*
* */
this.myDateRefiner.valueSubject.subscribe(newDateRefinerValue => {
console.log({
refinerSlug: this.myDateRefiner.slug,
refinerValue: newDateRefinerValue
});
});
}
/**
* Callback API Option
* --
* Callback fired when the Refiner value changes.
*
* */
myRefinerUpdateCallbackFunction([refinerKey, refinerValue]: [string, DateRefinerValue]) {
console.log({ refinerKey, refinerValue });
/*
'DateRefiner change',
'modifiedBefore',
{
from: Date(...) or null if unbounded
to: Date(...) or null if unbounded
}
*/
}
}
The updated value is an instance of DateRefinerValue. The date will be in the user's current timezone. Use the returned date objects to
work with the date provided using getUTCMonth()
, getUTCDay()
, and getUTCYear()
as appropriate to build date strings.
The Date Refiner Component will emit values in UTC. Be sure to handle these values appropriately so that your dates are returned properly.
If you serialize the value (into JSON or by using Date.toString()), the values for month, day and year may not be correct. As such, it is recommended to use JavaScript's UTC functions, such as getUTCFullYear
, getUTCMonth
, and getUTCDate
. Refer to MDN Date documentation for further details.
If you are using moment
to handle dates, ensure you call .utc()
before calling format. Refer to moment.js documentation for more details
moment.utc(refinerValue).format('YYYY-MM-DD');
A slug representing the preset chosen by the user is also returned.
let { optionSlug, to, from } = args[0];
Optionally, use moment
to work with the dates.
let { from, to } = args[0],
mTo = moment(to),
mFrom = moment(from),
fmtTo = mTo.format('YYYY-MM-DD'),
fmtFrom = mFrom.format('YYYY-MM-DD');
The Simple radio Refiner component is used to display a set of limited options for applicator component
<porcelain-simple-radio-refiner
[refiner]="mySimpleRefiner"
(onRefinerChange)="myRefinerUpdateCallbackFunction($event)"
></porcelain-simple-radio-refiner>
Inside the component, use the SimpleRefiner
class to define your refiner's appearance. Write a callback function to be called when the value is updated.
####### Note:- Update the type as radio!!!
import { SimpleRefiner } from '@my-ul/ng-porcelain';
class MyComponent {
// Provided to the <porcelain-simple-refiner> component
mySimpleRefiner = new SimpleRefiner({
type: 'radio',
slug: 'mySimpleRefiner',
title: 'Select three options',
options: {
optionKey1: 'Option Value One',
optionKey2: 'Option Value Two',
optionKey3: 'Option Value Three'
}
});
// called when the refiner has a new value.
myRefinerUpdateCallbackFunction([refinerKey, refinerValue]: [string, string[]]) {
console.log('SimpleRefiner change', refinerKey, refinerValue);
// => 'simple refiner values have changed', 'mySimpleRefiner', ['optionKey1', 'optionKey2', 'optionKey3']
}
}
tooltipText
is feild used to input the text to be show
when refiner option is inserted into refiner definition, update the tooltipText feild like below
refiner: new SimpleRefinerDefinition({
slug: 'simple',
type: 'radio',
title: 'United States of America (full definitions; see notes)',
options: {
AL: new SimpleOption({
badge: 4888949,
label: 'Compliance summary',
slug: 'AL',
isSelected: true,
tooltipText: 'Unified view of all compliance impacts'
})
})
####### In case you want to change tooltip icon, In the UI the icon is rendered via CSS content property. Use feild customToolTipImageUrl
refiner: new SimpleRefinerDefinition({
slug: 'simple',
type: 'radio',
title: 'United States of America (full definitions; see notes)',
options: {
AL: new SimpleOption({
badge: 4888949,
label: 'Compliance summary',
slug: 'AL',
isSelected: true,
tooltipText: 'Unified view of all compliance impacts',
customToolTipImageUrl: "/assets/info-icon.png"
})
})
####### NOTE!!! Important!!:- only get relevant path file of where image is location application project. Any other value will not work!!
The Applicator component allows a user to defer updates on an expensive operation (such as querying a server for search results) by staging a series of changes and then clicking apply.
The Applicator uses the Expando component to provide accordion behavior. These actions will be animated if you import BrowserAnimationsModule
in the root of your app.
<porcelain-applicator
[refiners]="refiners"
(onApply)="myApplyHandler($event)"
(onReset)="myResetHandler($event)"
></porcelain-applicator>
class MyComponent implements OnInit {
refiners = [
new DateRefiner(/* ... */),
new SimpleRefiner(/* ... */),
new SimpleRefiner(/* ... */),
new DateRefiner(/* ... */)
];
// Using Callback API
myApplyHandler(indexedValues, initialLoad) {
// initialLoad sets to true when refiner emits event on ngOninit
// and it sets to false when user clicks on apply/reset button
console.log(indexedValues, initialLoad);
}
myResetHandler(resetClicked) {
// resetClicked sets to true when reset button is clicked
// and it sets to false when user clicks on apply button
console.log(resetClicked);
}
// Using Subscription API
ngOnInit() {
// Subscribe to each refiner's value subject
this.refiners.forEach(refiner => {
refiner.valueSubject.subscribe(newRefinerValue => {
console.log({
refinerSlug: refiner.slug,
refinerValue: newRefinerValue
});
});
});
// Use combine to gather values for generating a query
combineLatest(this.refiners.map(refiner => refiner.valueSubject)).subscribe(
([date1, simple1, simple2, date2]) => {
/*
Called...
- once every subscription has emitted
- if any subscription emits again (refiner changes)
- great for combining search params
*/
}
);
}
}
The Refiners component is a macro that will automatically build a series of Refiners (Simple and Date), resulting in less template code, allowing developers to rely on type-checked definitions.
<porcelain-refiners
[refiners]="refiners"
(onRefinersChange)="refinersChange($event)"
></porcelain-refiners>
Your component should include a refiner array definition and a callback function to be called when a values have changed.
class MyComponent {
refiners = [
new DateRefiner({
slug: 'modifiedRange',
title: 'Modified'
// options: null // OMIT to use the default ranges
}),
new SimpleRefiner({
slug: 'mySimpleRefiner',
title: 'Select three options',
options: {
optionKey1: 'Option Value One',
optionKey2: 'Option Value Two',
optionKey3: 'Option Value Three'
}
})
];
refinersChange() {
console.log('refiners update', args);
}
}
Shows a spinner, suitable for loading, or activity indication.
<porcelain-spinner></porcelain-spinner>
Truncates a string to the width of its container. Any truncation will show ellipses.
<porcelain-truncate [value]="'stringToTruncate'"></porcelain-truncate>
Pipes provide common operations to be used in templates. To use pipes, import the PipesModule
into your @NgModule
.
import { PipesModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [
/* */
],
imports: [PipesModule],
exports: [
/* */
]
})
export class YourModuleNameHere {}
Performs a mathematic ceil
operation on a numeric value. Non-numeric values are passed through.
{{ 1234.01 | ceil }}
<!-- 1235 -->
Performs a mathematic floor
operation on a numeric value. Non-numeric values are passed through.
{{ 1234.99 | floor }}
<!-- 1234 -->
Performs a mathematic round
operation on a numeric value. Non-numeric values are passed through.
{{ 1234.01 | round }}
<!-- 1234 -->
{{ 1234.50 | round }}
<!-- 1235 -->
{{ 1234.99 | round }}
<!-- 1235 -->
Formats a string using unix-style sprintf syntax.
const currentLocale = getLocale(userLocale);
const projectCount = 1234;
const taskCount = 4321;
Here, the translate
pipe would provide a translation for the string it was provided.
{{ 'There are %1$u projects and %2$u tasks available.' | translate : currentLocale | sprintf :
projectCount : taskCount }}
<!--
en: There are 1234 projects and 4321 tasks available
es: Hay 1234 y 4321 pryectos de tareas disponibles
fr: Il y a 1234 projets et 4321 tâches disponibles.
-->
Filter an array of items using an Angular pipe.
A string representing the search query.
Set to true to filter each item on its labelProp
property.
Property on each item to use for the filter.
export class MyComponent {
searchQuery = '';
items = ['Apple', 'Banana', 'Cherry', 'Durian'];
}
<input [(ngModel)]="searchQuery" placeholder="filter the list..." />
<li *ngFor="let item of items | filter : searchQuery">
{{item}}
</li>
Note the use of the third (true
) and fourth ('type'
) properties in *ngFor
.
export class MyComponent {
searchQuery = '';
items = [
{ id: '5001', type: 'None' },
{ id: '5002', type: 'Glazed' },
{ id: '5005', type: 'Sugar' },
{ id: '5007', type: 'Powdered Sugar' },
{ id: '5006', type: 'Chocolate with Sprinkles' },
{ id: '5003', type: 'Chocolate' },
{ id: '5004', type: 'Maple' }
];
}
<input [(ngModel)]="searchQuery" placeholder="filter the list..." />
<li *ngFor="let item of items | filter : searchQuery : true : 'type' ">
{{item.type}} ({{item.id}})
</li>
Highlight text in a string.
Import the Pipes Module.
import { PipesModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [MyComponent],
imports: [PipesModule],
exports: [MyComponent]
})
export class MyModule {}
Use the highlight pipe by binding to an innerHTML
attribute.. You must bind to innerHTML
, or you may see raw escaped HTML.
<input [(ngModel)]="myQuery" type="text" /> <span innerHTML="{{myString | highlight : myQuery}}"></span>
Formats a date or number for the current locale (or a specified locale).
{{ 1234.56 | toLocaleString : 'en-US' }}
<!-- 1,234.56 -->
{{ new Date() || toLocaleString }}
<!-- 1/2/2020, 12:00:00 AM -->
Services can be used to provide application-wide functionalities like translation and analytics to your application.
Inject services directly into your components.
export class MyComponent {
constructor(
public translationService: TranslationService,
public googleAnalyticsService: GoogleAnalyticsService
) {}
}
The Google Analytics service is a proper Angular service wrapping the async Google Analytics API. When Angular is in dev mode, events will be output to the console.
Replace references to window._gaq
like this...
declare _gaq;
@Component({
// ...
})
export class MyComponent {
constructor() {
_gaq.push(['_trackPageview']);
}
}
with
import { GoogleAnalyticsService } from '@my-ul/ng-porcelain';
@Component({
// ...
providers: [GoogleAnalyticsService]
})
export class MyComponent {
constructor(ga: GoogleAnalyticsService) {
this.ga.push(['_trackPageview']);
}
}
Use the translation to reliably subscribe to a translation dictionary.
import { TranslationService } from '@my-ul/ng-porcelain';
@Component({
// ... //
providers: [TranslationService]
})
class MyComponent {
// Define labels as defaults
applyLabel: string = 'Apply';
cancelLabel: string = 'Cancel';
resetLabel: string = 'Reset';
constructor(private translationService: TranslationService) {
translationService.getTranslations().subscribe(
// Optional static translate method makes installing translations simple
TranslationService.translate(this, {
label_Apply: 'applyLabel',
label_Cancel: 'cancelLabel',
label_Reset: 'resetLabel'
})
);
}
}
Renders consistent, manageable breadcrumbs.
Import the module
import { BreadcrumbModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [
/**/
],
imports: [BreadcrumbModule],
exports: [
/**/
]
})
export class BreadcrumbModule {}
Use the components. Use links within the breadcrumb-item to show clickable links, if required.
<porcelain-breadcrumbs>
<porcelain-breadcrumb-item>
<a [routerLink]="['/']">{{translations.label_Orders}}</a>
</porcelain-breadcrumb-item>
<porcelain-breadcrumb-item>
{{translations.label_OrderDetails}}
</porcelain-breadcrumb-item>
</porcelain-breadcrumbs>
since 1.13.0
The Inputs Module contains form input controls for use in form building and data collection.
import { InputsModule } from '@my-ul/ng-porcelain';
@NgModule({
/* ... */
imports: [InputsModule]
/* ... */
})
export class MyModule {}
Since 1.4.0
The Search Input Component provides a familiar search control with a search and a clear button. In addition to clickable clear/search buttons, the Search Input will also provide clear when escape is pressed and submit when enter is pressed (as long as the component has focus)
Import the InputsModule
. Prior to Porcelain 1.13.0, import the SearchInputModule
.
import { InputsModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [YourComponent],
imports: [CommonModule, InputsModule],
exports: []
})
class YourModule {}
As of 1.13.0, the Search Input supports two-way binding. This makes it easy to provide and consume values in both directions.
In your template...
<porcelain-search-input [(value)]="myValue"></porcelain-search-input>
In your controller...
@Component(/* ... */)
export class MyComponent {
myValue: string = '';
}
If you prefer manual control over how values are provided and consumed by the Search Input, you can split the binding...
In your template...
<porcelain-search-input [value]="myValue" (valueChanged)="myHandler($event)"></porcelain-search-input>
In your controller...
@Component()
export class MyComponent {
myValue: string = '';
myHandler(newValue: string): void {
if (this.myValue !== newValue) {
this.myValue = newValue;
}
}
}
In addition to two-way binding, the Search Input provides two buttons that emit when clicked (or used via keyboard): (submit)
and (clear)
. Note that in this example, myValue
is bound to the Search Input with split binding, but myValue
could also be bound with Two-Way Binding to simplify the marshalling of values between the parent component and the Search Input.
In your template...
<porcelain-search-input
[value]="myValue"
(valueChanged)="valueChangeHandler($event)"
(submit)="submitHandler($event)"
(clear)="clearHandler($event)"
></porcelain-search-input>
In your controller...
@Component()
export class MyComponent {
myValue: string = '';
valueChangeHandler(changedValue: string): void {
if (this.myValue !== changedValue) {
this.myValue = changedValue;
}
}
submitHandler(submittedValue: string): void {
// do something with submittedValue
}
clearHandler(): void {
// do something to "clear"
this.myValue = '';
}
}
Install Porcelain 1.4 and its dependencies
npm install --save @my-ul/ng-porcelain@^1.4.0
Import the SearchInputModule
import { SearchInputModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [YourComponent],
imports: [CommonModule, SearchInputModule],
exports: []
})
class YourModule {}
Write a function to handle new values from the component...
@Component()
class YourComponent {
handleNewValue(newValue: string): void {
this.value = newValue;
}
}
Place the component in your template, with reference to the handler...
<porcelain-search-input (submitHandler)="handleNewValue($event)"></porcelain-search-input>
Change the Placeholder Text to change the displayed text. Useful for i18n/translation.
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[placeholderLabel]="'Volume'"
></porcelain-search-input>
Just use uservalue to assign value to it in the HTML
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[userValue]="searchTerm"
></porcelain-search-input>
For Search box border Toggle set borders for
1.)true for enabling border 2.)false for disabling border
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[borders]="false"
></porcelain-search-input>
For getting just empty value when search cancel is clicked, use emptyhandler
<porcelain-search-input
[submitHandler]="handleNewValue($event)"
[borders]="false"
[emptyHandler]="clearSearchClick($event)"
></porcelain-search-input>
Alternative Font Awesome icons can be used instead of the defaults for 'Clear' and 'Submit'. See Font Awesome for Angular docs for more information.
<porcelain-search-input
(submitHandler)="..."
[submitIcon]="mySubmitIcon"
[clearIcon]="myClearIcon"
></porcelain-search-input>
The Password Input allows a user to input a password. The control allows the user to optionally reveal the password to make managing strong passwords easier.
<porcelain-password-input [(value)]="myPassword"></porcelain-password-input>
In your controller...
export class MyController {
myPassword: string = '';
}
The Text Input control allows a user to enter text. It is not validated.
In your template...
<porcelain-text-input [(value)]="textValue"></porcelain-text-input>
In your controller...
export class MyController {
textValue: string = '';
}
since 1.10.0
The lists module is used for building styled list items with aligned headers and the ability to sort columns.
To use the List Module within your application, import the ListsModule
.
import { ListsModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [
/* ... */
],
imports: [ListsModule],
exports: [
/* ... */
]
})
export class MyApplicationModule {}
since 1.13.0
The Dynamic Header can be used to allow the end user to reorder columns.
Where the List Header is populated with static instances of Sort Header and Text Header, the Dynamic Header uses an array JavaScript objects to control appearance. These objects implement the DynamicHeader interface. These header references are built in your application, and can also be used to render the List Item Cell components.
The DynamicColumn
provides a thorough, easy-to-use type that makes it easy to compose columns for use in the Dynamic Header.
export interface DynamicColumn {
/**
* The user-visible label for the column
*/
label: string;
/**
* The internal identifier used to identify/track the column.
* These values should be unique.
*/
key: string;
/**
* Locks a column in the active state. When `locked` is true, a column cannot be removed
*/
locked: boolean;
/**
* The type of control to display for representing the column. Either 'text', 'search', or 'sort'.
*/
type: DynamicColumnType;
/**
* A numeric representation of how wide the column should be.
* The sum of the `width` values should be less than or equal to 1
*/
width: number;
}
The Dynamic Header uses two-way binding to manage changes to the columns. Any external changes to the column order will be reflected immediately, and any changes made using drag-drop functionality will be published.
The simplest way to bind columns is with two-way binding.
In your template...
<porcelain-dynamic-header [(columns)]="activeColumns"></porcelain-dynamic-header>
In your controller...
import { DynamicColumn } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
},
{
label: 'Price',
key: 'price',
type: 'text',
locked: false,
width: 0.5
}
];
}
The Dynamic Header will aggregate changes to Sort Header instances and publish them via a number of bindings.
- The Dynamic Column's
key
will be used as each column'ssortKey
. - Changes to the active sort are handled with three
@Output()
bindings. In order to use sort functionality, use bindingsactiveSortKeyChange
,activeSortDirectionChange
andsortChange
. - Two output bindings,
activeSortKeyChange
andactiveSortDirectionChange
, can be used with two-way bindings to simplify implementation.
Sort change fires once per change to sort, and includes sortKey
and sortDirection
. The $event
type is SortTuple
. It's usage is similar to the other bindings.
In these examples, notice that activeSortKey
and activeSortDirection
are not using two-way binding.
In your template...
<porcelain-dynamic-header
[(columns)]="activeColumns"
[activeSortKey]="activeSortKey"
[activeSortDirection]="activeSortDirection"
(sortChange)="updateSort($event)"
></porcelain-dynamic-header>
In your controller...
import { DynamicColumn, SortTuple, SortDirection } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
}
/** ... **/
];
activeSortKey: string = null;
activeSortDirection: SortDirection = null;
updateSort([activeSortKey, activeSortDirection]: SortTuple): void {
this.activeSortKey = activeSortKey;
this.activeSortDirection = activeSortDirection;
this.updateList();
}
updateList() {
this.listService.getList(this.activeSortKey, this.activeSortDirection).subscribe(updatedList => {
this.list = updatedList;
});
}
}
Although most cases will probably use (onSort)
bindings, it is possible to bind to sortKey and sortDirection changes separately.
####### Binding to (activeSortKeyChange)
Whenever the sort column key changes, this output will receive an update.
In your template...
<porcelain-dynamic-header
[activeSortKey]="activeSortKey"
(activeSortKeyChange)="updateActiveSortKey($event)"
></porcelain-dynamic-header>
In your controller...
import { DynamicColumn } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
}
/** ... **/
];
activeSortKey: string = null;
updateActiveSortKey(activeSortKey: string): void {
this.activeSortKey = activeSortKey;
this.updateList();
}
updateList() {
this.listService.getList(this.activeSortKey).subscribe(updatedList => {
this.list = updatedList;
});
}
}
####### Binding to (activeSortDirectionChange)
Whenever the active sort direction changes, this output will receive an update.
In your template...
<porcelain-dynamic-header
[activeSortDirection]="activeSortDirection"
(activeSortDirectionChange)="updateActiveSortDirection($event)"
></porcelain-dynamic-header>
In your controller...
import { DynamicColumn, SortDirection } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
}
/** ... **/
];
activeSortKey: string = null;
updateActiveSortKey(activeSortKey: string): void {
this.activeSortKey = activeSortKey;
// updateActiveSortDirection also calls this
// this may result in two calls to updateList()
// consider binding to (sortChange) instead.
this.updateList();
}
activeSortDirection: SortDirection = null;
updateActiveSortDirection(activeSortDirection: SortDirection): void {
this.activeSortDirection = activeSortDirection;
// updateActiveSortKey also calls this
// this may result in two calls to updateList()
// consider binding to (sortChange) instead.
this.updateList();
}
updateList() {
this.listService.getList(this.activeSortKey, this.activeSortDirection).subscribe(updatedList => {
this.list = updatedList;
});
}
}
####### Timing problems with activeSortKeyChange
and activeSortDirectionChange
You may notice that when using the split bindings for activeSortDirection
and activeSortKey
, it becomes difficult to sanely trigger updates. Commonly, both of these may fire at once, which might cause excessive API calls. You might consider using a throttle/debounce to ensure that these aren't called too quickly, but these will rely on arbitrary timeouts.
For most cases, it is most sensible to use the (sortChange)
output binding, as it emits a tuple containing both updated activeSortKey
and activeSortDirection
values. Implementations using (sortChange)
do not need to rely on arbitrary timeouts (such as throttle/debounce). As an added benefit, there is typically less code needed for this implementation.
####### Two-Way Binding with [(activeSortKey)]
and [(activeSortDirection)]
It is possible to use two-way binding for activeSortKey and activeSortDirection. In most cases, you will need to use the (sortChange) output to trigger a list reload, so two-way binding, for most cases, is not recommended.
In your template...
<porcelain-dynamic-header
[(columns)]="activeColumns"
[(activeSortKey)]="activeSortKey"
[(activeSortDirection)]="activeSortDirection"
(sortChange)="updateSort($event)"
></porcelain-dynamic-header>
In your controller...
export class MyController {
activeSortKey: string = null;
activeSortDirection: SortDirection = null;
updateSort(sort: SortTuple) {
// activeSortKey and activeSortDirection
// are assumed to be updated, but this isn't
// a guarantee. The `sort` argument IS
// guaranteed to be most-recent.
this.updateList();
}
}
In addition to offering controls to sort, the Search Header can be toggled to offer keyword search for values in a column. These values can perform in-memory searches or reach out to an API for a more exhaustive search.
Since the Search Header offers support for Sort, it offers the same event bindings as the Sort Header, but also offers two Angular @Output
bindings to support searching.
- The Search header offers a toggle to switch between Search and Sort functionality.
- The Sort outputs of the Search Header are bound using the Sort Header API. Your application will need to bind an adequate combination of
[activeSortKey]
,[activeSortDirection]
,(activeSortKeyChange)
,(activeSortDirectionChange)
, and(sortChange)
in addition to search bindings. - Most sort cases can be supported with just
[activeSortKey]
,[activeSortDirection]
and(sortChange)
. - Search functionality provides two-way binding for the query, as well as @Output bindings for the search submit and search clear events.
The search query field supports two-way binding. This will keep your application up-to-date with internal state of the search input.
In your template...
<porcelain-dynamic-header [(query)]="searchQuery"></porcelain-dynamic-header>
In your controller...
export class MyController {
searchQuery: string = '';
}
####### Using split binding
If you do not want to use the two-way binding syntax for the search query, you can split it.
The (queryChange)
binding will be called with every keystroke, so do not bind it to any expensive operations, such as an API call. If you are building live search functionality, you may benefit from rxjs debounce.
In your template...
<porcelain-dynamic-header
[query]="searchQuery"
(queryChange)="queryChanged($event)"
></porcelain-dynamic-header>
In your controller...
export class MyController {
searchQuery: string = '';
queryChanged(searchQuery: string) {
if (this.searchQuery !== searchQuery) {
this.searchQuery = searchQuery;
}
}
}
####### Debouncing the (queryChange)
Output.
Some applications of the Search Header might want semi-live updating of search results. To safely implement this, using rxjs debounceTime
and throttleTime
may help.
The debounceTime
operator will emit the last value over a specified interval, whereas the throttleTime
operator will emit the first value and wait the specified interval before emitting again.
In your template...
<porcelain-dynamic-header
[query]="searchQuery"
(queryChange)="queryChanged($event)"
></porcelain-dynamic-header>
In your controller...
import { Subject } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
class Widget {
widget_id?: number;
name: string;
}
export class MyController {
query$: Subject<string> = new Subject();
results: any[] = null;
constructor(public widgetService: WidgetService) {
// Use a debounced observable to slow down queries
this.query$
.pipe(
// after 200 ms, take the last update
debounceTime(200),
// turn this observable into another
map(query => this.search(query))
)
.subscribe(results => (this.results = results));
}
queryChanged(query: string) {
this.query$.next(query);
}
search(query: string): Observable<Widget[]> {
return this.widgetService.search(query);
}
}
The queryChange Output will fire when a new search query is available. The emitted $event
is a dictionary containing column search values. The dictionary will be keyed with the column.key
property. If a user clears a search input, the key will be removed from the dictionary.
Take the following column, for example.
let column = {
label: 'Description',
key: 'description',
locked: false,
type: 'search',
width: 1 / 2
};
If a user typed "television" as a search query, the following object would be returned on the (queryChange)
output:
{
"description": "television"
}
You can do whatever you need to with this object. Commonly, you might want to lowercase or uppercase the value so that the string will be matched for any case.
In your template...
<porcelain-dynamic-header (queryChange)="search($event)"></porcelain-dynamic-header>
In your controller...
export class MyController {
searchQuery: string = '';
search(searchQuery: DynamicSearchQuery): void {
// use DynamicSearchQuery
}
}
The List Component is the outer-most wrapper for the List Component System
<porcelain-list>
<!-- children here -->
</porcelain-list>
The List Body Component wraps a series of items. Although there is no technical requirement to use List Item components (use any elements necessary), it is recommended. The List Body Component provides padding and provides spacing between List Item components.
<porcelain-list>
<porcelain-list-header>
<!-- porcelain-list-header-cell instances here -->
</porcelain-list-header>
<porcelain-list-body>
<!-- porcelain-list-item instances here -->
</porcelain-list-body>
</porcelain-list>
The List Header Component wraps a series of List Header Cells. The List Header Component can be made sticky using the @w11k/angular-sticky-things library.
<porcelain-list>
<porcelain-list-header>
<!-- porcelain-list-header-cell instances here -->
</porcelain-list-header>
</porcelain-list>
The List Header Cell Component is used to define table headers for a List. This is considered a container.
The width
input is mandatory, and is a number between zero and one. For all list-header-cell components in a list-header, the total of the "width" inputs should be 1
.
<porcelain-list>
<porcelain-list-header>
<porcelain-list-header-cell [width]="2 / 3">
<!-- Header here -->
</porcelain-list-header-cell>
<porcelain-list-header-cell [width]="1 / 3">
<!-- Header here -->
</porcelain-list-header-cell>
</porcelain-list-header>
</porcelain-list>
The List Item Component is used to markup the display of a series of items. Since it is designed to be a single atomic instance of an item, it is designed to be used with the *ngFor
directive.
The List Item component is responsible for drawing borders between List Item Cell components.
Property | Type | Description |
---|---|---|
[success] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the list item will be set to UL Medium Green. |
[error] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Medium Red. |
[warning] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Medium Yellow. |
[primary] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Action Blue. |
[secondary] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Medium Teal. |
<porcelain-list>
<porcelain-list-header>
<!-- porcelain-list-header-cell instances here -->
</porcelain-list-header>
<porcelain-list-body>
<porcelain-list-item>
<!-- porcelain-list-item-cell instances here -->
</porcelain-list-item>
</porcelain-list-body>
</porcelain-list>
The List Item Cell defines a cell within a List Item. This typically corresponds to a property within an entity that is being looped.
Property | Type | Description |
---|---|---|
[width] |
number |
A number between zero and one. All cells within a List Item component should have [width] inputs that sum to 1. |
[alignTop] |
boolean |
When true , the contents of the cells will be at the top of the row of cells. Cells are middle aligned by default. |
[alignBottom] |
boolean |
When true , the contents of the cells will be at the bottom of the row of cells. Cells are middle aligned by default. |
[padAll] |
boolean |
When true , the contents of the cell will be wrapped with ~20px of spacing. Default false . |
[padTop] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the top of the cell. Default false . |
[padRight] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the right of the cell. Default false . |
[padBottom] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the bottom of the cell. Default false . |
[padLeft] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the left of the cell. Default false . |
<porcelain-list-item>
<porcelain-list-item-cell [width]="1/5" [padAll]="true">
{{ item.price | currency : 'USD' }}
</porcelain-list-item-cell>
</porcelain-list-item>
The Sort Header can be used to control sort variables within a list view.
This component supports banana-box/two-way binding notation for updating the values of activeSortKey
and activeSortDirection
.
Property | Type | Description |
---|---|---|
[label] |
string |
User-visible label shown in the clickable portion of the sort header. |
[sortKey] |
string |
Sets the key that this sort header is responsible for toggling. When equal to activeSortKey are equal, the component will be in the active state. Typically, this is a constant value, wrapped in single quotes, but it can be a variable, which is useful for loops. This value should NOT change often, if ever, after the component loads. |
<porcelain-sort-header [label]=" 'Date Modified' " [sortKey]=" 'date' "></porcelain-sort-header>
The (onSortChange) output will emit the sortKey
and the sortDirection
in a tuple: [sortKey, sortDirection]
. Use this to trigger any page load updates, as binding to the activeSortKeyChange
and activeSortColumnChange
may cause any bound actions to execute sporadically (twice in some cases, or not at all in others).
<porcelain-sort-header
(onSortChange)=" updateList() "
[(activeSortKey)]=" currentSortKey "
[(activeSortDirection)]=" currentSortDirection "
></porcelain-sort-header>
The following code pattern can be used to refresh a list after the sort fields are modified.
@Component({
// ...
})
export class MyComponent {
public currentSortKey;
public currentSortDirection;
constructor(public listService: ListService) {
this.resetSort();
}
resetSort() {
this.currentSortKey = 'price';
this.currentSortDirection = 'asc';
}
updateList() {
this.listService
.getList({
sortKey: this.currentSortKey,
sortDirection: this.currentSortDirection
})
.subscribe(newListItems => {
this.items = newListItems;
});
}
}
Binds the current active sort column from your component. Can be split into @Input
/@Output
bindings if you need a callback, although this is not recommended in favor of (onSortChange)
, which fires a callback once per toggle.
Set to the application's current sort column. When equal to [sortKey]
, the component will be in the active
state.
<porcelain-sort-header [(activeSortKey)]=" currentSortColumn "></porcelain-sort-header>
Set this to your application's current sort column. If [sortKey]
and [activeSortKey]
are equal with a valid [activeSortDirection]
, the header will appear active.
Valid values are null
, 'asc'
and 'desc'
.
Fires whenever the [activeSortKey]
is set to [sortKey]
. Value can be accessed with $event
.
<porcelain-sort-header (activeSortKeyChange)=" handleSortKeyChange($event) "></porcelain-sort-header>
Use two-way binding to keep the sort-header and your application's sort column state in sync.
<porcelain-sort-header [(activeSortDirection)]=" myCurrentSortDirection "></porcelain-sort-header>
Set the activeSortDirection
from your application.
<porcelain-sort-header [activeSortDirection]=" myCurrentSortDirection "></porcelain-sort-header>
Bind a callback that will fire upon changes to activeSortDirection
. $event
will contain the new sort direction.
<porcelain-sort-header (activeSortDirectionChange)=" handleSortDirectionChange($event)">
</porcelain-sort-header>
Skeletons can be used to improve perceived load time by providing a hint about the content that is about to load.
Creates an animated skeleton region, suitable as a placeholder for images. The Skeleton Block tries to fill all vertical and horizontal space available to it, so wrap it with another element to specify the dimensions.
The Skeleton Block has no @Input()
or @Output()
decorators.
<p-skeleton-block></p-skeleton-block> <porcelain-skeleton-block></porcelain-skeleton-block>
Creates an animated Skeleton Block that can be sized in characters (ems). Internally used by the Skeleton Line.
A number
specifying how many characters (ems) the word skeleton should be.
<porcelain-skeleton-block [characters]="6"></porcelain-skeleton-block>
<p-skeleton-block [characters]="6"></p-skeleton-block>
<porcelain-skeleton-line [minLength]="7" [maxLength]="13"></porcelain-skeleton-line>
<p-skeleton-line [minLength]="7" [maxLength]="13"></p-skeleton-line>
Automatically creates a line with randomly-sized words.
Minimum character width for the words placed in the line. Defaults to 7
.
Maximum character width for the words placed in the line. Defaults to 13
.
<porcelain-skeleton-paragraph></porcelain-skeleton-paragraph>
<p-skeleton-paragraph></p-skeleton-paragraph>
Automatically creates a paragraph of Skeleton Word elements.
Number of lines of skeleton text to generate. Lines will be generated to the full width of the block. If this is undesired, use the Skeleton Word.
The toolbar module consists of several components that can be used to quickly compose toolbars within an Angular application.
To use the Toolbar Module, import it into your module. Subtle dropdown animations can be used by the Toolbar system. To enable these animations, import BrowserAnimationsModule
in the root module of your app.
import { ToolbarModule } from '@my-ul/ng-porcelain';
@NgModule({
imports: [ToolbarModule]
})
export class AppModule {}
The Toolbar component composes one row of Toolbar Cells to create a toolbar. To stack more than one Toolbar, wrap with the ToolbarsComponent
(note the added 's').
- Toolbar Cell Component -
<porcelain-toolbar-cell>
<porcelain-toolbar>
<porcelain-toolbar-cell>...</porcelain-toolbar-cell>
</porcelain-toolbar>
The Toolbar Cell component builds cells within a Toolbar. Adjacent cells are divided with a thin line.
-
[flex]
- a flex definition, specifying the grow/shrink/basis property for element sizing.
- any
<porcelain-toolbar-cell [flex]="'0 0 auto'">
...
</porcelain-toolbar-cell>
The Toolbar Text component allows the placement of arbitrary text within a toolbar cell. This can be used near other controls, such as a Toolbar Option or Toolbar Button, to provide context about the purpose of those controls. For example, a Toolbar Text could be placed near a Toolbar Button to describe the state of pagination.
-
[alignRight]
- true if you want the text aligned right -
[alignCenter]
- true if you want the text centered -
[noWrap]
- true if you don't want your text to break into lines
<porcelain-toolbar-cell ...>
<porcelain-toolbar-text [alignRight]="true" [alignCenter]="true" [noWrap]="true">
{{totalResultCount.toLocaleString()}} Results
</porcelain-toolbar-text>
</porcelain-toolbar-cell>
The Toolbar Button component is an accessible button that includes an optional icon.
-
[icon]
- a Font Awesome 5IconDefinition
-
[isLabelSrOnly]
-true
if you only want to hide the button text (except for screen readers). -
(onClick)
- a callback function to be called when the button is clicked or triggered with enter/space.
Text will be used for the button label.
<porcelain-toolbar-cell ...>
<porcelain-toolbar-button [icon]="faSave" [isLabelSrOnly]="false" (onClick)="mySaveHandler($event)">
Save
</porcelain-toolbar-button>
</porcelain-toolbar-cell>
The Toolbar Select component closely mimics an HTML <select>
element. The Toolbar Select component provides a highly-customizable, yet accessible dropdown implementation. The component allows for a HTML-templated dropdown options, as well as in the currently-selected-item window.
-
[fullWidth]
-true
to enable the full-width presentation, which is left-aligned, and spans the full width of the Toolbar Select Component. -
[label]
- Label to be displayed to the left of the currently-selected item. -
[(value)]
- two-way binding property for the current value.
The two-way binding of value can be broken apart if you would like to register callbacks to handle updating your values. This syntax would use the following properties instead of [(value)]="myValue"
. When split, the (valueChange)
callback is responsible for updating the values passed to [value]
.
<porcelain-toolbar-select [value]="myValue" (valueChange)="myValueChangeHandler($event)"
><!-- ... --></porcelain-toolbar-select
>
-
<porcelain-toolbar-option>
- used to define options and the template used to represent the option in the dropdown list. -
<porcelain-toolbar-selected-template>
- used to define the display of the currently-selected option when the component is closed. Use more than one to define null states based onvalue
The following code could be used to create a people picker.
@Component({
// ...
})
export class MyComponent {
/**
* The current person selected by the Component
*/
currentPerson = null;
/**
* People, indexed by their username to make the selected-template easy to populate.
* Sample data from https://fakepersongenerator.com/
*/
people = {
'cory.ramer': {
username: 'cory.ramer',
first: 'Cory',
last: 'Ramer',
email: 'cory.ramer@ul.com',
phone: '(309) 548-8281'
},
'eric.hawes': {
username: 'eric.hawes',
first: 'Eric',
last: 'Hawes',
email: 'eric.hawes@ul.com',
phone: '(708) 826-6749'
},
'james.dock': {
username: 'james.dock',
first: 'James',
last: 'Dock',
email: 'james.dock@ul.com',
phone: '(801) 638-3830'
}
};
/**
* Turns an object into an array for looping
*/
public getValues(obj) {
return Object.keys(obj).map(key => obj[key]);
// or Object.values(obj) if you have the polyfill enabled
}
}
<porcleain-toolbar-cell ...>
<porcelain-toolbar-select
[fullWidth]="true"
[label]="'To:'"
[(value)]="currentPerson">
<!-- define a template for the currently selected item -->
<porcelain-toolbar-selected-template *ngIf="currentPerson">
{{recipients[currentPerson].email}}
</porcelain-toolbar-selected-template>
<!-- define a template for the current window for null/undefined selection -->
<porcelain-toolbar-selected-template *ngIf="currentPerson === null">
—
</porcelain-toolbar-selected-template>
<!--
- define the people dictionary in the TypeScript Component, above.
- loop with *ngFor, if necessary. Rendering static options is also possible.
- bind to [value] to specify what value is returned
- use HTML for rich formatting control over the option
-->
<porcelain-toolbar-option
*ngFor="let person of getValues(people)"
[value]="person.username"
>
<strong>{{person.first}} {{person.last}}</strong><br>
{{person.email}} • {{person.phone}}
</porcelain-toolbar-option>
</porcelain-toolbar-select>
</porcelain-toolbar-cell>
The table view module is used for building styled table view list items with aligned headers and the ability to sort columns and interchange columns if rack component is used.
To use the List Module within your application, import the ListsModule
.
import { TableviewModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [
/* ... */
],
imports: [TableviewModule],
exports: [
/* ... */
]
})
export class MyApplicationModule {}
The parent component is table view header and insdie it each block is table view header item. The tableview header item takes an input width which should be same as for tableview columns so that they neatly aling in UI
<p-tableview-header>
<p-tableview-header-item [width]="column.width"
*ngFor="let column of getColumnValues()">
<ng-container>
<p-tableview-text-header>
<span innerHTML="{{column.label}}" title="{{column.label}}"></span>
</p-tableview-text-header>
</ng-container>
</p-tableview-header-item>
</p-tableview-header>
Property | Type | Description |
---|---|---|
[width] |
number |
A number between zero and one. All cells within a List Item component should have [width] inputs that sum to 1. |
[alignTop] |
boolean |
When true , the contents of the cells will be at the top of the row of cells. Cells are middle aligned by default. |
[alignBottom] |
boolean |
When true , the contents of the cells will be at the bottom of the row of cells. Cells are middle aligned by default. |
[padAll] |
boolean |
When true , the contents of the cell will be wrapped with ~0.5em of spacing. Default false . |
[padTop] |
boolean |
When true , the contents of the cell will contain ~0.5em of padding at the top of the cell. Default false . |
[padRight] |
boolean |
When true , the contents of the cell will contain ~0.5em of padding at the right of the cell. Default false . |
[padBottom] |
boolean |
When true , the contents of the cell will contain ~0.5em of padding at the bottom of the cell. Default false . |
[padLeft] |
boolean |
When true , the contents of the cell will contain ~0.5em of padding at the left of the cell. Default false . |
<p-tableview-header>
<p-tableview-header-item [width]="1/5" [padAll]="true"> </p-tableview-header-item>
</p-tableview-header>
Table view text header is just used to display the title value of the header. This should always be enclosed in
<p-tableview-text-header>
<span innerHTML="{{column.label}}" title="{{column.label}}"></span>
</p-tableview-text-header>
The Sort Header can be used to control sort variables within a Table view. This should always be enclosed in
The search sort header takes search component and sort component. The switching functionality is built inside already. It takes both legacy search input and new search input.
<p-tableview-searchSort-header>
<p-tableview-sort-header> </p-tableview-sort-header>
<p-search-input> </p-search-input>
</p-tableview-searchSort-header>
<p-tableview-searchSort-header>
<p-tableview-sort-header> </p-tableview-sort-header>
<porcelain-search-input> </porcelain-search-input>
</p-tableview-searchSort-header>
This component supports banana-box/two-way binding notation for updating the values of activeSortKey
and activeSortDirection
.
Property | Type | Description |
---|---|---|
[label] |
string |
User-visible label shown in the clickable portion of the sort header. |
[sortKey] |
string |
Sets the key that this sort header is responsible for toggling. When equal to activeSortKey are equal, the component will be in the active state. Typically, this is a constant value, wrapped in single quotes, but it can be a variable, which is useful for loops. This value should NOT change often, if ever, after the component loads. |
<p-tableview-sort-header [label]=" 'Date Modified' " [sortKey]=" 'date' "></p-tableview-sort-header>
The (onSortChange) output will emit the sortKey
and the sortDirection
in a tuple: [sortKey, sortDirection]
. Use this to trigger any page load updates, as binding to the activeSortKeyChange
and activeSortColumnChange
may cause any bound actions to execute sporadically (twice in some cases, or not at all in others).
<p-tableview-sort-header
(onSortChange)=" updateList() "
[(activeSortKey)]=" currentSortKey "
[(activeSortDirection)]=" currentSortDirection "
></p-tableview-sort-header>
The following code pattern can be used to refresh a list after the sort fields are modified.
@Component({
// ...
})
export class MyComponent {
public currentSortKey;
public currentSortDirection;
constructor(public listService: ListService) {
this.resetSort();
}
resetSort() {
this.currentSortKey = 'price';
this.currentSortDirection = 'asc';
}
updateList() {
this.listService
.getList({
sortKey: this.currentSortKey,
sortDirection: this.currentSortDirection
})
.subscribe(newListItems => {
this.items = newListItems;
});
}
}
Binds the current active sort column from your component. Can be split into @Input
/@Output
bindings if you need a callback, although this is not recommended in favor of (onSortChange)
, which fires a callback once per toggle.
Set to the application's current sort column. When equal to [sortKey]
, the component will be in the active
state.
<p-tableview-sort-header [(activeSortKey)]=" currentSortColumn "></p-tableview-sort-header>
Set this to your application's current sort column. If [sortKey]
and [activeSortKey]
are equal with a valid [activeSortDirection]
, the header will appear active.
Valid values are null
, 'asc'
and 'desc'
.
Fires whenever the [activeSortKey]
is set to [sortKey]
. Value can be accessed with $event
.
<p-tableview-sort-header (activeSortKeyChange)=" handleSortKeyChange($event) "></p-tableview-sort-header>
Use two-way binding to keep the sort-header and your application's sort column state in sync.
<p-tableview-sort-header [(activeSortDirection)]=" myCurrentSortDirection "></p-tableview-sort-header>
Set the activeSortDirection
from your application.
<p-tableview-sort-header [activeSortDirection]=" myCurrentSortDirection "></p-tableview-sort-header>
Bind a callback that will fire upon changes to activeSortDirection
. $event
will contain the new sort direction.
<p-tableview-sort-header (activeSortDirectionChange)=" handleSortDirectionChange($event)">
</p-tableview-sort-header>
The tableview list wraps the body content. All Error messages and unexception implementation includuing showing messages should be done with
<p-tableview-list>
<p-tableview-list-body> </p-tableview-list-body>
</p-tableview-list>
The table view list item and table view list item cell forms the row and columns. Use Dynamic columns type to maintain widths. The widths of header andd table view list item should be same to be properly aligned.
The has properly ElementIndex, for alternate background row colors to enabled pass array iterated number.
Incase to change the color values, use view child and access bgColorforListitem.
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { TableviewListItemComponent } from '@my-ul/ng-porcelain';
@Component({})
export class MyApplicationModule {
@ViewChild('tableViewListRow', { static: false }) list_item_row: TableviewListItemComponent;
changecolor() {
this.list_item_row.bgColorforListitem.oddColor = 'green';
this.list_item_row.bgColorforListitem.evenColor = 'grey';
}
}
<p-tableview-list>
<p-tableview-list-body>
<p-tableview-list-item #tableViewListRow [ElementIndex]="i">
<p-tableview-list-item-cell> </p-tableview-list-item-cell>
</p-tableview-list-item>
</p-tableview-list-body>
</p-tableview-list>
Incase there are multiple row , use viewchilder along with querylist and convert ToArray(). Then use forEach and change the color
To create column and row, use *ngfor list data for rendering which will create row. Then use *ngfor column data inside to genarate column data. Below is example.
<p-tableview-list>
<p-tableview-list-body>
<ng-container *ngFor="let person of getListItems();let i = index">
<p-tableview-list-item [ElementIndex]="i">
<p-tableview-list-item-cell
[width]="column.width"
[padAll]="true"
*ngFor="let column of getColumnValues()"
>
<ng-container *ngIf="column.key === 'last_name'">
<strong>{{person.first_name}} {{person.last_name}}</strong>
</ng-container>
<ng-container *ngIf="column.key !== 'last_name'">
<span>{{person[column.key]}}</span>
</ng-container>
</p-tableview-list-item-cell>
</p-tableview-list-item>
</ng-container>
</p-tableview-list-body>
</p-tableview-list>
The dropDown System is a generic component that has 3 main parts. It is basically stripped down version of dropdown into select container, selected container and list option container.
To use the dropDown Module within your application, import the DropdownSystemModule
.
import { DropdownSystemModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [
/* ... */
],
imports: [DropdownSystemModule],
exports: [
/* ... */
]
})
export class MyApplicationModule {}
The Dropdown container is the parent component that houses the dropdown list component and dropdown selected template component
<porcelain-dropdown-select [(value)]="value">
<porcelain-dropdown-selectedtemplate>
<porcelain-dropdown-inputbox [(query)]="searchText"></porcelain-dropdown-inputbox>
</porcelain-dropdown-selectedtemplate>
<porcelain-dropdown-selectoption [value]="option.value" *ngFor="let option of getValues(options)">
<strong>{{option.name}}</strong> <span style="font-size: 90%; color: #888">{{option.group}}</span><br>
{{option.value}}@ul.com
</porcelain-dropdown-selectoption>
</porcelain-dropdown-select>
since 1.10.0
The lists module is used for building styled list items with aligned headers and the ability to sort columns.
To use the List Module within your application, import the ListsModule
.
import { ListsModule } from '@my-ul/ng-porcelain';
@NgModule({
declarations: [
/* ... */
],
imports: [ListsModule],
exports: [
/* ... */
]
})
export class MyApplicationModule {}
since 1.13.0
The Dynamic Header can be used to allow the end user to reorder columns.
Where the List Header is populated with static instances of Sort Header and Text Header, the Dynamic Header uses an array JavaScript objects to control appearance. These objects implement the DynamicHeader interface. These header references are built in your application, and can also be used to render the List Item Cell components.
The DynamicColumn
provides a thorough, easy-to-use type that makes it easy to compose columns for use in the Dynamic Header.
export interface DynamicColumn {
/**
* The user-visible label for the column
*/
label: string;
/**
* The internal identifier used to identify/track the column.
* These values should be unique.
*/
key: string;
/**
* Locks a column in the active state. When `locked` is true, a column cannot be removed
*/
locked: boolean;
/**
* The type of control to display for representing the column. Either 'text', 'search', or 'sort'.
*/
type: DynamicColumnType;
/**
* A numeric representation of how wide the column should be.
* The sum of the `width` values should be less than or equal to 1
*/
width: number;
}
The Dynamic Header uses two-way binding to manage changes to the columns. Any external changes to the column order will be reflected immediately, and any changes made using drag-drop functionality will be published.
The simplest way to bind columns is with two-way binding.
In your template...
<porcelain-dynamic-header [(columns)]="activeColumns"></porcelain-dynamic-header>
In your controller...
import { DynamicColumn } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
},
{
label: 'Price',
key: 'price',
type: 'text',
locked: false,
width: 0.5
}
];
}
The Dynamic Header will aggregate changes to Sort Header instances and publish them via a number of bindings.
- The Dynamic Column's
key
will be used as each column'ssortKey
. - Changes to the active sort are handled with three
@Output()
bindings. In order to use sort functionality, use bindingsactiveSortKeyChange
,activeSortDirectionChange
andsortChange
. - Two output bindings,
activeSortKeyChange
andactiveSortDirectionChange
, can be used with two-way bindings to simplify implementation.
Sort change fires once per change to sort, and includes sortKey
and sortDirection
. The $event
type is SortTuple
. It's usage is similar to the other bindings.
In these examples, notice that activeSortKey
and activeSortDirection
are not using two-way binding.
In your template...
<porcelain-dynamic-header
[(columns)]="activeColumns"
[activeSortKey]="activeSortKey"
[activeSortDirection]="activeSortDirection"
(sortChange)="updateSort($event)"
></porcelain-dynamic-header>
In your controller...
import { DynamicColumn, SortTuple, SortDirection } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
}
/** ... **/
];
activeSortKey: string = null;
activeSortDirection: SortDirection = null;
updateSort([activeSortKey, activeSortDirection]: SortTuple): void {
this.activeSortKey = activeSortKey;
this.activeSortDirection = activeSortDirection;
this.updateList();
}
updateList() {
this.listService.getList(this.activeSortKey, this.activeSortDirection).subscribe(updatedList => {
this.list = updatedList;
});
}
}
Although most cases will probably use (onSort)
bindings, it is possible to bind to sortKey and sortDirection changes separately.
Whenever the sort column key changes, this output will receive an update.
In your template...
<porcelain-dynamic-header
[activeSortKey]="activeSortKey"
(activeSortKeyChange)="updateActiveSortKey($event)"
></porcelain-dynamic-header>
In your controller...
import { DynamicColumn } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
}
/** ... **/
];
activeSortKey: string = null;
updateActiveSortKey(activeSortKey: string): void {
this.activeSortKey = activeSortKey;
this.updateList();
}
updateList() {
this.listService.getList(this.activeSortKey).subscribe(updatedList => {
this.list = updatedList;
});
}
}
Whenever the active sort direction changes, this output will receive an update.
In your template...
<porcelain-dynamic-header
[activeSortDirection]="activeSortDirection"
(activeSortDirectionChange)="updateActiveSortDirection($event)"
></porcelain-dynamic-header>
In your controller...
import { DynamicColumn, SortDirection } from '@my-ul/ng-porcelain';
@Component(/* ... */)
export class MyComponent {
activeColumns: DynamicColumn[] = [
{
label: 'Description',
key: 'description',
type: 'text',
locked: false,
width: 0.5
}
/** ... **/
];
activeSortKey: string = null;
updateActiveSortKey(activeSortKey: string): void {
this.activeSortKey = activeSortKey;
// updateActiveSortDirection also calls this
// this may result in two calls to updateList()
// consider binding to (sortChange) instead.
this.updateList();
}
activeSortDirection: SortDirection = null;
updateActiveSortDirection(activeSortDirection: SortDirection): void {
this.activeSortDirection = activeSortDirection;
// updateActiveSortKey also calls this
// this may result in two calls to updateList()
// consider binding to (sortChange) instead.
this.updateList();
}
updateList() {
this.listService.getList(this.activeSortKey, this.activeSortDirection).subscribe(updatedList => {
this.list = updatedList;
});
}
}
You may notice that when using the split bindings for activeSortDirection
and activeSortKey
, it becomes difficult to sanely trigger updates. Commonly, both of these may fire at once, which might cause excessive API calls. You might consider using a throttle/debounce to ensure that these aren't called too quickly, but these will rely on arbitrary timeouts.
For most cases, it is most sensible to use the (sortChange)
output binding, as it emits a tuple containing both updated activeSortKey
and activeSortDirection
values. Implementations using (sortChange)
do not need to rely on arbitrary timeouts (such as throttle/debounce). As an added benefit, there is typically less code needed for this implementation.
It is possible to use two-way binding for activeSortKey and activeSortDirection. In most cases, you will need to use the (sortChange) output to trigger a list reload, so two-way binding, for most cases, is not recommended.
In your template...
<porcelain-dynamic-header
[(columns)]="activeColumns"
[(activeSortKey)]="activeSortKey"
[(activeSortDirection)]="activeSortDirection"
(sortChange)="updateSort($event)"
></porcelain-dynamic-header>
In your controller...
export class MyController {
activeSortKey: string = null;
activeSortDirection: SortDirection = null;
updateSort(sort: SortTuple) {
// activeSortKey and activeSortDirection
// are assumed to be updated, but this isn't
// a guarantee. The `sort` argument IS
// guaranteed to be most-recent.
this.updateList();
}
}
In addition to offering controls to sort, the Search Header can be toggled to offer keyword search for values in a column. These values can perform in-memory searches or reach out to an API for a more exhaustive search.
Since the Search Header offers support for Sort, it offers the same event bindings as the Sort Header, but also offers two Angular @Output
bindings to support searching.
- The Search header offers a toggle to switch between Search and Sort functionality.
- The Sort outputs of the Search Header are bound using the Sort Header API. Your application will need to bind an adequate combination of
[activeSortKey]
,[activeSortDirection]
,(activeSortKeyChange)
,(activeSortDirectionChange)
, and(sortChange)
in addition to search bindings. - Most sort cases can be supported with just
[activeSortKey]
,[activeSortDirection]
and(sortChange)
. - Search functionality provides two-way binding for the query, as well as @Output bindings for the search submit and search clear events.
The search query field supports two-way binding. This will keep your application up-to-date with internal state of the search input.
In your template...
<porcelain-dynamic-header [(query)]="searchQuery"></porcelain-dynamic-header>
In your controller...
export class MyController {
searchQuery: string = '';
}
If you do not want to use the two-way binding syntax for the search query, you can split it.
The (queryChange)
binding will be called with every keystroke, so do not bind it to any expensive operations, such as an API call. If you are building live search functionality, you may benefit from rxjs debounce.
In your template...
<porcelain-dynamic-header
[query]="searchQuery"
(queryChange)="queryChanged($event)"
></porcelain-dynamic-header>
In your controller...
export class MyController {
searchQuery: string = '';
queryChanged(searchQuery: string) {
if (this.searchQuery !== searchQuery) {
this.searchQuery = searchQuery;
}
}
}
Some applications of the Search Header might want semi-live updating of search results. To safely implement this, using rxjs debounceTime
and throttleTime
may help.
The debounceTime
operator will emit the last value over a specified interval, whereas the throttleTime
operator will emit the first value and wait the specified interval before emitting again.
In your template...
<porcelain-dynamic-header
[query]="searchQuery"
(queryChange)="queryChanged($event)"
></porcelain-dynamic-header>
In your controller...
import { Subject } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
class Widget {
widget_id?: number;
name: string;
}
export class MyController {
query$: Subject<string> = new Subject();
results: any[] = null;
constructor(public widgetService: WidgetService) {
// Use a debounced observable to slow down queries
this.query$
.pipe(
// after 200 ms, take the last update
debounceTime(200),
// turn this observable into another
map(query => this.search(query))
)
.subscribe(results => (this.results = results));
}
queryChanged(query: string) {
this.query$.next(query);
}
search(query: string): Observable<Widget[]> {
return this.widgetService.search(query);
}
}
The queryChange Output will fire when a new search query is available. The emitted $event
is a dictionary containing column search values. The dictionary will be keyed with the column.key
property. If a user clears a search input, the key will be removed from the dictionary.
Take the following column, for example.
let column = {
label: 'Description',
key: 'description',
locked: false,
type: 'search',
width: 1 / 2
};
If a user typed "television" as a search query, the following object would be returned on the (queryChange)
output:
{
"description": "television"
}
You can do whatever you need to with this object. Commonly, you might want to lowercase or uppercase the value so that the string will be matched for any case.
In your template...
<porcelain-dynamic-header (queryChange)="search($event)"></porcelain-dynamic-header>
In your controller...
export class MyController {
searchQuery: string = '';
search(searchQuery: DynamicSearchQuery): void {
// use DynamicSearchQuery
}
}
The List Component is the outer-most wrapper for the List Component System
<porcelain-list>
<!-- children here -->
</porcelain-list>
The List Body Component wraps a series of items. Although there is no technical requirement to use List Item components (use any elements necessary), it is recommended. The List Body Component provides padding and provides spacing between List Item components.
<porcelain-list>
<porcelain-list-header>
<!-- porcelain-list-header-cell instances here -->
</porcelain-list-header>
<porcelain-list-body>
<!-- porcelain-list-item instances here -->
</porcelain-list-body>
</porcelain-list>
The List Header Component wraps a series of List Header Cells. The List Header Component can be made sticky using the @w11k/angular-sticky-things library.
<porcelain-list>
<porcelain-list-header>
<!-- porcelain-list-header-cell instances here -->
</porcelain-list-header>
</porcelain-list>
The List Header Cell Component is used to define table headers for a List. This is considered a container.
The width
input is mandatory, and is a number between zero and one. For all list-header-cell components in a list-header, the total of the "width" inputs should be 1
.
<porcelain-list>
<porcelain-list-header>
<porcelain-list-header-cell [width]="2 / 3">
<!-- Header here -->
</porcelain-list-header-cell>
<porcelain-list-header-cell [width]="1 / 3">
<!-- Header here -->
</porcelain-list-header-cell>
</porcelain-list-header>
</porcelain-list>
The List Item Component is used to markup the display of a series of items. Since it is designed to be a single atomic instance of an item, it is designed to be used with the *ngFor
directive.
The List Item component is responsible for drawing borders between List Item Cell components.
Property | Type | Description |
---|---|---|
[success] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the list item will be set to UL Medium Green. |
[error] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Medium Red. |
[warning] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Medium Yellow. |
[primary] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Action Blue. |
[secondary] |
boolean | When set to true , a 3 pixel left border and .text-accent elements within the List Item will have their color set to UL Medium Teal. |
<porcelain-list>
<porcelain-list-header>
<!-- porcelain-list-header-cell instances here -->
</porcelain-list-header>
<porcelain-list-body>
<porcelain-list-item>
<!-- porcelain-list-item-cell instances here -->
</porcelain-list-item>
</porcelain-list-body>
</porcelain-list>
The List Item Cell defines a cell within a List Item. This typically corresponds to a property within an entity that is being looped.
Property | Type | Description |
---|---|---|
[width] |
number |
A number between zero and one. All cells within a List Item component should have [width] inputs that sum to 1. |
[alignTop] |
boolean |
When true , the contents of the cells will be at the top of the row of cells. Cells are middle aligned by default. |
[alignBottom] |
boolean |
When true , the contents of the cells will be at the bottom of the row of cells. Cells are middle aligned by default. |
[padAll] |
boolean |
When true , the contents of the cell will be wrapped with ~20px of spacing. Default false . |
[padTop] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the top of the cell. Default false . |
[padRight] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the right of the cell. Default false . |
[padBottom] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the bottom of the cell. Default false . |
[padLeft] |
boolean |
When true , the contents of the cell will contain ~20px of padding at the left of the cell. Default false . |
<porcelain-list-item>
<porcelain-list-item-cell [width]="1/5" [padAll]="true">
{{ item.price | currency : 'USD' }}
</porcelain-list-item-cell>
</porcelain-list-item>
The Search Header offers search and sort functionality.
The Sort Header can be used to control sort variables within a list view.
This component supports banana-box/two-way binding notation for updating the values of activeSortKey
and activeSortDirection
.
Property | Type | Description |
---|---|---|
[label] |
string |
User-visible label shown in the clickable portion of the sort header. |
[sortKey] |
string |
Sets the key that this sort header is responsible for toggling. When equal to activeSortKey are equal, the component will be in the active state. Typically, this is a constant value, wrapped in single quotes, but it can be a variable, which is useful for loops. This value should NOT change often, if ever, after the component loads. |
<porcelain-sort-header [label]=" 'Date Modified' " [sortKey]=" 'date' "></porcelain-sort-header>
The (onSortChange) output will emit the sortKey
and the sortDirection
in a tuple: [sortKey, sortDirection]
. Use this to trigger any page load updates, as binding to the activeSortKeyChange
and activeSortColumnChange
may cause any bound actions to execute sporadically (twice in some cases, or not at all in others).
<porcelain-sort-header
(onSortChange)=" updateList() "
[(activeSortKey)]=" currentSortKey "
[(activeSortDirection)]=" currentSortDirection "
></porcelain-sort-header>
The following code pattern can be used to refresh a list after the sort fields are modified.
@Component({
// ...
})
export class MyComponent {
public currentSortKey;
public currentSortDirection;
constructor(public listService: ListService) {
this.resetSort();
}
resetSort() {
this.currentSortKey = 'price';
this.currentSortDirection = 'asc';
}
updateList() {
this.listService
.getList({
sortKey: this.currentSortKey,
sortDirection: this.currentSortDirection
})
.subscribe(newListItems => {
this.items = newListItems;
});
}
}
Binds the current active sort column from your component. Can be split into @Input
/@Output
bindings if you need a callback, although this is not recommended in favor of (onSortChange)
, which fires a callback once per toggle.
Set to the application's current sort column. When equal to [sortKey]
, the component will be in the active
state.
<porcelain-sort-header [(activeSortKey)]=" currentSortColumn "></porcelain-sort-header>
Set this to your application's current sort column. If [sortKey]
and [activeSortKey]
are equal with a valid [activeSortDirection]
, the header will appear active.
Valid values are null
, 'asc'
and 'desc'
.
Fires whenever the [activeSortKey]
is set to [sortKey]
. Value can be accessed with $event
.
<porcelain-sort-header (activeSortKeyChange)=" handleSortKeyChange($event) "></porcelain-sort-header>
Use two-way binding to keep the sort-header and your application's sort column state in sync.
<porcelain-sort-header [(activeSortDirection)]=" myCurrentSortDirection "></porcelain-sort-header>
Set the activeSortDirection
from your application.
<porcelain-sort-header [activeSortDirection]=" myCurrentSortDirection "></porcelain-sort-header>
Bind a callback that will fire upon changes to activeSortDirection
. $event
will contain the new sort direction.
<porcelain-sort-header (activeSortDirectionChange)=" handleSortDirectionChange($event)">
</porcelain-sort-header>
The Text Header component will render its contents in a properly padded header cell.
<porcelain-text-header>Description</porcelain-text-header>
- Refactored tools to use
oclif
, which can compile TS files on demand, which takes the compile steps out of the package.json. - Added image link capability to the build-readme tool. Images are now resolved and copied to an output directory.
- Added
get-latest-version
tool, which is used to resolve the 'latest' version for npm. - Fixed the scss styling for shorthand selectors.
- Search Refiner fixes
- Added Expando module
- Added Skeletons Module
- Added Search Refiner
- Updated border colors
- Added Skeleton Docs
- Replaced expanding functionality in refiners with ExpandoComponent
- Added new shorthands... use
p-xxx
instead ofporcelain-xxx
if you want.
- Added Breadcrumb component system.
- Removed getter/setters from toolbar-select to improve performance by reducing redraws.
- Versioning has been modified to synchronize with Angular releases. Porcelain's major version number will now match the Angular version number. For example, Porcelain ^9.0.0 is designed to be compatible with Angular ^9.0.0
- Replaced peer dependency
mydatepicker
withangular-mydatepicker
. - Upgrade angular-fontawesome from
^0.3.0
to^0.6.1
. - Updated storybooks to use new Storybook formats and reorganized to take advantage of new hierarchy tools.
- Fixes type on interpolate pipe
- Fixes off-by-one error with custom date-picker values.
- Renames
*Refiner
classes to more-verbose*RefinerDefinition
. Implements shims that will be removed at 2.0.0 - Introduces
porcelain-search-input
to enable search clear/submit functionality.
- Fixes issues surrounding
shared
barrel type errors.
- Resolves missing peer dependency in nested package.json
- Introduces
porcelain-applicator
component to handle Apply/Reset functionality, with sticky scroll behavior. - Introduces
porcelain-footer
component to show UL copyright and legal notices. - Introduces
porcelain-spinner
component to show an appropriately-sized, consistent loading spinner.
- Empty handler additon and
porcelain-applicator
modification.