Electron構建一個文件瀏覽器應用(二)

在前一篇文章咱們已經學習到了使用Electron來構建咱們的文件瀏覽器了基礎東西了,咱們以前已經完成了界面功能和顯示文件或文件夾的功能了,想看以前文章,請點擊這個連接  。如今咱們須要在以前的基礎上來繼續完成餘下的功能,咱們以前的只完成了界面和顯示文件夾或文件。那麼這篇文章咱們須要完成以下功能:javascript

1. 若是它是一個文件夾,咱們能夠對該文件夾進行雙擊,而後打開該文件夾。
2. 當前文件夾就是剛剛咱們雙擊的那個文件夾。
3. 若是它內部有子文件夾的時候,咱們也能夠雙擊,而後重複第一步的操做步驟。css

那麼在完成這些功能以前,咱們先來整理一下咱們的js文件,那麼在整理以前,咱們先來看看咱們項目的整個目錄架構是一個什麼樣,這樣使咱們更加的清晰。而且瞭解下咱們各個文件的代碼及做用,哪些文件具體作哪些事情的。這有助於咱們更進一步來說解其餘方面的知識,使下面講解的內容更通俗易解。html

下面是咱們的整個目錄架構的結構以下:java

|----- 項目的根目錄
|  |--- image                # 存放文件夾或文件圖標
|  |--- node_modules         # 全部的依賴包
|  |--- .gitignore           # github排除文件
|  |--- app.css              # css文件
|  |--- app.js               # 功能實現代碼的文件
|  |--- index.html           # html頁面
|  |--- main.js              # electron界面啓動代碼
|  |--- package.json         

如上就是咱們在第一篇文章中項目目錄結構,咱們首先來看下咱們的main.js 代碼,該js文件最主要的是 啓動electron桌面應用的程序,咱們在package.json已經默認指定了該js文件就是咱們默認要加載的文件。node

package.json 代碼以下:linux

{
  "name": "electron-filebrowser",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "async": "^3.1.0",
    "fs": "0.0.1-security",
    "osenv": "^0.1.5",
    "path": "^0.12.7"
  }
}

而後咱們來看下咱們的main.js 文件代碼,該文件的代碼最主要的是 處理electron界面的啓動,以下代碼所示:git

'use strict';

// 引入 全局模塊的 electron模塊
const electron = require('electron');

// 建立 electron應用對象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定義變量 對應用視窗的引用 
let mainWindow = null;

// 監聽視窗關閉的事件(在Mac OS 系統下是不會觸發該事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 將index.html 載入應用視窗中
app.on('ready', () => {
  /*
   建立一個新的應用窗口,並將它賦值給 mainWindow變量。
  */
  mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true
    }
  });

  // 添加以下代碼 能夠調試
  mainWindow.webContents.openDevTools();

  // 載入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 當應用被關閉的時候,釋放 mainWindow變量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

app.js 代碼以下:github

'use strict';

// 在應用中加載node模塊
const fs = require('fs');
const osenv = require('osenv');

// 引入 aysnc模塊
const async = require('async');
// 引入path模塊
const path = require('path');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 來獲取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}

function inspectAndDescribeFile(filePath, cb) {
  let result = {
    file: path.basename(filePath),
    path: filePath,
    type: ''
  };
  fs.stat(filePath, (err, stat) => {
    if (err) {
      cb(err);
    } else {
      if (stat.isFile()) { // 判斷是不是文件
        result.type = 'file';
      }
      if (stat.isDirectory()) { // 判斷是不是目錄
        result.type = 'directory';
      }
      cb(err, result);
    }
  });
}

function inspectAndDescribeFiles(folderPath, files, cb) {
  // 使用 async 模塊調用異步函數並收集結果
  async.map(files, (file, asyncCB) => {
    const resolveFilePath = path.resolve(folderPath, file);
    inspectAndDescribeFile(resolveFilePath, asyncCB);
  }, cb);
}

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板實列的副本
  let clone = document.importNode(template.content, true);
  
  // 加入文件名及對應的圖標
  clone.querySelector('img').src = `images/${file.type}.svg`;
  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

// 該函數的做用是顯示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach(displayFile);
}

/*
 該函數的做用是:獲取到用戶我的文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();

  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('對不起,您沒有加載您的home folder');
    }
    console.log(files);
    /*
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
    */
    inspectAndDescribeFiles(folderPath, files, displayFiles);
  });
}

window.onload = function() {
  main();
};

如上app.js 代碼就是咱們頁面的功能代碼,代碼看起來有點混亂,所以咱們須要把該文件中的代碼分離出來。也就是說以前咱們全部功能性的代碼都放在咱們的app.js代碼裏面,這樣之後業務愈來愈複雜的時候,js代碼未來會愈來愈臃腫,所以如今咱們須要把該文件的功能性代碼邏輯拆分開來。所以咱們須要把它分紅三個js文件,app.js, fileSystem.js, userInterface.js.web

app.js 仍是負責入口文件。
fileSystem.js 負責處理對用戶計算機中的文件或文件夾進行操做。
userInterface.js 負責處理界面上的交互。shell

所以 fileSystem.js 代碼以下:

'use strict';

const fs = require('fs');
// 引入 aysnc模塊
const async = require('async');
// 引入path模塊
const path = require('path');
const osenv = require('osenv');

function getUsersHomeFolder() {
  return osenv.home();
}

// 使用 fs.readdir 來獲取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}

function inspectAndDescribeFile(filePath, cb) {
  let result = {
    file: path.basename(filePath),
    path: filePath,
    type: ''
  };
  fs.stat(filePath, (err, stat) => {
    if (err) {
      cb(err);
    } else {
      if (stat.isFile()) { // 判斷是不是文件
        result.type = 'file';
      }
      if (stat.isDirectory()) { // 判斷是不是目錄
        result.type = 'directory';
      }
      cb(err, result);
    }
  });
}

function inspectAndDescribeFiles(folderPath, files, cb) {
  // 使用 async 模塊調用異步函數並收集結果
  async.map(files, (file, asyncCB) => {
    const resolveFilePath = path.resolve(folderPath, file);
    inspectAndDescribeFile(resolveFilePath, asyncCB);
  }, cb);
}

module.exports = {
  getUsersHomeFolder,
  getFilesInFolder,
  inspectAndDescribeFiles
};

fileSystem.js文件把咱們以前的app.js中的 getUsersHomeFolder(), getFilesInFolder(), inspectAndDescribeFile(),
及 inspectAndDescribeFiles() 函數分離出來了,而且使用 module.exports 對暴露了 getUsersHomeFolder(), getFilesInFolder(), inspectAndDescribeFiles() 這三個函數。

userInterface.js 文件中的代碼以下:

'use strict';

let document;

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板實列的副本
  let clone = document.importNode(template.content, true);
  
  // 加入文件名及對應的圖標
  clone.querySelector('img').src = `images/${file.type}.svg`;
  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

// 該函數的做用是顯示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach(displayFile);
}

function bindDocument (window) {
  if (!document) {
    document = window.document;
  }
}

module.exports = {
  bindDocument,
  displayFiles
};

在userInterface.js中咱們暴露了 bindDocument 和 displayFiles 兩個函數,bindDocument該函數的做用是將window.document 上下文傳遞進去,displayFiles函數的做用是將全部的文件顯示出來。

接下來就是咱們的app.js 代碼了,該文件須要引入咱們剛剛 fileSystem.js 和 userInterface.js 的兩個文件,所以咱們的app.js 文件代碼被簡化成以下代碼:

'use strict';

const fileSystem = require('./fileSystem');
const userInterface = require('./userInterface');

/*
 該函數的做用是:獲取到用戶我的文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  // 把window上下文傳遞進去
  userInterface.bindDocument(window);

  const folderPath = fileSystem.getUsersHomeFolder();

  fileSystem.getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('對不起,您沒有加載您的home folder');
    }
    fileSystem.inspectAndDescribeFiles(folderPath, files, userInterface.displayFiles);
  });
}

window.onload = function() {
  main();
};

index.html 代碼和以前同樣不變,以下代碼:

<html>
  <head>
    <title>FileBrowser</title>
    <link rel="stylesheet" href="./app.css" />
  </head>
  <body>
    <template id="item-template">
      <div class="item">
        <img class='icon' />
        <div class="filename"></div>
      </div>
    </template>
    <div id="toolbar">
      <div id="current-folder">
      </div>
    </div>
    <!-- 該div元素是用來放置要顯示的文件列表信息-->
    <div id="main-area"></div>
    <script src="./app.js" type="text/javascript"></script>
  </body>
</html>

最後咱們在咱們的項目根目錄中運行 electron . 同樣也能夠看到以前同樣的界面,以下所示:

一:實現文件夾雙擊功能

那麼咱們如上代碼重構完成後,咱們如今須要實現咱們對文件夾雙擊的功能了,那麼須要實現該功能的話,咱們須要在 userInterface.js 中添加以下幾個函數來處理這些事情。

1. 首先新增一個 displayFolderPath 函數,該函數的做用是更新界面中的當前文件夾路徑。
2. 還須要新增 clearView 函數,該函數的做用是:顯示在主區域中的當前文件夾中的文件和清除文件夾。
3. 還須要新增一個 loadDirectory函數,該函數的做用是:根據指定文件夾的路徑,獲取計算機中該路徑下的文件或文件夾信息,
並將其顯示在應用界面的主區域中。
4. 修改displayFiles函數,該函數的做用是:在文件夾圖標上監聽事件來觸發加載該文件夾中的內容。

所以咱們的 userInterface.js 代碼就變成以下了:

'use strict';

let document;

// 引入 fileSystem.js 中的模塊代碼
const fileSystem = require('./fileSystem');

// 更新當前文件夾路徑的函數
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerText = folderPath;
}

// 移除 main-area div元素中的內容
function clearView() {
  const mainArea = document.getElementById('main-area');
  let firstChild = mainArea.firstChild;
  while (firstChild) {
    mainArea.removeChild(firstChild);
    firstChild = mainArea.firstChild;
  }
}

// 更新文本框中文件夾路徑,而且更新主區域中的內容
function loadDirectory(folderPath) {
  return function (window) {
    if (!document) {
      document = window.document;
    }
    // 更新最上面的文本框中的文件夾路徑
    displayFolderPath(folderPath);
    fileSystem.getFilesInFolder(folderPath, (err, files) => {
      // 先清除主區域中的內容
      clearView();
      if (err) {
        throw new Error('sorry, you could not load your folder');
      }
      fileSystem.inspectAndDescribeFiles(folderPath, files, displayFiles);
    });
  }
}

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板實列的副本
  let clone = document.importNode(template.content, true);
  
  // 加入文件名及對應的圖標
  clone.querySelector('img').src = `images/${file.type}.svg`;

  // 須要判斷若是該文件是目錄的話,須要對目錄圖片綁定雙擊事件
  if (file.type === 'directory') {
    clone.querySelector('img').addEventListener('dblclick', () => {
      // 咱們雙擊完成後,就須要加載該文件夾下全部目錄的文件
      loadDirectory(file.path)();
    }, false);
  }

  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

// 該函數的做用是顯示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach(displayFile);
}

function bindDocument (window) {
  if (!document) {
    document = window.document;
  }
}

module.exports = {
  bindDocument,
  displayFiles,
  loadDirectory
};

如上就是咱們的 userInterface函數添加的代碼,咱們仔細看下也是很是簡單的代碼,我相信你們都可以理解掉了,首先 displayFolderPath 這個函數,該函數的做用是顯示咱們 左上角文本框的路徑,好比以下所示的:

而後咱們 clearView 這個函數,該函數的做用是:清除主區域中全部的文件或文件夾。

最後就是 loadDirectory 這個函數,該函數首先會調用displayFolderPath函數,來更新咱們的左上角輸入框文件路徑。期次就是調用 fileSystem.inspectAndDescribeFiles 函數來從新渲染主區域中的全部文件。

給這個 displayFile 函數,判斷當前目錄是否是文件夾,若是是文件夾的話,對該文件夾圖標綁定了雙擊事件,雙擊後咱們又調用了 loadDirectory 函數,從新更新左上角輸入框文件夾路徑,而且從新渲染主區域中的內容。

如今咱們須要修改app.js 中的代碼了,讓他調用 userInterface.js 文件中的loadDirectory函數。從新初始化主區域內容,且更新左上角的輸入框的文件夾路徑。所以咱們的app.js 代碼更改爲以下所示:

'use strict';

const fileSystem = require('./fileSystem');
const userInterface = require('./userInterface');

/*
 該函數的做用是:獲取到用戶我的文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  // 把window上下文傳遞進去
  userInterface.bindDocument(window);

  const folderPath = fileSystem.getUsersHomeFolder();

  /*
  fileSystem.getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('對不起,您沒有加載您的home folder');
    }
    fileSystem.inspectAndDescribeFiles(folderPath, files, userInterface.displayFiles);
  });
  */
  userInterface.loadDirectory(folderPath)(window);
}

window.onload = function() {
  main();
};

如上全部的文件更改完成後,咱們如今再來重啓咱們的應用程序,在項目中的根目錄 運行 electron . 命運後便可重啓,當咱們雙擊應用中的某個文件夾時,就能看到工具條中當前文件夾路徑發送改變了,而且該文件夾下全部子目錄也會更新了。
首先咱們看下咱們頁面初始化的時候,以下所示:

而後當咱們點擊我 工做文檔 文件夾時候,會看到會更新工具條中的路徑,而且子目錄也會獲得更新了。以下所示:

二:實現快速搜索

如今咱們目錄中有不少不少文件及文件夾,可是當咱們想要找某個文件夾的時候,咱們很不方便,所以咱們如今須要一個搜索框,咱們只要搜索下咱們目錄下的某個文件就能找到,所以咱們如今須要這麼一個功能,所以第一步咱們須要在咱們應用項目中的右上角添加一個搜索框。咱們須要實現以下功能:

1. 在咱們的工具條的右上角添加一個搜索框。
2. 引入一個內存搜索庫來對文件或文件夾進行搜索。
3. 將當前文件夾中的文件和文件夾信息加入搜索索引。
4. 用戶開始搜索時,會對主區域顯示的文件進行過濾。

2.1 在工具條中增長搜索框

首先咱們須要在 index.html 中的 current-folder div元素後面插入以下代碼:

<input type="search" id="search" results="5" placeholder="Search" />

所以html部分代碼變成以下:

<div id="toolbar">
  <div id="current-folder">
  </div>
  <input type="search" id="search" results="5" placeholder="Search" />
</div>

而後咱們在咱們的 app.css 代碼中加入以下樣式:

#search {
  float: right;
  padding: 0.5em;
  min-width: 10em;
  border-radius: 3em;
  margin: 2em 1em;
  border: none;
  outline: none;
}

加入後,咱們再來運行下咱們的應用程序,使用命令 electron . ,會看到以下圖所示:

2.2 引入一個內存搜索庫

上面咱們已經經過html+css在咱們的工具條右側添加了一個搜索框,如今咱們要作的事情就是經過一個搜索庫來對文件或文件夾列表進行搜索。值得幸運的是,網上已經有一款叫 Iunr.js 客戶端搜索庫了,它支持對文件或文件夾列表進行索引,咱們能夠經過索引進行搜索。所以咱們須要在咱們項目中根目錄命令行中來安裝該模塊了,以下npm命令安裝:

npm i lunr --save

如今咱們須要在咱們的項目根目錄下新建一個叫 search.js 文件,該文件最主要的做用是處理搜索。

所以咱們再來看下咱們整個目錄架構變成以下這個樣子了:

|----- 項目的根目錄
|  |--- image                # 存放文件夾或文件圖標
|  |--- node_modules         # 全部的依賴包
|  |--- .gitignore           # github排除文件
|  |--- app.css              # css文件
|  |--- app.js               # 功能實現代碼的文件的入口
|  |--- index.html           # html頁面
|  |--- main.js              # electron界面啓動代碼
|  |--- fileSystem.js        # 處理文件操做的js
|  |--- userInterface.js     # 處理應用程序界面的js
|  |--- search.js            # 處理文件搜索的js
|  |--- package.json      

想要了解 lunr 庫的使用方法,請看這篇文章(http://www.uedsc.com/lunr-js.html

所以咱們的search.js 代碼以下:

'use strict';

// 引入 lunr 包進來
const lunr = require('lunr');

let index;

// 重置搜索的索引函數

function resetIndex() {
  index = lunr(function() {
    this.field('file');
    this.field('type');
    this.ref('path');
  });
}

// 添加對文件的索引,用於後續的搜索
function addToIndex(file) {
  index.add(file);
}

// 對一個指定的文件進行查詢
function find(query, cb) {
  if (!index) {
    resetIndex();
  }
  const results = index.search(query);
  cb(results);
}

module.exports = {
  addToIndex,
  find,
  resetIndex
};

如今咱們搜索庫已經引入了,而且代碼也編寫完成後,咱們如今要作的事情就是如何來監聽咱們的搜索框的事件了,那麼須要使用的'keyup' 事件來監聽,所以咱們須要在 userInterface.js 文件中添加以下一個函數,而且把該函數暴露出去。以下代碼:

// 監聽搜索函數
function bindSearchField(cb) {
  document.getElementById('search').addEventListener('keyup', cb, false);
}

module.exports = {
  bindDocument,
  displayFiles,
  loadDirectory,
  bindSearchField
}

如上代碼就是監聽搜索框 的keyup的事件了,當咱們每次搜索的時候,鼠標keyup的時候,就會觸發一個cb函數,那麼觸發cb函數的時候,咱們須要獲取輸入框的值,而後把該值傳遞進去查詢。若是沒有值的話,那麼不進行文件搜索,若是有值的話,咱們須要進行文件搜索,如今要實現這個搜索,咱們須要完成以下事情:

1. 當咱們的搜索框沒有值的時候,確保全部的文件都顯示在主區域中。
2. 當搜索框中有值的時候,咱們須要根據該值進行查詢及過濾且顯示出來。
3. 當搜索到某個文件夾的時候,咱們須要將該文件夾的全部的內容顯示在主區域中,而且重置該索引值。
4. 當有新文件要顯示在主區域中,須要將它添加到索引中。

所以首先咱們須要在咱們的 userInterface.js 文件中須要引入咱們的search.js ,引入後咱們就能夠訪問search模塊了。

引入完成後,咱們須要改js中的 loadDirectory 函數,該函數的做用咱們以前也講解過,就是更新左側文本框的文件路徑,而且更新主區域中的內容,所以在該函數內部,咱們每次調用該函數的時候都須要重置搜索索引,這樣作的目的是能實現只針對當前文件夾內容進行搜索。所以loadDirectory函數代碼改爲以下:

// 引入search模塊
const search = require('search');

// 更新文本框中文件夾路徑,而且更新主區域中的內容
function loadDirectory(folderPath) {
  return function (window) {
    if (!document) {
      document = window.document;
    }

    // 添加劇置搜索索引的函數調用
    search.resetIndex();

    // 更新最上面的文本框中的文件夾路徑
    displayFolderPath(folderPath);
    fileSystem.getFilesInFolder(folderPath, (err, files) => {
      // 先清除主區域中的內容
      clearView();
      if (err) {
        throw new Error('sorry, you could not load your folder');
      }
      fileSystem.inspectAndDescribeFiles(folderPath, files, displayFiles);
    });
  }
}

如上更改完成後,咱們須要更改下 displayFile 函數,在該函數中添加以下功能:

1. 把文件添加到搜索索引中的代碼。
2. 將文件路徑保存在圖片元素的data-filePath屬性中,這樣的話,在文件過濾的時候,咱們能夠根據該屬性值來過濾或顯示元素。

所以 displayFile 函數代碼變成以下:

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板實列的副本
  let clone = document.importNode(template.content, true);
  
  // 將文件添加到搜索索引中
  search.addToIndex(file);

  // 將文件路徑保存在圖片元素的data-filePath屬性中
  clone.querySelector('img').setAttribute('data-filePath', file.path);

  // 加入文件名及對應的圖標
  clone.querySelector('img').src = `images/${file.type}.svg`;

  // 須要判斷若是該文件是目錄的話,須要對目錄圖片綁定雙擊事件
  if (file.type === 'directory') {
    clone.querySelector('img').addEventListener('dblclick', () => {
      // 咱們雙擊完成後,就須要加載該文件夾下全部目錄的文件
      loadDirectory(file.path)();
    }, false);
  }

  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

如上displayFile函數的做用是把全部的文件夾顯示在主區域中,而且綁定了文件夾的雙擊事件,而且咱們把文件添加到索引中了,而且將文件路徑保存到圖片元素的 data-filePath屬性中。

如今咱們須要新增一個函數用於處理在界面上顯示搜索的結果的函數,該函數首先要獲取到主區域中顯示的文件或文件夾的路徑,而後判斷該路徑是否知足用戶在搜索框中條件,若是知足的話,直接過濾掉不知足的條件,顯示出來,所以咱們在 userInterface.js文件中後面新增一個函數,好比叫 filterResults函數。

function filterResults(results) {
  // 獲取搜索結果中的文件路徑用於對比
  const validFilePaths = results.map((result) => {
    return result.ref;
  });
  const items = document.getElementsByClassName('item');
  for (let i = 0; i < items.length; i++) {
    let item = items[i];
    let filePath = item.getElementsByTagName('img')[0].getAttribute('data-filePath');
    // 文件路徑匹配搜索結果
    if (validFilePaths.indexOf(filePath) !== -1) {
      item.style = null;
    } else {
      item.style = 'display:none;'; // 若是沒有匹配到,則將其掩藏掉 
    }
  }
}

上面函數編寫完成後,咱們還須要編寫一個函數用於處理重置過濾結果的狀況,當咱們搜索框值爲空的時候,咱們須要調用該函數來顯示文件出來。咱們能夠把該函數名叫 resetFilter函數。

function resetFilter() {
  const items = document.getElementsByClassName('item');
  for (let i = 0; i < items.length; i++) {
    items[i].style = null;
  }
}

函數編寫完成後,咱們還須要將該兩個函數暴露出去,所以代碼以下:

module.exports = {
  bindDocument,
  displayFiles,
  loadDirectory,
  bindSearchField,
  filterResults,
  resetFilter
};

userInterface.js 代碼已經完成後,咱們如今須要在咱們的app.js上更新代碼,如今咱們的app.js文件須要作以下事情:

1. 在界面上須要監聽搜索框。
2. 將搜索關鍵詞傳給Iunr搜索工具。
3. 將搜索工具處理完的結果顯示到界面上。

所以app.js 代碼變成以下:

'use strict';

const fileSystem = require('./fileSystem');
const userInterface = require('./userInterface');
// 引入search模塊
const search = require('./search');

/*
 該函數的做用是:獲取到用戶我的文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  // 把window上下文傳遞進去
  userInterface.bindDocument(window);

  const folderPath = fileSystem.getUsersHomeFolder();

  // 更新文本框中文件夾路徑,而且更新主區域中的內容, 而且重置搜索索引的函數調用
  userInterface.loadDirectory(folderPath)(window);
  // 監聽搜索框值的變化
  userInterface.bindSearchField((event) => {
    const val = event.target.value;
    if (val === '') {
      // 若是搜索框中的值爲空的狀況下, 重置過濾結果
      userInterface.resetFilter();
    } else {
      /*
       若是搜索框中有值的話,將該值傳遞到搜索模塊的find函數處理並過濾結果顯示在界面上
      */
      search.find(val, userInterface.filterResults);
    }
  });
}

window.onload = function() {
  main();
};

如今咱們在咱們文件的根目錄搜索 tuge 的內容的話,能夠看到以下所示的過濾:以下所示:

而後如今咱們把搜索條件清空的話,咱們又能夠看到全部的目錄了,以下所示:

如今咱們再繼續點擊 工做文檔,進入該目錄後,咱們在該目錄下繼續搜索 18 這樣的時候,咱們能夠看到以下所示:

咱們接着清空內容後,咱們就能夠看到 工做文檔 目錄下的全部內容了,以下圖所示:

三:添加後退功能

如上咱們已經實現了文件或文件夾搜索功能及顯示用戶文件夾的詳細路徑,而且還能夠雙擊文件夾進入內部的功能,如今咱們還須要實現後退功能,咱們雙擊完成後進入文件夾內部,咱們這個時候想後退的話不能後退,所以咱們如今要實現這樣的功能。

想實現回退功能,咱們又沒有和瀏覽器那樣有後退按鈕,所以咱們這邊想實現後退功能,咱們能夠點擊文件夾路徑後退便可,也就是說咱們須要實現每一個文件夾路徑可點擊功能。好比以下圖對應的路徑點擊便可:

實現當前文件夾路徑可單擊

如上圖一串路徑,咱們但願點擊某一個路徑的時候,但願切換到對應的文件夾那個地方,而且顯示文件夾下的全部內容,就像網頁連接同樣的。咱們看下咱們左側的路徑的源碼能夠看到,它是一個div元素顯示路徑的,以下圖所示:

咱們來看下咱們的工具條上顯示當前文件夾路徑的代碼,在咱們的userInterface.js中,有個函數,以下代碼:

// 更新當前文件夾路徑的函數
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerText = folderPath;
}

把路徑賦值給id爲current-folder元素上,如今咱們須要作的是,再也不是把路徑賦值該div元素上,而是但願把該路徑傳遞給另外一個函數去,而後該函數使用split分隔符分割('/')這樣的變成一個數組,而後遍歷這個數組,把它對應的文件名使用span標籤,也就是每一個文件夾路徑使用span標籤包圍起來,而且在該span標籤上設置一個屬性,好比叫 data-path 這樣的,該屬性值就是該文件夾的具體路徑,如今咱們須要作這些事情了。

咱們須要接受folderPath路徑字符串的函數,好比咱們如今叫 convertFolderPathIntoLinks 這個函數,把它放到咱們的 userInterface.js 中,該函數最主要作的事情就是分割路徑,而且把各個路徑使用span標籤包圍起來,所以咱們須要引用path模塊進來,

const path = require('path');

在Mac OS 和 linux中,路徑分隔符是斜槓(/), 可是在windows中,它是反斜槓(\), 所以咱們須要使用path模塊的 path.sep 來獲取分隔符。

convertFolderPathIntoLinks函數代碼以下:

function convertFolderPathIntoLinks(folderPath) {
  const folders = folderPath.split(path.sep);
  const contents = [];
  let pathAtFolder = '';
  folders.forEach((folder) => {
    pathAtFolder += folder + path.sep;
    const str = `<span class="path" data-path="${pathAtFolder.slice(0, -1)}">${folder}</span>`;
    contents.push(str);
  });
  return contents.join(path.sep).toString();
}

如上函數接收 文件夾的路徑做爲參數,咱們會根據該路徑上的分隔符將其變爲一個包含路徑上文件名的列表,有該列表,咱們就能夠對其中每一個文件夾名建立一個span標籤。每一個span標籤包含一個名爲path的類名以及一個data-path屬性保存當前文件夾的路徑。最後文件夾的名字以文本的形式包含在span標籤中。咱們把全部的span標籤存入一個數組 contents中,最後使用 分隔符分割,把數組的內容以字符串的形式返回回來。

咱們以前的 displayFolderPath 函數的代碼以下:

// 更新當前文件夾路徑的函數
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerText = folderPath;
}

如今咱們要把代碼改爲以下了:

// 更新當前文件夾路徑的函數
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerHTML = convertFolderPathIntoLinks(folderPath);
}

那麼這樣的話,咱們的 div中的id爲 current-folder 元素就包含span標籤了,而且每一個span標籤上都有 data-path 這個屬性,咱們再來運行下咱們的代碼能夠看到以下所示:

span標籤咱們已經弄好了,咱們如今須要的是再增長一個函數,該函數的做用是用於監聽單擊文件夾名的操做,並將單擊的文件夾路徑傳遞給回調函數,回調函數接收到咱們單擊的文件夾路徑後,將其傳遞給負責顯示文件夾內容的函數。咱們把該函數名取爲:bindCurrentFolderPath. 所以代碼添加以下:

function bindCurrentFolderPath() {
  const load = (event) => {
    const folderPath = event.target.getAttribute('data-path');
    loadDirectory(folderPath)();
  }
  const paths = document.getElementsByClassName('path');
  for (var i = 0; i < paths.length; i++) {
    paths[i].addEventListener('click', load, false);
  }
}

// 更新當前文件夾路徑的函數
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerHTML = convertFolderPathIntoLinks(folderPath);
  // 調用綁定事件
  bindCurrentFolderPath();
}

如上代碼實現完成後,咱們再來從新下咱們的運用程序,就能夠看到效果了,咱們點擊某一個文件夾進去的時候,再點擊路徑上的某一個文件夾便可返回到上一個頁面,就能夠看到效果了。

咱們須要添加一個簡單的樣式,就是說就是當咱們鼠標光標懸停到span元素上的時候,將鼠標光標顯示爲手狀形式。咱們須要在咱們的app.css 添加以下代碼:

span.path:hover {
  opacity: 0.7;
  cursor: pointer;
}

四:實現打開其餘的文件(好比文本文件,視頻及文檔等)

咱們以前全部的功能是針對文件夾來作的,如今咱們還要針對文件,圖片,視頻,文檔等這些類型的文件實現點擊功能,要實現這些,咱們須要實現單擊文件的功能。
所以咱們須要在咱們的 userInterface.js 文件中,在displayFile函數中,添加else代碼;以下代碼:

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板實列的副本
  let clone = document.importNode(template.content, true);
  
  // 將文件添加到搜索索引中
  search.addToIndex(file);

  // 將文件路徑保存在圖片元素的data-filePath屬性中
  clone.querySelector('img').setAttribute('data-filePath', file.path);

  // 加入文件名及對應的圖標
  clone.querySelector('img').src = `images/${file.type}.svg`;

  // 須要判斷若是該文件是目錄的話,須要對目錄圖片綁定雙擊事件
  if (file.type === 'directory') {
    clone.querySelector('img').addEventListener('dblclick', () => {
      // 咱們雙擊完成後,就須要加載該文件夾下全部目錄的文件
      loadDirectory(file.path)();
    }, false);
  } else {
    // 不屬於文件夾之外的文件,好比文本文件,文檔等
    clone.querySelector('img').addEventListener('dblclick', () => {
      fileSystem.openFile(file.path);
    })
  }

  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

如上代碼,咱們的else裏面實現了除了文件夾之外的文件也能夠進行雙擊操做,而且在回調函數中咱們調用了 fileSystem.js 模塊中的openFile函數,所以咱們須要實現 openFile函數的代碼。那麼該函數須要調用 shell API。shell API 可以使用系統默認的應用打開URL,文件以及其餘類型的文檔。所以在fileSystem.js 文件中,咱們須要添加以下代碼:

const shell = require('electron').shell;

function openFile(filePath) {
  // 調用shell API的openItem函數
  shell.openItem(filePath);
}

而且導出函數中須要添加 openFile ,以下所示:

module.exports = {
  getUsersHomeFolder,
  getFilesInFolder,
  inspectAndDescribeFiles,
  openFile
};

如今咱們基本功能已經完成了,咱們如今還須要當咱們鼠標移動到文件或文件夾的時候,咱們須要讓他變成鼠標的形式,所以咱們須要在咱們的app.css中加以下代碼:

img:hover {
  opacity: 0.7;
  cursor: pointer;
}

如今咱們全部的代碼已經完成了,咱們接下來從新啓動下咱們的應用程序,在項目的根目錄中使用命令 electron . 便可打開咱們的應用程序了。咱們能夠雙擊點擊不屬於文件夾的文件,也可使用咱們的shell默認方式打開文件了。以下所示:

github源碼查看

相關文章
相關標籤/搜索