Rumah  >  Artikel  >  hujung hadapan web  >  Backbone.js menguasakan aplikasi ToDo satu halaman

Backbone.js menguasakan aplikasi ToDo satu halaman

王林
王林asal
2023-08-29 15:17:081057semak imbas

Backbone.js 为单页 ToDo 应用程序提供支持

Backbone.js ialah rangka kerja JavaScript untuk membina aplikasi web yang fleksibel. Ia disertakan dengan model, koleksi, paparan, acara, penghala dan ciri hebat lain. Dalam artikel ini, kami akan membangunkan aplikasi ToDo ringkas yang menyokong menambah, mengedit dan memadam tugasan. Kita juga harus dapat menandai tugas sebagai "selesai" dan mengarkibkannya. Untuk memastikan artikel ini pada panjang yang munasabah, kami tidak akan memasukkan sebarang komunikasi dengan pangkalan data. Semua data akan disimpan di bahagian pelanggan.

Tetapan

Ini adalah struktur fail yang akan kami gunakan:

css
    └── styles.css
js
    └── collections
        └── ToDos.js
    └── models
        └── ToDo.js
    └── vendor
       └── backbone.js
       └── jquery-1.10.2.min.js
       └── underscore.js
    └── views
    └── App.js
 └── index.html

Sesetengah perkara adalah jelas, seperti /css/styles.css dan /index.html. Ia mengandungi gaya CSS dan penanda HTML. Dalam konteks Backbone.js, model ialah tempat kami menyimpan data kami. Oleh itu, tunggakan kami hanya akan menjadi model. Memandangkan kami akan mempunyai pelbagai tugas, kami akan menyusunnya ke dalam koleksi. Logik perniagaan diedarkan antara paparan dan fail aplikasi utama App.js. Backbone.js hanya mempunyai satu pergantungan keras - Underscore.js. Rangka kerja ini juga berfungsi dengan baik dengan jQuery, jadi kedua-duanya pergi ke direktori vendor. Apa yang kami perlukan sekarang ialah sedikit penanda HTML dan kami bersedia untuk pergi. /css/styles.css/index.html。它们包含 CSS 样式和 HTML 标记。在 Backbone.js 的上下文中,模型是我们保存数据的地方。因此,我们的待办事项将只是模型。因为我们将有多个任务,所以我们会将它们组织成一个集合。业务逻辑分布在视图和主应用程序文件 App.js 之间。 Backbone.js 只有一个硬依赖项 - Underscore.js。该框架也与 jQuery 配合得很好,因此它们都转到 vendor 目录。我们现在只需要一点 HTML 标记,就可以开始了。

<!doctype html>
<html>
  <head>
        <title>My TODOs</title>
        <link rel="stylesheet" type="text/css" href="css/styles.css" />
    </head>
    <body>
        <div class="container">
            <div id="menu" class="menu cf"></div>
            <h1></h1>
            <div id="content"></div>
        </div>
        <script src="js/vendor/jquery-1.10.2.min.js"></script>
        <script src="js/vendor/underscore.js"></script>
        <script src="js/vendor/backbone.js"></script>
        <script src="js/App.js"></script>
        <script src="js/models/ToDo.js"></script>
        <script src="js/collections/ToDos.js"></script>
        <script>
            window.onload = function() {
                // bootstrap
            }
        </script>
    </body>
</html>

正如您所看到的,我们将所有外部 JavaScript 文件都包含在底部,因为在 body 标记的末尾执行此操作是一个很好的做法。我们还正在准备应用程序的引导。有内容容器、菜单和标题。主导航是静态元素,我们不会更改它。我们将标题的内容和下面的div替换掉。

规划应用程序

在我们开始做某事之前制定一个计划总是好的。 Backbone.js 没有非常严格的架构,我们必须遵循它。这是该框架的好处之一。所以,在开始实现业务逻辑之前,我们先来谈谈基础。

命名空间

一个好的做法是将代码放入其自己的范围内。注册全局变量或函数不是一个好主意。我们将创建的是一个模型、一个集合、一个路由器和几个 Backbone.js 视图。所有这些元素都应该存在于私人空间中。 App.js 将包含包含所有内容的类。

// App.js
var app = (function() {

    var api = {
        views: {},
        models: {},
        collections: {},
        content: null,
        router: null,
        todos: null,
        init: function() {
            this.content = $("#content");
        },
        changeContent: function(el) {
            this.content.empty().append(el);
            return this;
        },
        title: function(str) {
            $("h1").text(str);
            return this;
        }
    };
    var ViewsFactory = {};
    var Router = Backbone.Router.extend({});
    api.router = new Router();

    return api;

})();

以上是揭示模块模式的典型实现。 api 变量是返回的对象,代表类的公共方法。 viewsmodelscollections 属性将充当 Backbone.js 返回的类的持有者。 content 是一个指向主用户界面容器的 jQuery 元素。这里有两个辅助方法。第一个更新该容器。第二个设置页面的标题。然后我们定义了一个名为 ViewsFactory 的模块。它将传递我们的视图,最后,我们创建了路由器。

你可能会问,为什么我们需要一个工厂来存储视图?嗯,使用 Backbone.js 时有一些常见的模式。其中之一与视图的创建和使用有关。

var ViewClass = Backbone.View.extend({ /* logic here */ });
var view = new ViewClass();

最好只初始化视图一次并让它们保持活动状态。一旦数据发生更改,我们通常会调用视图的方法并更新其 el 对象的内容。另一种非常流行的方法是重新创建整个视图或替换整个 DOM 元素。然而,从性能的角度来看,这并不是很好。因此,我们通常会得到一个实用程序类,它创建视图的一个实例并在需要时返回它。

组件定义

我们有了一个命名空间,所以现在我们可以开始创建组件了。主菜单如下所示:

// views/menu.js
app.views.menu = Backbone.View.extend({
    initialize: function() {},
    render: function() {}
});

我们创建了一个名为 menu 的属性,它保存导航的类。稍后,我们可以在工厂模块中添加一个方法来创建它的实例。

var ViewsFactory = {
    menu: function() {
        if(!this.menuView) {
            this.menuView = new api.views.menu({ 
                el: $("#menu")
            });
        }
        return this.menuView;
    }
};

上面是我们处理所有视图的方式,它将确保我们只获得同一个实例的一个。在大多数情况下,这种技术效果很好。

流程

应用程序的入口点是 App.js 及其 init 方法。这就是我们将在 window 对象的 onload

window.onload = function() {
    app.init();
}

Seperti yang anda lihat, kami telah menyertakan semua fail JavaScript luaran di bahagian bawah kerana ini adalah amalan yang baik untuk melakukan ini di penghujung teg badan. Kami juga sedang menyediakan aplikasi untuk but. Terdapat bekas kandungan, menu dan pengepala. Navigasi utama ialah elemen statik dan kami tidak akan mengubahnya. Kami menggantikan kandungan tajuk dan div di bawah.

Apl Perancangan

Adalah baik untuk mempunyai rancangan sebelum kita mula melakukan sesuatu. Backbone.js tidak mempunyai seni bina yang sangat ketat yang mesti kita ikuti. Ini adalah salah satu faedah rangka kerja ini. Jadi, sebelum kita mula melaksanakan logik perniagaan, mari kita bercakap tentang asasnya.

Ruang nama

Amalan yang baik ialah meletakkan kod ke dalam skopnya sendiri. Mendaftarkan pembolehubah atau fungsi global bukanlah idea yang baik. Perkara yang akan kami cipta ialah model, koleksi, penghala dan beberapa paparan Backbone.js. Semua elemen ini harus ada dalam ruang peribadi. App.js akan mengandungi kelas yang mengandungi segala-galanya.

// models/ToDo.js
app.models.ToDo = Backbone.Model.extend({
    defaults: {
        title: "ToDo",
        archived: false,
        done: false
    }
});

Di atas adalah pelaksanaan tipikal corak modul yang didedahkan. Pembolehubah api ialah objek dikembalikan yang mewakili kaedah awam kelas. Sifat views, models dan collections akan bertindak sebagai pemegang untuk kelas yang dikembalikan oleh Backbone.js. kandungan ialah elemen jQuery yang menunjuk ke bekas antara muka pengguna utama. Terdapat dua kaedah penolong di sini. Jadilah orang pertama mengemas kini bekas. Yang kedua menetapkan tajuk halaman. Kemudian kami mentakrifkan modul yang dipanggil ViewsFactory. Ia akan melepasi pandangan kami dan akhirnya, kami mencipta penghala. 🎜 🎜Anda mungkin bertanya, mengapa kita memerlukan kilang untuk menyimpan pandangan? Nah, terdapat beberapa corak biasa apabila menggunakan Backbone.js. Salah satunya ada kaitan dengan penciptaan dan penggunaan pandangan. 🎜
// collections/ToDos.js
app.collections.ToDos = Backbone.Collection.extend({
    initialize: function(){
        this.add({ title: "Learn JavaScript basics" });
        this.add({ title: "Go to backbonejs.org" });
        this.add({ title: "Develop a Backbone application" });
    },
    model: app.models.ToDo
    up: function(index) {
        if(index > 0) {
            var tmp = this.models[index-1];
            this.models[index-1] = this.models[index];
            this.models[index] = tmp;
            this.trigger("change");
        }
    },
    down: function(index) {
        if(index < this.models.length-1) {
            var tmp = this.models[index+1];
            this.models[index+1] = this.models[index];
            this.models[index] = tmp;
            this.trigger("change");
        }
    },
    archive: function(archived, index) {
        this.models[index].set("archived", archived);
    },
    changeStatus: function(done, index) {
        this.models[index].set("done", done);
    }
});
🎜Adalah lebih baik untuk memulakan paparan sekali sahaja dan memastikannya tetap aktif. Setelah data berubah, kami biasanya memanggil kaedah paparan dan mengemas kini kandungan objek elnya. Satu lagi pendekatan yang sangat popular ialah mencipta semula keseluruhan paparan atau menggantikan keseluruhan elemen DOM. Walau bagaimanapun, dari perspektif prestasi, ini tidak bagus. Oleh itu, kita biasanya mendapat kelas utiliti yang mencipta contoh pandangan dan mengembalikannya apabila diperlukan. 🎜

Takrifan komponen

🎜Kami mempunyai ruang nama, jadi sekarang kami boleh mula mencipta komponen. Menu utama kelihatan seperti ini: 🎜
// App.js
init: function() {
    this.content = $("#content");
    this.todos = new api.collections.ToDos();
    return this;
}
🎜Kami mencipta atribut yang dipanggil menu yang memegang kelas navigasi. Kemudian, kita boleh menambah kaedah dalam modul kilang untuk mencipta contoh daripadanya. 🎜
// views/menu.js
app.views.menu = Backbone.View.extend({
    template: _.template($("#tpl-menu").html()),
    initialize: function() {
        this.render();
    },
    render: function(){
        this.$el.html(this.template({}));
    }
});
🎜Di atas ialah cara kami mengendalikan semua pandangan, ia akan memastikan kami hanya mendapat satu daripada contoh yang sama. Dalam kebanyakan kes, teknik ini berfungsi dengan baik. 🎜

Proses

🎜Titik masuk aplikasi ialah App.js dan kaedah initnya. Inilah yang akan kami panggil dalam pengendali onload objek window. 🎜
_.template(templateString, [data], [settings])
🎜Selepas itu, penghala yang ditentukan akan mendapat kawalan. Berdasarkan URL, ia menentukan pengendali yang hendak dilaksanakan. Dalam Backbone.js, kami tidak mempunyai seni bina model-view-controller yang biasa. Pengawal hilang dan kebanyakan logik dimasukkan ke dalam paparan. Oleh itu, kami menyambungkan model terus kepada kaedah dalam paparan dan mengemas kini antara muka pengguna sebaik sahaja data berubah. 🎜 🎜Urus data🎜 🎜Perkara yang paling penting dalam projek kecil kami ialah data. Tugas kita adalah apa yang kita harus uruskan, jadi mari kita mulakan di sana. Ini adalah definisi model kami. 🎜
// index.html
<script type="text/template" id="tpl-menu">
    <ul>
        <li><a href="#">List</a></li>
        <li><a href="#archive">Archive</a></li>
        <li class="right"><a href="#new">+</a></li>
    </ul>
</script>
🎜Hanya tiga bidang. Yang pertama mengandungi teks tugasan dan dua lagi ialah bendera yang mentakrifkan status rekod. 🎜

框架内的所有东西实际上都是一个事件调度程序。由于模型是通过设置器更改的,因此框架知道数据何时更新,并可以通知系统的其余部分。一旦您将某些内容绑定到这些通知,您的应用程序就会对模型中的更改做出反应。这是 Backbone.js 中一个非常强大的功能。

正如我一开始所说的,我们将有很多记录,我们将它们组织到一个名为 ToDos 的集合中。

// collections/ToDos.js
app.collections.ToDos = Backbone.Collection.extend({
    initialize: function(){
        this.add({ title: "Learn JavaScript basics" });
        this.add({ title: "Go to backbonejs.org" });
        this.add({ title: "Develop a Backbone application" });
    },
    model: app.models.ToDo
    up: function(index) {
        if(index > 0) {
            var tmp = this.models[index-1];
            this.models[index-1] = this.models[index];
            this.models[index] = tmp;
            this.trigger("change");
        }
    },
    down: function(index) {
        if(index < this.models.length-1) {
            var tmp = this.models[index+1];
            this.models[index+1] = this.models[index];
            this.models[index] = tmp;
            this.trigger("change");
        }
    },
    archive: function(archived, index) {
        this.models[index].set("archived", archived);
    },
    changeStatus: function(done, index) {
        this.models[index].set("done", done);
    }
});

initialize 方法是集合的入口点。在我们的例子中,我们默认添加了一些任务。当然,在现实世界中,信息将来自数据库或其他地方。但为了让您集中注意力,我们将手动执行此操作。集合的另一件事是设置 model 属性。它告诉类正在存储什么类型的数据。其余方法实现与我们应用程序中的功能相关的自定义逻辑。 updown 函数更改 ToDos 的顺序。为了简化事情,我们将仅使用集合数组中的索引来标识每个 ToDo。这意味着如果我们想要获取一条特定记录,我们应该指向它的索引。所以,排序只是交换数组中的元素。正如您可能从上面的代码中猜到的那样, this.models 是我们正在讨论的数组。 archivechangeStatus 设置给定元素的属性。我们将这些方法放在这里,因为视图将有权访问 ToDos 集合,而不是直接访问任务。

此外,我们不需要从 app.models.ToDo 类创建任何模型,但我们需要从 app.collections.ToDos 集合创建一个实例。

// App.js
init: function() {
    this.content = $("#content");
    this.todos = new api.collections.ToDos();
    return this;
}

显示我们的第一个视图(主导航)

我们必须展示的第一件事是主应用程序的导航。

// views/menu.js
app.views.menu = Backbone.View.extend({
    template: _.template($("#tpl-menu").html()),
    initialize: function() {
        this.render();
    },
    render: function(){
        this.$el.html(this.template({}));
    }
});

虽然只有九行代码,但这里发生了很多很酷的事情。第一个是设置模板。如果您还记得,我们将 Underscore.js 添加到了我们的应用程序中?我们将使用它的模板引擎,因为它运行良好并且使用起来很简单。

_.template(templateString, [data], [settings])

最后有一个函数,它接受一个以键值对形式保存信息的对象,而 templateString 是 HTML 标记。好的,它接受一个 HTML 字符串,但是 $("#tpl-menu").html() 在那里做什么?当我们开发小型单页面应用程序时,我们通常将模板直接放入页面中,如下所示:

// index.html
<script type="text/template" id="tpl-menu">
    <ul>
        <li><a href="#">List</a></li>
        <li><a href="#archive">Archive</a></li>
        <li class="right"><a href="#new">+</a></li>
    </ul>
</script>

由于它是一个脚本标记,因此不会向用户显示。从另一个角度来看,它是一个有效的 DOM 节点,因此我们可以使用 jQuery 获取其内容。因此,上面的简短片段仅获取该脚本标记的内容。

render 方法在 Backbone.js 中非常重要。这就是显示数据的函数。通常,您将模型触发的事件直接绑定到该方法。然而,对于主菜单,我们不需要这样的行为。

this.$el.html(this.template({}));

this.$el 是框架创建的一个对象,每个视图默认都有它(el 前面有一个 $ 因为我们包含了 jQuery)。默认情况下,它是一个空的 。当然,您可以使用 tagName 属性来更改它。但这里更重要的是,我们没有直接为该对象赋值。我们不会改变它,我们只是改变它的内容。上面一行和下一行有很大的区别:

this.$el = $(this.template({}));

重点是,如果你想在浏览器中看到变化,你应该先调用 render 方法,将视图附加到 DOM 中。否则只会附加空的 div。还有另一种情况,您有嵌套视图。而且由于您直接更改属性,因此父组件不会更新。绑定的事件也可能被破坏,您需要重新附加侦听器。因此,您实际上应该只更改 this.$el 的内容,而不是属性的值。

视图现已准备就绪,我们需要初始化它。让我们将其添加到我们的工厂模块中:

// App.js
var ViewsFactory = {
    menu: function() {
        if(!this.menuView) {
            this.menuView = new api.views.menu({ 
                el: $("#menu")
            });
        }
        return this.menuView;
    }
};

最后只需调用引导区域中的 menu 方法即可:

// App.js
init: function() {
    this.content = $("#content");
    this.todos = new api.collections.ToDos();
    ViewsFactory.menu();
    return this;
}

请注意,当我们从导航类创建新实例时,我们传递了一个已经存在的 DOM 元素 $("#menu")。所以,视图中的 this.$el 属性实际上指向 $("#menu")

添加路线

Backbone.js 支持推送状态操作。换句话说,您可以操纵当前浏览器的 URL 并在页面之间移动。但是,我们将坚持使用旧的哈希类型 URL,例如 /#edit/3

// App.js
var Router = Backbone.Router.extend({
    routes: {
        "archive": "archive",
        "new": "newToDo",
        "edit/:index": "editToDo",
        "delete/:index": "delteToDo",
        "": "list"
    },
    list: function(archive) {},
    archive: function() {},
    newToDo: function() {},
    editToDo: function(index) {},
    delteToDo: function(index) {}
});

上面是我们的路由器。哈希对象中定义了五个路由。键是您将在浏览器地址栏中键入的内容,值是将要调用的函数。请注意,其中两条路由上有 :index。如果您想支持动态 URL,则需要使用该语法。在我们的例子中,如果您输入 #edit/3 ,则 editToDo 将使用参数 index=3 执行。最后一行包含一个空字符串,这意味着它处理我们应用程序的主页。

显示所有任务的列表

到目前为止,我们构建的是我们项目的主视图。它将从集合中检索数据并将其打印在屏幕上。我们可以将相同的视图用于两件事 - 显示所有活动的待办事项和显示已存档的待办事项。

在继续列表视图实现之前,让我们看看它是如何实际初始化的。

// in App.js views factory
list: function() {
    if(!this.listView) {
        this.listView = new api.views.list({
            model: api.todos
        });
    }   
    return this.listView;
}

请注意,我们正在传递集合。这很重要,因为我们稍后将使用 this.model 来访问存储的数据。工厂返回我们的列表视图,但路由器是必须将其添加到页面的人。

// in App.js's router
list: function(archive) {
    var view = ViewsFactory.list();
    api
    .title(archive ? "Archive:" : "Your ToDos:")
    .changeContent(view.$el);
    view.setMode(archive ? "archive" : null).render();
}

目前,在路由器中调用方法 list ,不带任何参数。因此该视图不是 archive 模式,它只会显示活动的 ToDos。

// views/list.js
app.views.list = Backbone.View.extend({
    mode: null,
    events: {},
    initialize: function() {
        var handler = _.bind(this.render, this);
        this.model.bind('change', handler);
        this.model.bind('add', handler);
        this.model.bind('remove', handler);
    },
    render: function() {},
    priorityUp: function(e) {},
    priorityDown: function(e) {},
    archive: function(e) {},
    changeStatus: function(e) {},
    setMode: function(mode) {
        this.mode = mode;
        return this;
    }
});

渲染期间将使用 mode 属性。如果其值为 mode="archive" 则仅显示已存档的 ToDos。 events 是一个我们将立即填充的对象。这是我们放置 DOM 事件映射的地方。其余方法是用户交互的响应,它们直接链接到所需的功能。例如, priorityUppriorityDown 更改待办事项的顺序。 archive 将项目移动到存档区域。 changeStatus 只是将 ToDo 标记为已完成。

initialize 方法内部发生的事情很有趣。前面我们说过,通常您会将模型(在我们的例子中为集合)中的更改绑定到视图的 render 方法。您可以输入 this.model.bind('change', this.render)。但很快您就会注意到 this 关键字在 render 方法中不会指向视图本身。这是因为范围发生了变化。作为解决方法,我们正在创建一个具有已定义范围的处理程序。这就是 Underscore 的 bind 函数的用途。

这里是 render 方法的实现。

// views/list.js
render: function() {)
    var html = '<ul class="list">', 
        self = this;
    this.model.each(function(todo, index) {
        if(self.mode === "archive" ? todo.get("archived") === true : todo.get("archived") === false) {
            var template = _.template($("#tpl-list-item").html());
            html += template({ 
                title: todo.get("title"),
                index: index,
                archiveLink: self.mode === "archive" ? "unarchive" : "archive",
                done: todo.get("done") ? "yes" : "no",
                doneChecked: todo.get("done")  ? 'checked=="checked"' : ""
            });
        }
    });
    html += '</ul>';
    this.$el.html(html);
    this.delegateEvents();
    return this;
}

我们循环遍历集合中的所有模型并生成一个 HTML 字符串,稍后将其插入到视图的 DOM 元素中。很少有检查可以区分待办事项从已存档到活动。在复选框的帮助下,任务被标记为完成。因此,为了表明这一点,我们需要将 checked=="checked" 属性传递给该元素。您可能会注意到我们正在使用 this.delegateEvents()。在我们的例子中这是必要的,因为我们正在从 DOM 中分离和附加视图。是的,我们不会替换主元素,但事件的处理程序将被删除。这就是为什么我们必须告诉 Backbone.js 再次附加它们。上面代码中使用的模板是:

// index.html
<script type="text/template" id="tpl-list-item">
    <li class="cf done-<%= done %>" data-index="<%= index %>">
        <h2>
            <input type="checkbox" data-status <%= doneChecked %> />
            <a href="javascript:void(0);" data-up>&#8593;</a>
            <a href="javascript:void(0);" data-down>&#8595;</a>
            <%= title %>
        </h2>
        <div class="options">
            <a href="#edit/<%= index %>">edit</a>
            <a href="javascript:void(0);" data-archive><%= archiveLink %></a>
            <a href="#delete/<%= index %>">delete</a>
        </div>
    </li>
</script>

请注意,定义了一个名为 done-yes 的 CSS 类,它将 ToDo 绘制为绿色背景。除此之外,还有很多链接,我们将使用它们来实现所需的功能。它们都具有数据属性。元素的主节点li,有data-index。该属性的值显示任务在集合中的索引。请注意,包裹在 中的特殊表达式被发送到 template 函数。这就是注入到模板中的数据。

是时候向视图添加一些事件了。

// views/list.js
events: {
    'click a[data-up]': 'priorityUp',
    'click a[data-down]': 'priorityDown',
    'click a[data-archive]': 'archive',
    'click input[data-status]': 'changeStatus'
}

在 Backbone.js 中,事件的定义只是一个哈希值。您首先输入事件的名称,然后输入选择器。属性的值实际上是视图的方法。

// views/list.js
priorityUp: function(e) {
    var index = parseInt(e.target.parentNode.parentNode.getAttribute("data-index"));
    this.model.up(index);
},
priorityDown: function(e) {
    var index = parseInt(e.target.parentNode.parentNode.getAttribute("data-index"));
    this.model.down(index);
},
archive: function(e) {
    var index = parseInt(e.target.parentNode.parentNode.getAttribute("data-index"));
    this.model.archive(this.mode !== "archive", index); 
},
changeStatus: function(e) {
    var index = parseInt(e.target.parentNode.parentNode.getAttribute("data-index"));
    this.model.changeStatus(e.target.checked, index);       
}

这里我们使用 e.target 进入处理程序。它指向触发事件的 DOM 元素。我们正在获取单击的 ToDo 的索引并更新集合中的模型。通过这四个函数,我们完成了我们的课程,现在数据显示在页面上。

正如我们上面提到的,我们将为 Archive 页面使用相同的视图。

list: function(archive) {
    var view = ViewsFactory.list();
    api
    .title(archive ? "Archive:" : "Your ToDos:")
    .changeContent(view.$el);
    view.setMode(archive ? "archive" : null).render();
},
archive: function() {
    this.list(true);
}

上面是与之前相同的路由处理程序,但这次使用 true 作为参数。

添加和编辑待办事项

按照列表视图的入门,我们可以创建另一个显示用于添加和编辑任务的表单的列表视图。下面是这个新类的创建方式:

// App.js / views factory
form: function() {
    if(!this.formView) {
        this.formView = new api.views.form({
            model: api.todos
        }).on("saved", function() {
            api.router.navigate("", {trigger: true});
        })
    }
    return this.formView;
}

几乎一样。然而,这次表单提交后我们需要做一些事情。这会将用户转发到主页。正如我所说,每个扩展 Backbone.js 类的对象实际上都是一个事件调度程序。您可以使用 ontrigger 等方法。

在继续查看代码之前,让我们看一下 HTML 模板:

<script type="text/template" id="tpl-form">
    <form>
        <textarea><%= title %></textarea>
        <button>save</button>
    </form>
</script>

我们有一个 textarea 和一个 button。如果我们要添加新任务,该模板需要一个 title 参数,该参数应该是一个空字符串。

// views/form.js
app.views.form = Backbone.View.extend({
    index: false,
    events: {
        'click button': 'save'
    },
    initialize: function() {
        this.render();
    },
    render: function(index) {
        var template, html = $("#tpl-form").html();
        if(typeof index == 'undefined') {
            this.index = false;
            template = _.template(html, { title: ""});
        } else {
            this.index = parseInt(index);
            this.todoForEditing = this.model.at(this.index);
            template = _.template($("#tpl-form").html(), {
                title: this.todoForEditing.get("title")
            });
        }
        this.$el.html(template);
        this.$el.find("textarea").focus();
        this.delegateEvents();
        return this;
    },
    save: function(e) {
        e.preventDefault();
        var title = this.$el.find("textarea").val();
        if(title == "") {
            alert("Empty textarea!"); return;
        }
        if(this.index !== false) {
            this.todoForEditing.set("title", title);
        } else {
            this.model.add({ title: title });
        }   
        this.trigger("saved");      
    }
});

该视图只有 40 行代码,但它的工作效果很好。仅附加一个事件,即单击“保存”按钮。根据传递的 index 参数,渲染方法的行为有所不同。例如,如果我们正在编辑 ToDo,我们会传递索引并获取确切的模型。如果没有,则表单为空,并且将创建一个新任务。上面的代码中有几个有趣的点。首先,在渲染中,我们使用 .focus() 方法在渲染视图后将焦点带到表单上。应再次调用 delegateEvents 函数,因为表单可以分离并再次附加。 save 方法以 e.preventDefault() 开头。这会删除按钮的默认行为,在某些情况下可能会提交表单。最后,一旦一切完成,我们就会触发 saved 事件,通知外界 ToDo 已保存到集合中。

路由器有两种方法需要填写。

// App.js
newToDo: function() {
    var view = ViewsFactory.form();
    api.title("Create new ToDo:").changeContent(view.$el);
    view.render()
},
editToDo: function(index) {
    var view = ViewsFactory.form();
    api.title("Edit:").changeContent(view.$el);
    view.render(index);
}

它们之间的区别在于,我们传入一个索引,如果 edit/:index 路由匹配。当然,页面标题也会相应更改。

从集合中删除记录

对于此功能,我们不需要视图。整个工作可以直接在路由器的处理程序中完成。

delteToDo: function(index) {
    api.todos.remove(api.todos.at(parseInt(index)));
    api.router.navigate("", {trigger: true});
}

我们知道要删除的 ToDo 的索引。集合类中有一个 remove 方法,它接受模型对象。最后,只需将用户转发到主页,其中就会显示更新后的列表。

结论

Backbone.js 拥有构建功能齐全的单页应用程序所需的一切。我们甚至可以将其绑定到 REST 后端服务,框架将同步您的应用程序和数据库之间的数据。事件驱动方法鼓励模块化编程以及良好的架构。我个人在多个项目中使用 Backbone.js,并且效果非常好。

Atas ialah kandungan terperinci Backbone.js menguasakan aplikasi ToDo satu halaman. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn