1
\$\begingroup\$

I recently created a file manager plugin for the TinyMCE, CKEditor and Redactor editors to be integrated within the Yii 1.xx framework. It provides you the ease of managing your files online that you frequently need to include, attach or preview inside your editor to create an article on your website or even a full content page.

These files can be:

  • Documents (*.doc, *.txt, *.pdf, *.docx ... e.t.c)
  • Videos
  • Compressed files
  • Images

My concern is not specific to any editor that I have developed this particular plugin for but more general regarding the main section that displays the files inside the dedicated directory that is browsed when we access that plugin via a button on the editor.

I have divided the js part of plugin into 2 files first one idows-file-browser.js for adding the dialog window functionality and binds the top bar menu buttons functionality, and the second one idows-file-list.js that contains the functions that are used to browse the directory files and bind the necessary js functions with the files like download, preview, cut, paste, delete, drag, drop etc.

I have 2 different views for listing the files:

  • Thumbnail
  • List

The button on the editor calls the controller/action to open dialog window like below from the plugin.js file.

editor.windowManager.open({
  title: 'IDOWS Tinyii File Manager.',
  icon: 'idows-ico idows-i-folder',
  file: '/filemanager/index?editor=',
  width: 850,
  height: 600,
  buttons: [{
    text: 'Close',
    onclick: 'close'
  }]
});

Inside the view file, I have this code in the head which calls the init() method from idows-file-browser.js file:

   idowsFileBrowser.init(
     {
        assetsDir:"' . $editorAssets . '",
        uploadDir:"' . str_replace ( "\\" , '/' , Yii::app ()->params['UPLOAD_DIR'] ) . '"
    }

It initializes the main dialog button bindings and then calls the switchView() method which further calls the $('#fileview').idowsFileList() plugin in the idows-file-list.js to list the directory with the selected view, see below I have added only concerned code from the file idows-file-browser.js.

var idowsFileBrowser = new function() {
    this.options = {
      iframeOpened: false,
      timeoutStore: false,
      defaultView: 'thumbnail',
      viewScript: 'thumbview',
      assetsDir: undefined,
      uploadDir: '',
      currentDirectory: '',
      phpViewScript: '/filemanager/thumbview',
    };



    /**
     * init the filetree 
     * This is the main entry point for the filemanager script and is 
     * called inside the views/filemanager/dialog.php file to initiate the filemanager.
     * @param {JSON} options
     */
    this.init = (options) => {

      //call the default view for the files listing
      idowsFileBrowser.switchView({
        view: idowsFileBrowser.options.defaultView
      });

      //bind menu buttons functionality
      idowsFileBrowser.menuButtonBinding();

      //init the modals if no editor manager detected
      let windowmanager = idowsFileBrowser.tinyMCEWindowManager();
      (windowmanager === null) && idowsFileBrowser.initializeBootstrapModals();
    };

    /**
     * @description Switches between views, list and thumbnail
     * @param {JSON} switchOptions
     * @returns {void}
     */
    this.switchView = (switchOptions) => {

      let defaultSwitchOptions = {
        subfolder: '',
        sortBy: 'sort_name',
        sortOrder: 'ASC'
      };

      //set options for the switchview
      switchOptions = idowsFileBrowser.setDefaults(switchOptions, defaultSwitchOptions);

      //add switch view even mappings
      let switchViewEvents = {

        'list': function() {
          //update defaults for the idows file manager
          idowsFileBrowser.options = idowsFileBrowser.setDefaults({
            currentDirectory: switchOptions.subfolder,
            defaultView: switchOptions.view,
            viewScript: 'listview',
            phpViewScript: '/filemanager/listview'
          }, idowsFileBrowser.options);

          $("#current_template").val(switchOptions.view);
          $("#thumbView").parent().removeClass('idows-active');
          $("#listView").parent().addClass('idows-active');
          $('#fileview').idowsFileList({
            root: switchOptions.subfolder,
            script: idowsFileBrowser.options.phpViewScript,
            idowsFileBrowser: idowsFileBrowser,
            viewType: switchOptions.view,
            sortBy: switchOptions.sortBy,
            sortOrder: switchOptions.sortOrder
          }, function(file, li) {

            var filepath = file.split("/");
            var file_name = filepath[filepath.length - 1];

            $('#image_name').html(file_name);
            $(li).parent().siblings().removeClass('selected');
            $(li).parent().addClass('selected');
          });
        },
        'thumbnail': function() {
          //update defaults for the idows file manager
          idowsFileBrowser.options = idowsFileBrowser.setDefaults({
            currentDirectory: switchOptions.subfolder,
            defaultView: switchOptions.view,
            viewScript: 'thumbview',
            phpViewScript: '/filemanager/thumbview/'
          }, idowsFileBrowser.options);

          $("#current_template").val(switchOptions.view);
          $("#thumbView").parent().addClass('idows-active');
          $("#listView").parent().removeClass('idows-active');


          $('#fileview').idowsFileList({
            root: switchOptions.subfolder,
            script: idowsFileBrowser.options.phpViewScript,
            idowsFileBrowser: idowsFileBrowser,
            viewType: switchOptions.view,
            sortBy: switchOptions.sortBy,
            sortOrder: switchOptions.sortOrder
          }, function(file, li) {
            var filepath = file.split("/");
            var file_name = filepath[filepath.length - 1];

            $('#image_name').html(file_name);
            $(li).parent().siblings().removeClass('selected');
            $(li).parent().addClass('selected');
          });
        }
      };

      //call the selected view event
      (switchViewEvents.hasOwnProperty(switchOptions.view)) && switchViewEvents[switchOptions.view].call(this);


      //toggle setting menu
      idowsFileBrowser.toggleSettingsMenu();
    };

Now in the switchView() function when the idowsFileList() is called, it is passed several parameters along with the phpViewScript (in my case it would be action of the controller like controller/action) where it scans the directory and creates desired (Thumbnail/List view) using the HTML view file, this controller/action is called via $.post inside populate() function which appends the HTML response in the dialog.

The idows-file-list.js files concerned sections are below:

if (jQuery)
  (function($) {

    $.fn.extend({
      /**
       * @description Initializes the plugin on the selected dom object with the given optoins
       * @param {JSON} listOptions
       * @returns {void}
       */
      idowsFileList: function(listOptions) {

        'use strict';

        //filetree object
        var fileListObj = this;


        // Get the initial file list
        fileListObj.listFiles($(this), listOptions.root);

        /**
         * @description Sends post request to populate file tree view requested
         * @param {JSON} options
         * @returns {jqXHR}
         */
        this.populate = (options) => {
          return $.post(listOptions.script, {
            dir: options.dir,
            sortBy: listOptions.sortBy,
            sortOrder: listOptions.sortOrder
          }, (data) => {
            $(options.elem).removeClass('wait').append(data);
          });
        };

        /**
         * @description Creates file list 
         * @param {Dom Object} elem
         * @param {text path} dir
         * @returns {void}
         */
        this.listFiles = (elem, dir) => {

          //init tree load request to null
          let fileLoadRequest = null;

          //send request to controller action to popoulate the trees
          fileLoadRequest = fileListObj.populate({
            elem: elem,
            dir: dir
          });

          //avoid concurrent request to same view
          fileListObj.abortPreviousTreeLoads(fileLoadRequest);

          // after successfull view load
          fileLoadRequest.done(function() {
            //........
           //the rest part of binding the functionality with the files
          });
        };

      }
    });
  })(jQuery);

The PHP concerned section is the one which actually scans the directory and then creates the list of the files and prints the HTML:

/**
 * actionThumbView : Displays directory structure in thumbnail view
 * @param type $sortBy
 * @return type
 */

public function actionThumbView() {

  //actual root for uploads
  $mainRoot = $this - > getRealPathUploadDir();

  //get post params
  list($sortBy, $targetDirectory, $sortOrder) = $this - > getParams();

  //scan directory for files and folders
  $itemList = scandir(realpath(Yii::app() - > basePath.DIRECTORY_SEPARATOR.
    '..'.$targetDirectory));

  //if the target directory and the root directory is the same then dont
  //populate . & .. else populate .. to go back
  //get filtered files and folders from the ignore case array
  //get files and folders list along with the .. to make the back link
  $itemList = (basename($targetDirectory) == basename($mainRoot)) ? $this - > getFilteredArray($itemList) : $this - > getFilteredArray($itemList, false);

  //sort the items in the array
  $sortedItemList = $this - > sortItems($sortBy, $sortOrder, $targetDirectory, $itemList);
  $key = array_search('..', $sortedItemList);

  if ($key > 0) {
    unset($sortedItemList[$key]);
    array_unshift($sortedItemList, '..');
  }

  $this - > renderPartial('thumb-view', [
    'itemList' => $sortedItemList,
    'targetDirectory' => $targetDirectory,
    'mainRoot' => $mainRoot
  ], false, true);
}

At this point, I would like you to open the plugin DEMO and try to experience the functionalities refresh, open folder, go back, sort, thumbview, listview each time you use any of these the inner section is loaded again which means every time the idowsFileList() is called and every time the dir is scanned by calling scandir.

  1. Keeping in mind if there are large number of files say 1K I won't be able to list the all the files neither would be a good approach, and I would need to limit them to a specific number like scroll pager does or a normal pagination, this is the only thing that I am left with otherwise the plugins is complete.

  2. Won't it be a better approach if I use a JSON file to store all the directory structure rather than using scandir as the scandir will read the disk every time when I need to reload the directory structure? Plus it would help me achieve the above 1st point in a better way.

(Note: There is no MySQL or any other database that is used in the plugin) neither would I like to go for it just for optimizing directory listing and avoiding excess Disk Read OP. If you think I should use a database at this point instead of a JSON file with the directory structure tree, please enlighten me.

Maybe I am totally wrong to think of the way I am thinking, but I feel lucky to be a part of this community as obviously, either you call me stupid dumb or anything but still, you guys can show me a bigger picture of what should actually be done.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.