umbraco-sorter

安装量: 59
排名: #12554

安装

npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-sorter
Umbraco Sorter
What is it?
The UmbSorterController provides drag-and-drop sorting functionality for lists of items in the Umbraco backoffice. It handles reordering items within a container, moving items between containers, and supports nested sorting scenarios. This is useful for block editors, content trees, and any UI that requires user-driven ordering.
Documentation
Always fetch the latest docs before implementing:
Foundation
:
https://docs.umbraco.com/umbraco-cms/customizing/foundation
Extension Registry
:
https://docs.umbraco.com/umbraco-cms/customizing/extending-overview/extension-registry
Reference Examples
The Umbraco source includes working examples:
Nested Containers
:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/
This example demonstrates nested sorting with items that can contain child items.
Two Containers
:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/
This example shows moving items between two separate containers.
Related Foundation Skills
State Management
For reactive updates when order changes
Reference skill:
umbraco-state-management
Umbraco Element
For creating sortable item elements Reference skill: umbraco-umbraco-element Workflow Fetch docs - Use WebFetch on the URLs above Ask questions - Single or multiple containers? Nested items? What data model? Generate files - Create container element + item element + sorter setup Explain - Show what was created and how sorting works Basic Sorter Setup import { UmbSorterController } from '@umbraco-cms/backoffice/sorter' ; import { html , customElement , property , repeat } from '@umbraco-cms/backoffice/external/lit' ; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ; interface MyItem { id : string ; name : string ; } @ customElement ( 'my-sortable-list' ) export class MySortableListElement extends UmbLitElement {

sorter

= new UmbSorterController < MyItem , HTMLElement

( this , { // Get unique identifier from DOM element getUniqueOfElement : ( element ) => { return element . getAttribute ( 'data-id' ) ?? '' ; } , // Get unique identifier from data model getUniqueOfModel : ( modelEntry ) => { return modelEntry . id ; } , // Identifier shared by all connected sorters (for cross-container dragging) identifier : 'my-sortable-list' , // CSS selector for sortable items itemSelector : '.sortable-item' , // CSS selector for the container containerSelector : '.sortable-container' , // Called when order changes onChange : ( { model } ) => { this . _items = model ; this . requestUpdate ( ) ; this . dispatchEvent ( new CustomEvent ( 'change' , { detail : { items : model } } ) ) ; } , } ) ; @ property ( { type : Array , attribute : false } ) public get items ( ) : MyItem [ ] { return this . _items ; } public set items ( value : MyItem [ ] ) { this . _items = value ; this .

sorter

. setModel ( value ) ; this . requestUpdate ( ) ; } private _items : MyItem [ ] = [ ] ; override render ( ) { return html `

${ repeat ( this . _items , ( item ) => item . id , ( item ) => html `
${ item . name }
` ) }

` ; } } Nested Sorter (Items with Children) import { UmbSorterController } from '@umbraco-cms/backoffice/sorter' ; import { html , customElement , property , repeat , css } from '@umbraco-cms/backoffice/external/lit' ; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ; export interface NestedItem { name : string ; children ? : NestedItem [ ] ; } @ customElement ( 'my-sorter-group' ) export class MySorterGroupElement extends UmbLitElement {

sorter

= new UmbSorterController < NestedItem , MySorterItemElement

( this , { getUniqueOfElement : ( element ) => element . name , getUniqueOfModel : ( modelEntry ) => modelEntry . name , // IMPORTANT: Same identifier allows items to move between all nested groups identifier : 'my-nested-sorter' , itemSelector : 'my-sorter-item' , containerSelector : '.sorter-container' , onChange : ( { model } ) => { const oldValue = this . _value ; this . _value = model ; this . requestUpdate ( 'value' , oldValue ) ; this . dispatchEvent ( new CustomEvent ( 'change' ) ) ; } , } ) ; @ property ( { type : Array , attribute : false } ) public get value ( ) : NestedItem [ ] { return this . _value ?? [ ] ; } public set value ( value : NestedItem [ ] ) { this . _value = value ; this .

sorter

. setModel ( value ) ; this . requestUpdate ( ) ; } private _value ? : NestedItem [ ] ; override render ( ) { return html `

${ repeat ( this . value , ( item ) => item . name , ( item ) => html ` { item . children = ( e . target as MySorterGroupElement ) . value ; } } > ` ) }

; } static override styles = css :host { display: block; min-height: 20px; border: 1px dashed rgba(122, 122, 122, 0.25); border-radius: var(--uui-border-radius); padding: var(--uui-size-space-1); } ; } Sortable Item Element import { html , customElement , property , css } from '@umbraco-cms/backoffice/external/lit' ; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ; @ customElement ( 'my-sorter-item' ) export class MySorterItemElement extends UmbLitElement { @ property ( { type : String } ) name = '' ; override render ( ) { return html

${ this . name }

; } static override styles = css :host { display: block; background: var(--uui-color-surface); border: 1px solid var(--uui-color-border); border-radius: var(--uui-border-radius); margin: var(--uui-size-space-1) 0; } .item-wrapper { padding: var(--uui-size-space-3); } .drag-handle { cursor: grab; display: inline-block; margin-right: var(--uui-size-space-2); } .drag-handle:active { cursor: grabbing; } .children { margin-left: var(--uui-size-space-5); margin-top: var(--uui-size-space-2); } ; } declare global { interface HTMLElementTagNameMap { 'my-sorter-item' : MySorterItemElement ; } } Two Containers (Cross-Container Sorting) @ customElement ( 'my-dual-sorter-dashboard' ) export class MyDualSorterDashboard extends UmbLitElement { listOneItems : MyItem [ ] = [ { id : '1' , name : 'Apple' } , { id : '2' , name : 'Banana' } , ] ; listTwoItems : MyItem [ ] = [ { id : '3' , name : 'Carrot' } , { id : '4' , name : 'Date' } , ] ; override render ( ) { return html

{ this . listOneItems = e . detail . items ; } } > { this . listTwoItems = e . detail . items ; } } >
`
;
}
}
Key
Both lists use the same identifier in their UmbSorterController to enable dragging between them. UmbSorterController Options Option Type Description identifier string Shared ID for connected sorters (enables cross-container dragging) itemSelector string CSS selector for sortable items containerSelector string CSS selector for the container getUniqueOfElement (element) => string Extract unique ID from DOM element getUniqueOfModel (model) => string Extract unique ID from data model onChange ({ model }) => void Called when order changes onStart () => void Called when dragging starts onEnd () => void Called when dragging ends Key Methods // Set the model (call when items change externally) this .

sorter

. setModel ( items ) ; // Get current model const currentItems = this .

sorter

. getModel ( ) ; // Disable sorting temporarily this .

sorter

. disable ( ) ; // Re-enable sorting this .

sorter

. enable ( ) ; CSS Classes Applied During Drag Class Applied To When .umb-sorter-dragging Container While any item is being dragged .umb-sorter-placeholder Placeholder element Indicates drop position Best Practices Use unique identifiers - Each item must have a unique ID Match selectors carefully - itemSelector and containerSelector must match your DOM Share identifier - Use same identifier for connected sorters Handle nested updates - Propagate changes up through nested structures Use repeat directive - Always use repeat() with a key function for proper DOM diffing Provide visual feedback - Style drag handles and drop zones clearly That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.

返回排行榜