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
// 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
(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
// 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.










Constructorul Zonal