-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore (demo app) : add hyperview filter example (#928)
asana: https://app.asana.com/0/1204008699308084/1208057868583175/f add hyperview filter example to advanced tab -> custom components dependent on: #927 --------- Co-authored-by: Florent Bonomo <[email protected]> Co-authored-by: flochtililoch <[email protected]>
- Loading branch information
1 parent
c0073ec
commit 4a64cce
Showing
3 changed files
with
238 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import * as Logging from 'hyperview/src/services/logging'; | ||
import type { HvComponentProps, LocalName } from 'hyperview'; | ||
import Hyperview, { | ||
Events, | ||
LOCAL_NAME, | ||
NODE_TYPE, | ||
Namespaces, | ||
} from 'hyperview'; | ||
import { useEffect } from 'react'; | ||
|
||
type FormDataPart = { | ||
fieldName: string; | ||
string: string; | ||
}; | ||
|
||
declare class FormData { | ||
getParts(): Array<FormDataPart>; | ||
} | ||
const FILTER_NS = 'https://instawork.com/hyperview-filter'; | ||
|
||
export const findElements = (node: Element, attributeNames: string[]) => { | ||
if (node.nodeType !== NODE_TYPE.ELEMENT_NODE) { | ||
return []; | ||
} | ||
|
||
if ( | ||
attributeNames.reduce( | ||
(found, name) => found || !!node.getAttributeNS(FILTER_NS, name), | ||
false, | ||
) | ||
) { | ||
return [node]; | ||
} | ||
|
||
return (Array.from(node.childNodes) as Element[]) | ||
.filter((child: Node | null) => { | ||
return child !== null && child.nodeType === NODE_TYPE.ELEMENT_NODE; | ||
}) | ||
.reduce((elements: Element[], child: Element) => { | ||
elements.push(...findElements(child, attributeNames)); | ||
return elements; | ||
}, []); | ||
}; | ||
|
||
const HyperviewFilter = (props: HvComponentProps) => { | ||
const onEventDispatch = (eventName: string) => { | ||
const filterEvent = | ||
props.element.getAttributeNS(FILTER_NS, 'on-event') || ''; | ||
if (filterEvent !== eventName) { | ||
return; | ||
} | ||
const filterParam = props.element.getAttributeNS(FILTER_NS, 'on-param'); | ||
if (!filterParam) { | ||
return; | ||
} | ||
|
||
const transformFlags: string[] = ( | ||
props.element.getAttributeNS(FILTER_NS, 'transform') || '' | ||
).split(','); | ||
const forceLowerCase: boolean = transformFlags.includes('lowercase'); | ||
|
||
const formData: FormData | null = props.options.componentRegistry | ||
? ((props.options.componentRegistry.getFormData( | ||
props.element, | ||
) as unknown) as FormData) | ||
: null; | ||
|
||
const formPart: FormDataPart | undefined = formData | ||
?.getParts() | ||
.find(part => part.fieldName === filterParam); | ||
const filterTerm = formPart ? formPart.string : ''; | ||
const transformedFilterTerm = forceLowerCase | ||
? filterTerm.toLowerCase() | ||
: filterTerm; | ||
|
||
// Hide/show each element with filter terms or matching given regex. Modify attributes in-place | ||
const filterElements: Element[] = findElements(props.element, [ | ||
'terms', | ||
'regex', | ||
]); | ||
filterElements.forEach((element: Element) => { | ||
const terms: string = element.getAttributeNS(FILTER_NS, 'terms') || ''; | ||
const regex: string | null = | ||
element.getAttributeNS(FILTER_NS, 'regex') || null; | ||
const termsArray: string[] = terms | ||
.split(',') | ||
.map((term: string) => (forceLowerCase ? term.toLowerCase() : term)); | ||
const showElement = (): boolean => { | ||
if (regex) { | ||
return new RegExp(regex).test(filterTerm); | ||
} | ||
return termsArray.some((term: string) => | ||
term.startsWith(transformedFilterTerm), | ||
); | ||
}; | ||
element.setAttribute('hide', String(!showElement())); | ||
}); | ||
|
||
// Clone/swap the entire element to swap in the visibility changes | ||
const newElement = props.element.cloneNode(true) as Element; | ||
props.onUpdate(null, 'swap', props.element, { newElement }); | ||
|
||
// Set elements that need to render the filter term | ||
findElements(newElement, ['role']).forEach((element: Element) => { | ||
if (element.getAttributeNS(FILTER_NS, 'role') === 'filter-terms') { | ||
if ( | ||
element.namespaceURI === Namespaces.HYPERVIEW && | ||
element.localName !== LOCAL_NAME.TEXT | ||
) { | ||
Logging.error( | ||
'Element with attribute `role="filter-terms"` should be a <text> element or a custom element', | ||
); | ||
return; | ||
} | ||
const newRoleElement = element.cloneNode(true) as Element; | ||
newRoleElement.textContent = filterTerm; | ||
props.onUpdate(null, 'swap', element, { newElement: newRoleElement }); | ||
} | ||
}); | ||
}; | ||
|
||
useEffect(() => { | ||
Events.subscribe(onEventDispatch); | ||
return () => { | ||
Events.unsubscribe(onEventDispatch); | ||
}; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [props.element]); | ||
|
||
return Hyperview.renderChildren( | ||
props.element, | ||
props.stylesheets, | ||
props.onUpdate, | ||
props.options, | ||
); | ||
}; | ||
|
||
HyperviewFilter.namespaceURI = FILTER_NS; | ||
HyperviewFilter.localName = 'container' as LocalName; | ||
HyperviewFilter.localNameAliases = [] as LocalName[]; | ||
|
||
export { HyperviewFilter }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { HyperviewFilter } from './HyperviewFilter'; | ||
import { HyperviewSvg } from './HyperviewSvg'; | ||
|
||
export default [HyperviewSvg]; | ||
export default [HyperviewSvg, HyperviewFilter]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
--- | ||
permalink: "/examples/advanced/custom-components/filter.xml" | ||
tags: custom_components | ||
hv_button_behavior: "back" | ||
hv_title: "Filter" | ||
--- | ||
{% extends 'templates/base.xml.njk' %} | ||
|
||
{% block custom_ns %} | ||
xmlns:filter="https://instawork.com/hyperview-filter" | ||
{% endblock %} | ||
|
||
{% block styles %} | ||
<style id="item" borderWidth="1" borderColor="#BDC4C4" marginHorizontal="24" padding="16" /> | ||
<style id="item-label" fontSize="16" /> | ||
<style id="input" fontSize="16" borderWidth="1" borderColor="#BDC4C4" padding="16" /> | ||
{% endblock %} | ||
{% block container %} | ||
<form> | ||
<filter:container filter:on-event="search" filter:on-param="search-term" filter:transform="lowercase"> | ||
<list key="list"> | ||
<item key="field" style="item"> | ||
<text-field name="search-term" style="input" auto-focus="true" placeholder="Search"> | ||
<behavior trigger="change" action="dispatch-event" event-name="search" /> | ||
</text-field> | ||
</item> | ||
<item key="1" style="item" filter:terms="Reagan Bennett,Reagan,Bennett,San Francisco,San,Francisco"> | ||
<text style="item-label">Reagan Bennett</text> | ||
<text>San Francisco</text> | ||
</item> | ||
<item key="2" style="item" filter:terms="Vicente Savage,Vicente,Savage,San,Jose,San Jose"> | ||
<text style="item-label">Vicente Savage</text> | ||
<text>San Jose</text> | ||
</item> | ||
<item key="3" style="item" filter:terms="Alanna,Powers,Burlingame"> | ||
<text style="item-label">Alanna Powers</text> | ||
<text>Burlingame</text> | ||
</item> | ||
<item key="4" style="item" filter:terms="Pablo,Hester"> | ||
<text style="item-label">Pablo Hester</text> | ||
</item> | ||
<item key="5" style="item" filter:terms="Sierra,Murphy"> | ||
<text style="item-label">Sierra Murphy</text> | ||
</item> | ||
<item key="6" style="item" filter:terms="Clark,Dawson"> | ||
<text style="item-label">Clark Dawson</text> | ||
</item> | ||
<item key="7" style="item" filter:terms="Marilyn,Reyes"> | ||
<text style="item-label">Marilyn Reyes</text> | ||
</item> | ||
<item key="8" style="item" filter:terms="Jerry,Wright"> | ||
<text style="item-label">Jerry Wright</text> | ||
</item> | ||
<item key="9" style="item" filter:terms="Madison,Peralta"> | ||
<text style="item-label">Madison Peralta</text> | ||
</item> | ||
<item key="10" style="item" filter:terms="Santos,Nelson"> | ||
<text style="item-label">Santos Nelson</text> | ||
</item> | ||
<item key="11" style="item" filter:terms="Nola,Woods"> | ||
<text style="item-label">Nola Woods</text> | ||
</item> | ||
<item key="12" style="item" filter:terms="Jeremy,Morton"> | ||
<text style="item-label">Jeremy Morton</text> | ||
</item> | ||
<item key="13" style="item" filter:terms="Ryann,Fowler"> | ||
<text style="item-label">Ryann Fowler</text> | ||
</item> | ||
<item key="14" style="item" filter:terms="Alfred,Santiago"> | ||
<text style="item-label">Alfred Santiago</text> | ||
</item> | ||
<item key="15" style="item" filter:terms="Rebekah,Acosta"> | ||
<text style="item-label">Rebekah Acosta</text> | ||
</item> | ||
<item key="16" style="item" filter:terms="Tobias,Russell"> | ||
<text style="item-label">Tobias Russell</text> | ||
</item> | ||
<item key="17" style="item" filter:terms="Harmony,Rocha"> | ||
<text style="item-label">Harmony Rocha</text> | ||
</item> | ||
<item key="18" style="item" filter:terms="Shawn,Bond"> | ||
<text style="item-label">Shawn Bond</text> | ||
</item> | ||
<item key="19" style="item" filter:terms="Malaysia,Campos"> | ||
<text style="item-label">Malaysia Campos</text> | ||
</item> | ||
<item key="20" style="item" filter:terms="Barrett,Li"> | ||
<text style="item-label">Barrett Li</text> | ||
</item> | ||
</list> | ||
</filter:container> | ||
</form> | ||
{% endblock %} |