-
Notifications
You must be signed in to change notification settings - Fork 7
MARKET-1466 Cascade Select #184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
fba6398
c374578
8314f6e
3bf0e3e
022fb51
95a4a7d
c1ffbc2
17372be
998d639
adbe54c
b5e1786
dc27d5d
557a11b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Cascade Select | ||
|
|
||
| Cascade Select is a component of Backendless UI-Builder designer. This allows you to select a value from a nested structure of options. | ||
|
|
||
| The component based on external [Cascade Select](https://www.primefaces.org/primereact/cascadeselect/). | ||
|
|
||
| ## Properties | ||
|
|
||
| | Property | Type | Default Value | Logic | Data Binding | UI Setting | Description | | ||
| |-------------------|---------|---------------------|-----------------------------|--------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| | Cascade | JSON | | Cascade Logic | YES | YES | Allows determinate an array of select items to display as the available options. Watch [Codeless Examples](#Examples). Signature of polygon: `[{name, code, ?children}]` | | ||
| | Placeholder | String | | Placeholder Logic | YES | YES | Allows determinate placeholder for input | | ||
|
|
||
| ## Events | ||
|
|
||
| | Name | Triggers | Context Blocks | | ||
| |---------------|-------------------------------|--------------------------------------------------------------| | ||
| | On Click Item | when the user select the item | Item: `{name: String, code: String, levelOfNesting: Number}` | | ||
|
|
||
| ## Action | ||
|
|
||
| | Action | Inputs | Return | | ||
| |---------------|--------|--------------------------| | ||
| | Get Select in | | Object: of a select item | | ||
|
||
|
|
||
| ## <a name="Examples"></a> Codeless Examples | ||
|
|
||
| Addition of cascade data | ||
|
|
||
|  | ||
|
|
||
| <details> | ||
| <summary>Try yourself</summary> | ||
|
|
||
| ``` | ||
| <block xmlns="http://www.w3.org/1999/xhtml" type="lists_create_with" id="I6`{YbX`1w)ZrZA[n(3l" x="-94.53923425078003" y="88.92089374100392"><mutation items="2"></mutation><value name="ADD0"><block type="create_object" id="hd^`S({p+5%(tCzQMIkl"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item><item id="property" prop-name="children"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="Rmz=y,*(iJ0^*7tqt^wN"><field name="TEXT">Australia</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="73(!S~9Bj8[(1dd.hOG%"><field name="TEXT">AU</field></block></value><value name="create_object_mutator_container_properties_stack_property2"><block type="lists_create_with" id="+^[pE!*BrEq@/~$ZXJDM"><mutation items="2"></mutation><value name="ADD0"><block type="create_object" id="ely].XX{?.SFw}g*Ux$F"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item><item id="property" prop-name="children"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="0,Ub7#U7iP^Uc;F1W,l%"><field name="TEXT">New South Wales</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="w#9]c/AL5qytPDCGEGh^"><field name="TEXT">AU-NSW</field></block></value><value name="create_object_mutator_container_properties_stack_property2"><block type="lists_create_with" id="NcaOOw}XzebfYg[$#]*V"><mutation items="3"></mutation><value name="ADD0"><block type="create_object" id="=@e``]rkt/IwmHZaxEGU"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="e(%`Bzh$xiJ9{xFo2YL/"><field name="TEXT">Sydney</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="[.3Q:STX@sn9Lw}pm7Qc"><field name="TEXT">AU-NSW-SY</field></block></value></block></value><value name="ADD1"><block type="create_object" id="dTF-V$w2/}%bGhSyA%]Q"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="0WOJ4K22**Az9=mscXx7"><field name="TEXT">Newcastle</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="^D)=+TRJD8Hbb(X%qAy."><field name="TEXT">AU-NSW-NC</field></block></value></block></value><value name="ADD2"><block type="create_object" id="ZAL1i-YAD!U*bPcteuR!"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="101HuiZBCvUT)Q=k;c7O"><field name="TEXT">Wollongong</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="II{6/P]UldmqR84w=#yo"><field name="TEXT">AU-NSW-WG</field></block></value></block></value></block></value></block></value><value name="ADD1"><block type="create_object" id="23VyPHa2^%a{BnrD;:oc"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item><item id="property" prop-name="children"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="qdN:ohQ^xc{~Rsn:GAq+"><field name="TEXT">Queensland</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="UFX@h3$}X52c}@S^*p/^"><field name="TEXT">AU-QS</field></block></value><value name="create_object_mutator_container_properties_stack_property2"><block type="lists_create_with" id="@=LxHd02i?4?t+rW|64h"><mutation items="2"></mutation><value name="ADD0"><block type="create_object" id="%wf4cFQJ/qGl{7/Il2A$"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id=".Tn=|{GrFAU]w[=21lH."><field name="TEXT">Brisbane</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="e`w,YXw1(ceOTdI2j9+L"><field name="TEXT">AU-QS-BB</field></block></value></block></value><value name="ADD1"><block type="create_object" id="%rbhI}Sb!@Vue942V_W}"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="x4~`U{VoDZ3gMG`i3-Fg"><field name="TEXT">Townsville</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="v{y{ntd-%*4fjlFH4)=!"><field name="TEXT">AU-QS-TS</field></block></value></block></value></block></value></block></value></block></value></block></value><value name="ADD1"><block type="create_object" id="st!J)Cx*,C_Xx4E)5,,|"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item><item id="property" prop-name="children"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="(*uAjQ7(V_NLb#`mc)!s"><field name="TEXT">Canada</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="JZrB.YadV5v/APu_6/xL"><field name="TEXT">CA</field></block></value><value name="create_object_mutator_container_properties_stack_property2"><block type="lists_create_with" id="It-{1g[_kl})XaTbVnlK"><mutation items="2"></mutation><value name="ADD0"><block type="create_object" id="[email protected]+b]ipJ3dnDD+"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item><item id="property" prop-name="children"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="G7j*jnSN(Annx89J{Ko:"><field name="TEXT">Quebec</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="w[O4hm|+LN4`3kHsfo^v"><field name="TEXT">CA-QB</field></block></value><value name="create_object_mutator_container_properties_stack_property2"><block type="lists_create_with" id="h8Aa4cJAZ+Z2bTu;v=@j"><mutation items="2"></mutation><value name="ADD0"><block type="create_object" id="eKI@M]|TJAL_3O@|tV7}"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="}l+-hZaLgz)z,SSO~yE`"><field name="TEXT">Montreal</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="lpCB5?#F[vP_b{!`yDGK"><field name="TEXT">CA-QB-MR</field></block></value></block></value><value name="ADD1"><block type="create_object" id="^LaaQXj.d}=ii[.0=`~;"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="m0L`+S|g$=qv~fKFe(WF"><field name="TEXT">Quebec City</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="A;RO8hooP(x*Feh}~ohR"><field name="TEXT">CA-QB-CBC</field></block></value></block></value></block></value></block></value><value name="ADD1"><block type="create_object" id="zlA_eq+Dyde5(~QORUv0"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item><item id="property" prop-name="children"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="Yt7Xd-sMIFsp]0DrG*~D"><field name="TEXT">Ontario</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="~8l2Q5MC/Ko3KR:EJeEs"><field name="TEXT">CA-OT</field></block></value><value name="create_object_mutator_container_properties_stack_property2"><block type="lists_create_with" id="_GWtd^*`0F1VY`aNycML"><mutation items="2"></mutation><value name="ADD0"><block type="create_object" id="RO2Sy[E4,FQU#z$IuFvx"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="@P`$ZS$j6tVBtX_7*sqz"><field name="TEXT">Ottawa</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="%AxPNTGKb#_)r{welh5,"><field name="TEXT">CA-OT-OW</field></block></value></block></value><value name="ADD1"><block type="create_object" id="d|X]SW;eH:GO]Hi9ff(H"><mutation><properties><item id="property" prop-name="name"></item><item id="property" prop-name="code"></item></properties></mutation><value name="create_object_mutator_container_properties_stack_property0"><block type="text" id="yxz9yUj@6RM^$caT*;Y?"><field name="TEXT">Toronto</field></block></value><value name="create_object_mutator_container_properties_stack_property1"><block type="text" id="[Xw5938k[M2kk;lD_#Q5"><field name="TEXT">CA-OT-TR</field></block></value></block></value></block></value></block></value></block></value></block></value></block> | ||
v-excelsior marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ``` | ||
| </details> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| { | ||
| "id": "c_4709a015328b307e652d915fc3f36fb7", | ||
| "name": "Cascade Select", | ||
| "description": "Cascade Select is a component to select a value from a nested structure of options.", | ||
| "showInToolbox": true, | ||
| "faIcon": "check-double", | ||
| "mainJS": "dist/index.js", | ||
| "type": "custom", | ||
| "category": "Custom Components", | ||
| "properties": [ | ||
| { | ||
| "type": "json", | ||
| "name": "cascade", | ||
| "label": "Cascade", | ||
| "showInSettings": true, | ||
| "hasLogicHandler": true, | ||
| "handlerId": "cascadeLogic", | ||
| "handlerLabel": "Cascade Logic", | ||
| "dataBinding": true, | ||
| "handlerDescription": "This is a handler for the logic to determine an array of select items to display as the available options." | ||
| }, | ||
| { | ||
| "type": "text", | ||
| "name": "placeholder", | ||
| "label": "Placeholder", | ||
| "showInSettings": true, | ||
| "hasLogicHandler": true, | ||
| "handlerId": "placeholderLogic", | ||
| "handlerLabel": "Placeholder Logic", | ||
| "dataBinding": true, | ||
| "handlerDescription": "This is a handler for the logic to determine the default text to display when no option is selected." | ||
| } | ||
| ], | ||
| "eventHandlers": [ | ||
| { | ||
| "name": "onClickItem", | ||
| "label": "On Click Item", | ||
| "contextBlocks": [ | ||
| { | ||
| "id": "item", | ||
| "label": "Item" | ||
| } | ||
| ], | ||
| "handlerDescription": "This event is triggered when user select item" | ||
| } | ||
| ], | ||
| "actions": [ | ||
| { | ||
| "id": "getSelected", | ||
| "label": "Get Selected in", | ||
| "hasReturn": true | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <div data-module-type="system" data-module-id="block" data-display data-uid="97c1e475f636c6292fddcff60a990462" style="display:flex;flex-shrink:0;min-width:100px;border:2px solid #aaaaaa;flex-direction:row;justify-content:space-between;align-items:center;padding:10px 10px 10px 10px;border-radius:6px 6px 6px 6px;"><span data-content="Cascade Select" data-module-type="system" data-module-id="text" data-display data-uid="398503af28abc33291c6092d9661cbe5" class="bl-text" style="color:#aaaaaa;"></span><i data-icon="arrow_forward_ios" data-size="small" data-module-type="system" data-module-id="icon" data-display data-uid="57c3cba667be0bfe1fdeafc5362aa967" style="color:#aaaaaa;"></i></div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| export function isCyclic(obj) { | ||
| const keys = []; | ||
| const stack = []; | ||
| const stackSet = new Set(); | ||
| let detected = false; | ||
| let locate; | ||
|
|
||
| function detect(obj, key) { | ||
|
||
| if (obj && typeof obj != 'object') { | ||
| return; | ||
| } | ||
|
|
||
| if (stackSet.has(obj)) { | ||
| locate = keys.join('.') + '.' + key; | ||
| detected = true; | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| keys.push(key); | ||
| stack.push(obj); | ||
| stackSet.add(obj); | ||
|
|
||
| for (const k in obj) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, k)) { | ||
| detect(obj[k], k); | ||
| } | ||
| } | ||
|
|
||
| keys.pop(); | ||
| stack.pop(); | ||
| stackSet.delete(obj); | ||
|
|
||
| return; | ||
Valodya marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| detect(obj, 'obj'); | ||
|
|
||
| return [detected, locate]; | ||
| } | ||
|
|
||
| export const prepareCascade = (cascade, setParentItems) => { | ||
| let levelOfNesting = 0; | ||
| const parentItems = []; | ||
| const groupParentItems = []; | ||
|
|
||
| const prepare = cascade => { | ||
| const validCascade = cascade.map(item => { | ||
| let validItem = { ...item, levelOfNesting }; | ||
|
|
||
| if (item.children) { | ||
| levelOfNesting++; | ||
| validItem = { | ||
| ...validItem, | ||
| children: prepare(item.children), | ||
| }; | ||
|
|
||
| parentItems.push({ code: item.code, isOpen: false, levelOfNesting }); | ||
| } | ||
|
|
||
| return validItem; | ||
| }); | ||
|
|
||
| levelOfNesting--; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you please explain the logic around if it needs to calculate nesting depth would not it be better to pass a new level into the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I understand, with this implementation, we will not be able to get the last depth value when this recursion ends. It's needed to get max depth
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do you need to know about the max depth?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get sorted and separated array of |
||
|
|
||
| return validCascade; | ||
| }; | ||
|
|
||
| const preparedCascade = prepare(cascade); | ||
|
|
||
| for (let i = 0; i <= -levelOfNesting; i++) { | ||
| groupParentItems.push(parentItems.filter(item => { | ||
| return item.levelOfNesting === i; | ||
| })); | ||
v-excelsior marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| setParentItems(groupParentItems); | ||
|
|
||
| return preparedCascade; | ||
| }; | ||
|
|
||
| export const findParentItem = (parentItems, item) => { | ||
| for (let i = 0; i < parentItems.length; i++) { | ||
| for (let j = 0; j < parentItems[i].length; j++) { | ||
| if (parentItems[i][j].code === item.code) { | ||
| return parentItems[i][j]; | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { useState, useEffect, useCallback } from 'react'; | ||
| import { CollapseButtonIcon, Cascade } from './subcomponent'; | ||
| import { prepareCascade, isCyclic } from './helpers'; | ||
|
|
||
| const { cn } = BackendlessUI.CSSUtils; | ||
|
|
||
| export default function CascadeSelect({ component, eventHandlers }) { | ||
| const { display, classList, style, cascade, placeholder } = component; | ||
| const { onClickItem } = eventHandlers; | ||
|
|
||
| const [itemsCascade, setItemsCascade] = useState(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the difference between them?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still do not understand why we need 3 lists instead of a single one
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because it's easy to find items in an array, instead of an array of objects that have in turn, have their own array of objects
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parent reference should solve that problem |
||
| const [parentItems, setParentItems] = useState([]); | ||
| const [selected, setSelected] = useState({ name: placeholder }); | ||
| const [isOpen, setIsOpen] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const [detected, locate] = isCyclic(cascade); | ||
|
|
||
| if (detected) { | ||
v-excelsior marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| throw new Error('cascade have cycling object in ' + locate); | ||
| } | ||
|
|
||
| if (cascade) { | ||
| setItemsCascade(prepareCascade(cascade, setParentItems)); | ||
| } | ||
| }, [cascade]); | ||
|
|
||
| const openCascadeHandler = useCallback(item => { | ||
| const { code, levelOfNesting } = item; | ||
|
|
||
| setParentItems(state => { | ||
| const currentParentItems = [...state]; | ||
v-excelsior marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| for (let i = 0; i < currentParentItems[levelOfNesting].length; i++) { | ||
| if (currentParentItems[levelOfNesting][i].code === code) { | ||
| currentParentItems[levelOfNesting][i].isOpen = !currentParentItems[levelOfNesting][i].isOpen; | ||
| } else { | ||
| currentParentItems[levelOfNesting][i].isOpen = false; | ||
| } | ||
v-excelsior marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| return currentParentItems; | ||
| }); | ||
| }, []); | ||
|
|
||
| const openItemHandler = useCallback(item => { | ||
| setSelected(item); | ||
| setIsOpen(false); | ||
|
|
||
| onClickItem({ item }); | ||
| }, []); | ||
|
|
||
| const onClickInput = () => setIsOpen(state => !state); | ||
|
|
||
| component.getSelected = () => selected; | ||
Valodya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (!display) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <div className={ cn('bl-cascadeSelect-component', ...classList) } style={ style }> | ||
| <div | ||
| className={ cn('cascade-select__input', { 'cascade-select__input--selected': selected.code }) } | ||
| onClick={ onClickInput }> | ||
| <span>{ selected.name }</span> | ||
| <CollapseButtonIcon/> | ||
| </div> | ||
| <Cascade | ||
| isOpen={ isOpen } | ||
| selected={ selected } | ||
| itemsCascade={ itemsCascade } | ||
| parentItems={ parentItems } | ||
| openCascadeHandler={ openCascadeHandler } | ||
| openItemHandler={ openItemHandler } | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.