保存和获取数据

实验三中的示例使用静态数组存放待办事项,每次您的应用重新启动后,您的更改都会丢失。在这一部分,我们将使用 chrome.storage.sync 保存所有更改

这样可以让您存储少量数据,在您在线并登录到 Chrome 时自动同步到云端。如果您处于离线状态或未登录,它将透明地在本地保存,而不需要在您的应用中处理在线检查及离线处理。

将您的待办事项保存到云端

注意:Chrome 同步存储并不是用来充当通用数据库的,它对您可以保存的信息量有一些限制,所以它更适合保存设置和其他小块数据。

更新清单文件

在您的清单文件中请求使用存储的权限,权限与 AngularJS manifest.json JavaScript manifest.json中一致:

{
  ... ,
  "permissions": ["storage"]
}

更新控制器

修改您的控制器,从可同步的存储中获取待办事项列表,而不是静态列表:AngularJS controller.js JavaScript controller.js

Angular
JavaScript
// Notice that chrome.storage.sync.get is asynchronous
chrome.storage.sync.get('todolist', function(value) {
  // The $apply is only necessary to execute the function inside Angular scope
  $scope.$apply(function() {
    $scope.load(value);
  });
});

// If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
$scope.load = function(value) {
  if (value && value.todolist) {
    $scope.todos = value.todolist;
  } else {
    $scope.todos = [
      {text:'learn angular', done:true},
      {text:'build an angular app', done:false}];
  }
}

$scope.save = function() {
  chrome.storage.sync.set({'todolist': $scope.todos});
};
    
  /**
   * Listen to changes in the model and trigger the appropriate changes in the view
   **/
  model.addListener(function(model, changeType, param) {
    if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
      redrawUI(model);
    } else if ( changeType === 'added' ) {
      drawTodo(model.todos[param], list);
    } else if ( changeType === 'stateChanged') {
      updateTodo(model.todos[param]);
    }
    storageSave();
    updateCounters(model);
  });

// If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
  var storageLoad = function() {
    chrome.storage.sync.get('todolist', function(value) {
      if (value && value.todolist) {
        model.setTodos(value.todolist);
      } else {
        model.addTodo('learn Chrome Apps', true);
        model.addTodo('build a Chrome App', false);
      }
    });
  }

  var storageSave = function() {
    chrome.storage.sync.set({'todolist': model.todos});
  };
    

更新视图

在视图中,即 AngularJs index.hmtl JavaScript index.html,每当数据更改时就存储数据。在 Angular 中,我们显式调用 save(),不过有多种方式可以实现这一点。例如,您也可以在区域上使用 $watchers

Angular
JavaScript
...
       [ <a href="" ng-click="archive() || save()">archive</a> ]
...
            <input type="checkbox" ng-model="todo.done" ng-change="save()">
...
       <form ng-submit="addTodo() || save()">
...
    
<form>
  <input type="text" size="30"
         placeholder="add new todo here">
  <input class="btn-primary" type="submit" value="add">
</form>
    

检查结果

重新加载应用,检查结果:打开应用,单击右键并选择“重新加载应用”。现在您可以添加待办事项,关闭应用,在您重新打开应用时新项目仍然存在。

如果您遇到了困难,希望立刻看到应用,请进入 chrome://extensions,加载未打包的应用,并从新标签页中运行应用:Angular JS 1_storage_sync JavaScript 1_storage_sync

处理拖放文件和 URL

假设您想创建与本地文件和/或 URL 关联的待办事项,实现这一点的最自然的方式就是接受拖放的项目。使用标准 HTML5 拖放 API 可以非常简单地在 Chrome 应用中添加拖放支持。

更新控制器

在控制器中,添加代码处理 dragover、dragleave 和 dragdrop 事件:AngularJS controller.js JavaScript controller.js

Angular
JavaScript
var defaultDropText = "Or drop files here...";
$scope.dropText = defaultDropText;

// on dragOver, we will change the style and text accordingly, depending on
// the data being transferred
var dragOver = function(e) {
  e.stopPropagation();
  e.preventDefault();
  var valid = e.dataTransfer && e.dataTransfer.types
    && ( e.dataTransfer.types.indexOf('Files') >= 0
      || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
  $scope.$apply(function() {
    $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
        : "Can only drop files and remote images here";
    $scope.dropClass = valid ? "dragging" : "invalid-dragging";
  });
}

// reset style and text to the default
var dragLeave = function(e) {
  $scope.$apply(function() {
    $scope.dropText = defaultDropText;
    $scope.dropClass = '';
  });
}

// on drop, we create the appropriate TODOs using dropped data
var drop = function(e) {
  e.preventDefault();
  e.stopPropagation();

var newTodos=[];
  if (e.dataTransfer.types.indexOf('Files') >= 0) {
    var files = e.dataTransfer.files;
    for (var i = 0; i < files.length; i++) {
      var text = files[i].name+', '+files[i].size+' bytes';
      newTodos.push({text:text, done:false, file: files[i]});
    }
  } else { // uris
    var uri=e.dataTransfer.getData("text/uri-list");
    newTodos.push({text:uri, done:false, uri: uri});
  }

$scope.$apply(function() {
    $scope.dropText = defaultDropText;
    $scope.dropClass = '';
    for (var i = 0; i < newTodos.length; i++) {
      $scope.todos.push(newTodos[i]);
    }
    $scope.save();
  });
}

document.body.addEventListener("dragover", dragOver, false);
document.body.addEventListener("dragleave", dragLeave, false);
document.body.addEventListener("drop", drop, false);
    
var defaultDropText = "Or drop files here...";

var dropText = document.getElementById('dropText');
dropText.innerText = defaultDropText;

// on dragOver, we will change the style and text accordingly, depending on
// the data being transfered
var dragOver = function(e) {
  e.stopPropagation();
  e.preventDefault();
  var valid = isValid(e.dataTransfer);
  if (valid) {
    dropText.innerText="Drop files and remote images and they will become Todos";
    document.body.classList.add("dragging");
  } else {
    dropText.innerText="Can only drop files and remote images here";
    document.body.classList.add("invalid-dragging");
  }
}

var isValid = function(dataTransfer) {
  return dataTransfer && dataTransfer.types
    && ( dataTransfer.types.indexOf('Files') >= 0
      || dataTransfer.types.indexOf('text/uri-list') >=0 )
}

// reset style and text to the default
var dragLeave = function(e) {
  dropText.innerText=defaultDropText;
  document.body.classList.remove('dragging');
  document.body.classList.remove('invalid-dragging');
}

// on drop, we create the appropriate TODOs using dropped data
var drop = function(e, model) {
  e.preventDefault();
  e.stopPropagation();
  if (isValid(e.dataTransfer)) {
    if (e.dataTransfer.types.indexOf('Files') >= 0) {
      var files = e.dataTransfer.files;
      for (var i = 0; i < files.length; i++) {
        var text = files[i].name+', '+files[i].size+' bytes';
        model.addTodo(text, false, {file: files[i]});
      }
    } else { // uris
      var uri=e.dataTransfer.getData("text/uri-list");
      model.addTodo(uri, false, {uri: uri});
    }
  }

  dragLeave();
}

exports.setDragHandlers = function(model) {
  document.body.addEventListener("dragover", dragOver, false);
  document.body.addEventListener("dragleave", dragLeave, false);
  document.body.addEventListener("drop", function(e) {
      drop(e, model);
    }, false);
}
    

更新视图

如果使用 AngularJS 的话,让我们将 Angular 区域定义从 AngularJS index.html 文件的 div 移动至 body 中,使窗口中的所有区域都接受拖放事件,同时仍然在相同的区域内工作。此外,让我们将 body 的 CSS 类与 Angular 的控制器类关联,这样我们就能直接在区域内修改类名,并使它自动在 DOM 中更改。

Angular
<body ng-controller="TodoCtrl" ng-class="dropClass">
<!-- remember to remove the ng-controller attribute from the div where it was before -->
    

AngularJS index.html JavaScript index.html中添加消息占位符,警告用户不允许某些类型的拖放:

Angular
JavaScript
<div>
 {{dropText}}
</div>
    
<div id="dropText">
</div>
    

更新样式表

在 CSS 中为 dragginginvalid-dragging CSS 选择器添加合适的样式,AngularJS todo.css JavaScript todo.css 是一样的。这里我们使用绿色和红色的背景颜色动画:

@-webkit-keyframes switch-green {
  from { background-color: white;} to {background-color: rgb(163, 255, 163);}
}
@-webkit-keyframes switch-red {
  from { background-color: white;} to {background-color: rgb(255, 203, 203);}
}
.dragging {
  -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
}

.invalid-dragging {
  -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
}

检查结果

重新加载应用,检查结果:打开应用,单击右键并选择“重新加载应用”。现在您可以将文件拖放至待办事项列表。

如果您遇到了困难,希望立刻看到应用,请进入 chrome://extensions,加载未打包的应用,并从新标签页中运行应用:AngularJS 2_drop_files JavaScript 2_drop_files

挑战

当前的代码只保存了文件引用,但是它并没有打开文件。使用 HTML 5 文件系统 API,将文件内容保存在经过沙盒屏蔽的文件系统中。当待办事项完成后,从经过沙盒屏蔽的文件系统中删除相应的文件。在每一个与文件关联的待办事项上添加一个“打开”链接,单击项目后,如果文件在经过沙盒屏蔽的文件系统中存在,则使用 Chrome 应用的文件系统 API向用户请求可写的 FileEntry(文件项),将经过沙盒屏蔽的文件系统中的文件数据保存至文件项。

提示: 直接使用 HTML5 文件系统 API 管理文件项并不是很一般,您可能希望使用外包库,例如 Eric Bidelman 的 filer.js

更多信息

  • 使用 chrome.storage.sync 保存您需要在不同设备间同步的少量数据,例如配置选项、应用程序状态等等。同步是自动的,只要所有设备上都是同一个用户登录 Chrome。

  • Chrome 应用支持几乎所有 HTML5 API,例如拖放。HTML 文件系统 API 也支持,同时还有来自 Chrome 应用文件系统 API 扩展的额外特性,例如让用户在本地磁盘上选择文件进行读写,而普通的 HTML5 文件系统 API 只允许访问经过沙盒屏蔽的文件系统。

您还应该阅读

管理数据教程

接下来做什么?

5 - 管理应用的生命周期 中,您将会学习 Chrome 应用生命周期的基础知识。