桌面应用程序已经消亡?如果从Node-Webkit项目在Github上的Star数量来看的话,结果并不是那么悲观。虽然现在越来越多的程序是在云上部署,还是有很多的工作需要在我们本地的电脑上完成,类似文本编辑工具,Markdown编辑器,游戏等等,都还需要离线状态下运行。
之前我就说过,我个人非常的喜欢Markdown标记语言,如果能在本地实现一个简单的Markdown编辑器的话,应该也是一件不错的事情。本文将简单的介绍一些Node Webkit的基础知识,我们将基于Windows进行演示,但是可以非常简单的部署到别的系统中。
什么是Node WebKit
Node Webkit是Chromium的一个分离版本,Chromium是著名的Google Chrome浏览器的开源基础(The open source base of Google Chome)。使用NW,我们可以使用最常见的HTML,CSS和JavaScript来创建可以运行中Windows,Max OSX,Linux桌面上的应用程序。也许,你可以使用HTML,CSS,JavaScript来创建强大的Web应用程序,但是,如果我们想对系统层面进行操作,比喻说修改系统上的某一个文件,目前你无法实现。使用NW,可以很方便实现上面的任务,这也是NW强大的部分,因为NW使用了Node.js,依赖NodeJS的模块,比喻说,fs(对系统中的文件进行操作的模块)和request(实现对各种Web Service的请求,而不需要考虑跨域的问题),基于对模块的实用,你可以实现很多之前无法完成对任务。还有振奋人心的是,你甚至可以实用很多的本地数据库的操作(NodeJS已经包含了很多对数据库进行操作的模块,比喻说MySQL),而且,因为它是分离的Chromium,所以也是支持localStorage和sessionStorage的,可以非常简单的实现持久化。
开始做点正事吧
移步到Node Webkit Github主页,根据你自己的系统下载NW。本文中,我将使用Windows(X64)版本,NodeJS已经中NW中包含了,但是如果你本地已经安装了NodeJS了的话,也是没有问题到,你也可以继续实用npm命令来安装依赖包。
zip文件下载后,解压,然后创建一个 package.json 文件,这个文件中需要包含你对应用程序的相关的配置,例如:版本,名字,需要安装的模块,Webkit的工作栏是否该显示等信息,下面是我的文件的内容。
{ "name": "markdown_editor", "main": "./html/index.html", "window": { "toolbar": true, "show": true, "icon": "./img/icon.png"
}, "dependencies": { "marked": "^0.3.3"
}
}
name: 应用程序的名字,这个名字不能带空格。
main:应用程序启动后,需要加载的HTML文件。
window:Webkit窗口的对象信息
toolbar:定义Webkit的工具栏是否该显示,工具栏包含一个刷新按钮,一个地址栏,一个可以打开Chromium 开发者工具的按钮。
show:打开应用程序后,窗口是否该打开。某些场景下,不需要打开窗口程序,比喻说,你只想让程序运行为一个系统托盘图标。
icon:窗口图标。
dependencies:依赖的NodeJS模块
package.json可以包含更多的选项,你可以参考下面的链接。
在项目的根目录下,我创建了4个文件夹:css,html,js和img,当然,你已经明白这4个文件夹代表的含义,
在img文件中,我增加了一个名字为'icon.png'的图片(如上面的package.json描述对应)。下面,你可以看到其他2个文件的代码块
html/index.html
<!DOCTYPE html><html><head>
<title>Markdown Editor</title>
<link rel="stylesheet" type="text/css" href="../css/screen.css" media="screen" />
<script src="../js/jquery/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript" src="../js/main.js"></script></head><body>
<textarea name="markdown" id="editor" class="md_editor"></textarea>
<div class="md_result">
</div></body></html>
css/screen.css
html, body{ height:98%;
}body{ font-family:Arial;
}a{ text-decoration:none !important;
}.md_editor{ float:left; width:50%; height:100%; resize:none; padding:0; margin:0;
}.md_result{ float:left; width:49%; box-sizing:border-box; padding:10px 10px 13px 10px; margin:0; height:100%; overflow-y:scroll;
}
你可以创建js/main.js(内容会在下面逐渐完善)。
上面的操作是一个最简单的配置,目前这个程序还看到任何的有用的功能,如果你点击"nw.exe",package.json会被读取,程序会解析到应该首先加载"index.html",截止目前,页面中只会包含一个文本编辑器(text editor)和位于右边的一个空的div,接下来,我们会完善左边的编辑器,让它包含一个Markdown 标记语言编辑器,然后对应的HTML展现中右边的div中。
增加Markdown解析器
在这一节中,我们首先要增加的是一个Markdown解析器,利用这个解析器,当你在左边输入内容的时候,内容都会得到解析,并在右边的div中以HTML的形式进行展现。回头,再来看package.json,我们中dependencies这个部分增加了marked依赖,现在需要做的是,打开项目的根目录(包含package.json的目录),在这个目录中,执行 ** npm install ** (请确保npm能正常运行)。NPM会坚持项目的依赖项,然后从NPM仓库中获取,并下载到本地。执行结束后,观察项目目录,会看到一个名字为node_modules的目录,这个目录里已经包含了marked这个依赖项。
现在,marked依赖已经安装,我们可以在程序中调用了,首先,我们需要在 index.html中的部分包含此脚本。
<script type="text/javascript">global.window = window;global.$ = $;global.gui = require('nw.gui');
init();
</script>
上面的代码中,我们为global全局变量,增加了 window 变量,$(jQuery)变量和gui变量(我们可以使用这个变量来控制窗口的样式,比喻说,Webkit窗体的菜单栏)。这样做的原因是,NodeJS上下文中并不包含这些变量,NODEJS文件中,你不可以使用window.document的方式来进行调用,但是如果你执行全局变量的指定的话,你可以使用global.window.document方式来进行调用。
现在中文件js/main.js中,增加 init() 方法,目前为止,这个方法还是空的,我们再创建一个名字为js/editor.js,这个文件包含Markdown编辑器的相关逻辑,代码中包含如何的代码段
exports.reload = function(){ var marked = require("marked");
marked.setOptions({ gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: false
}); var resultDiv = global.$('.md_result'); var textEditor = global.$('#editor'); var text = textEditor.val();
resultDiv.html(marked(text));
};
该文件中,我们增加reload() 给 exports 变量,这个变量会在require()方法被调用时候返回。而 require() 方法会在你试图加载a.js或者NODEJS模块时候被调用。这个方法调用 marked模块然后设置它的属性,其中最重要的选项是 sanitize,如果这个选项设置为true的话,Markdown中的HTML属性会被编码,这个不会是你希望的样子,因为Markdown不可避免的与HTML一起混合编写。
resultDiv是包含HTML代码展示的元素,而 textEditor是我们的文本区域,最后一行代码是将解析完毕的HTML代码展现在resultDiv中。
接下来,我们编辑 main.js ,下面是 main.js 部分的代码:
function init(){ global.$(global.window.document).ready(function(){ var editor = require("./../js/editor.js"); var textEditor = global.$('#editor');
textEditor.bind('input propertychange', function() {
editor.reload();
});
});
}
当窗体加载完成后,init方法将被调用,editor文件将被 requied,然后 raload() 方法会被调用。editor.js文件,我们使用相对路径的方式来进行加载。然后,编辑器(textEditor变量)会被调用。我们需要绑定一个文本编辑器的input propertychange 事件,达到编辑器中任何文本修改的操作都会使得 reload() 方法得到执行,新的HTML会展现在右边的DIV中。
图示:目前为止文本编辑的运行状态,HTML已经得到正确的展现。
为程序增加菜单
在这一部分,我们将为顶层的Webkit窗口增加一个菜单,这个菜单包含 "New","Open","Save" 和"Exit",我们首先要创建一个 menu.js 文件。文件内容如下:
exports.initMenu = function(){ var win = global.gui.Window.get(); var menubar = new global.gui.Menu({ type: 'menubar' }); var fileMenu = new global.gui.Menu();
fileMenu.append(new global.gui.MenuItem({
label: 'New',
click: function() {
}
}));
fileMenu.append(new global.gui.MenuItem({
label: 'Open',
click: function() {
}
}));
fileMenu.append(new global.gui.MenuItem({
label: 'Save',
click: function() {
}
}));
fileMenu.append(new global.gui.MenuItem({
label: 'Exit',
click: function() { global.gui.App.quit();
}
}));
menubar.append(new global.gui.MenuItem({ label: 'File', submenu: fileMenu}));
win.menu = menubar;
};
上面的代码中,win 对象得到请求调用。如果需要改变Webkit窗口对象的话,这个对象就需要获取到,比喻说现在,增加一个菜单。一个菜单对象被创建出来,而且增加了不同选项到子菜单,现在只有Exit菜单做一些真实的事情,关闭事件被调用时,会有动作需要执行。你需要增加这部分代码到 main.js 的 init()方法中。
接下来,我们需要做 editor.js 中增加2个方法。
exports.loadText = function(text){ var textEditor = global.$('#editor');
textEditor.val(text);
exports.reload();
};
exports.loadFile = function(file){ var fs = require('fs');
fs.readFile(file, 'utf8', function (err,data) { if (err) { return console.log(err);
}
exports.loadText(data);
});
};
loadText() 会替换当前左侧编辑区域的内容,当文本替换后, reload() 会被调用,所以Markdown可以直接被解析为展现的HTML文档,做右侧的div中显示。而loadFile()方法是加载一个给定的地址的文件,读取内容,并显示在文本区域中。
现在我们可以实现 文件打开 的业务了,我们首先在 index.html中增加下面的代码。
<input style="display:none;" id="openFileDialog" type="file" />
这是一个隐藏的文件选择对话框,当"Open"按钮按下的时候,我们让这个对话框打开并显示。在editor.js中增加如下代码:
exports.chooseFile = function(name, callback) { var chooser = global.$(name);
chooser.change(function(evt) {
callback(global.$(this).val());
});
chooser.trigger('click');
};
该方法会得到文件对话框的名字,name对应的就是上面定义的#openFileDialog,第二个参数是我们选择要打开的文件后,要执行的方法,要执行成功,你需要在 menu.js 文件中 "require" editor.js ,然后在initMenu()方法中要做相应的调整。
var editor = require("./../js/editor.js");
接下来,我们可以在menu.js中完成打开文件的方法。
fileMenu.append(new global.gui.MenuItem({ label: 'Open',
click: function() {
editor.chooseFile("#openFileDialog", function(filename){
editor.loadFile(filename);
});
}
}));
这一段代码调用editor.js中的 chooseFile() 方法,如果你选择了文件的话,文件的路径会传递给loadFile()方法,然后文本区域会替换。
接下来,我们可以实现"New"命令,新建按钮按下的时候,左边的文本区域会被置空,右边的区域也会移除,在上面我们开发的代码的基础上,我们可以通过几行简单的代码来实现该功能:
fileMenu.append(new global.gui.MenuItem({ label: 'New',
click: function() {
editor.loadText("");
}
}));
最后一个要实现的功能,我们可以实现菜单中的"Save"按钮,当这个按钮被按下时,一个新文件对话框会打开,你可以指定保存的文件的位置。首先要做的是在index.html中增加一个hidden域,你可以把它放在打开文件的下面。
<input style="display:none;" type="file" id="saveFileDialog" nwsaveas="file.md" />
nwsaveas用来设置保存的文字的默认名字,然后我们在editor.js中,编写如下的代码:
fileMenu.append(new global.gui.MenuItem({ label: 'Save', click: function() {
editor.chooseFile("#saveFileDialog", function(filename){ var fs = require('fs'); var textEditor = global.$('#editor');
fs.writeFile(filename, textEditor.val(), function(err) { if(err) { console.log(err);
} else { console.log("The file was saved!");
}
});
});
}
}));
当Save按钮按下时,我可以使用chooseFile()方法,文件路径和文件名字输入后,fs模块会加载,文本区域的内容会通过writeFile()方法保存到文件中。
截止目前我们编写了一个简陋但是可以运行的Markdown编写工具,我们可以看一下解析来,可以做一些什么。
其他的功能
文件后缀
我们的程序需要处理的时.md文件,所以我们需要做的是,读取传输给程序的arguments,可以在main.js的init()方法中进行操作:
if(global.gui.App.argv.length > 0){
editor.loadFile(global.gui.App.argv[0]);
}
多选行,进行Tab
Notepad++ 这一类的程序有一个非常好用的功能是,你可以同时选择多行,然后按下Tab键时,多行同时移动,按下 Shift+Tab的时候,进行相反的移动,这里我们可以使用一个非常好用的JavaScript库,Taboverride,你需要做的是,在index.html中包含它,然后在main.js中的init()方法中进行编写。
tabOverride.set(global.window.document.getElementsByTagName('textarea'));
完结
我们已经实现了一个很好的简单的编辑奇,当然,还需要一些其他的功能,比喻说:
区分Save和Save As
现在当你点击Save按钮的时候,总会有一个保存文件的对话框出现,但是当你点击Save As按钮后,程序应该记住,你之前保存的位置,然后进行一个直接的保存。
关闭时候保存
目前,如果你正在编辑一个文件,某种情况下,关闭应用程序,不会有任何的提示,你需要保存一个变量changed,用来标记系统是不是有改动,如果有改动的话,要弹出提示,提示用户是否保存。
快捷键
现在系统还缺少快捷键的操作,比喻说常用的Ctrl+S保存,Ctrl+O打开。
打印到PDF
如果程序支持打印功能的话,就更好了,我们可以将Markdown变成PDF,然后将PDF通过打印程序打印,如果感兴趣的话也可以使用PhontomJS来进行HTML到PDF的打印。
创建一个工具条
MS Word形式的工具条也是一个非常好的功能,比喻说,我们可以 标题号,斜体,粗体,等等,放置在工具条上,方便操作
移除调试条
如果你是在进行开发,那么Debug工具条是一个非常好的帮手,但是如果你需要进行发布,你可以通过编辑package.json中的toolbar属性为false来移除.
重定向外部URL
如果你想要点击一个链接时,在Webkit窗口中打开一个外部网站的话,你可以通过捕获URL的点击,然后在默认浏览器中打开选择的URL.
打包Editor程序
如果要打包程序的话,打包成一个独立的.exe或者.app程序,你可以阅读官方的文档.
https://github.com/nwjs/nw.js/wiki/How-to-package-and-distribute-your-apps
文档中,详细描述了所有的过程.
更多文章,查看 www.wahwoo.org.
本文暂时没有评论,来添加一个吧(●'◡'●)