Идея простого, удобного и специализированного под диалоговые решения фреймоворка родилась после нескольких лет работы нашей команды с интерактивными приложениями. Нам хотелось создать простой конструктор интерактивных виджетов, не перегруженный сложным функциналом и максимально заточенный под конкреную задачу. В итоге мы пришли к решению перекроить наши наработки и немного изменить концепцию продукта.
В отличие от большинства фреймворков, WidLib не претендует на универсальность: он предназначен для быстрого создания многостраничных диалоговых приложений.
Спектр задач
- Виджеты заказа доставки
- Wi-Fi приложения (путеводитель по торговому центру или интерактивное меню при подключении к местному Wi-Fi)
- Калькуляторы (кредиты, пластиковые окна и двери)
- Формы подписки на сайте
- Викторины (игровые, а так же для маркетинговых акций и розыгрышей)
- Тесты (проверка компетенций, обучение, работа с кадрами)
- Онлайн-помощники (например, подбор туристического маршрута или ассистент колл-центра)
- Встраивание виджетов в мобильные приложения (здесь он похож на PhoneGap)
- Диалоговые приложения для соцсетей (опять-таки викторины, нелинейные опросы)
- Бронирование (билеты на самолет, номер в гостинице или время у стоматолога)
- и т.д.
Чтобы создать 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"
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 клиентского к серверному скрипту.
Воспользоваться открытой библиотекой WidLib можно будет уже в декабре.
Призываю вас в комментариях описать, где бы вы хотели использовать подобный фреймворк.
P.S. для проекта будет доступна исчерпывающая документация, планируется библиотека примеров и визардов, а также сверхшустрый хостинг виджетов с плюшками в виде простого доступа к различным API.
ссылка на оригинал статьи http://habrahabr.ru/post/202484/
Добавить комментарий