【翻译】Ext JS 5:为不同设备设置不同的主题
最后更新于:2022-04-01 07:23:32
原文:[Sencha Ext JS 5: Supporting Different Themes for Different Devices](https://druckit.wordpress.com/category/ext-js-5/)
- [步骤1创建一个应用程序](#)
- [步骤2定义主题](#)
- [步骤3编辑Appjson文件以便支持多平台生成](#)
- [步骤4编辑output定义以便创建多个应用程序的manifests](#)
- [步骤5更新应用程序](#)
- [步骤6替换Appjson中的CSS配置](#)
- [步骤7替换bootstrap属性以便加载appropriate manifest文件](#)
- [步骤8在indexhtml文件中在微加载之上添加以下代码到一个script标记中以加载appropriate manifest](#)
- [步骤9生成应用程序](#)
- [步骤10在桌面或移动设备浏览器上测试应用程序](#)
Sencha Ext JS 5是第一个完全支持iOS平板的Ext框架。
为应用程序添加平板支持,并能根据使用的设备自动切换桌面或基于触碰的主题(CSS文件)可能是相当重要的任务。
本教程将演示如何将该功能添加到应用程序。
# 步骤1:创建一个应用程序
1. 在Ext JS 5文件夹打开命令行提示符
1. 运行以下命令:
**sencha generate app TestApp ../TestApp**
# 步骤2:定义主题
1. 在命令行提示符切换到TestApp目录
1. 运行以下命令
1. sencha generate theme TestApp-Desktop(注:为桌面创建主题)
1. sencha generate theme TestApp-Tablet(注:为平板创建主题)
1. 在编辑器打开 /TestApp/packages/TestApp-Desktop/package.json
1. 修改“extend”属性为“extend ext-theme-neptune”
1. 保存文件
1. 在编辑器打开/TestApp/packages/TestApp-Tablet/package.json
1. 将“extend”属性从ext-theme-classic修改ext-theme-neptune-touch
# 步骤3:编辑App.json文件以便支持多平台生成
1. 在编辑器打开 /TestApp/app.json
1. 添加“builds”属性作为指示:
~~~
"builds": {
"testAppDesktop": {
"theme": "TestApp-Desktop"
},
"testAppTouch": {
"theme": "TestApp-Tablet"
}
}
~~~
# 步骤4:编辑output定义以便创建多个应用程序的manifests
使用以下代码替换app.json中的output配置:
~~~
"output": {
"base": "${workspace.build.dir}/${build.environment}/${app.name}/${build.id}",
"page": "./index.html",
"manifest": "../${build.id}.json",
"deltas": {
"enable": false
},
"cache": {
"enable": false
}
}
~~~
# 步骤5:更新应用程序
返回命令行提示符,输入以下命令:
**sencha app refresh**
这将生产manifest文件:testAppDesktop.json和testAppTouch.json
# 步骤6:替换App.json中的CSS配置
使用以下代码替换App.json中的css配置:
~~~
"css": [{
"path": "${build.out.css.dir}/TestApp-all.css",
"bootstrap": true
}]
~~~
# 步骤7:替换bootstrap属性以便加载appropriate manifest文件
~~~
"bootstrap": {
"manifest": "${build.id}.json"
}
~~~
# 步骤8:在index.html文件中,在微加载之上,添加以下代码到一个script标记中,以加载appropriate manifest:
~~~
var Ext = Ext || {};
Ext.beforeLoad = function(tags){
var theme = location.href.match(/theme=([\w-]+)/);
theme = (theme && theme[1]) || (tags.desktop ? 'testAppDesktop' : 'testAppTouch');
Ext.manifest = theme;
tags.test = /testMode=true/.test(location.search);
Ext.microloaderTags = tags;
};
~~~
# 步骤9:生成应用程序
返回命令行提示符并输入以下命令:
**sencha app build development**
# 步骤10:在桌面或移动设备浏览器上测试应用程序
【翻译】将Ext JS Grid转换为Excel表格
最后更新于:2022-04-01 07:23:30
原文:[Converting an Ext 5 Grid to Excel Spreadsheet](https://druckit.wordpress.com/2014/12/26/converting-an-ext-5-grid-to-excel-spreadsheet/)
稍微迟来的礼物——Ext JS Grid转为Excel代码,现在支持Ext JS 5!
功能包括:
- 支持分组
- 数字的处理 VS 字符串数据类型
- 对于不支持客户端下载的浏览器会提交回服务器
Enjoy!
~~~
/*
Excel.js - convert an ExtJS 5 grid into an Excel spreadsheet using nothing but
javascript and good intentions.
By: Steve Drucker
Dec 26, 2014
Original Ext 3 Implementation by: Nige "Animal" White?
Contact Info:
e. sdrucker@figleaf.com
blog: druckit.wordpress.com
linkedin: www.linkedin.com/in/uberfig
git: http://github.com/sdruckerfig
company: Fig Leaf Software (http://www.figleaf.com / http://training.figleaf.com)
Invocation: grid.downloadExcelXml(includeHiddenColumns,title)
Upgraded for ExtJS5 on Dec 26, 2014
*/
var Base64 = (function() {
// Private property
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// Private method for UTF-8 encoding
function utf8Encode(string) {
string = string.replace(/\r\n/g, "\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
// Public method for encoding
return {
encode: (typeof btoa == 'function') ? function(input) {
return btoa(utf8Encode(input));
} : function(input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = utf8Encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
}
return output;
}
};
})();
Ext.define('MyApp.overrides.view.Grid', {
override: 'Ext.grid.GridPanel',
requires: 'Ext.form.action.StandardSubmit',
/*
Kick off process
*/
downloadExcelXml: function(includeHidden, title) {
if (!title) title = this.title;
var vExportContent = this.getExcelXml(includeHidden, title);
/*
dynamically create and anchor tag to force download with suggested filename
note: download attribute is Google Chrome specific
*/
if (Ext.isChrome) {
var gridEl = this.getEl();
var location = 'data:application/vnd.ms-excel;base64,' + Base64.encode(vExportContent);
var el = Ext.DomHelper.append(gridEl, {
tag: "a",
download: title + "-" + Ext.Date.format(new Date(), 'Y-m-d Hi') + '.xls',
href: location
});
el.click();
Ext.fly(el).destroy();
} else {
var form = this.down('form#uploadForm');
if (form) {
form.destroy();
}
form = this.add({
xtype: 'form',
itemId: 'uploadForm',
hidden: true,
standardSubmit: true,
url: 'http://webapps.figleaf.com/dataservices/Excel.cfc?method=echo&mimetype=application/vnd.ms-excel&filename=' + escape(title + ".xls"),
items: [{
xtype: 'hiddenfield',
name: 'data',
value: vExportContent
}]
});
form.getForm().submit();
}
},
/*
Welcome to XML Hell
See: http://msdn.microsoft.com/en-us/library/office/aa140066(v=office.10).aspx
for more details
*/
getExcelXml: function(includeHidden, title) {
var theTitle = title || this.title;
var worksheet = this.createWorksheet(includeHidden, theTitle);
if (this.columnManager.columns) {
var totalWidth = this.columnManager.columns.length;
} else {
var totalWidth = this.columns.length;
}
return ''.concat(
'<?xml version="1.0"?>',
'<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">',
'<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Title>' + theTitle + '</Title></DocumentProperties>',
'<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"><AllowPNG/></OfficeDocumentSettings>',
'<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">',
'<WindowHeight>' + worksheet.height + '</WindowHeight>',
'<WindowWidth>' + worksheet.width + '</WindowWidth>',
'<ProtectStructure>False</ProtectStructure>',
'<ProtectWindows>False</ProtectWindows>',
'</ExcelWorkbook>',
'<Styles>',
'<Style ss:ID="Default" ss:Name="Normal">',
'<Alignment ss:Vertical="Bottom"/>',
'<Borders/>',
'<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"/>',
'<Interior/>',
'<NumberFormat/>',
'<Protection/>',
'</Style>',
'<Style ss:ID="title">',
'<Borders />',
'<Font ss:Bold="1" ss:Size="18" />',
'<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1" />',
'<NumberFormat ss:Format="@" />',
'</Style>',
'<Style ss:ID="headercell">',
'<Font ss:Bold="1" ss:Size="10" />',
'<Alignment ss:Horizontal="Center" ss:WrapText="1" />',
'<Interior ss:Color="#A3C9F1" ss:Pattern="Solid" />',
'</Style>',
'<Style ss:ID="even">',
'<Interior ss:Color="#CCFFFF" ss:Pattern="Solid" />',
'</Style>',
'<Style ss:ID="evendate" ss:Parent="even">',
'<NumberFormat ss:Format="yyyy-mm-dd" />',
'</Style>',
'<Style ss:ID="evenint" ss:Parent="even">',
'<Numberformat ss:Format="0" />',
'</Style>',
'<Style ss:ID="evenfloat" ss:Parent="even">',
'<Numberformat ss:Format="0.00" />',
'</Style>',
'<Style ss:ID="odd">',
'<Interior ss:Color="#CCCCFF" ss:Pattern="Solid" />',
'</Style>',
'<Style ss:ID="groupSeparator">',
'<Interior ss:Color="#D3D3D3" ss:Pattern="Solid" />',
'</Style>',
'<Style ss:ID="odddate" ss:Parent="odd">',
'<NumberFormat ss:Format="yyyy-mm-dd" />',
'</Style>',
'<Style ss:ID="oddint" ss:Parent="odd">',
'<NumberFormat Format="0" />',
'</Style>',
'<Style ss:ID="oddfloat" ss:Parent="odd">',
'<NumberFormat Format="0.00" />',
'</Style>',
'</Styles>',
worksheet.xml,
'</Workbook>'
);
},
/*
Support function to return field info from store based on fieldname
*/
getModelField: function(fieldName) {
var fields = this.store.model.getFields();
for (var i = 0; i < fields.length; i++) {
if (fields[i].name === fieldName) {
return fields[i];
}
}
},
/*
Convert store into Excel Worksheet
*/
generateEmptyGroupRow: function(dataIndex, value, cellTypes, includeHidden) {
var cm = this.columnManager.columns;
var colCount = cm.length;
var rowTpl = '<Row ss:AutoFitHeight="0"><Cell ss:StyleID="groupSeparator" ss:MergeAcross="{0}"><Data ss:Type="String"><html:b>{1}</html:b></Data></Cell></Row>';
var visibleCols = 0;
// rowXml += '<Cell ss:StyleID="groupSeparator">'
for (var j = 0; j < colCount; j++) {
if (cm[j].xtype != 'actioncolumn' && (cm[j].dataIndex != '') && (includeHidden || !cm[j].hidden)) {
// rowXml += '<Cell ss:StyleID="groupSeparator"/>';
visibleCols++;
}
}
// rowXml += "</Row>";
return Ext.String.format(rowTpl, visibleCols - 1, Ext.String.htmlEncode(value));
},
createWorksheet: function(includeHidden, theTitle) {
// Calculate cell data types and extra class names which affect formatting
var cellType = [];
var cellTypeClass = [];
console.log(this);
if (this.columnManager.columns) {
var cm = this.columnManager.columns;
} else {
var cm = this.columns;
}
console.log(cm);
var colCount = cm.length;
var totalWidthInPixels = 0;
var colXml = '';
var headerXml = '';
var visibleColumnCountReduction = 0;
for (var i = 0; i < cm.length; i++) {
if (cm[i].xtype != 'actioncolumn' && (cm[i].dataIndex != '') && (includeHidden || !cm[i].hidden)) {
var w = cm[i].getEl().getWidth();
totalWidthInPixels += w;
if (cm[i].text === "") {
cellType.push("None");
cellTypeClass.push("");
++visibleColumnCountReduction;
} else {
colXml += '<Column ss:AutoFitWidth="1" ss:Width="' + w + '" />';
headerXml += '<Cell ss:StyleID="headercell">' +
'<Data ss:Type="String">' + cm[i].text.replace("<br>"," ") + '</Data>' +
'<NamedCell ss:Name="Print_Titles"></NamedCell></Cell>';
var fld = this.getModelField(cm[i].dataIndex);
switch (fld.$className) {
case "Ext.data.field.Integer":
cellType.push("Number");
cellTypeClass.push("int");
break;
case "Ext.data.field.Number":
cellType.push("Number");
cellTypeClass.push("float");
break;
case "Ext.data.field.Boolean":
cellType.push("String");
cellTypeClass.push("");
break;
case "Ext.data.field.Date":
cellType.push("DateTime");
cellTypeClass.push("date");
break;
default:
cellType.push("String");
cellTypeClass.push("");
break;
}
}
}
}
var visibleColumnCount = cellType.length - visibleColumnCountReduction;
var result = {
height: 9000,
width: Math.floor(totalWidthInPixels * 30) + 50
};
// Generate worksheet header details.
// determine number of rows
var numGridRows = this.store.getCount() + 2;
if ((this.store.groupField &&!Ext.isEmpty(this.store.groupField)) || (this.store.groupers && this.store.groupers.items.length > 0)) {
numGridRows = numGridRows + this.store.getGroups().length;
}
// create header for worksheet
var t = ''.concat(
'<Worksheet ss:Name="' + theTitle + '">',
'<Names>',
'<NamedRange ss:Name="Print_Titles" ss:RefersTo="=\'' + theTitle + '\'!R1:R2">',
'</NamedRange></Names>',
'<Table ss:ExpandedColumnCount="' + (visibleColumnCount + 2),
'" ss:ExpandedRowCount="' + numGridRows + '" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="15">',
colXml,
'<Row ss:Height="38">',
'<Cell ss:MergeAcross="' + (visibleColumnCount - 1) + '" ss:StyleID="title">',
'<Data ss:Type="String" xmlns:html="http://www.w3.org/TR/REC-html40">',
'<html:b>' + theTitle + '</html:b></Data><NamedCell ss:Name="Print_Titles">',
'</NamedCell></Cell>',
'</Row>',
'<Row ss:AutoFitHeight="1">',
headerXml +
'</Row>'
);
// Generate the data rows from the data in the Store
var groupVal = "";
var groupField = "";
if (this.store.groupers && this.store.groupers.keys.length > 0) {
groupField = this.store.groupers.keys[0];
} else if (this.store.groupField != '') {
groupField = this.store.groupField;
}
for (var i = 0, it = this.store.data.items, l = it.length; i < l; i++) {
if (!Ext.isEmpty(groupField)) {
if (groupVal != this.store.getAt(i).get(groupField)) {
groupVal = this.store.getAt(i).get(groupField);
t += this.generateEmptyGroupRow(groupField, groupVal, cellType, includeHidden);
}
}
t += '<Row>';
var cellClass = (i & 1) ? 'odd' : 'even';
r = it[i].data;
var k = 0;
for (var j = 0; j < colCount; j++) {
if (cm[j].xtype != 'actioncolumn' && (cm[j].dataIndex != '') && (includeHidden || !cm[j].hidden)) {
var v = r[cm[j].dataIndex];
if (cellType[k] !== "None") {
t += '<Cell ss:StyleID="' + cellClass + cellTypeClass[k] + '"><Data ss:Type="' + cellType[k] + '">';
if (cellType[k] == 'DateTime') {
t += Ext.Date.format(v, 'Y-m-d');
} else {
t += Ext.String.htmlEncode(v);
}
t += '</Data></Cell>';
}
k++;
}
}
t += '</Row>';
}
result.xml = t.concat(
'</Table>',
'<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">',
'<PageLayoutZoom>0</PageLayoutZoom>',
'<Selected/>',
'<Panes>',
'<Pane>',
'<Number>3</Number>',
'<ActiveRow>2</ActiveRow>',
'</Pane>',
'</Panes>',
'<ProtectObjects>False</ProtectObjects>',
'<ProtectScenarios>False</ProtectScenarios>',
'</WorksheetOptions>',
'</Worksheet>'
);
return result;
}
});
~~~
附:在原文底部有Ext JS 4版本的链接
【翻译】从Store生成Checkbox Group
最后更新于:2022-04-01 07:23:28
原文:[Ext JS: Generating a Checkbox Group from a Store](https://druckit.wordpress.com/2015/02/12/ext-js-generating-a-checkbox-group-from-a-store/)
Ext JS的checkbox group可以用来将复选框组合成一个单一的逻辑字段。由于复选框时不时需要动态的从Store中生成,因而,如果将store绑定到扩展类,就最好不过了。以下是第一次尝试:
~~~
Ext.define('Ext.ux.CheckboxStoreGroup', {
extend: 'Ext.form.CheckboxGroup',
alias: 'widget.checkboxstoregroup',
config: {
store: null,
labelField: 'label',
valueField: 'id',
checkedField: 'checked',
columns: 3,
boxFieldName: 'mycheckbox'
},
applyStore: function(store) {
if (Ext.isString(store)) {
return Ext.getStore(store);
} else {
return store;
}
},
updateStore: function(newStore, oldStore) {
if (oldStore) {
store.removeEventListener('datachanged', this.onStoreChange, this)
}
newStore.on('datachanged', this.onStoreChange, this);
},
onStoreChange: function(s) {
Ext.suspendLayouts();
this.removeAll();
var vField = this.getValueField();
var lField = this.getLabelField();
var cField = this.getCheckedField();
var fName = this.getBoxFieldName();
var rec = null;
for (var i=0; i<s.getCount(); i++) {
rec = s.getAt(i);
this.add({
xtype: 'checkbox',
inputValue: rec.get(vField),
boxLabel: rec.get(lField),
checked: rec.get(cField),
name: fName
});
}
Ext.resumeLayouts(true);
},
initComponent: function() {
this.callParent(arguments);
this.on('afterrender', this.onAfterRender);
},
onAfterRender: function() {
if (this.getStore().totalCount) {
this.onStoreChange(this.getStore);
}
}
});
~~~
测试地址:
[https://fiddle.sencha.com/#fiddle/i51](https://fiddle.sencha.com/#fiddle/i51)
【翻译】培训提示:解决常见编码问题的简单技巧
最后更新于:2022-04-01 07:23:26
原文:[Training Tip: Simple Techniques for Solving Common Coding Problems](http://www.sencha.com/blog/training-tip-simple-techniques-for-solving-common-coding-problems/)
很多时候,在我教授一个Sencha培训课程的时候,学生经常会请求我帮忙看一下他们的应用程序,因为有些问题他们不知道如何去解决。由于不是我写的代码,因而,有时很难快速的向他们给出答案。还好,我有一套简单的技术,可以用来筛选出最明显的问题。
在本文,我将对一些最常见的问题进行分类,并介绍一些简单而有效的策略来解决这些问题。
### 问题:我看不到我的数据
你正在浏览你的应用程序,单看不到任何数据。通常,该问题很容易解决。
### 试一下以下方法:
首先,先检查存储(Store),可以在应用程序运行时在浏览器控制台来做这个:
~~~
Ext.getStore('MyStore').load();
~~~
这将返回存储对象。可以通过data配置项来模拟一些数据,然后看看数组的长度是否大于0。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-01_56aeca7e2d5cb.jpg)
如果有可用数据,那么问题可能与渲染有关。考虑以下这些可能出现的问题:
没有将数据字段映射到模型的字段?
数据数组是否为空?在浏览器开发者工具栏,单击网络标签页。
状态码是否200?如果不是,那就是请求出了问题,要检查模型或存储的代理。
请求工作正常,但仍然没有数据显示?那就要确认返回的数据是否有效。例如,当返回的数据格式是JSON格式的时候,可以从浏览器的网络标签页中将返回的数据复制到http://jsonlint.com或http://jsonplint.com/来验证数据是否有效。还可以手动编写一些测试数据。
### 问题:不能生成应用程序
Sencha Cmd不能生成应用程序。大多数时候,Sencha Cmd会很清晰的描述这是怎么回事或者需要做什么改变。不过,无论是现在还是以后,都会碰到Sencha Cmd不能生成的问题,而且错误描述也不太清晰的情况。
这很有可能是代码存在错误。例如,代码可以很完美的在本地环境运行,但是不能生成。
### 试一下以下方法:
这个方法非常激进,但大多时候很有效。在命令行使用相同的命名空间创建一个新的应用程序:
~~~
sencha generate app App ../myapp
~~~
下一步,把app文件夹复制过来,并确保app.js也做了相应的修改。
现在,再试一下。
### 问题:奇怪的组件-x行为
这类型的问题一直很难去处理。
例如,在网格中不知道为什么显示了多个滚动条,又或者,在标签页面板中的显示样式是错误的。要在应用程序中解决这类问题非常的费时。这不但需要通过应用程序的导航来重现问题,还要知道造成这问题的包括哪些因素。
### 试一下以下方法:
开发人员常见的解决问题的方法就是将问题隔离起来,将范围缩小,且让数据块更易于管理。
### 隔离问题
使用Sencha Cmd来以相同的命名空间创建一个新的应用程序。
接着,将有问题的类复制过来并对它进行测试。
是否出现了相同的bug?尝试在测试应用程序中解决这个问题。
还可以更进一步,尝试通过重新创建类来进行隔离。在开始的时候只包含必需的代码。
它能工作吗?如果框架没有错误,类也没有错误,哪说明肯定是别的东西出现了错误。
###
### 切换到默认主题
返回应用程序, 并尝试切换到Sencha的默认样式(Sencha Touch中的是Sencha默认样式,Ext JS是海王星主题)。
能工作吗?如果能,说明问题在自定义的样式中。
仍然不能工作?这至少可以知道,自定义的样式没有错误。这有可能是嵌套造成的错误,也可能是使用了错误的布局。
### 组件查询
要想知道是否组件查询出了问题,可以轻松的在浏览器的开发者工具的控制台中进行验证:
~~~
Ext.ComponentQuery.query('button[action="test"]');
~~~
是否返回了一个空数组?如果是,问题就在这里!又或者可能是返回了组件,但查询时机错了。在处理回调的时候,经常会发生这种事情。当代码在执行的时候,该组件可能还没渲染到屏幕上的。
### 常用的调试技术
作为开发人员,碰到需要解决的bug或问题是常有的事。关键是,使用什么样的工具才能让这变得富有挑战性,是不?
除了上面提到的技术,还有几个标准技巧。首先,要了解框架已经所使用的工具。阅读API文档(或者更进一步,浏览器框架的源代码)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc5a7cfa.png)
将框架切换为调试框架之一。这样做的好处是可以看到额外的日志信息,而且还可以直接阅读框架代码。对于Sencha Touch项目,打开app.json文件并临时修改框架为:
~~~
"js": [
{
"path": "../touch/sencha-touch-all-debug.js",
"x-bootstrap": true
},
~~~
对于Ext JS项目,打开index.html文件并临时修改框架为:
~~~
<script src="../ext/ext-all-debug.js"></script>
~~~
浏览器的开发者工具也是相当有用的(Google Chrome或Firebug)。此外,还有一些是专门针对Sencha开发使用的便利插件:illumination和Sencha的App Inspector。
如果想快速设计原型,可以尝试使用Sencha Fiddle。
对于测试,有大量的工具可以使用 ,如Siesta。
最后但并非最不重要的是,如果以上工具都帮不了,而你已经盯着你的代码几个小时了,那么,请你……休息一下!通常,在休息的时候,可以放松头脑,说不定就立马把问题解决了。尤其是在犯了拼写错误或打字错误(没区分大小写)的时候,这会让你郁闷上几个小时,这是因为你会忽略他们。
需要寻找更多的帮助?可以了解一下在世界各地的Sencha Ext JS或Sencha Touch的培训课程,又或者参加在线课程。
作者:**Lee Boonstra**
Lee is a technical trainer at Sencha. She’s located in Amsterdam and has experience in both front-end and back-end development. Lee spends her spare time developing web and mobile apps. She is writing a cookbook for O'Reilly about Sencha Touch.
演练Ext JS 4.2自定义主题
最后更新于:2022-04-01 07:23:23
本文将根据API文档中关于主题的介绍做的一次演练,以便熟悉自定义主题的过程。
练习环境:
- Sencha Cmd v4.0.1.45
- Ruby 1.9.3-p392
- firefox 26
首先,使用以下Sencha Cmd命令创建一个名为TestMyTheme的应用程序:
~~~
sencha -sdk c:\ext4 generate app TestMyThemec:\TestMyTheme
~~~
命令中,参数sdk声明了框架文件所在目录为C盘的ext4目;generate为命令(commands),表示要创建点什么,而app则表示要创建的是应用程序;MyTheme是要创建的应用程序名称,这样,创建的应用程序将会以TestMyTheme作为命名空间;最后的“C:\TestMyTheme”则表示应用程序将创建到C盘的TestMyTheme目录。
应用程序创建后,使用以下Sencha Cmd命令来将应用程序转换为Web服务,以便预览效果:
~~~
sencha fs web -port 8000 start -map c:\TestMyTheme
~~~
命令中,fs命令表示将要使用功能命令;web则表示创建一个简单的Web服务器;参数port则用例定义访问端口,在这里将使用8000端口;start表示启动服务器;最后的路径表示Web服务器指向的访问目录。
命令执行后,如果显示以下提示信息,则表示Web服务已经成功启动:
~~~
Sencha Cmd v4.0.1.45
[INF] Starting shutdown listener socket
[INF] Listening for stop requests on: 49331
[INF] Mapping http://localhost:8000/ toc:\TestMyTheme...
[INF] Starting http://localhost:8000
[INF] jetty-8.1.7.v20120910
[INF] NO JSP Support for /, did not findorg.apache.jasper.servlet.JspServlet
[INF] startedo.e.j.w.WebAppContext{/,file:/C:/TestMyTheme/}
[INF] started o.e.j.w.WebAppContext{/,file:/C:/TestMyTheme/}
[INF] Started SelectChannelConnector@0.0.0.0:8000
~~~
现在,在浏览器输入以下地址,会看到如下图所示效果:[http://localhost:8000/](http://localhost:8000/)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-01_56aeca7e0c06a.jpg)
从图中可以看到,目前使用的是经典主题。下面来创建自定义主题。切换到C:\TestMyTheme目录,输入以下命令来创建自定义主题:
~~~
sencha generate theme MyTheme
~~~
命令执行完后,切换到C:\TestMyTheme\packages目录会看到多了一个MyTheme目录,该目录就是用来创建自定义主题的目录。目前的主题是从经典主题扩展出来的,而这里需要从海王星主题扩展,所以,要进入MyTheme目录,打开package.json文件,将extend属性的值修改为ext-theme-neptune就行了。
下面先来测试一下自定义主题。在DOS窗口下切换到C:\TestMyTheme\packages\MyTheme目录,然后输入以下命令来生成自定义主题:
~~~
sencha package build
~~~
主题生成以后,打开C:\TestMyTheme\.sencha\app目录下的sencha.cfg文件。在文件中查找app.theme,会找到以下语句:
~~~
app.theme=ext-theme-classic
~~~
从语句中可以看到,当前使用的主题是经典主题,现在要使用自定义主题,因而需要将ext-theme-classic修改为MyTheme。修改完成后,在DOS中,切换到C:\TestMyTheme目录,输入以下命令生成一次应用程序:
~~~
sencha app build
~~~
生成过程完成后,刷新浏览器应该可以看到下图所示的效果,主题已经更换为自定义主题(从海王星主题继承的)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-01_56aeca7e1acbb.jpg)
下面来研究下怎么修改主题。一般情况下,主要的修改方式有两种,一种是通过修改主题变量来实现,一种是直接定义自己的样式。要修改变量值,需要在C:\TestMyTheme\packages\MyTheme\sass\var目录下进行,要直接定义自己的样式需要在C:\TestMyTheme\packages\MyTheme\sass\src目录进行。具体修改方式可参考Ext JS包里packages\ext-theme-neptune\sass目录里的文件。
例如,要修改标签页标签栏的背景颜色,可在C:\TestMyTheme\packages\MyTheme\sass\var目录下创建一个名为tab的目录,然后创建一个名为Bar.scss的 文件,在API中,可以查到Ext.tab.Bar的背景颜色的CSS变量代码是$tabbar-base-color,在Bar.scss文件中添加以下代码就可以修改标签栏的背景颜色了:
~~~
$tabbar-base-color: #ff0 !default;
~~~
修改完成后,在C:\TestMyTheme目录运行“senccha app build”命令生成一次应用程序,就可看到标签栏的背景颜色已经修改为黄色了。
如果想直接修改面板主体的背景颜色,但不想修改变量值,可以在C:\TestMyTheme\packages\MyTheme\sass\src目录下创建一个名为panel的目录,再在新剪的目录下创建名为Panel.scss的文件,然后在文件中添加以下代码:
~~~
.x-panel-body-default {
background:none repeat scroll 0 0 #00f;
}
~~~
样式x-panel-body-default是面板主体所使用的默认样式,在这里会将背景颜色修改为蓝色。重新生成应用程序后,就会看到面板的背景颜色已经修改为蓝色了。
执行打包命令后,就可以C:\TestMyTheme\packages\MyTheme\build\resources目录下找到打包好以后的css文件,将css文件和images目录复制到项目中就可以在项目中使用自定义的主题了。
【翻译】在Ext JS应用程序中使用自定义图标
最后更新于:2022-04-01 07:23:21
原文:[Using Custom Icons in Your Ext JS App](http://www.sencha.com/blog/using-custom-icons-in-your-ext-js-apps?mkt_tok=3RkMMJWWfF9wsRolu63MZKXonjHpfsX57uwtUae2i4kz2EFye%2BLIHETpodcMTcNnMa%2BTFAwTG5toziV8R7PCKM1338YQWhPj)
作者:Lee Boonstra
Lee is a technical trainer at Sencha. She’s located in Amsterdam and has experience in both front-end and back-end development. Lee spends her spare time developing web and mobile apps. She is writing a cookbook for O'Reilly about Sencha Touch.
正如我所做的,你喜欢Ext JS 4.2的glyphs(字形)属性么?对于glyphs,可以实现从字体中创建图标。使用图标字体的优势是,他们是矢量的,因此永远不会失真,而且可以在不使用Photoshop的情况下很容易实现样式图标,以及只需要做一次页面请求就可以下载所有的图标。
属性glyphs可应用于Ext JS按钮和面板。可以从IcoMoon这样的网站下载自定义的字体或者根据自己的样式表来实现字体。属性glyphs的值是映射到它所代表的图标的Unicode字符的十进制代码。将自定义字体的名称添加到该属性会更好,如以下代码:
~~~
glyph: '115@MyIconFont',
~~~
有大量的Ext JS组件会从面板扩展,但是否考虑过在不从Ext.panel.Panel或Ext.button.Button扩展的其他组件里实现图标字体吗?
要回答这个问题,可以从以下隐藏而实际正在实行的概念入手:
一个字符在插入到确定的DOM元素之前(或之后),能看到图标是因为该字符被样式化为包含所有图标的自定义字体(@font-face技术)。
下面自己来试试这个:
1. 在浏览器的开发工具内,选择想实现图标的DOM元素。理想的情况下是在它的顶部放置一个CSS类(如:箭头),这样就可以很容易的从Sass中引用它。
1. 下载图标字体并将它映射到一些字符(使用以下字符:>)。
1. 在Sass实现图标字体。
~~~
@font-face {
font-family: 'MyIconFont';
src: url('../resources/fonts/Nouveau.eot');
src: url('../resources/fonts/Nouveau.eot?#iefix') format('embedded-opentype'),
url('../resources/fonts/Nouveau.woff') format('woff'),
url('../resources/fonts/Nouveau.ttf') format('truetype'),
url('../resources/fonts/Nouveau.svg#Nouveau') format('svg');
font-weight: normal;
font-style: normal;
}
~~~
1. 好了,现在是见证奇迹的时刻。在Sass样式表,编写以下CSS规则:
~~~
.arrow:before {
content: ">"; //the character mapped to an icon
font-family: 'MyIconFont'; //the name of the icon font
color: red; //set additional colors or dimensions...
margin-right: 10px;
}
~~~
伪CSS选择符“:befor”会在DOM元素的左边创建图标。伪CSS选择符“:affter”会在DOM元素的右边创建图标。
现在已经了解了如何使用这项技术,就可以在任何组件来尝试它了,如模板、数据视图、表单字段等等。
想了解更多么?Sencha将在1月27日到31日在线直播提供高级Ext JS主题培训。鳄鱼看一看位于世界各地的开放式课程或参加网上培训。
【翻译】热门支持小提示:2013年12月
最后更新于:2022-04-01 07:23:19
原文地址:[http://www.sencha.com/blog/top-support-tips-december-2013?mkt_tok=3RkMMJWWfF9wsRolu63MZKXonjHpfsX57uwtUae2i4kz2EFye%2BLIHETpodcMTcNnMa%2BTFAwTG5toziV8R7PCKM1338YQWhPj](http://www.sencha.com/blog/top-support-tips-december-2013?mkt_tok=3RkMMJWWfF9wsRolu63MZKXonjHpfsX57uwtUae2i4kz2EFye%2BLIHETpodcMTcNnMa%2BTFAwTG5toziV8R7PCKM1338YQWhPj)
作者:**Sencha Support Team**
### Seth Lemmons:充分利用TaskRunner
有时候,重用正在运行的可以启动和暂停的的任务非常有用。相当幸运,在Ext JS中实现可重用的任务相当容易。Ext.util.TaskManager 是一个单例模式(singleton)的类,它的start方法可以通过传递的配置自动创建任务。或者,也可以创建自己的任务实例。任务会保持一个内部运行的根据间隔时间自动增长的计数器,且可在每次调用start方法进行重置。可以为任务定义一个在任务停止(或取决于如何缓存进度的暂停)时调用的onStop函数。使用fireOnStart配置项可以用来判断任务在调用start方法或在第一次时间间隔过后run函数是否执行正确。
这里有一个创建计时器并允许用户启动和暂停它的示例。任务的进度被缓存它的值属性指向的counter组件中。现在,每当调用stop方法就像暂停这个用例。
可以在[https://fiddle.sencha.com/#fiddle/1ca](https://fiddle.sencha.com/#fiddle/1ca)来查看它的行为。
如果想阅读更多有关任务间隔(Taskk Intervals)的文档,可参阅[http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.util.TaskRunner](http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.util.TaskRunner)。
### Mitchell Simoens:Sencha Touch中的事件委托
你知道吗,即使没有在MVC应用程序中使用控制器,容器也可以监听子组件的事件?有两种方法可以实现这个:
~~~
Ext.Viewport.add({
layout : {
type : 'card',
animation : 'slide'
},
items : [
{
html : 'Card One',
items : [
{
xtype : 'button',
ui : 'forward',
text : 'View Details'
}
]
}
],
listeners : {
delegate : 'button[ui=forward]',
tap : function(button, e) {
this.setActiveItem({
html : 'Details are here!'
});
}
}
});
~~~
在上面的示例中,在容器中使用了卡片布局。第一张卡片中包含一个子按钮,预期的效果是通过子按钮的tap事件在顶层容器显示详细视图。不过,由于未来可能将按钮移动到工具栏上,所以不希望直接在这里监听按钮的事件。为了做成防护措施,在这里可以在顶层容器添加监听并采用委托事件来实现,而它的目标就是按钮的ui属性值forward。配置项delegate会使用到ComponentQuery的选择符,选择符可以是简单的,也可以时候复杂的,看你需要。但我的建议是尽量保持简单。
这个示例的演示可以浏览https://fiddle.sencha.com/#fiddle/1f9。
如果是使用Ext.define自定义的组件,可以使用control配置项,就像在控制器中一样,代码如下:
~~~
Ext.define('MyContainer', {
extend : 'Ext.Container',
xtype : 'mycontainer',
config : {
layout : {
type : 'card',
animation : 'slide'
},
items : [
{
html : 'Card One',
items : [
{
xtype : 'button',
ui : 'forward',
text : 'View Details'
}
]
}
],
control : {
'button[ui=forward]' : {
tap : 'onButtonTap'
}
}
},
onButtonTap : function(button, e) {
this.setActiveItem({
html : 'Details are here!'
});
}
});
Ext.Viewport.add({
xtype : 'mycontainer'
});
~~~
和第一个示例一样,在control配置项中,使用了按钮的ui属性值forward来切换详细视图。要注意的是,tap事件映射的onButtonTap方法是在顶层的MyContainer组件中定义的。当要实现将子组件的事件定义在容器这种方法,就可是有control配置项。不过,要记住尽可能使用简单的选择符。
本示例的演示可以访问[https://fiddle.sencha.com/#fiddle/1fa](https://fiddle.sencha.com/#fiddle/1fa)。
如果有一个锚链标记(<a>),且需要捕获tap事件并阻止它进入对应的链接,可以类似第一个示例,使用delegate配置项来实现,不过要告诉监听,这是元素监听,代码如下:
~~~
Ext.Viewport.add({
html : '<div>You can capture tap event on this <a href="#" class="tappable">link</a> in your components</div>',
listeners : {
element : 'element',
delegate : 'div a.tappable',
tap : function(e, t) {
e.stopEvent();
Ext.Viewport.removeAll();
Ext.Viewport.setHtml('You tapped it!');
}
}
});
~~~
可以使用element配置项来告诉监听现在监听的是基于组件元素的事件,且使用delegate配置项来为tap事件监听特定元素。在这里,delegate配置项使用了DOM选择符,选择符可以使用所需的任何选择符,但我建议尽可能的保持简单。要注意的是,在浏览器上,如Google Chrome,将不会停止事件,不过在移动设备上,它会正常工作。
这个例子的演示可以访问[ https://fiddle.sencha.com/#fiddle/1fb](https://fiddle.sencha.com/#fiddle/1fb)。
### Greg Barry:在Sencha Cmd 中隐藏Gems
Sencha Cmd严重依赖于一组已知的有关你的环境变量和应用程序的内部属性。这些属性包括软件路径、系统设置和Sencha软件版本。
在命令行中,只要在项目根目录中输入以下命令就可以查看和使用这些信息:
~~~
sencha diag show
~~~
通过命令行或自定义ANT脚本都就可以查看所有这些属性。
还可以很容易的查看到一些属性的设置位置。通常可以根据命名空间来确定属性的源,例如:
- app. — 查看"app.json"和".sencha/app/sencha.cfg"
- workspace. — 查看"workspace.json"和".sencha/workspace/sencha.cfg"
- framework. — 在Ext JS或Sencha Touch的SDK中查看"cmd/sencha.cfg"
- cmd. — 在Sencha Cmd安装目录中查看"sencha.cfg"
想了解更多有关将这些属性继承到自定义脚本的信息,可参阅:[ http://docs.sencha.com/extjs/4.2.2/#!/guide/command_advanced](http://docs.sencha.com/extjs/4.2.2/#!/guide/command_advanced)
一个很不错的支持Ext JS 4的上传按钮
最后更新于:2022-04-01 07:23:16
以前经常使用的swfUpload,自从2010年开始到现在,很久没更新了。而这几年,flash版本已经换了好多个,所以决定抛弃swfupload,使用新找到的上传按钮。
新的上传按钮由[**harrydeluxe**](http://www.sencha.com/forum/member.php?8007-harrydeluxe "harrydeluxe is offline")扩展的,下载地址是http://www.sencha.com/forum/showthread.php?205576-File-upload-with-drag-amp-drop-support。
新的上传按钮派生于按钮,因而使用非常方便,它使用了Plupload(http://www.plupload.com/)作为上传控件,功能很不错,支持直接将文件拖到页面指定的区域进行上传,也就是说,除了可以使用按钮上传外,还可以直接将文件拖动到数据视图内实现上传。
下图是我测试的按钮效果,效果比使用swfupload好多了,建议采用:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-01_56aeca7dc59a5.jpg)
上传按钮自带了一个上传对话框,可以用,也可以不用,还可以自行修改,下图就是自带上传对话框:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-01_56aeca7dd45a0.jpg)
为什么要使用“var me=this”这样的写法
最后更新于:2022-04-01 07:23:14
很多人都会奇怪,为什么在Ext JS 4中会大量使用“var me=this”这样的写法,其实,在官方论坛以下地址的帖子已经给出了很好的说明:
[http://www.sencha.com/forum/showthread.php?132045](http://www.sencha.com/forum/showthread.php?132045)
帖子里提到的最主要原因是脚本的压缩问题,例如以下代码:
~~~
function doA() {
var me = this;
me.a();
me.b();
me.c();
me.d();
}
function doB() {
this.a();
this.b();
this.c();
this.d();
}
~~~
压缩后:
~~~
function doA(){var a=this;a.a();a.b();a.c();a.d()};
function doB(){this.a();this.b();this.c();this.d()};
~~~
从压缩后的代码可见,使用“var me=this”的写法,压缩率更高。可以想象,在Ext JS这样类很多,且大量需要使用this关键字的框架,使用“var me=this”,确实可以大大减少压缩包的大小。
在VS2012中实现Ext JS的智能提示太简单了
最后更新于:2022-04-01 07:23:12
Visual Studio 2012太强大了,居然能自己会去提取Ext JS的类的属性和方法,从而实现只能提示。下面就来介绍一下实现这个功能。
在Visual Studio 2012中随便创建一个Web项目,我创建了一个空的Web项目,目录结构如下图所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc51475a.jpg)
关键就是Scripts中的_references.js文件,文件的内容如下:
~~~
/// <reference path="ext-all-dev.js" />
~~~
这个和VS2010中实现只能提示的原理一样。需要注意的是ext-all-dev.js的路径,如果不是和_references.js在同一目录,记得补上相对路径。
经过这样处理后,就可以实现智能提示了,例如在JavaScript.js文件输入:
~~~
var store = Ext.create(Ext.data.Store, {})
store.
~~~
就会显示如下图的效果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-01_56aeca7dac5c2.jpg)
这实在太的强大和太简单了。
一个不错的扩展:Ext.ux.container.ButtonSegment
最后更新于:2022-04-01 07:23:10
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc50397e.jpg)
地址:[http://www.sencha.com/forum/showthread.php?132048-Ext.ux.container.ButtonSegment](http://www.sencha.com/forum/showthread.php?132048-Ext.ux.container.ButtonSegment)
【翻译】十大要避免的Ext JS开发方法
最后更新于:2022-04-01 07:23:07
原文地址:http://www.sencha.com/blog/top-10-ext-js-development-practices-to-avoid/
作者:**Sean Lanktree**
Sean is an Ext JS Professional Services Lead at CNX Corporation.
在[CNX](http://www.cnxcorp.com/),尽管大多数的Ext JS开发工作需要从0开始创建新的应用程序,偶尔会有客户让我们帮他们解决内部工作上的性能问题、臭虫和结构性问题。我们以“清洁工”这种角色进行工作已经有很长一段时间了,在我们审查过的应用程序中,我们注意到,有一些共同的不明智的编码方法经常会出现。基于过去几年的审查工作,我们列出了十个我们建议的,在Ext JS应用程序中应该避免的开发方法。
### 1. 过多或不必要的组件嵌套
开发人员最常见的错误之一是没理由的嵌套组件。这样做,会影响性能和也会造成应用程序的不美观,如爽边框火意外的布局行为。在下面的示例1A,在面板内只包含了一个Grid。在这种情况下,该面板是不必要的。如示例1B所示,额外的面板可以取消。要记住的是,表单面板、树面板、标签面板和Grid面板都是从面板扩展的,隐藏,在使用这些组件的时候,应该特别注意不要的嵌套情况。
~~~
items: [{
xtype : 'panel',
title: ‘My Cool Grid’,
layout: ‘fit’,
items : [{
xtype : 'grid',
store : 'MyStore',
columns : [{...}]
}]
}]
~~~
示例1A 不好的:面板(panel)是不必要的
~~~
layout: ‘fit’,
items: [{
xtype : 'grid',
title: ‘My Cool Grid’,
store : 'MyStore',
columns : [{...}]
}]
~~~
示例1B 好:Grid已经是面板,因而可以直接在Grid中使用任何面板属性
### 2. 清理未使用组件失败造成内存泄漏
许多开发人员不知道为什么他们的应用程序随着使用时间越长越来越慢。在用户浏览整个应用程序期间清理未使用组件失败是最大的一个原因。在下面的实例2A中,每次用户右键单击Grid的行,都会创建一个新的右键菜单。如果用户保持应用程序处于打开状态并右键单击行上百次,那么,就会有上百个永远不会被摧毁的右键菜单。对于开发人员和用户来说,应用程序“看上去”显示是争取的是因为只有最后一个被创建的菜单能显示在页面上,而且与的则是隐藏的。由于没有创建新菜单并没有清理旧的,应用程序的内存利用率就会不断增长,这最终将导致较慢的操作或浏览器崩溃。
示例2A就很好,由于右键菜单只在Grid初始化时创建一次,并在用户每次右键单击行时重复使用。不过,如果Grid被销毁,右键菜单一直存在,尽管它不再需要。最好的方式是示例2C,在Grid销毁的时候,把右键菜单也销毁。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
columns : [{...}],
store: ‘MyStore’,
initComponent : function(){
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
}).showAt(event.getXY());
}
});
~~~
示例2A 不好:每一次右键单击都会创建菜单,且永远不会被销毁
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
store : 'MyStore',
columns : [{...}],
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
buildMenu : function(){
return Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
});
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
this.menu.showAt(event.getXY());
}
});
~~~
示例2B 较好:菜单会在Grid创建时被创建,且每次可重用
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
store : 'MyStore',
columns : [{...}],
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
buildMenu : function(){
return Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
});
},
onDestroy : function(){
this.menu.destroy();
this.callParent(arguments);
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
this.menu.showAt(event.getXY());
}
});
~~~
示例2C 最好:Grid被销毁时,右键菜单也被销毁
### 3.怪物控制器
当看到应用程序拥有一个上千行代码的超级控制器的时候,我们不知道震惊了多少次。我们更倾向于根据应用程序功能拆分控制器。例如,订单处理应用程序可能会有划分条目、出货量、客户查找等控制器。这将使导航和维护代码更容易。
一些开发人员喜欢根据视图拆分控制器。例如,如果一个应用程序有一个Grid和表单,这将会使用一个控制器来管理Grid和使用一个控制器来管理表单。并没有一个“正确”的方式来划分控制器逻辑,只要是一致的就行。要记住,控制器可以与其他控制器进行通信。在示例3A,可以看到如何检索其他的控制器并调用其中的方法。
~~~
this.getController('SomeOtherController').runSomeFunction(myParm);
~~~
示例3A 获取另一个控制器的引用并调用它的方法。
作为替代,也可以触发任何控制器可以监听到的应用程序层事件。在示例3B和3C,可以看到如何在一个控制器触发一个应用程序层事件并在另外一个控制器监听它。
~~~
MyApp.getApplication().fireEvent('myevent');
~~~
示例3B 触发一个应用程序层事件
~~~
MyApp.getApplication().on({
myevent : doSomething
});
~~~
示例3C 在另一个控制器监听应用程序层事件
注意: 自从Ext JS 4.2开始,使用多个控制器变得更容易了——他们可以触发其他控制器可以直接监听的事件
### 4.源代码的文件夹结构差
这虽然不影响性能和操作,但会让找跟进应用程序结构变得困难。随着应用程序的增长,如果源代码很有组织,那么寻找源代码以及增加特性或功能就会很容易。我们常常看到许多开发人员会将所有视图(即使是很大的应用程序)如示例4A一样放在一个目录。我们建议如示例4B所示通过逻辑功能来组织视图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc4a8cbe.png)
示例4A 不好:所有视图都处于同样层次
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc4c0b72.png)
示例4B 好:视图根据逻辑功能进行组织
### 5.全局变量的使用
即使全局变量是不好的已经广为人知,但仍然会在我们审查过的一些应用程序中看到他们的身影。使用全局变量的应用程序可能会有名称冲突等重大问题,并且很难去调试。不使用全局变量,可以在类中定义“属性”,并引用这些属性的getter和setter。
例如,假设应用程序需要记录最后选择的客户。一般情况下会如示例5A那样在应用程序中定义一个变量,这很容易且值可以很便利的被应用程序的其他部分使用。
~~~
myLastCustomer = 123456;
~~~
示例5A 不好:创建全局变量来存储最后的客户编号
作为替代,好的做法是创建一个用来保存属性的类来代替全局变量。在当前情况下,可以创建一个名为Runtime.js来保存应用程序中要使用到的运行属性。示例5B显示了Runtime.js在源代码结构中的位置。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc4d447b.png)
示例5B Runtime.js文件的位置
示例5C显示了Runtime.js文件的内容,而示例5D显示了如何在app.js中“请求(require)”它。在应用程序中,就可以如5E或5F那样在任何地方“设置(set)”或“获取(get)”属性。
~~~
Ext.define(‘MyApp.config.Runtime’,{
singleton : true,
config : {
myLastCustomer : 0 // initialize to 0
},
constructor : function(config){
this.initConfig(config);
}
});
~~~
示例5C 用来为应用程序保存全局属性的Runtime.js文件示例
~~~
Ext.application({
name : ‘MyApp’,
requires : [‘MyApp.config.Runtime’],
...
});
~~~
示例5D 在app.js文件中请求Runtime类
~~~
MyApp.config.setMyLastCustomer(12345);
~~~
示例5E 设置最后客户的方式
~~~
MyApp.config.getMyLastCustomer();
~~~
示例5F 获取最后客户的方式
### 6. id的使用
我们不建议在组件上使用id,因为每一个id必须是唯一的。而这样很容易导致不小时使用了相同的id,从而引起重复的DOM id(名称冲突)。相反,应该让框架来处理id的生成。使用Ext JS的组件查询,没有任何理由要为Ext JS组件指定一个id。示例6A显示了一个应用程序中的两段代码,在这里创建了两个不同的保存按钮,而这两个保存按钮的id都为“savebutton”,从而导致了名称冲突。显而易见,在下面的代码很容易找出名称冲突,但在一个大型应用程序中,要确定名称冲突会很困难。
~~~
// here we define the first save button
xtype : 'toolbar',
items : [{
text : ‘Save Picture’,
id : 'savebutton'
}]
// somewhere else in the code we have another component with an id of ‘savebutton’
xtype : 'toolbar',
items : [{
text : ‘Save Order’,
id : 'savebutton'
}]
~~~
示例6A 不好:为组件分配了重复id将造成名称冲突
替代方法是,如果需要手动确定每一个组件的,可以如示例6B那样使用itemid代替id。这就可以解决命名冲突,并且仍然可以通过itemid来引用组件。通过itemid有许多方法来获取组件的引用。示例6C列出了部分方法。
~~~
xtype : 'toolbar',
itemId : ‘picturetoolbar’,
items : [{
text : 'Save Picture',
itemId : 'savebutton'
}]
// somewhere else in the code we have another component with an itemId of ‘savebutton’
xtype : 'toolbar',
itemId: ‘ordertoolbar’,
items : [{
text : ‘Save Order’,
itemId: ‘savebutton’
}]
~~~
示例6B 好:使用itemid来创建组件
~~~
var pictureSaveButton = Ext.ComponentQuery.query('#picturetoolbar > #savebutton')[0];
var orderSaveButton = Ext.ComponentQuery.query('#ordertoolbar > #savebutton')[0];
// assuming we have a reference to the “picturetoolbar” as picToolbar
picToolbar.down(‘#savebutton’);
~~~
示例6C 好:使用itemid引用组件
### 7.不可靠的组件引用
有时候会看到代码利用组件位置来引用组件。应该避免出现这样的情况,因为有任何条目被增加、移除或嵌入不同的组件,就会导致错误。示例7A显示了几种常见情况。
~~~
var mySaveButton = myToolbar.items.getAt(2);
var myWindow = myToolbar.ownerCt;
~~~
示例7A 不好:避免基于组件位置来获取组件引用
替代方法是,如示例7B那样使用组件查询或者最佳的up或down方法来返回引用。使用这种技术,代码就很少会因以后的结构或组件位置变化而导致错误。
~~~
var mySaveButton = myToolbar.down(‘#savebutton’); // searching against itemId
var myWindow = myToolbar.up(‘window’);
~~~
示例7B 好:使用组件查询来返回相关引用
### 8. 不遵守大写/小写命名约定
Sencha在为组件、属性或xtype等等命名的时候,会遵循某些大写/小写标准。为了避免混淆,保持代码清洁,应对遵循相同的标准。示例8A显示几个不正确的情形。示例8B显示了在相同情况下,使用正确的大写/小写命名约定的情形。
~~~
Ext.define(‘MyApp.view.customerlist’,{ // should be capitalized and then camelCase
extend : ‘Ext.grid.Panel’,
alias : ‘widget.Customerlist’, // should be lowercase
MyCustomConfig : ‘xyz’, // should be camelCase
initComponent : function(){
Ext.apply(this,{
store : ‘Customers’,
….
});
this.callParent(arguments);
}
});
~~~
示例8A 粗体显示的地方为不正确的大写/小写命名
~~~
Ext.define(‘MyApp.view.CustomerList’,{
extend : ‘Ext.grid.Panel’,
alias : ‘widget.customerlist’,
myCustomConfig : ‘xyz’,
initComponent : function(){
Ext.apply(this,{
store : ‘Customers’,
….
});
this.callParent(arguments);
}
});
~~~
示例8B 粗体显示的地方为正确的大写/小写命名
另外,如果触发任何自定义事件,事件的名称应对是小写的。当然,不遵循这些约定,一切都仍然会工作,但为什么要迷失在标准之外并编写不太干净的代码?
### 9. 将组件约束在父组件的布局
在示例9A,面板总是会有“region:center”属性,因此,当想重用它的时候就可能行不通,例如将它放到“west”区域。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
region : 'center',
......
});
this.callParent(arguments);
}
});
~~~
示例9A 坏:“center”区域不应该放在这里
代替方法是,如示例8B那样,在创建组件的时候才指定布局配置。这样,就可以将组件重用到任何你希望的地方,切不受布局配置的约束。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
......
});
}
});
// specify the region when the component is created...
Ext.create('MyApp.view.MyGrid',{
region : 'center'
});
~~~
示例9B 好:在创建组件的时候才知道区域
如示例9C所示,也可以为组件提供一个默认的区域,如果需要,可以重写它。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
region : 'center', // default region
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
......
});
}
});
Ext.create(‘MyApp.view.MyGrid’,{
region : ‘north’, // overridden region
height : 400
});
~~~
示例9C 也很好:指定默认区域,在需要的时候重写
### 10. 使代码比所需的更复杂
有很多时候,我们会看到比所需更复杂的代码。这通常是由于对每个组件的可用方法不熟悉造成的。最常见的一种情况是,为每一个表单字段单独从数据记录中加载数据。示例10A就显示这种情况。
~~~
// suppose the following fields exist within a form
items : [{
fieldLabel : ‘User’,
itemId : ‘username’
},{
fieldLabel : ‘Email’,
itemId : ‘email’
},{
fieldLabel : ‘Home Address’,
itemId : ‘address’
}];
// you could load the values from a record into each form field individually
myForm.down(‘#username’).setValue(record.get(‘UserName’));
myForm.down(‘#email’).setValue(record.get(‘Email’));
myForm.down(‘#address’).setValue(record.get(‘Address’));
~~~
示例10A 不好:为表单字段单独的从记录中加载数据
替代单独加载每一个值的方法是,使用loadRecord方法从记录中为所有字段的数据到表单字段,这只需要一行代码。如示例10B所示,这里的关键是确保表单字段的name属性和记录的字段名称是一样的。
~~~
items : [{
fieldLabel : ‘User’,
name : ‘UserName’
},{
fieldLabel : ‘Email’,
name : ‘Email’
},{
fieldLabel : ‘Home Address’,
name : ‘Address’
}];
myForm.loadRecord(record);
~~~
示例10B 好:使用loadRecord方法只需要一行代码就可加载所有表单字段
这只是比必需的更复杂的代码的其中一个例子。而当中的重点是要审视组件的所有方法和和示例,以确保正在使用简单和适当的技术。
[CNX公司](http://www.cnxcorp.com/)是Sencha认证选择合作伙伴。Sencha合作伙伴网络是Sencha专业服务器团队的宝贵扩展。
自从1996年以来,CNX一直处于自定义商业应用程序开发的先行者。在2008年,CNX在Ext JS上对它的基于浏览器的用户界面开发进行了标准化,在2010年,又添加了Sencha Touch作为移动开发的标准。我们已经在世界各地,为教育、金融、食品、法律、物流、制造、出版和零售等许多行业的客户创建了一流的Web应用程序。我们的开发团队以芝加哥市中心的公司办公室为基地,可以处理任何规模的项目。CNX可以独立工作或与您的团队一起,以快速、经济高效的方式去实现项目目标。请查阅我们的网站http://www.cnxcorp.com。
使用Ext JS,不要使用页面做组件重用,尽量不要做页面跳转
最后更新于:2022-04-01 07:23:05
今天,有人请教我处理办法,问题是:
一个Grid,选择某条记录后,单击编辑后,弹出编辑窗口(带编辑表单),编辑完成后单击保存按钮保存表单,并关闭窗口,刷新Grid。
这,本来是很简单的,但囿于开发人员对Ext JS的理解不到位,搞得相当的复杂了。
主要复杂的地方在以下几点:
- 为了实现编辑表单的可重用,把表单做成了页面,然后在Window中套IFRAME打开页面。
- 表单的提交不是用Ajax提交,而是使用习惯的页面跳转方式提交,于是,一切都复杂起来了。
要这样实现,也不是不可以,在最后的提交页面,调用parent对象操作父页面的对象关闭窗口并刷新Grid。不过,这样实在太复杂了。
这里存在的问题是对Ext JS的开发理解不到位,还是根据老的开发方式去来写Ext JS的应用程序,因而本来简单的东西一下子就复杂化了,这也是很多初学者经常犯的错误。
要很好的使用Ext JS进行开发,要牢记以下几点:
- Ext JS的数据交互,基本上是以Ajax为工具,以JSON或XML格式数据进行交互,这个过程,不需要任何的页面跳转来实行,数据的处理都以一种很标准化的数据格式进行处理,如错误处理、成功保存等等信息,都可通过JSON或XML格式的数据来告诉客户端,让客户端去进行处理。
- 使用Ext JS(尤其是4)编写自己的扩展,实现组件的重用,非常的方便,不需要把重用部分做成一个页面那么麻烦。
- 使用Ext JS 4可以很容易实现单页面的应用程序,也就是只需要一个页面就行了,客户端与服务器端的数据交互都是遵循第一点来处理的。当然,担心性能问题的,也会以IFRAME形式来实现多页面的应用程序,但是,必须明白的是,这也是以单页面为基础的,意思就是,一个IFRAME页面的流程,基本就是一个单页面的应用程序的流程,不需要类似习惯的Web开发方式那样进行多个页面的控制。理解这点很重要,不然,还真不如不用Ext JS,直接使用习惯的Web开发方式来开发。
- Ext JS在客户端也是数据与UI分离的,千万别在UI中找数据。
- 使用Ext JS,是基于组件形式来组织UI的,而不是以HTML代码来组织UI的,尽管最终生成的都是HTML代码。一般情况下,是不需要直接编写HTML代码就能实现应用程序的,如果确实需要使用使用HTML代码,那就要考虑为什么要用、是否有替代办法、怎么用这样问题。
以上纯个人观点,可能还有没考虑到的地方,忘大家斧正。
Ext JS添加子组件的误区
最后更新于:2022-04-01 07:23:03
经常会有人问我,为什么我的Grid不能岁窗口的变得而自动调整。了解后,发现很多人都习惯在渲染子组件的时候将Gird渲染到容器内的一个div里,而这正是问题的所在。
在Ext JS的布局系统中,能控制到的是容器的子组件,而对于渲染到容器中一个DIV的Grid,它并不知道在这容器里添加了一个Grid,当调整大小的时候,也就无法去调整Grid的大小了,而这也就是为什么Grid不会随容器的改变而改变了。
为什么那么多人喜欢使用这种方式来添加子组件呢?我想原因主要有以下两点:
1. 不知道如何在容器内添加子组件,
1. 习惯了使用JQuery等其他框架的开发方法,一时无法改变
第一个原因,只要是动态添加Grid,搞到很多初学者束手无策,譬如,我的Grid要从远程返回后才知道怎么创建,我怎么去拿这个脚本和添加到容器呢?笔者在学习Ext JS也犯过这样的错误,可以理解。所以,本文的作用就是来解惑的。
办法有两个:一是,使用Ajax把整个Grid(或其他组件的配置对象)加载到本地,然后使用容器的add方法就可以将组件添加到容器;一是直接使用容器的load功能,直接加载子组件并渲染,返回的数据就是子组件的配置对象。
第二个原因是习惯问题,只能自己去修正了,尽快熟悉Ext JS的开发模式就可以很容易改掉这个习惯。
如果是使用Ext JS 4的MVC做开发,基本不会出现这么尴尬的情况了,因而可以将子组件做成视图,然后在控制器中将视图添加到容器中就可以了。
初学者比较容易犯的布局错误(手风琴布局)
最后更新于:2022-04-01 07:23:01
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc491aea.jpg)
从上图面板中那条横线可以很清楚的看出树面板的容器没有使用Fit布局造成了树面板没有填满整个布局,而是根据自身大小进行显示。
实际的代码:
~~~
var mainAccirdion = new Ext.Panel({
id: "MainAccirdion",
region: 'west',
split: true,
layout: 'accordion',
collapsible: true,
width: 200,
layoutConfig: {
titleCollapse: false,
animate: true,
activeOnTop: true
},
items: [{
title: '病人报告列表',
items: [{ items: treepanel, flex: 1, layout: 'fit' }]
}
]
})
~~~
代码中,首先存在的问题是,使用了不必要的嵌套布局,其实这个在第一层直接使用treepanel就可以了,没必要再套容器。由于套多了一层布局,就造成了虽然在下一层布局使用了Fit布局,但是还是不能填满顶层容器。
在我的书《Ext JS权威指南》的9.8.2节中有一个示例可供参考,代码如下:
~~~
Ext.create("Ext.Viewport",{
layout:{type:"border",padding:5},
items:[
//区域定义
{xtype:"container",region:"north",height:30,
html:"<h1>示例9-5 单页面应用中使用Card实现“页面”切换</h1>"
},
{region:"west",split:true,width:200,minWidth:100,
layout:"accordion",
items:[
{title:"产品管理",xtype:"treepanel",
rootVisible: false,
root: {
text: 'root',id: 'rootProduct',
expanded: true,children:[
{text:"产品管理",id:"Product",leaf:true},
{text:"统计管理",id:"Stat",leaf:true}
]
},
listeners:{itemclick:itemclick}
},
{title:"系统管理",xtype:"treepanel",
rootVisible: false,
root: {
text: 'root',id: 'rootSyetem',
expanded: true,children:[
{text:"用户管理",id:"User",leaf:true},
{text:"系统设置",id:"System",leaf:true}
]
},
listeners:{itemclick:itemclick}
}
]
},
{region:"center",layout:'card',border:false,
id:"ContentPage",loader:true,
items:[
{title:"产品管理",id:"ProductContent",tbar:[
{text:"增加"},{text:"编辑"},{text:"删除"}
]}
],
listeners:{
add:function(cmp,con,pos){
if(this.items.length>1){
this.getLayout().setActiveItem(pos);
}
}
}
}
]
})
~~~
修改Ext.ux.GroupTabPanel让它支持延迟渲染
最后更新于:2022-04-01 07:22:58
在Ext JS包示例目录的ux目录下有一个Ext.ux.GroupTabPanel组件,可实现左侧分组显示菜单。这个组件有个小问题,就是在第一次渲染的时候,会把所有标签页都渲染了。这对有很多标签页的应用程序很不利,第一渲染的时间成本太高。
在Ext.ux.GroupTabPanel内部是使用卡片(Card)布局来实现标签页的切换的,但是作者在定义卡片布局的时候,并没有设置为可以延迟渲染,而是使用了默认了非延迟渲染,因而,修改的基本思路就是改变布局的设置。
在GroupTabPanel.js文件的initComponent方法内,在items内定义两个一个树面板和一个容器,其中的容器就是用来显示标签页的,但它的布局定义如下:
~~~
layout: 'card'
~~~
这是造成没有延迟渲染的原因,因为卡片布局默认是不延迟渲染的。因而,要修改的地方就是这里,首先,是在initComponent方法之上添加一个属性deferredRender,然后在使用时,可通过该属性控制卡片布局是否使用延迟渲染,默认值为true,也就是使用延时渲染,代码如下:
~~~
deferredRender : true,
~~~
然后,将容器的布局修改为:
~~~
layout: {
type:'card',
deferredRender:me.deferredRender
},
~~~
这样,就可以通过deferredRender来控制是否采用延迟渲染了。
现在,修改examples\grouptabs目录下的grouptabs.js文件,在“activeGroup: 0,”下添加“deferredRender:false,”,现在是没有延迟渲染的,在Firebug的HTML面板中会看到如下图所示的情况,所有的标签都已经渲染出来了,只是没有显示而已(display:none)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc46aa2e.PNG)
把“deferredRender:false,”屏蔽掉,会在Firebug的HTML面板中看到下图所示的情况,现在只渲染了活动标签页,其他标签页还没渲染。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc47fc68.PNG)
一个网上找到的,在Grid中嵌套Grid的示例:Nested Grids Example
最后更新于:2022-04-01 07:22:56
示例地址:http://mikhailstadnik.com/ext/examples/nested-grid.htm
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc445331.jpg)
很多人需要的,带时间的日期选择器
最后更新于:2022-04-01 07:22:54
链接地址:[http://www.sencha.com/forum/showthread.php?137242](http://www.sencha.com/forum/showthread.php?137242)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569dcdc42e17b.jpg)
如何了解事件中回调函数的参数
最后更新于:2022-04-01 07:22:51
经常碰到有人问:
事件中怎么获取某个对象?
事件中的参数有什么用?
我要某个数据,怎么在事件中获取?
其实,要解决这个问题很简单,甚至不用看API,自己去分析一下就好了。要做分析,只要在事件的回调函数内加入以下语句:
console.log(arguments);
然后在Firebug中,就会把所有参数都列出来了。通过在DOM面板对这些对象进行分析,就能获得大部分需要的信息了。如果在这些参数中获取不到需要的对象或者信息,那么就可以考虑根据返回的对象,使用up、down方法找到合适的组件,或者使用全局变量的方法(这个不建议)。如果是要获取Store,可以用getStore,lookup等方法。一般带Store的组件,都会有getStore方法,或直接访问store属性就能获取到Store。而lookup是Ext.data.StoreManager的方法。
如何编写一个使用Store更新复选框的CheckboxGroup的插件
最后更新于:2022-04-01 07:22:49
近日,有网友留言问如何编写一个使用Store更新复选框的CheckboxGroup的插件
我的建议是:
1、从CheckboxGroup扩展
2、在构造函数中绑定Store的refresh方法
3、在refresh的回调函数中,先清理旧的复选框,然后根据Store的数据再调用add方法添加新的复选框。
最终他写出了这个扩展,可以使用,看来该方法可行。