Central de Aplicativos

Criando um aplicativo do Twitter usando ÁreaCreating the Area Tweet app

Esta tradução está incompleta. Por favor, ajude a traduzir este artigo.

Com todo desenho e padrões de código aprendidos, é tempo de iniciarmos nosso aplicativo.

HTML

Com em qualquer template de aplicação web, necessitaremos de uma página HTML utilizando a versão 5 do doctype, <head>, and <body> elements.

<!doctype html>
<html>
<head>
  <title></title>
  
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta charset="utf-8" />
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
 
  <meta name="viewport" content="width=device-width, initial-scale=1">
 
  <!-- stylesheets go here -->
 
</head>
<body>
 
  <!-- HTML app structure goes here -->
 
 
  <!-- javascript goes here -->
 
</body>
</html>

Estaremos usando jQuery Mobile como um framework JavaScript. Para isso é necessário colocarmos também os códigos abaixo:

<!-- stylesheets -->
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
	
<!-- scripts -->
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>

Com o template padrão criado e as bibliotecas corretamente linkadas (jQuery mecionadas acima) estamos prontos para inserir novos conteúdos. Desde que decidimos usar o jQuery Mobile como nossa bilblioteca JavaScript, nosso widget HTML irá ter a seguinte estrutura: um painel provendo um campo de busca e uma lista de histórico. Veja abaixo:

<!-- home pane -->
<div data-role="page" id="home">
 
  <div data-role="header">
    <h1>Area Tweet</h1>
  </div>
 
  <div data-role="content">
    
    <div class="ui-body ui-body-b">
      <h2>Location Search</h2>
      <form id="locationForm">
        <input type="search" name="location" id="locationInput" placeholder="Your Location" />
        <button type="submit" data-role="button" data-theme="b">Search</button>
      </form>
    </div>
    
    <div id="prevLocationsContainer" class="opaque">
      <h2>
        Previous Locations
        <a href="#" id="clearHistoryButton" data-role="button" data-icon="delete" data-iconpos="notext"
                data-theme="b" data-inline="true" style="top: 5px;">Clear History</a>
      </h2>
      <ul id="prevLocationsList" data-role="listview" data-inset="true" data-filter="true"></ul>
    </div>
    
  </div>
</div>

Há duas coisas a considerar no código acima:

  • A estrutura do HTML5 possui semantica ao qual garante uma acessibilidade máxima.
  • Como qualquer aplicativo web, nós adicionamos classes CSSs e IDs nos elementos que queremos estilizar e acessar via JavaScript (sendo puro ou por meio da biblioteca jQuery).

Caso tenha alguma dúvida a respeito da sintáxe, ou mesmo aprender, consulte a Documentação da Biblioteca jQuery Mobile (em inglês).

O segundo painel será usado para mostrar a lista de tweets:

<!-- tweets pane -->
<div data-role="page" id="tweets">
 
  <div data-role="header">
    <a href="#home" id="tweetsBackButton">Back</a>
    
    <h1><span id="tweetsHeadTerm"></span> Tweets</h1>
  </div>
 
  <ul id="tweetsList" data-role="listview" data-inset="true"></ul>
 
</div>

CSS

O CSS de nosso aplicativo web conterá uma animação fading (aparecer/desaparecer) para qualquer elemento, e também para sobrescrever os CSSs da biblioteca jQuery Mobile e elementos gerais de estilo.

/* animations */
@-moz-keyframes fadeIn {
  0%    { opacity: 0; }
  100%  { opacity: 1; }
}
@-webkit-keyframes fadeIn {
  0%    { opacity: 0; }
  100%  { opacity: 1; }
}

.fadeIn {
  -moz-animation-name: fadeIn;
  -moz-animation-duration: 2s;
 
  -webkit-animation-name: fadeIn;
  -webkit-animation-duration: 2s;
 
  opacity: 1 !important;
}

/* Custom CSS classes */
.clear        { clear: both; }
.hidden       { display: none; }
.opaque       { opacity: 0; }


/* Basic styling */
#prevLocationsContainer { margin-top: 40px; }

/* Customizing jQuery Styles */
#tweetsList li.ui-li-divider,
#tweetsList li.ui-li-static     { font-weight: normal !important; }


/* Tweet list */
#tweetsList li {
 
}

#tweetsList li .tweetImage {
  float: left;
  margin: 10px 10px 0 10px;
}

#tweetsList li .tweetContent {
  float: left;
  /* margin: 10px 0 0 0; */
}

#tweetsList li .tweetContent strong {
  display: block;
  padding-bottom: 5px;
}

/* Media Queries for device support */
@media screen and (max-device-width: 480px) {
  #prevLocationsContainer { margin-top: 10px; }
}

JavaScript

JavaScript é o maior componte para adicionar em nosso aplicativo Web. O código seguinte engloba todas capacidades do aplicativo:

$(document).ready(function() {
 
  // Start from scratch on page load
  if(window.location.hash) {
    window.location = "index.html";
  }
 
  // Feature tests and settings
  var hasLocalStorage = "localStorage" in window,
    maxHistory = 10;
 
  // List of elements we'll use
  var $locationForm = $("#locationForm"),
    $locationInput = $("#locationInput"),
    
    $prevLocationsContainer = $("#prevLocationsContainer"),
    
    $tweetsHeadTerm = $("#tweetsHeadTerm"),
    $tweetsList = $("#tweetsList");
    
  // Hold last request jqXHR's so we can cancel to prevent multiple requests
  var lastRequest;
 
  // Create an application object
  app = {
    
    // App initialization
    init: function() {
      var self = this;
      
      // Focus on the search box
      focusOnLocationBox();
      
      // Add the form submission event
      $locationForm.on("submit", onFormSubmit);
      
      // Show history if there are items there
      this.history.init();
      
      // When the back button is clicked in the tweets pane, reset the form and focus
      $("#tweetsBackButton").on("click", function(e) {
        $locationInput.val("");
        setTimeout(focusOnLocationBox, 1000);
      });
      
      // Geolocate!
      geolocate();
      
      // When the tweets pane is swiped, go back to home
      $("#tweets").on("swiperight", function() {
        window.location.hash = "";
      });
      
      // Clear history when button clicked
      $("#clearHistoryButton").on("click", function(e) {
        e.preventDefault();
        localStorage.removeItem("history");
        self.history.hideList();
      })
    },
    
    // History modules
    history: {
      $listNode: $("#prevLocationsList"),
      $blockNode: $("#homePrev"),
      init: function() {
        var history = this.getItemsFromHistory(),
          self = this;
        
        // Add items to the list
        if(history.length) {
          history.forEach(function(item) {
            self.addItemToList(item);
          });
          self.showList();
        }
        
        // Use event delegation to look for list items clicked
        this.$listNode.delegate("a", "click", function(e) {
          $locationInput.val(e.target.textContent);
          onFormSubmit();
        });
      },
      getItemsFromHistory: function() {
        var history = "";
        
        if(hasLocalStorage) {
          history = localStorage.getItem("history");
        }
        
        return history ? JSON.parse(history) : [];
      },
      addItemToList: function(text, addToTop) {
        var $li = $("<li><a href='#'>" + text + "</a></li>"),
          listNode = this.$listNode[0];
          
        if(addToTop && listNode.childNodes.length) {
          $li.insertBefore(listNode.childNodes[0]);
        }
        else {
          $li.appendTo(this.$listNode);
        }
        
        this.$listNode.listview("refresh");
      },
      addItemToHistory: function(text, addListItem) {
        var currentItems = this.getItemsFromHistory(),
          newHistory = [text],
          self = this,
          found = false;
        
        // Cycle through the history, see if this is there
        $.each(currentItems, function(index, item) {
          if(item.toLowerCase() != text.toLowerCase()) {
            newHistory.push(item);
          }
          else {
            // We've hit a "repeater": signal to remove from list
            found = true;
            self.moveItemToTop(text);
          }
        });
        
        // Add a new item to the top of the list
        if(!found && addListItem) {
          this.addItemToList(text, true);
        }
        
        // Limit history to 10 items
        if(newHistory.length > maxHistory) {
          newHistory.length = maxHistory;
        }
        
        // Set new history
        if(hasLocalStorage) {
          // Wrap in try/catch block to prevent mobile safari issues with private browsing
          // http://frederictorres.blogspot.com/2011/11/quotaexceedederr-with-safari-mobile.html
          try {
            localStorage.setItem("history", JSON.stringify(newHistory));
          }
          catch(e){}
        }
        
        // Show the list
        this.showList();
      },
      showList: function() {
        $prevLocationsContainer.addClass("fadeIn");
        this.$listNode.listview("refresh");
      },
      hideList: function() {
        $prevLocationsContainer.removeClass("fadeIn");
      },
      moveItemToTop: function(text) {
        var self = this,
          $listNode = this.$listNode;
        
        $listNode.children().each(function() {
          if($.trim(this.textContent.toLowerCase()) == text.toLowerCase()) {
            $listNode[0].removeChild(this);
            self.addItemToList(text, true);
          }
        });
        
        $listNode.listview("refresh");
      }
    }
    
  };
 
  // Search submission
  function onFormSubmit(e) {
    if(e) e.preventDefault();
    
    // Trim the value
    var value = $.trim($locationInput.val());
    
    // Move to the tweets pane
    if(value) {
      
      // Add the search to history
      app.history.addItemToHistory(value, true);
      
      // Update the pane 2 header
      $tweetsHeadTerm.html(value);
      
      // If there's another request at the moment, cancel it
      if(lastRequest && lastRequest.readyState != 4) {
        lastRequest.abort();
      }
      
      // Make the JSONP call to Twitter
      lastRequest = $.ajax("http://search.twitter.com/search.json", {
        cache: false,
        crossDomain: true,
        data: {
          q: value
        },
        dataType: "jsonp",
        jsonpCallback: "twitterCallback",
        timeout: 3000
      });
    }
    else {
      // Focus on the search box
      focusOnLocationBox();
    }
    
    return false;
  }
 
  // Twitter reception
  window.twitterCallback = function(json) {
    
    var template = "<li><img src='{profile_image_url}' class='tweetImage' /><div class='tweetContent'>"
                   +"<strong>{from_user}</strong>{text}</div><div class='clear'></div></li>",
      tweetHTMLs = [];
      
    // Basic error handling
    if(json.error) { // Error for twitter
      showDialog("Twitter Error", "Twitter cannot provide tweet data.");
      return;
    }
    else if(!json.results.length) { // No results
      showDialog("Twitter Error", "No tweets could be found in your area.");
      return;
    }
    
    // Format the tweets
    $.each(json.results, function(index, item) {
      item.text = item.text.
            replace(/(https?:\/\/\S+)/gi,'<a href="$1" target="_blank">$1</a>').
            replace(/(^|\s)@(\w+)/g,'$1<a href="http://twitter.com/$2" target="_blank">@$2</a>').
            replace(/(^|\s)#(\w+)/g,'$1<a href="http://search.twitter.com/search?q=%23$2" target="_blank">#$2</a>')
      tweetHTMLs.push(substitute(template, item));
    });
    
    // Place tweet data into the form
    $tweetsList.html(tweetHTMLs.join(""));
    
    // Refresh the list view for proper formatting
    try {
      $tweetsList.listview("refresh");
    }
    catch(e) {}
    
    // Go to the tweets view
    window.location.hash = "tweets";
  };
 
  // Template substitution
  function substitute(str, obj) {
    return str.replace((/\\?{([^{}]+)}/g), function(match, name){
      if (match.charAt(0) == '\\') return match.slice(1);
      return (obj[name] != null) ? obj[name] : "";
    });
  }
 
  // Geolocates the user
  function geolocate() {
    if("geolocation" in navigator) {
      // Attempt to get the user position
      navigator.geolocation.getCurrentPosition(function(position) {
        // Set the address position
        if(position.address && position.address.city) {
          $locationInput.val(position.address.city);
        }
      });
    }
  }
 
  // Focuses on the input box
  function focusOnLocationBox() {
    $locationInput[0].focus();
  }
 
  // Modal function
  function showDialog(title, message) {
    $("#errorDialog h2.error-title").html(title);
    $("#errorDialog p.error-message").html(message);
    $.mobile.changePage("#errorDialog");
  }
 
  // Initialize the app
  app.init();
 
});

Não iremos mencionar todos detalhes do JavaScript mencionado acima, contudo podemos apontar os itens:

  • O script utiliza recursos de teste para os dois localStorage e geolocation. O aplicativo não tentará usar um recurso que não esteja presente no navegador.
  • O recurso geolocation da API é usado para detectar a localização atual do usuário e o recurso localStorage é usado para guardar o histórico de busca do usuário.
  • O código JavaScript foi escrito de maneira modular facilitando a execução de testes.
  • Um evento swipe atraves do tweet pane. Quando o pane é swiped, o usuário é levado de volta ao primeiro pane.
  • Twitter é a origem de todos os dados. Nenhuma costumização é realizada no lado do servidor.

 

Etiquetas do documento e colaboradores

Colaboradores desta página: fabianosantos.net
Última atualização por: fabianosantos.net,