当前位置:Gxlcms > 数据库问题 > 前端的数据库:IndexedDB 。 ps:入门

前端的数据库:IndexedDB 。 ps:入门

时间:2021-07-01 10:21:17 帮助过:2人阅读

  技术分享
window.indexedDB = window.indexedDB ||
                   window.mozIndexedDB ||
                   window.webkitIndexedDB ||
                   window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction ||
                   window.webkitIDBTransaction ||
                   window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange ||
                   window.webkitIDBKeyRange ||
                   window.msIDBKeyRange;
View Code

 

现在,每个数据库相关的全局对象持有正确的版本,应用程序可以准备使用IndexedDB开始工作。

应用概述

在本教程中,您将学习如何创建一个使用IndexedDB存储数据的模块化JavaScript应用程序。为了了解应用程序是如何工作的,参考图4,它描述了任务应用程序处于空白状态。从这里您可以为列表添加新任务。图5显示了录入了几个任务到系统的画面。图6显示如何删除一个任务,图7显示了正在编辑任务时的应用程序。

技术分享

图4:空白的任务应用程序

技术分享 图5:任务列表 技术分享 图6:删除任务 技术分享 图7:编辑任务
现在你熟悉的应用程序的功能,下一步是开始为网站铺设基础。

 

铺设基础

这个例子从实现这样一个模块开始,它负责从数据库读取数据,插入新的对象,更新现有对象,删除单个对象和提供在一个object store删除所有对象的选项。这个例子实现的代码是通用的数据访问代码,您可以在任何object store上使用。

这个模块是通过一个立即执行函数表达式(IIFE)实现,它使用对象字面量来提供结构。下面的代码是模块的摘要,说明了它的基本结构。

          JavaScript   技术分享
(function (window) {
    ‘use strict‘;
    var db = {
        /* implementation here */
    };
    window.app = window.app || {};
    window.app.db = db;
}(window));
View Code

 

用这样的结构,可以使这个应用程序的所有逻辑封装在一个名为app的单对象上。此外,数据库相关的代码在一个叫做db的app子对象上。

这个模块的代码使用IIFE,通过传递window对象来确保模块的适当范围。使用use strict确保这个函数的代码函数是按照(javascript严格模式)严格编译规则。db对象作为与数据库交互的所有函数的主要容器。最后,window对象检查app的实例是否存在,如果存在,模块使用当前实例,如果不存在,则创建一个新对象。一旦app对象成功返回或创建,db对象附加到app对象。

本文的其余部分将代码添加到db对象内(在implementation here会评论),为应用程序提供特定于数据库的逻辑。因此,如你所见本文后面的部分中定义的函数,想想父db对象移动,但所有其他功能都是db对象的成员。完整的数据库模块列表见清单2。

Implementing Database-Specific Code

对数据库的每个操作关联着一个先决条件,即有一个打开的数据库。当数据库正在被打开时,通过检查数据库版本来判断数据库是否需要任何更改。下面的代码显示了模块如何跟踪当前版本,object store名、某成员(保存了一旦数据库打开请求完成后的数据库当前实例)。

          JavaScript  
version: 1,
objectStoreName: ‘tasks‘,
instance: {},

 

在这里,数据库打开请求发生时,模块请求版本1数据库。如果数据库不存在,或者版本小于1,upgrade needed事件在打开请求完成前触发。这个模块被设置为只使用一个object store,所以名字直接定义在这里。最后,实例成员被创建,它用于保存一旦打开请求完成后的数据库当前实例。

接下来的操作是实现upgrade needed事件的事件处理程序。在这里,检查当前object store的名字来判断请求的object store名是否存在,如果不存在,创建object store。

          JavaScript  
upgrade: function (e) {
    var
        _db = e.target.result,
        names = _db.objectStoreNames,
        name = db.objectStoreName;
    if (!names.contains(name)) {
        _db.createObjectStore(
            name,
            {
                keyPath: ‘id‘,
                autoIncrement: true
            });
    }
},

 


在这个事件处理程序里,通过事件参数e.target.result来访问数据库。当前的object store名称的列表在_db.objectStoreName的字符串数组上。现在,如果object store不存在,它是通过传递object store名称和store的键的定义(自增,关联到数据的ID成员)来创建。

模块的下一个功能是用来捕获错误,错误在模块不同的请求创建时冒泡。

          JavaScript   技术分享
errorHandler: function (error) {
    window.alert(‘error: ‘ + error.target.code);
    debugger;
},
View Code

 

在这里,errorHandler在一个警告框显示任何错误。这个函数是故意保持简单,对开发友好,当你学习使用IndexedDB,您可以很容易地看到任何错误(当他们发生时)。当你准备在生产环境使用这个模块,您需要在这个函数中实现一些错误处理代码来和你的应用程序的上下文打交道。

现在基础实现了,这一节的其余部分将演示如何实现对数据库执行特定操作。第一个需要检查的函数是open函数。

          JavaScript  
open: function (callback) {
    var request = window.indexedDB.open(
        db.objectStoreName, db.version);
    request.onerror = db.errorHandler;
    request.onupgradeneeded = db.upgrade;
    request.onsuccess = function (e) {
        db.instance = request.result;
        db.instance.onerror =
            db.errorHandler;
        callback();
    };
},

 


open函数试图打开数据库,然后执行回调函数,告知数据库成功打开可以准备使用。通过访问window.indexedDB调用open函数来创建打开请求。这个函数接受你想打开的object store的名称和你想使用的数据库版本号。

一旦请求的实例可用,第一步要进行的工作是设置错误处理程序和升级函数。记住,当数据库被打开时,如果脚本请求比浏览器里更高版本的数据库(或者如果数据库不存在),升级函数运行。然而,如果请求的数据库版本匹配当前数据库版本同时没有错误,success事件触发。

如果一切成功,打开数据库的实例可以从请求实例的result属性获得,这个实例也缓存到模块的实例属性。然后,onerror事件设置到模块的errorHandler,作为将来任何请求的错误捕捉处理程序。最后,回调被执行来告知调用者,数据库已经打开并且正确地配置,可以使用了。

下一个要实现的函数是helper函数,它返回所请求的object store。

          JavaScript   技术分享
getObjectStore: function (mode) {
    var txn, store;
    mode = mode || ‘readonly‘;
    txn = db.instance.transaction(
        [db.objectStoreName], mode);
    store = txn.objectStore(
        db.objectStoreName);
    return store;
},
View Code

 


在这里,getObjectStore接受mode参数,允许您控制store是以只读还是读写模式请求。对于这个函数,默认mode是只读的。

每个针对object store的操作都是在一个事物的上下文中执行的。事务请求接受一个object store名字的数组。这个函数这次被配置为只使用一个object store,但是如果你需要在事务中操作多个object store,你需要传递多个object store的名字到数组中。事务函数的第二个参数是一个模式。

一旦事务请求可用,您就可以通过传递需要的object store名字来调用objectStore函数以获得object store实例的访问权。这个模块的其余函数使用getObjectStore来获得object store的访问权。

下一个实现的函数是save函数,执行插入或更新操作,它根据传入的数据是否有一个ID值。

          JavaScript   技术分享
save: function (data, callback) {
    db.open(function () {
        var store, request,
            mode = ‘readwrite‘;
 
        store = db.getObjectStore(mode),
        request = data.id ?
            store.put(data) :
            store.add(data);
        request.onsuccess = callback;
    });
},
View Code

 

save函数的两个参数分别是需要保存的数据对象实例和操作成功后需要执行的回调。读写模式用于将数据写入数据库,它被传入到getObjectStore来获取object store的一个可写实例。然后,检查数据对象的ID成员是否存在。如果存在ID值,数据必须更新,put函数被调用,它创建持久化请求。否则,如果ID不存在,这是新数据,add请求返回。最后,不管put或者add 请求是否执行了,success事件处理程序需要设置在回调函数上,来告诉调用脚本,一切进展顺利。

下一节的代码在清单1所示。getAll函数首先打开数据库和访问object store,它为store和cursor(游标)分别设置值。为数据库游标设置游标变量允许迭代object store中的数据。data变量设置为一个空数组,充当数据的容器,它返回给调用代码。

在store访问数据时,游标遍历数据库中的每条记录,会触发onsuccess事件处理程序。当每条记录访问时,store的数据可以通过e.target.result事件参数得到。虽然实际数据从target.result的value属性中得到,首先需要在试图访问value属性前确保result是一个有效的值。如果result存在,您可以添加result的值到数据数组,然后在result对象上调用continue函数来继续迭代object store。最后,如果没有reuslt了,对store数据的迭代结束,同时数据传递到回调,回调被执行。

现在模块能够从data store获得所有数据,下一个需要实现的函数是负责访问单个记录。

          JavaScript  
get: function (id, callback) {
    id = parseInt(id);
    db.open(function () {
        var
            store = db.getObjectStore(),
            request = store.get(id);
        request.onsuccess = function (e){
            callback(e.target.result);
        };
    });
},

 


get函数执行的第一步操作是将id参数的值转换为一个整数。取决于函数被调用时,字符串或整数都可能传递给函数。这个实现跳过了对如果所给的字符串不能转换成整数该怎么做的情况的处理。一旦一个id值准备好了,数据库打开了和object store可以访问了。获取访问get请求出现了。请求成功时,通过传入e.target.result来执行回调。它(e.target.result)是通过调用get函数得到的单条记录。

现在保存和选择操作已经出现了,该模块还需要从object store移除数据。

          JavaScript   技术分享
‘delete‘: function (id, callback) {
    id = parseInt(id);
    db.open(function () {
        var
            mode = ‘readwrite‘,
            store, request;
        store = db.getObjectStore(mode);
        request = store.delete(id);
        request.onsuccess = callback;
    });
},
View Code

 

delete函数的名称用单引号,因为delete是JavaScript的保留字。这可以由你来决定。您可以选择命名函数为del或其他名称,但是delete用在这个模块为了API尽可能好的表达。

传递给delete函数的参数是对象的id和一个回调函数。为了保持这个实现简单,delete函数约定id的值为整数。您可以选择创建一个更健壮的实现来处理id值不能解析成整数的错误例子的回调,但为了指导原因,代码示例是故意的。

一旦id值能确保转换成一个整数,数据库被打开,一个可写的object store获得,delete函数传入id值被调用。当请求成功时,将执行回调函数。

在某些情况下,您可能需要删除一个object store的所有的记录。在这种情况下,您访问store同时清除所有内容。

          JavaScript   技术分享
deleteAll: function (callback) {
    db.open(function () {
        var mode, store, request;
        mode = ‘readwrite‘;
        store = db.getObjectStore(mode);
        request = store.clear();
        request.onsuccess = callback;
    });
}
View Code

 

这里deleteAll函数负责打开数据库和访问object store的一个可写实例。一旦store可用,一个新的请求通过调用clear函数来创建。一旦clear操作成功,回调函数被执行。

执行用户界面特定代码

现在所有特定于数据库的代码被封装在app.db模块中,用户界面特定代码可以使用此模块来与数据库交互。用户界面特定代码的完整清单(index.ui.js)可以在清单3中得到,完整的(index.html)页面的HTML源代码可以在清单4中得到。

结论

随着应用程序的需求的增长,你会发现在客户端高效存储大量的数据的优势。IndexedDB是可以在浏览器中直接使用且支持异步事务的文档数据库实现。尽管浏览器的支持可能不能保障,但在合适的情况下,集成IndexedDB的Web应用程序具有强大的客户端数据的访问能力。

在大多数情况下,所有针对IndexedDB编写的代码是天然基于请求和异步的。官方规范有同步API,但是这种IndexedDB只适合web worker的上下文中使用。这篇文章发布时,还没有浏览器实现的同步格式的IndexedDB API。

一定要保证代码在任何函数域外对厂商特定的indexedDB, IDBTransaction, and IDBKeyRange实例进行了规范化且使用了严格模式。这允许您避免浏览器错误,当在strict mode下解析脚本时,它不会允许你对那些对象重新赋值。

你必须确保只传递正整数的版本号给数据库。传递到版本号的小数值会四舍五入。因此,如果您的数据库目前版本1,您试图访问1.2版本,upgrade-needed事件不会触发,因为版本号最终评估是相同的。

立即执行函数表达式(IIFE)有时叫做不同的名字。有时可以看到这样的代码组织方式,它称为self-executing anonymous functions(自执行匿名函数)或self-invoked anonymous functions(自调用匿名函数)。为进一步解释这些名称相关的意图和含义,请阅读Ben Alman的文章Immediately Invoked Function Expression (IIFE) 。

Listing 1: Implementing the getAll function

          JavaScript   技术分享
getAll: function (callback) {
 
    db.open(function () {
 
        var
            store = db.getObjectStore(),
            cursor = store.openCursor(),
            data = [];
 
        cursor.onsuccess = function (e) {
 
            var result = e.target.result;
 
            if (result &&
                result !== null) {
 
                data.push(result.value);
                result.continue();
 
            } else {
 
                callback(data);
            }
        };
 
    });
},
View Code

 


Listing 2: Full source for database-specific code (index.db.js)
          JavaScript   技术分享
// index.db.js
 
;
 
window.indexedDB = window.indexedDB ||
                   window.mozIndexedDB ||
                   window.webkitIndexedDB ||
                   window.msIndexedDB;
 
window.IDBTransaction = window.IDBTransaction ||
                   window.webkitIDBTransaction ||
                   window.msIDBTransaction;
 
window.IDBKeyRange = window.IDBKeyRange ||
                   window.webkitIDBKeyRange ||
                   window.msIDBKeyRange;
 
(function(window){
 
    ‘use strict‘;
 
    var db = {
 
        version: 1, // important: only use whole numbers!
 
        objectStoreName: ‘tasks‘,
 
        instance: {},
 
        upgrade: function (e) {
 
            var
                _db = e.target.result,
                names = _db.objectStoreNames,
                name = db.objectStoreName;
 
            if (!names.contains(name)) {
 
                _db.createObjectStore(
                    name,
                    {
                        keyPath: ‘id‘,
                        autoIncrement: true
                    });
            }
        },
 
        errorHandler: function (error) {
            window.alert(‘error: ‘ + error.target.code);
            debugger;
        },
 
        open: function (callback) {
 
            var request = window.indexedDB.open(
                db.objectStoreName, db.version);
 
            request.onerror = db.errorHandler;
 
            request.onupgradeneeded = db.upgrade;
 
            request.onsuccess = function (e) {
 
                db.instance = request.result;
 
                db.instance.onerror =
                    db.errorHandler;
 
                callback();
            };
        },
 
        getObjectStore: function (mode) {
 
            var txn, store;
 
            mode = mode || ‘readonly‘;
 
            txn = db.instance.transaction(
                [db.objectStoreName], mode);
 
            store = txn.objectStore(
                db.objectStoreName);
 
            return store;
        },
 
        save: function (data, callback) {
 
            db.open(function () {
 
                var store, request,
                    mode = ‘readwrite‘;
 
                store = db.getObjectStore(mode),
 
                request = data.id ?
                    store.put(data) :
                    store.add(data);
 
                request.onsuccess = callback;
            });
        },
 
        getAll: function (callback) {
 
            db.open(function () {
 
                var
                    store = db.getObjectStore(),
                    cursor = store.openCursor(),
                    data = [];
 
                cursor.onsuccess = function (e) {
 
                    var result = e.target.result;
 
                    if (result &&
                        result !== null) {
 
                        data.push(result.value);
                        result.continue();
 
                    } else {
 
                        callback(data);
                    }
                };
 
            });
        },
 
        get: function (id, callback) {
 
            id = parseInt(id);
 
            db.open(function () {
 
                var
                    store = db.getObjectStore(),
                    request = store.get(id);
 
                request.onsuccess = function (e){
                    callback(e.target.result);
                };
            });
        },
 
        ‘delete‘: function (id, callback) {
 
            id = parseInt(id);
 
            db.open(function () {
 
                var
                    mode = ‘readwrite‘,
                    store, request;
 
                store = db.getObjectStore(mode);
 
                request = store.delete(id);
 
                request.onsuccess = callback;
            });
        },
 
        deleteAll: function (callback) {
 
            db.open(function () {
 
                var mode, store, request;
 
                mode = ‘readwrite‘;
                store = db.getObjectStore(mode);
                request = store.clear();
 
                request.onsuccess = callback;
            });
 
        }
    };
 
    window.app = window.app || {};
    window.app.db = db;
 
}(window));
View Code

 



Listing 3: Full source for user interface-specific code (index.ui.js)
          JavaScript   技术分享
// index.ui.js
 
;
 
(function ($, Modernizr, app) {
 
    ‘use strict‘;
 
    $(function(){
 
        if(!Modernizr.indexeddb){
            $(‘#unsupported-message‘).show();
            $(‘#ui-container‘).hide();
            return;
        }
 
        var
          $deleteAllBtn = $(‘#delete-all-btn‘),
          $titleText = $(‘#title-text‘),
          $notesText = $(‘#notes-text‘),
          $idHidden = $(‘#id-hidden‘),
          $clearButton = $(‘#clear-button‘),
          $saveButton = $(‘#save-button‘),
          $listContainer = $(‘#list-container‘),
          $noteTemplate = $(‘#note-template‘),
          $emptyNote = $(‘#empty-note‘);
 
        var addNoTasksMessage = function(){
            $listContainer.append(
                $emptyNote.html());
        };
 
        var bindData = function (data) {
 
            $listContainer.html(‘‘);
 
            if(data.length === 0){
                addNoTasksMessage();
                return;
            }
 
            data.forEach(function (note) {
              var m = $noteTemplate.html();
              m = m.replace(/{ID}/g, note.id);
              m = m.replace(/{TITLE}/g, note.title);
              $listContainer.append(m);
            });
        };
 
        var clearUI = function(){
            $titleText.val(‘‘).focus();
            $notesText.val(‘‘);
            $idHidden.val(‘‘);
        };
 
        // select individual item
        $listContainer.on(‘click‘, ‘a[data-id]‘,
 
            function (e) {
 
                var id, current;
 
                e.preventDefault();
 
                current = e.currentTarget;
                id = $(current).attr(‘data-id‘);
 
                app.db.get(id, function (note) {
                    $titleText.val(note.title);
                    $notesText.val(note.text);
                    $idHidden.val(note.id);
                });
 
                return false;
            });
 
        // delete item
        $listContainer.on(‘click‘, ‘i[data-id]‘,
 
            function (e) {
 
                var id, current;
 
                e.preventDefault();
 
                current = e.currentTarget;
                id = $(current).attr(‘data-id‘);
 
                app.db.delete(id, function(){
                    app.db.getAll(bindData);
                    clearUI();
                });
 
                return false;
        });
 
        $clearButton.click(function(e){
            e.preventDefault();
            clearUI();
            return false;
        });
 
        $saveButton.click(function (e) {
 
            var title = $titleText.val();
 
            if (title.length === 0) {
                return;
            }
 
            var note = {
                title: title,
                text: $notesText.val()
            };
 
            var id = $idHidden.val();
 
            if(id !== ‘‘){
                note.id = parseInt(id);
            }
 
            app.db.save(note, function(){
                app.db.getAll(bindData);
                clearUI();
            });
        });
 
        $deleteAllBtn.click(function (e) {
 
            e.preventDefault();
 
            app.db.deleteAll(function () {
                $listContainer.html(‘‘);
                addNoTasksMessage();
                clearUI();
            });
 
            return false;
        });
 
        app.db.errorHandler = function (e) {
            window.alert(‘error: ‘ + e.target.code);
            debugger;
        };
 
        app.db.getAll(bindData);
 
    });
 
}(jQuery, Modernizr, window.app));
View Code

 



Listing 3: Full HTML source (index.html)
            JavaScript   技术分享
<!doctype html>
<html lang="en-US">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Introduction to IndexedDB</title>
        <meta name="description"
              content="Introduction to IndexedDB">
        <meta name="viewport"
              content="width=device-width, initial-scale=1">
        <link rel="stylesheet"
              href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/css/font-awesome.min.css" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/FontAwesome.otf" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.eot" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.svg" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.ttf" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.woff" >
        <style>
            h1 {
                text-align: center;
                color:#999;
            }
 
            ul li {
                font-size: 1.35em;
                margin-top: 1em;
                margin-bottom: 1em;
            }
 
            ul li.small {
                font-style: italic;
            }
 
            footer {
                margin-top: 25px;
                border-top: 1px solid #eee;
                padding-top: 25px;
            }
 
            i[data-id] {
                cursor: pointer;
                color: #eee;
            }
 
            i[data-id]:hover {
                color: #c75a6d;
            }
 
            .push-down {
                margin-top: 25px;
            }
 
            #save-button {
                margin-left: 10px;
            }
        </style>
        <script src="//cdnjs.cloudflare.com/ajax/libs/modernizr
/2.8.2/modernizr.min.js" ></script>
    </head>
    <body class="container">
        <h1>Tasks</h1>
        <div id="unsupported-message"
             class="alert alert-warning"
             style="display:none;">
            <b>Aww snap!</b> Your browser does not support indexedDB.
        </div>
        <div id="ui-container" class="row">
            <div class="col-sm-3">
 
                <a href="#" id="delete-all-btn" class="btn-xs">
                    <i class="fa fa-trash-o"></i> Delete All</a>
 
                <hr/>
 
                <ul id="list-container" class="list-unstyled"></ul>
 
            </div>
            <div class="col-sm-8 push-down">
 
                <input type="hidden" id="id-hidden" />
 
                <input
                       id="title-text"
                       type="text"
                       class="form-control"
                       tabindex="1"
                       placeholder="title"
                       autofocus /><br />
 
                <textarea
                          id="notes-text"
                          class="form-control"
                          tabindex="2"
                          placeholder="text"></textarea>
 
                <div class="pull-right push-down">
 
                    <a href="#" id="clear-button" tabindex="4">Clear</a>
 
                    <button id="save-button"
                            tabindex="3"
                            class="btn btn-default btn-primary">
                                <i class="fa fa-save"></i> Save</button>
                </div>
            </div>
        </div>
        <footer class="small text-muted text-center">by
            <a href="http://craigshoemaker.net" target="_blank">Craig Shoemaker</a>
            <a href="http://twitter.com/craigshoemaker" target="_blank">
                <i class="fa fa-twitter"></i></a>
        </footer>
        <script id="note-template" type="text/template">
            <li>
                <i data-id="{ID}" class="fa fa-minus-circle"></i>
                <a href="#" data-id="{ID}">{TITLE}</a>
            </li>
        </script>
        <script id="empty-note" type="text/template">
            <li class="text-muted small">No tasks</li>
        </script>
        <script src="//ajax.googleapis.com/ajax/libs
/jquery/1.11.1/jquery.min.js"></script>
        <script src="index.db.js" type="text/javascript"></script>
        <script src="index.ui.js" type="text/javascript"></script>
    </body>
</html>
View Code

 


<!doctype html> <html lang="en-US">     <head>         <meta charset="utf-8">         <meta http-equiv="X-UA-Compatible" content="IE=edge">         <title>Introduction to IndexedDB</title>         <meta name="description"               content="Introduction to IndexedDB">         <meta name="viewport"               content="width=device-width, initial-scale=1">         <link rel="stylesheet"               href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">         <link rel="stylesheet"               href="//cdnjs.cloudflare.com/ajax/libs /font-awesome/4.1.0/css/font-awesome.min.css" >         <link rel="stylesheet"               href="//cdnjs.cloudflare.com/ajax/libs /font-awesome/4.1.0/fonts/FontAwesome.otf" >         <link rel="stylesheet"               href="//cdnjs.cloudflare.com/ajax/libs /font-awesome/4.1.0/fonts/fontawesome-webfont.eot" >         <link rel="stylesheet"               href="//cdnjs.cloudflare.com/ajax/libs /font-awesome/4.1.0/fonts/fontawesome-webfont.svg" >         <link rel="stylesheet"               href="//cdnjs.cloudflare.com/ajax/libs /font-awesome/4.1.0/fonts/fontawesome-webfont.ttf" >         <link rel="stylesheet"               href="//cdnjs.cloudflare.com/ajax/libs /font-awesome/4.1.0/fonts/fontawesome-webfont.woff" >         <style>             h1 {                 text-align: center;                 color:#999;             }               ul li {                 font-size: 1.35em;                 margin-top: 1em;                 margin-bottom: 1em;             }               ul li.small {                 font-style: italic;             }               footer {                 margin-top: 25px;                 border-top: 1px solid #eee;                 padding-top: 25px;             }               i[data-id] {                 cursor: pointer;                 color: #eee;             }               i[data-id]:hover {                 color: #c75a6d;             }               .push-down {                 margin-top: 25px;             }               #save-button {                 margin-left: 10px;             }         </style>         &

人气教程排行