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.