Page Example

Archive Manager Sample Overview

The Archive Manager sample application demonstrates how you can create and manage archive files. You can browse the filesystem, select files to be archived, start the compression process, browse the content of the archive file, extract the archive file, and control the compression or extraction process.

The user interface of the application is divided logically into 3 modes. Each mode is associated with specific main screen elements, such as header, footer, and content with list elements representing the filesystem elements.

  • Standard mode

    The standard mode is used to browse the filesystem and during the file compression. The following figure illustrates the root folders, file browsing, and file compression.

    Figure: Standard mode screens

    Root folders Browsing files Input screen Compressing files

    To create a new archive, click Create archive and select the files in the archive mode. Enter the archive name in the input screen, and click Done.The progress of the compression process is indicated by a progress bar.

    To abort the compression process, click Abort.

  • Archive mode

    The archive mode is used to select the files to be archived.

    Figure: Archive mode screen

    Archive mode

    After selecting the files, start the compression process by clicking Compress. To return to the standard mode without compression, click Back.

  • Preview mode

    The preview mode is used to browse the archives and during the file extraction. The following figure illustrates the archive browsing and file extraction.

    Figure: Preview mode screens

    Browsing archive entries Extracting confirmation Extracting files

    To browse the archives, click the zipped file you want to browse. The application goes to the preview mode and shows the content of the zipped file. The navigation path starts with the name of the browsed archive. You can browse deeper, if there are folders in the archive. However, you cannot browse another archive file that is an element of the currently browsed archive.

    You can extract ZIP files that have been prepared using any packaging software. To start the extraction process, click Extract and, in the confirmation popup, OK. Wait until the archive is extracted to a new folder. The name of the new folder corresponds to the name of the archive. If the name is already in use, the application creates another folder with the name expanded using an appropriate index in parentheses.

Source Files

You can create and view the sample application project including the source files in the IDE.

File name Description
config.xml This file contains the application information for the platform to install and launch the application, including the view mode and the icon to be used in the device menu.
css/style.css This file contains CSS styles for the application UI.
images/ This directory contains the images used to create the UI.
index.html This is a starting file from which the application starts loading. It contains the layout of the application screens.
js/ This directory contains the application code.
js/core/ This directory contains the application framework.
lib/ This directory contains external libraries (TAU library).
templates/ This directory contains the layouts of the application screens and templates for smaller UI elements.

Implementation

Application Layout

The application has a main screen layout, an archive name input screen layout, and a template for the filesystem item view.

The main screen layout consists of multiple UI elements:

  1. The main screen elements are arranged as follows:

    <!--index.html-->
    <div data-role="page" id="main">
       <!--header (with a title, navigation buttons, and navigation bar)-->
       <!--content (with a list of filesystem items)-->
       <!--footer (with action buttons)-->
       <!--popup (for displaying progress bars and messages)-->
    </div>
    
  2. The header element contains a title, 2 navigation buttons (Home and Up), and a navigation bar. The navigation buttons are visible only in the standard mode. The navigation bar with the current filesystem path is visible in the standard and preview modes. In the archive mode, the navigation bar displays a checkbox.

    <!--index.html-->
    <div data-role="header" data-position="fixed">
       <h1>Archive</h1>
       <div data-role="button" class="naviframe-button hidden" id="home-button">Home</div>
       <div data-role="button" class="naviframe-button hidden" id="up-button">Up</div>
       <div class="navigation">
          <div class="navigation-background"></div>
          <p class="navigation-path" id="navigation-path">/<span id="navigation-path-text"></span></p>
          <form id="select-all" class="hidden">
             <ul data-role="listview">
                <li class="ui-li-has-checkbox">
                   <a href="#" id="select-all-area">
                      <input type="checkbox"/>Select all
                   </a>
                </li>
             </ul>
          </form>
       </div>
    </div>
    
  3. The content element contains a list of files. Depending on the current application mode, the list can display:

    • Filesystem items without checkboxes (standard mode)
    • Filesystem items with checkboxes (archive mode)
    • Archive entries (preview mode)
    <!--index.html-->
    <div data-role="content">
       <form>
          <ul id="files-list" data-role="listview"
              class="standard-mode"></ul>
       </form>
    </div>
    

    The template being used defines the appearance of the list items:

    /* templates/row.tpl */
    <li id="row{{index}}" label="{{label}}" class="{{type}}" url="{{url}}" isDirectory="{{isDirectory}}">
       <a href="#">
          <input type="checkbox"/>
          <img class="ui-li-bigicon">{{label}}
          <div class="clickable"></div>
       </a>
    </li>
    
  4. The main screen uses 3 footers, which all contain different buttons. At any time, only a single footer is visible, depending on the current application mode:

    <!--index.html-->
    <div data-role="footer" data-position="fixed" id="footer-create"
         class="ui-disabled">
       <div data-role="button" id="create-button">Create archive</div>
    </div>
    <div data-role="footer" data-position="fixed" id="footer-compress"
         class="hidden">
       <div data-role="button" id="compress-button">Compress</div>
       <div data-role="button" id="back-compress-button">Back</div>
    </div>
    <div data-role="footer" data-position="fixed" id="footer-preview"
         class="hidden">
       <div data-role="button" id="extract-preview-button">Extract</div>
    </div>
    
  5. The pop-up element can be displayed with a progress bar during the compression and extraction process, with progress circles while preparing data to be archived, or with a message as a message popup:

    <!--index.html-->
    <div id="main-popup" data-role="popup" data-transition="none"
         data-position-to="window" class="center_title_1btn">
       <div class="ui-popup-title">
          <h1 id="main-popup-title"></h1>
       </div>
       <div class="ui-popup-text">
          <div id="total-frame" class="progress-frame hidden">
             <div data-role="progressbar" id="total-progress"></div>
             <div id="total-value" class="progress-value"></div>
          </div>
          <div id="partial-frame" class="progress-frame hidden">
             <div data-role="progressbar" id="partial-progress"></div>
             <div id="partial-value" class="progress-value"></div>
          </div>
          <div id="preprogress-frame" class="preprogress-frame">
             <div data-role="progress" data-style="circle"
                  id="preprogress-progress"></div>
          </div>
          <span id="main-popup-message" class="hidden"></span>
       </div>
       <div class="ui-popup-button-bg">
          <a data-role="button" data-rel="back" data-inline="true" id="main-popup-ok-button" class="hidden">OK</a>
          <a data-role="button" data-rel="back" data-inline="true" id="main-popup-cancel-button" class="hidden">Cancel</a>
          <a data-role="button" data-rel="back" data-inline="true" id="main-popup-abort-button" class="hidden">Abort</a>
       </div>
    </div>
    

The archive name input screen allows the user to enter a name for the selected data to be archived. If an archive with the typed name already exists or the name contains invalid characters, an appropriate message is displayed. When the name is valid, clicking Done on the keyboard starts the archiving process.

<!--index.html-->
<div data-role="page" id="name">
   <div data-role="header" id="name-header" data-position="fixed">
      <h1>Input archive name</h1>
   </div>
   <div data-role="content">
      <div id="name-text-frame">
         <div>
            <input type="text" id="name-text"
                   placeholder="Enter name" class="hidden invisible">
         </div>
      </div>
      <div id="name-error-message"></div>
   </div>
</div>

Mode Switching

The switching between the application modes is controlled by the router module defined in the js/helpers/router.js file. It provides the following methods:

  • register() method registers callbacks for a given path.

    All routes responsible for switching between modes are registered with this method in the js/views/pages/main.js file:

    function registerRoutes() 
    {
       router.register('views/modes/standard/show', showStandardMode);
       router.register('views/modes/preview/show', showPreviewMode);
       router.register('views/modes/archive/show', showArchiveMode);
    }
    
  • navigate() calls the registered callbacks later on using a proper path as parameter.

    The navigate() method can be called from anywhere in the application code. The following example shows the code that changes the current mode from standard to archive when the user clicks Create archive in the standard mode:

    /* js/views/modes/standard.js */
    function onCreateBtnClick() 
    {
       router.navigate('views/modes/archive/show');
    }
    

The following example shows other important code snippets in the router module:

/* js/helpers/router.js */
var routes = [],
    currentRoute = {},
    Error = window.Error;

function Route(load, unload) 
{
   this.load = load;
   this.unload = unload;
}

function register(path, load, unload) 
{
   if (routes[path]) 
   {
      throw new Error('Route ' + path + ' is already registered');
   }
   routes[path] = new Route(load, unload);
}

function navigate(path, options) 
{
   var route = routes[path];

   if (typeof route !== 'object' || typeof route.load !== 'function') 
   {
      throw new Error('Route ' + path + ' does not exist');
   }

   if (typeof currentRoute.unload === 'function') 
   {
      currentRoute.unload();
   }

   currentRoute = route;

   return route.load(options || {});
} 

File Compression

To compress files:

  1. The compression process starts when the user clicks Done on the keyboard displayed on the input screen, and the keyboard gets hidden. To make sure that the keyboard is not visible, the application uses code from the js/helpers/keyboard.js file.

    When initialized, the keyboard module registers an event listener, which triggers the onKeyboardChange() method every time the state of the keyboard changes. Depending on the keyboard state, the helpers.keyboard.opened or helpers.keyboard.closed event is triggered.

    function onKeyboardChange(ev) 
    {
       if (ev.state === 'on') 
       {
          e.fire('opened');
       } 
       else 
       {
          e.fire('closed');
       }
    }
    
    function init() 
    {
       window.addEventListener('softkeyboardchange', onKeyboardChange);
    }
    
  2. The js/views/pages/name.js module that controls the input screen behavior, listens for the helpers.keyboard.opened and helpers.keyboard.closed events.

    /* helpers.keyboard.closed event triggered */
    function onKeyboardClosed() 
    {
       if (enterPressed) 
       {
          enterPressed = false;
          router.navigate('views/modes/standard/show', {start: true});
       }
    }
    
    e.listeners(
    {
       'helpers.keyboard.closed': onKeyboardClosed,
    });
    
  3. Every time the onKeyboardClosed() method is called (because the keyboard gets hidden and the helpers.keyboard.closed event is triggered), the router.navigate() method is also called with the views/modes/standard/show string path as its first parameter. The start option is also set to true.

    Since the options.start returns true, both the activateStandardMode() and startArchiving() methods are called. The activateStandardMode() method simply activates the standard mode by calling the activate() method on the standardMode object (a reference to the views/modes/standard module). It triggers a pop-up with an animated circle displaying the waiting progress. The circle is shown for as long as it takes to count the data to be archived.

    /* js/views/pages/main.js */
    function activateStandardMode(animate) 
    {
       standardMode.activate(animate);
    }
    
    function startArchiving() 
    {
       popupView.showPreProgress();
       countNumberOfArchivedData();
    }
    
    function showStandardMode(options) 
    {
       if (options.start) 
       {
          activateStandardMode(false);
          /* Start archiving */
          startArchiving();
       } 
       else 
       {
          /* Show the standard mode */
          activateStandardMode(true);
       }
    }
    
    function registerRoutes() 
    {
       router.register('views/modes/standard/show', showStandardMode);
    }
    
  4. The application must know the amount of data to properly show the progress bar for each file being archived. The countNumberOfArchivedData() method calls the countNumberOfData() method from the filesystem module. It requires 2 parameters supplied by the getSelectedFiles() and getPath() methods from the fileList module. The fileList module contains methods that allow the application to manage the appearance of the main screen (including current path, filesystem list elements, and mode activation).

    At the end, the countNumberOfData() method triggers the foldersize.read event.

    /* js/views/pages/main.js */
    function countNumberOfArchivedData() 
    {
       var files = fileList.getSelectedFiles();
    
       filesystem.countNumberOfData(fileList.getPath().toString(), files);
    }
    
    function createArchive(name, sizes) 
    {
       var list = fileList.getSelectedFiles(sizes),
          archiveName = fileList.getPath().toString() +
                '/' + name + '.zip';
    
       archivingDescriptor = archive.createArchive(list, archiveName);
    }
    
    function onFolderSizeRead(ev) 
    {
       createArchive(nameView.getName(), ev.detail.sizes);
    }
    
    e.listeners(
    {
       'model.filesystem.foldersize.read': onFolderSizeRead,
    });
    
  5. The createArchive() method from the archive module is called during the foldersize.read event to create an archive. This method requires 2 parameters, a list of files to be archived and the archive name, supplied by methods from the fileList module. It returns an archivingDescriptor object that contains information about progress and is useful when there is a need to abort the compression process. The Progress object constructor is used to create an object displaying the whole compression process.

    The archive module located in js/model/archive.js file provides methods that have access to the Archive API. The module gives all necessary functionality for the compression process. It also supports browsing through an archive, extraction processes and abort action.

    /* js/model/archive.js */
    function Progress(max, hasPartialInfo) 
    {
       this.hasPartialInfo = !!hasPartialInfo;
    
       /* Total progress */
       this.id = 0;
       this.current = 0;
       this.subTotal = 0;
       this.max = max;
    
       /* Partial progress */
       this.partialId = 0;
       this.partialMax = 0;
       this.value = 0.0;
       this.filename = '';
    
       this.getPartial = getPartial;
       this.getTotal = getTotal;
    }
    
    function createArchive(files, destination) 
    {
       files = files.slice(0);
    
       var progress = null,
           len = files.length,
           i = 0,
           totalSize = 0;
    
       for (i; i < len; i += 1) 
       {
          totalSize += files[i].filesize;
       }
    
       progress = new Progress(totalSize, true);
    
       try 
       {
          progress.id = tizen.archive.open(destination, WRITE_MODE, function onCreateSuccess(archive) 
          {
             e.fire('create.started');
             e.fire('progress', progress);
             progress.archive = archive;
             progress.destination = destination;
             addToArchive(files, archive, progress);
          });
       } 
       catch (ev) 
       {
          e.fire('open.error', ev);
       }
    
       return progress;
    }
    
  6. The above example shows that before the first call to the Archive API, the application creates a progress object. The application calls the tizen.archive.open() method, which opens the archive file.

    After the archive creation operation, you can add or get files to and from the archive. The tizen.archive.open() method requires 3 mandatory parameters. The first one is a string path describing the archive file to be opened, and the second is also a string describing the file mode for the opened archive. The file mode determines which operations on the archive are available (the application can use w() for the write mode and r() for the read mode). The third parameter is a callback method to be invoked when the archive is opened successfully. It takes 1 parameter, the reference to the archive object. This callback fires the create.started() and progress() events, updates progress parameters, and calls the addToArchive() method to add files to the created archive.

    The addToArchive() method calls itself recursively as long as it is finished with each file list with file object items, which is passed as the first argument. Every time it is called, the application checks whether the compression process was aborted or whether the process is finished, and updates the progress object and calls the add() method.

    The add() method adds a new member file to the archive file. It requires a single parameter, the location of the archived file. Optional parameters include the callback method to be invoked when the adding operation is completed successfully. After completion, the application updates the progress object, calls the addToArchive() method, and triggers the progress event.

    /* js/model/archive.js */
    function addToArchive(files, archive, progress) 
    {
       var file = files.shift(),
           size = 0,
           progressHandler = null;
    
       if (progress.aborted) 
       {
          return;
       }
    
       if (!file) 
       {
          e.fire('completed', progress);
    
          return;
       }
    
       size = file.filesize;
       if (file.isDirectory) 
       {
          progressHandler = new ArchiveDirectoryProgressHandler(size, file.path, progress);
       } 
       else 
       {
          progressHandler = new ArchiveFileProgressHandler(size, progress);
       }
    
       progress.value = 0;
       progress.filename = '';
       progress.partialId = 0;
       progress.partialId = archive.add(file.path,
                                        function onAddSuccess() 
                                        {
                                           progress.subTotal += size;
    
                                           /* Add the rest of the files */
                                           addToArchive(files, archive, progress);
    
                                           e.fire('progress', progress);
                                        },
                                        function errorHandler(ev) 
                                        {
                                           progress.partialId = 0;
                                           onError(ev);
                                        },
                                        progressHandler.handle,
                                        {stripSourceDirectory: true});
    }
    
  7. The main.js module listens to events to make necessary changes in the application UI:

    • The onArchiveCreateStarted() method is called at the beginning of the compression process. It changes the pop-up appearance to show 2 progress bars, partial and overall.
    • The onArchiveProgress() method updates the progress bar values and is called once at the beginning of the compression process and every time a new file is added to the archive.
    • The onArchiveCompleted() method is called at the end of the compression process to hide the popup. It also updates the list of filesystem elements on the main screen.
    /* js/views/pages/main.js */
    function onArchiveCreateStarted() 
    {
       popupView.activateCreateProgress();
    }
    
    function onArchiveProgress(ev) 
    {
       popupView.setProgress(ev.detail);
    }
    
    function onArchiveCompleted() 
    {
       popupView.hide();
       if (fileList.getMode() === fileList.PREVIEW_MODE) 
       {
          router.navigate('views/modes/standard/show');
          fileList.getPath().goToArchiveDirectory();
       } 
       else 
       {
          filesystem.getFiles(fileList.getPath().toString());
       }
    }
    
    e.listeners(
    {
       'model.archive.progress': onArchiveProgress,
       'model.archive.create.started': onArchiveCreateStarted,
    
       'model.archive.completed': onArchiveCompleted,
    });
    

Archive Browsing

The list of filesystem items is wrapped in a <ul> element with the id="files-list" attribute. The appearance and behavior of the list is controlled by the js/views/filelist.js module.

The onFilesListClick() method is called every time a filesystem item on the content list is tapped. In the preview mode, the application triggers the selected event with the object type parameter. This parameter contains information about the selected file.

/* js/views/filelist.js */
var filesList = null;

function onFilesListClick(ev) 
{
   var element = domHelper.findParent(ev.target, 'li'),
       label = element.getAttribute('label'),
       url = element.getAttribute('url'),
       file = null;

   if (mode === ARCHIVE_MODE) 
   {
      e.fire('selection.changed');

      return;
   }

   file = 
   {
      label: label,
      url: url,
      type: ''
   };

   if (element.classList.contains(FOLDER_CLASS)) 
   {
      file.type = FOLDER_CLASS;
   } 
   else if (element.classList.contains(FILE_CLASS)) 
   {
      file.type = FILE_CLASS;
   } 
   else if (element.classList.contains(ZIP_CLASS)) 
   {
      file.type = ZIP_CLASS;
   }

   e.fire('selected', {file: file, mode: mode});
}

function bindEvents() 
{
   filesList.addEventListener('click', onFilesListClick);
}

function init() 
{
   filesList = document.getElementById('files-list');

   bindEvents();
}

When the preview mode is active, the views.filelist.selected event is listened to by the js/views/modes/preview.js module, and triggers the onFilelistElementSelected() method, which has the following condition statements:

  • Starting browsing: executed when the selected list element is an archive and the application is in the standard mode.
  • Browsing deeper: executed when the selected list element is a folder and the application is in the preview mode.

    When browsing deeper through the archive, the application calls the goDown() method from the js/model/path.js module. The path and the UI update are realized in the same way as in the starting browsing condition.

/* js/views/modes/preview.js */
function onFilelistElementSelected(ev) 
{
   var data = ev.detail,
      fileInfo = data.file;

   if (fileInfo.type === fileList.ZIP_CLASS &&
         data.mode === fileList.STANDARD_MODE) 
   {
      archive.loadEntries(fileInfo.url);
   }

   if (fileInfo.type === fileList.FOLDER_CLASS &&
         data.mode === fileList.PREVIEW_MODE) 
   {
      fileList.getPath().goDown(fileInfo.label);
   }
}

e.listen(
{
   'views.filelist.selected': onFilelistElementSelected,
});

To handle the starting browsing condition:

  1. To start the browsing process, the application calls the loadEntries() method from the js/model/archive.js module.

    The application calls the tizen.archive.open() method from the Archive API. The archive is opened in the read mode to access the archived entries. When the archive is opened, the application uses the reference to the archive object to call the getEntries() method from the Archive API.

    function loadEntries(url) 
    {
       try 
       {
          tizen.archive.open(url, READ_MODE, function onOpenSuccess(archive) 
             {
                archive.getEntries(function onGetEntriesSuccess(entries) 
                   {
                      prepareEntriesObject(url.split('/').pop(), entries);
                   }, 
                   onError);
             }, 
             onError);
       } 
       catch (ev) 
       {
          e.fire('open.error', ev);
       }
    }
    
  2. Now the application has access to the list of archived objects used by the prepareEntriesObject() method. The purpose of this method is to prepare 2 important data structures: entriesArray and entriesMap.

    The application uses the API methods only once to obtain information about the entries in the browsed archive. This information is useful to browse deeper in the archive contents. The entriesArray contains the entries object from the root level of the archive. Each entry has a list property that contains an entries object from the second level of the archive for this root entry, and so on. As a result, the array contains all information about the archive files, directories, and structure. The entriesMap array is string-indexed and contains the same objects as the entriesArray array. It gives the application access to these objects using a URL path string.

    /* js/model/archive.js */
    function prepareEntriesObject(name, entries) 
    {
       var lenI = entries.length,
           lenJ = 0,
           i = 0,
           j = 0,
           entry = null,
           entryName = '',
           entrySize = 0,
           entryArray = null,
           currentArray = null,
           currentObject = null,
           currentIndex = 0,
           endsWithSeparator = false;
    
       entriesArray = [];
       entriesMap = {};
    
       for (i; i < lenI; i += 1) 
       {
          entry = entries[i];
          entrySize = entry.size;
          endsWithSeparator = SLASH_AT_THE_END_REGEXP.test(entry.name);
          entryName = entry.name.replace(SLASH_AT_THE_END_REGEXP, '');
          entryArray = entryName.split('/');
          lenJ = entryArray.length;
          currentArray = entriesArray;
          for (j = 0; j < lenJ; j += 1) 
          {
             currentIndex = findObjectInArray(currentArray, 'name', entryArray[j]);
             if (currentIndex === -1) 
             {
                currentObject = 
                {
                   name: entryArray[j],
                   list: [],
                   fullPath: entryName,
                   isFile: !((j < lenJ - 1) || endsWithSeparator),
                   isDirectory: (j < lenJ - 1) || endsWithSeparator
                };
                entriesMap[entryName] = currentObject;
                currentArray.push(currentObject);
             } 
             else 
             {
                currentArray = currentArray[currentIndex].list;
             }
          }
       }
       e.fire('entries.loaded', {name: name});
    }
    
  3. The entries.loaded event is triggered and activates the preview mode:

    /* js/views/pages/main.js */
    function activatePreviewMode(data)
    {
       previewMode.activate(data);
    }
    
    function showPreviewMode(data) 
    {
       activatePreviewMode(data);
    }
    
    function onArchiveEntriesLoaded(ev) 
    {
       router.navigate('views/modes/preview/show', ev.detail);
    }
    
    function registerRoutes() 
    {
       router.register('views/modes/preview/show', showPreviewMode);
    }
    
    e.listeners(
    {
       'model.archive.entries.loaded': onArchiveEntriesLoaded,
    });
    

    The application activates the preview mode by calling the activate() method on the previewMode object (a reference to views/modes/preview module).

    The activate() method calls the activatePreviewMode() method from the js/views/filelist.js module.

    /* js/views/modes/preview.js */
    function activate(data) 
    {
       fileList.activatePreviewMode(data);
       showFooterPreview();
    }
    
    /* js/views/filelist.js */
    function activatePreviewMode(data) 
    {
       if (!data) 
       {
          return;
       }
       mode = PREVIEW_MODE;
       path.goDown(data.name, true);
    }
    
  4. The application calls the goDown() method from the js/model/path.js module. This module contains the Path class that provides functionalities for maintaining the URL path of the current filesystem location.

    At this point, any path change in this module triggers the changedevent. This event is listened to by the js/views/filelist.js module:

    /* js/views/filelist.js */
    function onPathChanged(ev) 
    {
       var currentPath = ev.detail.toString();
    
       if (mode === STANDARD_MODE) 
       {
          if (currentPath === '') 
          {
             modelFilesystem.getStorages();
          } 
          else 
          {
             modelFilesystem.getFiles(currentPath);
          }
       } 
       else if (mode === PREVIEW_MODE) 
       {
          if (currentPath !== '') 
          {
             showFiles(archive.getEntry(currentPath).list);
          } 
          else 
          {
             showFiles(archive.getRootEntries());
          }
       }
    
       updateHeader();
       updatePath();
    }
    
    e.listeners(
    {
       'model.path.changed': onPathChanged,
    });
    
  5. If the application is in the preview mode, the showFiles() method is called. This method is responsible for updating the UI and takes the list of archived entries as a parameter. This list can be returned by the getEntry() or getRootEntries() method from the js/model/archive.js module. These methods return the entriesArray and entriesMap data structures.

    /* js/model/archive.js */
    function getEntry(path) 
    {
       return entriesMap[path];
    }
          
    function getRootEntries() 
    {
       return entriesArray;
    }
    

File Extraction

To extract files:

  1. The extraction process starts when the user clicks Extract. The Extract button behavior is handled by the js/views/modes/preview.js module:

    var extractPreviewBtn = null;
    
    function onExtractPreviewBtnClick() 
    {
       popupView.showExtractConfirm(fileList.getPath().getFullArchiveName());
    }
    
    function bindEvents() 
    {
       extractPreviewBtn.addEventListener('click', onExtractPreviewBtnClick);
    }
    
    function init() 
    {
       extractPreviewBtn = document.getElementById('extract-preview-button');
       bindEvents();
    }
    
  2. The confirmation popup is displayed by the showExtractConfirm() method from the js/views/popup.js module.

    The showExtractConfirm()method is responsible for showing the confirm popup and its appearance. It takes the name of the extracted archive as a parameter. The name is displayed in the pop-up header.

    /* js/views/popup.js */
    function showExtractConfirm(name) 
    {
       activateMessage(ZIP_ARCHIVE_EXTRACT_MESSAGE, name);
       activateOKCancelButtons();
       show();
    }
    
  3. The user can interrupt the extraction process by clicking Cancel in the popup. If they click OK, the extraction process continues and the ok.button.click event is triggered:

    /* js/views/popup.js */
    function onOkBtnClick() 
    {
       if (isExitPopup) 
       {
          isExitPopup = false;
          e.fire('exit.button.click');
       } 
       else 
       {
          e.fire('ok.button.click');
       }
    }
    
  4. The ok.button.click event is listened to by the js/views/modes/preview.js module. When the vent occurs, the application tries to resolve the name of the folder to which the archive entries are extracted:

    /* js/views/modes/preview.js */
    function onOkBtnClick() 
    {
       if (fileList.getMode() === fileList.PREVIEW_MODE) 
       {
          modelFilesystem.resolveNameForPath(fileList.getPath().getArchiveName(),
                                             fileList.getPath().getArchivePath());
       }
    }
    
    e.listen(
    {
       'views.popup.ok.button.click': onOkBtnClick,
    });
    
  5. If the application is in preview mode, the resolveNameForPath() method from the js/model/filesystem.js module is called. It takes the name of the archive (without extension) and the archive file path as parameters.

    The resolveNameForPath() method uses the js/core/core/filesystem.js module to call the dir() method to retrieve the list of files in a given location path.

    The dir() method calls a function that takes the archived list of files as a parameter. Then, the resolveName() method is called to determine the name of the folder to which the archive entries are extracted.

    The resolveName() method operates in 2 phases. First, the method iterates through the list of files and prepares the namesObject object (associative array) indexed by file names with any value. Then, the method performs the while loop that modifies the original name of the archive as long as this name is identical to the one in the namesObject object parameters. If the original name of the archive is unique for the current filesystem location, the while loop is not performed at all and the new folder has the name that corresponds to the name of the extracted archive.

    /* js/model/filesystem.js */
    function resolveName(name, files) 
    {
       var namesObject = {},
           length = files.length,
           newName = '',
           i = 0;
    
       for (i = 0; i < length; i += 1) 
       {
          namesObject[files[i].name] = true;
       }
    
       i = 2;
       newName = name;
    
       while (namesObject.hasOwnProperty(newName)) 
       {
          newName = name + ' (' + i + ')';
          i += 1;
       }
    
       e.fire('name.resolved', {name: newName});
    }
    
    function resolveNameForPath(name, path) 
    {
       coreFilesystem.dir(path, function onDirSuccess(files) 
       {
          resolveName(name, files);
       });
    }
    
  6. The resolveName() method triggers the name.resolved event with the resolved name string for the new folder as a parameter.

    The model.filesystem.name.resolved event is listened to by the js/views/modes/preview.js module. When it occurs, the application creates a new folder with the given name:

    /* js/views/modes/preview.js */
    function onNameResolved(ev) 
    {
       modelFilesystem.createDir(ev.detail.name,
                                 fileList.getPath().getArchivePath());
    }
    
    e.listen(
    {
       'model.filesystem.name.resolved': onNameResolved,
    });
    
  7. The onNameResolved() method calls the createDir() method from the js/model/filesystem.js module. This method takes the resolved name for the new folder and a location path where this folder is to be created as parameters.

    The createDir() method uses the js/core/core/filesystem.js module to call the openDir() method, which opens the folder located on the given path. It also calls a callback function to call the createDir() method from the js/core/core/filesystem.js module.

    /* js/model/filesystem.js */
    function createDir(name, path) 
    {
       coreFilesystem.openDir(path, function onOpenDirSuccess(dir) 
          {
             e.fire('dir.created',
                    {
                       dir: coreFilesystem.createDir(dir, name)
                    });
          }, 
          onError);
    }
    
  8. The callback function triggers the dir.created event containing the reference to the new folder.

    The model.filesystem.dir.created event is listened to by the js/views/modes/preview.js module. When it occurs, the application tries to extract the archived entries into the new created folder.

    /* js/views/modes/preview.js */
    function onDirCreated(ev) 
    {
       archive.extractArchive(fileList.getPath().getFullArchivePath(),
                              ev.detail.dir);
    }
    
    e.listen(
    {
       'model.filesystem.dir.created': onDirCreated
    });
    
  9. The extractArchive() method from the js/core/core/archive.js module is called. This method takes the full string path to the archive file and the reference to the folder as parameters.

    Before the first call to the Archive API, the application creates a progress object, which is used during the whole extraction process. The application then calls the tizen.archive.open() method from the Archive API. This method opens the archive file in the read mode. When the archive file is opened, the application triggers the extract.started event and calls the extractAll() method of the archive object from the API.

    The extractAll() method extracts every file from the archive file to a given directory. It takes the location of the folder where the extracted files are stored as parameter.

    /* js/core/core/archive.js */
    function extractArchive(archiveFullPath, destinationFolder) 
    {
       var progress = new Progress(100, false);
    
       try 
       {
          tizen.archive.open(archiveFullPath, READ_MODE, function onOpenSuccess(archive) 
             {
                e.fire('extract.started', archive.extractAll(
                   destinationFolder,
                   function extractSuccess() 
                   {
                      e.fire('completed', progress);
                   },
                   onError,
                   function onProgress(operationId, value, filename) 
                   {
                      progress.partialId = operationId;
                      progress.value = value;
                      progress.filename = filename;
                      e.fire('progress', progress);
                   }));
             }, 
             onError);
       } 
       catch (ev) 
       {
          e.fire('open.error', ev);
       }
    }
    
  10. The main.js module listens various events to make the necessary changes in application UI:

    • The onArchiveExtractStarted() method is called to change the popup appearance to show a progress bar.
    • The onArchiveProgress() method updates the progress bar values for each progress callback during the extraction process.
    • The onArchiveCompleted() method is called at the end of the extraction process to hide the progress popup.
    /* js/views/pages/main.js */
    function onArchiveExtractStarted(ev) 
    {
       extractionTaskId = ev.detail;
       popupView.showExtractProgress();
    } 
    
    function onArchiveProgress(ev) 
    {
       popupView.setProgress(ev.detail);
    }
    
    function onArchiveCompleted() 
    {
       popupView.hide();
       if (fileList.getMode() === fileList.PREVIEW_MODE) 
       {
          router.navigate('views/modes/standard/show');
          fileList.getPath().goToArchiveDirectory();
       } 
       else 
       {
          filesystem.getFiles(fileList.getPath().toString());
       }
    }
    
    e.listeners(
    {
       'model.archive.progress': onArchiveProgress,
    
       'model.archive. extract.started ': onArchiveExtractStarted,
       'model.archive.completed': onArchiveCompleted,
    });