...

вторник, 19 ноября 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 клиентского к серверному скрипту.




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

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

P.S. для проекта будет доступна исчерпывающая документация, планируется библиотека примеров и визардов, а также сверхшустрый хостинг виджетов с плюшками в виде простого доступа к различным API.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий