上下文
在我的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指出的那样,重新取消订阅非常重要。如果不这样做,最终会有很多订阅,很可能是内存泄漏。
祝你好运, 迈克尔
答案 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 。您将了解到您正在做的事情。