JUL – Limbajul UI JavaScript

V-ați dorit vreodată să construiți o aplicație web deosebită? Și apoi ați căutat framework-ul JavaScript potrivit pentru a vă ușura munca?
Când alegeți un framework puternic pentru dezvoltare, sunt câteva lucruri ce trebuie învățate. Acesta are un mod specific de a inițializa aplicația, are propriul mod de a agrega elementele interfeței utilizator, are propriul mod de a atașa logica interfeței inclusiv listener-ele pe evenimente. După ce stăpânim acești pași observăm că am că am consumat jumătate din timpul alocat dezvoltării aplicației. Și nu am rezolvat încă aspectul reutilizării codului. Și în fine dar nu mai puțin important, framework-ul JavaScript cel mai potrivit pentru a rezolva toate acestea poate că nu e așa de ieftin.

Ce-ați zice dacă v-aș spune că tot ceea ce aveți nevoie pentru aplicația dvs. este să scrieți un fișier de configurare? Veți spune că sunt framework-uri care permit asta prin generarea unui fișier XML. Dar ceea ce urmează să vă prezint se aplică oricărui framework, vechi sau nou, și e scris în întregime în JavaScript.

Faceți cunoștință cu JUL – Limbajul UI JavaScript!

Exemplu JUL + SmartClient

 Exemplu JUL + SmartClientCod sursă:

// instanța aplicației
APP = {
	version: '1.0',
	parser: new JUL.UI.Parser({
		// SmartClient folosește 'items ca proprietate ''children' a containerului
		childrenProperty: 'items',
		// alte proprietăți ce necesită instanțiera ca și componente
		membersProperties: ['members', 'tabs', 'pane', 'dataSource'],
		// în SmartClient, ID-urile sunt automat expuse ca global
i		idProperty: 'ID',
		// fabricant simplu pentru apelarea utilitarului 'create' atunci când e specificată clasa componentei, 
		// sau pentru returnarea obiectului de configurare în caz contrar
		customFactory: function(oConfig) {
			if (oConfig.xclass === 'Object') { return oConfig; }
			else { return isc[oConfig.xclass].create(oConfig); }
		}
	}),
	// metodă de inițializare ce creează UI-ul
	init: function() {
		this.parser.create(this.ui, this.logic);
	}
};
// arbore de configurare a structurii pentru întreaga aplicație
APP.ui = {
	xclass: 'Dialog',
	ID: 'mainDialog',
	title:"JUL + SmartClient Dialog",
    showShadow:true,
	autoSize: true,
	autoDraw: true,
	buttons: [isc.Dialog.OK, isc.Dialog.CANCEL],
	items: [
		{xclass: 'TabSet', width: 680, height: 350, selectedTab: 0, tabs: [
			{title: 'TreeGrid', pane: {
				xclass: 'TreeGrid',
				ID: 'supplyTree',
    			animateFolders: true,
		    	selectionType: 'single'
			}},
			{title: 'ListGrid', pane: {
				xclass: 'ListGrid',
				ID: 'supplyList',
				alternateRecordStyles: true,
		    	selectionType: 'single'
			}},
			{title: 'DynamicForm', pane: {
				xclass: 'DynamicForm',
				ID: 'supplyForm',
				numCols: 4,
				colWidths: [100, 200, 100, 200],
			    margin: 25,
				cellPadding: 5,
				autoFocus: false 				
			}}
		]}
 	]
};
// logica interfeței legată prin ID-urile componentelor
APP.logic = {
	mainDialog: {
		okClick: function() {
			alert('Please fill the required info!');
		},
		cancelClick: function() {
			alert('Won\'t close for now.');
		},
		onCloseClick: function() {
			return false;
		}
	},
	supplyTree: {
			autoFetchData: true,
			loadDataOnDemand: false,
		dataSource: {
			xclass: 'DataSource',
			ID: 'supplyCategory',
			clientOnly: true,
			dataURL: '../data/supplyCategory.data.xml',
			recordXPath: '//supplyCategory',
			fields: [
				{name: 'categoryName', title: 'Item', type: 'text', length: 128, required: true, primaryKey: true},
				{name: 'parentID', hidden: true, type: 'text', required: true, foreignKey: 'supplyCategory.categoryName', rootValue: 'root'}
			]
		},
		nodeClick: function(viewer, node, recordNum) {
			supplyList.filterData({category: node.categoryName});
		}
	},
	supplyList: {
		autoFetchData: true,
		showAllRecords: true,
		dataSource: {
			xclass: 'DataSource',
			ID: 'supplyItem',
			clientOnly: true,
			dataURL: '../data/supplyItem.data.xml',
			recordXPath: '//supplyItem',
		fields: [
				{name: 'itemID', type: 'sequence', hidden: true, primaryKey: true},
				{name: 'itemName', type: 'text', title: 'Item', length: 128, required: true},
				{name: 'SKU', type: 'text', title: 'SKU', length: 10, required: true},
				{name: 'description', type: 'text', title: 'Description', length: 2000},
				{name: 'category', type: 'text', title: 'Category', length: 128, required: true, foreignKey: 'supplyCategory.categoryName'},
				{name: 'units', type: 'enum', title: 'Units', length: 5, valueMap: [
					'Roll', 'Ea', 'Pkt', 'Set', 'Tube', 'Pad', 'Ream', 'Tin', 'Bag', 'Ctn', 'Box'
				]},
				{name: 'unitCost', type: 'float', title: 'Unit Cost', required: true, validators: [
					{type: 'floatRange', min: 0, errorMessage: 'Please enter a valid (positive) cost'},
					{type: 'floatPrecision', precision: 2, errorMessage: 'The maximum allowed precision is 2'}
				]},
				{name: 'inStock', type: 'boolean', title: 'In Stock'},
				{name: 'nextShipment', type: 'date', title: 'Next Shipment'}
			]
		},
		recordClick: function(viewer, record, recordNum, field, fieldNum, value, rawValue) {
			supplyForm.setValues(record);
		}
	},
	supplyForm: {
		dataSource: 'supplyItem',
		useAllDataSourceFields: true
	}
};
// desenare automată a structurii dacă este specificat 'autoDraw'
isc.setAutoDraw(false);
// pornire aplicație
APP.init();

Exemplu JUL + YUI

 Exemplu JUL + YUICod sursă:

(function() {
// configurare a lui YUI Loader pentru a încărca dependențele de la CDN
var oLoader = new YAHOO.util.YUILoader({
    base: 'https://ajax.googleapis.com/ajax/libs/yui/2.9.0/build/',
	require: ['reset-fonts', 'container', 'button', 'tabview', 'datatable', 'editor', 'calendar'],
    loadOptional: true,
    // întreaga încărcare a metodei APP.init va fi finalizată
	onSuccess: function() { APP.init(); }
});
// instanță aplicație
APP = {
	version: '1.0',
	parser: new JUL.UI.Parser({
		// în afară de 'children', și toate aceste proprietăți sunt tratare ca vectori de configurații componente
		membersProperties: ['datasource', 'paginator'],
		// YUI necesită un fabricant puternic particularizat pentru a instanția un arbore de configurații componente
		customFactory: function(oConfig) {
			var oComponent = null;
			// constructorii YUI au un număr variabil de argumente
			// anumite componente necesită inițializarea body pentru randarea completă în faza de inițializare
			switch (oConfig.xclass) {
			case 'Dialog':
				oComponent = new YAHOO.widget.Dialog(oConfig.id, oConfig);
				oComponent.setHeader(oConfig.header || '');
				oComponent.setBody(oConfig.body || '');
				oComponent.render();
			break;
			case 'DataTable':
				oComponent = new YAHOO.widget.DataTable(oConfig.id, oConfig.columns, oConfig.datasource, oConfig);
			break;
			case 'DataSource':
				oComponent = new YAHOO.util.DataSource(oConfig.url, oConfig);
				oComponent.responseType = YAHOO.util.DataSource.TYPE_JSON;
			break;
			case 'Editor':
				oComponent = new YAHOO.widget.Editor(oConfig.id, oConfig);
				oComponent.render();
			break;
			case 'Calendar':
				oComponent = new YAHOO.widget.Calendar(oConfig.id, oConfig.container, oConfig);
				oComponent.render();
			break;
			default:
				oComponent = new YAHOO.widget[oConfig.xclass](oConfig);
			}
			// această parte adaugă instanțe copii la instanța părinte,
			// atunci când această acțiune nu e declanșată de constrictorul YUI
			if (oConfig.children) {
				var aChildren = [].concat(oConfig.children);
				for (var i = 0; i < aChildren.length; i++) {
					switch (oConfig.xclass) {
					case 'Dialog':
						aChildren[i].appendTo(oComponent.form);
					break;
					case 'TabView':
						oComponent.addTab(aChildren[i]);
					break;
					case 'Tab':
						if (aChildren[i].oDomContainer) {
							oComponent.get('contentEl').appendChild(aChildren[i].oDomContainer);
						}
						else {
							aChildren[i].appendTo(oComponent.get('contentEl'));
						}
					break;
					default:
						aChildren[i].appendTo(oComponent);
					}
				}
			}
			// aici tratăm listener-i la ierarhia de elemente
			if (oConfig.listeners) {
				for (var sItem in oConfig.listeners) {
					oComponent.subscribe(sItem, oConfig.listeners[sItem], oComponent);
				}
			}
// acestea sunt Custom Events care au o sintaxă de interceptare diferită
			if (oConfig.events) {
				for (sItem in oConfig.events) {
					oComponent[sItem].subscribe(oConfig.events[sItem], oComponent);
				}
			}
			return oComponent;
		}
	}),
	// această metodă creează arborele UI
	init: function() {
		var oMain = this.parser.create(this.ui, this.logic);
		oMain.show();
	}
};
// arborele de configurare a structurii pentru întreaga aplicație
APP.ui = {
	xclass: 'Dialog',
	id: 'dialog-main',
	header: 'JUL + YUI Dialog',
	width: '700px',
	height: '450px',
	fixedcenter: true,
	postmethod: 'manual',
	children: [
		{xclass: 'TabView', children: [
			{xclass: 'Tab', label: 'DataTable', active: true, children: [
				{xclass: 'DataTable', id: 'datatable-json', columns: [
					{key: 'id', label: 'ID', sortable: true},
					{key: 'name', label: 'Name', width: 272, sortable: true},
					{key: 'date', label: 'Date', width: 110, sortable: true, formatter: 'date'},
					{key: 'price', label: 'Price', width: 90, sortable: true, formatter: 'currency'},
					{key: 'number', label: 'Number', width: 72, sortable: true, formatter: 'number'}
				], paginator: {
					xclass: 'Paginator', rowsPerPage: 10
				}}
			]},
			{xclass: 'Tab', label: 'Editor', children: [
				{xclass: 'Editor', id: 'editor-test', width: '670px', height: '188px', animate: true, dompath: true}
			]},
			{xclass: 'Tab', label: 'Calendar', children: [
				{xclass: 'Calendar', id: 'APP.calendar', container: 'calendar-container'},
				{xclass: 'Button', id: 'button-test', label: 'Show selected date'}
			]}
		]}
	]
};
// logica interfeței legată prin ID-urile componentelor
APP.logic = {
	'dialog-main': {
		buttons: [
			{text: 'OK', isDefault: true, handler: function() { this.submit(); }},
			{text: 'Cancel', handler: function() { this.hide(); }}
		],
		events: {
			beforeHideEvent: function() {
				alert('Won\'t close for now.');
				return false;
			},
			beforeSubmitEvent: function() {
				alert('Please fill the required info!');
				return false;
			}
		}
	},
	'datatable-json': {
		datasource: {
			xclass: 'DataSource',
			url: '../data/data.json',
			responseSchema: {resultsList: 'records', fields: [
				{key: 'id', parser: 'number'}, 'name', {key: 'date', parser: 'date'},
				{key: 'price', parser: 'number'}, {key: 'number', parser: 'number'}
			]}
		},
		initialRequest: '?nc=' + (new Date()).getTime()
	},
	'button-test': {
		listeners: {
			click: function() {
				var aDates = APP.calendar.getSelectedDates();
				if (aDates.length) {
					alert(aDates[0].toLocaleString());
				}
				else {
					alert('Nothing selected!');
				}
			}
		}
	}
};
 // apelăm încărcătorul pentru a efectua sarcinile de încărcare a dependențelor și de pornite a aplicației
 oLoader.insert();
})();

Exemplu JUL + AmpleSDK

 Exemplu JUL + AmpleSDKCod sursă:

// definire spațiu de nume aplicație
JUL.ns('APP.ui');
JUL.apply(APP, {
	version: '1.0'
});

// construire arbore de configurare pentru dialogul principal XUL
APP.ui.components = {
	tag: 'dialog',
	// un ID punctat înseamnă publicarea componentei în spațiul global
	id: 'APP.mainWindow',
	title: 'JUL + Ample SDK Dialog',
	css: 'main-dialog',
	width: 610,
	height: 333,
	buttons: 'accept,cancel',
	context: 'none',
	hidden: true,
	commandset: [
		{tag: 'command', id: 'cmd-more-info', label: 'More info ...'}
	],
	children: [
		{tag: 'tabbox', selectedIndex: 0, tabs: [
			{tag: 'tab', label: 'XUL+SVG'},
			{tag: 'tab', label: 'Chart'},
			{tag: 'tab', label: 'Custom tab'}
		]}
	]
};

// pregătire vector de articole pentru configurația listbox-ului
var aItems = [];
	for (var n = 1; n < 11; n++) {
		aItems.push({tag: 'listitem', children: [
			{tag: 'listcell', label: 'First ' + n},
			{tag: 'listcell', label: 'Last ' + n}
		]});
	}

// adăugare toate configurațiile tabpanel
APP.ui.components.children[0].tabpanels = [{
	tag: 'tabpanel',
	orient: 'vertical',
	// utilizare formă compactă pentru un arbore de configurare DOM - în care componente container pot fi proprietăți 'membri'
	hbox: [
		{tag: 'image', src: '../media/shield.jpg'},
		{tag: 'description', value: 'Register online!', style: 'font-weight:bold;padding:15px', flex: 1},
		// container pentru arborele SVG
		{xclass:'html', tag: 'div', id: 'APP.svgBox'}
	],
	// utilizare proprietate 'membri' implicită adică 'children'
	children: [
		{tag: 'listbox', id: 'list-names', seltype: 'single', height: 99, listhead: [
			{tag: 'listheader', label: 'First Name', width: 200, tooltiptext: 'First Name'},
			{tag: 'listheader', label: 'Last Name', width: 200, tooltiptext: 'Last Name'}
		],
		// atribuire a listbody cu vectorul construit anterior
		listbody: aItems
		}
	],
	groupbox: [
		{tag: 'caption', label: 'Your information'},
		{tag: 'vbox', children: [
			{tag: 'hbox', children: [
				{tag: 'label', control:'register-firstname', value: 'Enter your first name', flex: 1},
				{tag: 'textbox', id: 'register-firstname', flex: 1}
			]},
			{tag: 'hbox', children: [
				{tag: 'label', control: 'register-lastname', value: 'Enter your last name', flex: 1},
				{tag: 'textbox', id: 'register-lastname', flex: 1}
			]},
			{tag: 'hbox', children: [
				{tag: 'button', command: 'cmd-more-info', flex: 1}
			]}
		]}
	]
}, {
	tag: 'tabpanel',
	orient: 'vertical',
	children: [
		// container pentru arborele Ample Chart
		{xclass:'html', tag: 'div', id: 'APP.chartBox'}
	]
}, {
	tag: 'tabpanel',
	// includere configurație specificată prin calea sa spațiu de nume
	include: 'APP.ui.customTab',
	// proprietățile de mai jos suprascriu pe cele incluse
	id: 'custom-tab',
	css: 'custom-class',
	orient: 'vertical'
}];

// creare obiect de configurare folosit drept configurație inclusă
APP.ui.customTab = {
	tag: 'tabpanel',
	children: [
		// altă includere
		{tag: 'description', include: 'APP.ui.copy'}
	],
	groupbox: [
		{tag: 'caption', label:'Content'},
		{include: 'APP.ui.hbox'},
		// proprietatea de legătură 'cid' este folosit pentru a lega UI-ul cu logica de configurare
		{tag: 'button', cid: 'Button.test', label: 'Test me!'}
	]
};

// aceasta este o logică de configurare - a se vedea corespondența cu proprietatea 'Button.test'
APP.test = {
	'Button.test': {
		listeners: {
		// fabricantul de creare DOM acceptă și listener-i căi spațiu de nume
		click: 'APP.doTest'
	}}
};

// definire listener pentru butonul 'Test me!'
APP.doTest = function() {
	alert('This is a test!');
};

// alt membru folosit ca și configurație inclusă
APP.ui.hbox = {
	tag: 'hbox',
	children: [
		{tag: 'label', value: 'Left line', flex: 1},
			{tag: 'label', value: 'Right line', flex: 1}
	]
};

// testare entități HTML
APP.ui.copy = {html: '© Copyright Note'};

// aceasta este logica de configurare pentru UI-ul aplicației - a se vedea corespondența între proprietățile sale și ID-urile componentelor
APP.ui.bindings = {
	// suportă de asemenea includerea unui vector de logici de configurare
	include: ['APP.test'],
	'APP.mainWindow': {listeners: {
		// listener-i inline pentru dialogul principal
		dialogaccept: function() {
			alert('Please fill the required info!');
			return false;
		},
		dialogcancel: function() {
			alert("Won't close for now.");
			return false;
		}
	}},
	// listener pentru elementul de comandă XUL
	'cmd-more-info': {listeners: {
		command: function() {
			alert('JUL example with different DOM component trees (XUL, SVG, Ample Chart)');
		}
	}}
};

// arbore de configurare SVG
APP.ui.svg = {
	tag: 'svg',
	width: '100px',
	height: '100px',
	viewBox: '0 0 100 100',
	g: [
		{tag: 'circle', cx: 50, cy: 50, r: 48, fill: 'none', stroke: '#000'},
			{tag: 'path', d: 'M50,2a48,48 0 1 1 0,96a24 24 0 1 1 0-48a24 24 0 1 0 0-48'},
			{tag: 'circle', cx: 50, cy: 26, r: 6},
			{tag: 'circle', cx: 50, cy: 74, r: 6, fill: '#FFF'}
		]
};

// arbore de configurare Ample Chart
APP.ui.chart = {
	tag: 'bar', title: 'Column chart', orient: 'vertical',
		xAxisLabel: 'X Axis', yAxisLabel: 'Y Label',
		xAxisValueLabels: '1999,2000,2001,2002,2003', children: [
		{tag: 'group', label: 'Set 0', children: [
			{tag: 'item', value: 10},
			{tag: 'item', value: 20},
			{tag: 'item', value: 30},
			{tag: 'item', value: 40},
			{tag: 'item', value: 50}
		]},
		{tag: 'group', label: 'Set 1', children: [
			{tag: 'item', value: 20},
			{tag: 'item', value: 10},
			{tag: 'item', value: 25},
			{tag: 'item', value: 45},
			{tag: 'item', value: 15}
		]},
		{tag: 'group', label: 'Set 2', children: [
			{tag: 'item', value: 30},
			{tag: 'item', value: 30},
			{tag: 'item', value: 5},
			{tag: 'item', value: 10},
			{tag: 'item', value: 40}
		]},
		{tag: 'group', label: 'Set 3', children: [
			{tag: 'item', value: 15},
			{tag: 'item', value: 25},
			{tag: 'item', value: 35},
			{tag: 'item', value: 30},
			{tag: 'item', value: 10}
		]}
	]
};

// schimbarea valorilor predefinite va fi moștenită de noii parser-i - toți vor trata configurațiile drept configurații elemente DOM
JUL.UI.useTags = true;
// așteptare ca Ample să fie gata
ample.ready(function() {
	// parser nou pentru arborele XUL
	var oParser = new JUL.UI.Parser({
		// fabrica 'JUL.UI.createDom' drept fabricant personalizat
		customFactory: JUL.UI.createDom,
		// toate elementele sunt create cu spațiu de nume XUL
		defaultClass: 'xul',
		// toate aceste proprietăți sunt tratate drept configurații container adică parsate pentru configurații membru
		membersProperties: ['commandset', 'hbox', 'vbox',
			'groupbox', 'tabs', 'tabpanels', 'listhead', 'listbody'],
		// utilizare instanțiere de sus în jos în loc de cea implicită de jos în sus
		topDown: true
	});
	// creare arbore de componente cu logică de legătură, rădăcina va fi publicată ca 'APP.mainWindow'
	// la instanțierea de sus în jos,  APP.ui.components trebuie să fie normalizat (expandat) 
	// pentru ca 'JUL.UI.createDom' să creeze componente container pentru membrii compactați
	oParser.create(oParser.expand(APP.ui.components), APP.ui.bindings);
	// adăugarea elementului DOM la elementul document Ample
	ample.documentElement.appendChild(APP.mainWindow);
	// parser nou pentru arborele SVG
	var oSvgParser = new JUL.UI.Parser({
		customFactory: 'JUL.UI.createDom',
		defaultClass: 'svg',
		membersProperties: ['g']
	});
	var oSvg = oSvgParser.create(APP.ui.svg);
	// elementul 'APP.svgBox' a fost publicat la crearea dialogului
	APP.svgBox.appendChild(oSvg);
	// parser nou pentru arborele Ample Chart
	var oChartParser = new JUL.UI.Parser({
		customFactory: JUL.UI.createDom,
		defaultClass: 'chart'
	});
	var oChart = oChartParser.create(APP.ui.chart);
	// elementul 'APP.chartBox' a fost publicat la crearea dialogului
	APP.chartBox.appendChild(oChart);
	// afișare dialogul principal
	APP.mainWindow.show();
});

Acestea au fost câteva exemple despre utilizarea JOUL. Dar se pot face și alte lucruri, așa cum se va vedea în postările viitoare.
Dacă sunteți interesat, vizitați vă rog pagina projectului pe Sourceforge.

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 Constructorul Zonal
Frontier Theme