第三方庫js庫選擇dojo,其官網地址為https://dojotoolkit.org/,git地址為https://github.com/dojo/dojo,demo地址為https://demos.dojotoolkit.org/demos/,如果打不開,可以多重新整理幾次。
因為使用ArcGIS API for js開發,接觸到了dojo,dojo是一個非常優秀的js框架庫,包含的內容非常全,做單頁面Web應用程式是一個非常不錯的選擇。
Main.js檔案主要還是設定electron的一些引數,程式碼如下。
var electron = require("electron"); var app = electron.app; var BrowserWindow = electron.BrowserWindow; var path = require("path"); var Menu = electron.Menu; var myMainWindow; function CreateWindow() { // 新建主窗體,設定圖示、載入主頁面 myMainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true, backgroundThrottling: false }, show: false }); myMainWindow.maximize(); myMainWindow.setIcon(path.join(__dirname, "Res/Images/Ico64.png")); myMainWindow.loadFile(path.join(__dirname, "Index.html")); myMainWindow.show(); //把electron帶的選單隱藏 Menu.setApplicationMenu(null); //遮蔽警告 process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' myMainWindow.on("closed", function () { myMainWindow = null; }); } app.on("ready", CreateWindow); // Quit when all windows are closed. app.on("window-all-closed", function () { if (process.platform !== "darwin") { app.quit() } }); app.on("activate", function () { if (myMainWindow === null) { CreateWindow() } });
我們在Potree Desktop程式碼中的Main.js檔案的基礎上上做了修改。透過引用electron,得到electron、app、BrowserWindow、path、Menu等引用,並定義了CreateWindow函式。
該函式是Main.js最主要的函式,在程式碼中,我們建立了一個BrowserWindow窗體,並最大化該窗體。設定了窗體圖示、載入的頁面等。最後呼叫窗體的Show函式,開啟窗體。BrowserWindow窗體自帶了一個選單,透過Menu.setApplicationMenu(null);隱藏electron自帶的選單。當窗體關閉的時候,設定myMainWindow變數的值為null。
後面的程式碼主要針對全域性App,當系統環境準備好的時候,呼叫CreateWindow函式。當所有的窗體都關閉後,退出App。當App被啟用的時候,如果myMainWindow變數為null,則呼叫CreateWindow函式。這些邏輯基本上都是從Potree Desktop中複製過來的,修改了下變數名稱,邏輯未變。
程式碼執行Mian.js,例項化BrowserWindow窗體,載入Index.html,此時系統就進入了Index.html頁面。
我們的目的是做一個Web單頁面應用程式,樣式以及操作方式要儘量能貼近普通的桌面應用程式。整體佈局包括選單欄、工具欄、左側工程樹以及中間的主顯示區。
首先引用外部的js和css檔案,包括dojo、Potree定義的檔案以及我們系統中自己定義的一些檔案,程式碼如下。
<link rel="stylesheet" type="text/css" href="./libs/potree/potree.css"> <link rel="stylesheet" type="text/css" href="./libs/jquery-ui/jquery-ui.min.css"> <link rel="stylesheet" type="text/css" href="./libs/openlayers3/ol.css"> <link rel="stylesheet" type="text/css" href="./libs/spectrum/spectrum.css"> <link rel="stylesheet" type="text/css" href="./libs/jstree/themes/mixed/style.css"> <link rel="stylesheet" href="Res/dojo/dijit/themes/claro/claro.css" /> <link rel="stylesheet" href="Res/dojo/dojox/grid/resources/claroGrid.css" /> <link rel="stylesheet" href="Res/dojo/dojox/form/resources/CheckedMultiSelect.css" /> <link rel="stylesheet" href="Res/dojo/dojox/form/resources/RangeSlider.css" /> <link rel="stylesheet" href="Res/dojo/dojox/widget/ColorPicker/ColorPicker.css" /> <link rel="stylesheet" href="Index.css" /> <link rel="stylesheet" href="AppUI/AppMenuUI.css" /> <link rel="stylesheet" href="AppUI/AppToolBarUI.css" /> <script> if (typeof module === 'object') { window.module = module; module = undefined; } </script> <script src="./libs/jquery/jquery-3.1.1.min.js"></script> <script src="./libs/spectrum/spectrum.js"></script> <script src="./libs/jquery-ui/jquery-ui.min.js"></script> <script src="./libs/other/BinaryHeap.js"></script> <script src="./libs/tween/tween.min.js"></script> <script src="./libs/d3/d3.js"></script> <script src="./libs/proj4/proj4.js"></script> <script src="./libs/openlayers3/ol.js"></script> <script src="./libs/i18next/i18next.js"></script> <script src="./libs/jstree/jstree.js"></script> <script src="./libs/potree/potree.js"></script> <script src="./libs/plasio/js/laslaz.js"></script> <script src="./libs/three.js/build/three.js"></script> <script src="./libs/three.js/build/three.js"></script>
接下來定義對electron和Nodejs中一些功能的引用。
<script> var Electron = require('electron'); var ElectronDialog = require('electron').remote.dialog; var NodeFS = require('fs'); var NodePath = require('path'); var NodeChildProcess = require('child_process'); </script>
ElectronDialog可彈出本地對話方塊,包括訊息對話方塊,是否對話方塊等,類似於.Net中的MessageBox。NodeFS模組可對檔案進行操作,類似於.Net中的File類。NodePath模組可檔案路徑進行操作,類似於.Net中的Path類。NodeChildProcess模組可呼叫本地的exe檔案,並可以傳入引數,捕捉輸出資訊等。
接下來是使用dojo庫的一些配置以及Index.html頁面直接引用的一些js檔案。
<script> var dojoConfig = { async: true, parseOnLoad: false, //用於頁面載入時立即載入的JS依賴 deps: ["dojo/parser"], callback: function (parser) { }, //載入一個模組的請求超時時間,如果超時說明載入模組失敗 waitSeconds: 10, //如果為true可以避免模組快取(原理就是在請求模組的URL加上當前時間戳) //cacheBust: true, } </script> <script src="Res/dojo/dojo/dojo.js"></script> <script src="Framework/URLHelper.js"></script> <script src="Framework/DateFormat.js"></script> <script src="Index.js"></script>
Index.html檔案最後,是該頁面的佈局程式碼,如下所示。
<body class="claro"> <table id="UI_Main_Table"> <tr style="height:30px"> <td padding:0"> <div id="UI_AppMenuUI_Div"></div> </td> </tr> <tr style="height:30px"> <td padding:0"> <div id="UI_AppToolBarUI_Div"></div> </td> </tr> <tr> <td style="padding:0;"> <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="gutters:true, liveSplitters:false" style="width: 100%; height: 100%; margin: 0; padding: 0; "> <div id="UI_AppTreeUI_Div" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="minSize:20, region:'leading', splitter:true" style="width: 260px;"></div> <div id="UI_Center_BorderContainer" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="region:'center'" style="width: 100%; height: 100%; margin: 0; padding: 0; "> <div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center',splitter:true"> <div class="potree_container" style="height:100%"> <div id="potree_render_area"></div> </div> </div> </div> </div> </td> </tr> </table> </body>
我們在body中直接定義了一個Table作為根元素,該表格分三行,第一行選單欄,第二行工具條,第三行就是主顯示區。如果想在底部增加狀態列,可再增加一行。
主顯示區使用了dojo中定義的dijit/layout/BorderContainer,在BorderContainer中新增了兩個dijit/layout/ContentPane,其中一個ContentPane的region屬性設定為leading,另外一個ContentPane的region屬性設定為center,並都設定splitter=true。這樣主顯示區域左側會新增一個區域,寬度為260px,剩下的為中間區域,並且兩區域可左右調整大小。
選單我們使用dojo中定義的dijit/MenuBar,如果我們新增檔案子選單,html程式碼如下。
<div style="font-size:14px"> <div data-dojo-type="dijit/MenuBar"> <div data-dojo-type="dijit/PopupMenuBarItem"> <span>檔案</span> <div data-dojo-type="dijit/DropDownMenu" data-dojo-attach-point="UI_File_DropDownMenu"></div> </div> </div> </div>
在js檔案中,我們可以例項化一個dijit/MenuItem,新增到UI_File_DropDownMenu中。
CreateMenuItem: function () { var myMenuItem = new MenuItem({ label: "開啟檔案", iconClass: "AppMenuUIMenuItemIcon FileOpenIcon" }); var myThis = this; var myEventHander = on(myMenuItem, "click", function () { myThis._OnClick(); }); this._EventHanderArray.push(myEventHander); return myMenuItem; }, //點選命令按鈕執行的函式 _OnClick: function () { var myFilePathArray = ElectronDialog.showOpenDialogSync(null, { title: "選擇工程檔案", properties: ["openFile"], filters: [ { name: '工程檔案', extensions: ['project'] } ] }); if (myFilePathArray == null) { return; } var myProject = new Project(); myProject.Open(myFilePathArray[0]); this._Application.SetProject(myProject); },
this.UI_File_DropDownMenu.addChild(new FileNewCommand({}, this._Application).CreateMenuItem());
工具條使用dojo中定義的dijit/Toolbar,html程式碼定義如下。
<div> <div data-dojo-type="dijit/Toolbar" data-dojo-attach-point="UI_Toolbar"> <button data-dojo-type="dijit/form/Button" data-dojo-attach-point="UI_TopView_Button" data-dojo-props="iconClass:'AppToolBarUIButtonIcon TopViewIcon',showLabel:false"> 上檢視 </button> <button data-dojo-type="dijit/form/Button" data-dojo-attach-point="UI_BottomView_Button" data-dojo-props="iconClass:'AppToolBarUIButtonIcon BottomViewIcon',showLabel:false"> 下檢視 </button> <span data-dojo-type="dijit/ToolbarSeparator"></span> <button data-dojo-type="dijit/form/Button" data-dojo-attach-point="UI_FullExtent_Button" data-dojo-props="iconClass:'AppToolBarUIButtonIcon FullExtentIcon',showLabel:false"> 全圖 </button> </div> </div>
在js檔案中,可以具體定義Button按鈕點選後執行的函式,程式碼如下。
//點選全圖按鈕執行的函式 on(this.UI_FullExtent_Button, "click", function () { myThis._Application.Viewer.fitToScreen(); });
一些邏輯較為複雜的工具,我們就要單獨定義了,類似於定義選單,我們會定義一個工具按鈕。我們以新建工程為例,js程式碼如下。
define([ "dojo/_base/declare", "dojo/on", "dijit/MenuItem", "dijit/form/Button" ], function ( declare, on, MenuItem, Button ) { return declare("ProjectNewCommand", null, { _Application: null, _EventHanderArray: null, //建構函式 constructor: function (args, pApplication) { declare.safeMixin(this, args); this._Application = pApplication; this._EventHanderArray = []; }, //建立MenuItem CreateMenuItem: function () { var myMenuItem = new MenuItem({ label: "新建工程", iconClass: "AppMenuUIMenuItemIcon ProjectNewIcon" }); var myThis = this; var myEventHander = on(myMenuItem, "click", function () { myThis._OnClick(); }); this._EventHanderArray.push(myEventHander); return myMenuItem; }, //建立Button CreateButton: function () { var myButton = new Button({ label: "新建工程", iconClass: "AppToolBarUIButtonIcon ProjectNewIcon", showLabel: false, }); var myThis = this; var myEventHander = on(myButton, "click", function () { myThis._OnClick(); }); this._EventHanderArray.push(myEventHander); return myButton; }, //點選命令按鈕執行的函式 _OnClick: function () { var myThis = this; require(["WebRoot/CoreUI/Projects/ProjectNewDialog"], function (Dialog) { var myDialog = new Dialog({}); myDialog.ShowDialog(); myDialog.on("ProjectCreated", function (e) { myThis._Application.SetProject(e.Project); }); }); }, //預設銷燬函式 destroy: function () { for (var i = 0; i < this._EventHanderArray.length; i++) { this._EventHanderArray[i].remove(); } this._EventHanderArray = []; }, }); });
程式碼中包含了CreateMenuItem和CreateButton函式,分別返回MenuItem和Button,點選這兩個UI都是執行_OnClick函式,也就是說,我們當前定義的ProjectNewCommand.js,例項化得到ProjectNewCommand物件後,呼叫CreateMenuItem函式獲取的按鈕可以新增到選單上,呼叫CreateButton函式獲取的按鈕可以新增到工具條上,且點選後,兩個按鈕的行為一致。
var myProjectNewCommand = new ProjectNewCommand({}, myApplication); myAppToolBarUI.UI_Toolbar.addChild(myProjectNewCommand.CreateButton(), 0);
系統整體佈局、選單欄,工具條、左側工程區域以及中間顯示區域都定義好了之後,我們就要定義Viewer了。定義的時候,我們依然要參考Potree Desktop中例項化Viewer的程式碼,並根據實際需求修改。
var myPotreeRenderArea = document.getElementById("potree_render_area"); var myViewerArgs = { noDragAndDrop: true, }; var myViewer = new Potree.Viewer(myPotreeRenderArea, myViewerArgs); myViewer.setEDLEnabled(true); myViewer.setFOV(60); myViewer.setPointBudget(3 * 1000 * 1000); myViewer.setMinNodeSize(0); myViewer.loadSettingsFromURL(); myViewer.setDescription(""); myViewer.setControls(myViewer.earthControls);