16

Click here to load reader

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

Embed Size (px)

Citation preview

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

Programming UI with FRP and Bacon.js

Vyacheslav Voronchuk

[email protected] Skype: voronhukvk

http://ua.linkedin.com/in/voronchuk/

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

What is FRP?

l  Sources of data l  Events l  Side-effects l  Switching

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

Javascript FRP frameworks

l  Elm l  Flapjax l  Javelin l  RxJS l  others

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

How it helps?

l  Callback spaghetti l  Refactoring l  New features and tweaks

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

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(); }); }); }); }); });

});

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

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');

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

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); }

);

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

Bus

var $deleteItem = new Bacon.Bus(); $deleteItem.plug(Bacon.fromEventTarget($('.delete'), 'click')); $deleteItem.map('.target.remove'); $deleteItem.push($('item1'));

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

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({}) }

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

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)

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

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);

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

Debug

l  Observable.log() l  Observable.toString() l  Observable.deps() l  Observable.internalDeps() l  Bacon.spy(f)

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

Notices

l  onValue l  fromArray

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

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/

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