Part 2: Google's APIs 和 RequireJS中,我们大概了解了Google’s JavaScript APIs。现在我们就开始讲讲todosAPI,但首先需要一个用户帐户.

准备

开始本教程之前,你需要了解以下几点:

  • alexyoung / dailyjs-backbone-tutorial提交到了9d09a66b1f版本
  • 第二部分中的API key
  • 第二部分中的“Client ID”
  • 更新app/js/config.js成你自己的key(如果你检出了我的代码)

要检出源码,请运行以下命令(或用Git GUI工具):

git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
cd dailyjs-backbone-tutorial
git reset --hard 9d09a66b1f

Google’s OAuth 2.0 客户端 API

打开app/js/gapi.js并查看行11至25,这个地方Google提供了一个叫gapi.auth.authorize的方法,它会使用“Client ID” 和scopes去尝试身份验证,在app/js/config.js我们设置了scopes:

config.scopes = 'https://www.googleapis.com/auth/tasks https://www.googleapis.com/auth/userinfo.profile';

这将告诉认证系统,我们的应用程序要访问用户的配置文件和Gmail任务。一切准备就绪,但还有两件事情是需要完成的:handleAuthResult方法和接口的实现.

模板

RequireJS可以通过使用text插件来加载模板,从GitHub上下载text.js并保存到app/js/lib/text.js.

这是我的首选技术处理模板.虽然这个应用程序可用index.html一个文件来实现,从长远来看把项目分解成更小的模板更易于管理,所以这是一个好主意,习惯了这样做。

现在打开app/js/main.js把text插件添加到RequireJS配置的paths属性中:

paths: {
  text: 'lib/text'
},

app/js/config.js里加上:

_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g
};

这告诉Underscore模板系统使用{{}}插入值,否则称为插值。

同时还需要目录来存储模板和相关文件:

  • app/js/views – Backbone.js 视图
  • app/js/templates – 纯HTML模板,由views加载
  • app/css

该应用程序index.html文件需要加载CSS,所以需要添加链接标签:

<link rel="stylesheet" href="css/app.css">

同时我们新建一个样式文件app/css/app.css:

#sign-in-container, #signed-in-container { display: none }

程序刚启动时我们需要隐藏主体内容和注册按钮.OAuth API查询现有用户凭据—果用户如已经登录其信息将被存储在cookie,因此views应该进行适当的配置。

在这个阶段,模板不是特别明显优势,只是先暂存到app/js/templates/app.html

<div class="row-fluid">
  <div class="span2 main-left-col" id="lists-panel">
    <h1>bTask</h1>
    <div class="left-nav"></div>
  </div>
  <div class="main-right-col">
    <small class="pull-right" id="profile-container"></small>
    <div>
      <div id="sign-in-container"></div>
      <div id="signed-in-container">
        <p>You're signed in!</p>
      </div>
    </div>
  </div>
</div>

模板只显示了sign-in-containersigned-in-container以外的一些元素.

接下来,将下面的粘贴到app/js/templates/auth.html:

<a href="#" id="authorize-button" class="btn btn-primary">Sign In with Google</a>

然后auth.html插入到sign-in-container中,这样是不是很简单,如此Backbone.js的视图就相当于得到了扩展。

Backbone 视图

这些模板需要相应的Backbone.js的视图来进行管理。这部分将演示如何用RequireJS来加载和渲染模板。新建app/js/views/app.js:

define([
  'text!templates/app.html'
],

function(template) {
  var AppView = Backbone.View.extend({
    id: 'main',
    tagName: 'div',
    className: 'container-fluid',
    el: 'body',
    template: _.template(template),

    events: {
    },

    initialize: function() {
    },

    render: function() {
      this.$el.html(this.template());
      return this;
    }
  });

  return AppView;
});

AppView类没有任何事件,但它确实绑定到了body元素,还通过define(['text!templates/app.html']加载了模板,text!是由RequireJS的 “text” 提供的上面我们添加过了。模板文件本身就是一个包含相应的HTML的字符串。它被绑定到Backbone.View渲染,然后调用jQuery的HTML()方法,插入到元素中this.$el.html(this.template());.

AuthView稍微有点不同的是,新建app/js/views/auth.js文件:

define(['text!templates/auth.html'], function(template) {
  var AuthView = Backbone.View.extend({
    el: '#sign-in-container',
    template: _.template(template),

    events: {
      'click #authorize-button': 'auth'
    },

    initialize: function(app) {
      this.app = app;
    },

    render: function() {
      this.$el.html(this.template());
      return this;
    },

    auth: function() {
      this.app.apiManager.checkAuth();
      return false;
    }
  });

  return AuthView;
});

app对象传递给initialize时AuthView被实例化(new AuthView(this)后面).这么做是因为我们需要让视图调用从ApiManager获取所需认证码.这也可以使用事件,或其他方式来处理—我只是想说明带值初始化视图就像任何其他类一样。

App 核心

视图要被实例化和渲染.我们需要修改app/js/app.js,使用RequireJS加载视图:

define([
  'gapi'
, 'views/app'
, 'views/auth'
],

function(ApiManager, AppView, AuthView) {
  var App = function() {
    this.views.app = new AppView();
    this.views.app.render();

    this.views.auth = new AuthView(this);
    this.views.auth.render();

    this.connectGapi();
  }

文件的其余部分保持不变,请注意,这些视图被渲染的顺序是非常重要的—AuthView必须在AppView之后.一个更好的方法就是把AuthView放到AppView里,作为它的依赖.你可以试试这个.

认证实现

app/js/gapi.js文件中handleAuthResult函数仍然是空得,所以工作尚未完成.下面是处理身份验证的代码:

function handleAuthResult(authResult) {
  var authTimeout;

  if (authResult && !authResult.error) {
    // Schedule a check when the authentication token expires
    if (authResult.expires_in) {
      authTimeout = (authResult.expires_in - 5 * 60) * 1000;
      setTimeout(checkAuth, authTimeout);
    }

    app.views.auth.$el.hide();
    $('#signed-in-container').show();
  } else {
    if (authResult && authResult.error) {
      // TODO: Show error
      console.error('Unable to sign in:', authResult.error);
    }

    app.views.auth.$el.show();
  }
}

this.checkAuth = function() {
  gapi.auth.authorize({ client_id: config.clientId, scope: config.scopes, immediate: false }, handleAuthResult);
};

一个技巧是当用户已经登录那么是直接可以看到登录后的信息的。如果是这样,那么认证应不用去处理,否则应提示用户去登录。

handleAuthResult函数是从checkAuth功能gapi.auth.authorize被调用的,这里没有显示这个函数(如果你想看这个函数,你可以在handleAuthResult源文件中的前找到).this.checkAuth方法不同的是 - 这是一个公共方法,调用gapi.auth.authorizeimmediate设置为false,而其他调用设置为true.

immediate选项比较重要他决定了是不是弹出一个浮层.当immediate: false时,我们已经登录用这个来检查它,将显示弹出一个(是否允许用户访问权限控制的)的浮层:

enter image description here

我们这样设计主要是基于Google APIs库javascript版文档:

使用OAuth 2.0 token刷新页面,标准情况下authorize()方法总会弹出一个浮层来.Google’s OAuth 2.0实现同样是支持“immediate”模式的,让刷新不出浮层.要使用“immediate”模式,只要添加“immediate: true”到你的登录配置文件。

我也改变了ApiManager类来存储对App的引用:

// Near the top of gapi.js
var app;

function ApiManager(_app) {
  app = _app;
  this.loadGapi();
}