置頂 0%

ExtJS 初探

前言

ExtJS 是由 Sencha 公司釋出的 JS Framework,相對於其他前端框架來說,在風格上ExtJS給人一種中規中矩的感覺,而在處裡效率上Extjs要引入的依賴檔案“ext-all.js”太肥了(1.5MB左右),導致載入速度會相對來說較慢,所以正常情況下幾乎不可能在網路上看到Extjs寫的前端網站。另Extjs發光發熱的地方就是在企業辦公自動化(OA)上面,主要是Extjs封裝了大多數會使用到的前端元件(component),且非常適合應用在企業軟體開發,還有Extjs可以輕鬆且快速用程式碼刻出一個畫面(layout),省去了花時間在思考畫面排版及樣式的時間。看到Extjs就讓我回想起當初實習的單位外包給IBM做的系統就是使用ExtJS來開發前端,所以這篇來記錄一些實作上的筆記和重點!

ExtJS特色

  • ExtJS最初是YUI(Yahoo UI)的一個擴充元件,後跳脫並自成一套客戶端的前端框架
  • ExtJS具有多種實用的元件,且讓開發者不須花時間在前端的佈局(layout)
  • Extjs可實現類MVC設計的模式
  • 如果使用ExtJS開發的系統具有盈利的話,就必須要付點費用來取得Ext的企業授權,但只要遵循GPL協定或是非盈利的內部使用的都可以免費(像是公司內部使用、自己學習)。ExtJS授權形式有三種,具體官方說明在: link
  • 目前最新ExtJS的版本是7.2.0 version,但現在版本好像沒有 GPL 授權的免費版, 只能下載 30 天試用版。而其他版本的ExtJS SDK和api文件都可以在下面的連結都可以取得

ExtJS套件安裝

下面是使用 Extjs 4.2.1-gpl.zip 解壓縮出來的Extjs來說明,資料夾內放了很多範例文件和有的沒的東西,但如果只是單純用 ExtJS 來學習或開發的話, 只需將目錄下的 ext-all.js 和 resources\css\ext-all.css 複製到專案目錄裡面,因為如果將解壓縮後的資料夾全部放進專案目錄會造成IDE的loading太重,另外 ext-all-debug.js 是開發階段能來使用的檔案,點開原始檔案可以發現會有很多註解和說明,所以引入ext-all-debug.js能夠在開發階段時顯示相關錯誤訊息

在html中的head的tag中引入剛剛的ExtJs的套件

1
2
3
4
<head>
<script type="text/javascript" src="ext_lib/ext-all.js"></script>
<link rel="stylesheet" type="text/css" href="ext_lib/resources/css/ext-all.css"/>
</head>

相關版本引入說明

開始hello world程式

myFirst.html

1
2
3
4
5
6
7
8
9
<html>
<head>
<script type="text/javascript" src="ext_lib/ext-all.js"></script>
<link rel="stylesheet" href="ext_lib/resources/css/ext-all.css"/>
<script type="text/javascript" src="layout.js"></script>
</head>
<body>
</body>
</html>

layout.js

1
2
3
4
5
6
7
8
Ext.onReady(function(){
Ext.create('Ext.panel.Panel', {
title: 'Sencha App',
bodyPadding:10,
renderTo: Ext.getBody(),
html:'<p>Hello World</p>'
});
}

結果圖

畫面渲染的元件

ExtJs可以替我們省去撰寫Html和css的麻煩,並可以加快開發速度,而且能夠渲染出風格都一致的畫面,主要是Ext提供了許多定義好的元件及版面配製。這邊要注意Ext的大部分可視元件都是繼承Ext.Component類別,像是我們常使用的Container、Button或textfield等元件都是繼承Component,下面紀錄一下常見的元件

  • Container

    • 所有的畫面的最外層都會包一個Container,裡面可以包含layout、items或buttons等屬性的config設定,官方文件都有詳細說明
  • Button

  • Grid

  • Model

    • 取代 Ext.data.Record 成為資料的核心
  • Store

    • Store 就可以直接載入資料。同時 Store 支援 local 與 remote 排序、篩選和分組
    • 可以定義不同 model 間多對多或一對多等的資歷關係

ExtJS事件處理機制

  • ExtJS的事件分為自訂事件和瀏覽器事件
    • 只要控制項是繼承Ext.util.Observable類別就可以擁有自訂事件,並可以設定事件的監聽器
    • 簡單說流程是當某事件被觸發時,Ext會自動幫我們呼叫對應的監聽器,並呼叫事先定義好的事情

範例

Ext.panel.Panel

  • 官方對Panel的定義是一種Container,且可以包含多種功能的元件(Component),並快速建立一個使用者介面,如下圖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Ext.create('Ext.panel.Panel', {
title: 'Panel title',
bodyPadding:10,
collapsible: true,
width: '25%',
height: 200,
html:'<p>test for html property</p><img src=\'https://....png\' />',
renderTo: Ext.getBody(),
items :[
{
xtype:'label',
forId: 'myFieldId',
text: 'please enter your name and email'
},
{
xtype:'textfield',
margin: '10 0 0 0',
fieldLabel : 'Name',
value : 'Kevin'
},
{
xtype:'textfield',
fieldLabel : 'Email',
margin: '10 0 0 0',
vtype: 'email'
}],
buttons: [
{ text: 'Alert',
handler: function() {
Ext.Msg.alert('Alert title', 'you click Alert');
}
},
{ text: 'Exit',
handler: function() {
this.up('panel').close();
}
}]
屬性 說明 備註
title 一個視窗的title 可以不設定title
bodyPadding Panel body裡面的Padding大小 預設值是undefined
collapsible 這個Panel視窗是可以收縮的,會出現在Panel Header的右上角 預設是false
width Panel視窗的寬度 直接給數值就是pixel,如果要給比例就要加上雙引號如’25%’,表示在父類別容器中的寬度佔25%
height Panel視窗的高度 同上
html 在Panel中使用html內容,像是可以加入<img>或<div>等等tag html的內容一定會在所有元件下面,官方定義是HTML content is added after the component is rendered
renderTo 指定這個Panel要渲染到Html的哪一個地方 通常可以直接指定在Html的body中或某個標籤的Id,例如:&nbsp;&nbsp;1.renderTo: document.body &nbsp;&nbsp;2.renderTo: document.getElementById('myDiv') &nbsp;&nbsp;3.renderTo: Ext.getBody()
items 官方定義是A single item, or an array of child Components to be added to this container,表示放入這個Panel的其他子元件 xtype指的是類別的物件?
buttons 就是這個Panel包含的按鈕

ExtJS物件導向實現

  • ExtJS的API支援物件導向的類別設計,讓我們可以快速定義一個具有繼承、封裝和多型的類別
  • new和create差別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//第一個參數是字串類型的類別名稱,第二個參數為object類型,來放入這類別的屬性和方法
Ext.define('demo.Demo', {
extend: 'Ext.Window',//繼承
//mixins: ['demo.Parent'],//多重繼承
title:'newTitle',
config:{//直接產生getTitle()和SetTitle() applyTitle()
title:'newTitle'
},
width: '25%',
name: 'test',
sayHi: function(){
return 'hello' + this.name;
},
initComponent: function(){
Ext.apply(this,{ // we can use Ext.apply to save initial configuration of a component
items:[{
html: 'panel1'
},{
html: 'panel2'
}]
});
this.callParent();//一定是搭配initComponent 才不會有error,調用了父類別的initComponent方法
}
});

重點說明

屬性 說明 備註
callParent() 一個視窗的title callParent簡介
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
Ext.onReady(function(){
//第一個參數是字串類型的類別名稱,第二個參數為object類型,來放入這類別的屬性和方法
/*Ext.define('demo.Demo', {
extend: 'Ext.Window',//繼承
//mixins: ['demo.Parent'],//多重繼承
title:'newTitle',
config:{//直接產生getTitle()和SetTitle() applyTitle()
title:'newTitle'
},
width: '25%',
name: 'test',
sayHi: function(){
return 'hello' + this.name;
},
//https://stackoverflow.com/questions/14492179/to-initcomponent-or-not-to-initcomponent
initComponent: function(){
Ext.apply(this,{ // we can use Ext.apply to save initial configuration of a component
items:[{
html: 'panel1'
},{
html: 'panel2'
}]
});
this.callParent();//一定是搭配initComponent 才不會有error,調用了父類別的initComponent方法
}
});*/

//var t = new demo.Demo();
//t.show();
//Ext.Msg.alert(t.getTitle());

/*var box = new Ext.Component({
el: 'test',
style: 'background-color: red; position: absolute;',
pageX: 100,
pageY: 50,
width: 200,
height: 150
});
box.render();*/

/*var panel1 = Ext.create('Ext.panel.Panel', {
title: 'use Ext.create',
html: 'use Ext.create',
height: 100,
width: 300,
renderTo: Ext.getBody()
})*/

var panel = new Ext.panel.Panel({
el: 'test',
title: 'test title',
//floating: true,//移動開來時候 其他的component會跑上來(浮動)
shadow: true,
draggable: true,
collapsible: true,
html: 'test content',
pageX: 100,
pageY: 50,
width: 200,
height: 150
})
panel.render(document.getElementById('myDiv'));


// http://extjscanred.blogspot.com/2013/10/extutilobservable.html
Ext.define('Dog',{
extend : Ext.util.Observale,
name: undefined,
constructor:function(pName){
this.name = pName;
},
run:function(){
Ext.Msg.alert('event',this.name+ " is runing");
},
eat:function(foodName){
Ext.Msg.alert('event',this.name + " is eat " + foodName);
},
sleep:function(){
Ext.Msg.alert('event',this.name + " is sleep");
}
});

Ext.create(Ext.form.Panel,{
id: 'main-window',
title:'Demo',
width:800,
height:400,
renderTo:Ext.getBody(),
padding:5,
layout:{
type:'vbox',
//align:'middle' //hbox時候才有影響
// Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
// top : Default child items are aligned vertically at the top of the container.
// middle : child items are aligned vertically in the middle of the container.
// bottom : child items are aligned vertically at the bottom of the container.
// stretch : child items are stretched vertically to fill the height of the container.
// stretchmax : child items are stretched vertically to the height of the largest item.
},
items:[
{
xtype : 'textfield',
name: 't1',
fieldLabel: 'input'
},
{
xtype : 'textfield',
fieldLabel: 'input2'
},
{
xtype : 'button',
text : 'fireEvent sleep event',
handler : function(){
blackDog.sleep();
}
}
],
// buttons: [
// {text: 'lock', handler : function(){
// this.up('form').setDisabled(true);
// //Ext.Msg.alert('alert','hello');
// //this.up('form').getForm().findField('t1').setDisabled(true);
// //this.up('panel').down('#t1').setDisabled(true);
// //Ext.getCmp('t1').setDisabled(true);
// //Ext.ComponentQuery.query('textfield[fieldLabel= "input"]').hide();
// }
// }
// ]
});==============here====================
Ext.create(Ext.form.Panel,{

renderTo:Ext.getBody(),
buttonAlign :'center',
bodyStyle: 'border-style: none;',
buttons: [
{text: 'lock', handler : function(){
Ext.getCmp('main-window').setDisabled(true);
}
}
]
});



//程式實現了一個Person類別,有個屬性name,初始化時呼叫this.addEvents()定義三個事件
/*Person = function(name){
this.name = name;
this.addEvents('walk', 'eat');
}

//繼承了Ext.util.Observable的所有屬性
Ext.extend(Person, Ext.util.Observable, {
info: function(event){//自己加入的方法
return this.name + 'is' + event + 'ing';
}
});

var person = new Person('Kevin');
//加入監聽器 on/addListener
person.on('walk', function(){
Ext.Msg.alert('Event', person.name()+'進行當中');
});*/
/*person.addListener('eat', function(){
Ext.Msg.alert('Event', person.name()+'進行當中2');
});


/*var container = new Ext.Viewport({
layout: 'border',
items: [
new Ext.Panel({region: 'north', html: 'hi north'}),
new Ext.Panel({region: 'south', html: 'hi south'}),
new Ext.Panel({region: 'west', html: 'hi west'}),
new Ext.Panel({region: 'east', html: 'hi east'}),
new Ext.Panel({region: 'center', html: 'hi center'})
]
})
container.render(Ext.getBody());*/

Ext.create('Ext.panel.Panel', {
title: 'Panel title',
bodyPadding:10,
collapsible: true,
colsable: true,
width: '25%',
height: 200,
renderTo: document.body,
//html:'<p>test for html property</p><img src=\'https://....png\' />',
items :[
{
xtype:'label',
forId: 'myFieldId',
text: 'please enter your name and email'
},
{
xtype:'textfield',
margin: '10 0 0 0',
fieldLabel : 'Name',
value : 'Kevin'
},
{
xtype:'textfield',
fieldLabel : 'Email',
margin: '10 0 0 0',
vtype: 'email'
}],
buttons: [
{ text: 'Alert',
handler: function() {
Ext.Msg.alert('Alert title', 'you click Alert');
}
},
{ text: 'Test',
handler: function() {
person.fireEvent('walk');//fireEvent()就會觸發事件
}
},
{ text: 'Exit',
handler: function() {
this.up('panel').close();
}
}]
});
// Ext.create('Ext.form.Panel', {
// title: 'Contact Info',
// width: 300,
// bodyPadding: 10,
// renderTo: Ext.getBody(),
// items: [{
// xtype: 'textfield',
// name: 'name',
// fieldLabel: 'Name',
// allowBlank: false // requires a non-empty value
// }, {
// xtype: 'textfield',
// name: 'email',
// fieldLabel: 'Email Address',
// vtype: 'email' // requires value to be a valid email address format
// }, {
// xtype: 'textfield',
// name: 'phone',
// fieldLabel: 'Phone'
// }],
// buttons: [
// { text: 'Button 1', handler: function() {

// var name = this.up('panel').down('textfield[name=name]').getValue();
// var email = this.up('panel').down('textfield[name=email]').getValue();
// var phone = this.up('panel').down('textfield[name=phone]').getValue();
// alert('You clicked the button!'+name);
// store.insert(0,{'name': name, "email":email, "phone":phone});
// }
// }
// ]
// });

// var store = Ext.create('Ext.data.Store', {
// id: 'store',
// storeId:'simpsonsStore',
// fields:['name', 'email', 'phone'],
// data:{'items':[
// { 'name': 'Lisa', "email":"lisa@simpsons.com", "phone":"555-111-1224" },
// { 'name': 'Bart', "email":"bart@simpsons.com", "phone":"555-222-1234" },
// { 'name': 'Homer', "email":"home@simpsons.com", "phone":"555-222-1244" },
// { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254" }
// ]},
// proxy: {
// type: 'memory',
// reader: {
// type: 'json',
// root: 'items'
// }
// }
// });

// Ext.create('Ext.grid.Panel', {
// title: 'Simpsons',
// store: Ext.data.StoreManager.lookup('simpsonsStore'),
// columns: [
// { text: 'Name', dataIndex: 'name' },
// { text: 'Email', dataIndex: 'email', flex: 1 },
// { text: 'Phone', dataIndex: 'phone' }
// ],
// height: 200,
// width: 400,
// renderTo: Ext.getBody()
// });
})

this.callParent(arguments):呼叫父類建構函式,Extjs中規定:在繼承關係中,子類有建構函式,不會自動呼叫父類建構函式,如果需要執行父類建構函式,需要手動呼叫。

Reference