JavaScript
Node.JS
19 November 2013

WidLib – декларативный js-фреймворк для построения виджетов

Сегодня я хотел бы анонсировать js-фреймворк, позволяющий создавать диалоговые микро-приложения в несколько строк js-кода, разделяемого между клиентом и сервером.

К сожалению, у автора проекта не хватило мужества довести его до ума. Простите.



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

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

Спектр задач
  1. Виджеты заказа доставки
  2. Wi-Fi приложения (путеводитель по торговому центру или интерактивное меню при подключении к местному Wi-Fi)
  3. Калькуляторы (кредиты, пластиковые окна и двери)
  4. Формы подписки на сайте
  5. Викторины (игровые, а так же для маркетинговых акций и розыгрышей)
  6. Тесты (проверка компетенций, обучение, работа с кадрами)
  7. Онлайн-помощники (например, подбор туристического маршрута или ассистент колл-центра)
  8. Встраивание виджетов в мобильные приложения (здесь он похож на PhoneGap)
  9. Диалоговые приложения для соцсетей (опять-таки викторины, нелинейные опросы)
  10. Бронирование (билеты на самолет, номер в гостинице или время у стоматолога)
  11. и т.д.

Чтобы создать DSL, мы спросили себя – каким мы хотим видеть предельно простой, но при этом гибкий язык описания (и в меньшей степени – программирования) этих диалоговых приложений. Мы использовали подход convention over configuration, знакомый по Ruby on Rails. Простые приложения пишутся в три строчки, сложные – чуть больше.
Давайте рассмотрим на примере виджета по заказу пиццы на сайте:
(DSL еще находится в доработке, поэтому предложения приветствуются.)
Coffeescript
widlib=require("widlib-server")
server=widlib.init
  # Можно использовать любой шаблонизатор (index_template=Handlebars.compile("...")),
  # в функцию передается объект страницы, шаблон сохранится в @app.template
  template: index_template
  pages:
    type:
      # шаблоны можно также задавать индивидуально для каждой страницы
      template: type_template # @app.pages["type"].template
      body: "Выберите пиццу"
      # при клике на ссылку происходит событие submit, автоматический переход на следующую страницу (если не указано явно)
      # также у каждой страницы есть события onLeave, onEnter
      inputs: [
        { value: "Маргарита", type: "link", name: "type", price: 350 },
        { value: "Пепперони", type: "link", name: "type", price: 360 },
        { value: "Филадельфия", type: "link", name: "type", price: 370 },
        { value: "Четыре сыра", type: "link", name: "type", price: 380 },
      ]

    size:
      body: "Выберите размер"
      # если в параметре используем функцию - значение вычисляется лениво
      inputs: ->
        price = @session.input("type").price # session - хранит данные текущей сессии. @session.input("type") присвоился автоматически после страницы type.
        [
          { value: "30", type: "link", name: "size", price: price },
          { value: "40", type: "link", name: "size", price: price*1.2 },
          { value: "50", type: "link", name: "size", price: price*1.5 },
        ]
      # можно в явном виде указать следующую страницу или использовать функцию
      onSubmit: "address"

    address:
      body: "Введите адрес"
      inputs: [ { name: "address", type: "text", placeholder: "улица, дом, подъезд, квартира" }, { type: "submit", value: "Далее" } ]

    phone:
      body: "Введите телефон"
      inputs: [ { name: "phone", type: "text", placeholder: "+7 xxx xx xx" }, { type: "submit", value: "Далее" }]
      onSubmit: -> # также возможно использовать события onLoad, on
        @data("orders").push @session.values() # данные записываем в постоянное хранилище
        @data("email").push email_template(@session.values())
        "success" # возвращаем имя следующей страницы

    success:
      body: "Ваша пицца уже едет к вам"
      image: -> "/images/#{@session.value("type")}.jpg" # можно использовать дополнительные параметры, в данном случае image для view

  data:
    orders:
      type: "spreadsheet" # используя модули для различных API можно хранить или синхронизировать данные с внешними сервисами
      url: "https://docs.google.com/spreadsheet/ccc?key=0Au4e-jj1-69ZdEloMW03UExKLXIZFSUE"
    email:
      type: "email"
      to: "1@interactiff.net"

server.listen "3000"

Код без комментариев
widlib=require("widlib-server")
server=widlib.init
  template: index_template
  pages:
    type:
      template: type_template
      body: "Выберите пиццу"
      inputs: [
        { value: "Маргарита", type: "link", name: "type", price: 350 },
        { value: "Пепперони", type: "link", name: "type", price: 360 },
        { value: "Филадельфия", type: "link", name: "type", price: 370 },
        { value: "Четыре сыра", type: "link", name: "type", price: 380 },
      ]

    size:
      body: "Выберите размер"
      inputs: ->
        price = @session.value("type").price
        [
          { value: "30", type: "link", name: "size", price: price },
          { value: "40", type: "link", name: "size", price: price*1.2 },
          { value: "50", type: "link", name: "size", price: price*1.5 },
        ]
      onSubmit: "address"

    address:
      body: "Введите адрес"
      inputs: [ { name: "address", type: "text", placeholder: "улица, дом, подъезд, квартира" }, { type: "submit", value: "Далее" } ]

    phone:
      body: "Введите телефон"
      inputs: [ { name: "phone", type: "text", placeholder: "+7 xxx xx xx" }, { type: "submit", value: "Далее" }]
      onSubmit: -> # также возможно использовать события onLoad, on
        @data("orders").push @session.values()
        @data("email").push email_template(@session.values())
        "success" 

    success:
      body: "Ваша пицца уже едет к вам"
      image: -> "/images/#{@session.value("type")}.jpg"

  data:
    orders:
      type: "spreadsheet"
      url: "https://docs.google.com/spreadsheet/ccc?key=0Au4e-jj1-69ZdEloMW03UExKLXIZFSUE"
    email:
      type: "email"
      to: "1@interactiff.net"

server.listen "3000"
В переводе на JS
var client, server, widlib;

widlib = require("widlib-server");

server = widlib.init({
  template: index_template,
  pages: {
    type: {
      template: type_template,
      body: "Выберите пиццу",
      inputs: [
        { value: "Маргарита", type: "link", name: "type", price: 350 },
        { value: "Пепперони", type: "link", name: "type", price: 360 },
        { value: "Филадельфия", type: "link", name: "type", price: 370 },
        { value: "Четыре сыра", type: "link", name: "type", price: 380 },
      ]
    },
    size: {
      body: "Выберите размер",
      inputs: function() {
        var price;
        price = this.session.input("type").price;
        return [
          { value: "30", type: "link", name: "size", price: price },
          { value: "40", type: "link", name: "size", price: price*1.2 },
          { value: "50", type: "link", name: "size", price: price*1.5 },
        ];
      },
      onSubmit: "address"
    },
    address: {
      body: "Введите адрес",
      inputs: [ { name: "address", type: "text", placeholder: "улица, дом, подъезд, квартира" }, { type: "submit", value: "Далее" } ]
    },
    phone: {
      body: "Введите телефон",
      inputs: [ { name: "phone", type: "text", placeholder: "+7 xxx xx xx" }, { type: "submit", value: "Далее" }],
      onSubmit: function() {
        this.data("orders").push(this.session.values());
        this.data("email").push(email_template(this.session.values()));
        return "success";
      }
    },
    success: {
      body: "Ваша пицца уже едет к вам",
      image: function() {
        return "/images/" + (this.session.value("type")) + ".jpg";
      }
    }
  },
  data: {
    orders: {
      type: "spreadsheet",
      url: "https://docs.google.com/spreadsheet/ccc?key=0Au4e-jj1-69ZdEloMW03UExKLXI3cGRlbkJteGZFSUE#gid=0"
    },
    email: {
      type: "email",
      to: "1@interactiff.net"
    }
  }
});

server.listen("3000");

Первой строкой мы создаем объект widget и загружаем в него сценарий.
Объект сценария состоит из страниц и данных. Каждая страница – отдельный экран, который увидит пользователь в этом виджете.

Каждый объект с данными соответствует адаптеру для их хранения или передачи. В простейшем случае – это просто массив, в сложных – REST интерфейс, MongoDB, Google Spreadsheet и прочие.

Мы можем использовать один и тот же сценарий, как на клиенте (в т.ч. standalone), так и на сервере node.js. Использование на сервере позволяет скрыть сценарий или его часть от пользователя, что может пригодиться для калькулятора кредита или викторины с розыгрышем призов, а также даёт доступ к динамическим данным и API, агрегированию и обработке пользовательской информации.
Клиентская часть виджета доставки пиццы, в данном случае пустая:

client=new Widlib.Client
  # здесь можно использовать тот же самый код, что и в серверной части.
  # pages: ...
  # data: ...
  server: "/"
  container: "#container"




Разработчику даже не нужно заботиться, каким образом передавать информацию, и в каком случае обмениваться данными с сервером. Библиотека автоматически проверяет наличие страниц или данных в локальном сценарии, после чего незаметно для автора запрашивает их у сервера (или отправляет), используя RPC. Серверная версия обладает более полным функционалом, а клиентская работает быстрее и независимо, что позволяет ее использовать в мобильных приложениях, а также не беспокоить сервер по пустякам.

Фичи
  • Декларативный стиль написания
  • Для простых случаев практически не требует навыков программирования
  • Возможно использовать готовые шаблоны (планируется также создать библиотеку шаблонов и визарды для создания виджетов обывателями)
  • Возможно использование декларативного html-шаблонизатора и биндинга rivets.js (подобен таковому в AngularJS). Скрипт связывания будет доступен в репозитории фреймворка
  • Независимость от библиотек для работы с DOM
  • Разделяемый код между клиентом и сервером
  • Незаметный fallback клиентского к серверному скрипту.


Призываю вас в комментариях описать, где бы вы хотели использовать подобный фреймворк.

P.S. для проекта будет доступна исчерпывающая документация, планируется библиотека примеров и визардов, а также сверхшустрый хостинг виджетов с плюшками в виде простого доступа к различным API.
Only registered users can participate in poll.Log in, please.
Нужен ли вам такой фреймворк?
31.14% Да, нужен 142
47.81% Возможно пригодится 218
8.55% Нет, для этих целей я лучше использую ... (прошу отписать в комментариях) 39
12.5% Я не занимаюсь такими вещами 57
456 users voted. 128 users abstained.

+48
16.1k 243
Comments 9
Top of the day