事件是指一个特定的动作,这个动作可以针对HTML元素的,如keydown,keyup,mouseover, mouseout等,也可以是对于其它自定义的动作,如对Ajax异步请求的响应等。在ExtJS中,该如何处理呢?
1. 处理HTML元素的标准事件
HTML元素的标准事件是指mouseover、mousedown、click、blur、focus、change等能够直接对HTML元素发生的事件。在ExtJS中,这些事件的处理可以用如下的代码:
注册一个事件处理函数使用: Ext.get('myElement').on('click', myHandler, myScope) myElement是要注册的元素的ID,click是事件的名称(注意,和HTML元素中的声明on
XXX不同,这里不需要on),myHandler 是处理函数的函数名称,myScope是一个可选的参数,指定处理函数绑定的对象,也就是处理函数的作用域,如果不提供这个参数,则是默认的 window。
撤销一个事件处理函数: Ext.get('myElement').un('click', myHandler, myScope) 参数的意义同上。
ExtJS会根据不同的浏览器进行相应的处理,根本不需要理会用户用的是什么浏览器。根据事件的不同,传给处理函数的参数也会不同,这个只能参考ExtJS的文档了,必要时还得参考源代码。
2. 处理自定义事件
在ExtJS中使用自定义事件,需要从Ext.util.Observable继承,示例代码如下:
Employee = function(name){ this.name = name; this.addEvents({ "fired" : true, "quit" : true });}Ext.extend(Employee, Ext.util.Observable, { ... });
在这段代码中,定义了一个Employee类,定义了fired和quit两个事件。如何触发这两个事件呢,基类Ext.util.Observable提供了触发自定义事件的方法fireEvent(eventName, arg1, arg2, ... argn), eventName是要触发的时间的名称(不区分大小写),后面的参数arg1,arg2等是要传给事件处理函数的参数。用上面的Employee类做示例,触发quit事件:
this.fireEvent('quit', this);
这行代码将触发quit事件,并将Empolyee类的实例传给quit事件的处理函数,quit事件的订阅可以采用如下代码:
function myHandler1(empolyee){ ... }function myHandler2(empolyee){ ... }var emp = new Empolyee('tom');emp.on('quit', myHandler1);emp.on('quit', myHandler2);
在上面的代码中,为quit事件注册了两个处理函数(myHandler1与myHandler),当quit事件被激发时,将会依次调用myHandler1和myHandler2两个函数。
值得注意的是,不管是HTML元素的标准事件还是自定义事件,如果为某个时间注册了多个处理函数,如前面的例子,如果myHandler1返回false 的话,则会取消在myHandler1之后注册的处理函数的执行,即该事件被取消,从而停止继续执行该事件的处理函数,而这个返回值false会作为事件 激发的结果,返回给empolyee,即:
var result = this.fireEvent('quit', this);if (result === false) { alert('event canceled'); //这里表示事件被某个处理函数取消}else { alert('event complete'); // 这里表示事件执行完毕}
通过Ext的自定义事件的机制,可以实现一对多的观察者模式,也可以实现一对一的绑定模式,这一点,在ExtJS的开发中是很重要的。
参考官方相关实现类:
/** * @class Ext.calendar.form.EventWindow * @extends Ext.Window *A custom window containing a basic edit form used for quick editing of events.
*This window also provides custom events specific to the calendar so that other calendar components can be easily * notified when an event has been edited via this component.
* @constructor * @param {Object} config The config object */Ext.define('Ext.calendar.form.EventWindow', { extend: 'Ext.window.Window', alias: 'widget.eventeditwindow', requires: [ 'Ext.form.Panel', 'Ext.calendar.util.Date', 'Ext.calendar.data.EventModel', 'Ext.calendar.data.EventMappings' ], constructor: function(config) { var formPanelCfg = { xtype: 'form', fieldDefaults: { msgTarget: 'side', labelWidth: 65 }, frame: false, bodyStyle: 'background:transparent;padding:5px 10px 10px;', bodyBorder: false, border: false, items: [{ itemId: 'title', name: Ext.calendar.data.EventMappings.Title.name, fieldLabel: 'Title', xtype: 'textfield', allowBlank: false, emptyText: 'Event Title', anchor: '100%' }, { xtype: 'daterangefield', itemId: 'date-range', name: 'dates', anchor: '100%', fieldLabel: 'When' }] }; if (config.calendarStore) { this.calendarStore = config.calendarStore; delete config.calendarStore; formPanelCfg.items.push({ xtype: 'calendarpicker', itemId: 'calendar', name: Ext.calendar.data.EventMappings.CalendarId.name, anchor: '100%', store: this.calendarStore }); } this.callParent([Ext.apply({ titleTextAdd: 'Add Event', titleTextEdit: 'Edit Event', width: 600, autocreate: true, border: true, closeAction: 'hide', modal: false, resizable: false, buttonAlign: 'left', savingMessage: 'Saving changes...', deletingMessage: 'Deleting event...', layout: 'fit', defaultFocus: 'title', onEsc: function(key, event) { event.target.blur(); // Remove the focus to avoid doing the validity checks when the window is shown again. this.onCancel(); }, fbar: [{ xtype: 'tbtext', text: ' Edit Details...' }, '->', { itemId: 'delete-btn', text: 'Delete Event', disabled: false, handler: this.onDelete, scope: this, minWidth: 150, hideMode: 'offsets' }, { text: 'Save', disabled: false, handler: this.onSave, scope: this }, { text: 'Cancel', disabled: false, handler: this.onCancel, scope: this }], items: formPanelCfg }, config)]); }, // private newId: 10000, // private initComponent: function() { this.callParent(); this.formPanel = this.items.items[0]; this.addEvents({ /** * @event eventadd * Fires after a new event is added * @param {Ext.calendar.form.EventWindow} this * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added */ eventadd: true, /** * @event eventupdate * Fires after an existing event is updated * @param {Ext.calendar.form.EventWindow} this * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated */ eventupdate: true, /** * @event eventdelete * Fires after an event is deleted * @param {Ext.calendar.form.EventWindow} this * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted */ eventdelete: true, /** * @event eventcancel * Fires after an event add/edit operation is canceled by the user and no store update took place * @param {Ext.calendar.form.EventWindow} this * @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled */ eventcancel: true, /** * @event editdetails * Fires when the user selects the option in this window to continue editing in the detailed edit form * (by default, an instance of {@link Ext.calendar.EventEditForm}. Handling code should hide this window * and transfer the current event record to the appropriate instance of the detailed form by showing it * and calling {@link Ext.calendar.EventEditForm#loadRecord loadRecord}. * @param {Ext.calendar.form.EventWindow} this * @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} that is currently being edited */ editdetails: true }); }, // private afterRender: function() { this.callParent(); this.el.addCls('ext-cal-event-win'); Ext.get('tblink').on('click', this.onEditDetailsClick, this); this.titleField = this.down('#title'); this.dateRangeField = this.down('#date-range'); this.calendarField = this.down('#calendar'); this.deleteButton = this.down('#delete-btn'); }, // private onEditDetailsClick: function(e){ e.stopEvent(); this.updateRecord(this.activeRecord, true); this.fireEvent('editdetails', this, this.activeRecord, this.animateTarget); }, /** * Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden. * @param {Ext.data.Record/Object} o Either a {@link Ext.data.Record} if showing the form * for an existing event in edit mode, or a plain object containing a StartDate property (and * optionally an EndDate property) for showing the form in add mode. * @param {String/Element} animateTarget (optional) The target element or id from which the window should * animate while opening (defaults to null with no animation) * @return {Ext.Window} this */ show: function(o, animateTarget) { // Work around the CSS day cell height hack needed for initial render in IE8/strict: var me = this, anim = (Ext.isIE8 && Ext.isStrict) ? null: animateTarget, M = Ext.calendar.data.EventMappings; this.callParent([anim, function(){ me.titleField.focus(true); }]); this.deleteButton[o.data && o.data[M.EventId.name] ? 'show': 'hide'](); var rec, f = this.formPanel.form; if (o.data) { rec = o; this.setTitle(rec.phantom ? this.titleTextAdd : this.titleTextEdit); f.loadRecord(rec); } else { this.setTitle(this.titleTextAdd); var start = o[M.StartDate.name], end = o[M.EndDate.name] || Ext.calendar.util.Date.add(start, {hours: 1}); rec = Ext.create('Ext.calendar.data.EventModel'); rec.data[M.StartDate.name] = start; rec.data[M.EndDate.name] = end; rec.data[M.IsAllDay.name] = !!o[M.IsAllDay.name] || start.getDate() != Ext.calendar.util.Date.add(end, {millis: 1}).getDate(); f.reset(); f.loadRecord(rec); } if (this.calendarStore) { this.calendarField.setValue(rec.data[M.CalendarId.name]); } this.dateRangeField.setValue(rec.data); this.activeRecord = rec; return this; }, // private roundTime: function(dt, incr) { incr = incr || 15; var m = parseInt(dt.getMinutes(), 10); return dt.add('mi', incr - (m % incr)); }, // private onCancel: function() { this.cleanup(true); this.fireEvent('eventcancel', this); }, // private cleanup: function(hide) { if (this.activeRecord && this.activeRecord.dirty) { this.activeRecord.reject(); } delete this.activeRecord; if (hide === true) { // Work around the CSS day cell height hack needed for initial render in IE8/strict: //var anim = afterDelete || (Ext.isIE8 && Ext.isStrict) ? null : this.animateTarget; this.hide(); } }, // private updateRecord: function(record, keepEditing) { var fields = record.fields, values = this.formPanel.getForm().getValues(), name, M = Ext.calendar.data.EventMappings, obj = {}; fields.each(function(f) { name = f.name; if (name in values) { obj[name] = values[name]; } }); var dates = this.dateRangeField.getValue(); obj[M.StartDate.name] = dates[0]; obj[M.EndDate.name] = dates[1]; obj[M.IsAllDay.name] = dates[2]; record.beginEdit(); record.set(obj); if (!keepEditing) { record.endEdit(); } return this; }, // private onSave: function(){ if(!this.formPanel.form.isValid()){ return; } if(!this.updateRecord(this.activeRecord)){ this.onCancel(); return; } this.fireEvent(this.activeRecord.phantom ? 'eventadd' : 'eventupdate', this, this.activeRecord, this.animateTarget); // Clear phantom and modified states. this.activeRecord.commit(); }, // private onDelete: function(){ this.fireEvent('eventdelete', this, this.activeRecord, this.animateTarget); }});