search check home clock-o tag tags chevron-left chevron-right chevron-up chevron-down twitter facebook github rss comment comments terminal code

[JS] Tworzymy aplikację JS z wykorzystaniem Backbone.js, część 2 – Integracja z Twitterem

[JS] Tworzymy aplikację JS z wykorzystaniem Backbone.js, część 2 – Integracja z Twitterem

Tak jak obiecałem wcześniej, zamierzam kontynuować serię wpisów dotyczących tworzenia aplikacji internetowej, której zadaniem będzie wspomaganie zarządzania kontami w sieciach Twitter i Facebook.

W poprzedniej części, omówiłem czym jest framework Backbone.js oraz jakie dodatkowe biblioteki będą potrzebne do poprawnego działania aplikacji.
W tej części, skupimy się na utworzeniu danych dostępowych aplikacji do serwisu Twitter, a następnie przejdziemy do części typowo programistycznej, gdzie utworzymy mechanizm autoryzacji oraz mechanizm pobierania wpisów z Twittera.

Integracja z Twitterem

Pierwszym krokiem jakie należy wykonać, to utworzenie konta w serwisie Twitter (jeśli go jeszcze nie masz), dzięki temu będzie można utworzyć aplikację, która uzyska autoryzowany dostęp do API Twittera. Gdy już posiadamy takie konto, należy się udać na stronę developerów Twittera i zalogować się. Z menu użytkownika klikamy w pozycję My applications (obrazek poniżej).
Menu developera Twittera

W nowym widoku znajdziemy przycisk Create a new application w który należy kliknąć. Pojawi się wtedy widok formularza, w którym należy wypełnić pola:

  • Name - nazwa aplikacji,
  • Description - opis aplikacji,
  • Website - adres strony internetowej, gdzie można będzie korzystać z aplikacji.

Opcjonalnie, można wypełnić pole: Callback URL, w którym powinien się znaleźć adres strony, która ma się pojawić po poprawnym zalogowaniu się za pomocą Twittera, możemy dać tam link do strony aplikacji. Zgadzamy się na warunki umowy i wypełniamy (tak bardzo znienawidzone) pole captchy.

Po wykonaniu tych wszystkich kroków pojawia się strona z danymi aplikacji, gdzie znajdziemy informacje dotyczące aplikacji oraz tokeny (zaznaczone na czerwono na obrazku poniżej).
Szczegóły aplikacji

Tym sposobem utworzyliśmy aplikację, która będzie stanowiła nasze źródło danych z Twittera, które będzie można później wykorzystać w naszej aplikacji społecznościowej. Na chwilę obecną, poziom dostępu pozwala tylko na odczyt danych, ale w ustawieniach aplikacji można to zmienić i na potrzeby aplikacji rozszerzyć uprawnienia do poziomu trzeciego, czyli: Read, Write and Access direct messages. Dzięki temu, można będzie odczytywać tweety z tablicy użytkownika oraz wysyłać własne tweety do serwisu Twitter.

Autoryzacja konta użytkownika za pomocą Twittera - sposób działania

Aby móc korzystać ze swojego konta na Twitterze za pomocą naszej aplikacji, użytkownik musi najpierw autoryzować dostęp aplikacji do swoich danych w serwisie Twitter. Dlatego na początku utworzymy prosty formularz logowania się, który będzie się składał z dwóch części:

  • standardowych pól logowania: login i hasło,
  • pole do wpisania kodu dostępowego, który zostanie wygenerowany przez API Twittera.

Logowanie i autoryzacja w Twitterze

W pierwszy momencie będą dostępne tylko dwa pola: login i hasło, a po kliknięciu w przycisk - Zaloguj się, pojawią się dodatkowe pole tekstowe i przyciski. Gdy użytkownik kliknie w przycisk - Autoryzuj aplikację, to zostanie przekierowany do strony logowania na Twitterze (jeśli nie jest już tam zalogowany), a następnie pojawi się okno z kodem, który należy wpisać w odpowiednim polu naszej aplikacji. Na sam koniec, użytkownik będzie musiał kliknąć przycisk - Zweryfikuj kod PIN i jeśli kod zostanie wpisany poprawnie, to aplikacja przeładuje swój widok i pojawią się tweety z tablicy użytkownika.

Backbone.js rusza do akcji - logowanie i autoryzacja za pomocą Twittera

Aby wspomóc i przyspieszyć proces tworzenia aplikacji wykorzystamy bibliotekę dostępną na Githubie - Codebird.js, której zadaniem realizacja żądań pomiędzy aplikacją a API Twittera.
Budowanie aplikacji internetowej zaczynamy od utworzenia pliku index.html, w którym zdefiniujemy bazową strukturę HTML oraz załadujemy niezbędne pliki CSS i JS. Początkowa struktura takiego pliku wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Aplikacja społecznościowa</title>
  <meta name="viewport" content="width=device-width, maximum-scale=1.0">
  <meta name="author" content="Piotr Nalepa">
  <link rel="stylesheet" href="css/main.css">
  <link rel="shortcut icon" href="favicon.png" type="image/png">
</head>
<body>
  <main></main>
  <script src="lib/js/jquery.2.0.3.min.js"></script>
  <script src="lib/js/lodash.1.3.1.min.js"></script>
  <script src="lib/js/backbone.1.0.0.min.js"></script>
  <script src="lib/js/backbone.localStorage-min.js"></script>
  <script src="lib/js/codebird.2.4.2.core.js"></script>
  <script src="lib/js/codebird.2.4.2.sha1.js"></script>
  <script src="js/app/globals.js"></script>
  <script src="js/app/model/tweet.js"></script>
  <script src="js/app/collection/tweets.js"></script>
  <script src="js/app/view/main.js"></script>
  <script src="js/app/view/tweet.js"></script>
  <script src="js/app/view/twitter.js"></script>
  <script src="js/app/view/topnav.js"></script>
  <script src="js/app/view/loginPopup.js"></script>
  <script src="js/app/app.js"></script>
</body>
</html>

Tak utworzony plik zapisujemy w folderze projektu. Zapomniałbym wspomnieć, że struktura projektu wygląda następująco:

Początkowa struktura projektu

Backbone.js - model dla pojedynczego tweeta

Jak wiadomo, Backbone.js jest frameworkiem MV*, czyli opiera się na modelach, widokach i pozostałych rzeczach (gwiazdka w nazwie). Dlatego, dla pojedynczego elementu, jakim jest tweet, należy utworzyć jego odwzorowanie w postaci modelu. W naszym przypadku, nie ma tutaj wielkiej logiki i model wygląda nastepująco:

1
2
3
4
5
6
7
8
// js/app/model/tweet.js
(function () {
  'use strict';
 
  APP.model = APP.model || {};
 
  APP.model.Tweet = Backbone.Model.extend();
})();

Należy jednak zwrócić tutaj uwagę na kilka rzeczy. Zastosowano tutaj dyrektywę "use strict", która wymusza pisanie kodu wg najnowszych standardów. Dzięki temu, przyszłościowo, kod ma być zgodny ze standardem ECMAScript 6.
Ponadto, wykorzystano przestrzenie nazw, dzięki czemu kod aplikacji nie będzie kolidował z kodem rozszerzeń czy bibliotek Javascript.
Poza tym, zastosowano standardowe tworzenie modelu wg specyfikacji Backbone.js bez potrzeby jego rozwinięcia.

Każdy model będzie miał wszystkie własności obiektu JSON, ktory jest zwracany przez Twitter API. Serwis ten zwraca dużą ilość danych, a przykładowa struktura obiektu pojedynczego tweeta (w tym przypadku jest to tweet Romana Kołtonia) wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
{
  created_at: "Sun Oct 06 07:27:46 +0000 2013",
  id: 386754638499688450,
  id_str: "386754638499688448",
  text: "W "Cafe Futbol" głównie o kadrze. Można zadawać pytania do Piotra Świerczewskiego i prowadzących na Polsatsport.pl! http://t.co/fMEwtgksKv",
  source: "web",
  truncated: false,
  in_reply_to_status_id: null,
  in_reply_to_status_id_str: null,
  in_reply_to_user_id: null,
  in_reply_to_user_id_str: null,
  in_reply_to_screen_name: null,
  user: {
    id: 472275856,
    id_str: "472275856",
    name: "Roman Kołtoń",
    screen_name: "KoltonRoman",
    location: "Polska",
    description: "Dziennikarz, reporter i komentator Polsat Sport",
    url: "http://t.co/SqJltf4RGS",
    entities: {
      url: {
        urls: [{
          url: "http://t.co/SqJltf4RGS",
          expanded_url: "http://POLSATSPORT.PL",
          display_url: "POLSATSPORT.PL",
          indices: [0, 22]
        }]
      },
      description: {
        urls: [ ]
      }
    },
    protected: false,
    followers_count: 23489,
    friends_count: 110,
    listed_count: 146,
    created_at: "Mon Jan 23 19:46:18 +0000 2012",
    favourites_count: 286,
    utc_offset: null,
    time_zone: null,
    geo_enabled: false,
    verified: false,
    statuses_count: 2250,
    lang: "pl",
    contributors_enabled: false,
    is_translator: false,
    profile_background_color: "C0DEED",
    profile_background_image_url: "http://abs.twimg.com/images/themes/theme1/bg.png",
    profile_background_image_url_https: "https://abs.twimg.com/images/themes/theme1/bg.png",
    profile_background_tile: false,
    profile_image_url: "http://a0.twimg.com/profile_images/1776192945/IMG_0324_face0_normal.jpg",
    profile_image_url_https: "https://si0.twimg.com/profile_images/1776192945/IMG_0324_face0_normal.jpg",
    profile_link_color: "0084B4",
    profile_sidebar_border_color: "C0DEED",
    profile_sidebar_fill_color: "DDEEF6",
    profile_text_color: "333333",
    profile_use_background_image: true,
    default_profile: true,
    default_profile_image: false,
    following: true,
    follow_request_sent: null,
    notifications: null
  },
  geo: null,
  coordinates: null,
  place: null,
  contributors: null,
  retweet_count: 2,
  favorite_count: 1,
  entities: {
    hashtags: [ ],
    symbols: [ ],
    urls: [{
      url: "http://t.co/fMEwtgksKv",
      expanded_url: "http://www.polsatsport.pl/pilka-nozna/swierczewski-gosciem-cafe-futbol.-zadawajcie-pytania-do-niego-i-ekspertow-polsatu-sport.html#.UlEQh4L7uTc",
      display_url: "polsatsport.pl/pilka-nozna/sw…",
      indices: [116, 138]
    }],
    user_mentions: [ ]
  },
  favorited: false,
  retweeted: false,
  possibly_sensitive: false,
  lang: "pl"
}

Backbone.js - kolekcja modeli

Kolejnym krokiem będzie utworzenie swoistego "kontenera" na modele tweetów. Backbone zapewnia odpowiednie mechanizmy tworzenia kolekcji. W naszym przypadku, wygląda to tak:

1
2
3
4
5
6
7
8
9
10
// js/app/collection/tweets.js
(function () {
  'use strict';
 
  APP.collection = APP.collection || {};
 
  APP.collection.Tweets = Backbone.Collection.extend({
    model : APP.model.Tweet
  });
})();

Należy pamiętać o tym, aby do każdej kolekcji przypisać model, którego instancje mają być zbierane. W tym przypadku, zaznaczono że chcemy zbierać modele tweetów.

Backbone.js - widok pojedynczego Tweeta

Kolejnym krokiem jest utworzenie widoku pojedynczego tweeta, który będzie stanowił samodzielną, niezależną jednostkę. W aplikacjach internetowych pisanych za pomocą JS, to widoki są tym miejscem gdzie dzieje się najwięcej. Są one odpowiedzialne za wyświetlenie elementu, za przypisanie obsługi odpowiednich zdarzeń na elementach umieszczonych w danym widoku oraz są odpowiedzialne za komunikację z innymi widokami czy serwisami.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// js/app/view/tweet.js
(function () {
  'use strict';
 
  APP.view = APP.view || {};
 
  APP.view.Tweet = Backbone.View.extend({
    tagName   : 'article',
    className : 'tweet clearfix',
    template  : 'tweet',
    render    : function () {
      var text;
      var templateVars;
      var item;
      var media;
      var user;
 
      // funkcja odpowiada za analizę tekstu zamieszczonego w wiadomości
      // i wyłuskanie z niej adresu URL
      // a następnie zastąpienie go linkiem
      var createUrl = function (text) {
        var urlRegex = /(https?:\/\/[^\s]+)/g;
        return text.replace(urlRegex, function (url) {
          return '<a href="http://blog.piotrnalepa.pl/2013/10/07/js-tworzymy-aplikacje-js-z-wykorzystaniem-backbone-js-czesc-2/" rel="nofollow" target="_blank">' + url + '</a>';
        });
      };
 
      // funkcja odpowiada za zamianę adresy URL do małego obrazka profilu
      // na adres URL do większego obrazka
      var biggerProfileImage = function (imageUrl) {
        imageUrl = imageUrl.replace('_normal.', '_bigger.');
        return imageUrl;
      };
 
      // pobieramy treść pojedynczej wiadomości
      text  = createUrl(this.model.get('text'));
      // pobieramy informację o ewentualnych treściach multimedialnych
      // zawartych w obiekcie wiaodmości, np. obrazki
      media = this.model.get('entities').media;
      // pobieramy informacje o użytkowniku
      user  = this.model.get('user');
 
      // tworzymy obiekt z danymi
      // który zostanie wykorzystany w templatce pojedynczego tweeta
      templateVars  = {
        text      : text,
        userName  : user.name,
        photoUrl  : biggerProfileImage(user.profile_image_url),
        postDate  : this.model.get('created_at'),
        additionalImage : _.isEmpty(media) ? '' : {
          src   : media[0].media_url,
          alt   : media[0].id,
          title : media[0].expanded_url
        }
      };
 
      // pobieramy wygenerowany widok HTML wiadomości
      item = APP.loadTemplate(this.template, templateVars);
 
      // dodajemy wygenerowany widok wiadomości, do kontenera na wiadomość
      // należy zwrócić uwagę na to, że this.el prowadzi do obiektu JS
      // kontenera wiadomości, a this.$el prowadzi do obiektu jQuery kontenera wiadomości.
      // Jest to zachowanie wymuszone przez framework Backbone.js
      this.$el.append(item);
 
      // zwracamy obiekt widoku wiadomości
      return this;
    }
  });
})();

Backbone.js - szablon pojedynczego widoku

W poprzedniej części kodu odnosiliśmy się do szablonu. Do obsługi szablonów wykorzystano mechanizm udostępniany przez bibliotekę Underscore.js/Lodash.js. Widok wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// js/app/template/tweet.js
<aside>
  <img class="post-user-image" src="{{= photoUrl }}" alt="{{= userName }}" width="75" height="75">
</aside>
<section>
  <header>
    <h3>{{= userName }}</h3>
    <time datetime="{{= postDate }}">{{= postDate }}</time>
  </header>
  <p>{{= text }}</p>
  {{ if (additionalImage) { }}
    <p><img src="{{= additionalImage.src }}" alt="{{= additionalImage.alt }}" title="{{= additionalImage.title }}"/></p>
  {{ } }}
</section>

Na pewno zauważyłeś/aś, że w kodzie się pojawiają specyficzny tekst w klamrach. Są to placeholdery na tekst, który jest przesyłany w zmiennej templateVars w kodzie widoku tweeta.

Powyżej, pojawia się jeszcze kilka ciekawych rzeczy. Szablon HTML jest zapisany jako plik JavaScript, dzięki temu może być odpowiednio parsowany przez mechanizm szablonów.
Zastosowane znaczniki placeholderów też nie są standardowe. Domyślne znaczniki systemu szablonów mają inny wygląd, który został nadpisany w ustawieniach aplikacji.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// js/app/globals.js
(function () {
  'use strict';
 
  // zmiana domyślnych placeholderów na wygodniejsze
  _.templateSettings = {
    evaluate    : /\{\{(.+?)\}\}/g,   // placeholder funkcyjny
    interpolate : /\{\{=(.+?)\}\}/g,  // placeholder treści
    escape      : /\{\{-(.+?)\}\}/g   // placeholder parsowany
  };
 
  window.APP = window.APP || {
    cache : {
      templates : {},
      views     : {}
    },
    twitter       : new Codebird(),
    loadTemplate  : function (templateFilePath, templateData) {
      console.info('Loading: ' + templateFilePath + ' with data:', templateData);
 
      // jeśli szablonu nie ma w cache aplikacji
      if (!APP.cache.templates[templateFilePath]) {
        // ścieżka dostępu do folderu z szablonem
        var templateDir     = 'js/app/template/';
        // ścieżka dostępu do pliku
        var templateUrl     =  templateDir + templateFilePath + '.js';
        var templateString  = '';
 
        $.ajax({
          async   : false,
          dataType: 'text',
          url     : templateUrl,
          success : function (response) {
            // jeśli załadowano szablon, przypisz wynik zapytania do zmiennej
            templateString = response;
          },
          error   : function (response) {
            // jeśli się nie udał, wyświetl błąd
            console.info('Error: ', response);
            return false;
          }
        });
 
        // parsuj szablon i zapisz go w cache
        APP.cache.templates[templateFilePath] = _.template(templateString);
      }
 
      // zwróć szablon z wypełnionymi placeholderami
      return APP.cache.templates[templateFilePath](templateData);
    },
    config : function (action, key, value) {
      var config = {
        debug : false,
        // Adres serwera proxy, który będzie odpytywany o nowe tweety.
        // Jesy wymagany do poprawnego działania aplikacji: https://github.com/jublonet/codebird-cors-proxy/
        proxy : 'http://localhost/social/proxy/',
        // dane dostępowe aplikacji do API Twittera - nie są bezpieczne!!
        consumer : {
          key     : '[consumer key aplikacji z API Twittera]',
          secret  : '[consumer secret aplikacji z API Twittera]'
        },
        // dane dostępowe użytkownika do aplikacji
        token : {}
      };
 
      return {
        // pobierz wartość wybranego ustawienia
        get : function (key, all) {
          console.info('Getting config for', key);
 
          if (all) {
            return config;
          } else {
            return config[key];
          }
        },
        // zmień wartość ustawienia
        set : function (key, value) {
          console.info('Updating config with', key, value);
 
          config[key] = value;
 
          return this;
        }
      };
    }
  };
})();

Powyższy kawałek kodu, definiuje kilka ciekawych mechanizmów. Jak zwykle, korzystamy z przestrzeni nazw aplikacji i w tej przestrzeni utworzyliśmy odniesienie do biblioteki Codebird.js, zdefiniowaliśmy funkcję loadTeamplate, ktora wspomaga ładowanie plików z szablonami w naszej aplikacji oraz utworzono mechanizm dostępu do danych konfiguracyjnych. Należy pamiętać o tym, że przedstawiony w powyższym kodzie sposób dostępu aplikacji do danych dostępowych API Twittera nie jest bezpiecznym rozwiązaniem. Ma on na celu uproszczenie aplikacji na potrzeby tego wpisu, ale w wersji produkcyjnej tego typu kody dostępowe powinny być umieszczone po stronie serwera, aby utrudnić dostęp do nich.

Backbone.js - widok panelu Twittera

Mamy już przygotowane modele, kolekcje i widoki dla pojedynczego tweeta. Teraz należy to złożyć w jedną całość, która będzie prezentowana jako ściana wiadomości z Twittera. W tym celu, należy utworzyć kolejny widok odpowiedzialny za wyświetlenie tej ściany.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// js/app/view/twitter.js
(function () {
  'use strict';
 
  APP.view = APP.view || {};
 
  // tworzymy widok ściany Twittera
  APP.view.Twitter = Backbone.View.extend({
    // określamy miejsce wstawienia kontenera widoku
    el          : $('body').find('main'),
    // tworzymy kontener widoku
    container   : $('<div/>').prop({ id : 'twitter' }),
    collection  : {},
    // gdy utworzymy funkcję initialize, to będzie ona uruchamiana automatycznie
    // podczas inicjacji widoku Twittera - jest to domyślne zachowanie frameworka Backbone.js
    initialize  : function () {
      console.info('Twitter: initializing Twitter timeline view');
 
      // ustawiamy połączenie z serwerem proxy
      APP.twitter.setProxy(APP.Config.get('proxy'));
      // konfigurujemy dane dostępowe aplikacji do API Twittera
      APP.twitter.setConsumerKey(APP.Config.get('consumer').key, APP.Config.get('consumer').secret);
 
      // zapisz dane użytkownika w localStorage przeglądarki użytkownika
      var twitterUserConfig = store.get('twitterUserConfig');
 
      // jeżeli użytkownik jest zalogowany i dokonał autoryzacji aplikacji
      if ((APP.Config.get('token').key !== undefined && APP.Config.get('token').secret !== undefined) || twitterUserConfig !== undefined) {
        console.info('Twitter: User is logged in.');
 
        // jeśli localStorage jest zdefiniowany
        if (twitterUserConfig) {
          // pobierz ustawienia z locaStorage
          APP.twitter.setToken(twitterUserConfig.key, twitterUserConfig.secret);
        } else {
          // pobierz ustawienia z cache aplikacji
          APP.twitter.setToken(APP.Config.get('token').key, APP.Config.get('token').secret);
        }
 
        // pobierz wiadomości
        this.getTweets();
      } else {
        console.info('Twitter: User is not logged in');
 
        // wyświetl okno logowania
        this.renderPopup(this.twitter);
      }
 
    },
    render : function () {
      var that = this;
 
      // usuń zawartość kontenera Twittera
      that.$el.empty();
      // usuń istniejące wiadomości z obiektu
      that.container.empty();
 
      // pobierz wiadomości z kolekcji tweetów i pokaż je
      _.each(this.collection.models, function (item) {
        that.renderTweet(item);
      }, this);
 
      // wstaw wygenerowane wiadomości do kontenera Twittera
      that.$el.append(that.container);
    },
    getTweets : function () {
      console.info('Twitter: getting tweets');
 
      var that = this;
 
      // pobierz tweety z tablicy użytkownika
      APP.twitter.__call('statuses_homeTimeline', {}, function (tweets) {
        // jeśli nie ma błędów
        if (!tweets.errors) {
          // utwórz nową kolekcję wiadomości
          that.collection = new APP.collection.Tweets(tweets);
          // wyrenderuj je
          that.render();
        } else {
          console.error('Twitter error', tweets.errors);
        }
      });
    },
    renderTweet : function (item) {
      // utwórz nowy obiekt pojedynczej wiadomości/tweeta
      var tweetView = new APP.view.Tweet({ model : item });
 
      // wstaw do kontenera na wiadomości wygenerowany tweet
      this.container.append(tweetView.render().el);
    },
    renderPopup : function () {
      console.info('Twitter: loading login popup');
 
      // utwórz nowy obiekt widoku okna logowania do Twittera
      var popup = new APP.view.LoginPopup();
 
      // wyrenderuj go
      popup = popup.render();
 
      // wstaw do kontenera Twittera
      this.$el.empty().append(popup);
    }
  });
})();

Za pomocą powyższego kodu decydujemy czy mamy pokazać najnowsze wiadomości z konta użytkownika czy też pokazać okno logowania. Widok ten pełni rolę swoistego kontrolera (jeśli można to tak nazwać).

Backbone.js - widok okna logowania do Twittera

Aby zapewnić aplikacji dostęp do twitterowych wiadomości z konta użytkownika, należy autoryzować aplikację w systemie. W tym celu potrzebujemy okno logowania (pierwsza faza) oraz okna autoryzacji (druga faza). Za obydwie fazy będzie odpowiedzialny jeden widok.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// js/app/view/loginPopup.jsj
(function () {
  'use strict';
 
  APP.view = APP.view || {};
  // tworzymy widok popupu
  APP.view.LoginPopup = Backbone.View.extend({
    el          : $('body').find('main'),
    container   : $('<div/>').prop({
      id        : 'login-popup',
      className : 'modal login'
    }),
    template    : 'loginPopup',
    // definiujemy zdarzenia na elementach formularza
    // zdarzenia też możemy umieszczać w przestrzeni nazw
    events      : {
      'click.APP #login-submit' : 'submit',
      'click.APP #login-verify' : 'verify'
    },
    initialize  : function () {
      console.info('Initializing login popup view');
    },
    // renderujemy widok i zwracamy kod HTML widoku (nie wyświetlamy go)
    render : function () {
      var form = APP.loadTemplate(this.template, {});
      var container = this.container.empty().append(form);
 
      return container;
    },
    // wysyłanie danych do logowania
    submit : function (event) {
      event.preventDefault();
      console.info('Submiting data', event);
 
      // wysyłamy prośbę o autoryzację dostępu
      this.authorize();
    },
    authorize : function () {
      console.info('Twitter: logging user');
      var  that = this;
 
      // wysyłamy żadanie zalogowania użytkownika
      APP.twitter.__call(
        'oauth_requestToken',
        { oauth_callback : 'oob' },
        function (reply) {
          // tokeny która otrzymaliśmy w odpowiedzi wstawiamy do kolejnych metod
          // w celu zapamiętania
          APP.twitter.setToken(reply.oauth_token, reply.oauth_token_secret);
 
          APP.Config.set('token', {
            key     : reply.oauth_token,
            secret  : reply.oauth_token_secret
          });
 
          // pobierz URL do autoryzacji aplikacji
          APP.twitter.__call('oauth_authorize', {}, function (authorizationUrl) {
            console.info('Twitter: authorize application', authorizationUrl);
 
            // wyświetl ukryte pola
            var hiddenFields = that.$el.find('.hidden');
 
            // wstaw pobrany adres URL do przycisku, który otworzy nowe okno ze stroną
            // z wygenerowanego adresu URL
            that.$el.find('#verification-link').prop({ href : authorizationUrl });
 
            // odblokuj ukryte przyciski
            hiddenFields.removeClass('hidden');
            hiddenFields.find('#login-pincode').prop({ disabled : false });
          });
      });
    },
    // funkcja weryfikująca kod PIN podany przez użytkownika
    verify : function (event) {
      event.preventDefault();
      console.info('Twitter: verify application PIN code');
 
      var that = this;
 
      // wykonaj żądanie potwierdzenia poprawności kodu PIN
      APP.twitter.__call(
        'oauth_accessToken',
        { oauth_verifier : $('#login-pincode').val() },
        function (reply) {
          // dokonano autoryzacji
          console.info('Twitter: application verified. Render tweets');
 
          // zachowaj tokeny otrzymane podczas autoryzacji (mogą być różne od tych otrzymanych w fazie logowania)
          var token = {
            key     : reply.oauth_token,
            secret  : reply.oauth_token_secret
          };
 
          APP.Config.set('token', token);
 
          store.set('twitterUserConfig', token);
 
          APP.twitter.setToken(reply.oauth_token, reply.oauth_token_secret);
 
          that.container.remove();
          // ponowne załadowanie widoku panelu Twittera
          APP.App.activeView = new APP.view.Twitter();
      });
    }
  });
})();

W tej części dużo się dzieje i ma miejsce komunikacja z API Twittera w celu weryfikacji użytkownika. Dzięki dwufazowej weryfikacji danych, użytkownik może być pewny że aplikacja jest autoryzowana w systemie.

Oprócz kodu widoku, potrzebny jest nam też szablon okna logowania. Wygląda on następująco:

1
2
3
4
5
6
7
8
9
10
11
// js/app/template/loginPopup.js
<form action="#">
  <ul>
    <li><input type="text" id="login-username" name="username" placeholder="Nazwa użytkownika"/></li>
    <li><input type="password" id="login-password" name="password" placeholder="Hasło"/></li>
    <li><button id="login-submit" class="btn">Zaloguj się</button></li>
    <li class="hidden"><a id="verification-link" href="#" target="_blank" class="btn">Autoryzuj aplikację</a></li>
    <li class="hidden"><label for="pincode">Wpisz kod PIN: </label><input type="text" name="pincode" id="login-pincode" disabled/></li>
    <li class="hidden"><button id="login-verify" class="btn">Zweryfikuj kod PIN</button></li>
  </ul>
</form>

Jak widać, jest prosty to formularz logowania, który jednak nie zawiera żadnych placeholderów na dane z aplikacji. Po prostu nie ma takiej potrzeby.

Podsumowanie

Jak widać, proces tworzenia aplikacji internetowej jest dość pracochłonny. Trzeba rozważyć różne przypadki: czy użytkownik jest zalogowany, czy dobrze podane są dane weryfikacyjne, itd. Lecz dzięki temu, otrzymujemy szkielet aplikacji, który pozwala nam na dalszy rozwój i zapobiega niepotrzebnym nerwom w różnych sytuacjach.

Tak utworzony kod aplikacji, jest odpowiedzialny tylko i wyłącznie za jedną część - za panel dostępu do wiadomości z Twittera. Należy mieć jednak na uwadze, że nie jest to kod z którego można być 100% zadowolonym i uznać go za gotową aplikację. Nie, jest to kod aplikacji, która działa i pokazuje jak można tworzyć aplikacje za pomocą frameworka Backbone.js. Zapraszam do zabawy z tym kodem, bo niewątpliwie kilka rzeczy można ulepszyć i usprawnić. Póki co, jest to kod aplikacji która działa i spełnia postawione jej wymagania.

Inne części z serii:

  1. Część 1 - Tworzymy aplikację JS z wykorzystaniem Backbone.js, część 1 – Zapoznanie z frameworkiem
  2. Część 3 - Tworzymy aplikację JS z wykorzystaniem Backbone.js – Integracja z Facebookiem
  3. Część 4 - Tworzymy aplikację JS z wykorzystaniem Backbone.js – wysyłanie wiadomości
  • Comandeer

    Po zobaczeniu HTML-owego fundamentu lekko się przeraziłem. mam nadzieję, że jedna część będzie poświęcona optymalizacji tego kodu 😉
    a po spojrzeniu na strukturę plików, od razu nasunęło mi się skojarzenie z yeomanem – po co mam to klupać ręcznie, skoro wpiszę komendę w konsoli i skrypt mi sam wszystko ładnie wygeneruje?
    widzę, że korzystasz z module pattern. osobiście jednak window.APP budzi we mnie pewne podejrzenia. raczej szedłbym w kierunku tego, co proponuje jQuery: sprawdza czy dostępny jest module.exports (czyli moduły Common.js), jeśli nie to szuka define (i sprawdza czy to AMD poprzez define.amd) a dopiero w ostateczności korzysta z globalnego obiektu. oczywiście w twoim wypadku takie sprawdzanie niekoniecznie jest konieczne i możesz od razu zastosować kod dla odpowiedniego systemu modułów 😉 ale to taka dygresja
    prawdopodobnie jestem staroświecki, ale nie robiłbym tego w ten sposób. nie jestem zwolennikiem pustych stron tam, gdzie można userowi zaserwować jakąś treść. tweety serwowałbym… normalnie, zgodnie z PE, jedynie ulepszając interfejs usera przez JS (funny fact: właśnie na przykładzie twitterowych klientów udowodniono czemu PE jest szybsze od takiego podejścia ;))
    btw funkcję loadTemplate widziałbym mimo wszystko jako asynchroniczną (tak jest przecież naturalniej dla JS!)