Durandal / Knockout - 全球可观察

时间:2014-03-20 18:30:47

标签: knockout.js durandal

上下文

在我的shell.html中,我已将语言选择放入标题部分。它看起来像这样:

    <select class="form-control select2"
      data-bind="options:languages,
      optionsText:'label',
      optionsValue:'code',
      value:selectedLanguage">
    </select>

我的shell.js:

define(['plugins/router', 'durandal/app', 'knockout'], function (router, app, ko) {
    return {
        router: router,
        selectedLanguage: ko.observable(),
        languages: [
            {code: 'cs', label: 'čeština'},
            {code: 'sk', label: 'slovenský'},
            {code: 'en', label: 'english'},
            {code: 'de', label: 'deutsch'},
            {code: 'ru', label: 'русский'}
        ],
        activate: function () {
            return router.map([
               { route: ['', 'dashboard'],      moduleId: 'core/dashboard/pages/index',     title: 'Dashboard',         icon: 'fa fa-dashboard fa-fw',      nav: true },
               { route: 'attributes*details',   moduleId: 'core/attribute/pages/index',     title: 'Attribute',     icon: 'fa fa-puzzle-piece fa-fw',   nav: true, hash: '#/attributes' }
           ]).buildNavigationModel()
             .mapUnknownRoutes('core/dashboard/pages/index', 'not-found')
             .activate();
        }
    };
});

当我加载其他视图时,它始终保留在标题中并且不会更改。

任务

每当用户更改语言时,我希望能够在任何子页面上注册/注意这一点,然后做出相应的反应 - 就像用另一种语言显示文本一样。

问题

我认为这更像是一个概念性问题。如果我改变语言,那么我确信我在shell.js中的observable会选择它。我不明白的是如何为我的子视图实现这一点。我是否需要注册一些全球用户?

在shell.js中做出反应显然是不够的,因为该视图模型不知道需要加载哪些内容。只有实际的子视图/模块知道这一点。但为了做出反应,他们需要知道语言已经改变了。

如果有人可以帮我这个并且给我一个关于如何在durandal / knockout环境中最好地处理这些类型特征的提示会很棒。

[编辑]当前解决方案

我现在尝试了一些事情,这就是我想出的:

main.js:创建一个AppViewModel并将其添加到全局myapp对象(所有页面都需要)。

  define([
          'durandal/system', 
          'durandal/app', 
          'durandal/viewLocator', 
          'plugins/router', 
          'myapp/myapp',
          'myapp/appViewModel'
          ],
    function (
        system, 
        app, 
        viewLocator, 
        router, 
        myapp,
        AppViewModel
    ) {
      system.debug(true);

      // Global view-model which will be required by sub-pages.
      myapp.app = new AppViewModel({
        defaultLanguage: 'cs'
      });

      app.title = 'Control Panel';

      app.configurePlugins({
          router:true,
          dialog: true,
          widget: true
      });

      app.start().then(function() {
          viewLocator.useConvention();
          app.setRoot('shell', 'entrance');
      });
  });

AppViewModel:处理语言事件

  //------------------------------------------------------------------
  // Global AppViewModel which holds the current language, 
  // listens for changes and notifies the current sub-page 
  // if is has registered a listener-function.
  //------------------------------------------------------------------
  define([ 'jquery', 'underscore', 'knockout' ], function($, _, ko) {

    "use strict";

    function AppViewModel(options) {

        if (!(this instanceof AppViewModel)) {
            throw new TypeError("AppViewModel constructor cannot be called as a function.");
        }

        this.options = options || {};   

        // Set the initial language.
        this.selectedLanguage = ko.observable(options.defaultLanguage);

        _.bindAll(this, 'languageChanged', 'onLanguageChange');
    }

    AppViewModel.prototype = {
        constructor: AppViewModel,
        //---------------------------------------------------------------
        // Calls listener when language changes. See shell.
        //---------------------------------------------------------------
        languageChanged: function(data, event) {
            if(!_.isNull(this.onLanguageChange)
                && !_.isUndefined(this.onLanguageChange) 
                && _.isFunction(this.onLanguageChange)) {
                this.onLanguageChange(data, event);
            }
        },
        //---------------------------------------------------------------
        // Listener that can be overridden by view-models of sub-pages.
        //---------------------------------------------------------------
        onLanguageChange: function(data, event) {
            // Override by ViewModel.
        },
        //---------------------------------------------------------------
        // Clear() should be called in deactivate callback of the 
        // view-model to stop being notified when the language changes.
        //---------------------------------------------------------------
        clear: function() {
            // Reset to empty function.
            this.onLanguageChange = function(data, event) {}
        }
    }

    return AppViewModel;    
  });

shell.js:

    define(['plugins/router', 'durandal/app', 'knockout', 'underscore', 'myapp/myapp'], function (router, app, ko, _, myapp) {

      return {
          router: router,
          languages: [
              {code: 'cs', label: 'čeština'},
              {code: 'sk', label: 'slovenský'},
              {code: 'en', label: 'english'},
              {code: 'de', label: 'deutsch'},
              {code: 'ru', label: 'русский'}
          ],
          app: myapp.app, // make app available to shell.html for the event handler. 
          activate: function () {
              return router.map([
               { route: ['', 'dashboard'],      moduleId: 'core/dashboard/pages/index',     title: 'Dashboard',         icon: 'fa fa-dashboard fa-fw',      nav: true },
               { route: 'attributes*details',   moduleId: 'core/attribute/pages/index',     title: 'Attribute',     icon: 'fa fa-puzzle-piece fa-fw',   nav: true, hash: '#/attributes' }
           ]).buildNavigationModel()
             .mapUnknownRoutes('core/dashboard/pages/index', 'not-found')
             .activate();
          }
    };
  });

shell.html:现在正在全局AppViewModel上调用languageChanged()

  <select class="form-control select2"
    data-bind="event: { change: app.languageChanged }, options:languages,
    optionsText:'label',
    optionsValue:'code',
    value:app.selectedLanguage"></select>       

最后,其中一个页面视图模型(现在在shell.html中的selecbox更改时会通知)

  define(['jquery', 'knockout', 'myapp/myapp'], function ($, ko, myapp) {

      return {
        activate: function(data) {

                myapp.app.onLanguageChange = function(data, event) {
                    // Handle language stuff ...
                }
        },
            deactivate : function() {
          // Stop listening for language changes
                myapp.app.clear();
            }
      }
  });
嗯......我想知道是否有人读得足以达到这条线。如果你这样做,谢谢你的时间。

那么,这是一种可行的方法,还是有更多“最佳实践”的方式呢?

[EDIT]使用durandal发布/订阅机制改进了解决方案:

AppViewModel:现在只发送消息 - 不需要监听器逻辑。

AppViewModel.prototype = {
    constructor : AppViewModel,
    // ---------------------------------------------------------------
    // Trigger message when language has changed.
    // ---------------------------------------------------------------
    languageChanged : function(data, event) {
        app.trigger('language:change', data);
    }
}

index.js:现在只收到消息 - 无需在AppViewModel上注册监听器。

  define(['durandal/app', 'jquery', 'knockout'], function (app, $, ko) {

      return {
        _langSubscriber: {},
        activate: function(data) {
            _langSubscriber = app.on('language:change').then(function(language) {
                // Handle language stuff ...
            });
        },
        deactivate : function() {
            _langSubscriber.off();
        }
      }
  });

正如Eric Taylor指出的那样,重新取消订阅非常重要。如果不这样做,最终会有很多订阅,很可能是内存泄漏。

祝你好运, 迈克尔

1 个答案:

答案 0 :(得分:4)

我们所做的是使用单个config模块。如果您不熟悉Durandal中的单例与实例模块,那么您应该知道:如果您的模块返回一个对象文字,它将是单例,并将在应用程序会话期间保留在内存中。

如果你的模块返回一个构造函数,那么Durandal将使用该构造函数在每次访问或组合时实例化模块的实例,并在模块不再活动时释放它。

考虑以下基本config模块:

define('config', ['durandal/app', 'knockout'],
    function (app, ko) {

        var currentUser = ko.observable('tester'),
            languages = ko.observableArray([
                {code: 'cs', label: 'čeština'},
                {code: 'sk', label: 'slovenský'},
                {code: 'en', label: 'english'},
                {code: 'de', label: 'deutsch'},
                {code: 'ru', label: 'русский'}
            ]);
    }

    return {
        currentUser: currentUser,
        languages: languages
    };
);

在整个模块中,您需要访问此信息,您只需要模块。此外,请确保config模块上需要更新UI或响应 UI中的更新的任何属性都是可观察的。因此,举例来说,我发现您的languages属性不是observableArray。如果UI必须响应此属性中的更新,则可能需要这样做。

使用config模块

define('someViewModel', ['durandal/app', 'knockout', 'config'],
    function (app, ko, config) {

        var languages = config.languages();
    }
);

请注意,我必须使用config.languages()上的括号来取消引用可观察数组。

如果您的本地config.languages()使用也需要观察,那么您将拥有:

var languages = ko.observableArray(config.languages());

除了上述内容之外,您只需将config模块直接传递回显示的对象文字中即可:

return {
    config: config
};

然后直接在视图中使用该模块。但我不知道我是否喜欢这种方法。如果我打算在视图中使用它们,我的首选是本地化注入的模块。

另一种方法

你也可以使用Durandal的pub / sub在视图模型之间发送消息以响应语言的变化等,而不是使用直观连接的observables。

任何一种方法都是有效的;这取决于你的需求。

<强>资源

在Pluralsight上查看John Papa的课程,单页应用程序,HTML5,Web API,Knockout和jQuery 。然后,看看John的课程的Durandal版本:单页应用程序JumpStart 。您将了解到您正在做的事情。