• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
JavaScript Modular e Event-Driven
 

JavaScript Modular e Event-Driven

on

  • 3,537 views

Em aplicações de larga escala, certas interações que parecem simples aos olhos do usuário escondem a complexidade de múltiplas requisições assíncronas e updates no DOM toda vez que uma ...

Em aplicações de larga escala, certas interações que parecem simples aos olhos do usuário escondem a complexidade de múltiplas requisições assíncronas e updates no DOM toda vez que uma informação é modificada. Esta complexidade pode facilmente virar um inferno de callbacks, e nesta palestra mostrarei como através de uma estrutura event driven criada do zero é possível quebrar uma página em componentes modulares, desacoplados, e testáveis.

Statistics

Views

Total Views
3,537
Views on SlideShare
3,389
Embed Views
148

Actions

Likes
29
Downloads
14
Comments
0

6 Embeds 148

https://twitter.com 84
http://eventifier.com 23
http://gnomex.github.io 18
http://www.linkedin.com 12
http://eventifier.co 10
https://www.linkedin.com 1

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

JavaScript Modular e Event-Driven JavaScript Modular e Event-Driven Presentation Transcript

  • JAVASCRIPT MODULAR E EVENT-DRIVEN @shiota 2013
  • OLÁ!slideshare.net/eshiota github.com/eshiota @shiota
  • front-end engineer @
  • * 28/08/2013 https://github.com/search?p=1&q=stars%3A%3E1&s=stars&type=Repositories Dos 50 repositórios mais populares do Github, 28 repositórios são relacionados a JavaScript.
  • JavaScript é legal!
  • Mas pode ser infernal também.
  • Ele pode ficar impossível de ser entendido ou alterado.
  • (function(){ window.app = jQuery.extend({ init: function(){ tab = $('.tabs li > a.tab-toggle'); tabs = $('.tabs').find('> div'); if (tabs.length > 1){ tab.each(function (i){$(this).attr('href', '#content-' + ++i)}); tabs.each(function(i){$(this).attr('id', 'content-' + ++i)}); tabs.addClass('tab-inactive'); $('.tabs li:first-child a').addClass('state-active'); } $('#initial-cash, #financing_value_vehicles, #tax, #bid-initial-cash, #bid-product-value').maskMoney({ thousands: '.', decimal: ',', allowZero: true, allowNegative: false, defaultZero: true }); /** FINANCING CALCULATOR **/ $("#financing_value_vehicles").on("blur", function(){ var price = (accounting.unformat($(this).val(), ",")) || 0; var suggestedInitialPayment = price * 0.2; var formattedResult = accounting.formatMoney(suggestedInitialPayment, "", "2", ".", ","); $("#initial-cash").val(formattedResult); }); $("#calculate-financing").click(function(event){ var price = (accounting.unformat($("#financing_value_vehicles").val(), ",")) || 0; var rate = (accounting.unformat($("#tax").val(), ",") / 100) || 0; var initialCash = (accounting.unformat($("#initial-cash").val(), ",")) || 0; var value = (accounting.unformat($("#amount-finance").val(), ",")) || 0; var finance = price - initialCash;
  • (um arquivo JS, uma função de 173 LOC, no mínimo 7 responsabilidades)
  • Ele pode virar um CALLBACK HELL (e algumas libraries facilitam ainda mais isso)
  • $(".submit-button").on("click", function () { $.ajax({ url : "/create", type : "POST", success : function (data) { $.each(data.created_items, function (index, value) { var item = $("<div />").text(value); $(".items-list").append(item).hide().fadeIn(400, function () { setTimeout(function () { item.fadeOut(function () { item.remove(); }); }, 1000); }); }); } }); });
  • Uma estrutura modular e baseada em eventos resolve esses problemas e muito mais.
  • DISCLAIMER #1 Sem MV* frameworks
  • DISCLAIMER #2 jQuery (para encurtar os exemplos)
  • DISCLAIMER #3 Pseudo-implementações =)
  • MÓDULOS
  • Responsabilidade única, integrante de um sistema complexo.
  • Responsabilidades e comportamentos isolados.
  • Testáveis.
  • Extensíveis e modificáveis (até certo ponto).
  • Podem ser substituídos, ou reutilizados.
  • Nesta apresentação single entry points namespaces module pattern constructors Module.js mediator & pub/sub mixins
  • Single entry points
  • (function(){ window.app = jQuery.extend({ init: function(){ tab = $('.tabs li > a.tab-toggle'); tabs = $('.tabs').find('> div'); if (tabs.length > 1){ tab.each(function (i){$(this).attr('href', '#content-' + ++i)}); tabs.each(function(i){$(this).attr('id', 'content-' + ++i)}); tabs.addClass('tab-inactive'); $('.tabs li:first-child a').addClass('state-active'); } $('#initial-cash, #financing_value_vehicles, #tax, #bid-initial-cash, #bid-product-value').maskMoney({ thousands: '.', decimal: ',', allowZero: true, allowNegative: false, defaultZero: true }); /** FINANCING CALCULATOR **/ $("#financing_value_vehicles").on("blur", function(){ var price = (accounting.unformat($(this).val(), ",")) || 0; var suggestedInitialPayment = price * 0.2; var formattedResult = accounting.formatMoney(suggestedInitialPayment, "", "2", ".", ","); $("#initial-cash").val(formattedResult); }); $("#calculate-financing").click(function(event){ var price = (accounting.unformat($("#financing_value_vehicles").val(), ",")) || 0; var rate = (accounting.unformat($("#tax").val(), ",") / 100) || 0; var initialCash = (accounting.unformat($("#initial-cash").val(), ",")) || 0; var value = (accounting.unformat($("#amount-finance").val(), ",")) || 0; var finance = price - initialCash;
  • Page load jQuery load jQuery plugins application.js
  • Sem pontos de entrada, é difícil segmentar a execução por página ou rota.
  • Pontos únicos de entrada controlam o flow da aplicação.
  • Page load Vendor code Application modules application.js dispatcher.js beforeCommand controllerCommand actionCommand afterCommand
  • Page load Vendor code Application modules application.js dispatcher.js beforeCommand controllerCommand actionCommand afterCommand
  • O dispatcher executa funções baseadas no controller e na action da página.
  • <body data-dispatcher="<%= dispatcher_label %>">
  • def dispatcher_label action_name = controller.action_name action_name.gsub!(/_/, "") "#{controller_name}##{action_name}" end
  • <body data-dispatcher="products#show">
  • dispatcher.js beforeCommand() productsControllerCommand() productsShowCommand() afterCommand() products#show
  • Os commands não contêm lógica, apenas inicializam outros módulos.
  • Namespaces
  • ns("MYAPP.commands.productsShowCommand", function () { console.log("Execute code from products#show page"); }); // Same as: window.MYAPP = { commands : { productsShowCommand : function () { console.log("Execute code from products#show page"); } } };
  • Ajuda na organização dos módulos.
  • /javascripts /myapp /commands /productsShowCommand.js
  • Não polui o contexto global.
  • Module pattern
  • ns("EDEN.modules.checkoutModule", (function () { // Stores the module's main element var element; // Private methods // --------------- // Prints a silly phrase var printSillyPhrase = function () { console.log("I love sushi"); console.log(this); }; return { // Public methods // -------------- // Inits the module init : function (el) { element = $(el); printSillyPhrase.call(this); } }; })());
  • Isola um comportamento e disponibiliza uma API pública.
  • É "Singleton-like".
  • Constructors & Prototypes
  • ns("EDEN.forms.AddressForm", function (el) { this.element = $(el); this.init(); }); $.extend(EDEN.forms.AddressForm.prototype, { // Public methods // -------------- // Inits the instance init : function () { // Do something } }); var shippingAddressForm = new EDEN.forms.AddressForm($("#shipping-address"));
  • Permite múltiplas instâncias e heranças por prototype.
  • Module.js
  • * github.com/fnando/module
  • Define namespaces e coloca açúcar sintático na definição de funções construtoras.
  • ns("EDEN.forms.AddressForm", function (el) { this.element = $(el); this.init(); }); $.extend(EDEN.forms.AddressForm.prototype, { // Public methods // -------------- // Inits the instance init : function () { // Do something } }); var shippingAddressForm = new EDEN.forms.AddressForm($("#shipping-address"));
  • Module("EDEN.forms.AddressForm", function (AddressForm) { AddressForm.fn.initialize = function (el) { this.element = $(el); // Do something } }); var shippingAddressForm = Module.run("EDEN.forms.AddressForm", $("#shipping-address"));
  • Padroniza a criação de novos módulos.
  • EVENTOS
  • Pub/Sub
  • PUB (server) "Quando sair o torrent do último episódio, vou avisar pra galera."
  • SUB (cliente piratinha) "Quando o server avisar que tem um episódio, vou começar a baixar."
  • SUB PUB
  • Yo, me avisa quando sair o eppy de Game of Thrones? o/ Blz é nóish véi
  • AE SAIU O "THE RAINS OF CASTAMERE" PRA BAIXAR, GALERE! Massa, vou baixar! Acho que vai ser um episódio feliz =D
  • // Pirate subscribes to torrent server torrentServer.on("new-got-episode", function (name) { this.download(name); }); // Torrent server publishes that it has a new GoT episode this.trigger("new-got-episode", "The Rains of Castamere");
  • Mediator
  • Interface central de comunicação entre módulos.
  • MEDIATOR
  • Nenhum módulo tem conhecimento do outro.
  • MEDIATOR Mediator, me avisa quando sair o novo do The Walking Dead? Blz
  • MEDIATOR Mediator, me avisa quando sair o novo do Mythbusters? É nóish.
  • MEDIATOR Mediator, saiu um eppy novo de The Walking Dead. Subscribers, saiu um eppy novo de The Walking Dead! Ae, vou baixar!
  • MEDIATOR Mediator, saiu um eppy novo de Mythbusters. Subscribers, saiu um eppy novo de Mythbusters! Ae, vou baixar!
  • // Pirate 1 subscribes to mediator mediator.on("new-twd-episode", function (name) { console.log("Downloading The Walking Dead - " + name); }); // Pirate 2 subscribes to mediator mediator.on("new-mythbusters-episode", function (name) { console.log("Downloading Mythbusters - " + name); }); // Torrent server 1 publishes on mediator mediator.trigger("new-twd-episode", "The Suicide King"); // Torrent server 2 publishes on mediator mediator.trigger("new-mythbusters-episode", "Food Fables");
  • Todos conhecem apenas o Mediator.
  • Mixin
  • Module("EDEN.forms.AddressForm", function (AddressForm) { // AddressForm now has the `on`, `off` and `trigger` methods $.extend(AddressForm.fn, EDEN.events); AddressForm.fn.initialize = function (el) { this.element = $(el); } });
  • MUNDO REAL
  • Modularizando
  • Exemplos
  • #1 Mudança no endereço de entrega muda o endereço de cobrança
  • // Code inside ShippingAddressForm _attachEvents : function () { this.element.find(".main-address-input").on("keyup paste cut change", this._onAddressModification.bind(this)); }, _onAddressModification : function (event) { EDEN.mediator.trigger("shipping-address-change", event.target.value); }
  • // Code inside BillingAddressSelector _registerInterests : function () { EDEN.mediator.on("shipping-address-change", this.updateAddressInfo, this); } updateAddressInfo : function (address) { this.element.find(".address-info").text(address); }
  • #2 Mudança no CEP de entrega muda frete, total e parcelas
  • // Code inside ShippingAddressForm _registerInterests : function () { this.element.find(".cep-input").on("keyup paste cut", this._onCepModification.bind(this)); }, _onCepModification : function (event) { if (this.isCepFilled()) { EDEN.mediator.trigger("shipping-cep-change", event.target.value); } else { EDEN.mediator.trigger("shipping-cep-incomplete", event.target.value); } }
  • // Code inside checkoutModule _registerIntests : function () { EDEN.mediator.on("shipping-cep-change", this._onShippingCepChange, this); this.shippingService.on("get-success", this._onShippingGetSuccess, this); }, _onShippingCepChange : function (cep) { this.shippingService.get(cep); } _onShippingGetSuccess : function (data) { EDEN.mediator.trigger("shipping-rate-change", data.rate); EDEN.mediator.trigger("delivery-estimate-change", data.estimate); }
  • // Code inside purchaseInfo _registerInterests : function () { EDEN.mediator.on("shipping-rate-change", this._onShippingRateChange, this); EDEN.mediator.on("delivery-estimate-change", this._onDeliveryEstimateChange, this); }, _onShippingRateChange : function (rate) { this.updateShippingRate(rate); }, _onDeliveryEstimateChange : function (days) { this.updateDeliveryEstimate(days); }, updateShippingRate : function (rate) { var formatter = EDEN.currency.formatter; this.element.find(".shipping-rate").text(formatter(rate)); this.shippingRate = rate; this.updateTotal(); }, updateTotal : function () { var total = this.subtotal + this.shippingRate, formatter = EDEN.currency.formatter; this.element.find(".total").text(formatter(total)); EDEN.mediator.trigger("cart-total-change", total); }
  • // Code inside installmentSelector _registerInterests : function () { EDEN.mediator.on("cart-total-change", this._onCartTotalChange, this); }, _onCartTotalChange : function (total) { this.updateInstallments(total); }, updateInstallments : function (total) { // Updates the values }
  • LIVE DEMO
  • futuro.
  • Sempre há espaço para melhoras.
  • Framework MV*?
  • Framework próprio?
  • Melhoria nas padronizações?
  • Sistema de eventos mais robusto?
  • É por isso que JavaScript é legal. =)
  • THANKSslideshare.net/eshiota github.com/eshiota @shiota