Example #1
0
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);
  }
});
Example #2
0
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);
    }
});
Example #3
0
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);
            }
        });
    }
});
Example #4
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');
    });
  }

});