feat(aio): add attribute utils for code atty interpretation.
These utils support flexible, natural attribute interpretation as applied to code-example and code-pane. Then apply those utils to code-example and live-example
This commit is contained in:
parent
8760bf7be4
commit
673d8ae583
|
@ -1,5 +1,6 @@
|
|||
/* tslint:disable component-selector */
|
||||
import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
import { getBoolFromAttribute } from 'app/shared/attribute-utils';
|
||||
|
||||
/**
|
||||
* An embeddable code block that displays nicely formatted code.
|
||||
|
@ -38,7 +39,7 @@ export class CodeExampleComponent implements OnInit {
|
|||
this.path = element.getAttribute('path') || '';
|
||||
this.region = element.getAttribute('region') || '';
|
||||
this.title = element.getAttribute('title') || '';
|
||||
this.hideCopy = element.getAttribute('hideCopy') === 'true';
|
||||
this.hideCopy = getBoolFromAttribute(element, ['hidecopy', 'hide-copy']);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import { Component, ElementRef, HostListener, Input, OnInit, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
||||
|
||||
const defaultPlnkrImg = 'plunker/placeholder.png';
|
||||
const imageBase = 'content/images/';
|
||||
const liveExampleBase = 'content/live-examples/';
|
||||
|
@ -87,7 +89,7 @@ export class LiveExampleComponent implements OnInit {
|
|||
private elementRef: ElementRef,
|
||||
location: Location ) {
|
||||
|
||||
const attrs = this.attrs = this.getAttrs();
|
||||
const attrs = this.attrs = getAttrs(this.elementRef);
|
||||
let exampleDir = attrs.name;
|
||||
if (!exampleDir) {
|
||||
// take last segment, excluding hash fragment and query params
|
||||
|
@ -98,12 +100,11 @@ export class LiveExampleComponent implements OnInit {
|
|||
this.plnkrName = attrs.plnkr ? attrs.plnkr.trim() + '.' : '';
|
||||
this.zip = `${zipBase}${exampleDir}/${this.plnkrName}${this.zipName}.zip`;
|
||||
|
||||
const noDownload = this.getAttrValue(['noDownload', 'nodownload']); // noDownload aliases
|
||||
this.enableDownload = !boolFromAtty(noDownload);
|
||||
this.enableDownload = !boolFromValue(getAttrValue(attrs, 'nodownload'));
|
||||
|
||||
this.plnkrImg = imageBase + (attrs.img || defaultPlnkrImg);
|
||||
|
||||
if (boolFromAtty(this.getAttrValue(['downloadOnly', 'downloadonly']))) {
|
||||
if (boolFromValue(getAttrValue(attrs, 'downloadonly'))) {
|
||||
this.mode = 'downloadOnly';
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +117,7 @@ export class LiveExampleComponent implements OnInit {
|
|||
let plnkrStyle = 'eplnkr'; // embedded style by default
|
||||
this.mode = 'default'; // display in another browser tab by default
|
||||
|
||||
this.isEmbedded = boolFromAtty(attrs.embedded);
|
||||
this.isEmbedded = boolFromValue(attrs.embedded);
|
||||
|
||||
if (this.isEmbedded) {
|
||||
this.mode = 'embedded'; // display embedded in the doc
|
||||
|
@ -126,11 +127,11 @@ export class LiveExampleComponent implements OnInit {
|
|||
// If wide enough, choose style based on style attributes
|
||||
if (width > this.narrowWidth) {
|
||||
// Make flat style with `flat-style` or `embedded-style="false`; support atty aliases
|
||||
const flatStyle = this.getAttrValue(['flat-style', 'flatstyle', 'flatStyle']);
|
||||
const isFlatStyle = boolFromAtty(flatStyle);
|
||||
const flatStyle = getAttrValue(attrs, ['flat-style', 'flatstyle']);
|
||||
const isFlatStyle = boolFromValue(flatStyle);
|
||||
|
||||
const embeddedStyle = this.getAttrValue(['embedded-style', 'embeddedstyle', 'embeddedStyle']);
|
||||
const isEmbeddedStyle = boolFromAtty(embeddedStyle, !isFlatStyle);
|
||||
const embeddedStyle = getAttrValue(attrs, ['embedded-style', 'embeddedstyle']);
|
||||
const isEmbeddedStyle = boolFromValue(embeddedStyle, !isFlatStyle);
|
||||
plnkrStyle = isEmbeddedStyle ? 'eplnkr' : 'plnkr';
|
||||
}
|
||||
}
|
||||
|
@ -138,17 +139,6 @@ export class LiveExampleComponent implements OnInit {
|
|||
this.plnkr = `${liveExampleBase}${exampleDir}/${this.plnkrName}${plnkrStyle}.html`;
|
||||
}
|
||||
|
||||
getAttrs(): any {
|
||||
const attrs = this.elementRef.nativeElement.attributes;
|
||||
const attrMap = {};
|
||||
Object.keys(attrs).forEach(key => attrMap[attrs[key].name] = attrs[key].value);
|
||||
return attrMap;
|
||||
}
|
||||
|
||||
getAttrValue(atty: string | string[]) {
|
||||
return this.attrs[typeof atty === 'string' ? atty : atty.find(a => this.attrs[a] !== undefined)];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// The `liveExampleContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
|
@ -168,11 +158,6 @@ export class LiveExampleComponent implements OnInit {
|
|||
toggleEmbedded () { this.showEmbedded = !this.showEmbedded; }
|
||||
}
|
||||
|
||||
function boolFromAtty(atty: string , def: boolean = false) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
return atty == undefined ? def : atty.trim() !== 'false';
|
||||
}
|
||||
|
||||
///// EmbeddedPlunkerComponent ///
|
||||
/**
|
||||
* Hides the <iframe> so we can test LiveExampleComponent without actually triggering
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
import { ElementRef } from '@angular/core';
|
||||
|
||||
import { getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
|
||||
describe('Attribute Utilities', () => {
|
||||
let testEl: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = `<div a b="true" c="false" D="foo" d-E></div>`;
|
||||
testEl = div.querySelector('div');
|
||||
});
|
||||
|
||||
describe('#getAttrs', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
this.expectedMap = {
|
||||
a: '',
|
||||
b: 'true',
|
||||
c: 'false',
|
||||
d: 'foo',
|
||||
'd-e': ''
|
||||
};
|
||||
});
|
||||
|
||||
it('should get attr map from getAttrs(element)', () => {
|
||||
const actual = getAttrs(testEl);
|
||||
expect(actual).toEqual(this.expectedMap);
|
||||
});
|
||||
|
||||
it('should get attr map from getAttrs(elementRef)', () => {
|
||||
const actual = getAttrs(new ElementRef(testEl));
|
||||
expect(actual).toEqual(this.expectedMap);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAttrValue', () => {
|
||||
let attrMap: { [index: string]: string };
|
||||
|
||||
beforeEach(() => {
|
||||
attrMap = getAttrs(testEl);
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "a"', () => {
|
||||
expect(getAttrValue(attrMap, 'a')).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "A"', () => {
|
||||
expect(getAttrValue(attrMap, 'a')).toBe('');
|
||||
});
|
||||
|
||||
it('should return "true" for attribute "b"', () => {
|
||||
expect(getAttrValue(attrMap, 'b')).toBe('true');
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "d-E"', () => {
|
||||
expect(getAttrValue(attrMap, 'd-e')).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string for attribute ["d-e"]', () => {
|
||||
// because d-e will be found before d
|
||||
expect(getAttrValue(attrMap, ['d-e'])).toBe('');
|
||||
});
|
||||
|
||||
it('should return "foo" for attribute ["d", "d-e"]', () => {
|
||||
// because d will be found before d-e
|
||||
expect(getAttrValue(attrMap, ['d', 'd-e'])).toBe('foo');
|
||||
});
|
||||
|
||||
it('should return empty string for attribute ["d-e", "d"]', () => {
|
||||
// because d-e will be found before d
|
||||
expect(getAttrValue(attrMap, ['d-e', 'd'])).toBe('');
|
||||
});
|
||||
|
||||
it('should return undefined value for non-existent attribute "x"', () => {
|
||||
expect(getAttrValue(attrMap, 'x')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if no argument', () => {
|
||||
expect(getAttrValue(attrMap)).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#boolFromValue', () => {
|
||||
let attrMap: { [index: string]: string };
|
||||
|
||||
beforeEach(() => {
|
||||
attrMap = getAttrs(testEl);
|
||||
});
|
||||
|
||||
it('should return true for present but unassigned attr "a"', () => {
|
||||
expect(boolFromValue(getAttrValue(attrMap, 'a'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for attr "b" which is "true"', () => {
|
||||
expect(boolFromValue(getAttrValue(attrMap, 'b'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for attr "c" which is "false"', () => {
|
||||
expect(boolFromValue(getAttrValue(attrMap, 'c'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false by default for undefined attr "x"', () => {
|
||||
expect(boolFromValue(getAttrValue(attrMap, 'x'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for undefined attr "x" when default is true', () => {
|
||||
const value = getAttrValue(attrMap, 'x');
|
||||
expect(boolFromValue(value, true)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for undefined attr "x" when default is false', () => {
|
||||
const value = getAttrValue(attrMap, 'x');
|
||||
expect(boolFromValue(value, false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for present but unassigned attr "a" even when default is false', () => {
|
||||
// default value is only applied when the attribute is missing
|
||||
const value = getAttrValue(attrMap, 'a');
|
||||
expect(boolFromValue(value, false)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// Combines the three utilities for convenience.
|
||||
describe('#getBoolFromAttribute', () => {
|
||||
it('should return true for present but unassigned attr "a"', () => {
|
||||
expect(getBoolFromAttribute(testEl, 'a')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for attr "b" which is "true"', () => {
|
||||
expect(getBoolFromAttribute(testEl, 'b')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for attr "c" which is "false"', () => {
|
||||
expect(getBoolFromAttribute(testEl, 'c')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for attributes ["d-e", "d"]', () => {
|
||||
// because d-e will be found before D="foo"
|
||||
expect(getBoolFromAttribute(testEl, ['d-e', 'd'])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-existent attribute "x"', () => {
|
||||
expect(getBoolFromAttribute(testEl, 'x')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
// Utilities for processing HTML element attributes
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
interface StringMap { [index: string]: string; }
|
||||
|
||||
/**
|
||||
* Get attribute map from element or ElementRef `attributes`.
|
||||
* Attribute map keys are forced lowercase for case-insensitive lookup.
|
||||
* @param el The source of the attributes
|
||||
*/
|
||||
export function getAttrs(el: HTMLElement | ElementRef): StringMap {
|
||||
const attrs: NamedNodeMap = el instanceof ElementRef ? el.nativeElement.attributes : el.attributes;
|
||||
const attrMap = {};
|
||||
Object.keys(attrs).forEach(key =>
|
||||
attrMap[(attrs[key] as Attr).name.toLowerCase()] = (attrs[key] as Attr).value);
|
||||
return attrMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attribute that matches `atty`.
|
||||
* @param atty Name of the attribute or a string of candidate attribute names
|
||||
*/
|
||||
export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): string {
|
||||
return attrs[typeof attr === 'string' ? attr : attr.find(a => attrs[a] !== undefined)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of an attribute value (if supplied)
|
||||
* @param attyValue The string value of some attribute (or undefined if attribute not present)
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function boolFromValue(attrValue: string, def: boolean = false) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
return attrValue == undefined ? def : attrValue.trim() !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of attribute from an element
|
||||
* @param el The source of the attributes
|
||||
* @param atty Name of the attribute or a string of candidate attribute names
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function getBoolFromAttribute(
|
||||
el: HTMLElement | ElementRef,
|
||||
attr: string | string[],
|
||||
def: boolean = false): boolean {
|
||||
return boolFromValue(getAttrValue(getAttrs(el), attr), def);
|
||||
}
|
Loading…
Reference in New Issue