亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

SingalPageApp:使用Knockout和RequireJS創建高度模塊化的單頁應用引擎

2019-11-14 19:57:14
字體:
來源:轉載
供稿:網友


開篇扯淡

距離上一篇文章已經有好幾個月,也不是沒有時間記錄點東西,主要是換了新的工作,在一家外資工作,目前的工作內容大多都是前端開發,新接觸的東西因為時間原因,大多還不成體系,所以這么長時間什么都沒記錄下來,也正是因為新的工作內容,才有了今天這篇文章。

這篇文章是我自己的博客項目的前端重寫,因為目前asp.net API和單頁應用的流行,結合目前工作中用到的東西,我決定把我的博客項目的前端部分整個重寫,(以前的就是一坨…)

步入正題

背景知識

RequireJS http://www.requirejs.org/

Knockout http://knockoutjs.com/

BootStrap http://getbootstrap.com/

PubSubJS https://github.com/mroderick/PubSubJS

如果沒有接觸過的朋友請自行谷歌百度吧,就不浪費口水,額,鍵盤啦,什么,沒有jQuery,呵呵,呵呵,正如Knockout官方文檔里說的,Everyoue loves jquery。

RequireJS我用來做模塊加載器,Knockout做MVVM分離也是爽到沒朋友(誰用誰知道),Bootstrap搭建界面布局,PubSub,看著名字就知道啦。

文檔結構

QQ截圖20141021211500

Libs:放置上文中提到的各種框架和工具;

App:主要的工作目錄,articleList、catalog、articleViewer分別代表整個前端應用中的一個組件,對應的.html文件是他們自身的視圖模板;

Utilities:存放一些工具類,如檢測設備、格式化Url和字符串等;

Layout:只有一個文件,存放了整個前端應用的模板,可以通過更改這個文件,來改變各個組件的表現形式。

服務端API準備

為這個示例,我只準備了三個服務端API:

GetCatalog 得到文章類型目錄:

namespace MiaoBlog.Controllers.API
{
    public class CatalogController:ApiController
    {
        public IEnumerable<CategoryView> Get()
        {
            GetAllCategoriesResponse response = articleCatalogService.GetAllCategories();
            return response.Categories;
        }
    }
}

GetArticlesByCategory 根據類型ID和頁面編號獲取文章目錄,

GetArticle 根據文章ID獲取文章內容等信息:

 

namespace MiaoBlog.Controllers.API
{
    public class ArticlesController:ApiController
    {
        public IEnumerable<ArticleSummaryView> GetArticlesByCategory(int categoryId, int pageNumber)
        {
            GetArticlesByCategoryRequest request = GenerateArticlesByCategoryRequestFrom(categoryId, pageNumber-1);
            GetArticlesByCategoryResponse response = articleCatalogService.GetArticlesByCategory(request);
            return response.Articles;
        }

        public ArticleDetailPageView GetArticle(int id)
        {
            ArticleDetailPageView detailView = new ArticleDetailPageView();
            GetArticleRequest request = new GetArticleRequest() { ArticleId = id };
            GetArticleResponse response = articleCatalogService.GetArticle(request);
            ArticleView articleView = response.Article;
            detailView.Article = articleView;
            detailView.Comments = response.Comments;
            detailView.Tags = response.Tags;
            return detailView;
        }


        PRivate static GetArticlesByCategoryRequest GenerateArticlesByCategoryRequestFrom(int categoryId, int index)
        {
            GetArticlesByCategoryRequest request = new GetArticlesByCategoryRequest();
            request.NumberOfResultsPerPage = int.Parse(applicationSettingsFactory.GetApplicationSettings().NumberOfResultsPerPage);
            request.Index = index;
            request.CategoryId = categoryId;
            request.ExcerptLength = int.Parse(ApplicationSettingsFactory.GetApplicationSettings().ExcerptLength);
            return request;
        }
    }
}

Require配置與系統配置

這里我用到的Require的幾個常用插件:domReady、CSS、text.

paths配置了引用的js的別稱:

paths:{
        'lib/jquery': './Libs/jquery-1.11.1',
        'lib/underscore': './Libs/underscore',
        'lib/unserscore/string': './Libs/underscore.min',
        'lib/backbone':'./Libs/backbone',
        'lib/backbone/eproxy':'./Libs/backbone.eproxy',
        'lib/backbone/super': './Libs/backbone.super',
        'lib/pubsub': './Libs/pubsub',
        'r/css': './Libs/css',
        'r/less': './Libs/less',
        'r/text': './Libs/text',
        'r/domReady': './Libs/domReady',
        'r/normailize': './Libs/normalize',
        'pubsub': './Libs/pubsub',
        'lib/ko': './Libs/knockout-3.2.0',
        'utility': './Utilities/utility',
        'util/matrix2d': './Utilities/matrix2d',
        'util/meld':'./Utilities/meld',
        'lib/bootstrap': './Libs/bootstrap-3.2.0/dist/js/bootstrap',
        'lib/bootstrap/css': './Libs/bootstrap-3.2.0/dist/css/'
    },

shim的配置略過;

然后就是require的調用入口了,從這里啟動整個前端應用:

require(['lib/jquery', 'r/domReady', 'lib/underscore', 'config', 'r/text!Layout/template.html', 'r/css!lib/bootstrap/css/bootstrap.css', 'lib/bootstrap', ], function ($, domReady, _, config, template) {
    domReady(function () {
        var rootContainer = $("body").find("[data-container='root']");
        var oTemplate=$(template);
        var modules = $("[data-module]",oTemplate);
        _.each(modules, function (module, index) {
            require(["App/" + $(module).attr("data-module")], function (ModuleClass) {
                var combineConfig = _.defaults(config[$(module).attr("data-module")], config.application);
                var oModule = new ModuleClass(combineConfig);
                oModule.load();
                oModule.render(modules[index]);
            });
        });
        rootContainer.append(oTemplate);
    });
});

這里看到了template.html通過r/text引入,上文中提到過,它就是整個應用程序的模板文件,先看一下它的結構我再接著解釋代碼內容:

<div class="container">
    <div class="row">
        <div class="col-lg-3" data-module="catalog"></div>
        <div class="col-lg-9" data-module="articleList"></div>
    </div>
    <div data-module="articleViewer"></div>
</div>

 
這樣看起來,代碼的意圖就明晰多了,在頁面中查到了data-container為root的節點,將它作為整個前端應用的根節點,然后再讀取上面的模板文檔,根據模板中標簽的data-module屬性,獲得模塊名稱,然后動態的加載模塊。
在這里我使用了Underscore的_.defaults方法,給各個模塊取得了各自的配置內容和公用配置內容,Underscore是js的一個工具類,自行百度,不多介紹,還有個個人推薦的Underscore.string,它提供了很多js處理字符串的方法,比較方便好用。
上文所提及的應用配置文件如下:
 
define(function () {
    return {
        application: {
            Events: {
                SWITCH_CATEGORY:"Miaoblog_Switch_Category",
                OPEN_ARTICLE:"Miaoblog_Open_Article"
            }
        },

        catalog: {
            APIs: {
                GetCatalog: {
                    Url: "http://localhost:15482/api/Catalog"
                }
            }
        },
        articleList: {
            APIs: {
                GetArticleList: {
                    Url: "http://localhost:15482/api/Articles",
                    ParamsFormat: "categoryId={0}&pageNumber={1}"
                }
            }
        },
        articleViewer: {
            APIs: {
                GetArticle: {
                    Url:"http://localhost:15482/api/Articles/{0}"
                }
            }
        }
    };
});

結合上文中的代碼,可以明確的知道一點,各個組件模塊最終只會得到關于它自己的配置項目和公用的,也就是application級別的配置內容,在application對象中的Events對象在下文中將會做詳細的介紹。
 

模塊中的工作

就已catalog模塊為例,先貼上代碼,再做解釋:

/// <reference path="../Libs/require.js" />
define(['lib/jquery', 'lib/ko', 'lib/underscore','pubsub', 'r/text!App/catalogList.html'],
    function ($, ko,_, hub,template) {
        var app = function (config) {
            this.catalogList = null;
            this.oTemplate = $(template);
            this.config = config;
        }

        _.extend(app.prototype, {
            load: function () {
                var self = this;
                $.Ajax({
                    type: "GET",
                    async: false,
                    url: self.config.APIs.GetCatalog.Url,
                    dataType: "json",
                    success: function (data) {
                        self.catalogList = data;
                    },
                    error: function (jqXHR, textStatus, error) {
                        console.log(error);
                    }
                });
            },
            render: function (container) {
                var self = this;
                var oContainer = $(container);
          
                var list = {
                    categories: ko.observableArray(),
                    switchCategory: function (selected) {
                        //alert("Hello world"+selected.Id);
                        hub.publish(self.config.Events.SWITCH_CATEGORY, selected.Id);
                    }
                };
                list.categories(self.catalogList);
                oContainer.append(this.oTemplate);
                ko.applyBindings(list, this.oTemplate.get(0));
            }
        });

        return app;
});

 
這里唯一新的內容就是大殺器knockout終于出場了。
從上一節內容可以看到,主模塊將會一次調用子模塊的load和render方法,在這個子模塊catalog中,load階段,通過對服務端的api調用得到了文章目錄,API的地址是通過config文件的解析傳遞過來的,的數據結構是這樣的:
 
QQ截圖20141021215018
 
 
而在render階段,傳入的參數為僅供給當前組件的占位,組件自身可以決定怎樣去布局這個占位,這就涉及到了它自身的模板文件了:
 
<ul class="nav nav-pills nav-stacked" data-bind="foreach:categories">
    <li>
        <a href="#" data-bind="attr:{categoryId:Id},click:$parent.switchCategory">
            <!--ko text: Name--><!--/ko-->
            <span class="badge pull-right" data-bind="text:ArticlesCount"></span>
        </a>
    </li>
</ul>


在數據和視圖兩者間,我使用了Knockout進行綁定,它的優勢在文檔中有詳細的描述,如果您想了解的話,就在文章開始找鏈接吧;
接著分析代碼,在視圖中,使用了Bootstrap的樣式創建了一個目錄樣式,并且banding了一個switchCategory方法到viewModel中,當我們點擊每一個類型鏈接時候,系統會通過上文中提到的Pubsub工具發布一個SWITCH_CATEGORY的事件出去,并且攜帶了所點擊類型的ID,這個常量字符串也是在上一節中的config文件中配置的。
 

模塊間的工作

上一節中提到了Pubsub發布了一個事件出去,意圖是希望文章列表或者其他什么關心這個事件的組件去做它自己的工作,在這個示例中當然就只有articleList這個組件了,來看一下這個組件的代碼:

 

/// <reference path="../Libs/require.js" />
define(['lib/jquery', 'lib/ko', 'lib/underscore', 'utility', 'pubsub', 'r/text!App/articleList.html','r/css!App/CommonStyle/style.css'],
    function ($, ko, _, utility,hub,layout) {
        var app = function (config) {
            this.config = config;
            this.oTemplate = $(layout);
            this.currentPageArticles = null;
            this.currentCategoryId = null;
            this.currentPageNumber = null;
            this.articleListViewModel = null;
        }

        _.extend(app.prototype, {
            initialize:function(){
               
            },

            load: function () {
                var self = this;
                hub.subscribe(this.config.Events.SWITCH_CATEGORY, function (msg, data) {
                    self.switchCategory(data);
                });
            },

            render: function (container) {
                var self = this;
                var oContainer = $(container);
                this.articleListViewModel = {
                    articles: ko.observableArray(),
                    openArticle: function (selected) {
                        hub.publish(self.config.Events.OPEN_ARTICLE, selected.Id);
                    }
                };
                oContainer.append(this.oTemplate);
                ko.applyBindings(this.articleListViewModel, this.oTemplate.get(0));
            },

            switchCategory: function (categoryId) {
                var self = this;
                self.currentCategoryId = categoryId;
                self.currentPageNumber = 1;
                $.ajax({
                    type: "GET",
                    async: true,
                    url: utility.FormatUrl(false,self.config.APIs.GetArticleList,categoryId,self.currentPageNumber),
                    dataType: "json",
                    success: function (data) {
                        self.articleListViewModel.articles(data);
                    }
                });
            },
            turnPage: function (pageNumber) {

            }
        });

        return app;
    }
);
 
 
這里主要看以下兩個點:
1.在Load階段,組件監聽了SWITH_CATEGORY這個事件,在事件觸發后,將調用switchCategory方法;因為這個SWITCH_CATEGORY這個常量是配置在application對象中,所以它在各個組件間是公用的;
2.在switchCategory中,傳入的即使上一節中提到的類型ID,然后同樣通過上一節的方法,調用服務端API,獲得數據,然后使用knockout進行數據綁定,在ViewModel中,可以看到一個openArticle方法,同樣發布了一個事件,在這個示例中,是右articleViewer監聽的,由于原理相近,就不多做解釋了,僅有破圖了代碼送上。
 

爛圖賞鑒

QQ截圖20141021220818

 

QQ截圖20141021220835

 

代碼送上,僅供吐槽

onedrive就不用了,雖然很搞到上,但是誰知道哪天就又…你懂的

百度網盤地址:http://pan.baidu.com/s/1o6meoKa


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美猛交ⅹxxx乱大交视频| 亚洲第一中文字幕在线观看| 色老头一区二区三区在线观看| 国产精品日韩精品| 日韩av色综合| 亚洲高清色综合| 日韩欧美在线视频日韩欧美在线视频| 久久久人成影片一区二区三区| 国产亚洲精品一区二区| 欧美午夜精品久久久久久久| 国内精品400部情侣激情| 国产偷国产偷亚洲清高网站| 国产美女搞久久| 2018中文字幕一区二区三区| 亚洲欧美国产高清va在线播| 国产精品毛片a∨一区二区三区|国| 日韩风俗一区 二区| 日本韩国欧美精品大片卡二| 亚洲精品98久久久久久中文字幕| 亚洲视频一区二区| 久久久久久高潮国产精品视| 日韩www在线| 8x海外华人永久免费日韩内陆视频| 日韩在线精品视频| 亚洲91精品在线观看| 欧美激情视频网站| 久久精品国产成人| 日韩中文字幕免费视频| 91精品视频在线看| 777777777亚洲妇女| 成人精品网站在线观看| 亚洲国产精久久久久久| 在线视频日本亚洲性| 中文字幕av一区二区| 欧美性生活大片免费观看网址| 成人中文字幕+乱码+中文字幕| 亚洲性av网站| 日韩av男人的天堂| 中文字幕精品在线视频| 国模吧一区二区三区| 亚洲毛片在线观看| 91亚洲精品久久久| 欧美激情videos| 久久人体大胆视频| 国产精品久久久久99| 欧美成年人网站| 26uuu久久噜噜噜噜| 亚洲人成亚洲人成在线观看| 亚洲全黄一级网站| 欧美一级淫片aaaaaaa视频| 日韩久久免费视频| 亚洲人午夜精品免费| 亚洲精品成人免费| 成人激情视频在线播放| 欧美与黑人午夜性猛交久久久| 日韩av在线导航| 成人网在线观看| 欧美猛交ⅹxxx乱大交视频| 久久久这里只有精品视频| 日韩精品免费观看| 亚洲一区久久久| 亚洲欧美色婷婷| 97在线视频免费| 日韩精品在线电影| 久久久精品在线观看| 一区二区中文字幕| 国产欧美精品一区二区| 色999日韩欧美国产| 国产suv精品一区二区三区88区| 亚洲成在人线av| 亚洲va久久久噜噜噜| 久久久亚洲精选| 久久影视电视剧免费网站| 久久久久久美女| 日韩国产欧美精品在线| 亚洲福利小视频| 神马国产精品影院av| 欧美成aaa人片在线观看蜜臀| 亚洲片在线观看| 久久久久久久久久国产精品| 欧美日韩美女视频| 欧美激情欧美狂野欧美精品| 77777少妇光屁股久久一区| 成人精品一区二区三区电影免费| 亚洲性视频网站| 亚洲free性xxxx护士白浆| 精品香蕉在线观看视频一| 国产a级全部精品| 欧美成人在线免费视频| 777精品视频| 国产精品久久久久久久久久东京| 97精品在线观看| 91在线视频精品| 久久97精品久久久久久久不卡| 精品国内自产拍在线观看| 日韩av片电影专区| 性欧美xxxx| 国产va免费精品高清在线| 久久电影一区二区| 欧美激情视频在线| 亚洲国产欧美久久| 欧美激情久久久久久| 亚洲日韩中文字幕在线播放| 韩国精品久久久999| 国产日韩在线看| 国产z一区二区三区| 姬川优奈aav一区二区| 5278欧美一区二区三区| 91超碰caoporn97人人| 日韩美女av在线| 国产成人精品一区二区三区| 91精品在线观看视频| 免费97视频在线精品国自产拍| 国产99视频在线观看| 国产精品无码专区在线观看| 亚洲日本欧美日韩高观看| 中文在线资源观看视频网站免费不卡| 不卡av电影在线观看| 欧美性视频网站| 国产成人精品久久| 欧美另类69精品久久久久9999| 97免费视频在线| 欧美成人国产va精品日本一级| 日韩一区在线视频| 国产亚洲人成a一在线v站| 欧美成人精品三级在线观看| 久久欧美在线电影| 亚洲图片制服诱惑| 深夜福利一区二区| 色无极亚洲影院| 69久久夜色精品国产69乱青草| 77777亚洲午夜久久多人| 欧美裸体xxxxx| 国产精品久久久久久久美男| 久久久精品免费视频| 亚洲欧美日韩精品久久亚洲区| 国产91精品久久久久| 欧美性资源免费| 美女精品视频一区| 国产情人节一区| 91成人精品网站| 亚洲激情自拍图| 国产有码在线一区二区视频| 日韩欧美精品中文字幕| 国产亚洲精品久久| 欧美在线视频网站| 成人av色在线观看| 亚洲另类激情图| 国产亚洲激情视频在线| 一区二区三区四区视频| 自拍偷拍亚洲精品| 国产精品久久久久77777| 精品夜色国产国偷在线| 国产精品www色诱视频| 成人乱人伦精品视频在线观看| 欧美老少配视频| 久久久久久久久久久亚洲| 久热精品在线视频| 久久免费视频在线观看| 伦理中文字幕亚洲| 日韩美女av在线免费观看| 国内自拍欧美激情| 国产成人综合一区二区三区| 97成人精品视频在线观看|