Десктопные приложения на JavaScript. Часть 2

    Данная статья является продолжением статьи «Десктопные приложения на JavaScript. Часть 1». В предыдущей части мы рассмотрели следующее:
    • установка NW.js
    • сборка и запуск приложений на NW.js
    • основы работы с нативными контроллами (на примере создания меню)

    В рамках статьи мы рассмотрим создание приложения для хранения паролей. Приложение относительно простое и является по большей части прототипом для реального. Однако при желании и наличии времени, его можно доработать и вполне использовать для повседневной работы.


    Основа приложения для хранения паролей


    Как известно, разработку можно вести как на чистом JavaScript, так и используя разнообразные фреймворки, которых существует такое огромное количество, что порой теряешься в их многообразии и долго не можешь решиться, что же в итоге выбрать. Для разработки приложений особенно популярны паттерны, название которых начинается с MV (MVC , MVVM , MVP ). Одним из фрейворков использующих подобный паттерн, является Angular JS, именно его мы и будем использовать при разработке нашего приложения. Если вы не знакомы с ним, советую почитать документацию (tutorial , API), также можно почерпнуть основные сведения в руководстве на русском языке.

    Что же будет представлять из себя приложение? Все данные отображаются в виде таблицы, при этом логин должен быть виден, а вместо пароля должны стоять звездочки. Пользователь может добавить новый логин/пароль, а также удалить записи, ставшие ненужными. Кроме того, необходимо предусмотреть возможность редактирования.

    Реализуем базовую функциональность приложения. Для этого необходимо создать папку, в которой мы будем распологать исходный код, а также поместить в нее package.json (о том как это сделать, см. Часть 1).

    Создадим базовую структуру папок, состоящую из следующих директорий:
    • CSS — в этой папке будем размещать стили (Добавим сюда файл index.css, в котором будут содержаться основные стили)
    • Controller — здесь будут находится контроллеры
    • View — папка для представлений
    • Directive — папка с директивами
    • Lib — библиотеки (в эту папку необходимо скопировать angular.min.js, о том как добавить angularJS)

    Кроме того в корень проекта добавим файл index.html, который будет являться точкой входа в приложение. Создадим базовую разметку:
    Базовая разметка
    <html ng-app="main">
       <head>
          <meta charset="utf-8">
          <title>Password keeper</title>
          <link rel="stylesheet" type="text/css" href="css/index.css">
       </head>
       <body>
          <table>
             <thead>
                <tr>
                   <td></td>
                   <td>Login</td>
                   <td>Password</td>
                   <td></td>
                </tr>
             </thead>
             <tbody>
                <tr>
                   <td></td>
                   <td></td>
                   <td></td>
                   <td><a>удалить</a></td>
                </tr>
             </tbody>
             <tfoot>
                <tr>
                   <td></td>
                   <td><a>добавить</a></td>
                   <td></td>
                   <td></td>
                </tr>
             </tfoot>
          </table>
          <script type="text/javascript" src="lib/angular.min.js"></script>
       </body>
    </html>
    



    Так как приложение у нас достаточно простое, создадим контроллер и в рамках него будем размещать всю основную логику приложения (по мере разрастания логики, необходимо добавить папку Service и размещать в ней сервисы, в которых и должна размещаться вся сложная логика, контроллеры по возможности необходимо оставлять «тонкими»). Назовем контроллер main, а файл контроллера main.ctrl.js. Итак, заготовка для контроллера:

    (function () {
        'use strict';
    
        angular
            .module('main', [])
            .controller('MainCtrl', [MainCtrl]);
    
        function MainCtrl() {
          this.data = [];
    
            return this;
        }
    
    })();
    


    Данные, содержащие логины/пароли, для нашего прототипа будут размещаться в массиве data. Для упрощения реализации редактирования, создадим свой элемент EditableText и оформим его в виде директивы. Данный элемент будет работать следующим образом: элемент отображается как текст, при щелчке по элементу, текст превращается в тектовое поле input, при потере фокуса элемент вновь отображается как текст. Для этого создадим внутри папки View файл с разметкой для директивы editableText.html:

    <input  ng-model="value">
    <span ng-click="edit()">{{value}}</span>
    


    А внутри папки directive создадим файл editableText.js:
    Код директивы editable-text
    (function () {
        'use strict';
    
        angular
            .module('main')
            .directive('editableText', [editableText]);
    
        function editableText() {
            var directive = {
                restrict: 'E',
                scope: {
                    value: "="
                },
                templateUrl: 'view/editableText.html',
                link: function ( $scope, element, attrs ) {
                    // получаем ссылку на внутренний input нашей директивы
                    var inputElement = angular.element( element.children()[0] );
                    element.addClass( 'editable-text' );
    
                    // функция, вызываемая при щелчке на элементе, когда он отображается
                    // в режиме для чтения
                    $scope.edit = function () {
                        element.addClass( 'active' );
                        inputElement[0].focus();
                    };
    
                    // при потере фокуса, т.е. когда пользователь закончил редактирование
                    inputElement.prop( 'onblur', function() {
                        element.removeClass( 'active' );
                    });
                }
            };
    
            return directive;
        }
    })();
    



    Для работы директивы необходимы также некоторые стили, которые можно разместить внутри index.css:

    .editable-text span {
       cursor: pointer;
    }
    
    .editable-text input {
       display: none;
    }
    
    .editable-text.active span {
       display: none;
    }
    
    .editable-text.active input {
       display: inline-block;
    }
    


    Использование директивы происходит следующим образом:

    <editable-text value="variable"></editable-text>
    


    Для логина все в порядке — отображаем либо текст, либо текстовое поле, но как быть с паролем, ведь мы не должны его показывать. Добавим в scope нашей директивы поле crypto следующим образом:

    scope: {
       value: "=",
       crypto: "="
    }
    


    А также изменим разметку директивы:

    <input  ng-model="value">
    <span ng-click="edit()">{{crypto?'***':value}}</span>
    


    Кроме того, необходимо не забывать добавлять скрипты в index.html:

    <script type="text/javascript" src="lib/angular.min.js"></script>
    <script type="text/javascript" src="controller/main.ctrl.js"></script>
    <script type="text/javascript" src="directive/editableText.js"></script>
    


    Пришло время добавить функциональность. Изменим контроллер следующим образом:
    Код контроллера
    function MainCtrl() {
       var self = this;
       this.data = [];
    
       this.remove = remove;
       this.copy = copy;
       this.add = add;
    
       return this;
    
       function remove(ind){
          self.data.splice(ind, 1);
       };
    
       function copy(ind){
          // добавим позже
       };
    
       function add(){
          self.data.push({login: "login", password: "password"});
       };
    }
    



    Кроме того необходимы изменения в разметке:
    Итоговая разметка
    <body ng-controller="MainCtrl as ctrl">
       <table>
          <thead>
             <tr>
                <td></td>
                <td>Login</td>
                <td>Password</td>
                <td></td>
             </tr>
          </thead>
          <tbody>
             <tr ng-repeat="record in ctrl.data track by $index">
                <td><a ng-click="ctrl.copy($index)">{{$index}}</a></td>
                <td><editable-text value="record.login"></editable-text></td>
                <td><editable-text value="record.password" crypto="true"></editable-text></td>
                <td><a ng-click="ctrl.remove($index)">удалить</a></td>
             </tr>
          </tbody>
          <tfoot>
             <tr>
                <td></td>
                <td><a ng-click="ctrl.add()">добавить</a></td>
                <td></td>
                <td></td>
             </tr>
          </tfoot>
       </table>
       <script type="text/javascript" src="lib/angular.min.js"></script>
       <script type="text/javascript" src="controller/main.ctrl.js"></script>
       <script type="text/javascript" src="directive/editableText.js"></script>
    </body>
    



    На данном этапе можно заняться стилизацией. Пример простой стилизации (напоминаю, что добавляем стили в index.css, однако если стилей будет достаточно много, можно разбить стили по файлам или даже использовать препроцессор, например LESS):
    Пример стилизации приложения
    table {
       border-collapse: collapse;
       margin: auto;
       width: calc(100% - 40px);
    }
    
    table, table thead, table tfoot,
    table tbody tr td:first-child,
    table tbody tr td:nth-child(2),
    table tbody tr td:nth-child(3),
    table thead tr td:nth-child(2),
    table thead tr td:nth-child(3) {
       border: 1px solid #000;
    }
    
    table td {
       padding: 5px;
    }
    
    table thead {
       background: #EEE;
    }
    
    table tbody tr td:first-child {
       background: #CCC;
    }
    
    table tbody tr td:nth-child(2) {
       background: #777;
       color: #FFF;
    }
    
    table tbody tr td:nth-child(3) {
       background: #555;
       color: #FFF;
    }
    
    table thead tr td:nth-child(2),table thead tr td:nth-child(3) {
       text-align: center;
    }
    
    table a {
       font-size: smaller;
       cursor: pointer;
    }
    



    Приложение выглядит следующим образом:



    Работа с буфером обмена


    Итак, основа приложения готова, но оно пока не реализует основное назначение, мы не можем копировать пароли (вернее можем, но достаточно неудобно). Для начала рассмотрим работу с буфером обмена в NW.js
    Существует специальный объект — Clipboard, который используется как абстракция для буфера обмена Windows и GTK, а также для pasteboard (Mac). На момент написания статьи осуществляется поддержка записи и чтения только текста.
    Для работы с объектом нам понадобится знакомый нам модуль nw.gui:

    var gui = require('nw.gui');
    var clipboard = gui.Clipboard.get();
    


    Обратите внимание, что мы не может создать свой объект, мы можем получить лишь системный. Поддерживаются три метода:
    • get ([type]) — получить объект из буфера обмена с указанием типа данного объекта, по умолчанию text, однако пока это единственный поддерживаемый тип
    • set (data, [type]) — отправить объект в буфер обмена (также поддерживается лишь type — «text»)
    • clear — очистить буфер обмена

    Теперь можно доделать функциональность приложения, и контроллер будет выглядеть следующим образом:
    Код контроллера
    function MainCtrl() {
         var self = this;
         var gui = require('nw.gui');
         var clipboard = gui.Clipboard.get();
         this.data = [];
    
         this.remove = remove;
         this.copy = copy;
         this.add = add;
    
         return this;
    
         function remove(ind){
             self.data.splice(ind, 1);
         };
    
         function copy(ind){
             clipboard.set(self.data[ind].password);
         };
    
         function add(){
             self.data.push({login: "login", password: "password"});
         };
     }
    



    Хранение паролей


    После того, как приложение было запущено, пользователь занес на хранение несколько паролей, закрыл приложение. На следующий день оказывается, что пароли пропали. Проблема в том, что мы держали их в обычной локальной переменной, которая при закрытии удалилась.
    В третьей части мы рассмотрим работу NW.js с базами данных, а пока будем хранить пароли в localStorage. Прежде чем приступить к созданию функционала, (хотя приложение у нас пока лишь только прототип) необходимо позаботиться о безопасности. Для этого мы не должны хранить пароли в открытом виде.
    Для шифрования/дешифрования существуют различные библиотеки на JavaScript. Одной из таких библиотек является crypto-js. Установим ее как модуль для node.js. Библиотека поддерживает большое количество стандартов, полный список которых можно найти в документации. При этом, можно подключить как все модули, так и отдельный модуль:

    // подключаем все модули, доступ к отдельному модулю можно получить например так CryptoJS.HmacSHA1
    var CryptoJS = require("crypto-js"); 
    
    // подключаем отдельный модуль, например AES
    var AES = require("crypto-js/aes");
    


    Для того, чтобы зашифровать сообщение используется метод encrypt:

    var ciphertext = CryptoJS.AES.encrypt('сообщение', 'секретный ключ');
    


    Расшифровка происходит немного сложнее:

    var bytes  = CryptoJS.AES.decrypt(ciphertext.toString(), 'секретный ключ');
    var plaintext = bytes.toString(CryptoJS.enc.Utf8);
    


    Давайте модифицируем наше приложение для того, чтобы мы могли сохранять пароли при закрытии приложения и загружать их при запуске.
    Создадим сервис crypto.svc и поместим в папку service (если вы еще не создали данную папку, то создайте ее в корне приложения):
    Код сервиса
    (function () {
       'use strict';
    
       angular
          .module('main')
          .factory('CryptoService', [CryptoService]);
    
       function CryptoService() {
          var CryptoJS = require("crypto-js");
          var secretKey = "secretKey";
    
          var service = {
             encrypt: encrypt,
             decrypt: decrypt
          };
    
          return service;
    
          function encrypt(data) {
             return CryptoJS.AES.encrypt(JSON.stringify(data), secretKey);
          }
    
          function decrypt(text) {
             var bytes  = CryptoJS.AES.decrypt(text.toString(), secretKey);
             var decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
    
             return decryptedData;
          }
       }
    
    })();
    



    Для использования нашего сервиса модернизируем контроллер:
    Код контроллера
    (function () {
       'use strict';
    
       angular
          .module('main', [])
          .controller('MainCtrl', ['$scope', 'CryptoService', MainCtrl]);
    
       function MainCtrl($scope, CryptoService) {
          var self = this;
          var gui = require('nw.gui');
          var clipboard = gui.Clipboard.get();
          var localStorageKey = "loginPasswordData"
          this.data = [];
    
          this.remove = remove;
          this.copy = copy;
          this.add = add;
    
          load();
    
          $scope.$watch("ctrl.data", save, true);
    
          return this;
    
          function remove(ind){
             self.data.splice(ind, 1);
          };
    
          function copy(ind){
             clipboard.set(self.data[ind].password);
          };
    
          function add(){
             self.data.push({login: "login", password: "password"});
          };
    
          function load(){
             var text = localStorage.getItem(localStorageKey);
             if(text) {
                self.data = CryptoService.decrypt(text);
             }
          }
    
          function save(){
             if(self.data) {
                localStorage.setItem(localStorageKey, CryptoService.encrypt(self.data));
             }
          }
       }
    
    })();
    



    Кроме подключения сервиса, нам так же понадобился уже существующий в AngularJS сервис $scope. Мы используем метод $watch для отслеживания момента изменения данных, для того, чтобы вовремя сохранить их (обратите внимание, что третьим аргументом мы передаем true для того, чтобы отслеживались изменения не только в массиве, т.е. вставка/удаление, но и изменения элементов массива, т.е. изменение логина или пароля отдельного элемента массива). Загрузка данных происходит при открытии представления.

    Сворачиваем в трей


    Основа приложения готова, но как известно подобные программы зачастую сворачиваются в системный трей, чтобы не перегружать пользователя обилием открытых окон.
    Еще одна абстракция, которую ввели в NW.js — это трей: System Tray Icon для Windows, Status Icon для GTK, Status Item для OSX. Данный объект создается с помощью известного нам модуля gui:

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

    При работе с данным объектом необходимо заботиться об области видимости переменной, если создать внутри функции, то вскоре он будет удален GC. При создании объекта можно сразу же создать свойства, как мы это сделали в примере, а можно позаботиться об этом несколько позже. Следующие свойства можно определить для данного объекта:
    • Title — будет показываться только в Mac OSX
    • Tooltip — подсказка, доступная на всех платформах
    • Icon — иконка, отображаемая в трее, также доступна на всех платформах
    • Menu — меню, которое в Mac OS X будет появляться по щелчку, для Windows и Linux — будет реагировать на одиночный щелчок и щелчок правой кнопкой (о том, как создать меню, см первую часть цикла статей)

    Для того, чтобы использовать трей в нашем приложении, необходимо создать любой элемент разметки, наиболее очевидным вариантом является кнопка button. Далее необходимо подписаться на событие click, и использовать методы объекта window, с которым мы сейчас и познакомимся.

    Работаем с окном


    Для работы с окном, необходимо либо получить существующее окно, либо создать новое. Итак, для того, чтобы получить текущее окно, в котором и отображается наше приложение, необходимо выполнить команду:

    var win = gui.Window.get();
    

    А для создания нового окна, необходимо указать адрес, по которому располагается страница, для открытия в данном окне, а также параметры для открытия (данные параметры соотвествуют тем, что мы указываем при создании манифеста, см. первую часть из цикла статей):

    var win = gui.Window.open ('https://myurl', {
      position: 'center',
      width: 901,
      height: 127
    });
    

    Также в параметрах можно передать специальное свойство focus: true, при указании которого, только что созданное окно сразу же получит фокус, в противном случае фокус останется на нашем текущем окне.
    Если мы создаем новое окно и хотим что-то с ним сделать после того, как оно будет создано, необходимо подписаться на соответствующее событие:

    win.on ('loaded', function(){
      // получим элемент document текущего окна, с которым впоследствии мы можем работать
      var document = win.window.document;
      
      // логика для работы с созданным окном...
    });
    


    Как видно из примера, одним из свойств окна является объект window, из которого мы можем получить остальные элементы, включая document. Кроме данного свойства окно также поддерживает множество других:
    • x, y — координаты окна
    • width, height — размеры окна
    • title — заголовок окна
    • menu — главное меню приложения, которое будет располагаться в верхней части окна
    Данные свойства можно не только читать, но и изменять. Кроме них также есть свойства, доступные только для чтения (все они логические и могут принимать значения true или false)
    • isTransparent — является ли окно прозрачным
    • isFullscreen — открыто ли окно на полный экран
    • isKioskMode — открыто ли приложение в киоск моде

    Помимо свойств, объект поддерживает большое количество методов. Основные методы приводятся ниже и для удобства объединены по группам.
    Методы для изменения позиции и размеров окна:
    • moveTo — переместить окно на позицию, переданную в параметрах в виде координаты x и координаты y
    • moveBy — переместить окно на определенное количество пикселей вправо и вниз (в случае задания отрицательных аргументов влево и вверх)
    • resizeTo — изменить размеры окна: первый аргумент указывает ширину, второй — высоту окна
    • resizeBy — изменить размеры окна на определенное количество пикселей вправо и вниз (в случае задания отрицательных аргументов влево и вверх)
    • setPosition — задать специфическую позицию окна, переданную в качестве аргумента (на данный момент поддерживается только 'center')

    Методы для работы с фокусом и видимостью:
    • focus — метод без параметров для передачи фокуса окну
    • blur — метод без параметров для того, чтобы сделать окно не активным
    • hide — скрыть окно
    • show — показать окно, однако если передать в качестве аргумента false, то метод будет работать как hide

    Методы для управления свертыванием/развертыванием, закрытием окна:
    • close — закрытие окна, при этом возникает событие close, однако если передать в качестве аргумента true, то событие возникать не будет
    • reload — перезагрузить окно
    • reloadDev — перезагрузить окно, но с элементами разработчика
    • maximize — распахнуть окно на весь экран
    • unmaximize — вернуть окно в исходный размер после того, как окно распахнули
    • minimize — свернуть окно
    • restore — развернуть окно, противоположно minimize
    • setShowInTaskbar — показывать ли окно на панели задач
    • setAlwaysOnTop — показывать ли окно поверх других

    Методы для управления состоянием:
    • enterFullscreen, leaveFullscreen, toggleFullscreen — управление полноэкранным режимом
    • enterKioskMode, leaveKioskMode, toggleKioskMode — управление киоск режимом
    • setTransparent — установить/сбросить прозрачность окна, в зависимости от переданного аргумента
    • showDevTools — показать инструменты разработчика
    • closeDevTools — скрыть инструменты разработчика
    • isDevToolsOpen — проверка: открыты ли инструменты разработчика

    Методы управления возможностью изменять размеры окна
    • setResizable — установить/сбросить возможность изменения размера экрана
    • setMaximumSize — задать ограничения на максимальный размер экрана (первый аргумент — ширина, второй — высота)
    • setMinimumSize — задать ограничения на минимальный размер экрана (первый аргумент — ширина, второй — высота)

    Итак, познакомившись с объектами tray и window, напишем функциональность сворачивания в трей. Для этого в разметку необходимо (как говорилось выше) добавить элемент, например кнопку или ссылку:
    <a ng-click="ctrl.toTray()">В трей</a>
    

    И изменить контроллер следующим образом:
    Код контроллера
    (function () {
       'use strict';
    
       angular
          .module('main', [])
          .controller('MainCtrl', ['$scope', 'CryptoService', MainCtrl]);
    
       function MainCtrl($scope, CryptoService) {
          var self = this;
          var localStorageKey = "loginPasswordData"
          this.data = [];
          var gui = require('nw.gui');
          var clipboard = gui.Clipboard.get();
          var win = gui.Window.get();
          var tray =  new gui.Tray({ title: 'Tray', icon: 'img/test.png' });
          tray.on("click", restoreFromTray);
    
          this.remove = remove;
          this.copy = copy;
          this.add = add;
          this.toTray = toTray;
    
          load();
    
          $scope.$watch("ctrl.data", save, true);
    
          return this;
    
          function remove(ind){
             self.data.splice(ind, 1);
          };
    
          function copy(ind){
             clipboard.set(self.data[ind].password);
          };
    
          function add(){
             self.data.push({login: "login", password: "password"});
          };
    
          function load(){
             var text = localStorage.getItem(localStorageKey);
             if(text) {
                self.data = CryptoService.decrypt(text);
             }
          }
    
          function save(){
             if(self.data) {
                localStorage.setItem(localStorageKey, CryptoService.encrypt(self.data));
             }
          }
    
          function toTray(){
            win.minimize();
            win.setShowInTaskbar(false);
          }
    	
          function restoreFromTray(){
            win.restore();
            win.setShowInTaskbar(true);
          }
       }
    
    })();
    


    Также для работы данного примера необходимо создать папку img и поместить туда иконку трея (в данном примере это img/test.png).

    Заключение


    В рамках статьи мы написали прототип приложения, который вы можете улучшить различным образом: начиная от стилей и заканчивая улучшениями в функционале. Например:
    • можно подписаться на событие keydown и для первых 10 паролей, при нажатии на кнопку от 0 до 9, копировать пароль в буфер обмена, это упростит и ускорит работу с программой
    • добавить способ копирования не только пароля, но и логина
    Успехов в программировании!
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 14
    • –15
      Знаете что, JS-фэнбои, я вас ненавижу!

      Я на работе выхожу в Интернет с компьютера с одноядерным 32-битным Pentium 4 Northwood. Весь Интернет делится для меня на 2 части: нормальные сайты и те, которые делали разработчики с JS головного мозга. Притом первые и вторые могут быть красивыми, со стильным рендерингом шрифтов и фишечками HTML5. Но одни приемлемо работают на 130нм процессоре, а другие жутко тормозят.

      Знакомый работает в школе. Во многих школах стоят старые компьютеры. Но разработчики новомодного интернет-дневника плевать хотели на этот факт — они любят JS и суют тяжеловесные фреймворки, перегруженные абстракциями, для решения простейших задач.

      Мало того, что вы превратили Web в -moz-webkit-дерьмо, впихиваете монструозный V8 просто везде, вы хотите испортить разработку для десктопов! Прекратите, пожалуйста.
      Нормальные люди делают вещи типа Kivy и Gideros. Или даже GTK+.
      Valve тоже могла бы сделать клиент Steam на базе Node.js. Но они не идиоты и не сумасшедшие, поэтому взяли на работу разработчика SDL и сделали кроссплатформенный стильный интерфейс сами.

      Кажется, что нынешние приложения с GUI делаются с расчётом, что они будут запускаться на компьютере в одиночку. А если запущено хотя бы 3-4 приложения с GUI, им внезапно не хватает последних процессоров.

      Извините, просто накипело.
      • +9
        Как же надоели все эти тру-ассемблер-моралисты. Вы что-нибудь слышали про подбор инструмента под задачу? Если у бизнеса не стоит задача «разработать продукт для школ с поддержкой Pentium 4», то в чем проблема?
        Оставьте бесконечные попытки совершенствования. Бизнесу нужно, чтобы продукт продавался ПРЯМО СЕЙЧАС. А как сделать, чтобы продукт запускался на 0,0001 мс быстрее или чтобы он не тормозил на компьютере двадцатилетней давности, бизнес разберется ПОТОМ.
        • –8
          Вот именно, под задачу, а не «пихаем JS везде, улыбаемся и машем»
          Я про ассемблер ни слова не сказал. В качестве примера я приводил библиотеки на Python, Lua и C(с привязками к куче динамических языков)

          Когда ты только заикаешься про графический интерфейс и разработчик уже мысленно впихнул в проект nw.js; когда ты говоришь «сделать сайт», а разработчик, даже не дослушав какой, уже думает какую групповуху из angular, react и пр. он устроит на этот раз; когда все задачи решаются через создание абстрактной фабрики, которая возвращает объект, при вызове метода которого возвращается lambda-функция, которая возвращает другой объект, свойство которого является ответом на вопрос «сколько же будет 2+2» — вот это всё бесит.

          И по-хорошему винить надо не JS, а тех людей, которые начали его _так_ использовать. В итоге вполне годный инструмент для умеренного добавления динамики в веб-страницы превратили в очень плохой язык общего назначения.
          • 0
            да просто большинство из этих жс-боев пришли из фронтенда. клепали сайтики на jquery и понятия не имели про desktop\server-side программирования. а тут портировали любимый жс во все сферы, они и набежали. переучиваться же не надо. клепай спагетти под ноджс, пиши на нем же десктопные приложения. а то, что результирующий пакет для хэлоуворлда под 50 метров и оперативы жрет под 100мб — похер. люто поддерживаю evocatus
            • +2
              А Вам-то что за беда? Вот я, например, сайтики никогда не клепал, и с прототипами дружил ещё до того, как jQuery появилось. Я, если буду писать десктопные приложения, буду их писать нормально. А те кто пишет ненормально мне не интересны.
        • 0
          Вы бы это Py-фэнбоям написли…
          • +2
            А простите, не заметил, вы таки предлагаете на python'е писать для десктопа, ясно все…
            • 0
              Это при том, что интерпретатор для JS родился в жесточайшей конкурентной борьбе лучших умов мира (Google, Microsoft, Mozilla и т.д.). А откуда интерпретатор питона вышел?
              • 0
                Да я откуда знаю, лучше б не выходил вообще, весь линукс уже им испортили…
          • +9
            «Проблемы индейцев шерифа не волнуют» (с)

            IMHO, в наше стремительно летящее время приложения должны разрабатываться в том виде, в каком девелоперу удобно их создавать и изменять (второе даже важнее первого). И если девелоперу удобнее создавать и изменять (второе важнее) приложение с использованием JS — он будет это делать с использованием JS. Более того, этого будет требовать сам заказчик разработки. Ну а пользователи… 80% пользователей приложения проапргрейдятся, 20% будут выкинуты на обочину курить бамбук.

            Я не имею в виду, что JS «захватит мир», я имею в виду, что «одноядерный 32-битный Pentium 4 Northwood» это если и не 20%, то уже очень близко к нему.
            • +4
              Если лень разбираться с конфигами json и прочими настройками в nw.js, а превратить самописную HTML5-игрушку в экзешник хочется, можно установить Web2Executable, который предоставляет графический интерфейс настройки будущего приложения NW.js. Можно создавать сборки для Mac, Windows, Linux.

              image
              • +1
                А с какой целью в ваших примерах все директивы и контроллеры (и даже методы контроллеров) определены отдельными функциями, а потом отдельно зарегистрированы и объявлены? Это результат какого-либо js-транслятора или имеет особый смысл?
                • 0
                  Если я вас правильно понял, то это должно быть ответ на ваш вопрос. Кроме того, на данном ресурсе можно ознакомиться с лучшими практиками по использованию AngularJS
                  • 0
                    Контроллеры, директивы помещены в отдельные функции, придерживаясь code style, и методы отдельно зарегистрированы также придерживаясь code style.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.