Programming UI with FRP and Bacon.js

Vyacheslav Voronchuk
voronchuk@wm-software.com
Skype: voronhukvk
http://ua.linkedin....
What is FRP?
l 

Sources of data

l 

Events

l 

Side-effects

l 

Switching
Javascript FRP frameworks
l 

Elm

l 

Flapjax

l 

Javelin

l 

RxJS

l 

others
How it helps?
l 

Callback spaghetti

l 

Refactoring

l 

New features and tweaks
FlatMap

$.ajax({ url: “/items” }).done(function(items) {
if(items.error) {
return handleError(items.error);
}

$.each(ite...
FlatMap
isError = function(serverResponse) { return typeof serverResponse.error !== 'undefined' }
isNotError = function(se...
When and Combine
isError = function(serverResponse) { return typeof serverResponse.error !== 'undefined' }
isNotError = fu...
Bus

var $deleteItem = new Bacon.Bus();
$deleteItem.plug(Bacon.fromEventTarget($('.delete'), 'click'));
$deleteItem.map('....
Bus
var chopsticks = [new Bacon.Bus(), new Bacon.Bus(), new Bacon.Bus()]
var hungry = [new Bacon.Bus(), new Bacon.Bus(), n...
Usage in Node.js
getInvoiceStream = (id) -> Bacon.fromNodeCallback Invoice, 'findOne', id: id
getInvoiceDataStream = ($inv...
AJAX Search
// Stream of search queries
var $query = $("#search").asEventStream("keyup").map(function(event) {
return $(ev...
Debug
l 

Observable.log()

l 

Observable.toString()

l 

Observable.deps()

l 

Observable.internalDeps()

l 

Baco...
Notices
l 

onValue

l 

fromArray
Links
l 

Github page: https://github.com/baconjs/bacon.js

l 

Bacon.js blog: http://baconjs.blogspot.fi/

l 

Snake g...
FrontendLab: Programming UI with FRP and Bacon js - Вячеслав Ворончук
FrontendLab: Programming UI with FRP and Bacon js - Вячеслав Ворончук
Upcoming SlideShare
Loading in …5
×

FrontendLab: Programming UI with FRP and Bacon js - Вячеслав Ворончук

1,417 views

Published on

Published in: Technology, Education
1 Comment
1 Like
Statistics
Notes
No Downloads
Views
Total views
1,417
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
14
Comments
1
Likes
1
Embeds 0
No embeds

No notes for slide

FrontendLab: Programming UI with FRP and Bacon js - Вячеслав Ворончук

  1. 1. Programming UI with FRP and Bacon.js Vyacheslav Voronchuk voronchuk@wm-software.com Skype: voronhukvk http://ua.linkedin.com/in/voronchuk/
  2. 2. What is FRP? l  Sources of data l  Events l  Side-effects l  Switching
  3. 3. Javascript FRP frameworks l  Elm l  Flapjax l  Javelin l  RxJS l  others
  4. 4. How it helps? l  Callback spaghetti l  Refactoring l  New features and tweaks
  5. 5. FlatMap $.ajax({ url: “/items” }).done(function(items) { if(items.error) { return handleError(items.error); } $.each(items, function(item) { $.ajax({ url: “/item/” + item.id }).done(function(item) { if(item.error) { return handleError(item.error); } renderItem(item, function(itemEl) { itemEl.children('.delete').click(function() { $.ajax({ url: “/item/” + item.id, type: 'DELETE' }).done(function(response) { if(response.error) { return handleError(response.error); } itemEl.remove(); }); }); }); }); }); });
  6. 6. FlatMap isError = function(serverResponse) { return typeof serverResponse.error !== 'undefined' } isNotError = function(serverResponse) { return !isError(serverResponse); } $allItems = Bacon.fromPromise($.ajax({ url: “/items” })); $errors = $allItems.filter(isError); $items = $allItems.filter(isNotError).flatMap(function (item) { return Bacon.fromPromise($.ajax({ url: “/item” + item.id })); }); $errors.merge($items.filter(isError)); $renderedItems = $items.filter(isNotError).flatMap(function(item) { return Bacon.fromCallback(renderItem, item); }); $renderedItemsDeleteClicks = $renderedItems.flatMap(function(renderedItem){ return Bacon.fromEventTarget(renderedItem.children('.delete'), 'click', function(event) { return renderedItem; }); }); $deleteItemRequest = $renderedItemsDeleteClicks.flatMap(function(renderedItem) { return Bacon.fromPromise($.ajax({ url: “/item” + renderedItem.data('id'), type: 'DELETE' })); }); $errors.merge($deleteItemRequest.filter(isError)); $errors.onValue(handleError); $deleteItemRequest.filter(isNotError).onValue('.remove');
  7. 7. When and Combine isError = function(serverResponse) { return typeof serverResponse.error !== 'undefined' } isNotError = function(serverResponse) { return !isError(serverResponse); } $items = Bacon.fromPromise($.ajax({ url: “/items” })); $renderedItems = $items.filter(isNotError).flatMap(function(item) { return Bacon.fromCallback(renderItem, item); }); $quantity = $renderedItems.flatMap(function(element) { return Bacon.fromEventTarget($(element).children('.quantity'), 'change').map(function(event) { return [element, $(element).val()]; }); }); $price = $quantity.map(function(data) { return $(data[0]).data('price') * data[1]; }); $refreshClick = Bacon.fromEventTarget($('#refresh_cart'), 'change'); Bacon.when( [$refreshClick, $quantity, $price], function(event, data, price) { $(data[0]).children('.internal-price').text(price); $(data[0]).children('.price').text(price); }, [$quantity, $price], function(data, price) { $(data[0]).children('.internal-price').text(price); } );
  8. 8. Bus var $deleteItem = new Bacon.Bus(); $deleteItem.plug(Bacon.fromEventTarget($('.delete'), 'click')); $deleteItem.map('.target.remove'); $deleteItem.push($('item1'));
  9. 9. Bus var chopsticks = [new Bacon.Bus(), new Bacon.Bus(), new Bacon.Bus()] var hungry = [new Bacon.Bus(), new Bacon.Bus(), new Bacon.Bus()] var eat = function(i) { return function() { setTimeout(function() { chopsticks[i].push({}) chopsticks[(i+1) % 3].push({}) }, 1000); return 'philosopher ' + i + ' eating' } } var dining = Bacon.when( [hungry[0], chopsticks[0], chopsticks[1]], eat(0), [hungry[1], chopsticks[1], chopsticks[2]], eat(1), [hungry[2], chopsticks[2], chopsticks[0]], eat(2) ).log() // make all chopsticks initially available chopsticks[0].push({}); chopsticks[1].push({}); chopsticks[2].push({}) // make philosophers hungry in some way, in this case we just push to their bus for (var i = 0; i < 3; i++) { hungry[0].push({}); hungry[1].push({}); hungry[2].push({}) }
  10. 10. Usage in Node.js getInvoiceStream = (id) -> Bacon.fromNodeCallback Invoice, 'findOne', id: id getInvoiceDataStream = ($invoice) -> $invoice.flatMap (invoice) -> Bacon.fromNodeCallback invoice, 'toDeepJSON' # Load Invoice and it's deps get: (req, res) -> $invoice = getInvoiceStream req.param 'id' $invoiceData = getInvoiceDataStream invoice $invoiceData.onValue _.bind(res.json, res) $errors = Bacon.mergeAll $invoice.errors(), $invoiceData.errors() $errors.onError _.bind(res.send, res, 500) # Generate PDF export pdf: (req, res) -> $invoice = getInvoiceStream req.param 'id' $invoiceData = getInvoiceDataStream $invoice $invoiceRender = $invoiceData.map renderInvoicePDF $invoiceRenderData = $invoiceRender.flatMap (pdf) -> Bacon.fromCallback pdf, 'output' $invoiceRenderData.onValue _.bind(res.end, res) $errors = Bacon.mergeAll $invoice.errors(), $invoiceData.errors() $errors.onError _.bind(res.send, res, 500)
  11. 11. AJAX Search // Stream of search queries var $query = $("#search").asEventStream("keyup").map(function(event) { return $(event.srcElement).val(); }).skipDuplicates(); // Search result strings var $results = $query.throttle(500).flatMapLatest(function(query) { return Bacon.fromPromise($.ajax("/search/" + query)) }).mapError("Search error"); // Render results $results.onValue(function(result) { $("#results").html(result); }); // Search status var searchStatus = $query.map(true).merge($results.map(false)).skipDuplicates().toProperty(false);
  12. 12. Debug l  Observable.log() l  Observable.toString() l  Observable.deps() l  Observable.internalDeps() l  Bacon.spy(f)
  13. 13. Notices l  onValue l  fromArray
  14. 14. Links l  Github page: https://github.com/baconjs/bacon.js l  Bacon.js blog: http://baconjs.blogspot.fi/ l  Snake game: http://philipnilsson.github.io/badness/ l  Worzone game: http://juhajasatu.com/worzone/

×