Desktop app với Electron phần 3 : Electron và AngularJS tạo ra app preview Markdown
Electron
5
AngularJS
23
Markdown
5
White

Mạnh Mẽ lên viết ngày 03/06/2015

Electron

Ở 2 phần trước mình đã tổng lại app desktop đầu tiên viết bằng framework Electron của Github. Các bạn có thể theo dõi lại link của 2 bài viết trước. Lần này mục tiêu sẽ cao hơn một chút, mình sẽ sử dụng Angular JS để tạo ra một app có khả nămg live preview cho nội dung markdown giống như trang Kipalog này.

Thành quả từ phần trước

Trước hết mình sẽ note lại thành quả và code của 2 phần trước để các bạn tiện theo dõi. Xong phần 2 chúng ta đã có 1 app với giao diện cửa sổ quen thuộc, có menu bên trên và context menu khi click chuột phải.
Electron

Dưới đây là code của lần lượt các file context.js

'use strict';

var remote = require('remote');
var Menu = remote.require('menu');
var MenuItem = remote.require('menu-item');

var menu = new Menu();
menu.append(new MenuItem({
  label: 'ping',
  click: function() {
    console.log('ping');
  }
}));
menu.append(new MenuItem({
  type: 'separator'
}));
menu.append(new MenuItem({
  label: 'pong',
  type: 'checkbox', checked: true,
  click: function() {
    console.log('pong');
  }
}));

window.addEventListener('contextmenu', function (e) {
  e.preventDefault();
  menu.popup(remote.getCurrentWindow());
}, false);

app.js

'use strict';

var app = require('app');
var BrowserWindow = require('browser-window');
var Menu = require('menu');

var mainWindow = null;

app.on('window-all-closed', function() {
  if (process.platform != 'darwin')
    app.quit();
});

app.on('ready', function() {

  createApplicationMenu();

  openWindow();

});

var openWindow = function(){
  mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  mainWindow.on('closed', function() {
    mainWindow = null;
  });
}

var createApplicationMenu = function() {
  var menuTemplate = [
    {
      label: 'Mdpreview',
      submenu: [
        {
          label: 'Quit',
          accelerator: 'Ctrl+Q',
          click: function () {app.quit();}
        }
      ]
    }, {
      label: 'View',
      submenu: [
        {
          label: 'Reload',
          accelerator: 'Ctrl+R',
          click: function() {
            BrowserWindow.getFocusedWindow().reloadIgnoringCache();
          }
        },
        {
          label: 'Toggle DevTools',
          accelerator: 'Alt+Ctrl+I',
          click: function() {
            BrowserWindow.getFocusedWindow().toggleDevTools();
          }
        }
      ]
    }
  ];

  var menu = Menu.buildFromTemplate(menuTemplate);
  Menu.setApplicationMenu(menu)
}

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sample Markdown Preview</title>
</head>
<body>
  <h2>Electron hello the world</h2>
</body>
</html>

Thư viện

Để làm được một app có khả năng live preview markdown, mình cần AngularJS, angular-sanitize và bộ thư viện parse markdown marked. Bạn có thể dùng Bower để install

npm install -g bower
bower install angular angular-sanitize marked --save

Sau khi đã chuẩn bị đầy đủ các bước trên, thư mục của chúng ta có dạng như sau:

$ tree -L 2
.
├── app.js
├── bower_components
│   ├── angular
│   ├── angular-sanitize
│   └── marked
├── context.js
├── index.html
└── package.json

4 directories, 4 files

Live preview với AngularJS

Nếu bạn đã quen với 2-way binding của AngularJS thì bạn sẽ dễ dàng làm được một ô input, hoặc một text-area với nội dung được phản ánh ngay xuống div bên dưới. Mình sẽ nhúng các thẻ Angular vào file index.html.

<!DOCTYPE html>
<html ng-app="mdPreview">
<head>
  <meta charset="UTF-8">
  <title>Sample Markdown Preview</title>
  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
  <script src="bower_components/marked/lib/marked.js"></script>
  <script src="context.js"></script>
</head>
<body ng-controller="MainController">
  <h2>Electron hello the world</h2>

  <textarea ng-model="md" rows="4" cols="50"></textarea>
  <div>{{md}}</div>

</body>
</html>

Ngoài các phần include thư viện bằng tag <script>, ở đây file index.html đã được gán các thẻ của AngularJS như ng-app, ng-controller. 2-way binding ở đây là 2 dòng

<textarea ng-model="md" rows="4" cols="50"></textarea>
<div>{{md}}</div>

Ở bài này sẽ không đi vào giải thích cụ thể vềAngularJS. Nếu bạn muốn đọc kỹ hơn thì nên xem homepage của AngularJS. Mình sẽ thêm định nghĩa controller của Angular vào context.js như dưới đây

angular.module('mdPreview', ['ngSanitize'])
.controller('MainController', function ($scope) {}
});

Với 2 thay đổi bên trên thì chúng ta đã có một ô textarea để viết nội dung tuỳ thích, và một div hiện kết quả phản ánh thay đổi theo từng chữ được nhập vào ô textarea bên trên.

Markdown Render

Chúng ta sẽ tiến thêm một bước nữa, nội dung của textarea sẽ được convert thành HTML thông qua thư viện marked. Trong index.html mình sửa lại div hiển thị như sau

<!-- <div>{{md}}</div> -->
<div ng-bind-html="preview()"></div>

Ở đây mình sử dụng ng-bind-html của thư viện angular-sanitize để lấy kết quả trả về từ hàm preview() rồi gán trực tiếp vào HTML của div trên. Hàm preview sẽ được viết rất đơn gỉan ở context.js

angular.module('mdPreview', ['ngSanitize'])
.controller('MainController', function ($scope) {
  $scope.preview = function(){
    if ($scope.md == undefined ) {
      return '';
    } else {
      return marked($scope.md);
    }
  }
});

Vậy là xong! Chúng ta đã có một desktop app với khă năng live preview markdown, chay được trên WIndow/ MacOSX và Linux !
Electron

Tổng hợp

Vậy là App đầu tiên đã hoàn thành với chức năng không thua kém gì Kipalog :P Nói đùa thế chứ để làm đẹp được như Kipalog cũng còn rất nhiều vấn đề. Mình sẽ viết nốt 1 bài nữa trong series này nói về cách đóng gói và deliver sản phẩm viết bằng Electron. Link full source cho phần này ở dưới đây. Cảm ơn các bạn đã đọc bài viết !

'use strict';
var app = require('app');
var BrowserWindow = require('browser-window');
var Menu = require('menu');
var mainWindow = null;
app.on('window-all-closed', function() {
if (process.platform != 'darwin')
app.quit();
});
app.on('ready', function() {
createApplicationMenu();
openWindow();
});
var openWindow = function(){
mainWindow = new BrowserWindow({width: 800, height: 600});
mainWindow.loadUrl('file://' + __dirname + '/index.html');
mainWindow.on('closed', function() {
mainWindow = null;
});
}
var createApplicationMenu = function() {
var menuTemplate = [
{
label: 'Mdpreview',
submenu: [
{
label: 'Quit',
accelerator: 'Ctrl+Q',
click: function () {app.quit();}
}
]
}, {
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'Ctrl+R',
click: function() {
BrowserWindow.getFocusedWindow().reloadIgnoringCache();
}
},
{
label: 'Toggle DevTools',
accelerator: 'Alt+Ctrl+I',
click: function() {
BrowserWindow.getFocusedWindow().toggleDevTools();
}
}
]
}
];
var menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu)
}
view raw app.js hosted with ❤ by GitHub
'use strict';
var remote = require('remote');
var Menu = remote.require('menu');
var MenuItem = remote.require('menu-item');
var menu = new Menu();
menu.append(new MenuItem({
label: 'ping',
click: function() {
console.log('ping');
}
}));
menu.append(new MenuItem({
type: 'separator'
}));
menu.append(new MenuItem({
label: 'pong',
type: 'checkbox', checked: true,
click: function() {
console.log('pong');
}
}));
window.addEventListener('contextmenu', function (e) {
e.preventDefault();
menu.popup(remote.getCurrentWindow());
}, false);
angular.module('mdPreview', ['ngSanitize'])
.controller('MainController', function ($scope) {
$scope.preview = function(){
if ($scope.md == undefined ) {
return '';
} else {
return marked($scope.md);
}
}
});
view raw context.js hosted with ❤ by GitHub
<!DOCTYPE html>
<html ng-app="mdPreview">
<head>
<meta charset="UTF-8">
<title>Sample Markdown Preview</title>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/marked/lib/marked.js"></script>
<script src="context.js"></script>
</head>
<body ng-controller="MainController">
<h2>Electron hello the world</h2>
<textarea ng-model="md" rows="4" cols="50"></textarea>
<!-- <div>{{md}}</div> -->
<div ng-bind-html="preview()"></div>
</body>
</html>
view raw index.html hosted with ❤ by GitHub

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Mạnh Mẽ lên

7 bài viết.
26 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
45 6
(Ảnh) Flux trong giới Javascript không chỉ là chủ đề nổi tiếng nhất mà còn là chủ đề khó nắm bắt nhất. Bài viết này sẽ cố gắng giải thích một cách...
Mạnh Mẽ lên viết hơn 2 năm trước
45 6
White
31 14
Electron và Atom (Ảnh) Các bạn có biết đến trình soạn thảo Atom của Github không nhỉ. Atom là một dự án mã nguồn mở khá giống Sublime nhưng có th...
Mạnh Mẽ lên viết gần 3 năm trước
31 14
White
21 9
(Ảnh) Bạn có biết Javascript, ngôn ngữ lập trình web mà chúng ta vẫn sử dụng còn có một tên gọi khác là ECMAScript ? ECMAScript hiện nay không phả...
Mạnh Mẽ lên viết hơn 2 năm trước
21 9
Bài viết liên quan
White
31 14
Electron và Atom (Ảnh) Các bạn có biết đến trình soạn thảo Atom của Github không nhỉ. Atom là một dự án mã nguồn mở khá giống Sublime nhưng có th...
Mạnh Mẽ lên viết gần 3 năm trước
31 14
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
7 bài viết.
26 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!