Samples / Controls / sap.m

Plugin for Tree Table with folder hierarchy & folder uploads

This example demonstrates the usage of UploadSetwithTable plugin with a tree table featuring folder hierarchy, folder creation, folder uploads, folder navigation with breadcrumbs and folder management

Class
z2ui5_cl_smpc_app_661
Repository
abap2UI5/samples-controls
Ports
sap.m.sample.UploadSetwithTablePlugin.TreeTableWithFolderHierarchy
UI5 libraries
sap.f, sap.m, sap.ui.core, sap.ui.table
Lowest UI5 release
1.139
In the browser
Runs in the playground
Controls it builds
sap.f.DynamicPage, sap.m.Breadcrumbs, sap.m.Button, sap.m.Dialog, sap.m.HBox, sap.m.Image, sap.m.Input, sap.m.Label, sap.m.Link, sap.m.OverflowToolbar, sap.m.Text, sap.m.VBox, sap.m.plugins.UploadSetwithTable, sap.m.upload.ActionsPlaceholder, sap.m.upload.UploadItemConfiguration, sap.ui.core.FragmentDefinition, sap.ui.core.Icon, sap.ui.core.mvc.View, sap.ui.table.Column, sap.ui.table.RowSettings, sap.ui.table.TreeTable

The class

" @keywords uploadsetwithtable upload setwith table sap.m.plugins treetablewithfolderhierarchy dynamicpage treetable overflowtoolbar breadcrumbs link actionsplaceholder
" @summary This example demonstrates the usage of UploadSetwithTable plugin with a tree table featuring folder hierarchy, folder creation, folder uploads, folder navigation with breadcrumbs and folder management
" @origin sap.m.sample.UploadSetwithTablePlugin.TreeTableWithFolderHierarchy - https://sdk.openui5.org/entity/sap.m.plugins.UploadSetwithTable/sample/sap.m.sample.UploadSetwithTablePlugin.TreeTableWithFolderHierarchy (status: generated)
CLASS z2ui5_cl_smpc_app_661 DEFINITION PUBLIC.

  PUBLIC SECTION.
    INTERFACES z2ui5_if_app.

    " the rows the TreeTable shows: the open folder's content, four levels deep
    TYPES:
      BEGIN OF ty_s_level4,
        documentid    TYPE string,
        filename      TYPE string,
        mimetype      TYPE string,
        imageurl      TYPE string,
        url           TYPE string,
        size          TYPE i,
        uploadeddate  TYPE string,
        uploadedby    TYPE string,
        isdirectory   TYPE abap_bool,
        status        TYPE string,
        documenttype  TYPE string,
        previewable   TYPE abap_bool,
        trustedsource TYPE abap_bool,
        color         TYPE string,
        bgcolor       TYPE string,
        uploadstate   TYPE string,
        icon          TYPE string,
        size_units    TYPE string,
      END OF ty_s_level4.
    TYPES ty_t_level4 TYPE STANDARD TABLE OF ty_s_level4 WITH EMPTY KEY.
    TYPES:
      BEGIN OF ty_s_level3,
        documentid    TYPE string,
        filename      TYPE string,
        mimetype      TYPE string,
        imageurl      TYPE string,
        url           TYPE string,
        size          TYPE i,
        uploadeddate  TYPE string,
        uploadedby    TYPE string,
        isdirectory   TYPE abap_bool,
        status        TYPE string,
        documenttype  TYPE string,
        previewable   TYPE abap_bool,
        trustedsource TYPE abap_bool,
        color         TYPE string,
        bgcolor       TYPE string,
        uploadstate   TYPE string,
        icon          TYPE string,
        size_units    TYPE string,
        items         TYPE ty_t_level4,
      END OF ty_s_level3.
    TYPES ty_t_level3 TYPE STANDARD TABLE OF ty_s_level3 WITH EMPTY KEY.
    TYPES:
      BEGIN OF ty_s_level2,
        documentid    TYPE string,
        filename      TYPE string,
        mimetype      TYPE string,
        imageurl      TYPE string,
        url           TYPE string,
        size          TYPE i,
        uploadeddate  TYPE string,
        uploadedby    TYPE string,
        isdirectory   TYPE abap_bool,
        status        TYPE string,
        documenttype  TYPE string,
        previewable   TYPE abap_bool,
        trustedsource TYPE abap_bool,
        color         TYPE string,
        bgcolor       TYPE string,
        uploadstate   TYPE string,
        icon          TYPE string,
        size_units    TYPE string,
        items         TYPE ty_t_level3,
      END OF ty_s_level2.
    TYPES ty_t_level2 TYPE STANDARD TABLE OF ty_s_level2 WITH EMPTY KEY.
    TYPES:
      BEGIN OF ty_s_level1,
        documentid    TYPE string,
        filename      TYPE string,
        mimetype      TYPE string,
        imageurl      TYPE string,
        url           TYPE string,
        size          TYPE i,
        uploadeddate  TYPE string,
        uploadedby    TYPE string,
        isdirectory   TYPE abap_bool,
        status        TYPE string,
        documenttype  TYPE string,
        previewable   TYPE abap_bool,
        trustedsource TYPE abap_bool,
        color         TYPE string,
        bgcolor       TYPE string,
        uploadstate   TYPE string,
        icon          TYPE string,
        size_units    TYPE string,
        items         TYPE ty_t_level2,
      END OF ty_s_level1.
    TYPES:
      BEGIN OF ty_s_history,
        name TYPE string,
        path TYPE string,
      END OF ty_s_history.
    DATA t_items             TYPE STANDARD TABLE OF ty_s_level1 WITH EMPTY KEY.
    DATA currentlocationtext TYPE string.
    DATA t_history           TYPE STANDARD TABLE OF ty_s_history WITH EMPTY KEY.
    " onSelectionChange's three buttons
    DATA download_enabled    TYPE abap_bool.
    DATA rename_enabled      TYPE abap_bool.
    DATA removeall_enabled   TYPE abap_bool.
    " the CreateFolderDialog fragment and the controller-built Rename Folder dialog
    DATA folder_name         TYPE string.
    DATA rename_name         TYPE string.

  PROTECTED SECTION.
    " the whole documents.json tree, flat: every node names its parent folder
    TYPES:
      BEGIN OF ty_s_doc,
        parentid      TYPE string,
        documentid    TYPE string,
        filename      TYPE string,
        mimetype      TYPE string,
        imageurl      TYPE string,
        url           TYPE string,
        size          TYPE i,
        uploadeddate  TYPE string,
        uploadedby    TYPE string,
        isdirectory   TYPE abap_bool,
        status        TYPE string,
        documenttype  TYPE string,
        previewable   TYPE abap_bool,
        trustedsource TYPE abap_bool,
        color         TYPE string,
        bgcolor       TYPE string,
        uploadstate   TYPE string,
        icon          TYPE string,
        size_units    TYPE string,
      END OF ty_s_doc.
    TYPES ty_t_index TYPE STANDARD TABLE OF i WITH EMPTY KEY.

    DATA client TYPE REF TO z2ui5_if_client.
    DATA t_docs      TYPE STANDARD TABLE OF ty_s_doc WITH EMPTY KEY.
    " the folder the table shows - initial is the root, Attachments
    DATA current_id  TYPE string.
    " the documentIds of the selected rows, of the row a removal asks about and
    " of the folder being renamed
    DATA t_selected  TYPE string_table.
    DATA remove_id   TYPE string.
    DATA rename_id   TYPE string.
    DATA new_no      TYPE i.

    METHODS view_display.
    METHODS popup_folder_display.
    METHODS popup_rename_display.
    METHODS on_event.
    METHODS selection_set
      IMPORTING
        paths TYPE string.
    METHODS download.
    METHODS folder_create
      IMPORTING
        name TYPE string.
    METHODS folder_open
      IMPORTING
        id TYPE string.
    METHODS remove_all_ask.
    METHODS remove_selected.
    METHODS rows_build.
    METHODS children_count
      IMPORTING
        parent        TYPE string
      RETURNING
        VALUE(result) TYPE i.
    METHODS doc_at_path
      IMPORTING
        path          TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS doc_delete
      IMPORTING
        id TYPE string.
    METHODS doc_insert
      IMPORTING
        doc           TYPE ty_s_doc
        after_folders TYPE abap_bool.
    METHODS path_indices
      IMPORTING
        path          TYPE string
      RETURNING
        VALUE(result) TYPE ty_t_index.
    METHODS row_derive
      CHANGING
        row TYPE ty_s_doc.
    METHODS icon_for
      IMPORTING
        mediatype     TYPE string
        filename      TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS size_with_units
      IMPORTING
        size          TYPE i
      RETURNING
        VALUE(result) TYPE string.
    METHODS model_init.

  PRIVATE SECTION.
ENDCLASS.


CLASS z2ui5_cl_smpc_app_661 IMPLEMENTATION.

  METHOD z2ui5_if_app~main.

    me->client = client.
    IF client->check_on_init( ).
      model_init( ).
      view_display( ).
    ELSEIF client->check_on_navigated( ).
      view_display( ).
    ELSEIF client->check_on_event( ).
      on_event( ).
    ENDIF.

  ENDMETHOD.


  METHOD view_display.

    DATA(view) = z2ui5_cl_ui5_view_builder=>factory( ).

    view->ele( n = `View` ns = `mvc`
        )->a( n = `xmlns`         v = `sap.ui.table`
        )->a( n = `xmlns:m`       v = `sap.m`
        )->a( n = `xmlns:mvc`     v = `sap.ui.core.mvc`
        )->a( n = `xmlns:f`       v = `sap.f`
        )->a( n = `xmlns:core`    v = `sap.ui.core`
        )->a( n = `xmlns:upload`  v = `sap.m.upload`
        )->a( n = `xmlns:plugins` v = `sap.m.plugins`
        )->a( n = `height`        v = `100%`
        )->a( n = `displayBlock`  v = `true`

        )->ele( n = `DynamicPage` ns = `f`
            )->a( n = `id` v = `page`

            )->ele( n = `content` ns = `f`
                " onToggleOpenState is an empty handler - the wire is not built
                )->ele( `TreeTable`
                    )->a( n = `id`                 v = `table-uploadSet`
                    " IMAGEURL and UPLOADSTATE stay absent where the data has none, so
                    " the view's undefined checks read as in the sample
                    )->a( n = `rows`               v = client->_bind( val                = t_items
                                                                      omit_initial_paths = VALUE #( ( `IMAGEURL` )
                                                                                                    ( `UPLOADSTATE` ) ) )
                    )->a( n = `selectionMode`      v = `MultiToggle`
                    )->a( n = `rowSelectionChange` v = client->_event( val = `SELECTION`
                                                                       arg = `$event.oSource.getBinding('rows').getSelectedContexts().join()` )

                    )->ele( `extension`
                        )->ele( n = `OverflowToolbar` ns = `m`
                            )->a( n = `style` v = `Clear`

                            )->ele( n = `Breadcrumbs` ns = `m`
                                )->a( n = `id`                  v = `breadcrumbs`
                                )->a( n = `currentLocationText` v = client->_bind( currentlocationtext )
                                )->a( n = `links`               v = client->_bind( t_history )

                                )->tag( n = `Link` ns = `m`
                                    )->a( n = `press` v = client->_event( val = `CRUMB`
                                                                          arg = `$event.oSource.getBindingContext().getPath()` )
                                    )->a( n = `text`  v = `{NAME}`

                            )->end(
                            )->tag( n = `ActionsPlaceholder` ns = `upload`
                                )->a( n = `id`             v = `uploadButton`
                                )->a( n = `placeholderFor` v = `UploadButtonPlaceholder`
                            )->tag( n = `Button` ns = `m`
                                )->a( n = `id`      v = `downloadSelectedButton`
                                )->a( n = `text`    v = `Download`
                                )->a( n = `enabled` v = client->_bind( download_enabled )
                                )->a( n = `press`   v = client->_event( `DOWNLOAD` )
                            )->tag( n = `Button` ns = `m`
                                )->a( n = `id`    v = `addFolderButton`
                                )->a( n = `text`  v = `Add Folder`
                                )->a( n = `press` v = client->_event( `ADD_FOLDER` )
                                )->a( n = `icon`  v = `sap-icon://add-folder`
                            " onRenameItem: a folder gets the controller's dialog (backend), a file
                            " the plugin's renameItem - the second handler hands it the selected
                            " context, or null for a folder, which renameItem ignores
                            )->tag( n = `Button` ns = `m`
                                )->a( n = `id`      v = `renameButton`
                                )->a( n = `text`    v = `Rename`
                                )->a( n = `enabled` v = client->_bind( rename_enabled )
                                )->a( n = `press`   v = client->_event( `RENAME` ) && `; ` &&
                                                        client->follow_up_action( val   = client->cs_event-control_by_id
                                                                                  t_arg = VALUE #( ( `uploadPlugin` )
                                                                                                   ( `renameItem` )
                                                                                                   ( `$controller.getView().byId('table-uploadSet').getBinding('rows')` &&
                                                                                                     `.getSelectedContexts()[0].getProperty('ISDIRECTORY') ? null : ` &&
                                                                                                     `$controller.getView().byId('table-uploadSet').getBinding('rows')` &&
                                                                                                     `.getSelectedContexts()[0]` ) ) )
                            )->tag( n = `Button` ns = `m`
                                )->a( n = `id`      v = `removeAllButton`
                                )->a( n = `text`    v = `Remove All`
                                )->a( n = `enabled` v = client->_bind( removeall_enabled )
                                )->a( n = `press`   v = client->_event( `REMOVE_ALL` )

                        )->end(
                    )->end(
                    )->ele( `dependents`
                        )->ele( n = `UploadSetwithTable` ns = `plugins`
                            )->a( n = `id`                 v = `uploadPlugin`
                            )->a( n = `uploadUrl`          v = `./uploadFiles`
                            )->a( n = `multiple`           v = `true`
                            )->a( n = `uploadEnabled`      v = `true`
                            " the mock server's part: the upload is vetoed and the backend
                            " files the document into the open folder
                            )->a( n = `beforeUploadStarts` v = client->_event( val    = `UPLOAD`
                                                                               t_arg  = VALUE #( ( `${$parameters>/item}.getFileName()` )
                                                                                                 ( `${$parameters>/item}.getMediaType()` )
                                                                                                 ( `${$parameters>/item}.getFileObject().size` ) )
                                                                               s_ctrl = VALUE #( check_prevent_default = abap_true
                                                                                                 check_queue_last      = abap_true ) )
                            )->a( n = `itemRenamed`        v = client->_event( val   = `RENAMED`
                                                                               t_arg = VALUE #( ( `${$parameters>/item}.getBindingContext().getPath()` )
                                                                                                ( `${$parameters>/item}.getFileName()` ) ) )
                            )->a( n = `actions`            v = `uploadButton`

                            " the paths name the model's upper-case ABAP fields
                            )->tag( n = `UploadItemConfiguration` ns = `upload`
                                )->a( n = `fileNamePath`        v = `FILENAME`
                                )->a( n = `mediaTypePath`       v = `MIMETYPE`
                                )->a( n = `urlPath`             v = `URL`
                                )->a( n = `fileSizePath`        v = `SIZE`
                                )->a( n = `isDirectoryPath`     v = `ISDIRECTORY`
                                )->a( n = `previewablePath`     v = `PREVIEWABLE`
                                )->a( n = `isTrustedSourcePath` v = `TRUSTEDSOURCE`

                        )->end(
                    )->end(
                    )->ele( `rowSettingsTemplate`
                        )->tag( `RowSettings`
                            )->a( n = `highlight` v = `{= ${UPLOADSTATE} === 'Complete' ? 'Indication05' : null }`

                    )->end(
                    )->ele( `columns`
                        )->ele( `Column`
                            )->a( n = `width` v = `40%`

                            )->tag( n = `Label` ns = `m`
                                )->a( n = `text` v = `File Name`

                            )->ele( `template`
                                )->ele( n = `HBox` ns = `m`
                                    )->a( n = `class` v = `sapUiSmallMarginTop sapUiTinyMarginBottom`

                                    )->tag( n = `Image` ns = `m`
                                        )->a( n = `src`     v = `{IMAGEURL}`
                                        )->a( n = `class`   v = `sapMUSTItemImage sapMUSTItemIcon`
                                        )->a( n = `visible` v = `{= ${IMAGEURL} !== undefined}`
                                    " getIconSrc runs in ABAP: the row carries the finished icon
                                    )->tag( n = `Icon` ns = `core`
                                        )->a( n = `src`             v = `{ICON}`
                                        )->a( n = `visible`         v = `{= ${IMAGEURL} === undefined || ${IMAGEURL} === '' }`
                                        )->a( n = `class`           v = `sapMUSTItemIcon`
                                        )->a( n = `color`           v = `{= ${ISDIRECTORY} ? ${COLOR} : '' }`
                                        )->a( n = `backgroundColor` v = `{= ${ISDIRECTORY} ? ${BGCOLOR} : '' }`

                                    )->ele( n = `VBox` ns = `m`
                                        )->a( n = `class` v = `sapUiTinyMargin sapUiSmallMarginBegin`

                                        )->tag( n = `Link` ns = `m`
                                            )->a( n = `text`    v = `{FILENAME}`
                                            )->a( n = `press`   v = client->_event( val = `OPEN_FOLDER`
                                                                                    arg = `$event.oSource.getBindingContext().getPath()` )
                                            )->a( n = `class`   v = `sapUiSmallMarginBottom`
                                            )->a( n = `visible` v = `{= ${ISDIRECTORY} === true }`
                                        )->tag( n = `Link` ns = `m`
                                            )->a( n = `text`    v = `{FILENAME}`
                                            )->a( n = `press`   v = client->follow_up_action( val   = client->cs_event-control_by_id
                                                                                              t_arg = VALUE #( ( `uploadPlugin` )
                                                                                                               ( `openFilePreview` )
                                                                                                               ( `$event.oSource.getBindingContext()` ) ) )
                                            )->a( n = `class`   v = `sapUiTinyMarginBottom`
                                            )->a( n = `visible` v = `{= ${ISDIRECTORY} === false }`
                                        )->tag( n = `Text` ns = `m`
                                            )->a( n = `text` v = `{DOCUMENTTYPE}`

                                    )->end(
                                )->end(
                            )->end(
                        )->end(
                        )->ele( `Column`
                            )->tag( n = `Label` ns = `m`
                                )->a( n = `text` v = `Status`

                            )->ele( `template`
                                )->tag( n = `Text` ns = `m`
                                    )->a( n = `text` v = `{STATUS}`

                            )->end(
                        )->end(
                        )->ele( `Column`
                            )->tag( n = `Label` ns = `m`
                                )->a( n = `text` v = `File Size`

                            " formatFileSize runs in ABAP as well
                            )->ele( `template`
                                )->tag( n = `Text` ns = `m`
                                    )->a( n = `text` v = `{SIZE_UNITS}`

                            )->end(
                        )->end(
                        )->ele( `Column`
                            )->tag( n = `Label` ns = `m`
                                )->a( n = `text` v = `Last Modified`

                            )->ele( `template`
                                )->ele( n = `VBox` ns = `m`
                                    )->tag( n = `Text` ns = `m`
                                        )->a( n = `text` v = `{UPLOADEDBY}`
                                    )->tag( n = `Text` ns = `m`
                                        )->a( n = `class` v = `sapUiTinyMarginTop`
                                        )->a( n = `text`  v = `{UPLOADEDDATE}`

                                )->end(
                            )->end(
                        )->end(
                        )->ele( `Column`
                            )->ele( `template`
                                )->tag( n = `Button` ns = `m`
                                    )->a( n = `class`   v = `sapUiTinyMarginBegin`
                                    )->a( n = `type`    v = `Transparent`
                                    )->a( n = `icon`    v = `sap-icon://decline`
                                    )->a( n = `press`   v = client->_event( val = `REMOVE_ROW`
                                                                            arg = `$event.oSource.getBindingContext().getPath()` )
                                    )->a( n = `visible` v = `{= ${ISDIRECTORY} === false }`
                                    )->a( n = `tooltip` v = `Delete File` ).

    client->view_display( view->stringify( ) ).

  ENDMETHOD.


  METHOD popup_folder_display.

    DATA(popup) = z2ui5_cl_ui5_view_builder=>factory( ).

    popup->ele( n = `FragmentDefinition` ns = `core`
        )->a( n = `xmlns`      v = `sap.m`
        )->a( n = `xmlns:core` v = `sap.ui.core`

        )->ele( `Dialog`
            )->a( n = `title` v = `Create New Folder`
            )->a( n = `class` v = `sapUiMediumMargin`

            )->ele( `content`
                )->ele( `VBox`
                    )->a( n = `class` v = `sapUiMediumMargin`

                    )->tag( `Label`
                        )->a( n = `text` v = `Folder name:`
                    )->tag( `Input`
                        )->a( n = `id`          v = `folderNameInput`
                        )->a( n = `value`       v = client->_bind( folder_name )
                        )->a( n = `placeholder` v = `Enter folder name`

                )->end(
            )->end(
            )->ele( `buttons`
                )->tag( `Button`
                    )->a( n = `text`  v = `Create`
                    )->a( n = `type`  v = `Emphasized`
                    )->a( n = `press` v = client->_event( `CREATE_FOLDER` )
                )->tag( `Button`
                    )->a( n = `text`  v = `Cancel`
                    )->a( n = `press` v = client->follow_up_action( client->cs_event-popup_close ) ).

    client->popup_display( popup->stringify( ) ).

  ENDMETHOD.


  METHOD popup_rename_display.

    " renameFolderWithDialog builds this Dialog in the controller
    DATA(popup) = z2ui5_cl_ui5_view_builder=>factory( ).

    popup->ele( n = `FragmentDefinition` ns = `core`
        )->a( n = `xmlns`      v = `sap.m`
        )->a( n = `xmlns:core` v = `sap.ui.core`

        )->ele( `Dialog`
            )->a( n = `title` v = `Rename Folder`

            )->ele( `content`
                )->tag( `Input`
                    )->a( n = `value` v = client->_bind( rename_name )
                    )->a( n = `width` v = `100%`

            )->end(
            )->ele( `beginButton`
                )->tag( `Button`
                    )->a( n = `text`  v = `OK`
                    )->a( n = `type`  v = `Emphasized`
                    )->a( n = `press` v = client->_event( `RENAME_FOLDER_OK` )

            )->end(
            )->ele( `endButton`
                )->tag( `Button`
                    )->a( n = `text`  v = `Cancel`
                    )->a( n = `press` v = client->follow_up_action( client->cs_event-popup_close ) ).

    client->popup_display( popup->stringify( ) ).

  ENDMETHOD.


  METHOD on_event.

    CASE client->get_event( ).

      WHEN `SELECTION`.
        selection_set( client->get_event_arg( ) ).

      WHEN `DOWNLOAD`.
        download( ).

      WHEN `ADD_FOLDER`.
        " onCreateFolder - the dialog opens with an empty name
        folder_name = ``.
        popup_folder_display( ).

      WHEN `CREATE_FOLDER`.
        " onCreateFolderDialogPress - an empty name keeps the dialog open
        DATA(name) = condense( folder_name ).
        IF name IS INITIAL.
          client->message_toast_display( `Please enter a folder name` ).
          RETURN.
        ENDIF.
        client->popup_destroy( ).
        folder_create( name ).

      WHEN `RENAME`.
        " onRenameItem - the plugin renames a file (the press's second handler);
        " a folder is renamed in the controller's own dialog
        IF lines( t_selected ) <> 1.
          client->message_toast_display( `Please select exactly one item to rename` ).
          RETURN.
        ENDIF.
        DATA(selected) = t_docs[ documentid = t_selected[ 1 ] ].
        IF selected-isdirectory = abap_true.
          rename_id   = selected-documentid.
          rename_name = selected-filename.
          popup_rename_display( ).
        ENDIF.

      WHEN `RENAME_FOLDER_OK`.
        DATA(folder) = REF #( t_docs[ documentid = rename_id ] ).
        DATA(new_name) = condense( rename_name ).
        IF new_name IS NOT INITIAL AND new_name <> folder->filename.
          folder->filename = new_name.
          rows_build( ).
          client->message_toast_display( `Folder renamed successfully` ).
        ENDIF.
        client->popup_destroy( ).

      WHEN `RENAMED`.
        " onDocumentRenamedSuccess
        DATA(renamed) = doc_at_path( client->get_event_arg( ) ).
        IF renamed IS NOT INITIAL.
          t_docs[ documentid = renamed ]-filename = client->get_event_arg( 2 ).
          rows_build( ).
          client->message_toast_display( text = `File renamed successfully` duration = `2000` ).
        ENDIF.

      WHEN `OPEN_FOLDER`.
        " onItemPress - navigateToFolder
        folder_open( doc_at_path( client->get_event_arg( ) ) ).

      WHEN `CRUMB`.
        " onBreadcrumbPress - a link is one level of the history
        DATA(crumb) = path_indices( client->get_event_arg( ) ).
        IF crumb IS INITIAL OR crumb[ 1 ] > lines( t_history ).
          RETURN.
        ENDIF.
        DATA(entry) = t_history[ crumb[ 1 ] ].
        folder_open( entry-path ).

      WHEN `REMOVE_ROW`.
        " onRemoveItemHandler - files only, the button is hidden on folders
        DATA(doc) = t_docs[ documentid = doc_at_path( client->get_event_arg( ) ) ].
        IF doc-isdirectory = abap_true.
          client->message_toast_display( `Folder deletion is not allowed` ).
          RETURN.
        ENDIF.
        remove_id = doc-documentid.
        client->message_box_display( text    = |Are you sure you want to delete the file '{ doc-filename }'?|
                                     type    = `confirm`
                                     title   = `Delete File`
                                     onclose = `REMOVE_CLOSE` ).

      WHEN `REMOVE_CLOSE`.
        " the file is filtered out of the OPEN folder's items
        IF client->get_event_arg( ) = `OK`.
          DATA(removed) = t_docs[ documentid = remove_id ]-filename.
          DELETE t_docs WHERE documentid = remove_id AND parentid = current_id.
          rows_build( ).
          client->message_toast_display( |File '{ removed }' deleted successfully| ).
        ENDIF.

      WHEN `REMOVE_ALL`.
        remove_all_ask( ).

      WHEN `REMOVE_ALL_CLOSE`.
        IF client->get_event_arg( ) = `OK`.
          remove_selected( ).
        ENDIF.

      WHEN `UPLOAD`.
        " the mock server files the upload into the open folder, after its folders
        new_no = new_no + 1.
        DATA(file) = VALUE ty_s_doc( parentid      = current_id
                                     documentid    = |upload-{ new_no }|
                                     filename      = client->get_event_arg( )
                                     mimetype      = client->get_event_arg( 2 )
                                     size          = CONV #( client->get_event_arg( 3 ) )
                                     uploadeddate  = |{ sy-datum DATE = ISO }|
                                     uploadedby    = `Current User`
                                     uploadstate   = `Complete`
                                     trustedsource = abap_true ).
        row_derive( CHANGING row = file ).
        doc_insert( doc = file after_folders = abap_true ).
        rows_build( ).
        client->message_toast_display( `Upload completed successfully.` ).

    ENDCASE.

  ENDMETHOD.


  METHOD selection_set.

    " onSelectionChange - files and folders are told apart, and Remove All needs
    " every item of the open folder selected
    t_selected = VALUE #( ).
    IF paths IS NOT INITIAL.
      SPLIT paths AT `,` INTO TABLE DATA(selected_paths).
      LOOP AT selected_paths INTO DATA(path).
        DATA(id) = doc_at_path( path ).
        IF id IS NOT INITIAL.
          INSERT id INTO TABLE t_selected.
        ENDIF.
      ENDLOOP.
    ENDIF.
    DATA(files) = 0.
    DATA(folders) = 0.
    LOOP AT t_selected INTO id.
      IF t_docs[ documentid = id ]-isdirectory = abap_true.
        folders = folders + 1.
      ELSE.
        files = files + 1.
      ENDIF.
    ENDLOOP.
    DATA(total) = children_count( current_id ).
    removeall_enabled = xsdbool( t_selected IS NOT INITIAL AND lines( t_selected ) = total AND total > 0 ).
    IF files > 0 AND folders = 0.
      download_enabled = abap_true.
      rename_enabled   = xsdbool( files = 1 ).
    ELSEIF folders > 0 AND files = 0.
      download_enabled = abap_false.
      rename_enabled   = xsdbool( folders = 1 ).
    ELSE.
      download_enabled = abap_false.
      rename_enabled   = abap_false.
    ENDIF.

  ENDMETHOD.


  METHOD download.

    " onDownloadFiles - one <a download> click per selected file with a URL
    DATA(files) = 0.
    LOOP AT t_selected INTO DATA(id).
      DATA(doc) = t_docs[ documentid = id ].
      IF doc-isdirectory = abap_true.
        CONTINUE.
      ENDIF.
      files = files + 1.
      IF doc-url IS NOT INITIAL.
        client->follow_up_action( val   = client->cs_event-download_b64_file
                                  t_arg = VALUE #( ( doc-url ) ( doc-filename ) ) ).
      ENDIF.
    ENDLOOP.
    IF files = 0.
      client->message_toast_display( `No files selected for download` ).
      RETURN.
    ENDIF.
    client->message_toast_display( |Download initiated for { files } file(s)| ).

  ENDMETHOD.


  METHOD folder_create.

    " createFolderWithName - into the one selected folder, else the open one
    DATA(target) = current_id.
    DATA(inside) = ``.
    DATA(selected_folders) = 0.
    LOOP AT t_selected INTO DATA(id).
      IF t_docs[ documentid = id ]-isdirectory = abap_true.
        selected_folders = selected_folders + 1.
        target = id.
      ENDIF.
    ENDLOOP.
    IF selected_folders = 1.
      inside = t_docs[ documentid = target ]-filename.
    ELSE.
      target = current_id.
    ENDIF.
    IF line_exists( t_docs[ parentid = target filename = name ] ).
      client->message_toast_display( `A folder with this name already exists` ).
      RETURN.
    ENDIF.
    new_no = new_no + 1.
    DATA(folder) = VALUE ty_s_doc( parentid     = target
                                   documentid   = |folder-new-{ new_no }|
                                   filename     = name
                                   isdirectory  = abap_true
                                   uploadeddate = |{ sy-datum DATE = ISO }|
                                   uploadedby   = `Current User`
                                   status       = `Active`
                                   documenttype = `Folder`
                                   color        = `#ffffff`
                                   bgcolor      = `#30914c`
                                   uploadstate  = `Complete` ).
    row_derive( CHANGING row = folder ).
    doc_insert( doc = folder after_folders = abap_false ).
    rows_build( ).
    IF selected_folders = 1.
      client->message_toast_display( |New folder created inside '{ inside }': { name }| ).
    ELSE.
      client->message_toast_display( |New folder created: { name }| ).
    ENDIF.

  ENDMETHOD.


  METHOD folder_open.

    " bindTableItems on the folder's items, the Breadcrumbs history up to its
    " parent and the folder as the current location; initial is the root
    current_id = id.
    t_history  = VALUE #( ).
    IF id IS INITIAL.
      currentlocationtext = `Attachments`.
    ELSE.
      currentlocationtext = t_docs[ documentid = id ]-filename.
      DATA(parent) = t_docs[ documentid = id ]-parentid.
      WHILE parent IS NOT INITIAL.
        INSERT VALUE #( name = t_docs[ documentid = parent ]-filename path = parent ) INTO t_history INDEX 1.
        parent = t_docs[ documentid = parent ]-parentid.
      ENDWHILE.
      INSERT VALUE #( name = `Attachments` ) INTO t_history INDEX 1.
    ENDIF.
    rows_build( ).
    " the rebound rows start without a selection
    t_selected        = VALUE #( ).
    download_enabled  = abap_false.
    rename_enabled    = abap_false.
    removeall_enabled = abap_false.
    client->follow_up_action( val   = client->cs_event-control_by_id
                              t_arg = VALUE #( ( `table-uploadSet` ) ( `clearSelection` ) ) ).

  ENDMETHOD.


  METHOD remove_all_ask.

    " onRemoveAllHandler - only when every item of the open folder is selected
    IF t_selected IS INITIAL.
      client->message_toast_display( `Please select items to delete` ).
      RETURN.
    ENDIF.
    IF lines( t_selected ) <> children_count( current_id ).
      client->message_toast_display( `Please select all items to use Remove All` ).
      RETURN.
    ENDIF.
    client->message_box_display( text    = |Are you sure you want to delete ALL { lines( t_selected ) } item(s) in this folder?|
                                 type    = `confirm`
                                 title   = `Delete All Items`
                                 onclose = `REMOVE_ALL_CLOSE` ).

  ENDMETHOD.


  METHOD remove_selected.

    " deleteAllSelectedItems - files and folders of the open folder
    DATA(deleted) = 0.
    LOOP AT t_selected INTO DATA(id).
      IF line_exists( t_docs[ documentid = id parentid = current_id ] ).
        doc_delete( id ).
        deleted = deleted + 1.
      ENDIF.
    ENDLOOP.
    IF deleted = 0.
      client->message_toast_display( `No items were found to delete` ).
      RETURN.
    ENDIF.
    t_selected = VALUE #( ).
    rows_build( ).
    client->message_toast_display( |{ deleted } item(s) deleted successfully| ).

  ENDMETHOD.


  METHOD rows_build.

    " the open folder's subtree as the TreeTable's rows
    t_items = VALUE #( ).
    LOOP AT t_docs INTO DATA(doc1) WHERE parentid = current_id.
      DATA(level1) = CORRESPONDING ty_s_level1( doc1 ).
      LOOP AT t_docs INTO DATA(doc2) WHERE parentid = doc1-documentid.
        DATA(level2) = CORRESPONDING ty_s_level2( doc2 ).
        LOOP AT t_docs INTO DATA(doc3) WHERE parentid = doc2-documentid.
          DATA(level3) = CORRESPONDING ty_s_level3( doc3 ).
          LOOP AT t_docs INTO DATA(doc4) WHERE parentid = doc3-documentid.
            INSERT CORRESPONDING ty_s_level4( doc4 ) INTO TABLE level3-items.
          ENDLOOP.
          INSERT level3 INTO TABLE level2-items.
        ENDLOOP.
        INSERT level2 INTO TABLE level1-items.
      ENDLOOP.
      INSERT level1 INTO TABLE t_items.
    ENDLOOP.

  ENDMETHOD.


  METHOD children_count.

    LOOP AT t_docs TRANSPORTING NO FIELDS WHERE parentid = parent.
      result = result + 1.
    ENDLOOP.

  ENDMETHOD.


  METHOD doc_at_path.

    " a row's binding path holds its position on every level below the open
    " folder: /T_ITEMS/0/ITEMS/1 is the 2nd child of its 1st item
    DATA(parent) = current_id.
    DATA(levels) = path_indices( path ).
    LOOP AT levels INTO DATA(index).
      result = ``.
      DATA(position) = 0.
      LOOP AT t_docs INTO DATA(doc) WHERE parentid = parent.
        position = position + 1.
        IF position = index.
          result = doc-documentid.
          EXIT.
        ENDIF.
      ENDLOOP.
      IF result IS INITIAL.
        RETURN.
      ENDIF.
      parent = result.
    ENDLOOP.

  ENDMETHOD.


  METHOD doc_delete.

    " the node and, below a folder, everything in it
    LOOP AT t_docs INTO DATA(child) WHERE parentid = id.
      doc_delete( child-documentid ).
    ENDLOOP.
    DELETE t_docs WHERE documentid = id.

  ENDMETHOD.


  METHOD doc_insert.

    " unshift into the parent's items - or, for an uploaded file, in front of
    " its first file (behind the folders)
    LOOP AT t_docs INTO DATA(sibling) WHERE parentid = doc-parentid.
      IF after_folders = abap_false OR sibling-isdirectory = abap_false.
        INSERT doc INTO t_docs INDEX sy-tabix.
        RETURN.
      ENDIF.
    ENDLOOP.
    INSERT doc INTO TABLE t_docs.

  ENDMETHOD.


  METHOD path_indices.

    SPLIT path AT `/` INTO TABLE DATA(parts).
    LOOP AT parts INTO DATA(part).
      IF part IS NOT INITIAL AND part CO `0123456789`.
        INSERT CONV i( part ) + 1 INTO TABLE result.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.


  METHOD row_derive.

    " getIconSrc - a folder icon for a directory - and formatFileSize
    IF row-isdirectory = abap_true.
      row-icon = `sap-icon://folder-full`.
    ELSE.
      row-icon = icon_for( mediatype = row-mimetype filename = row-filename ).
    ENDIF.
    row-size_units = size_with_units( row-size ).

  ENDMETHOD.


  METHOD icon_for.

    " UploadSetwithTable.getIconForFileType: IconPool's media type table, or the
    " file extension when there is no media type
    IF mediatype IS NOT INITIAL.
      DATA(type) = to_lower( mediatype ).
      CASE type.
        WHEN `application/msword` OR `application/vnd.google-apps.document` OR `application/rtf`
            OR `application/vnd.openxmlformats-officedocument.wordprocessingml.document`.
          result = `sap-icon://doc-attachment`.
        WHEN `application/pdf`.
          result = `sap-icon://pdf-attachment`.
        WHEN `application/msexcel` OR `application/vnd.ms-excel` OR `application/vnd.google-apps.spreadsheet`
            OR `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
            OR `application/vnd.ms-excel.sheet.macroenabled.12`
            OR `application/vnd.openxmlformats-officedocument.spreadsheetml.template`
            OR `application/vnd.ms-excel.template.macroenabled.12`
            OR `application/vnd.ms-excel.sheet.binary.macroenabled.12`
            OR `application/vnd.ms-excel.addin.macroenabled.12`.
          result = `sap-icon://excel-attachment`.
        WHEN `application/vnd.ms-powerpoint` OR `application/vnd.google-apps.presentation` OR `application/mspowerpoint`
            OR `application/vnd.openxmlformats-officedocument.presentationml.presentation`
            OR `application/vnd.openxmlformats-officedocument.presentationml.slideshow`.
          result = `sap-icon://ppt-attachment`.
        WHEN `application/xml` OR `application/xhtml+xml` OR `application/x-httpd-php` OR `application/x-javascript`
            OR `text/html`.
          result = `sap-icon://attachment-html`.
        WHEN `application/gzip` OR `application/x-rar-compressed` OR `application/x-tar` OR `application/zip`.
          result = `sap-icon://attachment-zip-file`.
        WHEN `audio/mpeg` OR `audio/voxware` OR `audio/x-aiff` OR `audio/x-midi` OR `audio/x-mpeg` OR `audio/x-m4a`
            OR `audio/x-pn-realaudio` OR `audio/x-pn-realaudio-plugin` OR `audio/x-qt-stream` OR `audio/x-wav`.
          result = `sap-icon://attachment-audio`.
        WHEN `image/png` OR `image/tiff` OR `image/bmp` OR `image/jpeg` OR `image/gif`.
          result = `sap-icon://attachment-photo`.
        WHEN `text/plain` OR `text/comma-separated-values` OR `text/css` OR `text/javascript` OR `text/richtext`
            OR `text/rtf` OR `text/tab-separated-values` OR `text/xml`.
          result = `sap-icon://attachment-text-file`.
        WHEN `video/mpeg` OR `video/mp4` OR `video/quicktime` OR `video/x-msvideo` OR `application/x-shockwave-flash`.
          result = `sap-icon://attachment-video`.
        WHEN OTHERS.
          result = `sap-icon://document`.
      ENDCASE.
      RETURN.
    ENDIF.

    DATA(dot) = find( val = filename sub = `.` occ = -1 ).
    IF dot < 0.
      result = `sap-icon://document`.
      RETURN.
    ENDIF.
    DATA(extension) = to_lower( substring( val = filename off = dot + 1 ) ).
    CASE extension.
      WHEN `bmp` OR `jpg` OR `jpeg` OR `png`.
        result = `sap-icon://card`.
      WHEN `csv` OR `xls` OR `xlsx`.
        result = `sap-icon://excel-attachment`.
      WHEN `doc` OR `docx` OR `odt`.
        result = `sap-icon://doc-attachment`.
      WHEN `pdf`.
        result = `sap-icon://pdf-attachment`.
      WHEN `ppt` OR `pptx`.
        result = `sap-icon://ppt-attachment`.
      WHEN `txt`.
        result = `sap-icon://document-text`.
      WHEN OTHERS.
        result = `sap-icon://document`.
    ENDCASE.

  ENDMETHOD.


  METHOD size_with_units.

    " UploadSetwithTable.getFileSizeWithUnits - KB below a megabyte, then MB, then GB
    CONSTANTS kilobyte TYPE i VALUE 1024.
    DATA(bytes) = CONV decfloat34( size ).
    IF size < kilobyte * kilobyte.
      result = |{ bytes / kilobyte DECIMALS = 2 } KB|.
    ELSEIF size < kilobyte * kilobyte * kilobyte.
      result = |{ bytes / kilobyte / kilobyte DECIMALS = 2 } MB|.
    ELSE.
      result = |{ bytes / kilobyte / kilobyte / kilobyte DECIMALS = 2 } GB|.
    ENDIF.

  ENDMETHOD.


  METHOD model_init.

    " model/documents.json, the whole tree flattened - the document URLs point at
    " the OpenUI5 host
    t_docs = VALUE #(
      ( documentid = `folder-1` filename = `My Documents` isdirectory = abap_true
        size = 0 uploadeddate = `2023-01-15` uploadedby = `System` status = `Active` documenttype = `Folder`
        color = `#ffffff` bgcolor = `#30914c` )
      ( parentid = `folder-1` documentid = `folder-images` filename = `Images` isdirectory = abap_true
        size = 0 uploadeddate = `2023-02-10` uploadedby = `User` status = `Active` documenttype = `Folder`
        color = `#ffffff` bgcolor = `#30914c` )
      ( parentid = `folder-images` documentid = `img-001` filename = `Animation.gif` mimetype = `image/gif` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Animation.gif`
        size = 1048576 uploadeddate = `2023-03-05` uploadedby = `John Doe` status = `Complete` documenttype = `Image`
        previewable = abap_true )
      ( parentid = `folder-images` documentid = `folder-screenshots` filename = `Screenshots` isdirectory = abap_true
        size = 0 uploadeddate = `2023-04-01` uploadedby = `User` status = `Active` documenttype = `Folder`
        color = `#ffffff` bgcolor = `#30914c` )
      ( parentid = `folder-screenshots` documentid = `screenshot-001` filename = `Video.mp4` mimetype = `video/mp4` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Video.mp4`
        size = 42000000 uploadeddate = `2023-04-01` uploadedby = `Current User` status = `Complete` documenttype = `Video`
        previewable = abap_true )
      ( parentid = `folder-1` documentid = `folder-reports` filename = `Reports` isdirectory = abap_true
        size = 0 uploadeddate = `2023-02-15` uploadedby = `Admin` status = `Active` documenttype = `Folder`
        color = `#ffffff` bgcolor = `#30914c` )
      ( parentid = `folder-reports` documentid = `report-001` filename = `Instructions.pdf` mimetype = `application/pdf` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Instructions.pdf`
        size = 30000 uploadeddate = `2023-04-01` uploadedby = `Finance Team` status = `Complete` documenttype = `Document`
        previewable = abap_true )
      ( parentid = `folder-reports` documentid = `report-002` filename = `Business Plan Agenda.doc` mimetype = `application/msword` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Business Plan Agenda.doc`
        size = 200000 uploadeddate = `2023-04-02` uploadedby = `Planning Team` status = `Complete` documenttype = `Document`
        previewable = abap_true )
      ( parentid = `folder-1` documentid = `doc-002` filename = `Third Quarter Results.ppt` mimetype = `application/vnd.ms-powerpoint` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Third Quarter Results.ppt`
        size = 3145728 uploadeddate = `2023-03-15` uploadedby = `Support Team` status = `Complete` documenttype = `Presentation`
        previewable = abap_true )
      ( documentid = `doc-001` filename = `Document.txt` mimetype = `text/plain` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Document.txt`
        size = 420 uploadeddate = `2023-02-20` uploadedby = `User` status = `Complete` documenttype = `Document`
        previewable = abap_true )
      ( documentid = `doc-003` filename = `Business Plan Topics.xls` mimetype = `application/vnd.ms-excel` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Business Plan Topics.xls`
        size = 1048576 uploadeddate = `2023-03-20` uploadedby = `Marketing` status = `In work` documenttype = `Spreadsheet`
        previewable = abap_true )
      ( documentid = `doc-004` filename = `sample-zip-file.zip` mimetype = `application/zip` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/sample-zip-file.zip`
        size = 42000 uploadeddate = `2023-03-25` uploadedby = `Development Team` status = `Complete` documenttype = `Archive` )
      ( documentid = `folder-temp` filename = `Temporary Files` isdirectory = abap_true
        size = 0 uploadeddate = `2023-04-01` uploadedby = `System` status = `Active` documenttype = `Folder`
        color = `#ffffff` bgcolor = `#30914c` )
      ( parentid = `folder-temp` documentid = `temp-001` filename = `Notes.txt` mimetype = `text/plain` isdirectory = abap_false
        url = `https://sdk.openui5.org/test-resources/sap/m/UploadSetwithTableSampleFiles/Notes.txt`
        size = 2048 uploadeddate = `2023-04-01` uploadedby = `System` status = `Complete` documenttype = `Document`
        previewable = abap_true ) ).
    LOOP AT t_docs REFERENCE INTO DATA(doc).
      row_derive( CHANGING row = doc->* ).
    ENDLOOP.
    folder_open( `` ).

  ENDMETHOD.

ENDCLASS.