ExtJS 4中的选取器控件
选取器控件通常具有这样的UI风格:
- 包含一个触发按钮
- 点击触发按钮,在字段输入框下方弹出一个“picker”
- 操作picker,会改变输入框的值
选取器控件的父类是Ext.form.field.Picker,它继承于Trigger,后者继承于Text
这是一个基础的输入框字段,可以用来替换 <input type="text" /> 表单字段,它是很多高级表单字段的基类,例如TextArea、Combo。
Text内置了若干验证机制,例如:allowBlank 、minLength、maxLength、regex ,并且,可以使用vtype、validator提供定制的验证功能
默认情况下,用户在Text输入文字后,就会立即启动验证,可以通过validateOnChange、checkChangeEvents、 checkChangeBuffer对此行为进行控制。
Text提供了以下成员:
配置项/属性/方法 | 说明 |
{disableKeyFilter} | Bolean=false,设置为true可以禁用击键过滤 |
{emptyCls} | String,当字段内容为空时,可以显示一个 emptyText,该配置定义文本的样式 |
{emptyText} |
String,当字段内容为空时,显示一个文本,注意该值默认会提交,设置form.Basic.submit的submitEmptyText选项可以修改此行为 对于支持HTML5的浏览器,将使用HTML5的placeholder属性来显示emptyText,对于老版本的浏览器,则直接显示在value中,这意味着字段原始值被认为是emptyText,特别的,对于密码字段,将会显示一系列的点号 |
{enableKeyEvents} | Boolean=false,设置为true,则代理HTML input元素的键盘事件 |
{enforceMaxLength } | Boolean=false,设置为true,则自动为底层input添加maxLength属性 |
{grow} | Boolean=false,设置为true,则该字段的长度随着其内容多少自动增长或收缩 |
{growAppend} | growAppend='W',如果grow=true,该配置用于添加在value后,以计算字段的目标长度 |
{growMax} | Number=800,自动伸缩的最大值 |
{growMin} | Number=30,自动伸缩的最小值 |
{maskRe} | RegExp,用于过滤无效击键的正则式,已经输入的部分不会过滤 |
{requiredCls} | String='x-form-required-field',必须填写的字段的附加样式 |
{selectOnFocus} | Boolean=false,如果设置为true,该字段获取焦点时,选择已有的字段文本 |
{size} | Number=20,input元素的初始size属性,只有在width没有配置、容器布局不会设置该字段宽度时有效 |
{stripCharsRe} | RegExp,用于从输入中清除不需要内容的正则式,如果配置了该参数,那么匹配的字符序列将从输入框的值中删除 |
验证相关配置项 | |
{allowBlank} | Boolean=true,如果设置为false,则要求value的长度大于0 |
{blankText} | String,allowBlank验证失败时的错误信息文本 |
{maxLength} | Number=Number.MAX_VALUE,最大输入长度 |
{maxLengthText} | String= "The maximum length for this field is {0}" ,maxLength验证失败时的错误信息文本 |
{minLength} | Number=0,最小输入长度 |
{minLengthText} | minLength验证失败时的错误信息文本 |
{regex} | RegExp,用于执行验证的正则式对象,错误信息文本使用regexText 或者invalidText |
{regexText} | String,regex验证失败时的错误信息文本 |
{validator} | Function(Object),自定义的,在字段验证(getErrors)时调用的函数,该函数把当前字段的值作为入参,该函数会比默认验证行为先调用 |
{vtype} | String,在Ext.form.field.VTypes中定义的验证类型 |
{vtypeText} | String,vtype验证失败时的错误信息文本 |
autoSize() | 如果grow=true有效,增长组件宽度以匹配value |
getErrors() |
String[]( Object value )。根据字段的验证规则执行字段验证,value默认为经过处理的原始值。如果出现任何错误,返回包含错误信息的数组。验证规则按以下顺序依次处理:
|
getRawValue( ) | String,获取原始值。覆盖Base的版本。返回input.value,如果input.value==emptyText则忽略 |
processRawValue() | String( String value ) 。覆盖Base的版本Object(Object value )。在准备对原始值进行转换、验证之前,进行必要的预处理。如果配置了stripCharsRe,将对input.value进行处理 |
reset( ) | 覆盖Field的版本。除了重置为初始值外,如果初始值为空,则处理emptyText、emptyCls |
selectText() | void ( [Number start], [Number end] )。选中文本范围 |
⚡autosize | void ( Ext.form.field.Text this, Number width, Object eOpts ) autoSize被调用后触发 |
⚡keydown | void ( Ext.form.field.Text this, Ext.EventObject e, Object eOpts ) ,如果enableKeyEvents=true,则在每次按下按键时触发 |
⚡keypress | void ( Ext.form.field.Text this, Ext.EventObject e, Object eOpts ),如果enableKeyEvents=true,则在每次按键时触发 |
⚡keyup | void ( Ext.form.field.Text this, Ext.EventObject e, Object eOpts ),如果enableKeyEvents=true,则在每次放开按键时触发 |
Ext.form.field.Text的源代码分析如下:
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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
Ext.define('Ext.form.field.Text', { extend:'Ext.form.field.Base', alias: 'widget.textfield', size: 20, growMin : 30, growMax : 800, growAppend: 'W', allowBlank : true, minLength : 0, maxLength : Number.MAX_VALUE, minLengthText : 'The minimum length for this field is {0}', maxLengthText : 'The maximum length for this field is {0}', blankText : 'This field is required', regexText : '', emptyCls : Ext.baseCSSPrefix + 'form-empty-field', requiredCls : Ext.baseCSSPrefix + 'form-required-field', componentLayout: 'textfield', //组件布局方式 valueContainsPlaceholder : false, initComponent: function () { var me = this; me.callParent(); me.addEvents( 'autosize', 'keydown', 'keyup', 'keypress' ); me.addStateEvents('change'); //值改变作为状态事件 me.setGrowSizePolicy(); }, setGrowSizePolicy: function(){ if (this.grow) { this.shrinkWrap |= 1; } }, //初始化事件处理 initEvents : function(){ var me = this, el = me.inputEl; me.callParent(); if(me.selectOnFocus || me.emptyText){ //聚焦选取 me.mon(el, 'mousedown', me.onMouseDown, me); } if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){ //击键过滤 me.mon(el, 'keypress', me.filterKeys, me); } if (me.enableKeyEvents) { //启用了按键事件 me.mon(el, { scope: me, keyup: me.onKeyUp, keydown: me.onKeyDown, keypress: me.onKeyPress }); } }, //判断两值相等:按字符串比较 isEqual: function(value1, value2) { return this.isEqualAsString(value1, value2); }, //值改变时的模板方法 onChange: function() { this.callParent(); this.autoSize(); }, //字段fieldSubTpl的填充数据 getSubTplData: function() { var me = this, value = me.getRawValue(), isEmpty = me.emptyText && value.length < 1, maxLength = me.maxLength, placeholder; if (me.enforceMaxLength) { if (maxLength === Number.MAX_VALUE) { maxLength = undefined; } } else { maxLength = undefined; } if (isEmpty) { if (Ext.supports.Placeholder) { placeholder = me.emptyText; } else { value = me.emptyText; me.valueContainsPlaceholder = true; } } //把必要的数据覆盖到父类版本 return Ext.apply(me.callParent(), { maxLength : maxLength, readOnly : me.readOnly, placeholder : placeholder, value : value, fieldCls : me.fieldCls + ((isEmpty && (placeholder || value)) ? ' ' + me.emptyCls : '') + (me.allowBlank ? '' : ' ' + me.requiredCls) }); }, //渲染后模板 afterRender: function(){ this.autoSize(); this.callParent(); }, //鼠标按下时模板 onMouseDown: function(e){ var me = this; if(!me.hasFocus){ //如果按下鼠标时焦点已经离开当前元素,则注册单次执行的事件句柄,阻止默认行为 me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true }); } }, //对原始值进行预处理 processRawValue: function(value) { var me = this, stripRe = me.stripCharsRe, newValue; if (stripRe) { newValue = value.replace(stripRe, ''); //把匹配正则式的部分剔除 if (newValue !== value) { me.setRawValue(newValue); //设置原始值 value = newValue; } } return value; }, //禁用时模板 onDisable: function(){ this.callParent(); if (Ext.isIE) { //IE特殊处理,禁止选中 this.inputEl.dom.unselectable = 'on'; } }, //启用时模板 onEnable: function(){ this.callParent(); if (Ext.isIE) { this.inputEl.dom.unselectable = ''; } }, //按下键时的模板 onKeyDown: function(e) { this.fireEvent('keydown', this, e);//发布事件 }, //放开按键时的模板 onKeyUp: function(e) { this.fireEvent('keyup', this, e);//发布事件 }, //击键时模板 onKeyPress: function(e) { this.fireEvent('keypress', this, e);//发布事件 }, //重置 reset : function(){ this.callParent(); this.applyEmptyText();//显示空白文本 }, //使用HTML5 placeholder或者input.value显示空白文本 applyEmptyText : function(){ var me = this, emptyText = me.emptyText, isEmpty; if (me.rendered && emptyText) { isEmpty = me.getRawValue().length < 1 && !me.hasFocus; if (Ext.supports.Placeholder) { me.inputEl.dom.placeholder = emptyText; } else if (isEmpty) { me.setRawValue(emptyText); me.valueContainsPlaceholder = true; } if (isEmpty) { me.inputEl.addCls(me.emptyCls); } me.autoSize(); } }, //设置组件布局后 afterFirstLayout: function() { this.callParent(); if (Ext.isIE && this.disabled) { var el = this.inputEl; if (el) { el.dom.unselectable = 'on'; } } }, //聚焦前,清除空白文本 preFocus : function(){ var me = this, inputEl = me.inputEl, emptyText = me.emptyText, isEmpty; me.callParent(arguments); if ((emptyText && !Ext.supports.Placeholder) && (inputEl.dom.value === me.emptyText && me.valueContainsPlaceholder)) { me.setRawValue(''); isEmpty = true; inputEl.removeCls(me.emptyCls); me.valueContainsPlaceholder = false; } else if (Ext.supports.Placeholder) { me.inputEl.removeCls(me.emptyCls); } if (me.selectOnFocus || isEmpty) { inputEl.dom.select(); } }, //处理聚焦 onFocus: function() { var me = this; me.callParent(arguments); if (me.emptyText) { me.autoSize(); } }, //聚焦后,显示空白文本 postBlur : function(){ this.callParent(arguments); this.applyEmptyText(); }, //获取原始值 getRawValue: function() { var me = this, v = me.callParent(); if (v === me.emptyText && me.valueContainsPlaceholder) { v = ''; //空白文本处理 } return v; }, //设置值 setValue: function(value) { var me = this, inputEl = me.inputEl; if (inputEl && me.emptyText && !Ext.isEmpty(value)) { //移除空白文本的样式 inputEl.removeCls(me.emptyCls); me.valueContainsPlaceholder = false; } me.callParent(arguments); me.applyEmptyText(); return me; }, //执行验证 getErrors: function(value) { var me = this, errors = me.callParent(arguments), validator = me.validator, emptyText = me.emptyText, allowBlank = me.allowBlank, vtype = me.vtype, vtypes = Ext.form.field.VTypes, regex = me.regex, format = Ext.String.format, msg; value = value || me.processRawValue(me.getRawValue()); if (Ext.isFunction(validator)) { msg = validator.call(me, value); if (msg !== true) { errors.push(msg); } } if (value.length < 1 || (value === me.emptyText && me.valueContainsPlaceholder)) { if (!allowBlank) { errors.push(me.blankText); } return errors; } if (value.length < me.minLength) { errors.push(format(me.minLengthText, me.minLength)); } if (value.length > me.maxLength) { errors.push(format(me.maxLengthText, me.maxLength)); } if (vtype) { if(!vtypes[vtype](value, me)){ errors.push(me.vtypeText || vtypes[vtype +'Text']); } } if (regex && !regex.test(value)) { errors.push(me.regexText || me.invalidText); } return errors; }, selectText : function(start, end){ var me = this, v = me.getRawValue(), doFocus = true, el = me.inputEl.dom, undef, range; if (v.length > 0) { start = start === undef ? 0 : start; end = end === undef ? v.length : end; if (el.setSelectionRange) { el.setSelectionRange(start, end); } else if(el.createTextRange) { range = el.createTextRange(); //文本选择范围 range.moveStart('character', start); range.moveEnd('character', end - v.length); range.select(); } doFocus = Ext.isGecko || Ext.isOpera; } if (doFocus) { me.focus(); } }, autoSize: function() { var me = this; if (me.grow && me.rendered) { me.autoSizing = true; me.updateLayout(); } }, //组件布局完成后 afterComponentLayout: function() { var me = this, width; me.callParent(arguments); if (me.autoSizing) { width = me.inputEl.getWidth(); if (width !== me.lastInputWidth) { me.fireEvent('autosize', me, width);//触发autosize事件 me.lastInputWidth = width; delete me.autoSizing; } } } }); |
该类简单的为Text添加一个可以触发某种行为的触发按钮(Trigger Button),默认效果如下拉框。触发按钮没有默认行为,子类需要通过实现onTriggerClick()方法来添加行为。
文件上传(File)、选取器(Picker)、数字字段(Number)是该类的直接或者间接子类型。
下面是一个简单的例子:
1 2 3 4 5 6 7 8 |
Ext.define( 'Ext.ux.AlertTrigger', { extend : 'Ext.form.field.Trigger', alias : 'widget.alerttrigger', onTriggerClick : function() { Ext.Msg.alert( 'Message', 'Current value is ' + this.getValue() ); } } ); |
Trigger提供了以下成员:
配置项/属性/方法 | 说明 |
{editable} | Boolean=true,如果设置为false,文本框的内容不得修改,必须通过触发按钮引发的行为设置 |
{hideTrigger} | Boolean=false,隐藏触发按钮,只显示文本框 |
{readOnly} | Boolean=false,隐藏触发按钮,并且禁止编辑 |
{repeatTriggerClick} | Boolean=false,如果设置为true,为触发按钮元素添加ClickRepeater |
{selectOnFocus } | Boolean=false,只有editable=true才有效 |
{triggerBaseCls} | String='x-form-trigger',总是应用到触发按钮的样式 |
{triggerCls } | String,用于装饰触发按钮的额外样式。一般修改此配置以定制触发按钮 |
{triggerNoEditCls } | String='x-trigger-noedit',字段只读或者不可编辑时,文本框的样式 |
{triggerWrapCls } | String= "x-form-trigger-wrap",包裹触发按钮的TABLE元素的样式 |
inputCell | Ext.Element,包裹input的td元素,渲染后设置 |
triggerEl | Ext.CompositeElement,所有触发按钮的复合元素 |
triggerWrap | Ext.Element,包裹整个输入框、触发按钮的TABLE元素 |
getTriggerWidth( ) | Number(),返回触发按钮DIV的总宽度 |
onTriggerClick() |
void ( Ext.EventObject e )。 触发按钮点击时的处理函数,默认不做任何事情,子类必须实现自己的逻辑 |
Ext.form.field.Trigger的源代码分析如下:
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 |
Ext.define('Ext.form.field.Trigger', { extend:'Ext.form.field.Text', alias: ['widget.triggerfield', 'widget.trigger'], //作为组件属性的子元素 childEls: [ { name: 'triggerCell', select: '.' + Ext.baseCSSPrefix + 'trigger-cell' }, { name: 'triggerEl', select: '.' + Ext.baseCSSPrefix + 'form-trigger' }, 'triggerWrap', 'inputCell' ], triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger', triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap', triggerNoEditCls: Ext.baseCSSPrefix + 'trigger-noedit', hideTrigger: false, editable: true, readOnly: false, repeatTriggerClick: false, autoSize: Ext.emptyFn, monitorTab: true, mimicing: false, triggerIndexRe: /trigger-index-(\d+)/, componentLayout: 'triggerfield', initComponent: function() { this.wrapFocusCls = this.triggerWrapCls + '-focus'; this.callParent(arguments); }, //覆盖父类版本:获取HTML标记,用于插入到DOM中 getSubTplMarkup: function() { var me = this, field = me.callParent(arguments);//Base类生成的input元素部分 return '<table id="' + me.id + '-triggerWrap" class="' + Ext.baseCSSPrefix + 'form-trigger-wrap" cellpadding="0" cellspacing="0"><tbody><tr>' + '<td id="' + me.id + '-inputCell" class="' + Ext.baseCSSPrefix + 'form-trigger-input-cell">' + field + '</td>' + me.getTriggerMarkup() + //触发按钮 '</tr></tbody></table>'; }, //生成触发按钮的包装元素的HTML标记 getTriggerMarkup: function() { var me = this, i = 0, hideTrigger = (me.readOnly || me.hideTrigger), triggerCls, triggerBaseCls = me.triggerBaseCls, triggerConfigs = []; if (!me.trigger1Cls) { me.trigger1Cls = me.triggerCls; } //可以指定trigger1Cls、trigger2Cls……等等,可以创建多个触发按钮 for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) { triggerConfigs.push({ tag: 'td', valign: 'top',//顶部对齐 cls: Ext.baseCSSPrefix + 'trigger-cell', style: 'width:' + me.triggerWidth + (hideTrigger ? 'px;display:none' : 'px'), cn: { cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '), role: 'button' } }); } triggerConfigs[i - 1].cn.cls += ' ' + triggerBaseCls + '-last'; return Ext.DomHelper.markup(triggerConfigs); }, //fieldSubTpl填充上下文 getSubTplData: function(){ var me = this, data = me.callParent(), readOnly = me.readOnly === true, editable = me.editable !== false; //添加额外两个数据项 return Ext.apply(data, { editableCls: (readOnly || !editable) ? ' ' + me.triggerNoEditCls : '', readOnly: !editable || readOnly }); }, //带标签的填充上下文 getLabelableRenderData: function() { var me = this, triggerWrapCls = me.triggerWrapCls, result = me.callParent(arguments); return Ext.applyIf(result, { triggerWrapCls: triggerWrapCls, triggerMarkup: me.getTriggerMarkup() }); }, //检查禁用状态 disableCheck: function() { return !this.disabled; }, //渲染前模板 beforeRender: function() { var me = this, triggerBaseCls = me.triggerBaseCls, tempEl; if (!me.triggerWidth) { //从临时元素自动计算触发按钮的宽度 tempEl = Ext.resetElement.createChild({ //resetElement专门用于存放临时元素 style: 'position: absolute;', cls: Ext.baseCSSPrefix + 'form-trigger' }); Ext.form.field.Trigger.prototype.triggerWidth = tempEl.getWidth(); tempEl.remove(); } me.callParent(); if (triggerBaseCls != Ext.baseCSSPrefix + 'form-trigger') { me.addChildEls({ name: 'triggerEl', select: '.' + triggerBaseCls }); } me.lastTriggerStateFlags = me.getTriggerStateFlags(); }, //渲染时模板 onRender: function() { var me = this; me.callParent(arguments); me.doc = Ext.getDoc(); me.initTrigger();//初始化触发按钮 me.triggerEl.unselectable();//禁止触发按钮上的文本选择 }, //触发按钮的初始化 initTrigger: function() { var me = this, triggerWrap = me.triggerWrap, triggerEl = me.triggerEl,//注意triggerEl是符合元素,包含若干按钮 disableCheck = me.disableCheck, els, eLen, el, e, idx; //添加事件处理句柄 if (me.repeatTriggerClick) { //ClickRepeater可以应用到任何元素,在元素点击时触发click事件 //它可以设置触发的最小时间间隔 me.triggerRepeater = new Ext.util.ClickRepeater(triggerWrap, { preventDefault: true, handler: me.onTriggerWrapClick, //点击事件处理 listeners: { mouseup: me.onTriggerWrapMouseup, //鼠标放开事件处理 scope: me }, scope: me }); } else { me.mon(triggerWrap, { click: me.onTriggerWrapClick, mouseup: me.onTriggerWrapMouseup, scope: me }); } //设置触发按钮的样式 triggerEl.setVisibilityMode(Ext.Element.DISPLAY); triggerEl.addClsOnOver(me.triggerBaseCls + '-over', disableCheck, me); els = triggerEl.elements; //所有触发按钮 eLen = els.length; for (e = 0; e < eLen; e++) { el = els[e]; idx = e+1; //为所有触发按钮添加样式:trigger1Cls-over el.addClsOnOver(me['trigger' + (idx) + 'Cls'] + '-over', disableCheck, me); el.addClsOnClick(me['trigger' + (idx) + 'Cls'] + '-click', disableCheck, me); } triggerEl.addClsOnClick(me.triggerBaseCls + '-click', disableCheck, me); }, //获取触发按钮总宽度 getTriggerWidth: function() { var me = this, totalTriggerWidth = 0; if (me.triggerWrap && !me.hideTrigger && !me.readOnly) { totalTriggerWidth = me.triggerEl.getCount() * me.triggerWidth; } return totalTriggerWidth; }, //设置触发按钮隐藏与否,更新布局 setHideTrigger: function(hideTrigger) { if (hideTrigger != this.hideTrigger) { this.hideTrigger = hideTrigger; this.updateLayout(); } }, //设置可编辑性 setEditable: function(editable) { if (editable != this.editable) { this.editable = editable; this.updateLayout();//更新布局 } }, //设置只读性 setReadOnly: function(readOnly) { if (readOnly != this.readOnly) { this.readOnly = readOnly; this.updateLayout();//更新布局 } }, onDestroy: function() { var me = this; //销毁成员变量 Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl'); delete me.doc; me.callParent(); }, onFocus: function() { var me = this; me.callParent(arguments); if (!me.mimicing) { me.bodyEl.addCls(me.wrapFocusCls); me.mimicing = true; me.mon(me.doc, 'mousedown', me.mimicBlur, me, { delay: 10 }); if (me.monitorTab) { me.on('specialkey', me.checkTab, me); } } }, checkTab: function(me, e) { if (!this.ignoreMonitorTab && e.getKey() == e.TAB) { this.triggerBlur(); } }, //用于检查DOM结构是否与组件状态失去同步 getTriggerStateFlags: function () { var me = this, state = 0; if (me.readOnly) { state += 1; } if (me.editable) { state += 2; } if (me.hideTrigger) { state += 4; } return state; }, onBlur: Ext.emptyFn, mimicBlur: function(e) { if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) { this.triggerBlur(e); } }, triggerBlur: function(e) { var me = this; me.mimicing = false; me.mun(me.doc, 'mousedown', me.mimicBlur, me); if (me.monitorTab && me.inputEl) { me.un('specialkey', me.checkTab, me); } Ext.form.field.Trigger.superclass.onBlur.call(me, e); if (me.bodyEl) { me.bodyEl.removeCls(me.wrapFocusCls); } }, validateBlur: function(e) { return true; }, //点击了触发按钮包装元素后的处理: onTriggerWrapClick: function() { var me = this, targetEl, match, triggerClickMethod, event; event = arguments[me.triggerRepeater ? 1 : 0]; if (event && !me.readOnly && !me.disabled) { targetEl = event.getTarget('.' + me.triggerBaseCls, null); match = targetEl && targetEl.className.match(me.triggerIndexRe); if (match) { //调用相应的onTrigger1Click……以支持多个触发按钮 //默认调用onTriggerClick triggerClickMethod = me['onTrigger' + (parseInt(match[1], 10) + 1) + 'Click'] || me.onTriggerClick; if (triggerClickMethod) { triggerClickMethod.call(me, event); } } } }, onTriggerWrapMouseup: Ext.emptyFn, onTriggerClick: Ext.emptyFn }); |
该类是一般“选取器”,例如:下拉列表(ComboBox)、日期控件(Date)、下拉树(TreePicker)等的基类。它提供了以下公共逻辑:
- 点击触发按钮、执行键盘导航后,切换Picker下拉窗的可见性
- 依据配置matchFieldWidth、pickerAlign/pickerOffset来缩放、对齐Picker下拉窗
子类必须实现createPicker(),以提供picker下拉窗的内容。
Picker类提供了以下成员:
配置项/属性/方法 | 说明 |
{matchFieldWidth } | Boolean=true,是否让Picker下拉窗的宽度精确的与字段宽度一致 |
{openCls} | String="x-pickerfield-open",Picker下拉窗打开时,添加到bodyEl的样式 |
{pickerAlign} | String="tl-bl?",Picker下拉窗的默认对齐方式 |
{pickerOffset} | Number[],与 pickerAlign一起使用,设置Picker下拉窗的位置偏移量 |
isExpanded | Boolean,指示当前Picker下拉窗是否打开 |
alignPicker() | protected void(),把Picker下拉窗对齐到INPUT元素 |
collapse( ) | 收起Picker下拉窗 |
expand( ) | 打开Picker下拉窗 |
getPicker( ) | Ext.Component(),得到当前Picker的下拉窗组件,如果必要,调用createPicker()创建之 |
createPicker( ) | 创建并返回一个组件,作为Picker下拉窗。子类必须实现该方法。当前Picker对象也必须作为该组件的构造参数pickerField |
onTriggerClick( ) | 覆盖版本。默认行为是使下拉框组件在打开/收起之间切换 |
⚡collapse | void ( Ext.form.field.Picker field, Object eOpts ) 下拉窗收起时触发 |
⚡expand | void ( Ext.form.field.Picker field, Object eOpts ) 下拉窗展开时触发 |
⚡select | void ( Ext.form.field.Picker field, Object value, Object eOpts ),通过下拉窗口选取了字段值时触发 |
Ext.form.field.Picker的代码分析如下:
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 |
Ext.define('Ext.form.field.Picker', { extend: 'Ext.form.field.Trigger', alias: 'widget.pickerfield', matchFieldWidth: true, pickerAlign: 'tl-bl?', //对齐下拉窗的左上角到inputEl的左下角,?表示尝试按指定方式对齐,但是会收到viewport的约束 openCls: Ext.baseCSSPrefix + 'pickerfield-open', editable: true, initComponent: function() { this.callParent(); //添加额外的事件 this.addEvents( 'expand', 'collapse', 'select' ); }, //初始化必要的事件处理器,该方法会由Renderable在渲染后自动调用,那时组件的DOM结构已经生成 initEvents: function() { var me = this; me.callParent(); //处理打开/收起下拉窗的键盘导航事件 me.keyNav = new Ext.util.KeyNav(me.inputEl, { down: me.onDownArrow, //向下箭头按钮 esc: { handler: me.onEsc, //ESC按键 scope: me, defaultEventAction: false }, scope: me, forceKeyDown: true }); //如果配置为不可编辑,点击输入框亦可打开下拉窗 if (!me.editable) { me.mon(me.inputEl, 'click', me.onTriggerClick, me); } if (Ext.isGecko) { me.inputEl.dom.setAttribute('autocomplete', 'off'); } }, //处理ESC按键 onEsc: function(e) { var me = this; if (me.isExpanded) { //如果是展开的,则收起,停止默认行为和事件传播 me.collapse(); e.stopEvent(); } else { //如果当前字段具有一个Window组件,必须让当前字段失去焦点 //因为Window会在收到ESC时隐藏,此时如果当前字段仍然获得焦点,那么向下箭头按键将导致下拉窗意外显示 if (me.up('window')) { me.blur(); } //如果没有启动焦点管理器,停止默认行为和事件传播 else if ((!Ext.FocusManager || !Ext.FocusManager.enabled)) { e.stopEvent(); } } }, //向下按键 onDownArrow: function(e) { if (!this.isExpanded) { //触发按钮点击逻辑 this.onTriggerClick(); } }, //打开Picker下拉窗 expand: function() { var me = this, bodyEl, picker, collapseIf; if (me.rendered && !me.isExpanded && !me.isDestroyed) { //如果尚未渲染,不执行任何动作 bodyEl = me.bodyEl; picker = me.getPicker(); //获取或者创建下拉窗组件 collapseIf = me.collapseIf; picker.show(); //显示下拉窗 me.isExpanded = true; //设置展开状态 me.alignPicker(); //对齐下拉窗 bodyEl.addCls(me.openCls); //侦听点击和鼠标滚轮事件 me.mon(Ext.getDoc(), { mousewheel: collapseIf, mousedown: collapseIf, scope: me }); //当浏览器窗口大小改变后,重新对齐下拉窗 Ext.EventManager.onWindowResize(me.alignPicker, me); me.fireEvent('expand', me); //发布展开事件 me.onExpand(); //调用模板方法 } }, //打开下拉窗时的模板方法 onExpand: Ext.emptyFn, //对齐下拉窗 alignPicker: function() { var me = this, picker = me.getPicker(); //如果是收起状态,不需要做任何事情 if (me.isExpanded) { if (me.matchFieldWidth) { //匹配当前字段的宽度 picker.setWidth(me.bodyEl.getWidth()); } if (picker.isFloating()) { //如果下拉窗是浮动的(一般都是) me.doAlign(); } } }, //对齐下拉窗 doAlign: function(){ var me = this, picker = me.picker, aboveSfx = '-above', isAbove; //浮动组件,以inputEl为基准进行对齐 me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset); //对齐后,是否位于输入框的上面? isAbove = picker.el.getY() < me.inputEl.getY(); //设置相应的样式 me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx); picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx); }, //收起下拉窗 collapse: function() { if (this.isExpanded && !this.isDestroyed) { var me = this, openCls = me.openCls, picker = me.picker, doc = Ext.getDoc(), collapseIf = me.collapseIf, aboveSfx = '-above'; picker.hide(); //隐藏下拉窗组件 me.isExpanded = false; //设置状态 //移除打开样式 me.bodyEl.removeCls([openCls, openCls + aboveSfx]); picker.el.removeCls(picker.baseCls + aboveSfx); //移除只有在打开时才有意义的监听器 doc.un('mousewheel', collapseIf, me); doc.un('mousedown', collapseIf, me); Ext.EventManager.removeResizeListener(me.alignPicker, me); me.fireEvent('collapse', me); //发布事件 me.onCollapse(); //调用模板 } }, //收起下拉窗时的模板方法 onCollapse: Ext.emptyFn, collapseIf: function(e) { var me = this; //仅匹配以下条件时,收起 if (!me.isDestroyed //当前组件没有被销毁 && !e.within(me.bodyEl, false, true) //且滚轮、鼠标事件不是发生在字段体内、或者子段体的子元素内 && !e.within(me.picker.el, false, true) //且滚轮、鼠标事件不是件发生在下拉窗体内、或者下拉窗体的子元素内 && !me.isEventWithinPickerLoadMask(e)//且滚轮、鼠标事件不是发生在下拉窗的LoadMask中 ){ me.collapse(); } }, //获取或者创建下拉窗组件 getPicker: function() { var me = this; return me.picker || (me.picker = me.createPicker()); }, //子类必须实现 createPicker: Ext.emptyFn, //默认行为:使下拉框组件在打开/收起之间切换 onTriggerClick: function() { var me = this; if (!me.readOnly && !me.disabled) { if (me.isExpanded) { me.collapse(); } else { me.expand(); } me.inputEl.focus(); } }, //模仿失焦 mimicBlur: function(e) { var me = this, picker = me.picker; //忽略下拉窗内部发生的mousedown事件 if (!picker || !e.within(picker.el, false, true) && !me.isEventWithinPickerLoadMask(e)) { me.callParent(arguments); } }, //销毁模板 onDestroy : function(){ var me = this, picker = me.picker; //销毁浏览器窗口大小改变监听 Ext.EventManager.removeResizeListener(me.alignPicker, me); //销毁键盘导航监听 Ext.destroy(me.keyNav); if (picker) { delete picker.pickerField; //防止循环引用 picker.destroy();//销毁下拉窗 } me.callParent(); }, //判断是否下拉窗当前具有一个加载遮罩,并且事件发生在遮罩或者其子元素里面 isEventWithinPickerLoadMask: function(e) { var loadMask = this.picker.loadMask; return loadMask ? e.within(loadMask.maskEl, false, true) || e.within(loadMask.el, false, true) : false; } }); |
取色面板组件:
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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 |
/** * 基于HSV(色相/饱和度/亮度)色彩空间的调色板 */ Ext.define( 'Ext.ux.panel.HSVColorPalette', { alias : 'widget.hsvcolorpalette', extend : 'Ext.panel.Panel', requires : [ 'Ext.Img' ], statics : { hsvImgSize : [ 181, 101 ], sldDivCount : 60, hsImgData : "data:Img/png;base64,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", hvImgData : "data:Img/png;base64,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", crossImgData : "data:Img/gif;base64,R0lGODlhDwAPAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAPAA8AAAIklB8Qx53b4otSUWcvyiz4/4AeQJbmKY4p1HHapBlwPL/uVRsFADs=", spotImgData : "data:image/png;base64,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" }, /** * @cfg {String} mode * 调色模式,支持hsv、hvs */ mode : 'hsv', /** * 所有颜色的数组,每个元素为三元数组,分别为色相(H)、饱和度(S)、明度(V) * 有效值范围分别为0-6, 0-1, 0-1 */ colors : [ [ 0, 0, 0 ] ], currentColorIndex : 0, initialPositioned : false, multiColorsMode : true, multiColorRows : 2, multiColorCols : 6, defaultColor : 'FFFFFF', colorCellCls : Ext.baseCSSPrefix + 'hsv-color-palette-cell', colorCellEmptyCls : Ext.baseCSSPrefix + 'hsv-color-palette-cell-empty', colorCellSelectedCls : Ext.baseCSSPrefix + 'hsv-color-palette-cell-selected', getCurrent : function() { return this.colors[this.currentColorIndex]; }, initComponent : function() { var me = this; me.colors = [ [ 0, 0, 0 ] ]; me.addEvents( { /** * @event * 当前颜色改变时触发该事件 */ curcolorchange : true, /** * @event * 当组件第一次被渲染到预期的位置后,触发该事件 */ initialpositioned : true } ); var sldDivCount = me.statics().sldDivCount; var multiColorCells = []; var multiColorCellsCount = me.multiColorRows * me.multiColorCols; for ( var i = 0; i < multiColorCellsCount; i++ ) { multiColorCells.push( { xtype : 'box', cls : [ me.colorCellCls, me.colorCellEmptyCls ] } ); } Ext.apply( me, { width : 194, minHeight : 160, cls : Ext.baseCSSPrefix + 'hsv-color-palette', bodyCls : Ext.baseCSSPrefix + 'hsv-color-palette-body', frame : false, border : true, layout : { type : 'vbox', align : 'center' }, items : [ { //上半部分部调色板 //包装容器 xtype : 'container', itemId : 'palette', layout : { type : 'hbox' }, items : [ { xtype : 'container', layout : 'fit', cls : Ext.baseCSSPrefix + 'hsv-color-palette-pad-wrapper', items : [ { //取色点 itemId : 'spot', xtype : 'image', src : me.statics().crossImgData, //相对于容器浮动 floating : true, //浮动组件的阴影效果 shadow : false, //只有autoShow为true的浮动组件才会自动显示 autoShow : false }, { //调色板 itemId : 'pad', xtype : 'image', width : me.statics().hsvImgSize[0], height : me.statics().hsvImgSize[1], src : me.mode == 'hsv' ? me.statics().hsImgData : me.statics().hvImgData } ] } ] }, { //调色滑动器 xtype : 'container', itemId : 'sld', layout : { type : 'hbox', align : 'stretch' }, cls : Ext.baseCSSPrefix + 'hsv-color-palette-sld-wrapper', width : 181, margin : '14 0 0 0', height : 20, defaults : { flex : 1, xtype : 'box' }, //覆盖父类方法 xhooks : { initComponent : function() { var sld = this; sld.items = []; for ( var i = 0; i < sldDivCount; i++ ) { sld.items.push( {} ); } sld.items.push( { xtype : 'box', itemId : 'knob', cls : Ext.baseCSSPrefix + 'hsv-color-palette-sld-knob', floating : true, shadow : false } ); sld.callParent( arguments ); } } }, { //下半部分值区域 xtype : 'container', itemId : 'cc', width : 181, margin : '15 0 0 0', padding : '3 0 3 0', hidden : !me.multiColorsMode, cls : Ext.baseCSSPrefix + 'hsv-color-palette-colors-container', layout : { type : 'table', columns : me.multiColorCols, tableAttrs : { style : { width : '100%' } }, tdAttrs : { style : { padding : 3 } } }, items : multiColorCells } ] } ); me.callParent( arguments ); }, /** * 得到调色板组件 * @return {} */ getPad : function() { var me = this; return me.queryById( 'pad' ); }, /** * 得到调色板中的取色点组件 * @return {} */ getPadSpot : function() { var me = this; return me.queryById( 'spot' ); }, /** * 得到颜色滑动条组件 * @return {} */ getSld : function() { var me = this; return me.queryById( 'sld' ); }, /** * 得到颜色滑动条中的滑块组件 * @return {} */ getSldKnob : function() { var me = this; return me.queryById( 'knob' ); }, /** * 得到颜色单元格的容器 * @return {} */ getColorContainer : function() { return this.queryById( 'cc' ); }, /** * 得到颜色单元格列表 * @return {} Ext.util.AbstractMixedCollection */ getColorCells : function() { return this.getColorContainer().items; }, //@private getRelevantColorCellIndex : function( e ) { var me = this; var ccs = me.getColorCells(); var len = ccs.getCount(); for ( var i = 0; i < len; i++ ) { var cell = ccs.getAt( i ); if ( e.within( cell.getEl(), false, true ) ) { return i; } } return -1; }, /** * @private * 将RGB色彩转换为HSV色彩 * @param {} r 0-1 红色 * @param {} g 0-1 绿色 * @param {} b 0-1 蓝色 * @return {} [h,s,v] */ rgb2hsv : function( r, g, b ) { if ( r.length == 3 ) { g = r[1]; b = r[2]; r = r[0]; } var n = Math.min( Math.min( r, g ), b ); var v = Math.max( Math.max( r, g ), b ); var m = v - n; if ( m === 0 ) { return [ null, 0, v ]; } var h = r === n ? 3 + ( b - g ) / m : ( g === n ? 5 + ( r - b ) / m : 1 + ( g - r ) / m ); return [ h === 6 ? 0 : h, m / v, v ]; }, /** * @private * 将HSV色彩转换为RGB色彩 * @param {} h 0-6 色相 * @param {} s 0-1 饱和度 * @param {} v 0-1 亮度 * @return {} [r, g, b] */ hsv2rgb : function( h, s, v ) { if ( h === null ) { return [ v, v, v ]; } var i = Math.floor( h ); var f = i % 2 ? h - i : 1 - ( h - i ); var m = v * ( 1 - s ); var n = v * ( 1 - s * f ); switch ( i ) { case 6 : case 0 : return [ v, n, m ]; case 1 : return [ n, v, m ]; case 2 : return [ m, v, n ]; case 3 : return [ m, n, v ]; case 4 : return [ n, m, v ]; case 5 : return [ v, m, n ]; } }, /** * 将RGB颜色转换为十六进制颜色字符串 * @param {} rgb [r, g, b]形式,元素的大小在0-1范围内 * @return {} */ rgb2hex : function( rgb ) { var hex = ( 0x100 | Math.round( 255 * rgb[0] ) ).toString( 16 ).substr( 1 ); hex += ( 0x100 | Math.round( 255 * rgb[1] ) ).toString( 16 ).substr( 1 ); hex += ( 0x100 | Math.round( 255 * rgb[2] ) ).toString( 16 ).substr( 1 ); return hex; }, /** * 将十六进制颜色字符串转换为RGB颜色 * @param {} hex 形如FF0000的6位十六进制数字组成的颜色字符串 * @return {} [r, g, b]形式,元素的大小在0-1范围内 */ hex2rgb : function( hex ) { var r = parseInt( hex.substr( 0, 2 ), 16 ); var g = parseInt( hex.substr( 2, 2 ), 16 ); var b = parseInt( hex.substr( 4, 2 ), 16 ); return [ r / 255, g / 255, b / 255 ]; }, hsv2hex : function( hsv ) { var me = this; var h = hsv[0]; var s = hsv[1]; var v = hsv[2]; return me.rgb2hex( me.hsv2rgb( h, s, v ) ).toUpperCase(); }, hex2hsv : function( hex ) { var me = this; return me.rgb2hsv( me.hex2rgb( hex ) ); }, /** * @private * 获取鼠标位置相对于调色板的位置百分比 * @param {} e 事件对象 * @return {} [xOffset,yOffset] 形式,元素值范围在0-1之间 */ getPadRelativePos : function( e ) { var me = this; var xy = e.getXY(); var p = me.getPad(); var xo = ( xy[0] - p.getPosition()[0] ) / p.getWidth(); if ( xo > 1 ) xo = 1; if ( xo < 0 ) xo = 0; var yo = 1 - ( xy[1] - p.getPosition()[1] ) / p.getHeight(); if ( yo > 1 ) yo = 1; if ( yo < 0 ) yo = 0; return [ xo, yo ]; }, /** * @private * 获取鼠标位置相对于滑块X轴方向的百分比 * @param {} e 事件对象 * @return {} xOffset,值范围在0-1之间 */ getSldRelativeX : function( e ) { var me = this; var xy = e.getXY(); var s = me.getSld(); var xo = ( xy[0] - s.getPosition()[0] ) / s.getWidth(); if ( xo > 1 ) xo = 1; if ( xo < 0 ) xo = 0; return xo; }, /** * @private * 设置取色点的位置 * @param {} xy 页面坐标 */ updatePadSpotByPos : function( xy ) { var me = this; var spot = me.getPadSpot(); spot.show(); spot.setPagePosition( xy[0] - 8, xy[1] - 8 ); }, /** * @private * 更新取色点的位置 */ updatePadSpot : function() { var me = this; var pad = me.getPad(); var xy = pad.getPosition(); var w = pad.getWidth(); var h = pad.getHeight(); xy[0] += w * ( me.getCurrent()[0] / 6 ); xy[1] += h * ( 1 - me.getCurrent()[1] ); me.updatePadSpotByPos( xy ); }, /** * @private * 设置取色滑块的位置 * @param {} xy 页面坐标 */ updateSldKnobByPos : function( x ) { var me = this; var sld = me.getSld(); var knob = me.getSldKnob(); knob.show(); knob.setPagePosition( x - 5, sld.getPosition()[1] - 9 ); }, /** * @private * 更新滑动条滑块 */ updateSldKnob : function() { var me = this; var sld = me.getSld(); var xy = sld.getPosition(); var w = sld.getWidth(); xy[0] += w * me.getCurrent()[2]; me.updateSldKnobByPos( xy[0] ); }, /** * @private * 更新滑动条背景颜色 */ updateSldBg : function() { var me = this; var sld = me.getSld(); var sldDivCount = me.statics().sldDivCount; for ( var i = 0; i < sldDivCount; i++ ) { var div = sld.getComponent( i ); var h = me.getCurrent()[0]; var s = me.getCurrent()[1]; var v = i / sldDivCount; var color = '#' + me.rgb2hex( me.hsv2rgb( h, s, v ) ); div.getEl().setStyle( { backgroundColor : color } ); } }, /** * @private * 更新调色板组件的样式 */ updatePad : function() { var me = this; me.updatePadSpot(); }, /** * @private * 更新滑动条组件的样式 */ updateSld : function() { var me = this; me.updateSldBg(); me.updateSldKnob(); }, /** * @private * 更新颜色容器区的单元格 */ updateCells : function() { var me = this; var colors = me.colors; var ccs = me.getColorCells(); var count = ccs.getCount(); for ( var i = 0; i < count; i++ ) { var cc = ccs.getAt( i ); if ( i > colors.length - 1 ) { cc.addCls( me.colorCellEmptyCls ); cc.removeCls( me.colorCellSelectedCls ); } else { cc.getEl().setStyle( { backgroundColor : '#' + me.hsv2hex( colors[i] ) } ); if ( i == me.currentColorIndex ) { cc.removeCls( me.colorCellEmptyCls ); cc.addCls( me.colorCellSelectedCls ); } else { cc.removeCls( me.colorCellEmptyCls ); cc.removeCls( me.colorCellSelectedCls ); } } } }, /** * @private * 更新当前组件的视觉样式 */ updateView : function() { var me = this; me.updateSld(); me.updatePad(); me.updateCells(); }, /** * @private * 根据取色板的位置信息来更新当前颜色 * @param e 当前事件对象 */ onPadSpotPositionChange : function( e ) { var me = this; var xy = e.getXY(); me.updatePadSpotByPos( xy ); //更新++++ var rxy = me.getPadRelativePos( e ); me.getCurrent()[0] = rxy[0] * 6; me.getCurrent()[1] = rxy[1]; me.fireEvent( 'curcolorchange' ); me.updateView(); }, /** * @private * 根据取色滑块的位置信息来更新当前颜色 * @param e 当前事件对象 */ onSldKnobPositionChange : function( e ) { var me = this; var xy = e.getXY(); me.getCurrent()[2] = me.getSldRelativeX( e ); me.fireEvent( 'curcolorchange' ); me.updateView(); }, //在此模板内完成内部事件句柄的初始化,该模板方法被调用时,组件已经完成渲染 initEvents : function() { var me = this; var pad = me.getPad(); //该面板被移动后,取色点、取色滑块的位置需要被更新 me.on( 'move', function() { me.updateView(); if ( !me.initialPositioned ) { me.initialPositioned = true; me.fireEvent( 'initialpositioned' ) } } ); me.getEl().on( 'mousedown', function( e ) { e.preventDefault(); } ); pad.getEl().on( 'mousedown', function( e ) { e.preventDefault(); } ); pad.getEl().on( 'click', function( e ) { me.onPadSpotPositionChange( e ); } ); var sld = me.getSld(); sld.getEl().on( 'mousedown', function( e ) { e.preventDefault(); } ); sld.getEl().on( 'click', function( e ) { me.onSldKnobPositionChange( e ); } ); var cc = me.getColorContainer(); cc.getEl().on( 'dblclick', function( e ) { var idx = me.getRelevantColorCellIndex( e ); if ( idx > 0 )//第一个颜色禁止删除 { if ( idx >= me.colors.length ) { me.addColor( me.defaultColor ); } else { me.removeColor( idx ); } } } ); cc.getEl().on( 'click', function( e ) { var idx = me.getRelevantColorCellIndex( e ); me.setCurrentColorIndex( idx ); } ); }, onShow : function() { var me = this; me.getPadSpot().show(); me.getSldKnob().show(); me.callParent( arguments ); }, onHide : function() { var me = this; me.getPadSpot().hide(); me.getSldKnob().hide(); me.callParent( arguments ); }, //@private getCurrent : function() { return this.colors[this.currentColorIndex]; }, /** * 获取当前取色器颜色值代码 * @return {} 形如FF0000的6位十六进制数字组成的字符串 */ getCurrentColor : function() { var me = this; return me.hsv2hex( me.getCurrent() ); }, /** * 设置当前取色器颜色值代码 * @param {} hex 形如FF0000的6位十六进制数字组成的字符串 */ setCurrentColor : function( hex ) { if ( !hex ) return; var me = this; me.colors[me.currentColorIndex] = me.hex2hsv( hex ); me.updateView(); }, /** * 设置当前取色器的索引 * @param {} idx */ setCurrentColorIndex : function( idx ) { var me = this; if ( idx >= me.colors.length ) return; me.currentColorIndex = idx; me.updateView(); }, getColors : function() { var me = this; var hexColors = []; for ( var i = 0; i < me.colors.length; i++ ) { var c = me.colors[i]; var h = c[0]; var s = c[1]; var v = c[2]; hexColors[i] = me.rgb2hex( me.hsv2rgb( h, s, v ) ).toUpperCase(); } return hexColors; }, /** * 设置当前取色器颜色值列表 * @param {} hexArray ['FF0000']形式的数组 * @param {} currentIndex 当前颜色的索引 */ setColors : function( hexArray, currentIndex ) { var me = this; if ( !currentIndex ) currentIndex = 0; for ( var i = 0; i < hexArray.length; i++ ) { me.colors[i] = me.rgb2hsv( me.hex2rgb( hexArray[i] ) ); } me.updateView(); }, /** * 添加一个颜色到末尾并设置其为当前颜色 * @param {} hex 'FF0000'形式的颜色代码 */ addColor : function( hex ) { var me = this; me.colors.push( me.hex2hsv( hex ) ); me.fireEvent( 'curcolorchange' ); me.setCurrentColorIndex( me.colors.length - 1 ); }, removeColor : function( idx, noUpdateView ) { var me = this; me.colors.splice( idx, 1 ); me.fireEvent( 'curcolorchange' ); me.setCurrentColorIndex( 0 ); } } ); |
选取器代码:
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 |
/** * 基于HSV(色相/饱和度/亮度)色彩空间的取色器组件 */ Ext.define( 'Ext.ux.form.field.HSVColorPicker', { alias : 'widget.hsvcolorpicker', extend : 'Ext.form.field.Picker', requires : [ 'Ext.ux.panel.HSVColorPalette' ], inputType : 'hidden', triggerCls : Ext.baseCSSPrefix + 'form-hsv-color-trigger', childEls : [ 'colorDisplay' ],//元素作为组件成员变量的声明 colorDisplayCmp : null, /** * @config 是否显示色块的边框 * @type Boolean */ colorDisplayCellBorder : true, multiColorsMode : false, initComponent : function() { var me = this; Ext.apply( me, { matchFieldWidth : false } ); me.callParent( arguments ); }, createPicker : function() { var me = this; var picker = Ext.create( 'Ext.ux.panel.HSVColorPalette', { //必须设置为浮动组件 floating : true, multiColorsMode : me.multiColorsMode, listeners : { initialpositioned : function() { //在选取器第一次定位完毕后,设置其当前颜色值,以渲染取色点、取色滑块以及滑动条颜色 me.setPickerValue(); }, curcolorchange : function() { //回填颜色值 me.setValue( this.getColors() ); } } } ); return picker; }, //多值支持,使用数组作为内部表示 valueToRaw : function( value ) { return value.join(); }, rawToValue : function( rawValue ) { rawValue || ( rawValue = '' ); return rawValue.split( ',' ); }, /** * 设置该控件的值 * @param {} value 要设置的值 * @return {} */ setValue : function( value ) { value || ( value = [] ); if ( !Ext.isArray( value ) ) value = [ value ]; var me = this; var ret = me.callParent( arguments ); if ( me.colorDisplayCmp ) me.updateColorDisplay(); return ret; }, /** * 更新颜色显示区 */ updateColorDisplay : function() { var me = this; me.colorDisplayCmp.removeAll( true ); var v = me.getValue(); for ( var i = 0; i < v.length; i++ ) { var cell = { style : { backgroundColor : '#' + v[i] } }; if ( !me.colorDisplayCellBorder ) { Ext.apply( cell, { border : 0 } ); } me.colorDisplayCmp.add( cell ); } }, /** * 设置调色板组件的值 */ setPickerValue : function() { var me = this; var value = me.getValue(); if ( value ) me.getPicker().setColors( value ); }, isEventWithinPicker : function( e ) { var me = this; var box = me.picker.getBox(); var x = e.getXY()[0]; var y = e.getXY()[1]; return x >= box.x && x <= ( box.x + box.width ) && y >= box.y && y <= ( box.y + box.height ); }, //覆盖此方法,防止点击到调色板的取色点、取色滑块时导致选取器被隐藏 collapseIf : function( e ) { var me = this; if ( !me.isDestroyed && !e.within( me.bodyEl, false, true ) && !me.isEventWithinPicker( e ) && !me.isEventWithinPickerLoadMask( e ) ) { me.collapse(); } }, //覆盖此方法,添加colorDisplay的点击监听 initEvents : function() { var me = this; me.callParent(); if ( !me.editable ) { me.mon( me.colorDisplay, 'click', function( e ) { me.onTriggerClick(); if ( !me.readOnly && !me.disabled ) { var cdp = me.colorDisplayCmp; var len = cdp.items.getCount(); for ( var i = 0; i < len; i++ ) { var cell = cdp.items.getAt( i ); if ( e.within( cell.getEl(), false, true ) ) { me.getPicker().setCurrentColorIndex( i ); break; } } } } ); } }, //覆盖此方法,改变默认的基于Ext.form.field.Text的表单字段外观 getSubTplMarkup : function() { var me = this; //隐藏字段 var field = Ext.form.field.Text.prototype.getSubTplMarkup.apply( this, arguments ); //添加一个颜色显示的区域 var display = '<div id="' + me.id + '-colorDisplay" class="' + Ext.baseCSSPrefix + 'form-hsv-color-picker-display"></div>'; return [ '<table id="' + me.id + '-triggerWrap" class="' + Ext.baseCSSPrefix + 'form-trigger-wrap" cellpadding="0" cellspacing="0">', ' <tbody>', ' <tr>', ' <td id="' + me.id + '-inputCell" class="' + Ext.baseCSSPrefix + 'form-trigger-input-cell">' + field + display + '</td>', ' ' + me.getTriggerMarkup(), ' </tr>', ' </tbody>', '</table>' ].join( '' ); }, afterRender : function() { var me = this; me.callParent( arguments ); me.colorDisplayCmp = Ext.create( 'Ext.container.Container', { cls : Ext.baseCSSPrefix + 'form-hsv-color-picker-display-container', layout : { type : 'hbox', align : 'stretch', pack : 'start' }, defaults : { xtype : 'box', flex : 1, cls : Ext.baseCSSPrefix + 'form-hsv-color-picker-display-cell' }, listeners : { afterrender : { scope : me, fn : me.updateColorDisplay, single : true } } } ); me.colorDisplayCmp.render( me.colorDisplay ); }, //覆盖此方法,在组件布局执行完毕后更新色块容器的布局 afterComponentLayout : function() { var me = this; me.callParent( arguments ); me.colorDisplayCmp.updateLayout(); }, //覆盖此方法,销毁内部组件 onDestroy : function() { var me = this; me.callParent( arguments ); if(me.colorDisplayCmp) me.colorDisplayCmp.destroy(); }, //覆盖此方法,改变对齐参考元素 doAlign : function() { var me = this, picker = me.picker, aboveSfx = '-above', isAbove; me.picker.alignTo( me.colorDisplay, me.pickerAlign, me.pickerOffset ); isAbove = picker.el.getY() < me.inputEl.getY(); me.bodyEl[isAbove ? 'addCls' : 'removeCls']( me.openCls + aboveSfx ); picker[isAbove ? 'addCls' : 'removeCls']( picker.baseCls + aboveSfx ); } } ); |
CSS样式文件:
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 |
/* HSVColorPalette */ .x-hsv-color-palette { border: 1px solid #8AB4B8; } .x-hsv-color-palette-body { padding-top: 4px; background-image: none; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #D8E6E7), color-stop(100%, #D8E6E7)); background-image: -webkit-linear-gradient(top, #D8E6E7, #E3EDEE); background-image: -moz-linear-gradient(top, #D8E6E7, #E3EDEE); background-image: -o-linear-gradient(top, #D8E6E7, #E3EDEE); background-image: -ms-linear-gradient(top, #D8E6E7, #E3EDEE); background-image: linear-gradient(top, #D8E6E7, #E3EDEE); } .x-hsv-color-palette-pad-wrapper { border-top: 1px solid #888888; border-left: 1px solid #999999; border-right: 1px solid #EEEEEE; border-bottom: 1px solid #DDDDDD; background-color: #C5DADC; } .x-hsv-color-palette-sld-wrapper { border-top: 1px solid #888888; border-left: 1px solid #999999; border-right: 1px solid #DDDDDD; border-bottom: 1px solid #DDDDDD; background-color: #C5DADC; } .x-hsv-color-palette-sld-knob { width: 11px; height: 38px; background-image: url('../../resources/themes/icons/blue-green/hsv-color-panel-knob.png'); } .x-hsv-color-palette-cell { border: 1px solid #D8E6E7; cursor: pointer; height: 14px; } .x-hsv-color-palette-cell-empty { background-image: url('../../resources/themes/images/blue-green/form/hsv-color-picker-display-cell-empty.png'); } .x-hsv-color-palette-cell-selected { border: 2px dotted #D8E6E7; } .x-hsv-color-palette-colors-container { background-color: #D8E6E7; } /* HSVColorPicker */ .x-form-hsv-color-trigger { background-image: url('../../resources/themes/images/blue-green/form/hsv-color-picker-trigger.gif'); } .x-form-hsv-color-picker-display { padding: 3px 0px; border: 1px solid; background-color: white; border-color: #AECBCE; height: 22px; cursor: pointer; } .x-form-hsv-color-picker-display-container { width: 100%; } .x-form-hsv-color-picker-display-cell { border: 1px dotted #C5DADC; height: 14px; margin: 0 1 0 1; } |
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 |
Ext.Loader.setConfig( { enabled : true, paths : { 'Ext.ux' : 'http://192.168.0.89:5050/sshe-static/extjs-4.1.1/ux' } } ); Ext.require( [ 'Ext.ux.form.field.HSVColorPicker' ] ); Ext.onReady( function() { var window = Ext.create( 'Ext.window.Window', { title : '下拉取色器演示', layout : 'fit', width : 360, height : 240, border : false, closable : false, items : { xtype : 'form', bodyPadding : 10, frame : true, border : false, defaults : { anchor : '100%' }, items : [ { xtype : 'fieldset', title : '普通取色器', items : [ { xtype : 'hsvcolorpicker', fieldLabel : '颜色', value : [ 'CC324C', '33CC00', '0033CC', 'AE6FCC', '000000' ], colorDisplayCellBorder : true, multiColorsMode : true, editable : false }, { xtype : 'hsvcolorpicker', fieldLabel : '单值颜色', value : [ 'CC324C' ], colorDisplayCellBorder : true, multiColorsMode : false, editable : false } ] }, { xtype : 'fieldset', title : '表格中的取色器', items : { xtype : 'grid', store : Ext.create( 'Ext.data.ArrayStore', { fields : [ { name : 'Item' }, { name : 'Color' } ], data : [ [ 'Item1', 'CC0000' ] ] } ), columns : [ { text : '项目', sortable : false, dataIndex : 'Item' }, { text : '颜色', sortable : true, dataIndex : 'Color', flex : 1, editor : { xtype : 'hsvcolorpicker', colorDisplayCellBorder : false, multiColorsMode : true, allowBlank : false } } ], plugins : [ Ext.create( 'Ext.grid.plugin.CellEditing', { clicksToEdit : 1 } ) ] } } ] } } ); window.show(); } ) |
运行效果如下(在线演示地址):
Leave a Reply