Durandal (knockout) app with multilanguage support - javascript

I am building multilingual support for the app I'm working on. After doing some research and reading SO (internationalization best practice) I am trying to integrate that in a 'framework-friendly' way.
What I have done at the moment is following:
Created .resource modules formatted like so:
resources.en-US.js
define(function () {
return {
helloWorlLabelText: "Hello world!"
}
});
On the app.start I get the resource module with requirejs and assign all data to app.resources. Inside of each module specific resource is assigned to observables and bonded with text binding to labels and other text related things. Like so:
define(function (require) {
var app = require('durandal/app'),
router = require('durandal/plugins/router')
};
return{
helloWorldLabelText: ko.observable(app.resources.helloWorldLabelText),
canDeactivate: function () {
}
}
});
On the view:
<label for="hello-world" data-bind="text: helloWorldLabelText"></label>
The resources are swapped just by assigning new module to app.resources.
Now the problem is when the language is changed and some of the views have been already rendered, the values of previous language are still there. So I ended up reassigning observables inside of activate method. Also tried wrapping app.resources into observable, but that didn't work either.
I don't think I ended up with the most clean way and maybe anybody else had some other way that could share. Thanks.

For those who are still confused about best practices, those who feel that something is lacking, or those who are simply curious about how to implement things in a better way with regard to Durandal, Knockout, RequireJS, and client-side web applications in general, here is an attempt at a more useful overview of what's possible.
This is certainly not complete, but hopefully this can expand some minds a little bit.
First, Nov 2014 update
I see this answer keeps being upvoted regularly even a year later. I hesitated to update it multiple times as I further developed our particular solution (integrating i18next to Durandal/AMD/Knockout). However, we eventually dropped the dependent project because of internal difficulties and "concerns" regarding the future of Durandal and other parts of our stack. Hence, this little integration work was canceled as well.
That being said, I hopefully distinguished generally applicable remarks from specific remarks below well enough, so I think they keep offering useful (perhaps even well needed) perspectives on the matters.
If you're still looking to play with Durandal, Knockout, AMD and an arbitrary localization library (there are some new players to evaluate, by the way), I've added a couple of notes from my later experiences at the end.
On the singleton pattern
One problem with the singleton pattern here is that it's hard to configure per-view; indeed there are other parameters to the translations than their locale (counts for plural forms, context, variables, gender) and these may themselves be specific to certain contexts (e.g. views/view models).
By the way it's important that you don't do this yourself and instead rely on a localization library/framework (it can get really complex). There are many questions on SO regarding these projects.
You can still use a singleton, but either way you're only halfway there.
On knockout binding handlers
One solution, explored by zewa666 in another answer, is to create a KO binding handler. One could imagine this handler taking these parameters from the view, then using any localization library as backend. More often than not, you need to change these parameters programmatically in the viewmodel (or elsewhere), which means you still need to expose a JS API.
If you're exposing such an API anyway, then you may use it to populate your view model and skip the binding handlers altogether. However, they're still a nice shortcut for those strings that can be configured from the view directly. Providing both methods is a good thing, but you probably can't do without the JS API.
Current Javascript APIs, document reloading
Most localization libraries and frameworks are pretty old-school, and many of them expect you to reload the entire page whenever the user changes the locale, sometimes even when translation parameters change, for various reasons. Don't do it, it goes against everything a client-side web application stands for. (SPA seems to be the cool term for it these days.)
The main reason is that otherwise you would need to track each DOM element that you need to retranslate every time the locale changes, and which elements to retranslate every time any of their parameters change. This is very tedious to do manually.
Fortunately, that's exactly what data binders like knockout make very easy to do. Indeed, the problem I just stated should remind you of what KO computed observables and KO data-bind attributes attempt to solve.
On the RequireJS i18n plugin
The plugin both uses the singleton pattern and expects you to reload the document. No-go for use with Durandal.
You can, but it's not efficient, and you may or may not uselessly run into problems depending on how complex your application state is.
Integration of knockout in localization libraries
Ideally, localization libraries would support knockout observables so that whenever you pass them an observable string to translate with observable parameters, the library gives you an observable translation back. Intuitively, every time the locale, the string, or the parameters change, the library modifies the observable translation, and should they be bound to a view (or anything else), the view (or whatever else) is dynamically updated without requiring you to do anything explicitly.
If your localization library is extensible enough, you may write a plugin for it, or ask the developers to implement this feature, or wait for more modern libraries to appear.
I don't know of any right now, but my knowledge of the JS ecosystem is pretty limited. Please do contribute to this answer if you can.
Real world solutions for today's software
Most current APIs are pretty straightforward; take i18next for example. Its t (translate) method takes a key for the string and an object containing the parameters. With a tiny bit of cleverness, you can get away with it without extending it, using only glue code.
translate module
define(function (require) {
var ko = require('knockout');
var i18next = require('i18next');
var locale = require('locale');
return function (key, opts) {
return ko.computed(function () {
locale();
var unwrapped = {};
if (opts) {
for (var optName in opts) {
if (opts.hasOwnProperty(optName)) {
var opt = opts[optName];
unwrapped[optName] = ko.isObservable(opt) ? opt() : opt;
}
}
}
return i18next.t(key, unwrapped);
});
}
});
locale module
define(function (require) { return require('knockout').observable('en'); });
The translate module is a translation function that supports observable arguments and returns an observable (as per our requirements), and essentially wraps the i18next.t call.
The locale module is an observable object containing the current locale used globally throughout the application. We define the default value (English) here, you may of course retrieve it from the browser API, local storage, cookies, the URI, or any other mechanism.
i18next-specific note: AFAIK, the i18next.t API doesn't have the ability to take a specific locale per translation: it always uses the globally configured locale. Because of this, we must change this global setting by other means (see below) and place a dummy read to the locale observable in order to force knockout to add it as a dependency to the computed observable. Without it, the strings wouldn't be retranslated if we change the locale observable.
It would be better to be able to explicitly define dependencies for knockout computed observables by other means, but I don't know that knockout currently provides such an API either; see the relevant documentation. I also tried using an explicit subscription mechanism, but that wasn't satisfactory since I don't think it's currently possible to trigger a computed to re-run explicitly without changing one of its dependencies. If you drop the computed and use only manual subscription, you end up rewriting knockout itself (try it!), so I prefer to compromise with a computed observable and a dummy read. However bizarre that looks, it might just be the most elegant solution here. Don't forget to warn about the dragons in a comment.
The function is somewhat basic in that it only scans the first-level properties of the options object to determine if they are observable and if so unwraps them (no support for nested objects or arrays). Depending on the localization library you're using, it will make sense to unwrap certain options and not others. Hence, doing it properly would require you to mimic the underlying API in your wrapper.
I'm including this as a side note only because I haven't tested it, but you may want to use the knockout mapping plugin and its toJS method to unwrap your object, which looks like it might be a one-liner.
Here is how you can initialize i18next (most other libraries have a similar setup procedure), for example from your RequireJS data-main script (usually main.js) or your shell view model if you have one:
var ko = require('knockout');
var i18next = require('i18next');
var locale = require('locale');
i18next.init({
lng: locale(),
getAsync: false,
resGetPath: 'app/locale/__ns__-__lng__.json',
});
locale.subscribe(function (value) {
i18next.setLng(value, function () {});
});
This is where we change the global locale setting of the library when our locale observable changes. Usually, you'll bind the observable to a language selector; see the relevant documentation.
i18next-specific note: If you want to load the resources asynchronously, you will run in a little bit of trouble due to the asynchronous aspect of Durandal applications; indeed I don't see an obvious way to wrap the rest of the view models setup code in a callback to init, as it's outside of our control. Hence, translations will be called before initialization is finished. You can fix this by manually tracking whether the library is initialized, for example by setting a variable in the init callback (argument omitted here). I tested this and it works fine. For simplicity here though, resources are loaded synchronously.
i18next-specific note: The empty callback to setLng is an artifact from its old-school nature; the library expects you to always start retranslating strings after changing the language (most likely by scanning the DOM with jQuery) and hence the argument is required. In our case, everything is updated automatically, we don't have to do anything.
Finally, here's an example of how to use the translate function:
var _ = require('translate');
var n_foo = ko.observable(42);
var greeting = _('greeting');
var foo = _('foo', { count: n_foo });
You can expose these variables in your view models, they are simple knockout computed observables. Now, every time you change the locale or the parameters of a translation, the string will be retranslated. Since it's observable, all observers (e.g. your views) will be notified and updated.
var locale = require('locale');
locale('en_US');
n_foo(1);
...
No document reload necessary. No need to explicitly call the translate function anywhere. It just works.
Integration of localization libraries in knockout
You may attempt to make knockout plugins and extenders to add support for localization libraries (besides custom binding handlers), however I haven't explored the idea, so the value of this design is unknown to me. Again, feel free to contribute to this answer.
On Ecmascript 5 accessors
Since these accessors are carried with the objects properties everywhere they go, I suspect something like the knockout-es5 plugin or the Durandal observable plugin may be used to transparently pass observables to APIs that don't support knockout. However, you'd still need to wrap the call in a computed observable, so I'm not sure how much farther that gets us.
Yet again, this is not something I looked at a lot, contributions welcome.
On Knockout extenders
You can potentially leverage KO extenders to augment normal observables to translate them on the fly. While this sounds good in theory, I don't think it would actually serve any kind of purpose; you would still need to track every option you pass to the extender, most likely by manually subscribing to each of them and updating the target by calling the wrapped translation function.
If anything, that's merely an alternative syntax, not an alternative approach.
Conclusion
It feels like there is still a lot lacking, but with a 21-lines module I was able to add support for an arbitrary localization library to a standard Durandal application. For an initial time investment, I guess it could be worse. The most difficult part is figuring it out, and I hope I've done a decent job at accelerating that process for you.
In fact, while doing it right may sound a little complicated (well, what I believe is the right way anyway), I'm pretty confident that techniques like these make things globally simpler, at least in comparison to all the trouble you'd get from trying to rebuild state consistently after a document reload or to manually tracking all translated strings without Knockout. Also, it is definitely more efficient (UX can't be smoother): only the strings that need to be retranslated are retranslated and only when necessary.
Nov 2014 notes
After writing this post, we merged the i18next initialization code and the code from the translate module in a single AMD module. This module had an interface that was intended to mimick the rest of the interface of the stock i18next AMD module (though we never got past the translate function), so that the "KO-ification" of the library would be transparent to the applications (except for the fact that it now recognized KO observables and took the locale observable singleton in its configuration, of course). We even managed to reuse the same "i18next" AMD module name with some require.js paths trickery.
So, if you still want to do this integration work, you may rest assured that this is possible, and eventually it seemed like the most sensible solution to us. Keeping the locale observable in a singleton module also turned out to be a good decision.
As for the translation function itself, unwrapping observables using the stock ko.toJS function was indeed far easier.
i18next.js (Knockout integration wrapper)
define(function (require) {
'use strict';
var ko = require('knockout');
var i18next = require('i18next-actual');
var locale = require('locale');
var namespaces = require('tran-namespaces');
var Mutex = require('komutex');
var mutex = new Mutex();
mutex.lock(function (unlock) {
i18next.init({
lng: locale(),
getAsync: true,
fallbackLng: 'en',
resGetPath: 'app/locale/__lng__/__ns__.json',
ns: {
namespaces: namespaces,
defaultNs: namespaces && namespaces[0],
},
}, unlock);
});
locale.subscribe(function (value) {
mutex.lock(function (unlock) {
i18next.setLng(value, unlock);
});
});
var origFn = i18next.t;
i18next.t = i18next.translate = function (key, opts) {
return ko.computed(function () {
return mutex.tryLockAuto(function () {
locale();
return origFn(key, opts && ko.toJS(opts));
});
});
};
return i18next;
});
require.js path trickery (OK, not that tricky)
requirejs.config({
paths: {
'i18next-actual': 'path/to/real/i18next.amd-x.y.z',
'i18next': 'path/to/wrapper/above',
}
});
The locale module is the same singleton presented above, the tran-namespaces module is another singleton that contains the list of i18next namespaces. These singletons are extremely handy not only because they provide a very declarative way of configuring these things, but also because it allows the i18next wrapper (this module) to be entirely self-initialized. In other words, user modules that require it will never have to call init.
Now, initialization takes time (might need to fetch some translation files), and as I already mentioned a year ago, we actually used the async interface (getAsync: true). This means that a user module that calls translate might in fact not get the translation directly (if it asks for a translation before initialization is finished, or when switching locales). Remember, in our implementation user modules can just start calling i18next.t immediately without waiting for a signal from the init callback explicitly; they don't have to call it, and thus we don't even provide a wrapper for this function in our module.
How is this possible? Well, to keep track of all this, we use a "Mutex" object that merely holds a boolean observable. Whenever that mutex is "locked", it means we're initializing or changing locales, and translations shouldn't go through. The state of that mutex is automatically tracked in the translate function by the KO computed observable function that represents the (future) translation and will thus be re-executed automatically (thanks to the magic of KO) when it changes to "unlocked", whereupon the real translate function can retry and do its work.
It's probably more difficult to explain than it is to actually understand (as you can see, the code above is not overly long), feel free to ask for clarifications.
Usage is very easy though; just var i18next = require('i18next') in any module of your application, then call i18next.t away at any time. Just like the initial translate function you may pass observable as arguments (which has the effect of retranslating that particular string automatically every time such an argument is changed) and it will return an observable string. In fact, the function doesn't use this, so you may safely assign it to a convenient variable: var _ = i18next.t.
By now you might be looking up komutex on your favorite search engine. Well, unless somebody had the same idea, you won't find anything, and I don't intend to publish that code as it is (I couldn't do that without losing all my credibility ;)). The explanation above should contain all you need to know to implement the same kind of thing without this module, though it clutters the code with concerns I'm personally inclined to extract in dedicated components as I did here. Toward the end, we weren't even 100% sure that the mutex abstraction was the right one, so even though it might look neat and simple, I advise that you put some thoughts into how to extract that code (or simply on whether to extract it or not).
More generally, I'd also advise you to seek other accounts of such integration work, as its unclear whether these ideas will age well (a year later, I still believe this "reactive" approach to localization/translation is absolutely the right one, but that's just me). Maybe you'll even find more modern libraries that do what you need them to do out of the box.
In any case, it's highly unlikely that I'll revisit this post again. Again, I hope this little(!) update is as useful as the initial post seems to be.
Have fun!

I was quite inspired by the answers in SO regarding this topic, so I came up with my own implementation of a i18n module + binding for Knockout/Durandal.
Take a look at my github repo
The choice for yet another i18n module was that I prefer storing translations in databases (which ever type required per project) instead of files. With that implementation you simply have a backend which has to reply with a JSON object containing all your translations in a key-value manner.
#RainerAtSpirit
Good tip with the singleton class was very helpful for the module

You might consider having one i18n module that returns a singleton with all required observables. In addition a init function that takes an i18n object to initialize/update them.
define(function (require) {
var app = require('durandal/app'),
i18n = require('i18n'),
router = require('durandal/plugins/router')
};
return{
canDeactivate: function () {
}
}
});
On the view:
<label for="hello-world" data-bind="text: i18n.helloWorldLabelText"></label>

Here is an example repo made using i18next, Knockout.Punches, and Knockout 3 with Durandal:
https://github.com/bestguy/knockout3-durandal-i18n
This allows for Handlebars/Angular-style embeds of localized text via an i18n text filter backed by i18next:
<p>
{{ 'home.label' | i18n }}
</p>
also supports attribute embeds:
<h2 title="{{ 'home.title' | i18n }}">
{{ 'home.label' | i18n }}
</h2>
And also lets you pass parameters:
<h2>
{{ 'home.welcome' | i18n:name }}
<!-- Passing the 'name' observable, will be embedded in text string -->
</h2>
JSON example:
English (en):
{
"home": {
"label": "Home Page",
"title": "Type your name…"
"welcome": "Hello {{0}}!",
}
}
Chinese (zh):
{
"home": {
"label": "家",
"title": "输入你的名字……",
"welcome": "{{0}}您好!",
}
}

Related

Drawbacks of using singleton with models

So let's say I'm making a React Redux app for handling a library. I want to create an API for my backend where each model (book, author, etc) is displayed in the UI.
Each model does not provide a public constructor, but a from static function which ensures that only one instance per id exists:
static from (id: string) {
if (Books.books[id]) {
return Books.books[id];
}
return Book.books[id] = new Book(id);
}
Each model provides an async fetch function which will fetch its props using the backend. The advantage is that there is no thousands instances, also I don't have to fetch twice (if two parts of my app needs the same model, fetch will actually be called only once). But I fail to find any drawbacks, except that there might be a discrepancy between a code that fetches its models and one that assumes they are still not fetched, but I still don't see when it would really be an issue
But I fail to find any drawbacks
I see at least two :
The singleton pattern is an anti pattern.
Static factory methods don't provide explicit dependencies.
Mocking the method in unit tests or switch to another implementation will be harder.
You don't have cache size limitation.
For short lists, it is OK.
But if you may cache many objects, you should keep only last recently used instances.
I can think of two problems:
Are your models mutable? If you change a property of the instance, it would reflect everywhere that instance is used. That might either be desirable, or not at all. And with that from method, you cannot do anything about it.
If your models are immutable, sharing instances is in fact a common practice, also known as hash consing.
Your implementation leaks memory like hell. The instances will stay referenced from that books array/object even if they are no longer needed.

JS: Best practice on global "window" object

Following a rapid-prototyping approach, I am developing an application in Marionette.js/backbone.js and heavily used the window-object to bind collections and views to the global stack (e.g. window.app.data, window.app.views).
Of course, it is always better (smoother!) to encapsulate objects in a single class and pass them as parameters where needed. However, this has some limitations when an app and its potential use-cases become really big. And as the data I deal with comes from an API and therefore would be anyway accessible to anybody interested, does that justify storing data in the window-object? Or are there other best-practices in ES6 (or especially Marionette.js) to achieve the same results, but in a more private manner?!
I already go into details about a simple namespacing pattern in JavaScript in another answer. You seem to be already close to this with window.app.data etc.
But it looks like you have a lot of misconceptions about how JavaScript works.
a namespace-based solution that integrates nicely with Browserify/AMD-modules
Then why not use RequireJS? Browserify? or Webpack? There's nothing that a global ridden spaghetti code can do that a modular approach can't do better.
such would be read-only
No. While not impossible to set an object property to read-only, you must explicitly do it with something like Object.seal or Object.freeze.
I do not want to attach objects to the namespace, but actual instances
JavaScript do not have "namespaces" as part of the language, it's just a pattern to scope all your code within literal objects (key-value).
You can put whatever you'd like.
const MyNamespace = {
MyType: Backbone.Model.extend({ /*...*/ }),
instance: new Backbone.Model(),
anyValue: "some important string",
};
Ideally, you would define the namespace within an IIFE to avoid leaking any variable to the global scope.
const app = app || {};
app.MyModel = (function(app){
return Backbone.Model.extend({
// ...
});
})(app);
[...] data I deal with comes from an API and therefore would be anyway accessible to anybody interested
Even if the data is contained within a module that do not leak to the global scope, anybody can access the data. That's how JavaScript works, it's in the user's browser, he can do whatever he wants with the code and the data.
does that justify storing data in the window-object?
No.
Or are there other best-practices in ES6
ES6 has nothing to do with the architecture and patterns you take for your app.
but in a more private manner?!
Like I said earlier, privacy in JavaScript can't be expected.
[encapsulate objects in a single class and pass them as parameters where needed] has some limitations when an app and its potential use-cases become really big.
That's just incorrect. It's the other way around. Software patterns exist solely to help alleviate any limitations that arise as a project grows in scope.
There are multiple patterns you can use that will help deal with the complexity of a bigger app, like:
Modular approach with components
Dependency injection
Service containers
Factories
Events
etc.
I didn't read specifically this book, but JavaScript Design Patterns seems to be a good way to learn more and it demonstrates specific implementations of software patterns in JS.

Marionette Controller Function Declarations - Best Practices

I'm writing a large scale marionette application, which is ran initially from a router / controller.
Here's my question -- is it good practice to include other functions in your controller that aren't meant for routes?
So say I have the following method:
index : function() {
alert('test!');
}
is it consistent with best practices to be able to declare other functions in the controller not called when routes are initialized? Like so:
noRouteAssociated: function() {
alert('test!');
}
index: function() {
this.noRouteAssociated();
}
Obviously this is a simplified example, but I am trying to avoid putting large amounts of information or function declarations inside of methods only because they're associated with routers.
The roles and responsibilities of controllers are best illustrated by #davidsulc in this SO post and better yet his new book.
Generally speaking, it's okay to include methods that aren't meant for routes, if they're controlling the workflow of your app. Event triggering is a good example, but if you want to change the appearance of something or retrieve data from a database, you should move these methods to a view or model, respectively. The block quote below is taken directly from Marionette's controller documentation.
The name Controller is bound to cause a bit of confusion, which is rather unfortunate. There was some discussion and debate about what to call this object, the idea that people would confuse this with an MVC style controller came up a number of times. In the end, we decided to call this a controller anyways, as the typical use case is to control the workflow and process of an application and / or module.
But the truth is, this is a very generic, multi-purpose object that can serve many different roles in many different scenarios. We are always open to suggestions, with good reason and discussion, on renaming objects to be more descriptive, less confusing, etc. If you would like to suggest a different name, please do so in either the mailing list or the github issues list.

Javascript MVVM design patterns - how to track dirty state and who should do Ajaxing?

I'm part way through a pretty complex Knockout.js app using the mapping plugin to work with a deep object graph which closely mirrors server side domain objects. I've been refining my patterns as I go to something which works pretty well for my own slightly awkward context but I'd like to know if it's a good / bad overall way to approach MVVM Javascript.
Essentially my page pattern is to have a revealing module function which acts a bit like a controller - it owns the hierarchy of view models and is responsible for detecting changes, Ajaxing changes to the server and using the mapping plugin to update its view model graph with any flow-on changes which may come back in the response as JSON. I've done it this way because my domain is such that a small change in one part of the graph, when validated on the server, may result in changes / removals in distant parts of the graph. When this happens I need a common point at which to re-map the changes, present message dialogs to the user, etc.
All of my view models are instantiable functions and I've designed it so that they know nothing about the page they're used in, or the server (i.e. they don't do their own Ajaxing). The constructor of each view model creates its children via mapping options and each level is passed a reference to its parent. I've implemented a generic dirty flag which most of the view models use, and when a change occurs they pass a reference to themselves up the chain to a "dirty view model" observable at the top, which the module is subscribed to. I know this sounds a bit odd but it seemed the best way to approach it because items at each level are constantly being added and removed so I can't statically subscribe to properties at initialization time. And I don't want to keep removing and re-adding subscriptions each time I re-map my graph (which can get quite big).
From a pure efficiency point of view this isn't the best. The simplest way would be that each view model directly calls a function in the module when it needs to, but that type of coupling has to be wrong. Or I could pass in a reference to the module (or its relevant function) to each view model constructor, and the view model calls that, a bit like Javascript dependency injection. But that just seems too abstract. This is complex enough as it is.
Should I be looking at a higher level framework such as Backbone to sit on top of all this? Is injecting the module reference really too abstract? Or does this way of structuring things basically make sense as it is? I'm keen to hear from anyone who has worked on similarly challenging scenarios as to how you progressed and refined your patterns.
EDIT: I should have clarified that for various reasons, this app works in "save as you go" mode, whereby a change at a given level causes an immediate discrete Ajax post of just that one view model (not including its children) to be sent to the server (which may return a result which represents a change to just about anything else). Despite this annoying need for constant Ajaxing as opposed to pure client side action, Knockout.js has still made my app WAY more elegant, maintainable and scalable than my MVC apps of Olde.
Decoupling your viewmodels and reducing references can be achieved with a pub/sub model, like the one Ryan Niemeyer discusses here.
Ryan also made a Dirty flag for viewmodels, which can be found here.

Best practise for context mode at runtime in JS

I have a web application based on apache. php, js and jquery. All works fine.
On the client side there is a small library in JS/jquery, offering some generic list handling methods. In the past I used callbacks to handle those few issues where those methods had to behave slightly different. That way I can reuse methods like list handling, dialog handling and stuff for different part of the application. However recently the number of callbacks I had to hand through when stepping into the library grew and I am trying a redesign:
Instead of specifying all callbacks as function arguments I created a central catalog object in the library. Each module of the application registers its own variant of callbacks into that catalog upon initialization. At runtime the methods lookup the required callbacks in that catalog instead of expecting it specified in their list of arguments. This cleans up things quite a lot.
However I have one thing I still cannot get rid of: I require a single argument (I call it context, mode might be another term) that is used by the methods to lookup the required callback in the catalog. This context has to be handed through to all methods. Certainly better than all sorts of different callbacks being specified everywhere, but I wonder if I can get rid of that last one to.
But where do I specify that context, if not as method argument ? I am pretty new to JS and jquery, so I failed to find an approach for this. Apparently I don't want to use global vars, and to be frank I doubt that I can simply store a context in a single variable anyway, since because of all the event handlers and external influences methods might be called in different contexts at the same time, or at least interleaving. So I guess I need something closer to a function stack. Maybe I can simply push a context object to the stack and read that from within the layers of the library that need to know ? The object would be removed when I leave the library again. Certainly other approaches exist too.
Here are so many experienced coders how certainly can give a newbie like a short hint, a starting point that leads to an idea, how to implement this. How is such thing 'usually' done ?
I tried round a while, exploring the arguments.callee.caller hierarchy. I thought maybe I could set a prototype member inside a calling function, then, when execution steps further down I could simply traverse the call stack upwards until I find a caller holding such property and use that value as context.
However I also saw the ongoing discussions that reveal two things: 1.) arguments.callee appears to be depreciated and 2.) it appears to be really expensive. So that is a no go.
I also read about the Function.caller alternative (which appears not to be depreciated and much more efficient, however until now I failed to explore that trail...
As written currently passing the context/mode down simply works by specifying an additional argument in the function calls. It carries a unique string that is used as a key when consulting the catalog. So something like this (not copied, but written as primitive example):
<!-- callbacks -->
callback_inner_task_base:function(arg1,arg2){
// do something with args
}
callback_inner_task_spec:function(arg1,arg2){
// do something with args
}
<!-- catalog -->
Catalog.Callback:function(context,slot){
// some plausibility checks...
return Catalog[context][slot];
}
Catalog.base.slot=callback_inner_task_base;
Catalog.spec.slot=callback_inner_task_spec;
<!-- callee -->
do_something:function(arg1,arg2,context){
...
// callback as taken from the catalog
Catalog.Callback(callback,'inner_task')(arg1,arg2);
...
}
<!-- caller -->
init:function(...){
...
do_something('thing-1',thing-2','base');
do_something('thing-1',thing-2','spec');
...
}
But where do I specify that context, if not as method argument ?
Use a function property, such as Catalog.Callback.context
Use a monad

Categories

Resources