var AutoComplete = Overlay.extend({ Implements: Templatable, attrs: { // 触发元素 trigger: null, classPrefix: 'ui-select', align: { baseXY: [0, '100%'] }, submitOnEnter: true, // 回车是否会提交表单 dataSource: { //数据源,支持 Array, URL, Object, Function value: [], getter: function (val) { var that = this; if ($.isFunction(val)) { return function () { val.apply(that, arguments); }; } return val; } }, locator: 'data', // 输出过滤 filter: null, disabled: false, selectFirst: false, delay: 100, // 以下为模板相关 model: { value: { items: [] }, getter: function (val) { val.classPrefix || (val.classPrefix = this.get('classPrefix')); return val; } }, template: template, footer: '', header: '', html: '{{{label}}}', // 以下仅为组件使用 selectedIndex: null, data: [] }, events: { 'mousedown [data-role=items]': '_handleMouseDown', 'click [data-role=item]': '_handleSelection', 'mouseenter [data-role=item]': '_handleMouseMove', 'mouseleave [data-role=item]': '_handleMouseMove' }, templateHelpers: { // 将匹配的高亮文字加上 hl 的样式 highlightItem: highlightItem, include: include }, parseElement: function () { var that = this; this.templatePartials || (this.templatePartials = {}); $.each(['header', 'footer', 'html'], function (index, item) { that.templatePartials[item] = that.get(item); }); AutoComplete.superclass.parseElement.call(this); }, setup: function () { AutoComplete.superclass.setup.call(this); this._isOpen = false; this._initInput(); // 初始化输入框 this._initDataSource(); // 初始化数据源 this._initFilter(); // 初始化过滤器 this._bindHandle(); // 绑定事件 this._blurHide([$(this.get('trigger'))]); this._tweakAlignDefaultValue(); this.on('indexChanged', function (index) { // scroll current item into view //this.currentItem.scrollIntoView(); var containerHeight = parseInt(this.get('height'), 10); if (!containerHeight) return; var itemHeight = this.items.parent().height() / this.items.length, itemTop = Math.max(0, itemHeight * (index + 1) - containerHeight); this.element.children().scrollTop(itemTop); }); }, show: function () { this._isOpen = true; // 无数据则不显示 if (this._isEmpty()) return; AutoComplete.superclass.show.call(this); }, hide: function () { // 隐藏的时候取消请求或回调 if (this._timeout) clearTimeout(this._timeout); this.dataSource.abort(); this._hide(); }, destroy: function () { this._clear(); if (this.input) { this.input.destroy(); this.input = null; } AutoComplete.superclass.destroy.call(this); }, // Public Methods // -------------- selectItem: function (index) { if (this.items) { if (index && this.items.length > index && index >= -1) { this.set('selectedIndex', index); } this._handleSelection(); } }, setInputValue: function (val) { this.input.setValue(val); }, // Private Methods // --------------- // 数据源返回,过滤数据 _filterData: function (data) { var filter = this.get('filter'), locator = this.get('locator'); // 获取目标数据 data = locateResult(locator, data); // 进行过滤 data = filter.call(this, normalize(data), this.input.get('query')); this.set('data', data); }, // 通过数据渲染模板 _onRenderData: function (data) { data || (data = []); // 渲染下拉 this.set('model', { items: data, query: this.input.get('query'), length: data.length }); this.renderPartial(); // 初始化下拉的状态 this.items = this.$('[data-role=items]').children(); if (this.get('selectFirst')) { this.set('selectedIndex', 0); } // 选中后会修改 input 的值并触发下一次渲染,但第二次渲染的结果不应该显示出来。 this._isOpen && this.show(); }, // 键盘控制上下移动 _onRenderSelectedIndex: function (index) { var hoverClass = this.get('classPrefix') + '-item-hover'; this.items && this.items.removeClass(hoverClass); // -1 什么都不选 if (index === -1) return; this.items.eq(index).addClass(hoverClass); this.trigger('indexChanged', index, this.lastIndex); this.lastIndex = index; }, // 初始化 // ------------ _initDataSource: function () { this.dataSource = new DataSource({ source: this.get('dataSource') }); }, _initInput: function () { this.input = new Input({ element: this.get('trigger'), delay: this.get('delay') }); }, _initFilter: function () { var filter = this.get('filter'); filter = initFilter(filter, this.dataSource); this.set('filter', filter); }, // 事件绑定 // ------------ _bindHandle: function () { this.dataSource.on('data', this._filterData, this); this.input.on('blur', this.hide, this).on('focus', this._handleFocus, this).on('keyEnter', this._handleSelection, this).on('keyEsc', this.hide, this).on('keyUp keyDown', this.show, this).on('keyUp keyDown', this._handleStep, this).on('queryChanged', this._clear, this).on('queryChanged', this._hide, this).on('queryChanged', this._handleQueryChange, this).on('queryChanged', this.show, this); this.after('hide', function () { this.set('selectedIndex', -1); }); // 选中后隐藏浮层 this.on('itemSelected', function () { this._hide(); }); }, // 选中的处理器 // 1. 鼠标点击触发 // 2. 回车触发 // 3. selectItem 触发 _handleSelection: function (e) { var isMouse = e ? e.type === 'click' : false; var index = isMouse ? this.items.index(e.currentTarget) : this.get('selectedIndex'); var item = this.items.eq(index); var data = this.get('data')[index]; if (index >= 0 && item) { this.input.setValue(data.label); this.set('selectedIndex', index, { silent: true }); // 是否阻止回车提交表单 if (e && !isMouse && !this.get('submitOnEnter')) e.preventDefault(); this.trigger('itemSelected', data, item); } }, _handleFocus: function () { this._isOpen = true; }, _handleMouseMove: function (e) { var hoverClass = this.get('classPrefix') + '-item-hover'; this.items.removeClass(hoverClass); if (e.type === 'mouseenter') { var index = this.items.index(e.currentTarget); this.set('selectedIndex', index, { silent: true }); this.items.eq(index).addClass(hoverClass); } }, _handleMouseDown: function (e) { if (IE678) { var trigger = this.input.get('element')[0]; trigger.onbeforedeactivate = function () { window.event.returnValue = false; trigger.onbeforedeactivate = null; }; } e.preventDefault(); }, _handleStep: function (e) { e.preventDefault(); this.get('visible') && this._step(e.type === 'keyUp' ? -1 : 1); }, _handleQueryChange: function (val, prev) { if (this.get('disabled')) return; this.dataSource.abort(); this.dataSource.getData(val); }, // 选项上下移动 _step: function (direction) { var currentIndex = this.get('selectedIndex'); if (direction === -1) { // 反向 if (currentIndex > -1) { this.set('selectedIndex', currentIndex - 1); } else { this.set('selectedIndex', this.items.length - 1); } } else if (direction === 1) { // 正向 if (currentIndex < this.items.length - 1) { this.set('selectedIndex', currentIndex + 1); } else { this.set('selectedIndex', -1); } } }, _clear: function () { this.$('[data-role=items]').empty(); this.set('selectedIndex', -1); delete this.items; delete this.lastIndex; }, _hide: function () { this._isOpen = false; AutoComplete.superclass.hide.call(this); }, _isEmpty: function () { var data = this.get('data'); return !(data && data.length > 0); }, // 调整 align 属性的默认值 _tweakAlignDefaultValue: function () { var align = this.get('align'); align.baseElement = this.get('trigger'); this.set('align', align); } });
var Calendar = Overlay.extend({ attrs: { date: { value: '', getter: function (val) { return val || helper.output.call(this) || new Date(); }, setter: function (val) { if (!val) { val = new Date(); } else if (typeof val === 'string') { val = DateUtil.stringToDate(val, this.get('format')) || val; } return val; } }, trigger: null, triggerType: 'click', output: { value: '', getter: function (val) { val = val ? val : this.get('trigger'); return $(val); } }, mode: 'dates', hideOnSelect: true, time: false, // 是否显示时间,true显示时分,细粒度控制则传入一个对象{hour: true, minute: true, second: false}, format: defaultFormat, // 输出格式 template: tpl.calendar(), align: { getter: function (val) { var trigger = $(this.get('trigger')); var parentNode = $(this.get('parentNode')); var baseElement; var baseXY = [0, 0]; if (trigger && trigger[0]) { baseElement = trigger; baseXY = [0, trigger.height() + 10]; } else if (parentNode && parentNode[0]) { baseElement = parentNode; } return { selfXY: [0, 0], baseElement: baseElement, baseXY: baseXY }; } }, // 底部按钮栏,默认不显示。当time设置后会自动开启。bar开启后,hideOnSelect会自动转为false bar: { display: false, // {clear: true, ok: true} 表示:清除,确定按钮的显示与否 text: defalutBar.text }, disabled: { // date, month, year date: function (date) { return false; }, month: function (date) { return false; }, year: function (date) { return false; } }, i18n: {}, view: 'date' // year: 年选择器;month: 年月选择器;date: 正常的日期选择器 }, events: { 'click [data-role=current-month]': function (e) { this.renderContainer(this.get('mode') === 'months' ? this.get('view') + 's' : 'months'); }, 'click [data-role=current-year]': function (e) { this.renderContainer(this.get('mode') === 'years' ? this.get('view') + 's' : 'years'); }, 'click [data-role=prev-year]': function (e) { this.years.prev(); this.renderContainer(this.get('mode')); }, 'click [data-role=next-year]': function (e) { this.years.next(); this.renderContainer(this.get('mode')); }, 'click [data-role=prev-month]': function (e) { this.months.prev(); this.renderContainer(this.get('mode')); }, 'click [data-role=next-month]': function (e) { this.months.next(); this.renderContainer(this.get('mode')); }, 'click [data-role="btn-clear"]': function (e) { this.output(''); this.hide(); }, 'click [data-role="btn-ok"]': function (e) { this.output(); this.hide(); } }, setup: function () { Calendar.superclass.setup.call(this); var self = this; var container = this.element.find('[data-role=container]'); var d = this.get('date'); var bar = this.get('bar'); var view = this.get('view'); var mode = view + 's'; (function (time) { // 存在时间显示的情况下设置默认格式 if (time) { if (this.get('format') === defaultFormat) { if (time === true) { this.set('time', { hour: true, minute: true }); this.set('format', defaultFormat + ' ' + 'HH:mm'); } else { var arr = []; time.hour = time.hour !== false; time.minute = time.minute !== false; time.second = time.second === true; time.hour && arr.push('HH'); time.minute && arr.push('mm'); time.second && arr.push('ss'); this.set('format', defaultFormat + ' ' + arr.join(':')); } } bar.display = true; } }).call(this, this.get('time')); (function (bar) { // 底部按钮栏控制,开启底部按钮,则选择日期后隐藏日历功能关闭 if (bar === true) { this.set('bar', defalutBar); bar = this.get('bar'); } if (bar.display === true) { bar.display = defalutBar.display; } if (bar.display) { this.set('hideOnSelect', false); container.after(tpl.bar({ bar: bar })); } }).call(this, bar); (function (view) { // 根据模式自动调整输出格式 if (this.get('format') === defaultFormat) { if (view === 'month') { this.set('format', 'yyyy-MM'); } else if (view === 'year') { this.set('format', 'yyyy'); } } if (typeof this.get('date') === 'string') { // 由于date的setter先于setup,而那时format还未自动处理,这里需要额外处理 this.set('date', DateUtil.stringToDate(this.get('date'), this.get('format'))); } }).call(this, view); this._setPosition(); this.enable(); var disabled = this.get('disabled'); this.set('mode', mode); this.dates = new DatePanel({ date: d, week: this.get('i18n').week, weekStart: this.get('weekStart'), disabled: function (date) { return disabled.date.call(self, date) || disabled.month.call(self, date) || disabled.year.call(self, date); }, parentNode: container }).render(); this.months = new MonthPanel({ date: d, months: this.get('i18n').months, disabled: function (date) { return disabled.month.call(self, date) || disabled.year.call(self, date); }, parentNode: container }).render(); this.years = new YearPanel({ date: d, prevPlaceholder: this.get('i18n').prevPlaceholder, nextPlaceholder: this.get('i18n').nextPlaceholder, disabled: function (date) { return disabled.year.call(self, date); }, parentNode: container }).render(); if (this.get('time')) { this.times = new TimePanel({ parentNode: container, display: this.get('time') }).render(); this.times.on('change', function (hour, minute, second) { helper.setDate.call(self, { hour: hour, minute: minute, second: second }); }); } this.dates.on('select', function (now, prev, node) { var d = helper.setDate.call(self, { date: now }); self.renderPannel(); if (node) { self.renderContainer(mode); self.get('hideOnSelect') && mode === 'dates' ? self.output() : self.trigger('selectDate', d); } return false; }); this.months.on('select', function (now, prev, node) { var d = helper.setDate.call(self, { year: now.getFullYear(), month: now.getMonth() }); self.renderPannel(); if (node && node.attr('data-role') === 'set-month') { self.renderContainer(mode); self.get('hideOnSelect') && mode === 'months' ? self.output() : self.trigger('selectMonth', d); } }); this.years.on('select', function (now, prev, node) { var d = helper.setDate.call(self, { year: now.getFullYear() }); self.renderPannel(); if (node && node.attr('data-role') === 'set-year') { self.renderContainer(mode); self.get('hideOnSelect') && mode === 'years' ? self.output() : self.trigger('selectYear', d); } }); }, show: function () { if (!this.rendered) { this.render(); } var value = helper.output.call(this) || this.get('date'); this.set('date', value); this.renderPannel(); this.renderContainer(this.get('mode')); Calendar.superclass.show.call(this); this.trigger('show'); return this; }, renderPannel: function () { var monthPannel = this.element.find('[data-role=current-month]'); var yearPannel = this.element.find('[data-role=current-year]'); var date = this.get('date'); var month = date.getMonth(); var year = date.getFullYear(); monthPannel.text(this.months.get('months')[month]); yearPannel.text(year); }, renderContainer: function (mode) { var bar = this.$('[data-role=bar]'); var d = this.get('date'); this.set('mode', mode); this.dates.element.hide(); this.months.element.hide(); this.years.element.hide(); this.times && this.times.element.hide(); bar.hide(); this.dates.set('date', d); this.months.set('date', d); this.years.set('date', d); if (mode === 'dates') { this.dates.show(); this.times && this.times.show(); bar.show(); } else if (mode === 'months') { this.months.show(); } else if (mode === 'years') { this.years.show(); } return this; }, refresh: function () { this.renderPannel(); this.renderContainer(this.get('mode')); return this; }, enable: function () { var trigger = $(this.get('trigger')); if (trigger && trigger[0]) { var self = this; var event = this.get('triggerType') + '.calendar'; trigger.on(event, function (e) { self.show(); e.preventDefault(); }); this._blurHide(this.get('trigger')); } }, disable: function () { var trigger = $(this.get('trigger')); if (trigger && trigger[0]) { var event = this.get('triggerType') + '.calendar'; trigger.off(event); } }, output: function (val, undef) { var output = this.get('output'); var view = this.get('view'); if (!output.length) { return; } var tagName = output[0].tagName.toLowerCase(); val = (val === null || val === undef) ? this.get('date') : val; var result = val.getDate ? DateUtil.format(val, this.get('format')) : val; output[(tagName === 'input' || tagName === 'textarea') ? 'val' : 'text'](result); if (this.get('hideOnSelect')) { this.hide(); } this.trigger('select' + view.replace(/[a-z]/, function (s) { return s.toUpperCase(); }), typeof val === 'string' ? DateUtil.stringToDate(val) || '' : val); } });
var Select = Overlay.extend({ Implements: Templatable, attrs: { trigger: { value: null, // required getter: function(val) { return $(val).eq(0); } }, classPrefix: 'ui-select', template: template, // 定位配置 align: { baseXY: [0, '100%-1px'] }, // trigger 的 tpl triggerTpl: '<a href="#"></a>', // 原生 select 的属性 name: '', value: '', length: 0, selectedIndex: -1, multiple: false, // TODO disabled: false, maxHeight: null, // 以下不要覆盖 selectSource: null // 原生表单项的引用,select/input }, events: { 'click': function(e){ e.stopPropagation(); }, 'click [data-role=item]': function(e) { var target = $(e.currentTarget); if(!target.data('disabled')){ this.select(target); } }, 'mouseenter [data-role=item]': function(e) { var target = $(e.currentTarget); if(!target.data('disabled')){ target.addClass(getClassName(this.get('classPrefix'), 'hover')); } }, 'mouseleave [data-role=item]': function(e) { var target = $(e.currentTarget); if(!target.data('disabled')){ target.removeClass(getClassName(this.get('classPrefix'), 'hover')); } } }, templateHelpers: { output: function(data) { return data + ''; } }, // 覆盖父类 // -------- initAttrs: function(config, dataAttrsConfig) { Select.superclass.initAttrs.call(this, config, dataAttrsConfig); var selectName, trigger = this.get('trigger'); trigger.addClass(getClassName(this.get('classPrefix'), 'trigger')); if (trigger[0].tagName.toLowerCase() === 'select') { // 初始化 name // 如果 select 的 name 存在则覆盖 name 属性 selectName = trigger.attr('name'); if (selectName) { this.set('name', selectName); } // 替换之前把 select 保存起来 this.set('selectSource', trigger); // 替换 trigger var newTrigger = $(this.get('triggerTpl')).addClass(getClassName(this.get('classPrefix'), 'trigger')); this.set('trigger', newTrigger); this._initFromSelect = true; // 隐藏原生控件 // 不用 hide() 的原因是需要和 arale/validator 的 skipHidden 来配合 trigger.after(newTrigger).css({ position: 'absolute', left: '-99999px', zIndex: -100 }); // trigger 如果为 select 则根据 select 的结构生成 this.set("model", convertSelect(trigger[0], this.get('classPrefix'))); } else { // 如果 name 存在则创建隐藏域 selectName = this.get('name'); if (selectName) { var input = $('input[name="' + selectName + '"]').eq(0); if (!input[0]) { input = $( '<input type="text" id="select-' + selectName.replace(/\./g, '-') + '" name="' + selectName + '" />' ).css({ position: 'absolute', left: '-99999px', zIndex: -100 }).insertAfter(trigger); } this.set('selectSource', input); } // trigger 如果为其他 DOM,则由用户提供 model this.set("model", completeModel(this.get("model"), this.get('classPrefix'))); } }, setup: function() { this._bindEvents(); this._initOptions(); this._initHeight(); this._tweakAlignDefaultValue(); // 调用 overlay,点击 body 隐藏 this._blurHide(this.get('trigger')); Select.superclass.setup.call(this); }, render: function() { Select.superclass.render.call(this); this._setTriggerWidth(); return this; }, destroy: function() { if (this._initFromSelect) { this.get('trigger').remove(); } this.get('selectSource') && this.get('selectSource').remove(); this.element.remove(); Select.superclass.destroy.call(this); }, // 方法接口 // -------- select: function(option) { var selectIndex = getOptionIndex(option, this.options); var oldSelectIndex = this.get('selectedIndex'); this.set('selectedIndex', selectIndex); // 同步 html 到 model var model = this.get('model'); if (oldSelectIndex >= 0) { model.select[oldSelectIndex].selected = false; } if (selectIndex >= 0) { model.select[selectIndex].selected = true; } this.set('model', model); // 如果不是原来选中的则触发 change 事件 if (oldSelectIndex !== selectIndex) { var current = this.options.eq(selectIndex); var previous = this.options.eq(oldSelectIndex); this.trigger('change', current, previous); } this.hide(); return this; }, syncModel: function(model) { this.set("model", completeModel(model, this.get('classPrefix'))); this.renderPartial('[data-role=content]'); // 同步原来的 select syncSelect(this.get('selectSource'), model); // 渲染后重置 select 的属性 this.options = this.$('[data-role=content]').children(); this.set('length', this.options.length); this.set('selectedIndex', -1); this.set('value', ''); var selectIndex = getOptionIndex('[data-selected=true]', this.options); var oldSelectIndex = this.get('selectedIndex'); this.set('selectedIndex', selectIndex); // 重新设置 trigger 宽度 this._setTriggerWidth(); return this; }, getOption: function(option) { var index = getOptionIndex(option, this.options); return this.options.eq(index); }, addOption: function(option) { var model = this.get("model").select; model.push(option); this.syncModel(model); return this; }, removeOption: function(option) { var removedIndex = getOptionIndex(option, this.options), oldIndex = this.get('selectedIndex'), removedOption = this.options.eq(removedIndex); // 删除 option,更新属性 removedOption.remove(); this.options = this.$('[data-role=content]').children(); this.set('length', this.options.length); // 如果被删除的是当前选中的,则选中第一个 if (removedIndex === oldIndex) { this.set('selectedIndex', 0); // 如果被删除的在选中的前面,则选中的索引向前移动一格 } else if (removedIndex < oldIndex) { this.set('selectedIndex', oldIndex - 1); } return this; }, enableOption: function(option) { var index = getOptionIndex(option, this.options); var model = this.get("model").select; model[index].disabled = false; this.syncModel(model); return this; }, disableOption: function(option) { var index = getOptionIndex(option, this.options); var model = this.get("model").select; model[index].disabled = true; this.syncModel(model); return this; }, // set 后的回调 // ------------ _onRenderSelectedIndex: function(index) { if (index === -1) return; var selected = this.options.eq(index), currentItem = this.currentItem, value = selected.attr('data-value'); // 如果两个 DOM 相同则不再处理 if (currentItem && selected[0] === currentItem[0]) { return; } // 设置原来的表单项 var source = this.get('selectSource'); if (source) { if (source[0].tagName.toLowerCase() === 'select') { source[0].selectedIndex = index; } else { source[0].value = value; } } // 处理之前选中的元素 if (currentItem) { currentItem.attr('data-selected', 'false') .removeClass(getClassName(this.get('classPrefix'), 'selected')); } // 处理当前选中的元素 selected.attr('data-selected', 'true') .addClass(getClassName(this.get('classPrefix'), 'selected')); this.set('value', value); // 填入选中内容,位置先找 "data-role"="trigger-content",再找 trigger var trigger = this.get('trigger'); var triggerContent = trigger.find('[data-role=trigger-content]'); if (triggerContent.length) { triggerContent.html(selected.html()); } else { trigger.html(selected.html()); } this.currentItem = selected; }, _onRenderDisabled: function(val) { var className = getClassName(this.get('classPrefix'), 'disabled'); var trigger = this.get('trigger'); trigger[(val ? 'addClass' : 'removeClass')](className); // trigger event var selected = this.options.eq(this.get('selectedIndex')); this.trigger('disabledChange', selected, val); }, // 私有方法 // ------------ _bindEvents: function() { var trigger = this.get('trigger'); this.delegateEvents(trigger, "mousedown", this._triggerHandle); this.delegateEvents(trigger, "click", function(e) { e.preventDefault(); }); this.delegateEvents(trigger, 'mouseenter', function(e) { trigger.addClass(getClassName(this.get('classPrefix'), 'trigger-hover')); }); this.delegateEvents(trigger, 'mouseleave', function(e) { trigger.removeClass(getClassName(this.get('classPrefix'), 'trigger-hover')); }); }, _initOptions: function() { this.options = this.$('[data-role=content]').children(); // 初始化 select 的参数 // 必须在插入文档流后操作 this.select('[data-selected=true]'); this.set('length', this.options.length); }, // trigger 的宽度和浮层保持一致 _setTriggerWidth: function() { var trigger = this.get('trigger'); var width = this.element.outerWidth(); var pl = parseInt(trigger.css('padding-left'), 10); var pr = parseInt(trigger.css('padding-right'), 10); // maybe 'thin|medium|thick' in IE // just give a 0 var bl = parseInt(trigger.css('border-left-width'), 10) || 0; var br = parseInt(trigger.css('border-right-width'), 10) || 0; trigger.css('width', width - pl - pr - bl - br); }, // borrow from dropdown // 调整 align 属性的默认值, 在 trigger 下方 _tweakAlignDefaultValue: function() { var align = this.get('align'); // 默认基准定位元素为 trigger if (align.baseElement._id === 'VIEWPORT') { align.baseElement = this.get('trigger'); } this.set('align', align); }, _triggerHandle: function(e) { e.preventDefault(); if (!this.get('disabled')) { this.get('visible') ? this.hide() : this.show(); } }, _initHeight: function() { this.after('show', function() { var maxHeight = this.get('maxHeight'); if (maxHeight) { var ul = this.$('[data-role=content]'); var height = getLiHeight(ul); this.set('height', height > maxHeight ? maxHeight : ''); ul.scrollTop(0); } }); } });
var Dialog = Overlay.extend({ Implements: Templatable, attrs: { // 模板 template: require('./dialog.handlebars'), // 对话框触发点 trigger: { value: null, getter: function (val) { return $(val); } }, // 统一样式前缀 classPrefix: 'ui-dialog', // 指定内容元素,可以是 url 地址 content: { value: null, setter: function (val) { // 判断是否是 url 地址 if (/^(https?:\/\/|\/|\.\/|\.\.\/)/.test(val)) { this._type = 'iframe'; // 用 ajax 的方式而不是 iframe 进行载入 if (val.indexOf('?ajax') > 0 || val.indexOf('&ajax') > 0) { this._ajax = true; } } return val; } }, // 是否有背景遮罩层 hasMask: true, // 关闭按钮可以自定义 closeTpl: '×', // 默认宽度 width: 500, // 默认高度 height: 300, // iframe 类型时,dialog 的最初高度 initialHeight: 300, // 简单的动画效果 none | fade effect: 'none', // 不用解释了吧 zIndex: Z_INDEX, // 是否自适应高度 autoFit: true, // 默认定位居中 align: { value: { selfXY: ['50%', '50%'], baseXY: ['50%', '42%'] }, getter: function (val) { // 高度超过一屏的情况 // https://github.com/aralejs/dialog/issues/41 if (this.element.height() > $(window).height()) { return { selfXY: ['50%', '0'], baseXY: ['50%', '0'] }; } return val; } } }, parseElement: function () { this.set("model", { classPrefix: this.get('classPrefix') }); Dialog.superclass.parseElement.call(this); this.contentElement = this.$('[data-role=content]'); this.mainElement = this.$('[data-role=main]'); // 必要的样式 this.contentElement.css({ height: '100%', zoom: 1 }); // 关闭按钮先隐藏 // 后面当 onRenderCloseTpl 时,如果 closeTpl 不为空,会显示出来 // 这样写是为了回避 arale.base 的一个问题: // 当属性初始值为''时,不会进入 onRender 方法 // https://github.com/aralejs/base/issues/7 this.$('[data-role=close]').hide(); }, events: { 'click [data-role=close]': function (e) { e.preventDefault(); this.hide(); } }, show: function () { // iframe 要在载入完成才显示 if (this._type === 'iframe') { // ajax 读入内容并 append 到容器中 if (this._ajax) { this._ajaxHtml(); } else { // iframe 还未请求完,先设置一个固定高度 !this.get('height') && this.mainElement.css('height', this.get('initialHeight')); this._showIframe(); } } Dialog.superclass.show.call(this); this.after('show', function() { onshowDialogs.push(this); if(this.get('height')) { var headerHeight = this.get('header') === false ? 0 : this.$('[data-role=header]').outerHeight(); var footerHeight = this.get('footer') === false ? 0 : this.$('[data-role=footer]').outerHeight(); this.mainElement.height(this.get('height') - headerHeight - footerHeight); } }); return this; }, hide: function () { // 把 iframe 状态复原 if (this._type === 'iframe' && this.iframe) { this.iframe.attr({ src: 'about:_blank' }); // 原来只是将 iframe 的状态复原 // 但是发现在 IE6 下,将 src 置为 javascript:''; 会出现 404 页面 this.iframe.remove(); this.iframe = null; } Dialog.superclass.hide.call(this); onshowDialogs.pop(); clearInterval(this._interval); delete this._interval; return this; }, destroy: function () { this.element.remove(); clearInterval(this._interval); return Dialog.superclass.destroy.call(this); }, setup: function () { Dialog.superclass.setup.call(this); this._setupTrigger(); this._setupMask(); this._setupFocus(); toTabed(this.element); toTabed(this.get('trigger')); // 默认当前触发器 this.activeTrigger = this.get('trigger').eq(0); }, // onRender // --- _onRenderHeader: function(val) { this.$('[data-role=header]')[val ? 'show' : 'hide'](); }, _onRenderFooter: function(val) { this.$('[data-role=footer]')[val ? 'show' : 'hide'](); }, _onRenderTitle: function(val) { this.$('[data-role=title]').eq(0).html(val); }, _onRenderClosable: function(val) { this.$('[data-role=close]').eq(0)[val === false ? 'hide' : 'show'](); }, _onRenderContent: function (val) { if (this._type !== 'iframe') { var value; // 有些情况会报错 try { value = $(val); } catch (e) { value = []; } if (value[0]) { this.contentElement.empty().append(value); } else { this.contentElement.empty().html(val); } // #38 #44 this._setPosition(); } }, _onRenderCloseTpl: function (val) { if (val === '') { this.$('[data-role=close]').html(val).hide(); } else { this.$('[data-role=close]').html(val).show(); } }, // 覆盖 overlay,提供动画 _onRenderVisible: function (val) { var effect = this.get('effect'); if(val) { if($.isFunction(effect)) { effect.call(this, this.element); } else { this.element.show(); } } else { this.element.hide(); } }, // 私有方法 // --- // 绑定触发对话框出现的事件 _setupTrigger: function () { this.delegateEvents(this.get('trigger'), 'click', function (e) { e.preventDefault(); // 标识当前点击的元素 this.activeTrigger = $(e.currentTarget); this.show(); }); }, // 绑定遮罩层事件 _setupMask: function () { var that = this; var action = function() { that.hide(); }; this.before('show', function() { this.set('zIndex', Z_INDEX); Z_INDEX += 2; var zIndex = this.get('zIndex'); var hasMask = this.get('hasMask'); if(hasMask) { mask.set('zIndex', zIndex - 1).show(); if(hasMask.hideOnClick) { // 点击遮罩关闭对话框 mask.element.one('click', action); } } }); this.after('hide', function() { var hasMask = this.get('hasMask'); if(hasMask) { mask.hide(); if(hasMask.hideOnClick) { mask.element.off('click', action); } } }); }, // 绑定元素聚焦状态 _setupFocus: function () { this.after('show', function () { this.element.focus(); }); this.after('hide', function () { // 关于网页中浮层消失后的焦点处理 // http://www.qt06.com/post/280/ this.activeTrigger && this.activeTrigger.focus(); }); }, _showIframe: function () { var that = this; // 若未创建则新建一个 if (!this.iframe) { this._createIframe(); } // 开始请求 iframe this.iframe.attr({ src: this._fixUrl(), name: 'dialog-iframe' + new Date().getTime() }); // 因为在 IE 下 onload 无法触发 // http://my.oschina.net/liangrockman/blog/24015 // 所以使用 jquery 的 one 函数来代替 onload // one 比 on 好,因为它只执行一次,并在执行后自动销毁 this.iframe.one('load', function () { // 如果 dialog 已经隐藏了,就不需要触发 onload if (!that.get('visible')) { return; } // 绑定自动处理高度的事件 if (that.get('autoFit')) { clearInterval(that._interval); that._interval = setInterval(function () { that._syncHeight(); }, 300); } that._syncHeight(); that._setPosition(); that.trigger('complete:show'); }); }, _fixUrl: function () { var s = this.get('content').match(/([^?#]*)(\?[^#]*)?(#.*)?/); s.shift(); s[1] = ((s[1] && s[1] !== '?') ? (s[1] + '&') : '?') + 't=' + new Date().getTime(); return s.join(''); }, _createIframe: function () { var that = this; this.iframe = $('<iframe>', { src: 'about:blank', scrolling: 'no', frameborder: 'no', allowTransparency: 'true', css: { border: 'none', width: '100%', display: 'block', height: '100%', overflow: 'hidden' } }).appendTo(this.contentElement); // 给 iframe 绑一个 close 事件 // iframe 内部可通过 window.frameElement.trigger('close') 关闭 Events.mixTo(this.iframe[0]); this.iframe[0].on('close', function () { that.hide(); }); }, _syncHeight: function () { var h; // 如果未传 height,才会自动获取 if (!this.get('height')) { try { this._errCount = 0; h = getIframeHeight(this.iframe) + 'px'; } catch (err) { // 页面跳转也会抛错,最多失败6次 this._errCount = (this._errCount || 0) + 1; if (this._errCount >= 6) { // 获取失败则给默认高度 300px // 跨域会抛错进入这个流程 h = this.get('initialHeight'); clearInterval(this._interval); delete this._interval; } } this.contentElement.css('height', h); // force to reflow in ie6 // http://44ux.com/blog/2011/08/24/ie67-reflow-bug/ this.element[0].className = this.element[0].className; } else { clearInterval(this._interval); delete this._interval; } }, _ajaxHtml: function () { var that = this; this.mainElement.css('height', this.get('initialHeight')); this.contentElement.load(this.get('content'), function () { that._setPosition(); that.mainElement.css('height', ''); that.trigger('complete:show'); }); } });