ANDRIY DACENKO

SOFTWARE ENGINEER

September 9, 2015

NW.JS OVERVIEW DESKTOP APPS USING JS

 THREE PLATFORMS ONE SOLUTION

JS TALK #20 KYIV

PLATFORMS

DEVELOPERS

DEVELOPERS

DEVELOPERS

TOOLS

C#

C++

OBJECTIVE-C

WHAT IF I TOLD YOU

YOU CAN MAKE  DESKTOP 

APPS WITH  HTML, CSS 

AND JAVASCRIPT?

node-webkit is an app runtime based on Chromium and  node.js"

Oct 30, 2011 – Aug 15, 2015

JS

// package.json
{
  "name": "simple",
  "version": "1.0.0",
  "main": "index.html",
  "window": {
    "toolbar": false
  }
}
<!-- index.html -->
<html>
<head>
  <title>Node WebKit App</title>
</head>
<body>
  <h1>Desktop App!</h1>
</body>
</html>
# Run application
nw /path/to/app/folder
// Load native UI library
var gui = require('nw.gui');

// Print arguments
console.log(gui.App.argv);

// Quit current app
gui.App.quit();

// Get the JSON object of the manifest file
gui.App.manifest

// Get the application's data 
// path in user's directory
gui.App.dataPath

// Lots more ...

APP

// menu.js
var gui = require('nw.gui');

// Create an empty menu
var menu = new gui.Menu();
var MenuItem = gui.MenuItem;

// Add some items
menu.append(new MenuItem({label: 'Item A'}));
menu.append(new MenuItem({label: 'Item B'}));
menu.append(new MenuItem({type: 'separator'}));
menu.append(new MenuItem({label: 'Item C'}));

// Remove one item
menu.removeAt(1);

// Popup as context menu
menu.popup(10, 10);

CONTEXT MENU

var gui = require('nw.gui');

// Create a tray icon
var tray = new gui.Tray({
    title: 'Tray', icon: 'img/icon.png'
});

// Give it a menu
var menu = new gui.Menu();
menu.append(new gui.MenuItem({
    type: 'checkbox', label: 'box1' 
}));

tray.menu = menu;

// Remove the tray
tray.remove();
tray = null;

TRAY

var options = {
  icon: "app.png",
  body: "Here is the notification text"
};

var notification = new Notification("Notification title", options);
notification.onclick = function() {
  console.log('notification clicked');
}

notification.onshow = function() {
  // auto close after 1 second  
  setTimeout(function() {
    notification.close();
  }, 1000);
}

NOTIFICATIONS

var gui = require('nw.gui');

// Open URL with default browser.
gui.Shell.openExternal(
  'https://github.com/nwjs/nw.js'
);

var path = process.cwd() + '/shell/test.txt';

// Open a text file with default text editor.
gui.Shell.openItem(path);

// Open a file in file explorer.
gui.Shell.showItemInFolder(path);

SHELL

// Load native UI library
var gui = require('nw.gui');

// We can not create a clipboard, 
// we have to receive the system clipboard
var clipboard = gui.Clipboard.get();

// Read from clipboard
var text = clipboard.get('text');
console.log(text);

// Or write something
clipboard.set('I love node-webkit :)', 'text');

// And clear it!
clipboard.clear();

CLIPBOARD

var gui = require('nw.gui');

var option = {
  key : "Ctrl+Shift+A",
  active : function() {
    console.log("Global desktop keyboard shortcut: " + this.key + " active."); 
  },
  failed : function(msg) { console.log(msg); }
};

var shortcut = new gui.Shortcut(option);

// Register global desktop shortcut, which can work without focus.
gui.App.registerGlobalHotKey(shortcut);

shortcut.on('active', function() {
  console.log("Global desktop keyboard shortcut: " + this.key + " active."); 
});

shortcut.on('failed', function(msg) { console.log(msg); });

// Unregister the global desktop shortcut.
gui.App.unregisterGlobalHotKey(shortcut);

SHORTCUT

// camFeed.js
function init()
{
    navigator.webkitGetUserMedia({video:true}, onSuccess, onFail);
}

function onSuccess(stream)
{
    var camFeed = document.getElementById('camFeed')
    camFeed.src = webkitURL.createObjectURL(stream);
}

function onFail()
{
    alert('could not connect stream');
}
<video id="camFeed" width="320" height="240" autoplay></video>

VIDEO

gui.Screen.Init();

gui.Screen.chooseDesktopMedia(
["window", "screen"], function(streamId) {
  var vid_constraint = {
    mandatory: {
      chromeMediaSource: 'desktop',
      chromeMediaSourceId: streamId,
      maxWidth: 1920,
      maxHeight: 1080,
      minFrameRate: 1,
      maxFrameRate: 5
    },
    optional: []
  };
  navigator.webkitGetUserMedia({
      audio: false,
      video: vid_constraint
    },
    function(stream) {
      document.getElementById('video_1').src = URL.createObjectURL(stream);
      stream.onended = function() { console.log("Ended"); };
    },
    function(error) {
      console.log('failure', error);
    });
});

SCREEN SHARING

// JS way 
gui.Window.open('noframe/index.html', { toolbar : false, frame : false });
<body style="-webkit-app-region: drag">
  <h1>No frame</h1>

  <button onclick="window.close()">Close</button>
</body>
// package.json
{
  "window": {
    "frame": false,
    "toolbar": false
  }
}

FRAMELESS

var gui = require('nw.gui');

gui.Window.get().enterKioskMode();

gui.Window.get().leaveKioskMode();

gui.App.quit();
// package.json
{
  "window": {
    "kiosk": true
  }
}

KIOSK

<iframe src="http://google.com" 
        nwdisable 
        nwfaketop
        nwUserAgent="Safari">
</iframe>

IFRAME

// setting value of file input element
var f = new File('/path/to/file', 'name');
var files = new FileList();
files.append(f);
document.getElementById('input0').files = files;
chooseFile('#fileDialog');

// emulate click on file inputs
function chooseFile(name) {
    var chooser = document.querySelector(name);
    chooser.addEventListener("change", function(evt) {
        console.log(this.value);
        console.log(this.files);
    }, false);

    chooser.click(); // <= here
}
<input type="file" webkitdirectory />

<input type="file" nwdirectory />

<input type="file" nwsaveas="filename.txt" />

<input type="file" nwworkingdir="/home/path/" />

CHANGES TO DOM

var gui = require('nw.gui');
var Datastore = require('nedb');
var path = require('path');

var persons = new Datastore({ 
    filename: path.join(gui.App.dataPath, 'persons.db') 
});
  • Web SQL Database
  • IndexedDB
  • PouchDB
  • NeDB
  • StoreDB
  • Application Cache

WORK WITH DATA

var gulp = require('gulp');
gulp.task('reload', function () {
  for (var module_name in global.require.cache)
      delete global.require.cache[module_name]
  if (location) location.reload();
});

gulp.watch('**/*', ['reload']);
$ npm i nw-dev --save-dev
<script src="node_modules/nw-dev/lib/dev.js"></script>
$ npm i gulp --save-dev

LIVERELOAD

// package.json
{
  "window": {
    "toolbar": true
  }
}
require('nw.gui')
  .Window.get()
  .showDevTools()
# Remote Debugging
# open http://localhost:9222/ to visit the debugger remotely
nw --remote-debugging-port=9222

DEBUGGING

$ npm install -g nw-gyp
$ nw-gyp configure --target=0.12.2
$ nw-gyp build
// binding.cc
#include <node.h>
#include <v8.h>

using namespace v8;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = Isolate::GetCurrent();
  HandleScope scope(isolate);
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
}

void init(Handle<Object> target) {
  NODE_SET_METHOD(target, "hello", Method);
}

NODE_MODULE(binding, init);
// binding.gyp
{
  "targets": [
    {
      "target_name": "binding",
      "sources": [ "binding.cc" ]
    }
  ]
}
// test.js
var assert = require('assert');
var binding = require('./build/Release/binding');
assert.equal('world', binding.hello());
console.log('binding.hello() =', binding.hello());

BUILD NATIVE MODULES

# nwjs 0.12.0-rc1+
nwjc source.js binary.bin
require('nw.gui')
  .Window.get()
  .evalNWBin(null, 'binary.bin');

mytest(2);
# nwjs < 0.12.0-rc1
nwsnapshot --extra_code source.js snapshot.bin
// package.json
"snapshot" : "snapshot.bin"
// source.js
function mytest(a) {
    document.write(a + 42);
}

ONE DOES NOT SIMPLY

HIDE HIS SOURCE

PTOTECT CODE WITH SNAPSHOT

PACKAGE YOUR APP

# Zip your application into .nw file
# zipped file would be at parent directory
zip -r ../${PWD##*/}.nw *
# Windows: Making an executable file out of a .nw file.
copy /b nw.exe+hello-world.nw hello-world.exe 
# Linux: Making an executable file out of a .nw file.
cat /usr/bin/nw hello-world.nw > hello-world && chmod +x hello-world
# Max OSX: Making an executable file.
# Create folder `HelloWorld.app/Contents/Resources/hello-world.nw` 
# and put all contents of your code in there
var NwBuilder = require('nw-builder');
var nw = new NwBuilder({
    files: './app/**/**', // use the glob format
    platforms: ['osx32', 'osx64', 'win32', 'win64'],
    macIcns: 'icons/app.icns'
});

// Build returns a promise
nw.build().then(function () {
   console.log('all done!');
}).catch(function (error) {
    console.error(error);
});
// package.json
{
  "window": {
     "icon": "app.png"
  }
}

BUILD

MADE USING NW.JS

LINKS

Q&A Time

ANDRIY DACENKO

SOFTWARE ENGINEER

September 9, 2015

NW.JS OVERVIEW DESKTOP APPS USING JS

 THREE PLATFORMS ONE SOLUTION

NW.js overview

By Andrew Dacenko

NW.js overview

Overview of nw.js - desktop applications development with JavaScript for JS Talk #20 Kyiv

  • 2,068