presentation front end from scratch event i a ls

Post on 13-May-2017

234 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Criando uma arquitetura de front-end do zero 

@shiota 2013

olá!slideshare.net/eshiota

github.com/eshiota @shiota

como a Baby (re)nasceu

* reprodução pelo WaybackMachine, não é 100% precisa

* reprodução do dia 27/08/2013

projeto greenfield (a.k.a. o sonho de todo desenvolvedor)

como estruturar o front-end do zero?

filosofia de um front-end de larga escala

Alta performance client-side.

Interface facilmente modificável.

Componentes portáveis entre diferentes aplicações.

Bulletproof web design.

* veja http://simplebits.com/publications/bulletproof/

Tipografia e grids flexíveis, responsive-ready.

Baixa barreira de entrada para outros desenvolvedores.

definição de suporte aos navegadores

latest latest 5+ iOS 6+

8+

latest latest 5+ iOS 6+

8+

u mad?

O meu website precisa ter o visual exatamente igual em todos os navegadores?

O meu website precisa ter exatamente a mesma experiência em todos os navegadores?

A escolha dos navegadores e o nível de suporte influencia escolhas e tempo de desenvolvimento.

CSS

pre-processors: sass

O uso de partials ajuda a organizar os módulos.

@import "compass/css3";!@import "base/variables";@import "base/functions";@import "base/mixins";@import "base/helpers";!@import "core/reset";@import "core/basic";@import "core/forms";@import "core/tables";@import "core/typography";@import "core/icons";@import "core/buttons";!@import "layout/main";@import "layout/header";@import "layout/footer";!@import "ui/loader";@import "ui/loaderBar";@import "ui/flashMessage";@import "ui/breadcrumb";

app/ assets/ stylesheets/ base/ _functions.scss _mixins.scss _variables.scss ui/ _breadcrumb.scss _carousel.scss _dentedBox.scss _flashMessage.scss

Variáveis ajudam a manter os mesmos padrões de interface.

/*********************************************************************** Variables Module** All constants that will be used through the styles must be* defined here.**********************************************************************/!/********************************************************************** =Dimensions*********************************************************************/!$SITE_WIDTH: 978px;$FOOTER_HEIGHT : 812px;$DEFAULT_FONT_SIZE : 16px;!/********************************************************************** =Colors*********************************************************************/!$TEXT_COLOR: #555;$LINK_COLOR: #447f87;!$PURPLE: #905194;$LIGHT: #fefefa;$ORANGE: #fbb100;$YELLOW: #fffd7d;

Mixins e placeholders padronizam repetições de código.

/********************************************************************** =Image replacement** `display` property may be overridden by the element.*********************************************************************/!%image_replacement { text-indent: 101%; overflow: hidden; white-space: nowrap; display: block;}

.my-logo { text-indent: 100%; overflow: hidden; white-space: nowrap; display: block; width: 200px; height: 280px; background: url("mylogo.png");}!.my-other-logo { text-indent: 100%; overflow: hidden; white-space: nowrap; display: block; width: 100px; height: 150px; background: url("myotherlogo.png");}

.my-logo { @extend %image_replacement; width: 200px; height: 280px; background: url("mylogo.png");}!.my-other-logo { @extend %image_replacement; width: 100px; height: 150px; background: url("myotherlogo.png");}

Mixins permitem a criação de temas.

/* <div class="section-header my-theme1"> ... </div>*/!.section-header { width: 100%; height: 15em; color: #fff; background-color: #905194; background-position: center center; background-repeat: no-repeat; background-image: url("themes/default_bg.jpg"); text-align: center;}!.my-theme1 { background-color: #fbb100; color: #fff; background-image: url("themes/theme1_bg.jpg");}!.my-theme2 { background-color: #fefefa; color: #333; background-image: url("themes/theme2_bg.jpg");}

/* <div class="section-header my-theme1"> ... </div>*/!@mixin header_theme($background_color: #905194, $text_color: #fff, $image: "default_bg.jpg") { background-color: $background_color; color: $text_color; background-image: url("themes/#{$image}");}

/* <div class="section-header my-theme1"> ... </div>*/!.section-header { @include header_theme; width: 100%; height: 15em; background-position: center center; background-repeat: no-repeat; text-align: center;}!.my-theme1 { @include header_theme(#fefefa, #333, "theme2_bg.jpg");}!.my-theme2 { @include header_theme(#fefefa, #333, "theme2_bg.jpg");}

(use com moderação)

Funções aceleram o processo de desenvolvimento.

// Returns unitless number@function remove-unit($number) { $unit: unit($number); $one: 1;! @if $unit == "px" { $one: 1px; } @if $unit == "em" { $one: 1em; } @if $unit == "%" { $one: 1%; }! @return $number / $one;}!// Returns flexible value using `target ÷ context` formula.// Returns `em` by default, accepts `%` as format.@function flex($target, $context, $unit: "em") { $size: remove-unit($target) / remove-unit($context);! @if $unit == "em" { @return #{$size}em; } @if $unit == "%" { @return percentage($size); }}!// Alias to `flex` function, using `%` as format.@function perc($target, $context: $DEFAULT_FONT_SIZE) { @return flex($target, $context, "%");}!// Alias to `flex` function, using `em` as format.@function em($target, $context: $DEFAULT_FONT_SIZE) { @return flex($target, $context, "em");}

.product-title { font-size: 1.5625em; /* 25px / 16px */}

.product-title { font-size: em(25px, 16px);}

.product-title { font-size: em(25px);}

Sintaxe SCSS: quase não há curva de adaptação para quem já escreve CSS.

Extensões podem auxiliar de jeitos inimagináveis. (mais sobre isso daqui a pouco)

modularização

Front-end deve saber de programação?

CSS possui muitas similaridades com princípios de programação.

estrutura base (reset, base styles)

grid

padrões

módulos

módulos contextualizados

Single Responsability Principle Módulos de CSS possuem comportamentos contidos e isolados.

/******************************************************************************* UI » Flyout** Flyouts are those UI components that look like tooltip, and* are activated when the user clicks on a link. The flyout window* opens text to the link, like those present on the iPad.** **Usage**** <div class="flyout-container">* <div class="flyout [vertical-position]-[horizontal-position]-flyout">* Flyout content* </div>* </div>******************************************************************************/!.flyout-container { position: relative; z-index: 100; // may be adjusted as needed through a context}!.flyout { @include box-sizing(border-box); background: #f9f9f9; border-radius: 2px; border: 1px solid #d5d5d5; box-shadow: 0 2px 0 rgba(0, 0, 0, .1); display: none; position: absolute;! // tip &:after { content: ""; display: block; width: 40px; height: 22px; background: sprite($icon-sprite, tooltip_top_large_gray) no-repeat; position: absolute; }}!...

Open/close Principle Módulos de CSS devem poder ser extendidos sem modificar sua definição core.

/********************************************************************************* UI > Loader** Animated loader for AJAX requests********************************************************************************/!@mixin loader_sprite_position($xoffset, $yoffset) { background-position: sprite-position($icon-sprite, loader_sprite, $xoffset, $yoffset);}!.loader { width: 25px; height: 25px; display: none;}!.loader b { display: block; width: 25px; height: 25px; background-image: sprite-url($icon-sprite);}!.loader b,.loader .f1 { @include loader_sprite_position(-10px, -10px); }.loader .f2 { @include loader_sprite_position(-45px, -10px); }.loader .f3 { @include loader_sprite_position(-80px, -10px); }.loader .f4 { @include loader_sprite_position(-115px, -10px); }.loader .f5 { @include loader_sprite_position(-150px, -10px); }.loader .f6 { @include loader_sprite_position(-185px, -10px); }.loader .f7 { @include loader_sprite_position(-220px, -10px); }.loader .f8 { @include loader_sprite_position(-255px, -10px); }

// On ui/_buttons.scss!.bt-wrapper .loader { position: absolute; z-index: 4; right: 20px; top: 50%; margin-top: -9px;}!// On modules/_checkoutAddressForm.scss!.address-form .cep-input .loader { position: absolute; right: -33px; top: em(29px);}

Dependency Inversion Principle Módulos macro não devem ter seus layouts alterados por módulos micro.

.flyout

guias de estilo

#cheat #wip

JavaScript

qual framework usar?

Analise qual (ou se) vale a pena.

Você precisa de rotas client-side?

Você precisa de sincronização e persistência de modelos client-side?

Você precisa de uma solução pronta pra fazer bind entre view e dados?

Você precisa de uma estrutura pronta e fechada para manter a consistência do código?

Às vezes você não precisa de um framework terceiro. =)

Talvez tudo o que você precise seja um código consistente e organizado.

decisões de arquitetura

"Mas Shiota, todo mundo falou pra eu abandonar o jQuery!"

O jQuery diminui bastante a barreira de entrada e dá agilidade.

Analise a necessidade. Pese os benefícios. Pesquise outras soluções.

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;! var months = (accounting.unformat($("#prize_parcela").val(), ",")) || 0; var tax = parseFloat(rate);!

Page load

jQuery load

jQuery plugins

application.js

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

<body data-dispatcher="<%= dispatcher_label %>">

<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

"JavaScript é zoado! Não tem nem namespaces!"

window.MYAPP = { commands : { productsShowCommand : function () { console.log("Execute code from products#show page"); } }};!MYAPP.commands.productsShowCommand();

"Mas ficar declarando objetos é um saco, e você pode acabar sobrescrevendo..."

;(function (root) { root.ns = function (name, obj, scope) { var parts = name.split(".") , curScope = scope || root , curPart , curObj ;! obj = obj || {};! while (typeof (curPart = parts.shift()) !== "undefined") { curObj = (parts.length > 0) ? ((typeof curScope[curPart] !== "undefined") ? curScope[curPart] : {}) : obj;! curScope[curPart] = curObj;! curScope = curScope[curPart]; }! return curScope; };})(this);

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

module.js

Define namespaces e coloca açúcar sintático na definição de funções construtoras.

window.EDEN = window.EDEN || {};EDEN.forms = EDEN.forms || {};!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 form stuff }!});!var shippingAddressForm = Module.run("EDEN.forms.AddressForm", $("#shipping-address"));!// or!var shippingAddressForm = new EDEN.forms.AddressForm($("#shipping-address"));

Padroniza a criação de novos módulos.

desacoplamento via eventos

MEDIATOR

MEDIATOR

Mediator, me avisa quando sair o novo do Game of

Thrones?

Blz

MEDIATOR

Mediator, me avisa quando sair o novo do Mythbusters?

É nóish.

MEDIATOR

Mediator, saiu um eppy novo de Game of Thrones.

Subscribers, saiu um eppy novo de Game of Thrones!

Ae, vou baixar, acho que vai ser feliz e tal

=D

MEDIATOR

Mediator, saiu um eppy novo de Mythbusters.

Subscribers, saiu um eppy novo de Mythbusters!

Ae, vou baixar!

Os módulos só conhecem o mediator.

módulos desacoplados, com comportamentos específicos e isolados

// 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!_registerInterests : 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 purchseInfo!_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}

testes

describe("EDEN.ui.Loader", function () { var Loader = EDEN.ui.Loader;! beforeEach(function () { loadFixtures("loader.html"); });! afterEach(function () { $("body").find(".loader").remove(); });! it("accepts instance creation without new operator", function () { var newLoader = Loader();! expect(newLoader).toBeInstanceOf(Loader); });! it("appends the loader to body as a default", function () { var loader = new Loader();! expect($("body").find(".loader").length).toEqual(1); });! it("appends the loader through an argument function", function () { var loader = new Loader(function ($loader) { $("#loader-placeholder").append($loader); });! expect($("#loader-placeholder").find(".loader").length).toEqual(1); });});

"Mas se eu escrever teste de JavaScript, eu não entrego o projeto!"

(e os testes de JavaScript quebram o build do CI.)

"Mas pra que teste de JavaScript?"

— "Precisamos atualizar o jQuery de 1.4 para 1.10"

Testes dão a segurança para atualizações, modificações e substituições.

HTML

sintaxe: erb

<% product.foreach_variant(current_cart) do |variant, size, i| %> <label class="variant"> <input value="<%= size.value %>" name="product_size" type="radio" data-price="<%= variant.price.to_f %>" data-variant-sku="<%= variant.sku %>" class="<%= "disabled" unless variant.has_stock? %>" /> <span class="variant-name"><%= size.value %></span> </label><% end %>

Próximo do HTML puro.

Todos sabem (ou deveriam saber) escrever HTML puro.

Menos uma dependência no projeto.

classes semânticas

Usem classes que descrevem o conteúdo e não o estilo.

<div class="column-left"> <!-- Dropdown Categoria --> <!-- Dropdown Marca --></div>!<div class="column-right"> <!-- Product Navigation --> <!-- Products List --></div>

column-left?

column-right?

Classes semânticas desacoplam o documento do estilo.

<div class="products-search-filters"> <!-- Dropdown Categoria --> <!-- Dropdown Marca --></div>!<div class="products-search-filtered-results"> <!-- Product Navigation --> <!-- Products List --></div>

products-search-filters

products-search-filtered-results

Maior portabilidade do markup entre diferentes projetos.

data-attributes

contentDropdown.js

Componentes com mesmo comportamento podem ter estilos diferentes.

<div class="size-selector"> <div class="size-selector-header" data-dropdown-header> <!-- some title, could be anything --> </div>! <div class="size-selector-content" data-dropdown-content> <!-- content here --> </div></div>!<div class="filter-selector"> <div class="filter-selector-header" data-dropdown-header> <!-- some title, could be anything --> </div>! <div class="filter-selector-content" data-dropdown-content> <!-- content here --> </div></div>

Classes diferentes, estilos diferentes.

data-attributes iguais, comportamento igual.

// Inits the `ContentDropdown` instance//// * `el`: jQuery selectorinit : function (el) { this.element = $(el); this.content = this.element.byData("dropdown-content"); this.selection = this.element.byData("dropdown-selection");}

$.fn.byData = function (dataAttr) { return $(this).find("[data-" + dataAttr + "]");};

Comportamento é adicionado através dos data-attributes.

dicas adicionais

performance client-side

Faça sprites. De preferência, de maneira automágica.

$icon-sprite: sprite-map("icon/*.png", $spacing: 16px, $repeat: no-repeat, $layout: vertical);

$icon-sprite: sprite-map("icon/*.png", $spacing: 16px, $repeat: no-repeat, $layout: vertical);

/* Compass sprite function receives the map variable and image as arguments */!background: sprite($icon-sprite, arrow_dropdown) no-repeat;

/* Compiled CSS */!background: url(/assets/icon-s5dab8c2901.png) -40px -158px no-repeat;

Use inline images para imagens < 1KB que estarão apenas em um lugar do CSS.

/* Compiled CSS */!background: #f5f3fb url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQCAMAAAAcVM5PAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF5+TW////////4qZUpQAAAAN0Uk5T//8A18oNQQAAACBJREFUeNpiYGBgAgMGBkYog4mJXAbILAiDkVxzAAIMAEMOAPMId2OWAAAAAElFTkSuQmCC') repeat;

/* Generates a base64 image */!background: #f5f3eb inline-image("bg_dots.png") repeat;

Use lazy-load onde fizer sentido.

code standards

"O código deve parecer que foi escrito pela mesma pessoa, independente de quem o escreveu."

Código consistente: maior legibilidade, manutenção mais fácil, evita bikeshedding.

build para o deploy

Ruby on Rails » Asset Pipeline » ♥

TL;DR

Alta performance client-side Interface facilmente modificável Componentes portáveis entre diferentes aplicações Bulletproof web design Tipografia e grids flexíveis, responsive-ready Baixa barreira de entrada para outros desenvolvedores

Não saia adicionando códigos de terceiros sem pensar.

Front-end deixou de ser coisa de agência e sobrinho.

obrigado!slideshare.net/eshiota

github.com/eshiota @shiota

top related