Advanced JUL Concepts part 2

In the previous article we have seen that JUL can generate component trees and manage the work with configuration objects.
But JUL has more cards in its sleeve when playing in the field of the configuration namespaces.

Namespaces – utility & relativity

In JavaScript, a namespace is a global object that stores various other objects and functions. Historically, namespaces were used to allow a non-conflicting existence of several libraries or JavaScript tools in the same runtime environment (web page). By using namespaces, authors achieved not only the clear non-polluting use of the global scope, but also the inherent organizing structure of their utilities. Most recently, the role of managing the loading and using the JavaScript libraries into an environment was taken by the modules, with the CommonJS in Node being the most relevant example. Some say that, with the advent of the module pattern, namespaces are obsolete, but namespaces mean structure, and structure is never obsolete.
JUL offers several utilities for working with namespaces: JUL.ns(), JUL.get(), JUL.Instance().
If you want to create an object into a given namespace, even when the namespace doesn’t exist, you can use JUL.ns().
If you want to retrieve an object by its path in the global scope (the succession of property names separated by dots), you can use JUL.get().

// creating a namespace
var oNS = JUL.ns('@.non-standard.tools & libs');
JUL.apply(oNS, {
	setting1: 'Custom NS',
	setting2: ['first', 'second', 'third'],
	method1: function() {
		console.log('Method 1');
	}
});
// retrieving a namespace member
console.log(JUL.get('@.non-standard.tools & libs.setting2.2'));

But namespaces are not necessarily linked to the global scope (absolute), they being able to be used relative to a local scope.
The most powerful scoping of the namespace usage is given by the JUL.Instance() class, which makes all the namespacing operations, like JUL.get() or JUL.ns(), relative to a base (root) namespace. For any parsers and references created by JUL, one can use JUL.getInstance() utility to get the actual instance that created the object.

var sNS = '#.non-standard.~tools~';
var oLocal = {};
// creating a relative namespace
var oNS = JUL.ns(sNS, {}, oLocal);
JUL.apply(oNS, {
	property1: {x: 5, y: 3},
	property2: /^new/i,
	'a-method': function() {
		console.log('A method');
	}
});
// getting a relative namespace member
var fCall = JUL.get(sNS + '.a-method', oLocal);
fCall();
// creating a JUL instance bound to a a namespace
var jul = new JUL.Instance({nsRoot: oNS});
console.log(jul.get('property1.x'));
// getting an implicit instance
var oParser = new JUL.UI.Parser({
	nsRoot: oNS,
	yopDown: true,
	useTags: true
});
console.log(JUL.getInstance(oParser).get('property2'));

Method retrieving and scoping

Several JUL utilities like JUL.factory() or JUL.UI.createDom() accept a dotted path where a function / callback is expected. Internally, this path is resolved to the actual object using JUL.get().

var oTree = {
	tag: 'div',
	css: 'wrapper',
	children: [
		{tag: 'input', id: 'input-name', value: 'A text', listeners: {change: 'NS.lib.defaultChange'}},
		{tag: 'button', id: 'button-go', html: 'Go', listeners: {click: 'NS.lib.defaultClick'}}
	]
};
var oParser = new JUL.UI.Parser({
	defaultClass: 'html',
	customFactory: 'JUL.UI.createDom',
	topDpwm: true,
	useTags: true
});
var oNS = JUL.ns('NS.lib');
JUL.apply(oNS, {
	defaultClick: function() {
		console.log('Button clicked');
	},
	defaultChange: function() {
		console.log('Text changed');
	}
});
oParser.create(oTree, null, window.document.body);

This allows a configuration object to store un-resolved callbacks by the time of their loading. But, if you want the desired callbacks to be called in a given scope, you can use JUL.makeCaller() to create a cached wrapper for a given function with a fixed scope (analogous to ES5 Function.prototype.bind()). When attaching listeners to a DOM element, JUL.UI.createDom() does the binding automatically if you provide a scope property in the listeners config object.

var oTree = {
	tag: 'div',
	css: 'wrapper',
	children: [
		{tag: 'input', id: 'input-name', value: 'A text', listeners: {scope: 'NS.lib', change: 'NS.lib.defaultChange'}},
		{tag: 'button', id: 'button-go', html: 'Go', listeners: {scope: 'NS.lib', click: 'NS.lib.defaultClick'}}
	]
};
var oParser = new JUL.UI.Parser({
	defaultClass: 'html',
	customFactory: 'JUL.UI.createDom',
	topDpwm: true,
	useTags: true
});
var oNS = JUL.ns('NS.lib');
JUL.apply(oNS, {
	clickMsg: 'Button clicked',
	changeMsg: 'Text changed',
	defaultClick: function() {
		console.log(this.clickMsg);
	},
	defaultChange: function() {
		console.log(this.changeMsg);
	}
});
oParser.create(oTree, null, window.document.body);

Dotted path escaping

When using the namespace path string (dotted oath) to retrieve an object, there are several things to take care of.
Each path segment describes an object property string, so it can have a much wider format than the strings for variable names or array indexes. A single separator (i.e. a dot) will be used to separate two segments of the path, and the path can be used to traverse any configuration objects, including arrays.
To cover all use cases, JUL offers an escape syntax for a dotted path, where all dots present in a property string are preceded by a backslash when used inside the path.
All JUL routines that use dotted oaths, like JUL.ns() or JUL.get(), support this syntax.

var oNS = JUL.ns('NS.dotted\\.segment');
JUL.apply(oNS, {
	property: {a: 'A', b: 'B', c: 'C'},
	'dotted.method': function() {
		console.log('Dotted access');
	}
});
var fCall = JUL.get('NS.dotted\\.segment.dotted\\.method');
fCall();

JSON replacers and JSON prefixes

One of the goals of JUL is the reliable serialization of the configuration objects. To achieve this, JUL converts them to an intermediate extended JSON format, before generating the final JavaScript code string.
The place to control how the generated JSON looks is JUL.UI._jsonReplacer callback, which acts as the JSON.stringify() second parameter. Inside this, the user can change what objects get strigified by JUL and how they will look. By default, besides the standard JSON types, JUL is able to serialize: functions, regex objects, date objects. In addition, it’s easy to output any known constructor based objects by prefixing their call with the ‘new’ keyword.

// a constructor that stores copy of an object
var Store = function(oConfig) {
	this._config = JUL.aply({}, oConfig);
};
Store.prototype.serialize = function() {
	return JUL.UI.obj2str(this._config);
};
// a custom parser that can serialize Store objects
var oParser = new JUL.UI.Parser({
	_jsonReplacer: function(sKey, oValue) {
		if (oValue instanceof Store) {
			return 'new Store(' + oValue.serialize() + ')';
		}
		else {
			return JUL.UI._jsonReplacer(sKey, oValue);
		}
	}
});
// output the JSON form of a mixed config object
var oConfig = {
	dates: [new Date(), new Date() + 144000e3, new Date() + 288000e3],
	regexs: {begin: /^\s+/, end: /\s+$/},
	'a-method': function() {
		console.log('A method');
	},
	'a-store': new Store({
		item1: 'An item',
		item2: [0, 2, 4],
		item3: /(red|green|blue)/
	})
};
console.log(oParser.obj2str(oConfig, true));

While the generated strings have a format correlated with their types, one cannot unequivoquely infer that a string with the same format is obtained from a serialized object. For the more advanced cases, when the control over the generated code is stricter, JUL offers the JSON prefixes concept.
A JSON prefix is a unique string that precedes the serialized string of a given type of object.
The JSON prefixes are activated by the JUL.UI._usePrefixes property and are stored in JUL.UI._jsonPrefixes hash (object). By default, they are: the prefix for functions, the prefix for regex syntax, the prefix for the ‘new’ construct, but they can be added or changed as needed. The prefixes only appear in the JSON serialized form of a config tree, but not in the JavaScript serialized form, where the code is directly runable.

var oConfig = {
	xclass: 'FWK.Dialog',
	autoCenter: true,
	avatar: /(hat|suit|shirt)/i,
	validity: [new Date(Date.UTC(2017, 10, 3)), new Date(Date.UTC(2018, 10, 3))],
	listeners: {
		done: function(bQuit) {
			if (bQuit) {
				this.close();
			}
		},
		show: function(nLeft, nTop) {
			this.drawAt(nLeft, nTop);
		}
	}
};
var oParser = new JUL.UI.Parser({
	_usePrefixes: true,
	_tabString: '    '
});
console.log(oParser.obj2str(oConfig, true));

Code references and decorators

There are cases when we need to output a specific code section, that cannot be stored as a configuration object. A recurring example is outpouring other globally namespaced JavaScript tools or libraries that are loaded before the configuration loads.
JUL.UI.obj2str() serialization tool identifies these code strings using JUL.UI.referencePrefix, and outputs them unquoted regardless of the JSON replacer and JSON prefixes settings.

var oConfig = {
	tag: 'div',
	id: 'panel-tools',
	css: 'blue-panel',
	'data-control': 'Test.UI.Panel',
	'data-options': {
		instantiator: '=ref: Test.ContainerBase',
		bounding: [0, 0, 400, 200]
	}
};
var oParser = new JUL.UI.Parser({
	defaultClass: 'html',
	customFactory: 'JUL.UI.createDom',
	topDown: true,
	useTags: true
});
console.log(oParser.obj2str(oConfig));
// JUL.UI.createDom() also obeys code references in the element attributes
oParser.create(oConfig, null, window.document.body);

Taking a closer look at JUL.UI.obj2str() method, we notice that we can change the resulting JavaScript code as we traverse the JSON string. JUL uses these access points to insert custom code before the keys of the serialized object. The first use case of these code segments, called code decorators, is for inserting comment blocks for documenting, like e.g. JCS (JUL Comment System) does.

var oConfig = {
	property1: 'First name',
	property2: ['a', 'b', 'c'],
	metod1: function() {
		console.log('Method 1');
	}
};
var fDecorator = function(sContent, sPath, sIndent) {
		if (!sPath) { return sContent; }
		var nParts = sPath.split('.').length;
		if (nParts > 1) { return sContent; }
		var oRef = JUL.get(sPath, this);
		var sText = '/**' + '\n' + sIndent + ' * ';
		switch (typeof oRef) {
		case 'function':
			sText = sText + 'Method description';
		break;
		default:
			sText = sText + 'Property description';
		}
		sText = sText + '\n' + sIndent + ' */';
		sText = sText + '\n' + sIndent + sContent;
		return sText;
};
var oParser = new JUL.UI.Parser();
console.log(oParser.obj2str(oConfig, false, fDecorator));

Members collections extensibility

In order to parse a configuration tree, JUL must know what members of a config object are representing child component configs. This kind of information is given by the JUL.UI.childremProperty and by the JUL.UI.membersProperties of the parser. The JUL.UI.childrenProperty is a main property, while the JUL.UI.membersProperties array may contain additional names of other direct descendant members, and these members can be reduced to a ‘unified’ form using JUL.UI.expand(). After expanding, the only children member will be that of the JUL.UI.childrenProperty value.
But, there are cases when the members / children properties of a config object depend on the component class of that config. For these cases JUL offers the ‘JUL.UI.membersMappings hash that associates members arrays to the class names. Even more, if a config object has a special property given by the JUL.UI.instantiateProperty value, the parser takes that members property into account when parsing the respective object.

var oTree = {
	xclass: 'Dialog',
	title: 'Title',
	content: [
		{xclass: 'BorderLayout', top: [
			{xclass: 'BorderLayoutArea', content: [
				{xclass: 'MenuBar', items: [
					{xclass: 'MenuItem', text: 'Menu 1', submenu: [
						{xclass: 'Menu', items: [
							{xclass: 'MenuItem', text: 'Submenu 1'}
						]}
					]},
					{xclass: 'MenuItem', text: 'Menu 2', submenu: [
						{xclass: 'Menu', items: [
							{xclass: 'MenuItem', enabled: false, text: 'Submenu 2'}
						]}
					]}
				]},
				{xclass: 'Toolbar', items: [
					{xclass: 'TextField', value: 'Value'},
					{xclass: 'Button', text: 'Button'}
				]}
			]}
		],
		begin: {
			xclass: 'BorderLayoutArea', content: [
				{xclass: 'ListBox'}
			]
		},
		center: {
			xclass: 'BorderLayoutArea', content: [
				{xclass: 'HTML', content: 'Content'}
			]
		}}
	]
};
var oParser = new JUL.UI.Parser({
	childrenProperty: 'items',
	membersMappings: {
		BorderLayout: ['top', 'bottom', 'begin', 'end', 'center'],
		BorderLayoutArea: 'content',
		Dialog: ['content', 'buttons'],
		MenuItem: 'submenu'
	},
	customFactory: function(oConfig) {
		console.log('Creating ' + oConfig.xclass);
		return oConfig;
	}
});
oParser.create(oTree);

JUL also has a ‘sparse’ mode, where the parser tries to traverse any child object of the current config to search for descendant configs to instantiate. In this mode, which is triggered by the fourth argument of JUL.UI.create() method, there is no default class assumed for an object that doesn’t have the class property set. In the sparse mode, any objects that are not component configs, will be copied to the output objects / instances with the sane membership structure.

Conclusion

Using JUL you can control many aspect of the configuration trees and their instantiation into components. This applies to the UI creation tasks, but not only to those. In fact, JUL aims to be a base for the rapid application development of a web application.
The philosophy that lies behind JUL and its tool set is that one should not pay to be able to express his or her positive creativity.
The platform that offers a free and public access to such a development is the JavaScript language. That, together with its client and server utilities, is a truly universal programming language, surpassing the obstacles faced by other free languages when they built upon a proprietary code.

Digg ThisShare via emailShare on MyspaceSubmit to redditSubmit to StumbleUpon

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Copyright © 2012 - ZB The Zonebuilder
Frontier Theme