该页面翻译自 Google Chrome Extensions 与 Google Chrome Apps。除非特别说明,该页面的内容遵循 Creative Commons Attribution 3.0 License,代码示例遵循 BSD License。
实验三中的示例使用静态数组存放待办事项,每次您的应用重新启动后,您的更改都会丢失。在这一部分,我们将使用 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 关联的待办事项,实现这一点的最自然的方式就是接受拖放的项目。使用标准 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 应用生命周期的基础知识。