保存和获取数据
实验三中的示例使用静态数组存放待办事项,每次您的应用重新启动后,您的更改都会丢失。在这一部分,我们将使用 chrome.storage.sync 保存所有更改
这样可以让您存储少量数据,在您在线并登录到 Chrome 时自动同步到云端。如果您处于离线状态或未登录,它将透明地在本地保存,而不需要在您的应用中处理在线检查及离线处理。
将您的待办事项保存到云端
注意:Chrome 同步存储并不是用来充当通用数据库的,它对您可以保存的信息量有一些限制,所以它更适合保存设置和其他小块数据。
更新清单文件
在您的清单文件中请求使用存储的权限,权限与 AngularJS manifest.json 和 JavaScript manifest.json中一致:
{ ... , "permissions": ["storage"] }
更新控制器
修改您的控制器,从可同步的存储中获取待办事项列表,而不是静态列表:AngularJS controller.js 或 JavaScript controller.js。
// 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
。
... [ <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。
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 中更改。
<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中添加消息占位符,警告用户不允许某些类型的拖放:
<div> {{dropText}} </div>
<div id="dropText"> </div>
更新样式表
在 CSS 中为 dragging
和 invalid-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 应用生命周期的基础知识。