function (require) { var esui = require('esui'); var lib = require('esui/lib'); var u = require('underscore'); var eoo = require('eoo'); var painters = require('esui/painters'); require('esui/Select'); require('esui/Panel'); var TableRichSelector = require('./TableRichSelector'); /** * 控件类 * * @class ui.TableRichSelectorWithFilter * @extends ui.RichSelector */ var TableRichSelectorWithFilter = eoo.create( TableRichSelector, { /** * 控件类型,始终为`"TableRichSelectorWithFilter"` * * @type {string} * @override */ type: 'TableRichSelectorWithFilter', /** * @override */ styleType: 'RichSelector', /** * 在搜索框的旁边增加筛选 * @override */ getSearchBoxHTML: function () { var searchBoxHTML = [ // 搜索区 '<div data-ui="type:Panel;childName:searchBoxArea"', ' class="' + this.helper.getPartClassName('search-wrapper') + '">', ' <div style="float:left" data-ui="type:Select;childName:filter;"></div>', ' <div', ' data-ui="buttonPosition:right;buttonVariants:bordered icon;', ' type:SearchBox;childName:itemSearch;variants:clear-border', ' hide-searched-button;searchMode:instant;">', ' </div>', '</div>' ].join(''); return searchBoxHTML; }, /** * 刷新筛选区 * @public */ refreshFilter: function () { var filter = this.getFilter(); if (filter) { filter.setProperties({datasource: this.filterDatasource}); filter.on('change', u.bind(this.search, this)); } }, /** * 重新渲染视图 * 仅当生命周期处于RENDER时,该方法才重新渲染 * * @param {Array=} 变更过的属性的集合 * @override */ repaint: painters.createRepaint( TableRichSelector.prototype.repaint, { name: 'filterDatasource', paint: function (control, filterDatasource) { control.refreshFilter(); } } ), /** * @override */ initStructure: function () { this.$super(arguments); // 状态筛选,最终调用search函数 var filter = this.getFilter(); filter.extensions[0].activate(); this.addState('with-filter'); }, /** * @override */ search: function (args) { var filterData = []; // 取自带搜索框的值 var searchBox = this.getSearchBox(); if (searchBox) { filterData.push({value: lib.trim(searchBox.getValue())}); } var filterSelect = this.getFilter(); if (filterSelect) { var value = filterSelect.getValue(); if (value && value !== '') { filterData.push({keys: [this.filterField], value: filterSelect.getRawValue()}); } } if (filterData.length) { // 查询,更新数据源 this.queryItem(filterData); // 更新腿部总结果 this.refreshFoot(); // 更新头部总结果 this.refreshHead(); // 更新状态 this.addState('queried'); } // 相当于执行清空操作 else { this.clearQuery(); } }, /** * 获取筛选区Panel * @return {esui.Panel} */ getFilter: function () { return this.getChild('body').getChild('searchBoxArea').getChild('filter'); } } ); esui.register(TableRichSelectorWithFilter); return TableRichSelectorWithFilter; }
function (require) { var $ = require('jquery'); var u = require('underscore'); var esui = require('esui'); var Control = require('esui/Control'); var eoo = require('eoo'); var painters = require('esui/painters'); require('./ImagePanel'); require('esui/Pager'); var ImageList = eoo.create( Control, { /** * 控件类型,始终为 `ImageList` * * @type {string} * @readonly * @override */ type: 'ImageList', /** * 初始化参数 * * @param {Object} options 参数对象 * @override */ initOptions: function (options) { var properties = { datasource: [], // 数据源 row: 2, // 行数 column: 5, // 列数 width: 0, // 宽度,单位px async: false, // 是否异步, needDesc: true, // 是否需要显示图标描述信息 canSelect: true, // 能否选中 operates: '', // 右上角的操作按钮 imageWidth: 100, // 图标长 imageId: 0, // 默认选中的图标ID noneTip: '' // 没有数据时显示的tip }; u.extend(properties, options); // originDatasource 保存一开始传入的datasource properties.originDatasource = u.clone(properties.datasource); this.setProperties(properties); }, /** * 初始化DOM结构 * * @override * @protected */ initStructure: function () { buildDOMStructure.call(this); this.initChildren(); }, /** * 初始化事件 * * @override * @protected */ initEvents: function () { var pager = this.getPager(); var that = this; // 翻页事件 pager.onchangepage = function () { var page = this.get('page'); var pageSize = this.get('pageSize'); that.fire('pagerchange', {page: page, pageSize: pageSize}); // 异步情况下,不执行默认翻页操作 if (that.async) { return; } // 默认翻页事件 var datasource = that.datasource; var ds = datasource.slice((page - 1) * pageSize, page * pageSize); that.getImagePanel().setDatasource(ds); }; // 改变每页大小事件 pager.onchangepagesize = function () { var pageSize = this.get('pageSize'); that.row = pageSize / that.column; that.fire('pagerchange', {page: 1, pageSize: pageSize}); // 异步情况下,不执行默认改变页面大小操作 if (that.async) { return; } // 默认改变页面大小事件 var datasource = that.datasource; this.set('page', 1); var ds = datasource.slice(0, pageSize); that.getImagePanel().setDatasource(ds); }; }, /** * 获取imagepanel * * @return {Object} */ getImagePanel: function () { return this.getChild('imagePanel'); }, /** * 获取pager * * @return {Object} */ getPager: function () { return this.getChild('pager'); }, /** * datasource重新赋值,并刷新 * * @param {Array} [datasource] 数据源 * @param {number} [count] 数据总数,异步下有效 * @param {number} [page] 当前页码,异步下有效 */ setDatasource: function (datasource, count, page) { this.datasource = u.clone(datasource); this.originDatasource = u.clone(datasource); this.count = count; this.page = page; buildDOMStructure.call(this); }, /** * 默认筛选函数,异步下不要调用 * * @param {Object} [arg] 对象或函数 */ filter: function (arg) { var filteredDS = null; var ds = u.clone(this.originDatasource); // arg可以是对象或者函数 // arg是对象时,根据对象包含的属性进行筛选 if (u.isObject(arg)) { var keys = u.keys(arg); // 根据filter中的属性和值进行筛选 filteredDS = u.filter(ds, function (icon) { var value = true; u.each(keys, function (key) { if (arg[key] !== '' && icon[key] !== arg[key]) { value = false; } }); return value; }); } // arg是函数时,将datasource传给这个函数作为参数,此函数必须返回一个结果 else if (u.isFunction(arg)) { filteredDS = arg(ds); } // 不支持其他类型 else { return; } // 将筛选结构重新赋值给datasource,供翻页操作 this.datasource = filteredDS; var pager = this.getPager(); var pageSize = pager.get('pageSize'); pager.set('count', filteredDS.length); pager.set('page', 1); filteredDS = filteredDS.slice(0, pageSize); this.getImagePanel().setDatasource(filteredDS); }, /** * 默认排序函数,异步下不要调用 * * @param {Object} [arg] 对象或函数 */ sort: function (arg) { var sortedDS = null; var ds = this.datasource; // arg可以是对象或者函数 // arg是对象时,根据对象包含的属性进行排序 // arg必须包含'field'、'order'两个属性 if (u.isObject(arg)) { var field = arg.field; var order = arg.order.toLowerCase(); // 根据field排序,升序 sortedDS = u.sortBy(ds, field); // 如果是降序,则反转 if (order === 'desc') { sortedDS.reverse(); } } // arg是函数时,将datasource传给这个函数作为参数,此函数必须返回一个结果 else if (u.isFunction(arg)) { sortedDS = arg(ds); } // 不支持其他类型 else { return; } // 将筛选结构重新赋值给datasource,供翻页操作 this.datasource = sortedDS; var pager = this.getPager(); var pageSize = pager.get('pageSize'); pager.set('page', 1); sortedDS = sortedDS.slice(0, pageSize); this.getImagePanel().setDatasource(sortedDS); }, /** * 重渲染 * * @override * @protected */ repaint: painters.createRepaint( Control.prototype.repaint, { name: 'datasource', paint: function (iconList, datasource) { iconList.setDatasource(datasource); } } ) } ); /** * 创建DOM节点 * * @param {string} [part] part名称 * @param {string} [nodeName] 标签名称 * @param {string} [innerHTML] innerHTML * * @return {string} html */ function create(part, nodeName, innerHTML) { return this.helper.getPartBeginTag(part, nodeName) + innerHTML + this.helper.getPartEndTag(part, nodeName); } /** * 构建DOM结构 */ function buildDOMStructure() { var imagePanel = this.getImagePanel(); var pager = this.getPager(); var pageSize = this.row * this.column; var pageSizes = [pageSize, 2 * pageSize, 5 * pageSize, 10 * pageSize]; // 构造main html if (!imagePanel || !pager) { imagePanel = null; pager = null; var imagePanelWrapper = create.call(this, 'image-panel', 'div', ''); var pagerWrapper = create.call(this, 'pager', 'div', ''); var html = create.call(this, '', 'div', imagePanelWrapper + pagerWrapper); this.main.innerHTML = html; } // ImagePanel appendTo main var ds = this.datasource.slice(0, pageSize); if (imagePanel) { imagePanel.setDatasource(ds); } else { // iconpanel的默认属性 var keys = [ 'needDesc', // 是否需要显示图标描述信息 'canSelect', // 能否选中 'operates', // 右上角的操作按钮 'imageWidth', // 图标长 'datasource', // 数据源 'imageId', // 默认选中的图标ID 'noneTip' // 没有数据时显示的tip ]; // 从this取出iconpanel配置 var properties = u.pick(this, keys); imagePanel = esui.create('ImagePanel', properties); this.addChild(imagePanel, 'imagePanel'); imagePanel.appendTo($('#' + this.helper.getId('image-panel'))[0]); } // Pager appendTo main var page = this.async ? this.page : 1; var count = this.async ? this.count : this.datasource.length; if (pager) { pager.set('count', count); pager.set('page', page); } else { // 根据 row 和 column设置pageSize 和 pageSizes pager = esui.create('Pager', { page: page, count: count, pageSize: pageSize, pageSizes: pageSizes, pageType: 'plain', layout: 'alignRight' }); this.addChild(pager, 'pager'); pager.appendTo($('#' + this.helper.getId('pager'))[0]); } if (this.width > 0) { $(this.main).css(this.width); } } esui.register(ImageList); return ImageList; }
function (require) { require('esui/Panel'); require('esui/TextBox'); require('esui/Select'); // 注册验证类 require('esui/validator/RequiredRule'); require('esui/validator/MaxRule'); require('esui/validator/MinRule'); require('esui/validator/PatternRule'); var esui = require('esui'); var u = require('underscore'); var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var exports = {}; /** * 控件的类型 * @override * @type {String} */ exports.type = 'Slider'; /** * 参数的初始化 * @protected * @override * @param {Object} options [初始化的参数] */ exports.initOptions = function (options) { /** * 默认的属性 * @type {Object} * @type {string} defaults.orientation 滑块的形式 横着为'' 竖着为’vertical‘ * @type {number} defaults.start 起始值 默认是0 * @type {number} defaults.end 结束值 默认是10, * @type {number} defaults.step 滑动杆值的步长默认是1 * @type {number | Arrary} defaults.value 滑动杆的值 默认为min或[min, max] * @type {number} defaults.min 最小值 不能小于start, 无值时与start相同 * @type {number} defaults.max 最大值 不能大于end,无值时与end相同 * @type {string} defaults.unitText 滑动杆内数值后面的单位 * @type {boolean} defaults.isShowSelectedBG 滑杆已选择的部分是否加背景色显示 显示true 不显示false 默认true * @type {boolean} defaults.hasHead 是否显示标题和头部 显示true 不显示false * @type {string} defaults.title 滑动杆的头部标题 * @type {string} defaults.headType 默认label 还可以是textbox、select * @type {string} defaults.pattern 文本框时验证的正则表达式 * @type {string} defaults.errorMessage 正则验证错误时的提示信息 * @type {Array} defaults.datasource 下拉框时的数据源 * @type {boolean} defaults.hasFoot 是否有脚 有true 无false 默认false * @type {number} defaults.footStep 显示角标的间隔 默认为2 * @type {Number} defaults.footLiWidth 每个角标的宽度 * @type {boolean} defaults.range 滑动杆控件是否是选择区间 默认false 是true */ var defaults = { orientation: '', start: 0, end: 10, step: 1, unitText: '', isShowSelectedBG: true, hasHead: false, title: '标题', headType: 'label', pattern: '^-?[1-9]\d*|0$', errorMessgae: '输入的值必须为数字', datasource: [], hasFoot: false, footStep: 2, range: false }; var properties = {}; lib.extend(properties, defaults, options); // 字符串转数字 properties.start = +properties.start; properties.end = +properties.end; properties.step = +properties.step; // 处理min和max properties.min = typeof properties.min !== 'undefined' ? properties.min : properties.start; properties.max = typeof properties.max !== 'undefined' ? properties.max : properties.end; // min和max只能在start和end的中间 properties.min = Math.max(properties.min, properties.start); properties.max = Math.min(properties.max, properties.end); // 水平、竖直滑动杆时的设置 if (properties.orientation === 'vertical') { // 竖直滑动杆时参数的设置 properties.leftTop = 'top'; properties.rightBottom = 'bottom'; properties.widthHeight = 'height'; properties.cursorWH = 'height'; properties.pageXY = 'pageY'; } else { // 水平时参数的设置 properties.leftTop = 'left'; properties.rightBottom = 'right'; properties.widthHeight = 'width'; properties.cursorWH = 'width'; properties.pageXY = 'pageX'; } // 适配value的数据 properties = adaptValue.call(this, properties); this.$super([properties]); }; /** * 将字符串类型的值转换成原始格式,复杂类型的输入控件需要重写此接口 * * @param {string} value 要转换的string * @param {Object} properties 参数对象 * @return {Mixed} * @protected */ exports.parseValue = function (value, properties) { if ((properties && properties.range) || this.range) { if (typeof value === 'string') { var arr = value.split(','); return [+arr[0], +arr[1]]; } } return value; }; /** * 适配控件的value * @param {Object} properties 参数 * @return {Object} 适配后的参数 */ function adaptValue (properties) { var value = properties.value; delete properties.value; if (value != null && properties.rawValue == null) { properties.rawValue = this.parseValue(value, properties); } properties.min = typeof properties.min !== 'undefined' ? properties.min : this.min; properties.max = typeof properties.max !== 'undefined' ? properties.max : this.max; if (properties.range || this.range) { // 值类型是区间时 properties.rawValue = typeof properties.rawValue === 'undefined' ? [properties.min, properties.max] : properties.rawValue; // 结果是区间时 properties.minRangeValue = properties.rawValue[0]; properties.maxRangeValue = properties.rawValue[1]; properties.minRangeValue = Math.max(properties.minRangeValue, properties.min); properties.maxRangeValue = Math.min(properties.maxRangeValue, properties.max); // value只能在[min, max]之间 properties.rawValue = [ properties.minRangeValue, properties.maxRangeValue ]; } else { // 值类型是单个值时 properties.rawValue = typeof properties.rawValue === 'undefined' ? properties.min : properties.rawValue; // value只能在min 和 max中间 properties.rawValue = Math.max(properties.rawValue, properties.min); properties.rawValue = Math.min(properties.rawValue, properties.max); } return properties; } /** * 批量设置控件的属性值 * @param {Object} properties 属性值集合 * @override */ exports.setProperties = function (properties) { // 给控件设值的时候适配数据用 if (properties.hasOwnProperty('rawValue')) { properties = adaptValue.call(this, properties); } this.$super([properties]); }; /** * 创建滑动杆的头部, * 有标题、显示值的label或输入框(文本框或者下拉框), * 放在原型里为了可以重写 * @return {Panel} 返回Panel的对象 * @protected */ exports.createHead = function () { if (this.hasHead) { // 有头 var headTpl = '<label for="${headValueDomId}" class="${headLabelClasses}">' + '${title}:' + '</label>'; var headData = { title: this.title, value: this.rawValue, headLabelClasses: this.helper.getPartClasses('head-label').join(), headValueClasses: this.helper.getPartClasses('head-value').join(), headValueDomId: this.helper.getId('head-value'), headValueId: this.id + '-head-value' }; switch (this.headType) { case 'textbox': // textbox时 headTpl += '<div class="${headValueClasses} ${headTextBoxClasses}"' + 'data-ui="id:${headValueId};childName:headValue;type:TextBox;' + 'required:required;value:${value};' + '${max}${min}${pattern}${errorMessgae}">' + '</div>'; headData.headTextBoxClasses = this.helper.getPartClasses('head-textbox').join(); headData.max = typeof this.max !== 'undefined' ? ('max:' + this.max + ';') : ''; headData.min = typeof this.min !== 'undefined' ? ('min:' + this.min + ';') : ''; headData.pattern = this.pattern ? ('pattern:' + this.pattern + ';') : ''; headData.errorMessgae = this.errorMessgae ? ('patternErrorMessage:' + this.errorMessgae + ';') : ''; break; case 'select': // select时 headTpl += '<div class="${headValueClasses} ${headSelectClasses}"' + 'data-ui="id:${headValueId};childName:headValue;type:Select;' + 'value:${value};">' + '</div>'; headData.headSelectClasses = this.helper.getPartClasses('head-select').join(); break; default: // 其它情况都当label处理 headTpl += '<span class="${headValueClasses} ${headSpanClasses}"' + 'id="${headValueDomId}">' + '${value}' + '</span>'; headData.headSpanClasses = this.helper.getPartClasses('head-span').join(); break; } // 滑动杆的数值有单位 if (this.unitText) { headData.unitText = this.unitText; headData.headUnitClasses = this.helper.getPartClasses('head-unit').join(); headTpl += '<span class="${headUnitClasses}">${unitText}</span>'; } var headHtml = lib.format(headTpl, headData); var headDom = this.helper.createPart('head'); headDom.innerHTML = headHtml; this.main.appendChild(headDom); var options = { main: headDom, renderOptions: this.renderOptions }; // 创建个panel来做容器 var panel = esui.create('Panel', options); panel.initChildren(); this.addChild(panel, 'head'); if (this.headType === 'select') { // select时 设置数据源 var select = panel.getChild('headValue'); select.setProperties( { datasource: this.datasource, value: this.value } ); } return panel; } }; /** * 获取头部的元素 * @return {[type]} [description] */ exports.getHeadTarget = function () { if (!this.hasHead) { return null; } var headTarget; if (this.headType === 'label') { // label headTarget = this.helper.getPart('head-value'); } else { // textbox 或 select headTarget = this.getChild('head').getChild('headValue'); } return headTarget; }; /** * 创建滑动杆体 * 有滑块的范围和滑块, * 滑块的范围分为显示的范围、已选的范围 * 滑动杆可能有一个滑块或两个滑块,类型是区间时可能有两个滑块,最大值和最小值 * 任意一个是显示的起始值时显示一个滑块 * 放在原型里是为了可重写 * @protected */ exports.createBody = function () { var bodyElement = this.bodyElement = this.helper.createPart('body'); var cursorElement = this.cursorElement = this.helper.createPart('body-cursor'); bodyElement.appendChild(cursorElement); // 区间时需要两个滑块 if (this.range) { var cursorElementTwo = this.cursorElementTwo = this.helper.createPart('body-cursortwo'); lib.addClass(this.cursorElementTwo, this.helper.getPartClasses('body-cursor')); bodyElement.appendChild(cursorElementTwo); } // 已选择的范围加个背景色 if (this.isShowSelectedBG) { // 已选择的区间元素 var bodySelectedElement = this.bodySelectedElement = this.helper.createPart('body-selected'); bodyElement.appendChild(bodySelectedElement); } this.main.appendChild(bodyElement); // 初始化body内元素的宽度和位置 initBodyElements(this); }; exports.footTemplate = '<li>${text}${unitText}</li>'; /** * 创建滑动杆的脚 * 脚就是一些数值的标尺显示 * 放在原型上是为了可重写 * @protected */ exports.createFoot = function () { if (!this.hasFoot) { return; } var footElement = this.helper.getPart('foot'); // 有就移除 重新创建 if (footElement) { lib.removeNode(footElement); } footElement = this.helper.createPart('foot', 'ul'); var footHtml = ''; // 显示的角标个数为len+1 var len = (this.end - this.start) / this.footStep; // 每个角标的宽度 var footStepWidth = this.width / len; // 控件值后面显示的单位 var unitText = ''; if (this.unitText) { unitText = this.unitText; } // 创建角标的元素 for (var i = 0; i <= len; i++) { var data = { text: this.start + i * this.footStep, unitText: unitText }; footHtml += lib.format(this.footTemplate, data); } footElement.innerHTML = footHtml; this.main.appendChild(footElement); u.each( lib.getChildren(footElement), function (elem, index) { elem.style.left = (index * footStepWidth - lib.getOffset(elem).width / 2) + 'px'; } ) }; /** * 初始化dom结构,仅在第一次渲染的时候使用 * @protected * @override */ exports.initStructure = function () { // 竖直滑动杆时增加样式 if (this.orientation === 'vertical') { lib.addClass(this.main, this.helper.getPartClasses('vertical')[0]); } /\d+/.test(this.size) && (this.main.style[this.widthHeight] = this.size + 'px'); this.createHead(); this.createBody(); this.createFoot(); }; /** * 绑定滑块拖拽的事件 * @private */ function bindCursorEvents() { var body = this.helper.getPart('body'); // 给滑块绑定事件 if (body) { initDragEvents(this, body); } } function returnFalse() { return false; } /** * 根据滑块left或top的值来计算value * @param {number} cursorLeftTop 滑块位置left或top的值 * @return {number} 滑块的值 * @private */ function getValueByLeftTop(cursorLeftTop) { var widthHeight = this.widthHeight; // 滑块的宽度 var cursorWH = getCursorWH(this.cursorElement, widthHeight); // 滑块容器的宽度 var tmpWidthHeight = this[widthHeight]; // 选择的宽度 var selectedWidthHeight = cursorLeftTop + cursorWH / 2; var similarValue = (selectedWidthHeight / tmpWidthHeight) * (this.end - this.start); // 根据步长算值 similarValue = similarValue - similarValue % this.step; var value = this.start + Math.round(similarValue); return value; } /** * 根据值获取滑块的位置 * @param {number} value 滑块的值 * @return {number} 滑块的左侧位置 * @private */ function getLeftTopByValue(value) { var widthHeight = this.widthHeight; var cursorWH = getCursorWH(this.cursorElement, widthHeight); var tmpwidthHeight = this[widthHeight]; var start = this.start; var end = this.end; var cursorLeftTop = (value - start) / (end - start) * tmpwidthHeight - cursorWH / 2; return cursorLeftTop; } /** * 根据值去做相应的调整,包括head里显示、赋值和微调滑块的位置 * 为啥要微调位置,因为你不知道鼠标会停在哪,比如1,2之间跨度太大时 要落到值上 * @param {Slider} slider 滑动杆控件 * @param {number} value 滑动杆的值 * @param {boolean} isSyncValue 需要设置控件的值true 不需要false */ function setByValue(slider, value, isSyncValue) { var cursorElement = slider.cursorElement; var cursorLeftTop; var leftTop = slider.leftTop; var widthHeight = slider.widthHeight; if (slider.range) { var cursorElementTwo = slider.cursorElementTwo; var cursorLeftTopTwo = getLeftTopByValue.call(slider, value[1]); cursorElementTwo.style[leftTop] = cursorLeftTopTwo + 'px'; cursorLeftTop = getLeftTopByValue.call(slider, value[0]); // hack: 默认第一个滑块的z-index是2 第二个滑块的z-index的是3 // 因为区间的值可以是2,2这种,当两个滑块值是这种切最大值时,这时只能滑块1可拖动 // 这时要把它放在第二个滑块上面 if (value[0] === value[1] && value[0] === slider.max) { cursorElement.style.zIndex = 3; cursorElementTwo.style.zIndex = 2; } else { cursorElement.style.zIndex = 2; cursorElementTwo.style.zIndex = 3; } } else { cursorLeftTop = getLeftTopByValue.call(slider, value); } // 调整滑块的位置 cursorElement.style[leftTop] = cursorLeftTop + 'px'; // 已选择的部分加个背景色显示 if (slider.isShowSelectedBG) { var cursorWH = getCursorWH(slider.cursorElement, widthHeight); if (slider.range) { slider.bodySelectedElement.style[leftTop] = cursorLeftTop + cursorWH / 2 + 'px'; slider.bodySelectedElement.style[widthHeight] = cursorLeftTopTwo - cursorLeftTop + 'px'; } else { slider.bodySelectedElement.style[widthHeight] = cursorLeftTop + cursorWH / 2 + 'px'; } } // 内部文本框或者下拉框触发的此操作 不需要再同步它们的值了 别的都需要 if (isSyncValue) { syncValue(slider, value); } } /** * 绑定头部的事件 * @param {Slider} slider slider控件对象 */ function bindHeadEvents(slider) { if (!slider.hasHead) { return; } // 获取头元素 var headTarget = slider.getHeadTarget(); if (headTarget.type === 'TextBox') { headTarget.on('blur', blurHandler, slider); } else if (headTarget.type === 'Select') { headTarget.on('change', changeHandler, slider); } } /** * 解除头部事件的绑定 * @param {Slider} slider slider控件对象 */ function unbindHeadEvents(slider) { if (!slider.hasHead) { return; } // 获取头部的元素 var headTarget = slider.getHeadTarget(); if (headTarget.type === 'TextBox') { headTarget.un('blur'); } else if (headTarget.type === 'Select') { headTarget.un('change'); } } /** * 同步控件内数值的显示 * @param {Slider} slider 滑杆对象 * @param {number} value 滑杆的值 */ function syncValue(slider, value) { // 同步头部的值 if (slider.hasHead) { // 获取头元素 var headTarget = slider.getHeadTarget(); if (headTarget instanceof InputControl) { // 先去掉绑定的事件 防止循环调用 unbindHeadEvents(slider); headTarget.setProperties( { value: value } ); // 再捆绑事件 bindHeadEvents(slider); } else { // label时 headTarget.innerHTML = value; } } } /** * 鼠标移动的事件 * @param {Event} e 事件对象 * @param {boolean} isMouseUp 是否是鼠标松开的触发 是为true 不是为false * @return {number} 返回value 让mouseup用 * @private */ function mousemoveHandler(e, isMouseUp) { var target = this.activeCursorElement; var cursorElement = this.cursorElement; var mousePos = lib.event.getMousePosition(e); var pageXY = this.pageXY; var leftTop = this.leftTop; var widthHeight = this.widthHeight; // 拖动的滑块距left的值 var cursorLeftTop; // 滑块区间的时候 if (this.range) { // 拖动的是否是第一个滑块 var isFirst = false; // 另外一个滑块的left var otherLeftTop; // 另一个滑块的值 var otherValue; // 滑块是第一个时 if (target.id === cursorElement.id) { otherLeftTop = getLeftTopByValue.call(this, this.maxRangeValue); otherValue = this.maxRangeValue; isFirst = true; cursorLeftTop = Math.max( this.minStartPos - this.startPos, mousePos[pageXY] - this.startPos ); cursorLeftTop = Math.min(cursorLeftTop, otherLeftTop); } else { // 滑块是第二个时 otherLeftTop = getLeftTopByValue.call(this, this.minRangeValue); otherValue = this.minRangeValue; cursorLeftTop = Math.max(otherLeftTop, mousePos[pageXY] - this.startPos); cursorLeftTop = Math.min(cursorLeftTop, this.maxEndPos - this.startPos); } } else { target = cursorElement; cursorLeftTop = Math.max( this.minStartPos - this.startPos, mousePos[pageXY] - this.startPos ); cursorLeftTop = Math.min( cursorLeftTop, this.maxEndPos - this.startPos ); } // 根据left来计算值 var value; var curValue = getValueByLeftTop.call(this, cursorLeftTop); if (this.range) { if (isFirst) { value = [curValue, otherValue]; } else { value = [otherValue, curValue]; } } else { value = curValue; } if (!isMouseUp) { // 避免抖动,这里根据value值重新计算出leftTop cursorLeftTop = getLeftTopByValue.call(this, curValue); target.style[this.leftTop] = cursorLeftTop + 'px'; // 已选择的部分加个背景色显示 if (this.isShowSelectedBG) { if (this.range) { var tmpWidthHeight; if (isFirst) { this.bodySelectedElement.style[leftTop] = cursorLeftTop + 'px'; tmpWidthHeight = otherLeftTop - cursorLeftTop; } else { this.bodySelectedElement.style[leftTop] = otherLeftTop + 'px'; tmpWidthHeight = cursorLeftTop - otherLeftTop; } this.bodySelectedElement.style[widthHeight] = tmpWidthHeight + 'px'; } else { var cursorWH = getCursorWH(this.cursorElement, widthHeight); this.bodySelectedElement.style[widthHeight] = cursorLeftTop + cursorWH / 2 + 'px'; } } // 滑动的时候触发move事件 this.fire('move', value); } // 同步头部的数值显示 syncValue(this, value); return value; } /** * 鼠标的松开事件 * @param {Event} e 事件的对象 * @private */ function mouseupHandler(e) { // 去掉active的样式 lib.removeClass( this.activeCursorElement, this.helper.getPartClasses('body-cursor-active')[0] ); // 放开和mousemove时做得事是一样的,再做一遍 var value = mousemoveHandler.call(this, e, true); // 设置控件的值,因为是内部设值不涉及重绘,所以不调set*方法了 this.rawValue = value; this.minRangeValue = value[0]; this.maxRangeValue = value[1]; setByValue(this, value, true); // 放开鼠标的时候触发change事件 this.fire('change', value); var doc = document; this.helper.removeDOMEvent(doc, 'mouseup', mouseupHandler); this.helper.removeDOMEvent(doc, 'mousemove', mousemoveHandler); // 清除浏览器select的事件 e.preventDefault(); } /** * 初始化body内元素的坐标和宽度 * @param {Slider} slider 滑动杆控件 */ function initBodyElements(slider) { var bodyElement = slider.bodyElement; var cursorElement = slider.cursorElement; // 获取滑块容器的位置 var bodyPos = lib.getOffset(bodyElement); var leftTop = slider.leftTop; var rightBottom = slider.rightBottom; var widthHeight = slider.widthHeight; // 获取滑块容器的宽度,用来计算值用 slider[widthHeight] = bodyPos[widthHeight]; // 滑块能去的最左边 if (typeof slider.min !== 'undefined') { var minLeftTop = getLeftTopByValue.call(slider, slider.min); // 滑块所能去的最左边 slider.minStartPos = bodyPos[leftTop] + minLeftTop; // 滑竿范围的最左边 slider.startPos = bodyPos[leftTop]; } // 滑块能去的最右侧 if (typeof slider.max !== 'undefined') { var maxLeftTop = getLeftTopByValue.call(slider, slider.max); slider.maxEndPos = bodyPos[leftTop] + maxLeftTop; slider.endPos = bodyPos[rightBottom]; } } /** * 根据鼠标位置,寻找离鼠标位置最近的handle * @param {Event} e 事件对象 * @private */ function findNearestCursorElement(e) { var mousePos = lib.event.getMousePosition(e); var pageXY = this.pageXY; var leftTop = this.leftTop; var bodyElement = this.helper.getPart('body'); var bodyPos = lib.getOffset(bodyElement); var mouseLeftTop = mousePos[pageXY] - bodyPos[leftTop]; // 有两个滑块 if (this.range) { var firstLeftTop = getLeftTopByValue.call(this, this.minRangeValue); var secondLeftTop = getLeftTopByValue.call(this, this.maxRangeValue); var middleLeftTop = firstLeftTop + (secondLeftTop - firstLeftTop) / 2; if (mouseLeftTop > middleLeftTop && this.cursorElementTwo) { return this.cursorElementTwo; } } return this.cursorElement; } /** * 获取滑块的宽高,由于页面加载过程中存在css抖动,这个值最好即时获取 */ function getCursorWH(elem, widthHeight) { return parseInt(lib.getStyle(elem, widthHeight), 10); } /** * 鼠标的按下事件 * @param {Event} e 事件对象 * @private */ function mousedownHandler(e) { var cursorElement = findNearestCursorElement.call(this, e); // 存住活动的对象 this.activeCursorElement = cursorElement; // 增加active的样式 lib.addClass(cursorElement, this.helper.getPartClasses('body-cursor-active')[0]); // 点击的时候再初始化各种坐标 为了一些初始化时不在屏幕内的控件 initBodyElements(this); // 禁止鼠标反选 e.preventDefault(); // 滑块首先移动到鼠标点击位置 mousemoveHandler.call(this, e); var doc = document; // 鼠标的松开事件 this.helper.addDOMEvent(doc, 'mouseup', mouseupHandler); // 鼠标的移动事件 this.helper.addDOMEvent(doc, 'mousemove', mousemoveHandler); } /** * 处理拖拽事件 * @param {Slider} slider 控件的实例 * @param {Element} element 处理事件的dom元素 * @param {boolean} unbind 绑定为false 解绑为true * @private */ function initDragEvents(slider, element, unbind) { if (unbind) { slider.helper.removeDOMEvent(element, 'mousedown', mousedownHandler); } else { slider.helper.addDOMEvent(element, 'mousedown', mousedownHandler); } } /** * 头部文本框的blur处理事件 * @param {Event} e 事件对象 * @private */ function blurHandler(e) { var target = e.target; var isValid = target.validate(); if (isValid) { // 验证通过 var value = target.getValue(); this.rawValue = +value; setByValue(this, value); } } /** * 头部下拉框的change处理事件 * @param {Event} e 事件对象 * @private */ function changeHandler(e) { var target = e.target; var value = target.getValue(); this.value = +value; setByValue(this, value); } /** * 初始化事件的交互 * @protected * @override */ exports.initEvents = function () { // 绑定滑块的事件 bindCursorEvents.call(this); // 绑定头部的事件 bindHeadEvents(this); }; /** * 获取滑动杆的值 * @return {*} 滑动杆的值 */ exports.getValue = function () { var value; if (this.range) { value = [this.minRangeValue, this.maxRangeValue]; } else { value = this.getRawValue(); } return value; }; /** * 重新渲染 * @protected * @override * @type {Function} 重新渲染时要执行的函数 */ exports.repaint = require('esui/painters').createRepaint( InputControl.prototype.repaint, { name: 'rawValue', paint: function (slider, value) { setByValue(slider, value, true); } } ); /** * 销毁控件 * @protected * @override */ exports.dispose = function () { this.bodyElement = null; this.cursorElement = null; this.bodySelectedElement = null; this.activeCursorElement = null; if (this.range) { this.cursorElementTwo = null; } this.$super(arguments); }; var Slider = require('eoo').create(InputControl, exports); esui.register(Slider); return Slider; }
function (require) { var eoo = require('eoo'); var InputControl = require('esui/InputControl'); var lib = require('esui/lib'); var u = require('underscore'); var m = require('moment'); var ui = require('esui'); var painters = require('esui/painters'); var $ = require('jquery'); require('esui/behavior/mousewheel'); /** * 微调输入控件 * * @class ui.Spinner * @extends esui.InputControl */ var Spinner = eoo.create( InputControl, { /** * 控件类型 * * @type {string} * @override */ type: 'Spinner', /** * 微调输入控件 * * @constructor */ constructor: function () { this.$super(arguments); // 短按计时器 this.timer = 0; // 长按计时器 this.longTimer = 0; }, initOptions: function (options) { var properties = {}; u.extend(properties, Spinner.defaultProperties, options); var format = properties.format; var max = properties.max; var min = properties.min; if (format === 'number') { max = max === 'indefinite' ? Number.MAX_VALUE : parseToNum(max); min = min === 'indefinite' ? -(Number.MAX_VALUE) : parseToNum(min); } else { max = max === 'indefinite' ? m().add(50, 'years') : m(max, format); min = min === 'indefinite' ? m().subtract(50, 'years') : m(min, format); } properties.max = max; properties.min = min; var scale = properties.scale; // 根据步长计算精度 if (format === 'number') { scale = scale + ''; var dotPosition = scale.indexOf('.'); if (dotPosition > -1) { properties.precision = scale.length - dotPosition - 1; } else { properties.precision = 0; } } // scale if (format !== 'number' && /^\s*\{/.test(scale)) { properties.scale = $.parseJSON(scale); } this.setProperties(properties); }, /** * 构建spinner */ initStructure: function () { var helper = this.helper; var spinnerTpl = [ '<input id="${inputId}" type="text" />', '<span id="${upId}" class="${upClass} ${iconClass}"></span>', '<span id=${downId} class="${downClass} ${iconClass}"></span>' ].join(''); var mainElement = this.main; mainElement.innerHTML = lib.format( spinnerTpl, { inputId: helper.getId('input'), upId: helper.getId('up'), upClass: helper.getPartClasses('up'), downId: helper.getId('down'), downClass: helper.getPartClasses('down'), iconClass: helper.getIconClass() } ); $(mainElement).addClass(helper.getPrefixClass('textbox')); }, /** * 初始化spinner事件 */ initEvents: function () { var helper = this.helper; var up = helper.getPart('up'); var down = helper.getPart('down'); helper.addDOMEvent(up, 'mousedown', mouseDownHandler); helper.addDOMEvent(down, 'mousedown', mouseDownHandler); helper.addDOMEvent(up, 'mouseup', mouseUpHandler); helper.addDOMEvent(down, 'mouseup', mouseUpHandler); helper.addDOMEvent(up, 'mouseout', mouseUpHandler); helper.addDOMEvent(down, 'mouseout', mouseUpHandler); // focus时支持鼠标滚轮 var input = this.getInput(); helper.addDOMEvent( input, 'focus', function () { helper.addDOMEvent(this.main, 'mousewheel', mouseWheelHandler); } ); helper.addDOMEvent( input, 'blur', function () { helper.removeDOMEvent(this.main, 'mousewheel', mouseWheelHandler); } ); }, /** * 渲染自身 * * @override * @protected */ repaint: painters.createRepaint( InputControl.prototype.repaint, { name: ['rawValue'], paint: function (spinner, rawValue) { var max = spinner.max; var min = spinner.min; var format = spinner.format; if (spinner.format === 'number') { rawValue = parseToNum(rawValue); rawValue = Math.max(rawValue, min); rawValue = Math.min(rawValue, max); } else { rawValue = m(rawValue, format); if (rawValue.isValid()) { rawValue = m.max(rawValue, min); rawValue = m.min(rawValue, max); } else { rawValue = min; } rawValue = m(rawValue, format).format(format); } setInputValue.call(spinner, rawValue); } }, { name: ['width'], paint: function (spinner, width) { width = parseInt(width, 10); spinner.main.style.width = width + 'px'; } }, { name: ['disabled', 'readOnly'], paint: function (spinner, disabled, readOnly) { var input = spinner.getInput(); input.disabled = disabled; input.readOnly = readOnly; } } ), /** * 获取input元素 * * @return {Element} */ getInput: function () { return lib.g(this.helper.getId('input')); }, /** * 获取控件值 * * @return {string} */ getValue: function () { var input = this.getInput(); return input.value; }, /** * 设置控件值 * * @param {string} value 控件值 */ setValue: function (value) { setInputValue.call(this, value); } } ); /** * 解析数字类型方法 * * @param {string} value 要解析的值 * @return {string} */ function parseToNum(value) { if (value) { value = parseFloat(value); } return isNaN(value) ? '' : value; } /** * 微调方向枚举值 * * @enum {string} */ var Direct = { UP: 'up', DOWN: 'down' }; /** * 更新日期类型方法 * * @param {Direct} direct up / down */ function updateDate(direct) { var input = this.getInput(); var scale = typeof this.scale === 'object' ? this.scale : parseToNum(this.scale); var timeFormat = this.format; var value = m(input.value, timeFormat); var max = this.max; var min = this.min; // 如果用户手动输入一个非法值,会默认显示最小值 value = value.isValid() ? value : min; if (direct === Direct.UP) { value = value.add(scale.value, scale.key); if (m.max(value, max) === max) { value = m(value, timeFormat).format(timeFormat); } else { if (!!this.turn && this.turn !== 'false') { value = m(min, timeFormat).format(timeFormat); } else { value = m(max, timeFormat).format(timeFormat); } } } else { value = value.subtract(scale.value, scale.key); if (m.min(value, min) === min) { value = m(value, timeFormat).format(timeFormat); } else { if (!!this.turn && this.turn !== 'false') { value = m(max, timeFormat).format(timeFormat); } else { value = m(min, timeFormat).format(timeFormat); } } } setInputValue.call(this, value); } /** * 更新数值类型的方法 * * @param {Direct} direct up / down */ function updateNumber(direct) { var input = this.getInput(); var scale = parseToNum(this.scale); var value = parseToNum(input.value); var max = this.max; var min = this.min; if (direct === Direct.UP) { value += scale; if (value > max) { if (!!this.turn && this.turn !== 'false') { value = min; } else { value = max; } } } else { value -= scale; if (value < min) { if (!!this.turn && this.turn !== 'false') { value = max; } else { value = min; } } } setInputValue.call(this, value); } /** * 更新值方法,用来判断值类型是数字类型还是时间类型 * * @param {Direct} direct 方向 */ function updateValue(direct) { if (this.format !== 'number') { updateDate.call(this, direct); } else { updateNumber.call(this, direct); } } /** * 改变value方法,该方法会触发 scrollValue 事件 * 如果用户想自定义方法,可以通过preventDefault()阻止默认行为 * * @param {Event} e 事件对象 */ function scrollValue(e) { if (!this.disabled && !this.readOnly) { var direct = (e.target.id === this.helper.getId('up')) ? Direct.UP : Direct.DOWN; var args = { direct: direct }; var eventArgs = this.fire('scrollValue', args); if (!eventArgs.isDefaultPrevented()) { updateValue.call(this, direct); } } } /** * 长按按钮自动更新方法 * 长按3秒时,速度加倍 * * @param {Event} e 事件对象 */ function autoUpdate(e) { var me = this; this.timer = setInterval( function () { return scrollValue.call(me, e); }, +parseToNum(this.timeInterval) ); this.longTimer = setTimeout( function () { clearInterval(me.timer); me.timer = setInterval( function () { return scrollValue.call(me, e); }, parseToNum(this.timeInterval) / 2 ); }, 3000 ); } /** * 鼠标滚轮方法 * * @param {Event} e 事件对象 * @param {Direct} direct 方向 */ function mouseWheelHandler(e, direct) { direct = direct === 1 ? Direct.UP : Direct.DOWN; updateValue.call(this, direct); e.preventDefault(); } /** * 鼠标点击方法 * * @param {Event} e 事件对象 */ function mouseDownHandler(e) { var delayTime = 1200 - this.timeInterval; scrollValue.call(this, e); var me = this; this.timer = setTimeout( function () { return autoUpdate.call(me, e); }, delayTime ); // 阻止鼠标双击后反选控件其他部分 e.preventDefault(); } /** * 取消事件方法 * * @param {Event} e 事件对象 */ function mouseUpHandler(e) { clearInterval(this.timer); clearTimeout(this.timer); clearTimeout(this.longTimer); this.timer = 0; this.longTimer = 0; } function setInputValue(value) { if (this.precision) { value = value.toFixed(this.precision); } var input = this.getInput(); input.value = value; } /** * Spinner默认属性 * turn: 当值到边界时是否反转 * scale: 刻度单位, 如果format为 number 类型,则为数字类型, 如果format为 日期类型,则为Object类型,格式为: * { * key: ***, //时间单位,如 'days', 'years'等,具体请参考 {@link moment} * value: *** //单位时间, 数字类型 * } * max: 上界 * min: 下界 * format: 值的格式,包括 number 和 日期 两种,如果使用日期格式,format按照{@link moment}的格式进行设置 * timeInterval: 长按按钮时,数值滚动的时间间隔 * @type {{spaceHolder: string, turn: boolean, scale: number, range: Array}} */ Spinner.defaultProperties = { turn: true, scale: 1, width: 200, height: 30, max: 'indefinite', min: 'indefinite', format: 'number', timeInterval: 100 }; ui.register(Spinner); return Spinner; }
function (require) { var lib = require('./lib'); var ui = require('esui'); var Control = require('./Control'); require('./TextBox'); require('./Button'); /** * 搜索框控件,由一个文本框和一个搜索按钮组成 * * @extends Control * @param {Object} [options] 初始化参数 * @constructor */ function SearchBox(options) { Control.apply(this, arguments); } SearchBox.prototype.type = 'SearchBox'; /** * 初始化参数 * * @param {Object} [options] 构造函数传入的参数 * @protected * @override */ SearchBox.prototype.initOptions = function (options) { var properties = {}; lib.extend(properties, options); if (properties.disabled === 'false') { properties.disabled = false; } if (lib.isInput(this.main)) { if (!properties.placeholder) { properties.placeholder = lib.getAttribute(this.main, 'placeholder'); } if (!properties.text) { properties.text = this.main.value; } if (!properties.maxLength && ( lib.hasAttribute(this.main, 'maxlength') || this.main.maxLength > 0)) { properties.maxLength = this.main.maxLength; } } //TODO: custom elments 的兼容 else { if (!properties.text) { properties.text = lib.getText(this.main); } } if (!properties.title) { properties.title = this.main.title; } Control.prototype.initOptions.call(this, properties); }; /** * 初始化DOM结构 * * @protected * @override */ SearchBox.prototype.initStructure = function () { // 一个搜索框由一个文本框和一个按钮组成 var textboxOptions = { mode: 'text', childName: 'text', height: this.height, viewContext: this.viewContext, placeholder: this.placeholder }; if (lib.isInput(this.main)) { this.helper.replaceMain(); } var textbox = ui.create('TextBox', textboxOptions); textbox.appendTo(this.main); this.addChild(textbox); var buttonOptions = { main: document.createElement('span'), childName: 'button', content: '搜索', viewContext: this.viewContext }; var button = ui.create('Button', buttonOptions); button.appendTo(this.main); this.addChild(button); }; /** * 初始化事件交互 * * @protected * @override */ SearchBox.prototype.initEvents = function () { var textbox = this.getChild('text'); var delegate = require('mini-event').delegate; delegate(textbox, this, 'input'); delegate(textbox, 'enter', this, 'search'); // 回车时要取消掉默认行为,否则会把所在的表单给提交了 textbox.on( 'keypress', function (e) { if (e.keyCode === 13) { e.preventDefault(); } } ); textbox.on('focus', focus, this); textbox.on('blur', lib.bind(this.removeState, this, 'focus')); var button = this.getChild('button'); button.on('click', click, this); }; function focus() { this.removeState('clear'); this.addState('focus'); } function click() { if (this.hasState('clear')) { this.getChild('text').setValue(''); this.removeState('clear'); } this.fire('search'); } /** * 获取输入值 * * @return {string} * @override */ SearchBox.prototype.getValue = function () { var text = this.getChild('text'); return text.getValue(); }; var paint = require('./painters'); /** * 渲染自身 * * @protected * @override */ SearchBox.prototype.repaint = paint.createRepaint( Control.prototype.repaint, paint.attribute('title'), { name: [ 'maxLength', 'placeholder', 'text', 'width', 'disabled', 'readOnly' ], paint: function (box, maxLength, placeholder, text, width, disabled, readOnly) { var properties = { /** * @property {number} maxLength * * 最大长度,参考{@link TextBox#maxLength} */ maxLength: maxLength, /** * @property {string} placeholder * * 无内容时的提示文字,参考{@link TextBox#placeholder} */ placeholder: placeholder, /** * @property {string} text * * 文字内容 */ value: text, /** * @property {number} width * * 设定文本框宽度,参考{@link TextBox#width} */ width: width, disabled: disabled, readOnly: readOnly }; box.getChild('text').setProperties(properties); } }, { name: 'disabled', paint: function (box, disabled) { if (disabled === 'false') { disabled = false; } var button = box.getChild('button'); button.set('disabled', disabled); } }, { /** * @property {boolean} fitWidth * * 设定当前控件是否独占一行宽度 */ name: 'fitWidth', paint: function (box, fitWidth) { var method = fitWidth ? 'addState' : 'removeState'; box[method]('fit-width'); } } ); /** * 获取用于比对的text属性值 * * @return {string} * @protected */ SearchBox.prototype.getTextProperty = function () { var textbox = this.getChild('text'); return textbox ? textbox.getValue() : this.text; }; lib.inherits(SearchBox, Control); ui.register(SearchBox); return SearchBox; }
define(function (require) { var u = require('underscore'); var esui = require('esui'); var lib = require('esui/lib'); var Control = require('esui/Control'); var eoo = require('eoo'); var $ = require('jquery'); var painters = require('esui/painters'); var previewHelper = require('./helper/previewHelper'); /** * 图片,视频和Flash预览控件 * * 显示图片,视频和Flash资源,并提供配置 * * @extends {Control} * @param {Object} options 初始化参数 * @constructor */ var MediaPreview = eoo.create( Control, { /** * 控件类型,始终为`"MediaPreview"` * * @type {string} * @readonly * @override */ type: 'MediaPreview', /** * 初始化参数 * * 对于图片资源,显示的宽度为容器宽度 * 而高度是默认高度,最大高度为容器高度 * * @param {Object} [options] 构造函数传入的参数 * @protected * @override */ initOptions: function (options) { var properties = { /** * @property {number} [width=300] * * 默认宽度 */ width: 300, /** * @property {number} [width=500] * * 默认高度 */ height: 500, /** * @property {number} [width=0] * * 预览media要显示的宽度 */ mediaWidth: 0, /** * @property {number} [width=0] * * 预览media要显示的高度 */ mediaHeight: 0, /** * @property {String} [toolRole=''] * * 用以标注添加工具icon的属性,要以','分隔,并由role生成相应的class */ toolRole: '', /** * @property {String} [sourceURL=''] * * 资源路径 */ sourceURL: '', /** * @property {String} [sourceType=''] * * 资源的类别,共支持以下四种类型 * 1.image: 图片类型 * 2.flash: 带有'swf'后缀名的flash文件 * 3.flv: 带有'flv'后缀名的视频文件 * 4.video: 带有'mp4|mov|mkv|mpg|avi|rmvb|rm|ogg|wmv|mp3|wma|mid'后缀名的文件 */ sourceType: '', /** * @property {String} [swfPath=''] * * 要使用的视频播放器路径 */ swfPath: '' }; u.extend(properties, MediaPreview.defaultProperties, options); this.setProperties(properties); }, /** * 初始化DOM结构 * * @protected * @override */ initStructure: function () { var helper = this.helper; var tpl = [ '<div id="${containerId}" class="${containerClass}">', '<div id="${bodyId}" class="${bodyClass}"></div>', '<div id="${toolId}" class="${toolClass}"></div>', '</div>' ].join(''); var mainElement = this.main; mainElement.innerHTML = lib.format( tpl, { containerId: helper.getId('container'), containerClass: helper.getPartClasses('container'), bodyId: helper.getId('body'), bodyClass: helper.getPartClasses('body'), toolId: helper.getId('tool'), toolClass: helper.getPartClasses('tool') } ); }, /** * 重渲染 * * @method * @protected * @override */ repaint: painters.createRepaint( Control.prototype.repaint, /** * @property {String} sourceURL * @property {String} sourceType * @property {number} mediaWidth * @property {number} mediaHeight * * 资源的路径以及type */ { name: ['sourceURL', 'sourceType', 'mediaWidth', 'mediaHeight'], paint: function (mediaPreview, sourceURL, sourceType) { initBody.call(mediaPreview); } }, /** * @property {String} toolRole * * 工具栏工具的自定义属性,以','分隔,用来标志工具icon */ { name: ['toolRole'], paint: function (mediaPreview, toolRole) { initToolBar.call(mediaPreview); } }, /** * @property {number} width * @property {number} height * * 文本框的宽度及高度 */ { name: ['width', 'height'], paint: function (mediaPreview, width, height) { $(mediaPreview.main).width(width).height(height); } } ), /** * 初始化事件交互 * * @protected * @override */ initEvents: function () { var me = this; // 为所有的具有'data-role="tool"'属性的span节点添加点击事件 // 并在该控件上fire一个'iconclick'的事件,参数是点击的span的toolRole this.helper.addDOMEvent( this.helper.getPart('tool'), 'click', '[data-role="tool"]', function (event) { var toolNode = event.target; me.fire('iconclick', {roleName: $(toolNode).attr('data-tool-role')}); } ); } } ); /** * 默认属性值 * * @type {Object} * @public */ MediaPreview.defaultProperties = { errorHTML: '无法预览该内容!' }; /** * 构造预览资源的html结构 * * @ignore */ function initBody() { var errorTpl = '<p class="${errorClass}">' + this.errorHTML + '</p>'; var width = u.min([this.mediaWidth || this.width, this.width]); var height = u.min([this.mediaHeight || this.height, this.height]); if (this.sourceType === 'image') { width = '100%'; height = 'auto'; } var html = previewHelper.preview({ width: width, height: height, url: this.sourceURL, type: this.sourceType, swfPath: this.swfPath }); // 如果previewHelper中无法将其渲染出来,这里要显示一个错误的模版 if (!html) { html = lib.format( errorTpl, { errorClass: this.helper.getPartClasses('error') } ); } // 获取body节点 var bodyElement = $(this.helper.getPart('body')); bodyElement.html(''); if (typeof html === 'string') { bodyElement.html(html); } else { if (html.appendTo) { html.appendTo(bodyElement[0]); } else { bodyElement.append(html); } } } /** * 构造工具栏的html结构 * * @ignore */ function initToolBar() { var toolRole = this.toolRole; if (!toolRole) { return; } var helper = this.helper; var toolTpl = '<span data-role="tool" class="${iconClass}" data-tool-role="${iconRole}"></span>'; u.each(toolRole.split(','), function (role) { $(helper.getPart('tool')).append(lib.format( toolTpl, { iconClass: helper.getIconClass() + '-' + role, iconRole: role } )); }); } esui.register(MediaPreview); return MediaPreview; });
define(function (require) { var esui = require('esui'); var lib = require('esui/lib'); var u = require('underscore'); var eoo = require('eoo'); var Control = require('esui/Control'); var painters = require('esui/painters'); var $ = require('jquery'); var previewHelper = require('./helper/previewHelper'); require('./helper/swfHelper'); require('esui/Dialog'); var LightBox = eoo.create( Control, { /** * 资源预览弹出框控件 * * @extends Control * @constructor */ constructor: function () { this.$super(arguments); this.dialog = null; }, type: 'LightBox', /** * 初始化配置 * * @protected * @override */ initOptions: function (options) { var properties = { currentIndex: 0, width: 'auto', height: 'auto', dialogVariants: 'lightbox', loadingStyle: this.helper.getPartClassName('media-loading'), loadFailedStyle: this.helper.getPartClassName('media-load-failed'), group: null, groupContainerId: null }; u.extend(properties, LightBox.defaultProperties, options); this.setProperties(properties); }, /** * 初始化DOM结构 * * @protected * @override */ initStructure: function () { var properties = { content: '', closeButton: true, mask: true, alwaysTop: true, closeOnHide: false, width: 'auto' }; u.extend( properties, { title: this.title || '', foot: this.foot || '', draggable: this.draggable || false, needfoot: this.needfoot || false, variants: this.dialogVariants } ); var dialog = esui.create('Dialog', properties); dialog.appendTo(document.body); this.dialog = dialog; }, /** * 初始化事件交互 * * @protected * @override */ initEvents: function () { this.initCarousel(); var leftLink = lib.g(this.dialog.helper.getId('link-left')); var rightLink = lib.g(this.dialog.helper.getId('link-right')); var me = this; this.dialog.helper.addDOMEvent( leftLink, 'click', function (e) { me.showPreviousMedia(); } ); this.dialog.helper.addDOMEvent( rightLink, 'click', function (e) { me.showNextMedia(); } ); if (this.group) { var container = this.groupContainerId ? lib.g(this.groupContainerId) : document.body; me.helper.addDOMEvent( container, 'click', '[data-lightbox-group]', function (e) { var target = e.currentTarget; e.preventDefault(); var $groupElements = $(container).find('[data-lightbox-group="' + me.group + '"]'); var i = $groupElements.index(target); var datasource = []; $groupElements.each(function (i, element) { var $el = $(element); var item = { url: $el.attr('href') }; var dataType = $el.attr('data-lightbox-type'); item.width = $el.attr('data-lightbox-width'); item.height = $el.attr('data-lightbox-height'); dataType && (item.type = dataType); datasource.push(item); }); me.datasource = datasource; me.show({ currentIndex: i }); } ); } }, /** * 初始化图片/视频轮播 * * @protected */ initCarousel: function () { var tpl = [ '<div id="${mediaId}" class="${mediaStyle}"></div>', '<div id="${linkId}" class="${linkStyle}">', '<a href="javascript:;" id="${leftLinkId}" class="${leftLinkStyle}"></a>', '<a href="javascript:;" id="${rightLinkId}" class="${rightLinkStyle}"></a>', '</div>' ].join(''); var body = this.dialog.getBody(); var dialogHelper = this.dialog.helper; var leftIcon = dialogHelper.getPartClassName('lightbox-content-link-left') + ' ' + dialogHelper.getIconClass(); var rightIcon = dialogHelper.getPartClassName('lightbox-content-link-right') + ' ' + dialogHelper.getIconClass(); body.setContent( lib.format( tpl, { mediaId: dialogHelper.getId('media'), mediaStyle: dialogHelper.getPartClassName('lightbox-content-media'), linkId: dialogHelper.getId('link'), linkStyle: dialogHelper.getPartClassName('lightbox-content-link'), leftLinkId: dialogHelper.getId('link-left'), leftLinkStyle: leftIcon, rightLinkId: dialogHelper.getId('link-right'), rightLinkStyle: rightIcon } ) ); }, mediaContainer: function () { return lib.g(this.dialog.helper.getId('media')); }, /** * 显示图片/视频对话框容器 * * @param {Object} args 显示对话框时传入的参数 * @protected */ show: function (args) { args && this.setProperties(args); var link = lib.g(this.dialog.helper.getId('link')); link.style.display = this.datasource.length <= 1 ? 'none' : ''; this.showMedia(); }, /** * 隐藏图片/视频对话框容器 * * @protected */ hide: function () { this.dialog.hide(); }, /** * 填充内容 * * @param {Array} list 图片或视频数据列表 * @protected */ setContent: function (list) { this.setProperties( { datasource: list } ); }, /** * 显示图片/视频 * * @protected */ showMedia: function () { var data = this.datasource[this.currentIndex]; this.showLoading(); if (!data.type) { if (/\.(?:jpg|png|gif|jpeg|bmp)$/i.test(data.url)) { data.type = 'image'; } else if (/\.swf/i.test(data.url)) { data.type = 'flash'; } else if (/\.(?:mp4|flv|mov|mkv|mpg|avi|rmvb|rm|ogg|wmv|mp3|wma|mid)/i.test(data.url)) { data.type = 'video'; } } this.preview(data); }, /** * 显示加载状态 * * @protected */ showLoading: function () { $(this.dialog.main).addClass(this.helper.getPartClassName('loading')); }, /** * 取消加载状态 * * @protected */ hideLoading: function () { $(this.dialog.main).removeClass(this.helper.getPartClassName('loading')); }, /** * 预览图片/视频/flash * * @protected * @param {Object} options 预览参数 */ preview: function (options) { if (options) { var type = options.type; options.id = options.id || 'preiew-' + Math.random(); options.width = options.width || this.width; options.height = options.height || this.height; type = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); (this['preview' + type] || this.previewNotSupported).call(this, options); } }, /** * 预览图片 * * @param {Object} options 图片数据 * @protected */ previewImage: function (options) { var me = this; options.width += 'px'; options.height += 'px'; var img = previewHelper.preview(options); img.onload = function () { me.hideLoading(); me.mediaContainer().innerHTML = ''; me.mediaContainer().appendChild(img); me.dialog.show(); img.onload = img.onerror = null; }; img.onerror = function () { me.hideLoading(); me.mediaContainer().innerHTML = lib.format(this.LOAD_FAILED_TPL, me); img.onload = img.onerror = null; me.dialog.show(); }; }, /** * 预览Flash * * @param {Object} options flash数据 * @protected */ previewFlash: function (options) { var html = previewHelper.preview(options); this.hideLoading(); this.mediaContainer().innerHTML = ''; this.mediaContainer().appendChild(html); this.dialog.show(); }, /** * 预览视频 * * @param {Object} options 视频数据 * @protected */ previewVideo: function (options) { var url = options.url; var html = ''; if (/\.flv/.test(url)) { options.type = 'flv'; } else if (/\.mp4|\.mov/.test(url)) { options.type = 'video'; } options.swfPath = this.swfPath; html = previewHelper.preview(options); var $container = $(this.mediaContainer()); this.hideLoading(); $container.html(''); $container.append($(html)); this.dialog.show(); }, previewNotSupported: function () { this.hideLoading(); this.mediaContainer().innerHTML = this.NOT_SUPPORT_MESSAGE; this.dialog.show(); }, /** * 显示下一个图片/视频 * * @protected */ showNextMedia: function () { this.currentIndex = ++this.currentIndex % this.datasource.length; this.showMedia(); }, /** * 显示上一个图片/视频 * * @protected */ showPreviousMedia: function () { this.currentIndex = (--this.currentIndex + this.datasource.length) % this.datasource.length; this.showMedia(); }, /** * 重渲染 * * @method * @protected * @override */ repaint: painters.createRepaint( Control.prototype.repaint, { name: ['title'], paint: function (control, title) { control.dialog.setTitle(title || ''); } } ) } ); LightBox.defaultProperties = { NOT_SUPPORT_MESSAGE: '暂不支持该格式预览', LOAD_FAILED_TPL: '<div class="${loadFailedStyle}">加载图片失败</div>' }; esui.register(LightBox); return LightBox; });
function (require) { var Control = require('esui/Control'); var lib = require('esui/lib'); var ui = require('esui'); require('esui/Panel'); require('esui/Overlay'); /** * 折叠控件 * * @class ui.TogglePanel * @extends.esui.Control */ var exports = {}; /** * 控件类型,始终为`"TogglePanel"` * * @type {string} * @override */ exports.type = 'TogglePanel'; /** * 初始化参数 * * @param {Object} options 构造函数传入的参数 * @override * @protected */ exports.initOptions = function (options) { var defaults = { expanded: false, position: 'layer' }; var properties = lib.extend(defaults, options); this.setProperties(properties); }; /** * 初始化DOM结构 * * @override * @protected */ exports.initStructure = function () { var children = lib.getChildren(this.main); var titleElem = children[0]; var contentElem = children[1]; // 初始化Title部分的DOM结构 initTitle.call(this, titleElem); // 初始化content部分的DOM结构 var position = this.position; if (position === 'fixed') { // 占位 initContentPanel.call(this, contentElem); } else { // 不占位 initContentOverlay.call(this, contentElem); } }; /** * 初始化Title部分的DOM结构 * * @inner * @param {Object} titleElem Title的DOM对象 */ function initTitle(titleElem) { var titlePanel = ui.create('Panel', {main: titleElem}); this.helper.addPartClasses('title', titlePanel.main); this.addChild(titlePanel, 'title'); titlePanel.render(); this.set('title', titleElem && titleElem.innerHTML); } /** * 按Panel模式初始化Content部分的DOM结构 * * @inner * @param {Object} contentElem content的DOM对象 */ function initContentPanel(contentElem) { var options = { main: contentElem, childName: 'content', viewContext: this.viewContext, renderOptions: this.renderOptions }; var contentPanel = ui.create('Panel', options); this.helper.addPartClasses('content', contentPanel.main); this.addChild(contentPanel, 'content'); contentPanel.render(); } /** * 按Overlay模式初始化Content部分的DOM结构 * * @inner * @param {Object} contentElem content的DOM对象 */ function initContentOverlay(contentElem) { var overlayMain = this.helper.createPart('layer', 'div'); lib.addClass(overlayMain, this.helper.getPartClassName('layer')); var options = { main: contentElem, childName: 'content', attachedDOM: this.main, attachedLayout: 'bottom,left', autoClose: false, viewContext: this.viewContext, renderOptions: this.renderOptions }; var contentLayer = ui.create('Overlay', options); this.helper.addPartClasses('content', contentLayer.main); this.addChild(contentLayer, 'content'); contentLayer.render(); var globalEvent = lib.bind(close, this); contentLayer.on( 'show', function () { this.helper.addDOMEvent(document, 'mousedown', globalEvent); } ); contentLayer.on( 'hide', function () { this.helper.removeDOMEvent(document, 'mousedown', globalEvent); } ); } /** * 关闭layer层的事件处理句柄 * * @param {mini-event.Event} e 事件对象 * @inner */ function close(e) { var target = e.target; var layer = this.getChild('content'); if (!layer) { return; } var isChild = lib.dom.contains(layer.main, target); if (!isChild) { layer.hide(); // 如果是点击attachedTarget的话,需要保持expanded状态. // 如果是点击其他空白区域的话,直接去掉expanded就行。 var attachedTarget = layer.attachedTarget; var isAttachedTarget = lib.dom.contains(attachedTarget, target) || attachedTarget === target; if (!isAttachedTarget) { this.removeState('expanded'); this.removeState('active'); } } } /** * 点击Title区域的句柄 * * @inner */ function onToggle() { this.toggleContent(); } /** * 切换展开/收起状态 * * @inner */ exports.toggleContent = function () { this.toggleStates(); this.fire('change'); }; exports.toggleStates = function () { var position = this.position; if (position === 'fixed') { // 占位模式 this.toggleState('expanded'); this.toggleState('active'); } else { // 浮层模式 var contentLayer = this.getChild('content'); if (this.isExpanded()) { this.removeState('expanded'); this.removeState('active'); contentLayer.hide(); } else { this.addState('expanded'); this.addState('active'); contentLayer.show(); } } }; exports.initEvents = function () { var me = this; me.$super(arguments); var titlePanel = me.getChild('title'); me.helper.addDOMEvent(titlePanel.main, 'click', lib.bind(onToggle, me)); }; var painters = require('esui/painters'); /** * 重绘 * * @override * @protected */ exports.repaint = painters.createRepaint( Control.prototype.repaint, painters.state('expanded'), { name: 'title', paint: function (panel, title) { panel.getChild('title').set('content', title); } }, { name: 'content', paint: function (panel, content) { panel.getChild('content').set('content', content); } }, /** * @property {number} width * * 宽度 */ painters.style('width') ); exports.isExpanded = function () { return this.hasState('expanded'); }; var TogglePanel = require('eoo').create(Control, exports); ui.register(TogglePanel); return TogglePanel; }
function (require) { var Control = require('esui/Control'); var lib = require('esui/lib'); var ui = require('esui'); require('esui/Panel'); /** * 折叠控件 */ function TogglePanel() { Control.apply(this, arguments); } TogglePanel.prototype.type = 'TogglePanel'; /** * 初始化参数 * * @param {Object} options 构造函数传入的参数 * @override * @protected */ TogglePanel.prototype.initOptions = function (options) { var defaults = { expanded: false }; var properties = lib.extend(defaults, options); this.setProperties(properties); }; /** * 初始化DOM结构 * * @override * @protected */ TogglePanel.prototype.initStructure = function () { var children = lib.getChildren(this.main); var titleElem = children[0]; var contentElem = children[1]; var titlePanel = ui.create('Panel', { main: titleElem }); this.helper.addPartClasses('title', titlePanel.main); this.addChild(titlePanel, 'title'); titlePanel.render(); this.set('title', titleElem && titleElem.innerHTML); titlePanel.helper.addDOMEvent(titlePanel.main, 'click', lib.bind(onToggle, this)); var contentPanel = ui.create('Panel', { main: contentElem }); this.helper.addPartClasses('content', contentPanel.main); this.addChild(contentPanel, 'content'); this.set('content', contentElem && contentElem.innerHTML); }; function onToggle() { this.toggleState('expanded'); this.fire('change'); } var painters = require('esui/painters'); /** * 重绘 * * @override * @protected */ TogglePanel.prototype.repaint = painters.createRepaint( Control.prototype.repaint, painters.state('expanded'), { name: 'title', paint: function (panel, title) { panel.getChild('title').set('content', title); } }, { name: 'content', paint: function (panel, content) { panel.getChild('content').set('content', content); } } ); TogglePanel.prototype.isExpanded = function () { return this.hasState('expanded'); }; lib.inherits(TogglePanel, Control); ui.register(TogglePanel); return TogglePanel; }
function (require) { var u = require('underscore'); var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var Layer = require('esui/Layer'); var eoo = require('eoo'); var painters = require('esui/painters'); var $ = require('jquery'); var esui = require('esui'); /** * 'MultiSelect'控件使用的层类 * @extends Layer * @ignore * @constructor */ var MultiSelectLayer = eoo.create(Layer, { create: function () { var ele = this.$super(arguments); $(ele).addClass(this.control.helper.getPrefixClass('dropdown')); $(this.control.main).after(ele); return ele; }, layerFootTpl: [ '<button type="button" class="${confirmClass}" data-ui="type:Button;variants:primary"' + 'data-ui-child-name="confirm">${okText}</button>', '<button type="button" class="${cancelClass}" data-ui="type:Button;variants:link"' + 'data-ui-child-name="cancel">${cancelText}</button>' ].join(''), render: function (element) { var multiSelector = this.control; var helper = multiSelector.helper; // 渲染layer主体部分 var html = helper.getPartBeginTag('wrapper', 'div'); for (var i = 0; i < this.control.datasource.length; i++) { html += multiSelector.getItemHTML(i); } html += helper.getPartEndTag('wrapper', 'div'); // 渲染layer底部确认和取消按钮 if (multiSelector.footer) { html += this.getLayerFoot(); } element.innerHTML = html; helper.initChildren(element); }, getLayerFoot: function () { var multiSelect = this.control; var helper = multiSelect.helper; var footBegin = helper.getPartBeginTag('layer-footer', 'div'); var footEnd = helper.getPartEndTag('layer-footer', 'div'); var data = { confirmClass: helper.getPrimaryClassName('layer-confirm'), cancelClass: helper.getPrimaryClassName('layer-cancel'), okText: multiSelect.okText, cancelText: multiSelect.cancelText }; var footBody = lib.format(this.layerFootTpl, data); return footBegin + footBody + footEnd; }, initBehavior: function (element) { var multiSelect = this.control; // item点击事件 $(element).on('click', 'input', u.bind(itemClickHandler, multiSelect)); // 确定、取消事件绑定 if (multiSelect.footer) { var layerConfirm = multiSelect.getChild('confirm'); var layerCancel = multiSelect.getChild('cancel'); layerConfirm.on('click', u.bind(confirmHandler, multiSelect)); layerCancel.on('click', u.bind(cancelHandler, multiSelect)); } }, syncState: function (element) { var helper = this.control.helper; // 根据selectedIndex的值更新视图 var selectClass = helper.getPrimaryClassName('item-checked'); var selectedIndex = lib.toDictionary(this.control.selectedIndex); for (var i = 0; i < this.control.datasource.length; i++) { var checkInput = $(element).find('input[data-index=' + i + ']'); var checkItem = $(checkInput).parent(); if (selectedIndex[i]) { checkInput.prop('checked', true); $(checkItem).addClass(selectClass); } else { checkInput.prop('checked', false); $(checkItem).removeClass(selectClass); } } }, dock: { strictWidth: true }, hide: function () { var multiSelect = this.control; // 没有确定按钮时,弹层隐藏即为确定 if (multiSelect.footer) { this.syncState(this.getElement(false)); } else { multiSelect.set('selectedIndex', [].concat(multiSelect.newSelectedIndex)); } this.$super(arguments); } }); /** * 下拉多选控件 * @extends InputControl * @constructor */ var MultiSelect = eoo.create(InputControl, { constructor: function () { this.$super(arguments); this.layer = new MultiSelectLayer(this); }, /** * 控件类型,始终为'MultiSelect' * @type {string} * @readonly * @override */ type: 'MultiSelect', initOptions: function (options) { var properties = { multi: false, title: '', width: 200, datasource: [], selectedIndex: [], footer: false, maxLength: null, displayText: null }; u.extend(properties, MultiSelect.defaultProperties, options); // 如果主元素是个'<select>'元素,则需要从元素中抽取数据源, // 这种情况下构造函数中传入的'datasource'无效 if (this.main.nodeName.toLowerCase() === 'select') { if ($(this.main).attr('multiple') === 'multiple') { properties.multi = true; } properties.datasource = []; var elements = this.main.getElementsByTagName('option'); for (var i = 0, length = elements.length; i < length; i++) { var item = elements[i]; var dataItem = { name: item.name || item.text, value: item.value }; if (item.disabled) { dataItem.disabled = true; } properties.datasource.push(dataItem); } this.helper.extractOptionsFromInput(this.main, properties); } properties.newSelectedIndex = [].concat(properties.selectedIndex); this.setProperties(properties); }, /** * 每个节点显示的内容的模板 * @type {string} */ itemTemplate: [ '<div title="${title}" class="${wrapperClass}">', '<div class="${itemWrapperClass}">', ' <input type="${type}" name="${name}" id="${id}" data-index="${dataIndex}"' + 'title="${title}" value="${value}" ${checked} ${disabled} />', ' <label for="${id}">${title}</label>', '</div>', '</div>' ].join(''), headItemTemplate: [ '<div title="${title}" class="${headerClass}">', ' <span">${title}</span>', '</div>' ].join(''), /** * 获取每个节点显示的内容 * * @param {number} index 当前节点的索引 * @return {string} 返回HTML片段 */ getItemHTML: function (index) { var item = this.datasource[index]; if (u.isFunction(this.getCustomItemHTML)) { return this.getCustomItemHTML(item); } var helper = this.helper; var classes = helper.getPartClasses('item'); // checked var wrapperClass = ''; var itemWrapperClass = ''; if (this.multi) { itemWrapperClass += [ helper.getPrefixClass('checkbox-custom'), helper.getPrefixClass('checkbox-single') ].join(' '); } else { itemWrapperClass += [ helper.getPrefixClass('radio-custom'), helper.getPrefixClass('radio-single') ].join(' '); } var valueIndex = lib.toDictionary(this.selectedIndex); if (valueIndex[index]) { wrapperClass += ' ' + helper.getPartClassName('item-checked'); } var headerClass = ''; if (item.header) { headerClass += ' ' + helper.getPartClassName('item-header'); } if (item.disabled) { wrapperClass += ' ' + helper.getPartClassName('item-disabled'); } var data = { wrapperClass: classes.join(' ') + ' ' + wrapperClass, id: helper.getId('multiselect-' + index), type: this.multi ? 'checkbox' : 'radio', name: this.name, title: lib.trim(item.title || item.name || item.text), value: item.value, checked: valueIndex[index] ? ' checked="checked"' : '', dataIndex: index, headerClass: headerClass, itemWrapperClass: itemWrapperClass, disabled: item.disabled ? 'disabled' : '' }; var tpl = item.header ? this.headItemTemplate : this.itemTemplate; return lib.format(tpl, data); }, /** * 显示选中值的模板 * * @type {string} */ displayTemplate: '${text}', /** * 获取选中值的内容 * * @param {meta.SelectItem | null} item 选中节点的数据项, * 如果{@link Select#emptyText}属性值不为空且未选中任何节点,则传递'null' * @return {string} 显示的HTML片段 */ getDisplayHTML: function (item) { if (u.isFunction(this.getCustomDisplayHTML)) { return this.getCustomDisplayHTML(item); } if (this.displayText) { return this.displayText; } if (item.length === 0) { return u.escape(this.emptyText); } var displayText = ''; $.each(item, function (index, selectItem) { displayText += u.escape(selectItem.name || selectItem.text); if (index !== item.length - 1) { displayText += ','; } }); return lib.format(this.displayTemplate, {text: displayText}); }, /** * 初始化DOM结构 * * @protected * @override */ initStructure: function () { var helper = this.helper; var arrow = 'arrow'; var span = 'span'; var mainElement = this.main; // 如果主元素是'<select>',删之替换成'<div>' if (mainElement.nodeName.toLowerCase() === 'select') { helper.replaceMain(); mainElement = this.main; } this.layer.autoCloseExcludeElements = [mainElement]; mainElement.innerHTML = helper.getPartHTML('text', span) + helper.getPartHTML(arrow, span); $(helper.getPart(arrow)).addClass(helper.getIconClass()); }, /** * 初始化事件交互 * * @protected * @override */ initEvents: function () { this.helper.addDOMEvent(this.main, 'click', toggle); this.layer.on('rendered', u.bind(addLayerClass, this)); }, /** * 将字符串类型的值转换成原始格式 * * @param {string} value 字符串值 * @return {string[]} * @protected * @override */ parseValue: function (value) { /** * @property {string} [value=""] * * `MultiSelect`的字符串形式的值为逗号分隔的多个值 */ return value.split(','); }, /** * 获取值 * * @return {Mixed} * @override */ getRawValue: function () { if (this.selectedIndex.length === 0) { return null; } var values = []; var datasource = this.datasource; u.each(this.selectedIndex, function (index) { var item = datasource[index]; if (item) { values.push(item.value); } }); return values; }, /** * 重渲染 * * @method * @protected * @override */ repaint: painters.createRepaint( InputControl.prototype.repaint, painters.style('width'), { name: 'datasource', paint: function (select) { select.layer.repaint(); } }, { name: ['selectedIndex'], paint: function (select, selectedIndex) { updateValue(select); } }, { name: ['rawValue'], paint: function (select, rawValue) { if (u.isArray(rawValue) && rawValue.length) { var selectedItems = u.filter( select.datasource, function (item) { return u.indexOf(rawValue, item.value) > -1; } ); if (!select.multi) { selectedItems = selectedItems.slice(-1); } select.selectedIndex = u.map( selectedItems, function (item) { return u.indexOf(select.datasource, item); } ); updateValue(select); } } }, { name: ['disabled', 'hidden', 'readOnly'], paint: function (select, disabled, hidden, readOnly) { if (disabled || hidden || readOnly) { select.layer.hide(); } } } ), /** * 更新{@link Select#datasource}属性,无论传递的值是否变化都会进行更新 * * @param {meta.SelectItem[]} datasource 新的数据源对象 */ updateDatasource: function (datasource) { if (!datasource) { datasource = this.datasource; } this.datasource = datasource; var record = {name: 'datasource'}; this.repaint([record], {datasource: record}); }, /** * 批量更新属性并重绘 * * @param {Object} properties 需更新的属性 * @override * @fires change */ setProperties: function (properties) { if (properties.datasource == null) { properties.datasource = this.datasource; } if (properties.value == null && properties.rawValue == null && properties.selectedIndex == null && properties.datasource === this.datasource ) { properties.selectedIndex = this.selectedIndex; } var changes = this.$super(arguments); return changes; }, /** * 销毁控件 * * @override */ dispose: function () { if (this.helper.isInStage('DISPOSED')) { return; } if (this.layer) { this.layer.dispose(); this.layer = null; } this.$super(arguments); }, /** * 获取当前选中的{@link meta.SelectItem}对象 * * @return {meta.SelectItem} */ getSelectedItem: function () { var datasource = this.datasource; var selectedItems = []; if (u.isArray(this.selectedIndex)) { $.each(this.selectedIndex, function (index, itemIndex) { if (datasource[itemIndex]) { selectedItems.push(datasource[itemIndex]); } }); } return selectedItems; } }); MultiSelect.defaultProperties = { emptyText: '请选择', okText: '确认', cancelText: '取消' }; /** * 切换下拉框 * * @param {Event} e click事件对象 */ function toggle(e) { this.layer.toggle.call(this.layer, e); this.layer.syncState(this.layer.getElement(false)); this.newSelectedIndex = [].concat(this.selectedIndex); } function itemClickHandler(e) { var target = e.target; // 值是否发生更改的标志位 var isChanged = false; if (!this.helper.isPart(target, 'item-disabled')) { var index = target.getAttribute('data-index'); var newIndex = +index; if (this.multi) { var isNewIndex = true; for (var i = 0; i < this.newSelectedIndex.length; i++) { if (this.newSelectedIndex[i] === newIndex) { isNewIndex = false; isChanged = true; this.newSelectedIndex.splice(i, 1); } } if (isNewIndex) { this.newSelectedIndex.push(+index); isChanged = true; } } else { this.newSelectedIndex = [newIndex]; isChanged = true; } this.fire('itemclick'); } if (this.maxLength && this.newSelectedIndex.length > this.maxLength) { var shiftIndex = this.newSelectedIndex.shift(); $(this.layer.getElement(false)).find('input[data-index=' + shiftIndex + ']').removeAttr('checked'); isChanged = false; } if (isChanged && !this.footer) { this.set('selectedIndex', [].concat(this.newSelectedIndex)); this.fire('change'); } } function confirmHandler(e) { this.set('selectedIndex', [].concat(this.newSelectedIndex)); this.fire('change'); this.layer.hide(); } function cancelHandler() { this.set('selectedIndex', this.selectedIndex); this.newSelectedIndex = [].concat(this.selectedIndex); updateValue(this); this.layer.hide(); } function addLayerClass() { this.fire('layerrendered', {layer: this.layer}); } /** * 根据控件的值更新其视图 * * @param {Select} select 控件实例 * @ignore */ function updateValue(select) { // 同步显示的文字 var textHolder = select.helper.getPart('text'); var selectedItem = select.getSelectedItem(); var text = select.getDisplayHTML(selectedItem); textHolder.innerHTML = text; textHolder.title = text; var layerElement = select.layer.getElement(false); if (layerElement) { select.layer.syncState(layerElement); } } esui.register(MultiSelect); return MultiSelect; }
function (require) { var $ = require('jquery'); var esui = require('esui'); var u = require('underscore'); var TogglePanel = require('../TogglePanel'); var eoo = require('eoo'); /** * @class ToggleSelector * @extends TogglePanel */ var ToggleSelector = eoo.create( TogglePanel, { /** * @override */ type: 'ToggleSelector', getCategory: function () { return 'input'; }, /** * @override */ initOptions: function (options) { var properties = { textField: null, collapseAfterChange: true, defaultText: '请选择' }; options = u.extend(properties, options); if (!options.title) { options.title = options.defaultText; } this.$super(arguments); }, /** * @override */ initStructure: function () { var me = this; var controlHelper = this.helper; this.triggerElement = this.main; me.$super(arguments); var $mainElement = $(me.main); var $children = $mainElement.children(); var $text = $children.eq(0); var $contentLayer = $children.eq(1); var $caret = $('<span></span>').addClass( controlHelper.getPrefixClass('select-arrow') + ' ' + controlHelper.getIconClass() ); $mainElement.addClass(controlHelper.getPrefixClass('select')); $text.addClass(controlHelper.getPrefixClass('select-text')); $mainElement.append($caret); $contentLayer.insertAfter($mainElement); }, /** * @override */ initEvents: function () { var me = this; var target = me.viewContext.getSafely(me.targetControl); var controlHelper = me.helper; target.on('change', u.bind(changeHandler, me)); target.on('add', u.bind(addHandler, me)); me.updateDisplayText(target); controlHelper.addDOMEvent( me.main, 'click', me.toggleContent ); }, /** * @override */ toggleContent: function () { if (!this.isDisabled()) { this.toggleStates(); } }, updateDisplayText: function (target) { var displayText = this.title; // 要render了以后才能获取到value if (target.helper.isInStage('RENDERED')) { var rawValue = target.getRawValue(); // 因为只针对单选控件,因此即便是多个也默认选第一个 if (u.isArray(rawValue)) { rawValue = rawValue[0]; } displayText = rawValue && rawValue[this.textField] ? rawValue[this.textField] : this.defaultText; } this.set('title', u.escape(displayText)); }, getRawValue: function () { var target = this.viewContext.getSafely(this.targetControl); var rawValue = target.getRawValue(); if (rawValue) { return rawValue[this.valueField]; } }, getFullRawValue: function () { var target = this.viewContext.getSafely(this.targetControl); return target.getRawValue(); }, setRawValue: function (value) { var target = this.viewContext.getSafely(this.targetControl); target.setRawValue(value); }, getValue: function () { return this.getRawValue(); }, setValue: function (value) { var rawValue = [{id: value}]; this.setRawValue(rawValue); }, /** * 进行验证 * * @return {boolean} */ validate: function () { var target = this.viewContext.get(this.targetControl); if (!target) { return true; } if (typeof target.validate === 'function') { return target.validate(); } } } ); /** * 数据变化时如果没有阻止,则更新显示文字 * * @event * @param {Object} e 事件对象 */ function changeHandler(e) { var event = this.fire('change'); if (!event.isDefaultPrevented()) { this.updateDisplayText(e.target); } } /** * 添加数据时才控制展开收起 * * @event * @param {Object} e 事件对象 */ function addHandler(e) { if (this.collapseAfterChange && this.hasState('expanded')) { this.toggleContent(); } } esui.register(ToggleSelector); return ToggleSelector; }
function (require) { require('esui/Button'); require('./FileInput'); require('./Progress'); var eoo = require('eoo'); var esui = require('esui'); var ui = require('esui/main'); var Control = require('esui/Control'); var painters = require('esui/painters'); var File = require('./File'); var $ = require('jquery'); var u = require('underscore'); var lib = require('esui/lib'); var supportXHR = (window.File && window.XMLHttpRequest); var supportXHR2 = (supportXHR && new window.XMLHttpRequest().upload); /** * 控件类 * * 上传控件有如下结构特点: * * - 上传组件 (必须,至少一个) * -- 文件上传控件 * - 上传列表 (可选,可自定义容器) * -- 由一个或多个进度条组件构成 * * 上传控件有两种模式: * * - 单文件上传 * - 多文件上传 * * @class ui.Uploader * @extends esui.Control */ var Uploader = eoo.create( Control, { /** * 控件类型,始终为`"Uploader"` * * @type {string} * @readonly * @override */ type: 'Uploader', /* * 文件上传队列 */ queue: { // 队列长度 length: 0, // 正在上传的文件 uploadingFiles: [], // 等待开始的文件 waitingFiles: [], // 出错的文件 failedFiles: [], // 遗弃的文件 // 当文件过长时会遗弃多余的文件 abandonedFiles: [], // 上传完成的文件 completeFiles: [] }, /** * @override */ initOptions: function (options) { var properties = {}; u.extend(properties, this.$self.defaultProperties, options); var adaptProperties = ['sequentialUploads', 'showProgress', 'multiple']; u.each( adaptProperties, function (propertyName) { if (properties[propertyName] === 'false') { properties[propertyName] = false; } } ); this.$super([properties]); }, /** * @override */ initStructure: function () { var tpl = [ '<div class="${uploadComboxClass}">', // 上传input ' <div data-ui-child-name="fileInput"', ' data-ui="type:FileInput;accept:${accept};multiple:${multiple};name:${paramKey};"></div>', // 伪装ge按钮 ' <div data-ui-child-name="submitButton" ', ' data-ui="type:Button;">${text}</div>', '</div>', '<div id="${defaultProgressContainerId}"></div>' ].join(''); this.main.innerHTML = lib.format( tpl, { uploadComboxClass: this.helper.getPartClassName('combox'), accept: this.accept, multiple: this.multiple, text: this.text, variants: this.buttonVariants || '', paramKey: this.paramKey, defaultProgressContainerId: this.helper.getId('default-progress-container') } ); // 创建控件树 this.helper.initChildren(); // 绑事件 var fileInput = this.getChild('fileInput'); fileInput.on('change', u.bind(inputChangeHandler, this)); var submitButton = this.getChild('submitButton'); submitButton.on( 'click', function (e) { fileInput.triggerUploadOutside(); e.preventDefault(); } ); }, /** * @override */ repaint: painters.createRepaint( Control.prototype.repaint, { name: ['text'], paint: function (uploader, text) { var button = uploader.getChild('submitButton'); button.setContent(text); } }, { name: ['disabled', 'readOnly'], paint: function (uploader, disabled, readOnly) { var input = uploader.getChild('fileInput'); var button = uploader.getChild('submitButton'); input.setProperties({disabled: disabled}); input.setProperties({readOnly: readOnly}); button.setProperties({disabled: disabled}); button.setProperties({readOnly: readOnly}); } }, painters.style('width') ), /** * 接收文件并做上传前的校验 * * @param {Array} files 接收到的文件,可以是input中取到的,也可以是drag外部传入的 * @public */ receiveFile: function (files) { // 如果仍然在uploading,则不执行新的上传操作 if (this.stage === 'UPLOADING') { return; } var event = this.fire('beforeupload', {files: files}); if (event.isDefaultPrevented()) { return; } this.doUpload(files); }, /** * 开始上传 * * @param {Array} files 接收到的文件 * @protected */ doUpload: function (files) { // 超出最大限制,直接返回 if (files.length > this.maxFileNumber) { this.notifyFail(this.message.ERROR_FILE_MAX_NUMBER); return; } files = u.map( files, function (file, index) { // 文件格式检查 if (!this.checkFileFormat(file)) { file.status = 'client-error'; file.message = this.message.ERROR_FILE_EXTENSIONS; } else if (!this.checkFileSize(file)) { file.status = 'client-error'; file.message = this.message.ERROR_FILE_MAX_SIEZ; } else { file.status = 'waiting'; // 单文件上传要覆盖之前的文件 if (this.multiple === false) { this.queue.waitingFiles = [file]; } else { this.queue.waitingFiles.push(file); } } return file; }, this ); initFileList.call(this, files); operationFileQueue.call(this); }, /** * 获取Uploader中的文件上传组件 * * @return {DOMElement} 容器中FileInput组件 * @public */ getFileInput: function () { return this.getChild('fileInput'); }, /** * 验证文件格式 * * @param {Object} file file对象 * @return {boolean} * @protected */ checkFileFormat: function (file) { if (this.accept) { // 这里就是个内置的`Rule`,走的完全是标准的验证流程, // 主要问题是上传控件不能通过`getValue()`获得验证用的内容, // 因此把逻辑写在控件内部了 var extension = file.name.split('.'); extension = '.' + extension[extension.length - 1].toLowerCase(); var isValid = false; if (typeof this.accept === 'string') { this.accept = lib.splitTokenList(this.accept); } for (var i = 0; i < this.accept.length; i++) { var acceptPattern = this.accept[i].toLowerCase(); if (acceptPattern === extension) { isValid = true; break; } // image/*之类的,表示一个大类 if (acceptPattern.slice(-1)[0] === '*') { var mimeType = acceptPattern.split('/')[0]; var targetExtensions = this.mimeTypes[mimeType]; if (targetExtensions && targetExtensions.hasOwnProperty(extension)) { isValid = true; break; } } } return isValid; } return true; }, /** * 验证文件大小 * * @param {Object} file file对象 * @return {boolean} * @protected */ checkFileSize: function (file) { // IE9中filechange返回的event只有fileName以及fileId // 所以如果出现这种情况就放过去,让后端做长度校验 if (this.maxFileSize && file.originalSize) { var isValid = false; if (file.originalSize) { isValid = parseInt(file.originalSize, 10) <= parseInt(this.maxFileSize, 10); } return isValid; } return true; }, /** * 解析返回中的错误 TODO 这个的具体解析格式要跟后端商定 * * @param {Object} response 请求返回的对象 * @return {string} * @protected */ parseError: function (response) { if (response.success === 'false') { return {message: response.error}; } return null; }, /** * 通知上传失败 * * @method ui.Uploader#notifyFail * @param {string} message 失败消息 * @protected */ notifyFail: function (message) { message = message || '上传失败'; this.fire('fail', {message: message}); }, /** * 通知上传完成 * * @protected * @method ui.Uploader#notifyComplete */ notifyComplete: function () { this.stage = 'COMPLETE'; this.fire('complete', { completeFiles: this.queue.completeFiles, failedFiles: this.queue.failedFiles }); } } ); /** * 默认属性 * * @type {Object} * @public */ Uploader.defaultProperties = { // 上传按钮文本 text: '点击上传', // 文件上传的路径 action: '/uploadFile', // 后台接收时的key名 paramKey: 'files', // 接收的文件类型 accept: '.gif,.jpg,.png,.swf,.xlsx', // 默认为单文件上传控件 // 单文件上传控件一次只能选择一个文件 multiple: false, // 单个文件最大大小,单位B,默认2M maxFileSize: 2147483648, // 单次最大上传文件数量 maxFileNumber: 6, // 多文件上传时,请求同时开始还是逐个开始 sequentialUploads: true, // 当前允许的最大连接数 // sequentialUploads为true时该选项无效 maxConnections: 6, // 提示信息 // 目前支持提供成功提示,文件大小不符 // 文件类型不匹配, message: { // 上传成功 SUCCESS_INFO: '上传成功', // 重新上传 RESTART_INFO: '正在重新上传', // http错误,一般来说就是status返回不是200 ERROR_HTTP: '上传失败,网络连接错误或上传路径无效', // 文件类型错误 ERROR_FILE_EXTENSIONS: '上传失败,安全因素不支持此类文件', // 文件超出最大尺寸, 标准浏览器下可以判断 ERROR_FILE_MAX_SIEZ: '超出最大上传尺寸', // 文件数量超过最大限制了 ERROR_FILE_MAX_NUMBER: '发生错误,上传文件超过限定。', // 格式错误 ERROR_FILE_FORMAT: '文件格式错误' }, // 是否显示进度 showProgress: true, // 进度模式,seperate和total两种,seperate代表每个文件独立进度;total代表所有文件统一计算进度 progressMode: 'seperate', singleProgressMode: 'detail', // 文件列表的容器 // 如果没有会添加一个默认容器 progressContainer: null }; /** * 上传输入组件变化事件处理 * * @param {mini-event.Event} e 事件对象 */ function inputChangeHandler(e) { var files = this.getFileInput().files; this.receiveFile(files); } /** * 创建上传进度队列 * * @param {Array} fileList 在队列中的文件 */ function initFileList(fileList) { if (!this.showProgress) { return; } var files = fileList ? fileList : this.queue.waitingFiles; this.progressContainer = this.progressContainer || this.helper.getId('default-progress-container'); var container; // 字符串处理 if (u.isString(this.progressContainer)) { // 先作为DOM id寻找 container = $('#' + this.progressContainer); // 如果没找到,找控件id if (!container[0] && this.viewContext.get(this.progressContainer)) { container = $(this.viewContext.get(this.progressContainer).main); } } // 只能认为扔了个控件进来 else { container = $(this.progressContainer.main); } if (!container[0]) { return; } var me = this; if (this.progressMode === 'seperate') { u.each(files, function (file, index) { // 创建主容器 var progressContainer = $('<div></div>'); container.append(progressContainer); var options = { file: file, childName: 'progress-' + file.id, main: progressContainer[0], progressMode: me.singleProgressMode }; // 如果不支持进度,那就强制不展示进度详情 if (!supportXHR) { options.singleProgressMode = 'general'; } // 如果定义了进度模板,使用定义的 if (me.progressItemTemplate) { options.template = me.progressItemTemplate; } var progress = ui.create('Progress', options); progress.render(); me.addChild(progress); progress.on('restart', function (e) { // 重新上传 var file = e.target.file; e.target.dispose(); // 将文件移出上传队列,然后重新进行上传 removeFileFromUploading.call(me, file, 'restart'); me.receiveFile([file]); }); progress.on('cancel', function (e) { var file = e.target.file; if (file.request) { file.request.abort(); removeFileFromUploading.call(me, file, 'cancel'); if (me.sequentialUploads) { operationFileQueue.call(me); } } // 从等待队列中清除 else { removeFileFromWaiting.call(me, file); } e.target.dispose(); }); }); } // TODO 文件的总进度条,待实现 // else { // } } /** * 执行上传队列 * */ function operationFileQueue() { // 当队列中 if (this.queue.waitingFiles && this.queue.waitingFiles.length) { // 一个个上传 if (this.sequentialUploads && this.queue.uploadingFiles.length < 1) { chooseProgressFile.call(this); } // 多文件上传,如果当前连接数未满就继续上传 else if ((this.maxConnections && this.queue.uploadingFiles.length < this.maxConnections) || !this.maxConnections) { chooseProgressFile.call(this); operationFileQueue.call(this); } } else if (this.queue.uploadingFiles.length === 0) { this.notifyComplete(); } } /** * 选择一个文件上传 */ function chooseProgressFile() { this.stage = 'UPLOADING'; // 等待队列中弹出 var file = this.queue.waitingFiles.shift(); // 进入上传队列 this.queue.uploadingFiles.push(file); // 执行上传 uploadFile.call(this, file); } /** * 上传文件 * * @param {ui.File} file 目标文件 */ function uploadFile(file) { // 文件对应的进度组件 var progress = this.getChild('progress-' + file.id); var me = this; // 创建请求 var request = getHttpRequest.call(this); // Need to modified file.request = request; // 修改文件状态 file.status = File.UPLOADING; // 创建一个符合后端接口的数据对象 file[this.paramKey] = file.fileData; delete file.fileData; request.send( { container: this.main }, file ); // 上传中 request.on( 'progress', function (response) { var loaded = response.loaded; var total = response.total; progress.setProperties({loaded: loaded, total: total}); } ); // 上传完成 request.on( 'load', function (event) { var response = event.target.response; // 解析一些校验错误 var error = me.parseError(response); if (error) { // 修改进度状态 progress.updateStatus('client-error', error.message); me.removeFileFromUploading.call(me, file, 'error'); me.fire('error', {file: file}); addToErrorQueue.call(me, file); event.preventDefault(); return; } file.status = File.DONE; // 修改进度状态 progress.updateStatus('done', me.message.SUCCESS_INFO); progress.fadeOut( 1000, function () { removeFileFromUploading.call(me, file, 'load'); if (me.sequentialUploads) { operationFileQueue.call(me); } // 通知完成,供外部捕获,执行预览等操作 me.fire( 'onecomplete', { file: file, data: response } ); } ); } ); // 上传出错 request.on( 'error', function (event) { // 修改进度状态 removeFileFromUploading.call(me, file, 'error'); progress.updateStatus('server-error', event.message || me.message.ERROR_HTTP); me.fire('error', {file: file}); addToErrorQueue.call(me, file); } ); // 传输终止 request.on( 'abort', function (event) { removeFileFromUploading.call(me, file, 'abort'); me.fire('abort', {file: file}); } ); } /** * 将文件移出等待队列 * * @param {ui.File} file 目标文件 */ function removeFileFromWaiting(file) { var queue = this.queue.waitingQueue; this.queue.waitingQueue = u.without(queue, file); } /** * 需要添加的错误文件 * * @param {ui.File} file 目标文件 */ function addToErrorQueue(file) { var sameFile = u.filter(this.queue.failedFiles, function (rawFile) { return rawFile.id === file.id; }); sameFile ? '' : this.queue.failedFiles.push(file); } /** * 将文件移出上传队列并放入完成队列 * * @param {ui.File} file 目标文件 * @param {string} operation 操作 */ function removeFileFromUploading(file, operation) { var queue = this.queue.uploadingFiles; file = u.find( queue, function (rawFile) { return rawFile.id === file.id; } ); this.queue.uploadingFiles = u.without(queue, file); if ('load' === operation) { var completeFiles = this.queue.completeFiles; var sameFile = u.filter(completeFiles, function (rawFile) { return rawFile.id === file.id; }); sameFile ? '' : completeFiles.push(file); } operationFileQueue.call(this); } /** * 获取合适的HttpRequest,用于文件上传 * 目前实现了Leve1 XHR、Level2 XHR以及iframe的封装 * * @return {ui.HttpRequest} */ function getHttpRequest() { var HTTPRequest; if (supportXHR2) { HTTPRequest = require('./L2XMLHttpRequest'); // HTTPRequest = require('./XMLHttpRequest'); } else if (supportXHR) { HTTPRequest = require('./XMLHttpRequest'); } else { HTTPRequest = require('./IframeHttpRequest'); } var httpInstance = new HTTPRequest('POST', this.action); // 修复Firefox上传文件的bug // 上传时reponse会设置concent-type为application/octet-stream // 但是request的accept没有这个取值 httpInstance.setRequestHeader([ {key: 'token', value: this.token}, {key: 'Accept', value: '*/*'} ]); return httpInstance; } esui.register(Uploader); return Uploader; }
function (require) { var u = require('underscore'); var InputControl = require('esui/InputControl'); var eoo = require('eoo'); var painters = require('esui/painters'); var esui = require('esui'); var $ = require('jquery'); /** * Filter * * @class filter.Filter * @extends esui.InputControl */ var Filter = eoo.create( InputControl, { /** * @override */ type: 'Filter', /** * 初始化配置 * * @protected * @override */ initOptions: function (options) { var properties = { // 默认单选 multiple: false, // 是否支持自定义 custom: false, // 自定义按钮Label customBtnLabel: '自定义', datasource: [], value: null }; u.extend(properties, options); this.setProperties(properties); }, /** * 初始化DOM结构 * * @protected * @override */ initStructure: function () { var controlHelper = this.helper; var template = '<div id="${filterPanelId}" class="${filterPanelStyle}">' + '<label id="${labelId}"></label>' + '<div id="${contentPanelId}" class="${contentPanelStyle}"></div>' + '</div>'; var data = { filterPanelStyle: controlHelper.getPartClassName('panel'), filterPanelId: controlHelper.getId('items-wrapper-panel'), labelId: controlHelper.getId('items-label'), contentPanelId: controlHelper.getId('items-panel'), contentPanelStyle: controlHelper.getPartClassName('items-panel') }; var mainElement = this.main; mainElement.innerHTML = this.helper.render(template, data); // 创建控件树 this.initChildren(mainElement); }, /** * 重渲染 * * @method * @protected * @override */ repaint: painters.createRepaint( InputControl.prototype.repaint, { name: ['datasource', 'rawValue'], paint: function (filter, datasource, rawValue) { if (!u.isArray(rawValue)) { rawValue = [rawValue]; } u.each(filter.datasource, function (item, index) { if (u.indexOf(rawValue, item.value) > -1) { item.selected = true; } }); filter.buildItems(); } }, { name: ['label'], paint: function (filter, label) { $(filter.helper.getPart('items-label')).text(label); } } ), /** * 初始化事件交互 * * @protected * @override */ initEvents: function () { var itemMainClass = this.helper.getPartClasses('item')[0]; var itemCmdClass = this.helper.getPartClasses('item-cmd')[0]; this.helper.addDOMEvent( this.main, 'click', '.' + itemMainClass + ', .' + itemCmdClass, function (e) { e.preventDefault(); this.changeItemStatus(e.target); } ); }, /** * 改变选择项的选中状态 * * @protected * @method filter.Filter#changeItemStatus * @param {HTMLElement} target 目标元素 */ changeItemStatus: function (target) { var helper = this.helper; var itemRemoveClass = helper.getPartClassName('item-remove'); var itemCommonClass = helper.getPartClasses('item')[0]; var itemCmdClass = this.helper.getPartClasses('item-cmd')[0]; var clickItem = $(target); var selectedItem = clickItem.closest('.' + itemCommonClass + ', .' + itemCmdClass); var itemIndex = selectedItem.data('index'); var item = u.clone(this.datasource[itemIndex]); if (clickItem.hasClass(itemRemoveClass)) { this.selectItem(item); this.removeItem(item); this.fire('customitemremove', {item: item}); } else { var itemClass = helper.getPartClassName('item'); var cmdItemClass = helper.getPartClassName('item-cmd'); if (selectedItem.hasClass(itemClass)) { this.selectItem(item); } else if (selectedItem.hasClass(cmdItemClass)) { this.fire('customlinkclick', {element: target}); } } }, /** * 根据datasource生成选择项 * * @param {Array} datasource 选项列表数据源 * @private */ buildItems: function () { var helper = this.helper; var htmls = u.map( this.datasource, function (item, index) { var active = item.selected ? helper.getPartClassName('item-active') : ''; return buildItem.call(this, item, active, index); }, this ); helper.getPart('items-panel').innerHTML = htmls.join(''); this.custom && buildCustomItem.call(this); }, /** * 新增选择项 * * @public * @param {Object} item 新增的选择项 */ addItem: function (item) { this.datasource.push(item); this.buildItems(); }, /** * 移除选择项 * * @param {Object} item 待移除的项 */ removeItem: function (item) { var removeItem = this.getItemByValue(item.value); this.datasource = u.without(this.datasource, removeItem); this.buildItems(); }, /** * 设置选择项 * * @param {Object} item 选中项的数据 格式如: {value: '', text: ''} * @public */ unselectItem: function (item) { if (!item || !this.getItemByValue(item.value)) { return; } var targetItem = this.getItemByValue(item.value); targetItem.selected = false; this.buildItems(); }, /** * 选择项 * * @param {Object} item 选中项的数据 格式如: {value: '', text: ''} * @param {HtmlElement} target 选中的元素 * @private */ selectItem: function (item) { var selectedItem = this.getItemByValue(item.value); var lastItem; var oldSelected = selectedItem.selected; // 需要移除前一个单选 if (!this.multiple && !oldSelected) { var selectedItems = this.getSelectedItems(); if (selectedItems.length > 0) { lastItem = selectedItems[0]; lastItem.selected = false; } } selectedItem.selected = !selectedItem.selected; /** * @event select * * 选择时触发 */ this.fire('change', { item: item, lastItem: lastItem, action: oldSelected ? 'unselect' : 'select' }); this.buildItems(); }, /** * 根据值获取整个选择项的数据 * * @param {string} value 值 * @param {Object=} datasource 数据源 * @return {Object} item 选中项的数据 格式如: {value: '', text: ''} * @public */ getItemByValue: function (value, datasource) { datasource = datasource || this.datasource; return u.find( datasource, function (single, index) { return single.value === value; } ); }, /** * 获取选中的项 * * @return {Object} 选中项 */ getSelectedItems: function () { var items = []; u.each(this.datasource, function (item, index) { if (item.selected) { items.push(item); } }); return items; }, /** * 获取选中的值 * * @return {Object} 选中项 */ getValue: function () { var items = this.getSelectedItems(); var valueArr = []; u.each(items, function (item, index) { valueArr.push(item.value); }); return valueArr; } } ); /** * 根据选项数据生成选择项 * * @param {Object} item 选项数据 * @param {string} style 额外的样式 * @param {number} index 数据源中的索引 * @return {HtmlElement} 生成的选择项元素 */ function buildItem(item, style, index) { var template = '' + '<div class="${item | class} ${style}"' + ' data-value="${value}" data-index="${index}" data-allow-delete="${allowDelete}">' + '<span>${text | raw}</span>' + '<!-- if: ${allowDelete}-->' + '<span class="ui-icon ui-filter-remove ui-filter-item-remove"></span>' + '<!-- /if -->' + '</div>'; var data = { style: style || '', value: item.value, index: index, text: item.text, allowDelete: item.allowDelete }; return this.helper.render(template, data); } /** * 生成自定义项 */ function buildCustomItem() { var controlHelper = this.helper; var template = '<div id="${custom-link | id}" class="${item-cmd | class}"><span>${text}</span></div>'; var data = { text: this.customBtnLabel }; var html = this.helper.render(template, data); $(controlHelper.getPart('items-panel')).append(html); } esui.register(Filter); return Filter; }
function (require) { var Control = require('esui/Control'); var u = require('underscore'); var eoo = require('eoo'); var painters = require('esui/painters'); var esui = require('esui'); require('./AdvancedColorPicker'); require('./SimpleColorPicker'); /** * @class FullColorPicker * @extends esui.InputControl */ var FullColorPicker = eoo.create( Control, { /** * 控件类型 * * @override */ type: 'FullColorPicker', /** * 初始化参数 * * @override */ initOptions: function (options) { var properties = { // 默认颜色 hex: '000000', displayHex: '000000', alpha: 100, displayAlpha: 100, // 默认拾色器模式,支持 'simple' | 'advanced' | 'full' defaultMode: 'simple', // 是否支持切换为'full'型 // mode为'simple',属性可配 // mode为'advanced',属性可配 // mode为'full',属性只能为false switchable: true, // 是否支持alpha选择 hasAlpha: true }; u.extend(properties, FullColorPicker.defaultProperties, options); // mode是full型,switchable必须是false if (properties.mode === 'full' || properties.switchable === false) { properties.switchable = false; } this.setProperties(properties); }, /** * @override */ initStructure: function () { var html = []; var advancedHTML = generateAdvancedHTML.call(this); var simpleHTML = generateSimpleHTML.call(this); // 根据模式创建结构 if (this.defaultMode === 'full' || this.switchable) { html = [advancedHTML, simpleHTML]; } else if (this.defaultMode === 'simple') { html = [simpleHTML]; } else { html = [advancedHTML]; } // 如果是可切换的,还要加上切换按钮 if (this.switchable) { var switchClass = this.helper.getPartClassName('mode-switch'); html.push('<div data-ui-variants="link fluid" data-ui-type="Button" data-ui-child-name="switch" '); html.push('class="' + switchClass + '">' + this.fullModeText + '</div>'); } this.main.innerHTML = html.join(''); this.initChildren(); this.currentMode = this.defaultMode; this.addState(this.defaultMode); }, /** * @override * @fires FullColorPicker#change */ initEvents: function () { var control = this; // 大色盘 var advancedColorPicker = this.getChild('advanced'); if (advancedColorPicker) { // 高级色盘改变引发简单色盘色彩重置 advancedColorPicker.on( 'change', function () { var hex = this.getDisplayHex(); var alpha = this.getDisplayAlpha(); control.displayHex = hex; control.displayAlpha = alpha; control.fire('change'); } ); } // 基本色盘变化 var SimpleColorPicker = this.getChild('simple'); if (SimpleColorPicker) { SimpleColorPicker.on( 'change', function () { var color = this.getRawValue(); control.displayHex = color; updateAdvancedColorPicker.call(control); control.fire('change'); } ); } var switchButton = this.getChild('switch'); if (switchButton) { switchButton.on('click', u.bind(switchState, this)); } }, /** * 渲染自身 * * @override */ repaint: painters.createRepaint( Control.prototype.repaint, { name: ['hex', 'alpha'], paint: function (colorPicker, hex, alpha) { if (hex == null && alpha == null) { return; } // 更新显示值 colorPicker.displayHex = hex; colorPicker.displayAlpha = alpha; // 更新高级模式 var advanced = colorPicker.getChild('advanced'); if (advanced) { advanced.setProperties({hex: hex, alpha: alpha}); } } } ), /** * 获取色值 * * @method FullColorPicker#getDisplayHex * @return {string} * @public */ getDisplayHex: function () { return this.displayHex; }, /** * 获取透明度 * * @method FullColorPicker#getDisplayAlpha * @return {number} * @public */ getDisplayAlpha: function () { return this.displayAlpha; } } ); FullColorPicker.defaultProperties = { fullModeText: '完整模式', advancedModeText: '高级模式', simpleModeText: '简单模式' }; function generateAdvancedHTML() { return '' + '<div class="' + this.helper.getPartClassName('advanced-section') + '">' + '<div data-ui-type="AdvancedColorPicker" data-ui-child-name="advanced"' + 'data-ui-no-alpha="' + (this.hasAlpha ? 'false' : 'true') + '">' + '</div>' + '</div>'; } function generateSimpleHTML() { return '' + '<div class="' + this.helper.getPartClassName('simple-section') + '">' + '<div data-ui-type="SimpleColorPicker" data-ui-child-name="simple">' + '</div>' + '</div>'; } /** * 模式切换 */ function switchState() { var newMode; var switchButton = this.getChild('switch'); // 如果当前处于full模式,切换回默认模式 if (this.currentMode === 'full') { newMode = this.defaultMode; switchButton.setContent(this.fullModeText); } else { newMode = 'full'; if (this.currentMode === 'simple') { switchButton.setContent(this.simpleModeText); } else { switchButton.setContent(this.advancedModeText); } } this.removeState(this.currentMode); this.addState(newMode); this.currentMode = newMode; } function updateAdvancedColorPicker() { // 更新色盘 var colorPicker = this.getChild('advanced'); if (colorPicker) { var color = this.displayHex; colorPicker.updateHex(color); var alpha = this.displayAlpha; colorPicker.updateAlpha(alpha); } } esui.register(FullColorPicker); return FullColorPicker; }
function (require) { require('esui/Panel'); require('esui/TextBox'); require('esui/Select'); var esui = require('esui'); var u = require('underscore'); var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var eoo = require('eoo'); var painters = require('esui/painters'); require('esui/behavior/mouseproxy'); var $ = require('jquery'); var Slider = eoo.create( InputControl, { /** * 控件的类型 * * @override * @type {String} */ type: 'Slider', /** * 参数的初始化 * * @protected * @override * @param {Object} options [初始化的参数] */ initOptions: function (options) { /** * 默认的属性 * * @type {Object} * @type {string} defaults.orientation 滑块的形式 横着为'' 竖着为’vertical‘ * @type {number} defaults.start 起始值 默认是0 * @type {number} defaults.end 结束值 默认是10, * @type {number} defaults.step 滑动杆值的步长默认是1 * @type {number | Arrary} defaults.value 滑动杆的值 默认为min或[min, max] * @type {number} defaults.min 最小值 不能小于start, 无值时与start相同 * @type {number} defaults.max 最大值 不能大于end,无值时与end相同 * @type {boolean} defaults.isShowSelectedBG 滑杆已选择的部分是否加背景色显示 显示true 不显示false 默认true * @type {boolean} defaults.range 滑动杆控件是否是选择区间 默认false 是true */ var defaults = { orientation: '', start: 0, end: 10, step: 1, min: null, max: null, isShowSelectedBG: true, range: false }; var properties = {}; u.extend(properties, defaults, options); // 处理min和max properties.min = properties.min || properties.start; properties.max = properties.max || properties.end; // min和max只能在start和end的中间 properties.min = Math.max(properties.min, properties.start); properties.max = Math.min(properties.max, properties.end); // 水平、竖直滑动杆时的设置 if (properties.orientation === 'vertical') { // 竖直滑动杆时参数的设置 properties.leftTop = 'top'; properties.rightBottom = 'bottom'; properties.widthHeight = 'height'; properties.pageXY = 'pageY'; } else { // 水平时参数的设置 properties.leftTop = 'left'; properties.rightBottom = 'right'; properties.widthHeight = 'width'; properties.pageXY = 'pageX'; } // 适配value的数据 properties = adaptValue.call(this, properties); this.$super([properties]); }, /** * 将字符串类型的值转换成原始格式,复杂类型的输入控件需要重写此接口 * * @param {string} value 要转换的string * @param {Object} properties 参数对象 * @return {Mixed} * @protected */ parseValue: function (value, properties) { if ((properties && properties.range) || this.range) { if (typeof value === 'string') { var arr = value.split(','); return [+arr[0], +arr[1]]; } } return value; }, /** * 批量设置控件的属性值 * * @param {Object} properties 属性值集合 * @override */ setProperties: function (properties) { // 给控件设值的时候适配数据用 if (properties.hasOwnProperty('rawValue')) { properties = adaptValue.call(this, properties); } this.$super([properties]); }, /** * 创建滑动杆体 * 有滑块的范围和滑块, * 滑块的范围分为显示的范围、已选的范围 * 滑动杆可能有一个滑块或两个滑块,类型是区间时可能有两个滑块,最大值和最小值 * 任意一个是显示的起始值时显示一个滑块 * 放在原型里是为了可重写 * * @protected */ createBody: function () { var bodyElement = this.bodyElement = this.helper.createPart('body'); var cursorElement = this.cursorElement = this.helper.createPart('body-cursor'); bodyElement.appendChild(cursorElement); // 区间时需要两个滑块 if (this.range) { var cursorElementTwo = this.cursorElementTwo = this.helper.createPart('body-cursortwo'); $(this.cursorElementTwo).addClass(this.helper.getPartClassName('body-cursor')); bodyElement.appendChild(cursorElementTwo); } // 已选择的范围加个背景色 if (this.isShowSelectedBG) { // 已选择的区间元素 var bodySelectedElement = this.bodySelectedElement = this.helper.createPart('body-selected'); bodyElement.appendChild(bodySelectedElement); } this.main.appendChild(bodyElement); // 初始化body内元素的宽度和位置 initBodyElements(this); }, /** * 初始化dom结构,仅在第一次渲染的时候使用 * * @protected * @override */ initStructure: function () { // 竖直滑动杆时增加样式 if (this.orientation === 'vertical') { $(this.main).addClass(this.helper.getPartClassName('vertical')); } /\d+/.test(this.size) && (this.main.style[this.widthHeight] = this.size + 'px'); this.createBody(); }, /** * 初始化事件的交互 * * @protected * @override */ initEvents: function () { // 绑定滑块的事件 bindCursorEvents.call(this); }, /** * 获取滑动杆的值 * * @return {*} 滑动杆的值 */ getValue: function () { var value; if (this.range) { value = [this.minRangeValue, this.maxRangeValue]; } else { value = this.getRawValue(); } return value; }, /** * 重新渲染 * * @protected * @override * @type {Function} 重新渲染时要执行的函数 */ repaint: painters.createRepaint( InputControl.prototype.repaint, { name: 'rawValue', paint: function (slider, value) { setByValue(slider, value, true); } } ), /** * 销毁控件 * * @protected * @override */ dispose: function () { this.bodyElement = null; this.cursorElement = null; this.bodySelectedElement = null; this.activeCursorElement = null; if (this.range) { this.cursorElementTwo = null; } this.$super(arguments); } } ); /** * 适配控件的value * * @param {Object} properties 参数 * @return {Object} 适配后的参数 */ function adaptValue(properties) { var value = properties.value; delete properties.value; if (value != null && properties.rawValue == null) { properties.rawValue = this.parseValue(value, properties); } properties.min = typeof properties.min !== 'undefined' ? properties.min : this.min; properties.max = typeof properties.max !== 'undefined' ? properties.max : this.max; if (properties.range || this.range) { // 值类型是区间时 properties.rawValue = typeof properties.rawValue === 'undefined' ? [properties.min, properties.max] : properties.rawValue; // 结果是区间时 properties.minRangeValue = properties.rawValue[0]; properties.maxRangeValue = properties.rawValue[1]; properties.minRangeValue = Math.max(properties.minRangeValue, properties.min); properties.maxRangeValue = Math.min(properties.maxRangeValue, properties.max); // value只能在[min, max]之间 properties.rawValue = [ properties.minRangeValue, properties.maxRangeValue ]; } else { // 值类型是单个值时 properties.rawValue = typeof properties.rawValue === 'undefined' ? properties.min : properties.rawValue; // value只能在min 和 max中间 properties.rawValue = Math.max(properties.rawValue, properties.min); properties.rawValue = Math.min(properties.rawValue, properties.max); } return properties; } /** * 绑定滑块拖拽的事件 * * @private */ function bindCursorEvents() { var body = this.helper.getPart('body'); // 给滑块绑定事件 if (body) { $(body).mouseproxy( { start: u.bind(mousedownHandler, this), drag: u.bind(mousemoveHandler, this), stop: u.bind(mouseupHandler, this) } ); // 点在其他空白处,滑块也要移动到这里 this.helper.addDOMEvent(body, 'click', mousedownHandler); } } /** * 根据滑块left或top的值来计算value * * @param {number} cursorLeftTop 滑块位置left或top的值 * @return {number} 滑块的值 * @private */ function getValueByLeftTop(cursorLeftTop) { var widthHeight = this.widthHeight; // 滑块容器的宽度 var tmpWidthHeight = this[widthHeight]; // 选择的宽度 var selectedWidthHeight = cursorLeftTop; var similarValue = (selectedWidthHeight / tmpWidthHeight) * (this.end - this.start); // 根据步长算值 similarValue = similarValue - similarValue % this.step; var value = this.start + Math.round(similarValue); return value; } /** * 根据值获取滑块的位置 * * @param {number} value 滑块的值 * @return {number} 滑块的左侧位置 * @private */ function getLeftTopByValue(value) { var widthHeight = this.widthHeight; var bodyElement = this.bodyElement; // 获取滑块容器的位置 var bodyPos = lib.getOffset(bodyElement); var tmpwidthHeight = bodyPos[widthHeight]; var start = this.start; var end = this.end; var cursorLeftTop = (value - start) / (end - start) * tmpwidthHeight; return cursorLeftTop; } /** * 根据值去做相应的调整,包括head里显示、赋值和微调滑块的位置 * 为啥要微调位置,因为你不知道鼠标会停在哪,比如1,2之间跨度太大时 要落到值上 * * @param {Slider} slider 滑动杆控件 * @param {number} value 滑动杆的值 */ function setByValue(slider, value) { var cursorElement = slider.cursorElement; var cursorLeftTop; var leftTop = slider.leftTop; var widthHeight = slider.widthHeight; if (slider.range) { var cursorElementTwo = slider.cursorElementTwo; var cursorLeftTopTwo = getLeftTopByValue.call(slider, value[1]); cursorElementTwo.style[leftTop] = cursorLeftTopTwo + 'px'; cursorLeftTop = getLeftTopByValue.call(slider, value[0]); // hack: 默认第一个滑块的z-index是2 第二个滑块的z-index的是3 // 因为区间的值可以是2,2这种,当两个滑块值是这种切最大值时,这时只能滑块1可拖动 // 这时要把它放在第二个滑块上面 if (value[0] === value[1] && value[0] === slider.max) { cursorElement.style.zIndex = 3; cursorElementTwo.style.zIndex = 2; } else { cursorElement.style.zIndex = 2; cursorElementTwo.style.zIndex = 3; } } else { cursorLeftTop = getLeftTopByValue.call(slider, value); } // 调整滑块的位置 cursorElement.style[leftTop] = cursorLeftTop + 'px'; // 已选择的部分加个背景色显示 if (slider.isShowSelectedBG) { if (slider.range) { slider.bodySelectedElement.style[leftTop] = cursorLeftTop + 'px'; slider.bodySelectedElement.style[widthHeight] = cursorLeftTopTwo - cursorLeftTop + 'px'; } else { slider.bodySelectedElement.style[widthHeight] = cursorLeftTop + 'px'; } } } /** * 鼠标移动的事件 * * @param {Event} e 事件对象 * @param {boolean=} isMouseUp 是否是鼠标松开的触发 是为true 不是为false * @param {Object} data 事件fire时的data * @return {number} 返回value 让mouseup用 * @private */ function mousemoveHandler(e, isMouseUp, data) { if (this.disabled === true) { return false; } if (!u.isBoolean(isMouseUp)) { data = isMouseUp; isMouseUp = false; } var target = this.activeCursorElement; var cursorElement = this.cursorElement; var pageXY = this.pageXY; var leftTop = this.leftTop; var widthHeight = this.widthHeight; // 拖动的滑块距left的值 var cursorLeftTop; // 滑块区间的时候 if (this.range) { // 拖动的是否是第一个滑块 var isFirst = false; // 另外一个滑块的left var otherLeftTop; // 另一个滑块的值 var otherValue; // 滑块是第一个时 if (target.id === cursorElement.id) { otherLeftTop = getLeftTopByValue.call(this, this.maxRangeValue); otherValue = this.maxRangeValue; isFirst = true; cursorLeftTop = Math.max( this.minStartPos - this.startPos, e[pageXY] - this.startPos ); cursorLeftTop = Math.min(cursorLeftTop, otherLeftTop); } else { // 滑块是第二个时 otherLeftTop = getLeftTopByValue.call(this, this.minRangeValue); otherValue = this.minRangeValue; cursorLeftTop = Math.max(otherLeftTop, e[pageXY] - this.startPos); cursorLeftTop = Math.min(cursorLeftTop, this.maxEndPos - this.startPos); } } else { target = cursorElement; cursorLeftTop = Math.max( this.minStartPos - this.startPos, e[pageXY] - this.startPos ); cursorLeftTop = Math.min( cursorLeftTop, this.maxEndPos - this.startPos ); } // 根据left来计算值 var value; var curValue = getValueByLeftTop.call(this, cursorLeftTop); if (this.range) { if (isFirst) { value = [curValue, otherValue]; } else { value = [otherValue, curValue]; } } else { value = curValue; } if (!isMouseUp) { // 避免抖动,这里根据value值重新计算出leftTop cursorLeftTop = getLeftTopByValue.call(this, curValue); target.style[this.leftTop] = cursorLeftTop + 'px'; // 已选择的部分加个背景色显示 if (this.isShowSelectedBG) { if (this.range) { var tmpWidthHeight; if (isFirst) { this.bodySelectedElement.style[leftTop] = cursorLeftTop + 'px'; tmpWidthHeight = otherLeftTop - cursorLeftTop; } else { this.bodySelectedElement.style[leftTop] = otherLeftTop + 'px'; tmpWidthHeight = cursorLeftTop - otherLeftTop; } this.bodySelectedElement.style[widthHeight] = tmpWidthHeight + 'px'; } else { this.bodySelectedElement.style[widthHeight] = cursorLeftTop + 'px'; } } // 滑动的时候触发move事件 this.fire('move', value); } return value; } /** * 鼠标的松开事件 * * @param {Event} e 事件的对象 * @private */ function mouseupHandler(e) { if (this.disabled === true) { return false; } // 去掉active的样式 $(this.activeCursorElement).removeClass( this.helper.getPartClassName('body-cursor-active') ); // 放开和mousemove时做得事是一样的,再做一遍 var value = mousemoveHandler.call(this, e, true); // 设置控件的值,因为是内部设值不涉及重绘,所以不调set*方法了 this.rawValue = value; this.minRangeValue = value[0]; this.maxRangeValue = value[1]; setByValue(this, value, true); // 放开鼠标的时候触发change事件 this.fire('change', value); } /** * 初始化body内元素的坐标和宽度 * * @param {Slider} slider 滑动杆控件 */ function initBodyElements(slider) { var bodyElement = slider.bodyElement; // 获取滑块容器的位置 var bodyPos = lib.getOffset(bodyElement); var leftTop = slider.leftTop; var rightBottom = slider.rightBottom; var widthHeight = slider.widthHeight; // 获取滑块容器的宽度,用来计算值用 slider[widthHeight] = bodyPos[widthHeight]; // 滑块能去的最左边 if (typeof slider.min !== 'undefined') { var minLeftTop = getLeftTopByValue.call(slider, slider.min); // 滑块所能去的最左边 slider.minStartPos = bodyPos[leftTop] + minLeftTop; // 滑竿范围的最左边 slider.startPos = bodyPos[leftTop]; } // 滑块能去的最右侧 if (typeof slider.max !== 'undefined') { var maxLeftTop = getLeftTopByValue.call(slider, slider.max); slider.maxEndPos = bodyPos[leftTop] + maxLeftTop; slider.endPos = bodyPos[rightBottom]; } } /** * 根据鼠标位置,寻找离鼠标位置最近的handle * * @private * @param {Event} e 事件对象 * @return {Element} */ function findNearestCursorElement(e) { var pageXY = this.pageXY; var leftTop = this.leftTop; var bodyElement = this.helper.getPart('body'); var bodyPos = lib.getOffset(bodyElement); var mouseLeftTop = e[pageXY] - bodyPos[leftTop]; // 有两个滑块 if (this.range) { var firstLeftTop = getLeftTopByValue.call(this, this.minRangeValue); var secondLeftTop = getLeftTopByValue.call(this, this.maxRangeValue); var middleLeftTop = firstLeftTop + (secondLeftTop - firstLeftTop) / 2; if (mouseLeftTop > middleLeftTop && this.cursorElementTwo) { return this.cursorElementTwo; } } return this.cursorElement; } /** * 鼠标的按下事件 * * @private * @param {Event} e 事件对象 * @return {boolean} */ function mousedownHandler(e) { if (this.disabled === true) { return false; } var cursorElement = findNearestCursorElement.call(this, e); // 存住活动的对象 this.activeCursorElement = cursorElement; // 增加active的样式 $(cursorElement).addClass(this.helper.getPartClassName('body-cursor-active')); // 点击的时候再初始化各种坐标 为了一些初始化时不在屏幕内的控件 initBodyElements(this); // 滑块首先移动到鼠标点击位置 mousemoveHandler.call(this, e); return true; } esui.register(Slider); return Slider; }
function (require) { var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var u = require('underscore'); var eoo = require('eoo'); var esui = require('esui'); var painters = require('esui/painters'); var $ = require('jquery'); /** * 简单版拾色器 * * @class SimpleColorPicker * @extends esui.InputControl */ var SimpleColorPicker = eoo.create( InputControl, { /** * 控件类型 * * @override */ type: 'SimpleColorPicker', /** * 初始化参数 * * @override * @protected */ initOptions: function (options) { var properties = {}; u.extend(properties, SimpleColorPicker.defaultProperties, options); this.setProperties(properties); }, /** * @override */ initStructure: function () { this.main.innerHTML = createColorBlocks(this); }, /** * @override */ initEvents: function () { this.$super(arguments); this.helper.addDOMEvent(this.main, 'click', chooseColor); }, /** * 渲染自身 * * @override * @protected */ repaint: painters.createRepaint( InputControl.prototype.repaint, { name: 'rawValue', paint: function (colorPicker, rawValue) { syncValue(colorPicker); } } ), /** * 批量更新属性并重绘 * * @fires SimpleColorPicker#change * @override * @public */ setProperties: function (properties) { var changes = this.$super(arguments); if (changes.hasOwnProperty('rawValue')) { this.fire('change'); } return changes; } } ); /** * 默认属性 * * @static * @type {Object} * @public */ SimpleColorPicker.defaultProperties = { colors: [ // row1 {text: '#ffffff', value: 'ffffff'}, {text: '#ededed', value: 'ededed'}, {text: '#d2d2d2', value: 'd2d2d2'}, {text: '#bfbfbf', value: 'bfbfbf'}, {text: '#a0a0a0', value: 'a0a0a0'}, {text: '#898989', value: '898989'}, {text: '#6f6f6f', value: '6f6f6f'}, {text: '#626262', value: '626262'}, {text: '#434343', value: '434343'}, {text: '#333333', value: '333333'}, {text: '#1b1b1b', value: '1b1b1b'}, {text: '#000000', value: '000000'}, // row2 {text: '#50a7f9', value: '50a7f9'}, {text: '#6ebf40', value: '6ebf40'}, {text: '#fff45c', value: 'fff45c'}, {text: '#f39017', value: 'f39017'}, {text: '#ec5d57', value: 'ec5d57'}, {text: '#b36ae2', value: 'b36ae2'}, {text: '#0065c0', value: '0065c0'}, {text: '#92d500', value: '92d500'}, {text: '#f5d327', value: 'f5d327'}, {text: '#c82503', value: 'c82503'}, {text: '#f39017', value: 'f39017'}, {text: '#ec5d57', value: 'ec5d57'}, // row3 {text: '#86ccc8', value: '86ccc8'}, {text: '#acd599', value: 'acd599'}, {text: '#7fcdf4', value: '7fcdf4'}, {text: '#8c97cb', value: '8c97cb'}, {text: '#aa8abd', value: 'aa8abd'}, {text: '#f19fc2', value: 'f19fc2'}, {text: '#f26071', value: 'f26071'}, {text: '#e60013', value: 'e60013'}, {text: '#eb6102', value: 'eb6102'}, {text: '#f8b551', value: 'f8b551'}, {text: '#7fc169', value: '7fc169'}, {text: '#009d97', value: '009d97'}, // row4 {text: '#0068b7', value: '0068b7'}, {text: '#1e2087', value: '1e2087'}, {text: '#611986', value: '611986'}, {text: '#920783', value: '920783'}, {text: '#e5007f', value: 'e5007f'}, {text: '#a40000', value: 'a40000'}, {text: '#a84300', value: 'a84300'}, {text: '#cea973', value: 'cea973'}, {text: '#996b34', value: '996b34'}, {text: '#81511c', value: '81511c'}, {text: '#372f2c', value: '372f2c'}, {text: '#a6927d', value: 'a6927d'} ], mode: 'block' }; function syncValue(colorPicker) { var blocks = colorPicker.main.getElementsByTagName('span'); var blockClass = colorPicker.helper.getPartClassName('block'); u.each( blocks, function (block) { var $block = $(block); if ($block.hasClass(blockClass)) { var color = $block.attr('data-value'); if (color === this.rawValue) { this.helper.addPartClasses('selected', block); } else { this.helper.removePartClasses('selected', block); } } }, colorPicker ); } /** * 创建候选颜色块 * * @param {SimpleColorPicker} colorPicker 控件实例 * @return {string} html */ function createColorBlocks(colorPicker) { var blockTemplate = '' + '<span class="' + colorPicker.helper.getPartClassName('block') + '" ' + 'title="${text}" ' + 'data-value="${value}" ' + 'style="background-color: ${diplayValue}">' + '${text}' + '</span>'; var html = '<div>'; u.each( colorPicker.colors, function (color, index) { color.diplayValue = color.value; if (color.value.indexOf('#') < 0) { color.diplayValue = '#' + color.value; } html += lib.format(blockTemplate, color); } ); html += '</div>'; return html; } /** * 选择颜色 * * @param {Event} e DOM事件对象 */ function chooseColor(e) { var blockClass = this.helper.getPartClassName('block'); var $t = $(e.target); if ($t.hasClass(blockClass)) { var color = $t.attr('data-value'); this.setRawValue(color); } } esui.register(SimpleColorPicker); return SimpleColorPicker; }
function (require) { var esui = require('esui'); var $ = require('jquery'); var u = require('underscore'); var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var eoo = require('eoo'); var painters = require('esui/painters'); require('esui/TextBox'); /** * TokenField * * @class * @extends esui.InputControl */ var TokenField = eoo.create( InputControl, { /** * 控件类型,始终为`"TokenField"` * * @type {string} * @readonly * @override */ type: 'TokenField', /** * 初始化配置 * * @protected * @override */ initOptions: function (options) { var properties = { /** * 控件宽度 * * @type {number} */ width: 300, /** * 输入框最小宽度,剩余宽度不够就换行了 * * @type {number} */ inputWidth: 90, /** * token最小字符串长度,低于该长度不创建, 默认不限制 * * @type {number} */ minLength: 0, /** * token最大数量,默认不限制 * * @type {number} */ limit: 0, /** * 是否允许重复 * * @type {bool} */ allowRepeat: false, /** * 默认空数组 */ rawValue: [] }; u.extend(properties, options); properties.name = properties.name || this.main.getAttribute('name'); this.setProperties(properties); }, /** * 初始化DOM结构 * * @protected * @override */ initStructure: function () { // 如果用的是一个`<input>`,替换成`<div>` if (this.main.nodeName.toLowerCase() === 'input') { this.helper.replaceMain(); this.main.id = this.helper.getId(); } var html = [ '<input type="text" autocomplete="off"', ' class="${inputClasses}"', ' data-ui-type="TextBox"', ' data-ui-width="${width}"', ' data-ui-id="${inputId}">' ].join(''); this.main.innerHTML = lib.format( html, { inputId: this.helper.getId('input'), inputClasses: this.helper.getPartClasses('input'), width: this.inputWidth } ); // 创建控件树 this.initChildren(); }, /** * 初始化事件交互 * * @protected * @override */ initEvents: function () { var controlHelper = this.helper; controlHelper.addDOMEvent(this.main, 'click', this.focusInput); var itemClass = this.helper.getPartClassName('item'); controlHelper.addDOMEvent(this.main, 'click', '.' + itemClass, this.remove); var input = this.getInput(); input.on('focus', this.focus, this); input.on('blur', this.blur, this); input.on('enter', this.enter, this); var inputElem = input.getFocusTarget(); controlHelper.addDOMEvent(inputElem, 'keydown', this.keydown); controlHelper.addDOMEvent(inputElem, 'keyup', this.keyup); }, /** * 获取真实输入框控件 * * @return {esui.TextBox} */ getInput: function () { var inputId = this.helper.getId('input'); return this.viewContext.get(inputId); }, /** * 输入框focus * * @param {Event} e 事件对象 */ focusInput: function (e) { var input = this.getInput(); input.getFocusTarget().focus(); }, /** * 响应focus, 输入框获取焦点时,控件整体相应的focus * * @param {Event} e 事件对象 */ focus: function (e) { this.focused = true; this.helper.addStateClasses('focus'); }, /** * 响应blur * * @param {Event} e 事件对象 */ blur: function (e) { this.focused = false; this.helper.removeStateClasses('focus'); }, /** * 响应keydown,keydown较keyup早触发,keydown时可记录输入框变化前的值 * * @param {Event} e 事件对象 */ keydown: function (e) { var input = this.getInput(); switch (e.keyCode) { // backspace case 8: if (input.getFocusTarget() === document.activeElement) { // keydown触发早于keyup,keydown时记下当前输入框的字符 // 用于keyup时判断是否应删除token this.lastInputValue = input.getRawValue(); } break; default: break; } }, /** * 响应keyup * * @param {Event} e 事件对象 */ keyup: function (e) { if (!this.focused) { return; } var input = this.getInput(); var inputValue = input.getRawValue(); switch (e.keyCode) { // backspace case 8: // delete case 46: if (input.getFocusTarget() === document.activeElement) { if (inputValue.length || this.lastInputValue) { break; } this.remove(); } break; } }, /** * 用户按下回车或者预设置的triggerKey,则触发token创建 * * @param {Event} e 事件对象 */ enter: function (e) { var input = e.target; var inputValue = input.getRawValue(); if (input.getFocusTarget() === document.activeElement && inputValue.length) { createTokensFromInput.call(this); } }, /** * 删除token,有两种情况: * 1. 用户点击删除按钮 * 2. 用户在真实输入框按下backspace / delete键,且输入框中无字符 * * @param {Event=} e 事件对象 */ remove: function (e) { var deleteIndex = -1; var rawValue = this.rawValue.slice(0); var target = e && e.currentTarget; if (!target) { // 通过回车删除, 则删除最后一个 deleteIndex = rawValue.length - 1; } else { // 找到用户点击了哪个item上的删除按钮 var itemClass = this.helper.getPartClassName('item'); deleteIndex = $(this.main).find('.' + itemClass).index(target); } if (deleteIndex >= 0) { var removedValue = rawValue.splice(deleteIndex, 1); this.fire('removetoken', {token: removedValue[0]}); this.setProperties({rawValue: rawValue}); } }, /** * 将字符串类型的值转换成数组格式 * * @param {string} value 字符串值 * @return {Array} * @protected */ parseValue: function (value) { if (u.isString(value)) { return value.split(','); } return value || []; }, /** * 将值从原始格式转换成字符串 * * @param {Array} rawValue 原始值 * @return {string} * @protected */ stringifyValue: function (rawValue) { if (u.isArray(rawValue)) { return rawValue.join(','); } return ''; }, /** * 重绘 * * @protected * @override */ repaint: painters.createRepaint( InputControl.prototype.repaint, painters.style('width'), { name: ['disabled', 'readOnly'], paint: function (textbox, disabled, readOnly) { var input = textbox.getInput(); input.setProperties( { disabled: disabled, readOnly: readOnly } ); } }, { name: ['rawValue'], paint: function (textbox, rawValue) { renderTokens.call(textbox, rawValue); } } ), /** * 清空所有token */ clearAllTokens: function () { var itemClass = this.helper.getPartClassName('item'); $(this.main).find('.' + itemClass).remove(); }, /** * 销毁 * * @protected * @override */ dispose: function () { var controlHelper = this.helper; controlHelper.removeDOMEvent(this.main, 'click', this.focusInput); var itemClass = this.helper.getPartClassName('item'); controlHelper.removeDOMEvent(this.main, 'click', '.' + itemClass, this.remove); var input = this.getInput(); input.un('focus', this.focus, this); input.un('blur', this.blur, this); input.un('enter', this.enter, this); var inputElem = input.getFocusTarget(); controlHelper.removeDOMEvent(inputElem, 'keydown', this.keydown); controlHelper.removeDOMEvent(inputElem, 'keyup', this.keyup); this.$super(arguments); } } ); /** * 检测token是否合法 * * @param {Object} token 要检测的token * @return {boolean} 是否合法 */ function isTokenValid(token) { // token长度要大于最小长度限制 if (!token || token.length <= this.minLength) { return false; } return true; } /** * 查找重复token * * @param {Object} token 要检测的token * @return {Object|null} 如果找到重复token,则返回,否则返回null */ function findRepeatToken(token) { if (!this.allowRepeat) { var repeatIndex = u.indexOf(this.rawValue, token); if (repeatIndex > -1) { var itemClass = this.helper.getPartClassName('item'); return { index: repeatIndex, element: $(this.main).find('.' + itemClass).get(repeatIndex), token: this.rawValue[repeatIndex] }; } } return null; } /** * 闪动指定元素 * * @param {Object} repeatToken 重复的token对象 */ function flashToken(repeatToken) { this.helper.addPartClasses('flash', repeatToken.element); var me = this; setTimeout( function () { me.helper.removePartClasses('flash', repeatToken.element); }, 300 ); } /** * 创建token,添加到dom中 * * @param {string|Object} token 要创建的token定义 */ function renderToken(token) { token = lib.trim(token); var event = this.fire('beforecreate', {token: token}); if (event.preventDefault()) { return; } var $tokenElem = $('<div></div>'); $tokenElem.addClass(this.helper.getPartClassName('item')); $tokenElem.html( this.helper.getPartHTML('label', 'span') + this.helper.getPartHTML('close', 'span') ); // token标签值 var $tokenLabel = $tokenElem.children(':first-child'); $tokenLabel.html(token); // 关闭按钮 var $closeButton = $tokenElem.children(':last-child'); $closeButton.addClass(this.helper.getIconClass()); var input = this.getInput(); var inputElem = input.main; $tokenElem.insertBefore(inputElem); this.fire('aftercreate', {token: token}); } /** * 对tokens进行预处理 * * @param {Array=} rawValue 要设置的token数组 */ function renderTokens(rawValue) { if (u.isArray(this.rawValue)) { // 因为renderTokens是对rawValue进行全量渲染,所以这里要全部清空 this.clearAllTokens(); // 合法性校验 this.rawValue = u.filter(this.rawValue, isTokenValid, this); // token数量要小于最大限制 this.rawValue = u.filter( this.rawValue, function (item, index) { return index < this.limit }, this ); // 重复检测,分为两个场景 // 1. 根据初始rawValue值生成tokens,仅去重; // 2. 在用户输入值时,如果与已存在列表重复,需提示用户, // 派发事件等,则在用户输入时进行处理; // 因此,这里仅对this.rawValue进行重复过滤 if (!this.allowRepeat) { this.rawValue = u.uniq(this.rawValue); } // 根据rawValue值进行全量更新 u.each(this.rawValue, renderToken, this); } } /** * 根据input输入创建tokens列表 */ function createTokensFromInput() { var beforeValue = this.getValue(); var input = this.getInput(); var inputValue = input.getRawValue(); // 检测重复元素 var repeatToken = findRepeatToken.call(this, inputValue); if (repeatToken) { flashToken.call(this, repeatToken); return; } // 这里要保证setProperties时rawValue前后值不同,这里复制一份 if (!this.rawValue) { // 第一次没有设置的时候设置一个空值进来 this.rawValue = []; } var rawValue = this.rawValue.slice(0); rawValue.push(inputValue); this.setProperties({rawValue: rawValue}); if (beforeValue === this.getValue() && inputValue.length) { return; } // token创建成功,清空输入框 input.setRawValue(''); } esui.register(TokenField); return TokenField; }
function (require) { require('esui/Tree'); var esui = require('esui'); var eoo = require('eoo'); var u = require('underscore'); var util = require('../helper/util'); var lib = require('esui/lib'); var RichSelector = require('./RichSelector'); var TreeStrategy = require('./SelectorTreeStrategy'); var painters = require('esui/painters'); var $ = require('jquery'); /** * 控件类 * * @constructor * @param {Object} options 初始化参数 */ var TreeRichSelector = eoo.create( RichSelector, { /** * 控件类型,始终为`"TreeRichSelector"` * * @type {string} * @override */ type: 'TreeRichSelector', /** * @override */ styleType: 'RichSelector', /** * @override */ initOptions: function (options) { var properties = { // 数据源 datasource: null, // 定向展开配置开关,true则可以根据需要默认展开指定的节点 orientExpand: false, // 全节点点击触发展开的配置开关 wideToggleArea: false, // 是否只允许选择叶子节点 onlyLeafSelect: true, // 是否隐藏根节点 hideRoot: true, // 节点状态切换时,父子节点是否需要同步状态 // 有些需求场景是,父子节点除了概念上的从属关系外,交互上没有任何关联 // 选择父节点不代表其下的子节点全被选中;选择全部子节点也不代表父节点选中 needSyncParentChild: true, // 树样式 treeVariants: 'icon angle hoverable', // 大小写是否敏感。默认无视大小写 caseSensitive: false }; u.extend(properties, options); this.$super([properties]); }, /** * @override */ initStructure: function () { this.$super(arguments); $(this.main).addClass( this.helper.getPrefixClass('treerichselector') ); if (this.onlyLeafSelect) { this.addState('only-leaf-selectable'); } }, /** * 重新渲染视图 * 仅当生命周期处于RENDER时,该方法才重新渲染 * * @param {Array=} 变更过的属性的集合 * @override */ repaint: painters.createRepaint( RichSelector.prototype.repaint, { name: 'datasource', paint: function (me, datasource) { me.refresh(); toggleTreeState(me, me.disabled); } }, { name: 'selectedData', paint: function (control, selectedData) { // 如果没有传selectedData,就别取消了。 if (u.isEmpty(selectedData)) { return; } // 先取消选择 var allData = control.allData; // 对于根节点隐藏的情况 // 因为取值也不会取根节点,赋值如果等于根节点 // 避免所有子孙都被选中,这里无视 if (this.hideRoot && allData && selectedData.length === 1) { var selectedItem = selectedData[0]; selectedItem = selectedItem.id || selectedItem.value || selectedItem; if (selectedItem === allData.id) { return; } } if (allData && allData.children) { var oldSelectedData = control.getSelectedItems(); control.selectItems(oldSelectedData, false); control.selectItems(selectedData, true); control.fire('add'); control.fire('change'); } } }, { name: 'disabled', paint: function (me, disabled) { toggleTreeState(me, disabled); } } ), /** * 适配数据,创建一个全集扁平索引 * * @param {ui.TreeRichSelector} treeForSelector 类实例 * @return {Object} 包含`indexData`和`selectedData`两个属性 * @ignore */ adaptData: function () { var selectedData = []; /** * datasource的数据结构: * { * id: -1, * text: '全部', * children: [ * { * id: 1, * text: '节点1', * children: [], * // 以下属性都可以自定义 * isSelected: true, * ... * } * ... * ] * } */ this.allData = lib.deepClone(this.datasource); // 一个扁平化的索引 // 其中包含父节点信息,以及节点选择状态 var indexData = {}; var allData = this.allData; if (allData && allData.children) { this.walkTree( allData, allData.children, function (parent, child) { parent.id = parent.id || parent.value; child.id = child.id || child.value; indexData[child.id] = { parentId: parent.id, node: child, isSelected: false }; if (child.hasOwnProperty('isSelected')) { indexData[child.id].isSelected = child.isSelected; } if (indexData[child.id].isSelected === true) { selectedData.push(child); } } ); // 把根节点也加上 indexData[allData.id] = { parentId: null, node: allData, isSelected: false }; } this.indexData = indexData; return { indexData: indexData, selectedData: selectedData }; }, /** * @override */ processDataAfterRefresh: function (adaptedData) { // 用这个数据结构更新选择状态 if (this.mode !== 'delete') { this.selectItems(adaptedData.selectedData, true); } }, /** * 刷新备选区 * * @override */ refreshContent: function () { var treeData = this.isQuery() ? this.queriedData : this.allData; if (!treeData || !treeData.children || !treeData.children.length) { this.addState('empty'); } else { this.removeState('empty'); } if (!treeData || !treeData.children) { return; } var queryList = this.getQueryList(); var tree = queryList.getChild('tree'); if (!tree) { var options = { childName: 'tree', datasource: treeData, allowUnselectNode: this.allowUnselectNode, strategy: new TreeStrategy( { mode: this.mode, onlyLeafSelect: this.onlyLeafSelect, orientExpand: this.orientExpand } ), wideToggleArea: this.wideToggleArea, hideRoot: this.hideRoot, selectMode: this.multi ? 'multiple' : 'single', variants: this.treeVariants }; if (this.getItemHTML) { options.getItemHTML = this.getItemHTML; } if (this.itemTemplate) { options.itemTemplate = this.itemTemplate; } tree = esui.create('Tree', options); queryList.addChild(tree); tree.appendTo(queryList.main); var control = this; tree.on( 'selectnode', function (e) { var node = e.node; control.handlerAfterClickNode(node); } ); tree.on( 'unselectnode', function (e) { // control.setItemState(e.node.id, 'isSelected', false); control.handlerAfterClickNode(e.node); } ); } else { tree.setProperties( { datasource: lib.deepClone(treeData), keyword: this.getKeyword() } ); } }, getStateNode: function (id) { return this.indexData[id]; }, getItemState: function (id, stateName) { if (this.indexData[id]) { var stateNode = this.getStateNode(id); return stateNode[stateName]; } return null; }, setItemState: function (id, stateName, stateValue) { if (this.indexData[id]) { var stateNode = this.getStateNode(id); stateNode[stateName] = stateValue; } }, getDatasourceWithState: function () { var datasource = lib.deepClone(this.datasource); var indexData = this.indexData; this.walkTree(datasource, datasource.children, function (parent, child) { child.isSelected = indexData[child.id].isSelected; }); return datasource; }, /** * 点击触发,选择或删除节点 * * @param {Object} node 节点对象 * @ignore */ handlerAfterClickNode: function (node) { // 这个item不一定是源数据元,为了连锁同步,再取一遍 var item = this.indexData[node.id]; if (!item) { return; } if (this.mode === 'add') { this.actionForAdd(item); } else if (this.mode === 'delete') { this.actionForDelete(item); } else if (this.mode === 'load') { this.actionForLoad(item); } }, /** * 添加动作 * * @param {Object} item 保存在indexData中的item * */ actionForAdd: function (item) { var stateNode = this.getStateNode(item.node.id); var toBeSelected = true; if (stateNode.isSelected && this.allowUnselectNode) { toBeSelected = false; } // 如果是单选,需要将其他的已选项置为未选 if (!this.multi) { // 如果以前选中了一个,要取消选择 // 节点的状态切换Tree控件会完成,因此无需这里手动unselect if (this.currentSeletedId != null) { this.setItemState(this.currentSeletedId, 'isSelected', !toBeSelected); } // 赋予新值 if (toBeSelected) { this.currentSeletedId = item.node.id; } } this.setItemState(item.node.id, 'isSelected', toBeSelected); // 多选同步父子状态 // 要先更新当前节点状态,再同步祖先与子孙 if (this.multi) { trySyncParentAndChildrenStates(this, item, toBeSelected); } this.fire('add', {item: item.node}); this.fire('change'); }, /** * 添加全部 * * @override */ selectAll: function () { var data = this.isQuery() ? this.queriedData : this.allData; var root = this.getStateNode(data.id); selectItem(this, root.node.id, true); trySyncChildrenStates(this, root, true); this.fire('add'); this.fire('change'); }, /** * 批量选择或取消选择,供外部调用,不提供fire事件 * * @param {Array} nodes 要改变状态的节点集合 * @param {boolean} toBeSelected 目标状态 true是选择,false是取消 * @override */ selectItems: function (nodes, toBeSelected) { var indexData = this.indexData; if (!indexData) { return; } var control = this; u.each( nodes, function (node) { var id = node.id !== undefined ? node.id : node; var item = indexData[id]; if (item != null && item !== undefined) { // 更新状态,但不触发事件 selectItem(control, id, toBeSelected); trySyncParentAndChildrenStates(control, item, toBeSelected); } } ); }, /** * 删除动作 * * @param {Object} item 保存在indexData中的item * */ actionForDelete: function (item) { // 外部需要知道什么数据被删除了 var event = this.fire('delete', {items: [item.node]}); // 如果外面阻止了默认行为(比如自己控制了Tree的删除),就不自己删除了 if (!event.isDefaultPrevented()) { deleteItem(this, item.node.id); this.fire('change'); } }, /** * 删除全部 * * @FIXME 删除全部要区分搜索和非搜索状态么 * @override */ deleteAll: function () { var event = this.fire('delete', {items: this.getSelectedItems()}); // 如果外面阻止了默认行为(比如自己控制了Tree的删除),就不自己删除了 if (!event.isDefaultPrevented()) { this.set('datasource', null); this.fire('change'); } }, /** * 加载动作 * * @param {Object} item 保存在indexData中的item */ actionForLoad: function (item) { this.setItemState(item.node.id, 'isActive', true); // 如果以前选中了一个,要取消选择 if (this.currentActiveId) { this.setItemState(this.currentActiveId, 'isActive', false); // load型树节点状态不是简单的“已选择”和“未选择”,还包含已激活和未激活 // -- 选择状态中的节点不可以激活 // -- 未选择状态的节点可以激活,激活后变成“已激活”状态,而不是“已选择” // -- 激活某节点时,其余“已激活”节点要变成“未激活”状态 // 说了这么多,就是想说,我要自己把“已激活”节点要变成“未激活”状态。。。 // 然后如果这个节点恰好是isSelected状态的,那则不许执行unselect操作 if (!this.getStateNode(this.currentActiveId).isSelected) { var tree = this.getQueryList().getChild('tree'); tree.unselectNode(this.currentActiveId, true); } } // 赋予新值 this.currentActiveId = item.node.id; this.fire('load', {item: item.node}); this.fire('change'); }, /** * 获取指定状态的叶子节点,递归 * * @param {Array} data 检测的数据源 * @param {boolean} isSelected 选择状态还是未选状态 * @return {Array} 叶子节点 * @ignore */ getLeafItems: function (data, isSelected) { data = data || (this.allData && this.allData.children) || []; var leafItems = []; var me = this; u.each( data, function (item) { if (isLeaf(item)) { var valid = (isSelected === this.getItemState(item.id, 'isSelected')); // delete型的树没有“选择”和“未选择”的状态区别,所以特殊处理 if (me.mode === 'delete' || valid) { leafItems.push(item); } } else { leafItems = u.union( leafItems, me.getLeafItems(item.children, isSelected) ); } }, this ); return leafItems; }, /** * 获取当前已选择数据的扁平数组结构 * * @return {Array} * @public */ getSelectedItems: function () { if (!this.allData) { return []; } var selectedItems = []; var control = this; this.walkTree( this.allData, this.allData.children, function (parent, child) { if (control.mode === 'delete' || control.getStateNode(child.id).isSelected) { selectedItems.push(child); } } ); return selectedItems; }, /** * 获取当前已选择的数据的树形结构 * * @return {Object} * @public */ getSelectedTree: function () { var control = this; // clone完整数据,这个数据是原始的,不带最新选择状态的 var copyData = lib.deepClone(this.allData); // 遍历树,把各个节点的children更新成只包含已选状态节点的 this.walkTree( copyData, copyData.children, function (parent, child) { // 找出所有选中节点或父节点状态为`isSomeSelected`, 即该节点存在选中的子孙节点 var selectedChildren = getSelectedNodesUnder(child, control); if (selectedChildren.length) { child.children = selectedChildren; } else { child.children = null; } } ); // 最外层再处理一下 copyData.children = u.filter(copyData.children, function (node) { // 可能是叶子节点 return node.children || control.indexData[node.id].isSelected; }); return copyData; }, /** * @override */ getSelectedItemsFullStructure: function () { return this.getSelectedTree(); }, /** * 清除搜索结果 * * @return {boolean} * @ignore */ clearQuery: function () { this.$super(arguments); if (this.mode !== 'delete') { var selectedData = this.getSelectedItems(); this.selectItems(selectedData, true); } return false; }, /** * 清空搜索的结果 * */ clearData: function () { // 清空数据 this.queriedData = {}; }, /** * 搜索含有关键字的结果 * * @param {Array} filters 过滤参数 */ queryItem: function (filters) { // Tree就只定位一个关键词字段 var keyword = filters[0].value; var filteredTreeData = []; filteredTreeData = queryFromNode.call(this, keyword, this.allData); // 更新状态 this.queriedData = { id: getTopId(this), text: '符合条件的结果', children: filteredTreeData }; this.addState('queried'); this.refreshContent(); var selectedData = this.getSelectedItems(); // 删除型的不用设置 if (this.mode !== 'delete') { this.selectItems(selectedData, true); } }, /** * 一个遍历树的方法 * * @param {Object} parent 父节点 * @param {Array} children 需要遍历的树的孩子节点 * @param {Function} callback 遍历时执行的函数 */ walkTree: function (parent, children, callback) { u.each( children, function (child, key) { callback(parent, child); this.walkTree(child, child.children, callback); }, this ); }, /** * 获取当前列表的结果个数 * * @return {number} * @public */ getFilteredItemsCount: function () { var node = this.isQuery() ? this.queriedData : this.allData; var count = getChildrenCount(this, node, true); return count; }, /** * 获取当前状态的显示个数 * * @return {number} * @override */ getCurrentStateItemsCount: function () { var node = this.isQuery() ? this.queriedData : this.allData; if (!node) { return 0; } var count = getChildrenCount(this, node, true); return count; }, /** * 判断是否根节点 * * @param {Object} node 节点对象 * @return {boolean} */ isRoot: function (node) { return node.id === getTopId(this); } } ); /** * 选择或取消选择 * 如果控件是单选的,则将自己置灰且将其他节点恢复可选 * 如果控件是多选的,则仅将自己置灰 * * @param {ui.TreeRichSelector} control 类实例 * @param {Object} id 结点对象id * @param {boolean} toBeSelected 置为选择还是取消选择 * * @ignore */ function selectItem(control, id, toBeSelected) { var tree = control.getQueryList().getChild('tree'); // 完整数据 var indexData = control.indexData; var item = indexData[id]; if (!item) { return; } // 如果是单选,需要将其他的已选项置为未选 if (!control.multi && toBeSelected) { unselectCurrent(control); // 赋予新值 control.currentSeletedId = id; } control.setItemState(id, 'isSelected', toBeSelected); if (toBeSelected) { tree.selectNode(id, true); } else { tree.unselectNode(id, true); } } /** * 撤销选择当前项 * * @param {ui.TreeRichSelector} control 类实例 * @ignore */ function unselectCurrent(control) { var curId = control.currentSeletedId; // 撤销当前选中项 var treeList = control.getQueryList().getChild('tree'); treeList.unselectNode(curId); control.currentSeletedId = null; } /** * 同步一个节点的父节点和子节点选择状态 * 比如:父节点选中与子节点全部选中的状态同步 * * @param {ui.TreeRichSelector} control 类实例 * @param {Object} item 保存在indexData中的item * @param {boolean} toBeSelected 目标状态 true是选择,false是取消 */ function trySyncParentAndChildrenStates(control, item, toBeSelected) { if (!control.needSyncParentChild) { return; } trySyncParentStates(control, item, toBeSelected); trySyncChildrenStates(control, item, toBeSelected); } /** * 同步一个节点的父节点选择状态 * * @param {ui.TreeRichSelector} control 类实例 * @param {Object} item 保存在indexData中的item * @param {boolean} toBeSelected 目标状态 true是选择,false是取消 */ function trySyncChildrenStates(control, item, toBeSelected) { // 如果当前节点确定选中或者未选中状态 // 就不可能处于半选状态了 control.setItemState(item.node.id, 'isSomeSelected', false); var indexData = control.indexData; var node = item.node; // 如果选的是父节点,子节点也要连带选上 var children = node.children || []; u.each(children, function (child) { selectItem(control, child.id, toBeSelected); trySyncChildrenStates(control, indexData[child.id], toBeSelected); }); } /** * 同步一个节点的子节点选择状态 * * @param {ui.TreeRichSelector} control 类实例 * @param {Object} item 保存在indexData中的item * @param {boolean} toBeSelected 目标状态 true是选择,false是取消 */ function trySyncParentStates(control, item, toBeSelected) { var indexData = control.indexData; // 选的是子节点,判断一下是不是全部选择了,全部选择了,也要勾上父节点 var parentId = item.parentId; var parentItem = indexData[parentId]; if (parentItem) { var brothers = parentItem.node.children || []; var allSelected = !u.find( brothers, function (brother) { return !control.getItemState(brother.id, 'isSelected'); } ); // 如果子节点部分选中,则标记父节点`isSomeSelected`为true var someSelected = u.some( brothers, function (brother) { return control.getItemState(brother.id, 'isSelected') || control.getItemState(brother.id, 'isSomeSelected'); } ); if (!allSelected && someSelected) { control.setItemState(parentId, 'isSomeSelected', true); } else { control.setItemState(parentId, 'isSomeSelected', false); } selectItem(control, parentId, allSelected); trySyncParentStates(control, parentItem, allSelected); } } /** * 删除选择的节点 * * @param {ui.TreeRichSelector} control 类实例 * @param {number} id 结点数据id * * @ignore */ function deleteItem(control, id) { // 完整数据 var indexData = control.indexData; var topId = getTopId(control); // 根节点 if (topId === id) { control.setProperties({datasource: {}}); } else { var item = indexData[id]; var parentId = item.parentId; var parentItem = indexData[parentId]; var node = parentItem.node; var children = node.children || []; // 从parentNode的children里删除 var newChildren = u.without(children, item.node); // 没有孩子了,父节点也删了吧,遇到顶级父节点就不要往上走了,直接删掉 if (newChildren.length === 0 && parentId !== topId) { deleteItem(control, parentId); } else { node.children = newChildren; control.setProperties({datasource: control.allData}); } } } function getSelectedNodesUnder(parentNode, control) { var children = parentNode.children; return u.filter( children, function (node) { return this.getItemState(node.id, 'isSelected') || this.getItemState(node.id, 'isSomeSelected'); }, control ); } /** * 供递归调用的搜索方法 * * @param {string} keyword 关键字 * @param {Object} node 节点对象 * @return {Array} 结果集 */ function queryFromNode(keyword, node) { var filteredTreeData = []; var treeData = node.children; u.each( treeData, function (data, key) { var filteredData; // 命中节点,先保存副本,之后要修改children var config = { caseSensitive: this.caseSensitive, isPartial: true }; if (util.compare(data.text, keyword, config)) { filteredData = u.clone(data); } if (data.children && data.children.length) { var filteredChildren = queryFromNode.call(this, keyword, data); // 如果子节点有符合条件的,那么只把符合条件的子结点放进去 if (filteredChildren.length > 0) { if (!filteredData) { filteredData = u.clone(data); } filteredData.children = filteredChildren; } // 这段逻辑我还是留着吧,以防哪天又改回来。。。 // else { // // 如果命中了父节点,又没有命中子节点,则只展示父节点 // if (filteredData) { // filteredData.children = []; // } // } } if (filteredData) { filteredTreeData.push(filteredData); } }, this ); return filteredTreeData; } function isLeaf(node) { return !node.children; } function getChildrenCount(control, node, onlyLeaf) { var count = 1; // 是叶子节点,但不是root节点 if (onlyLeaf) { if (isLeaf(node)) { // FIXME: 这里感觉不应该hardcode,后期想想办法 if (!node.id || node.id === getTopId(control)) { return 0; } return 1; } // 如果只算叶子节点,父节点那一个不算数,从0计数 count = 0; } else { // 顶级节点不算 if (node.id === getTopId(control)) { count = 0; } } count += u.reduce( node.children, function (sum, child) { return sum + getChildrenCount(control, child, onlyLeaf); }, 0 ); return count; } /** * 获取顶级节点id * * @param {ui.TreeRichSelector} control 当前的控件实例 * @return {number} */ function getTopId(control) { return control.allData.id; } function toggleTreeState(selector, disabled) { var queryList = selector.getQueryList(); var tree = queryList.getChild('tree'); if (!tree) { return; } disabled ? tree.disable() : tree.enable(); } esui.register(TreeRichSelector); return TreeRichSelector; }
function (require) { require('esui/Button'); require('esui/Link'); require('./ProgressQueue'); require('./FileInput'); var eoo = require('eoo'); var esui = require('esui'); var ui = require('esui/main'); var Control = require('esui/Control'); var painters = require('esui/painters'); var File = require('./File'); var $ = require('jquery'); var u = require('underscore'); var lib = require('esui/lib'); var NORMAL = 'normal'; var LIST = 'list'; var CARD = 'card'; var ALL = 'all'; var supportXHR = (window.File && window.XMLHttpRequest); var supportXHR2 = (supportXHR && new window.XMLHttpRequest().upload); var defaultQueue = { // 所有文件队列 queueList: [], // 队列中所有文件的大小 queueSize: 0, // 正在上传的文件 uploadingFiles: [], // 等待开始的文件 waitingFiles: [], // 出错的文件 failedFiles: [], // 遗弃的文件 // 当文件过长时会遗弃多余的文件 abandonedFiles: [], // 上传完成的文件 completeFiles: [] }; // card视图下模板 var cardTemplate = [ '<div class="${fileClass}">', ' <span class="${nameClass}">${fileName}</span>', ' <span class="${sizeClass}">${fileSize}</span>', '</div>', '<div class="${statusClass}">', ' <div class="${statusInfoClass}">', ' <div class="${barContainerClass}">', ' <div class="${barClass}" id="${barId}"></div>', ' </div>', ' </div>', ' <div class="${operationClass}">', ' <esui-button class="${startButtonClass} ui-button-link" data-ui-child-name="start">', ' 开始', ' </esui-button>', ' <div class="${resultClass}" id="${resultId}"></div>', ' <esui-button class="${cancelButtonClass} ui-button-link" data-ui-child-name="cancel">', ' <span class="ui-icon-remove ui-eicons-fw"></span>', ' </esui-button>', ' </div>', '</div>' ].join(''); // list视图下模板 var listTemplate = [ '<div class="${fileClass}">', ' <span class="${nameClass}">${fileName}</span>', ' <span class="${sizeClass}">${fileSize}</span>', '</div>', '<div class="${statusClass}">', ' <div class="${statusInfoClass}">', ' <div class="${barContainerClass}">', ' <div class="${barClass}" id="${barId}"></div>', ' </div>', ' </div>', ' <div class="${operationClass}">', ' <esui-button class="${startButtonClass} ui-button-link" data-ui-child-name="start">', ' 开始', ' </esui-button>', ' <div class="${resultClass}" id="${resultId}"></div>', ' <esui-button class="${cancelButtonClass} ui-button-link" data-ui-child-name="cancel">', ' <span class="ui-icon-remove ui-eicons-fw"></span>', ' </esui-button>', ' </div>', '</div>' ].join(''); /** * 控件类 * * 上传控件有如下结构特点: * * - 上传组件 (必须,至少一个) * -- 文件上传控件 * - 上传列表 (可选,可自定义容器) * -- 由一个或多个进度条组件构成 * * 上传控件有两种模式: * * - 单文件上传 * - 多文件上传 * * @class ui.Uploader * @extends esui.Control */ var Uploader = eoo.create( Control, { /** * 控件类型,始终为`"Uploader"` * * @type {string} * @readonly * @override */ type: 'Uploader', /** * @override */ initOptions: function (options) { var properties = { // 文件上传队列 queue: lib.deepClone(defaultQueue) }; u.extend(properties, this.$self.defaultProperties, options); var adaptProperties = ['sequentialUploads', 'multiple']; u.each( adaptProperties, function (propertyName) { if (properties[propertyName] === 'false') { properties[propertyName] = false; } } ); this.$super([properties]); }, /** * @override */ initStructure: function () { var containerHtml = getContainerHtml.call(this, this.mode); this.main.innerHTML = lib.format( containerHtml, { uploadComboxClass: this.helper.getPartClassName('combox'), uploadComboxHeaderClass: this.helper.getPartClassName('combox-header'), uploadHeaderTag: this.helper.getPartClassName('header-tag'), uploadHeaderTagOperation: this.helper.getPartClassName('tag-operation'), uploadHeaderOutline: this.helper.getPartClassName('header-outline'), totalCount: 0, uploadTotalCountId: this.helper.getId('total-count'), totalSize: 0, uploadTotalSizeId: this.helper.getId('total-size'), uploadRemoveAll: this.helper.getPartClassName('remove-all'), uploadComboxBodyClass: this.helper.getPartClassName('combox-body'), defaultProgressContainerId: this.helper.getId('default-progress-container'), uploadComboxFooterClass: this.helper.getPartClassName('combox-footer'), uploadComboxFooterId: this.helper.getId('combox-footer') } ); // 创建控件树 this.helper.initChildren(); // 创建主容器 if (this.mode !== NORMAL) { var queueMode = this.mode === LIST ? LIST : CARD; var progressContainer = $('#' + this.helper.getId('default-progress-container'))[0]; var progressQueue = ui.create('ProgressQueue', { main: progressContainer, progressMode: this.mode, progressTemplate: this.itemTemplate, showMode: queueMode }); progressQueue.render(); this.progressQueue = progressQueue; var cardTag = this.getChild(queueMode + 'Tag'); var tagClassName = this.helper.getPartClassName('tag-operation'); addedTagClass($(this), tagClassName, cardTag.main); } }, /** * @override */ initEvents: function () { var fileInput = this.getChild('fileInput'); // 伪装button的点击事件 var submitButton = this.getChild('submitButton'); submitButton.on( 'click', function (e) { fileInput.triggerUploadOutside(); e.preventDefault(); } ); // 监听上传input的变化 fileInput.on('change', u.bind(inputChangeHandler, this)); var startAllBtn = this.getChild('startAll'); startAllBtn && startAllBtn.on('click', operationFileQueue, this); var cancelAllBtn = this.getChild('cancelAll'); cancelAllBtn && cancelAllBtn.on('click', this.clear, this); var $main = $(this.main); var cardTag = this.getChild('cardTag'); var tagClassName = this.helper.getPartClassName('tag-operation'); cardTag && cardTag.on('click', function () { addedTagClass($main, tagClassName, cardTag.main); this.progressQueue.setProperties({ showMode: 'card' }); }, this); var listTag = this.getChild('listTag'); listTag && listTag.on('click', function () { addedTagClass($main, tagClassName, listTag.main); this.progressQueue.setProperties({ showMode: 'list' }); }, this); var progressQueue = this.progressQueue; if (progressQueue) { var me = this; progressQueue.on('start', function (e) { // 点击开始上传 chooseProgressFile.call(me, e.file, true); }); progressQueue.on('restart', function (e) { // 重新上传 var file = e.file; e.target.dispose(); // 将文件移出上传队列,然后重新进行上传 removeFileFromUploading.call(me, file); me.receiveFile([file]); }); progressQueue.on('cancel', function (e) { var file = e.file; if (file.request) { file.request.abort(); if (me.sequentialUploads) { operationFileQueue.call(me); } } // 从等待队列中清除 removeFileFromWaiting.call(me, file); progressQueue.removeProgress(file); refreshStutas.call(me); }); $(this.main).on('click', '.state-selector', { progressQueue: progressQueue }, switchProgressByState); } }, /** * @override */ repaint: painters.createRepaint( Control.prototype.repaint, { name: ['text'], paint: function (uploader, text) { var button = uploader.getChild('submitButton'); button.setContent(text); } }, { name: ['disabled', 'readOnly'], paint: function (uploader, disabled, readOnly) { var input = uploader.getChild('fileInput'); var button = uploader.getChild('submitButton'); input.setProperties({disabled: disabled}); input.setProperties({readOnly: readOnly}); button.setProperties({disabled: disabled}); button.setProperties({readOnly: readOnly}); } }, painters.style('width'), painters.style('height') ), /** * 接收文件并做上传前的校验 * * @param {Array} files 接收到的文件,可以是input中取到的,也可以是drag外部传入的 * @public */ receiveFile: function (files) { var event = this.fire('beforeupload', {files: files}); if (event.isDefaultPrevented()) { return; } this.doUpload(files); }, /** * 开始上传 * * @param {Array} files 接收到的文件 * @protected */ doUpload: function (files) { // 超出最大限制,直接返回 if (files.length > this.maxFileNumber) { this.notifyFail(this.message.ERROR_FILE_MAX_NUMBER); return; } files = u.map( files, function (file, index) { // 文件格式检查 if (!checkFileFormat.call(this, file)) { file.status = 'fail'; file.message = this.message.ERROR_FILE_EXTENSIONS; if (this.multiple === false) { this.queue.failedFiles = [file]; } else { this.queue.failedFiles.push(file); } } else if (!checkFileSize.call(this, file)) { file.status = 'fail'; file.message = this.message.ERROR_FILE_MAX_SIEZ; if (this.multiple === false) { this.queue.failedFiles = [file]; } else { this.queue.failedFiles.push(file); } } else { file.status = 'waiting'; // 单文件上传要覆盖之前的文件 if (this.multiple === false) { this.queue.waitingFiles = [file]; } else { this.queue.waitingFiles.push(file); } } return file; }, this ); initFileList.call(this, files); }, /** * 获取Uploader中的文件上传组件 * * @return {DOMElement} 容器中FileInput组件 * @public */ getFileInput: function () { return this.getChild('fileInput'); }, /** * 解析返回中的错误 TODO 这个的具体解析格式要跟后端商定 * * @param {Object} response 请求返回的对象 * @return {string} * @protected */ parseError: function (response) { if (response.success === 'false') { return {message: response.error}; } return null; }, /** * 通知上传失败 * * @method ui.Uploader#notifyFail * @param {string} message 失败消息 * @protected */ notifyFail: function (message) { message = message || '上传失败'; this.fire('fail', {message: message}); }, /** * 通知上传完成 * * @protected * @method ui.Uploader#notifyComplete */ notifyComplete: function () { this.stage = 'COMPLETE'; this.fire('complete', { completeFiles: this.queue.completeFiles, failedFiles: this.queue.failedFiles }); }, /** * 通知上传完成 * * @public * @method ui.Uploader#notifyComplete */ clear: function () { // 等待队列先置空 this.queue.waitingFiles = []; // 上传队列中都取消 u.each(this.queue.uploadingFiles, function (file) { if (file.request) { file.request.abort(); } }); this.progressQueue.clearAllProgress(); this.queue = lib.deepClone(defaultQueue); this.stage = 'COMPLETE'; refreshStutas.call(this); } } ); /** * 默认属性 * * @type {Object} * @public */ Uploader.defaultProperties = { // 上传按钮文本 text: '点击上传', // 文件上传的路径 action: '/uploadFile', // 后台接收时的key名 paramKey: 'files', // 接收的文件类型 accept: '.gif,.jpg,.png,.swf,.flv', // 默认为单文件上传控件 // 单文件上传控件一次只能选择一个文件 multiple: false, // 单个文件最大大小,单位B,默认2M maxFileSize: 2147483648, // 单次最大上传文件数量 maxFileNumber: 20, // 多文件上传时,请求同时开始还是逐个开始 sequentialUploads: false, // 提示信息 // 目前支持提供成功提示,文件大小不符 // 文件类型不匹配, message: { // 上传成功 SUCCESS_INFO: '已完成', // 重新上传 RESTART_INFO: '正在重新上传', // http错误,一般来说就是status返回不是200 ERROR_HTTP: '上传失败,网络连接错误或上传路径无效', // 文件类型错误 ERROR_FILE_EXTENSIONS: '上传失败,安全因素不支持此类文件', // 文件超出最大尺寸, 标准浏览器下可以判断 ERROR_FILE_MAX_SIEZ: '超出最大上传尺寸', // 文件数量超过最大限制了 ERROR_FILE_MAX_NUMBER: '发生错误,上传文件超过限定。', // 格式错误 ERROR_FILE_FORMAT: '文件格式错误' }, // 是否显示进度 showProgress: true, // removed // 进度模式,seperate和total两种,seperate代表每个文件独立进度;total代表所有文件统一计算进度 // progressMode: 'seperate', // singleProgressMode: 'detail', // 文件列表的容器 // 如果没有会添加一个默认容器 progressContainer: null, // 是否自己开始 autoStart: false, /** * @property {string} [mode="all"] * * 指定文本框模式,可以有以下值: * * - `normal`:表示仅仅只有一个上传按钮,不自带任何预览容器 * - `card`:表示使用card视图进行显示 * - `list`:表示使用list列表进行显示 * - `all`: 表示使用card和list两种视图同时进行展示 * * 此属性仅能在初始化时设置,运行期不能修改 * */ mode: 'all', // 容器模板 containerTemplate: '', itemTemplate: { // card视图下的item的展示模板 card: cardTemplate, /** * @property {string|Object} [itemTemplate.list=""] * * 指定文本框模式,可以有以下值: * * - string:表示仅仅只有一个上传按钮,不自带任何预览容器 * - Object:表示使用card视图进行显示 * - header 表示list需要一个默认的title * - content 表示content的模板,一般指item的模板 * * 此属性仅能在初始化时设置,运行期不能修改 * */ list: listTemplate }, // 是否可拖拽 dragable: true, // 是否分片 chunk: true, // 默认4M chunkSize: 4194304 }; /** * 上传输入组件变化事件处理 * * @param {mini-event.Event} e 事件对象 */ function inputChangeHandler(e) { var files = this.getFileInput().files; this.receiveFile(files); } /** * 验证文件格式 * * @param {Object} file file对象 * @return {boolean} * @protected */ function checkFileFormat(file) { if (this.accept) { // 这里就是个内置的`Rule`,走的完全是标准的验证流程, // 主要问题是上传控件不能通过`getValue()`获得验证用的内容, // 因此把逻辑写在控件内部了 var extension = file.name.split('.'); extension = '.' + extension[extension.length - 1].toLowerCase(); var isValid = false; if (typeof this.accept === 'string') { this.accept = lib.splitTokenList(this.accept); } for (var i = 0; i < this.accept.length; i++) { var acceptPattern = this.accept[i].toLowerCase(); if (acceptPattern === extension) { isValid = true; break; } // image/*之类的,表示一个大类 if (acceptPattern.slice(-1)[0] === '*') { var mimeType = acceptPattern.split('/')[0]; var targetExtensions = this.mimeTypes[mimeType]; if (targetExtensions && targetExtensions.hasOwnProperty(extension)) { isValid = true; break; } } } return isValid; } return true; } /** * 验证文件大小 * * @param {Object} file file对象 * @return {boolean} * @protected */ function checkFileSize(file) { // IE9中filechange返回的event只有fileName以及fileId // 所以如果出现这种情况就放过去,让后端做长度校验 if (this.maxFileSize && file.originalSize) { var isValid = false; if (file.originalSize) { isValid = parseInt(file.originalSize, 10) <= parseInt(this.maxFileSize, 10); } return isValid; } return true; } /** * 创建上传进度队列 * * @param {Array} fileList 在队列中的文件 */ function initFileList(fileList) { if (this.mode === NORMAL) { // 不显示进度条的时候自动开始 // TODO 流程 operationFileQueue.call(this); return; } var files = fileList ? fileList : this.queue.waitingFiles; u.each(files, function (file, index) { this.progressQueue.addProgress(file); this.queue.queueList.push(file); }, this); refreshStutas.call(this); } /** * 执行上传队列 * */ function operationFileQueue() { // 当队列中 if (this.queue.waitingFiles && this.queue.waitingFiles.length) { // 一个个上传 if (this.sequentialUploads && this.queue.uploadingFiles.length < 1) { chooseProgressFile.call(this); } else { chooseProgressFile.call(this); operationFileQueue.call(this); } } else if (this.queue.uploadingFiles.length === 0) { this.notifyComplete(); } } /** * 选择一个文件上传 * * @param {Object|undefined} file 待上传的文件 * @param {boolean} singleFlag 是否单线上传 */ function chooseProgressFile(file, singleFlag) { if (!file) { file = this.queue.waitingFiles.shift(); } else { this.queue.waitingFiles = u.filter(this.queue.waitingFiles, function (wFile) { return file.id !== wFile.id; }); } // 等待队列中弹出 // 进入上传队列 this.queue.uploadingFiles.push(file); // 执行上传 uploadFile.call(this, file, singleFlag); refreshStutas.call(this); if (!singleFlag && !this.sequentialUploads) { operationFileQueue.call(this); } } // function chunkFiles(file, singleFlag) { // if (file.originalSize) { // var chunkCount = Math.ceil(file.originalSize); // if (chunkCount >= 2) { // for (var i = 0; i < chunkCount; i++) { // var chunFile; // } // } // else { // } // } // else { // uploadFile.call(this, file, singleFlag); // } // } /** * 上传文件 * * @param {ui.File} file 目标文件 * @param {boolean} singleFlag 是否是one by one上传 */ function uploadFile(file, singleFlag) { var me = this; // 创建请求 var request = getHttpRequest.call(this); file.request = request; // 修改文件状态 file.status = File.UPLOADING; // 创建一个符合后端接口的数据对象 var sendFile = {}; sendFile[this.paramKey] = file.sourceFile; request.send( {container: this.main}, sendFile ); // 正常模式中不包含以下功能 if (this.mode !== NORMAL) { // 上传中 request.on( 'progress', function (response) { var loaded = response.loaded; var total = response.total; me.progressQueue.setProgressDetail(file, total, loaded); } ); // 传输终止 request.on( 'abort', function (event) { removeFileFromUploading.call(me, file, 'abort'); me.fire('abort', {file: file}); } ); } // 上传完成 request.on( 'load', function (event) { var response = event.target.response; // 解析一些校验错误 var error = me.parseError(response); if (error) { me.fire('error', {file: file}); if (me.mode !== NORMAL) { // 修改进度状态 me.progressQueue.notifyError({ file: file, status: 'fail', message: error.message }); me.removeFileFromUploading.call(me, file, 'error'); addToErrorQueue.call(me, file); } refreshStutas.call(me); event.preventDefault(); return; } file.status = File.COMPLETE; me.fire( 'onecomplete', { file: file, data: response } ); if (me.mode !== NORMAL) { // 修改进度状态 removeFileFromUploading.call(me, file, 'load'); if ((me.autoStart && me.sequentialUploads) || !singleFlag) { operationFileQueue.call(me); } me.progressQueue.notifyError(file, 'complete', me.message.SUCCESS_INFO); refreshStutas.call(me); } } ); // 上传出错 request.on( 'error', function (event) { me.fire('error', {file: file}); if (me.mode !== NORMAL) { // 修改进度状态 removeFileFromUploading.call(me, file, 'error'); me.progressQueue.notifyError(file, 'fail', event.message || me.message.ERROR_HTTP); addToErrorQueue.call(me, file); if ((me.autoStart && me.sequentialUploads) || !singleFlag) { operationFileQueue.call(me); } refreshStutas.call(me); } } ); } /** * 将文件移出等待队列 * * @param {ui.File} file 目标文件 */ function removeFileFromWaiting(file) { // TDDO 优化,移出队列方法 this.queue.waitingFiles = u.without(this.queue.waitingFiles, file); this.queue.queueList = u.without(this.queue.queueList, file); this.queue.failedFiles = u.without(this.queue.failedFiles, file); this.queue.uploadingFiles = u.without(this.queue.uploadingFiles, file); this.queue.completeFiles = u.without(this.queue.completeFiles, file); } /** * 需要添加的错误文件 * * @param {ui.File} file 目标文件 */ function addToErrorQueue(file) { var sameFile = u.filter(this.queue.failedFiles, function (rawFile) { return rawFile.id === file.id; }); sameFile.length ? '' : this.queue.failedFiles.push(file); } /** * 将文件移出上传队列并放入完成队列 * * @param {ui.File} file 目标文件 * @param {string} operation 操作 * [operation = 'load'] 将该操作放入到完成队列当中 * [operation = 'error'] 将该操作放入到错误队列当中 */ function removeFileFromUploading(file, operation) { var queue = this.queue.uploadingFiles; file = u.find( queue, function (rawFile) { return rawFile.id === file.id; } ); this.queue.uploadingFiles = u.without(queue, file); // 放入到完成队列当中 if ('load' === operation) { var completeFiles = this.queue.completeFiles; var sameFile = u.filter(completeFiles, function (rawFile) { return rawFile.id === file.id; }); sameFile.length ? '' : completeFiles.push(file); } } /** * 获取合适的HttpRequest,用于文件上传 * 目前实现了Leve1 XHR、Level2 XHR以及iframe的封装 * * @return {ui.HttpRequest} */ function getHttpRequest() { var HTTPRequest; if (supportXHR2) { HTTPRequest = require('./L2XMLHttpRequest'); } else if (supportXHR) { HTTPRequest = require('./XMLHttpRequest'); } else { HTTPRequest = require('./IframeHttpRequest'); } var httpInstance = new HTTPRequest('POST', this.action); return httpInstance; } /** * 获取容器的string * * @param {string} uploaderMode 当前的视图模式 * @return {string} */ function getContainerHtml(uploaderMode) { switch (uploaderMode) { case NORMAL: return getNormalTemplate.call(this); default: return getContainerTemplate.call(this); } } function getNormalTemplate() { if (this.containerTemplate) { return this.containerTemplate; } var tpl = [ '<div class="${uploadComboxClass}">', // 上传input ' <div data-ui-child-name="fileInput"', ' data-ui="type:FileInput;accept:${accept};multiple:${multiple};name:${paramKey};"></div>', // 伪装ge按钮 ' <div data-ui-child-name="submitButton" ', ' data-ui="type:Button;">${text}</div>', '</div>' ].join(''); return tpl; } function getContainerTemplate() { var cardTagTemplate = '<a class="${uploadHeaderTagOperation}" ' + 'data-ui="type:Link;childName:cardTag;href:javascript:void(0);">卡片</a>'; var listTagTemplate = '<a class="${uploadHeaderTagOperation}" ' + 'data-ui="type:Link;childName:listTag;href:javascript:void(0);">列表</a>'; var opTemplate = ''; if (this.mode === ALL) { opTemplate = cardTagTemplate + listTagTemplate; } else if (this.mode === CARD) { opTemplate = cardTagTemplate; } else if (this.mode === LIST) { opTemplate = listTagTemplate; } var tpl = [ '<div class="${uploadComboxClass}">', ' <div class="${uploadComboxHeaderClass}">', ' <div class="${uploadHeaderTag}">', ' ' + opTemplate, ' </div>', ' <div class="${uploadHeaderOutline}">', ' <span id="${uploadTotalCountId}">总个数:${totalCount}</span>', ' <span id="${uploadTotalSizeId}">总大小:${totalSize}KB</span>', ' <esui-button data-ui-child-name="cancelAll" ', ' class="ui-button ui-button-link ${uploadRemoveAll}">', ' 删除全部</esui-button>', ' </div>', ' </div>', ' <div class="${uploadComboxBodyClass}">', ' <div id="${defaultProgressContainerId}"></div>', ' </div>', ' <div class="${uploadComboxFooterClass}" id="${uploadComboxFooterId}">', ' ' + getStatusOperationsHtml.call(this), ' ' + getOperationHtml.call(this), ' </div>', '</div>' ].join(''); return tpl; } // 获取四种状态操作结构 function getStatusOperationsHtml() { // 完成,上传中,等待中,出错 var statusTpl = [ '<div class="ui-button-group" id="${uploadStatusList}">', ' <button type="button" data-state="complete" class="state-selector ui-button-group-first ', ' ui-button ui-button-primary-inverted">完成: ${completeFiles}个</button>', ' <button type="button" data-state="uploading" class="state-selector ui-button ', ' ui-button-primary-inverted">上传中: ${uploadingFiles}个</button>', ' <button type="button" data-state="waiting" class="state-selector ui-button ', ' ui-button-primary-inverted">等待中: ${waitingFiles}个</button>', ' <button type="button" data-state="fail" class="state-selector ui-button-group-last', ' ui-button ui-button-primary-inverted">出错: ${failedFiles}个</button>', '</div>' ].join(''); return lib.format(statusTpl, { uploadStatusList: this.helper.getId('status-list'), completeFiles: this.queue.completeFiles.length, uploadingFiles: this.queue.uploadingFiles.length, waitingFiles: this.queue.waitingFiles.length, failedFiles: this.queue.failedFiles.length, totalCount: this.queue.queueList.length, totalSize: this.queue.queueSize }); } // 获取操作项模板 function getOperationHtml() { var operationHtml = [ '<div class="operation-list">', ' <esui-button data-ui-child-name="startAll" class="ui-button ui-button-link">', ' 全部开始', ' </esui-button>', // 断点续传没有实现,取消功能先注释掉 // ' <esui-button data-ui-child-name="cancelAll" class="ui-button ui-button-link">', // ' 全部取消', // ' </esui-button>', ' <esui-button data-ui-child-name="submitButton" class="ui-button ui-button-primary">', ' <span class="ui-icon-upload"></span>${text}', // 伪装ge按钮 ' </esui-button>', ' <div data-ui-child-name="fileInput"', // 上传input ' data-ui="type:FileInput;accept:${accept};multiple:${multiple};name:${paramKey};"></div>', '</div>' ].join(''); return lib.format(operationHtml, { text: this.text, accept: this.accept, multiple: this.multiple, paramKey: this.paramKey }); } function refreshStutas() { var totalSize = u.reduce(this.queue.queueList, function (memo, file) { var size = 0; if (file.originalSize && u.isNumber(file.originalSize)) { size = file.originalSize; } return memo + size; }, 0); this.queue.queueSize = File.formatSize(totalSize); $('#' + this.helper.getId('total-size')).html('总大小:' + this.queue.queueSize); $('#' + this.helper.getId('total-count')).html('总个数:' + this.queue.queueList.length); $('#' + this.helper.getId('status-list')).replaceWith(getStatusOperationsHtml.call(this)); } function switchProgressByState(e) { var $this = $(this); var progressQueue = e.data.progressQueue; var state = $this.data('state'); $('.state-selector').not($this).removeClass('state-selector-active'); $this.toggleClass('state-selector-active'); progressQueue.showSelectPrgress($this.hasClass('state-selector-active') ? state : 'all'); } function addedTagClass($container, className, target) { $container.find('.' + className).removeClass('tag-selected'); $(target).addClass('tag-selected'); } esui.register(Uploader); return Uploader; }
function (require) { require('esui/Label'); require('esui/Panel'); require('esui/SearchBox'); var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var u = require('underscore'); var eoo = require('eoo'); var painters = require('esui/painters'); var esui = require('esui'); /** * 控件类 * * 富选择控件有如下结构特点: * * - 一个标题栏 (可选) * - 标题栏总数显示 (可选) * - 标题栏批量操作 (可选) * - 一个选择区 (必须) * - 选择区头部搜索框 (可选) * - 一个底部状态栏 (可选) * * 富选择控件有三种交互模式: * * - add 点击节点,执行选择行为,并触发'add'事件 * - load 点击节点,执行选择行为,并触发'load'事件 * - del 点击节点,执行删除行为,并触发'delete'事件 * * 富选择控件有两种选择模式: * * - 单选 * - 多选 * * @class ui.RichSelector * @extends esui.InputControl */ var RichSelector = eoo.create( InputControl, { /** * 控件类型,始终为`"RichSelector"` * * @type {string} * @override */ type: 'RichSelector', /** * @override */ initOptions: function (options) { var properties = { // 是否需要标题栏 hasHead: true, // 标题栏是否需要统计数 needHeadCount: true, // 这个名字出现在标题栏 title: '标题名', // 是否需要批量操作 needBatchAction: false, // 批量操作文字 batchActionLabel: '批量操作', // 是否有搜索功能 hasSearchBox: true, // 是否有腿部信息 hasFoot: true, // 这个字段是对腿部信息的填充 itemName: '结果', // 搜索为空的提示 emptyText: '没有相应的搜索结果', // 是否刷新数据时保持搜索状态 holdState: false, // 选择器类型 'load', 'add', 'delete' // load: 点击某个节点,加载 出一堆别的数据,主要用于样式区分 // add: 点击一个节点,把这个节点加到另一个容器里 // delete: 点击一个节点,删 mode: 'add', multi: true, // 是否允许反选 allowUnselectNode: false }; u.extend(properties, options); this.$super([properties]); }, /** * 创建标题栏HTML * @return {string} */ getHeadHTML: function () { var helper = this.helper; var actionLink = ''; var headCount = ''; if (this.needBatchAction) { var linkClassName = helper.getPartClassName('batch-action-link'); var linkId = this.helper.getId('batch-action'); actionLink = '' + '<a class="' + linkClassName + '" id="' + linkId + '" >' + this.batchActionLabel + '</a>'; } if (this.hasHead && this.needHeadCount) { var countClass = helper.getPartClassName('head-count'); headCount = '<span class="' + countClass + '" data-ui="type:Label;childName:headTotalCount;title:;"></span>'; } var head = [ '<div data-ui="type:Panel;childName:head;"', ' class="${headClass}">', '<span class="${headTitleClass}" data-ui="type:Label;childName:title;title:;">', '${title}</span>${totalCount}', '${actionLink}', '</div>' ].join('\n'); head = lib.format( head, { headClass: helper.getPartClassName('head'), headTitleClass: helper.getPartClassName('head-title'), title: this.title, actionLink: actionLink, totalCount: headCount } ); return head; }, /** * 创建底部状态栏HTML * @return {string} */ getFootHTML: function () { var tpl = [ '<div data-ui="type:Panel;childName:foot;" class="${classes}">', ' <span data-ui="type:Label;childName:totalCount"></span>', ' ${footButton}', '</div>' ].join('\n'); var footButton = ''; if (this.footButtonText) { footButton = '<button data-ui="type:Button;childName:button;variants:link;">' + this.footButtonText + '</button>'; } return lib.format( tpl, { classes: this.helper.getPartClassName('foot'), footButton: footButton } ); }, /** * 创建搜索框HTML * @return {string} */ getSearchBoxHTML: function () { return [ // 搜索区 '<div data-ui="type:Panel;childName:searchBoxArea"', ' class="' + this.helper.getPartClassName('search-wrapper') + '">', ' <div', ' data-ui="buttonPosition:right;buttonVariants:bordered icon;', ' type:SearchBox;childName:itemSearch;variants:clear-border', ' hide-searched-button;searchMode:instant;">', ' </div>', '</div>' ].join(''); }, /** * @override */ initStructure: function () { var tpl = [ // 标题栏 '${head}', // 内容 '<div data-ui="type:Panel;childName:body;" class="${bodyClass}">', // 搜索框 ' ${searchInput}', // 列表区 ' <div data-ui="type:Panel;childName:content" class="${contentClass}">', // 结果为空提示 ' <div data-ui="type:Label;childName:emptyText" ', ' class="${emptyTextClass}">${emptyText}</div>', // 结果列表 ' <div data-ui="type:Panel;childName:queryList" class="${queryListClass}"></div>', ' </div>', '</div>', // 底部概要信息 '${footInfo}' ]; this.main.innerHTML = lib.format( tpl.join('\n'), { head: this.hasHead ? this.getHeadHTML() : '', bodyClass: this.helper.getPartClassName('body'), searchInput: this.hasSearchBox ? this.getSearchBoxHTML() : '', contentClass: this.helper.getPartClassName('content-wrapper'), emptyTextClass: this.helper.getPartClassName('empty-text'), emptyText: this.emptyText, queryListClass: this.helper.getPartClassName('query-list'), footInfo: this.hasFoot ? this.getFootHTML() : '' } ); this.initChildren(); // 初始化模式状态 this.addState(this.mode || 'delete'); // 绑事件 // 批量操作 if (this.needBatchAction) { this.helper.addDOMEvent( this.helper.getPart('batch-action'), 'click', u.bind(this.batchAction, this) ); } // 搜索框 if (this.hasSearchBox) { this.addState('has-search'); var searchBox = this.getSearchBox(); searchBox.on('search', u.bind(search, this)); searchBox.on('clear', u.bind(this.clearQuery, this)); } // 为备选区绑定点击事件 var queryList = this.getQueryList().main; this.helper.addDOMEvent( queryList, 'click', u.bind(this.eventDispatcher, this) ); }, /** * @override */ initEvents: function () { this.$super(arguments); var foot = this.getChild('foot'); var button = foot && foot.getChild('button'); if (button) { button.on( 'click', function (e) { e.preventDefault(); this.fire('footclick'); }, this ); } }, /** * 点击行为分发器 * @param {Event} e 事件对象 * @return {boolean} * @ignore */ eventDispatcher: function (e) { return false; }, /** * 按条件搜索 * @param {string | Object} args 搜索参数 */ search: function (args) { // filterData中的元素要满足一个标准结构: { keys: [], value: '' } // 其中数组型的keys代表一种“并集”关系,也可以不提供 // filterData的各个元素代表“交集”关系。 var filterData = []; var event = this.fire('search', filterData); // 如果没有外部提供搜索条件 if (!event.isDefaultPrevented()) { // 取自带搜索框的值 var searchBox = this.getSearchBox(); if (searchBox) { var defaultFilter = { value: lib.trim(searchBox.getValue()) }; filterData.push(defaultFilter); } } if (filterData.length) { // 查询,更新数据源 this.queryItem(filterData); // 更新腿部总结果 this.refreshFoot(); // 更新头部总结果 this.refreshHead(); // 更新状态 this.addState('queried'); } // 相当于执行清空操作 else { this.clearQuery(); } }, /** * 清除搜索结果 * * @param {ui.RichSelector} richSelector 类实例 * @return {boolean} * @ignore */ clearQuery: function () { // 重置搜索 resetSearchState(this); // 清空数据 this.clearData(); // 更新备选区 this.refreshContent(); // 更新腿部总结果 this.refreshFoot(); // 更新头部总结果 this.refreshHead(); this.fire('clearquery'); return false; }, /** * 获取结果列表承载容器控件,列表在它里面 * @param {ui.RichSelector} richSelector 类实例 * @return {ui.Panel} * @ignore */ getContent: function () { var body = this.getChild('body'); if (body) { return body.getChild('content'); } return null; }, getKeyword: function () { var searchBox = this.getSearchBox(); var isQuery = this.isQuery(); if (searchBox && isQuery) { return lib.trim(searchBox.getValue()); } return null; }, /** * 获取结果列表控件 * @return {ui.TreeForSelector | ui.ListForSelector} * @ignore */ getQueryList: function () { var content = this.getContent(); if (content) { return content.getChild('queryList'); } return null; }, /** * 获取搜索控件 * @return {ui.Panel} * @ignore */ getSearchBox: function () { var searchBoxArea = this.getChild('body').getChild('searchBoxArea'); if (searchBoxArea) { return searchBoxArea.getChild('itemSearch'); } }, /** * 获取腿部总个数容器 * @param {ui.RichSelector} richSelector 类实例 * @return {ui.Panel} * @ignore */ getTotalCountPanel: function () { var foot = this.getChild('foot'); if (!foot) { return null; } return foot.getChild('totalCount'); }, /** * 获取头部总个数容器 * @param {ui.RichSelector} richSelector 类实例 * @return {ui.Panel} * @ignore */ getHeadTotalCountPanel: function () { var head = this.getChild('head'); if (!head) { return null; } return head.getChild('headTotalCount'); }, /** * 判断是否处于query状态 * @return {boolean} */ isQuery: function () { return this.hasState('queried'); }, /** * 批量操作事件处理 * 可重写 * * @return {boolean} */ batchAction: function () { if (this.mode === 'delete') { this.deleteAll(); this.refreshFoot(); this.refreshHead(); } else if (this.mode === 'add') { this.selectAll(); } return false; }, deleteAll: function () { return false; }, addAll: function () { return false; }, /** * 数据适配 * * @public */ adaptData: function () {}, /** * 手动刷新 * * @param {ui.RichSelector} richSelector 类实例 * @ignore */ refresh: function () { // 重建数据,包括索引数据的创建 var adaptedData = this.adaptData(); var needRefreshContent = true; // 刷新搜索区 if (this.hasSearchBox && this.isQuery()) { // 有一种场景(一般在删除型控件里) // 在搜索状态下,删除了某个节点之后,希望还保留在搜索状态下 if (this.holdState) { // 根据关键字获取结果 this.search(this.getKeyword()); // search方法里面已经执行过了 needRefreshContent = false; } // 清空搜索区 else { resetSearchState(this); } } // 刷新主体 if (needRefreshContent) { // 重绘视图 this.refreshContent(); // 视图重绘后的一些额外数据处理 this.processDataAfterRefresh(adaptedData); // 更新底部信息 this.refreshFoot(); // 更新头部总结果 this.refreshHead(); } }, /** * 视图刷新后的一些额外处理 * * @param {Object} adaptedData 适配后的数据 */ processDataAfterRefresh: function (adaptedData) {}, /** * 更新腿部信息 * * @param {ui.RichSelector} richSelector 类实例 * @ignore */ refreshFoot: function () { if (!this.hasFoot) { return; } var count = this.getCurrentStateItemsCount(); // 更新腿部总结果 var totalCountPanel = this.getTotalCountPanel(); if (totalCountPanel) { var itemName = u.escape(this.itemName); totalCountPanel.setText('共 ' + count + ' 个' + itemName); } }, /** * 更新头部信息 * * @ignore */ refreshHead: function () { if (!this.hasHead || !this.needHeadCount) { return; } var count = this.getCurrentStateItemsCount(); // 更新头部总结果 var totalCountPanel = this.getHeadTotalCountPanel(); if (totalCountPanel) { totalCountPanel.setText('(' + count + ')'); } }, getCurrentStateItemsCount: function () { return 0; }, /** * 重新渲染视图 * 仅当生命周期处于RENDER时,该方法才重新渲染 * * @param {Array=} 变更过的属性的集合 * @override */ repaint: painters.createRepaint( InputControl.prototype.repaint, { name: 'title', paint: function (me, title) { var head = me.getChild('head'); var titleLabel = head && head.getChild('title'); titleLabel && titleLabel.setText(title); } }, { name: 'disabled', paint: function (me, disabled) { var serachbox = me.getSearchBox(); if (disabled) { serachbox && serachbox.disable(); } else { serachbox && serachbox.enable(); } } }, painters.style('width') ), /** * 获取已经选择的数据项 * 就是一个代理,最后从结果列表控件里获取 * @return {Array} * @public */ getSelectedItems: function () { return []; }, /** * 获取已经选择的数据的完整数据结构 * @return {*} * @public */ getSelectedItemsFullStructure: function () { return {}; }, /** * 批量更新状态 * @param {Array} items 需要更新的对象集合 * @param {boolean} toBeSelected 要选择还是取消选择 * @public */ /** * 批量更新选择状态 * @param {Array} items 需要更新的对象集合 * @param {boolean} toBeSelected 要选择还是取消选择 * @public */ selectItems: function (items, toBeSelected) {}, /** * 设置元数据 * * @param {Array | Object} value 置为选择的项. */ setRawValue: function (value) { if (!u.isArray(value)) { value = [value]; } if (!this.multi && value.length > 1) { value = value.slice(0); } this.selectItems(value, true); }, /** * 获取已经选择的数据项 * * @return {Array | Object} 多选模式返回数组,单选模式返回单值 */ getRawValue: function () { var selectedItems = this.getSelectedItems(); if (!this.multi) { return selectedItems[0]; } return selectedItems; }, /** * 将value从原始格式转换成string * * @param {*} rawValue 原始值 * @return {string} */ stringifyValue: function (rawValue) { var selectedIds = []; if (!u.isArray(rawValue)) { selectedIds = [rawValue]; } else { u.each(rawValue, function (item) { selectedIds.push(item.id); }); } return selectedIds.join(','); } } ); /** * 根据关键词搜索结果 * @param {event} e SearchBox的点击事件对象 * @ignore */ function search(e) { this.search(); } function resetSearchState(control) { // 删除搜索状态 control.removeState('queried'); // 清空搜索框 var searchBox = control.getSearchBox(); if (searchBox) { searchBox.set('text', ''); } } esui.register(RichSelector); return RichSelector; }
function (require) { var eoo = require('eoo'); var echarts = require('echarts'); var painters = require('esui/painters'); var util = require('../helper/util'); var Control = require('esui/Control'); var $ = require('jquery'); var esui = require('esui'); var u = require('underscore'); /** * 图表基类控件 * * @class ui.BaseChart * @extends esui.Control */ var BaseChart = eoo.create(Control, { /** * @override */ type: 'Chart', /** * @override */ initOptions: function (options) { var properties = { width: 'auto', height: 300 }; u.extend(properties, BaseChart.defaultProperties, options); this.setProperties(properties); }, /** * 初始化图表数据 * * @protected * @method ui.BaseChart#initChartOptions * @return {Object} */ initChartOptions: function () { return {}; }, /** * 获得提示层的title * * @protected * @method BaseChart#getTipTitleHtml * @param {Object} params 相关参数 * @param {boolean} params.isContrast 是否是对比数据 * @param {number} params.pointIndex 坐标点的横坐标索引 * @param {Array} params.pointInfo 坐标点相关信息 * @return {string} 构建完成的title */ getTipTitleHtml: function (params) { return ''; }, /** * 提示层格式器 * * @protected * @method ui.BaseChart#tipFormatter * @param {Array.<Object>} params 坐标点中的信息,具体数据参见ECHARTS文档 * @param {string} axisIndex 坐标点在x轴上的索引位置信息 * @return {string} 格式化后的弹出层html * ========================= * 2013-12-04 星期三 * <图例色块> 展现量:42,000 * <图例色块> 点击量:12,000 * 2012-12-04 星期二 * <图例色块> 展现量:42,000 * <图例色块> 点击量:12,000 * ========================= */ tipFormatter: function (params, axisIndex) { // 要截取一下才是真实的index axisIndex = +axisIndex.slice(5); params = u.map( params, function (param, index) { param.index = index; return param; } ); // 如果是对比图表,那么一个节点中的几个指标值,隔位分成两组 // 按照规定,对比数据,基线数据在前,对比数据在后 // 对应的groups也一样 var paramsGroup; if (this.isContrast) { paramsGroup = [[], []]; u.each( params, function (param, index) { paramsGroup[index % 2].push(param); } ); } else { paramsGroup = [params]; } var html = []; u.each( paramsGroup, function (params, index) { var title = this.getTipTitleHtml({ pointInfo: params, pointIndex: axisIndex, isContrast: index % 2 !== 0 }); html.push(title); u.each( params, function (param, paramIndex) { var itemHTML = buildTipItem.call(this, param); html.push(itemHTML); }, this ); }, this ); return html.join('<br>'); }, /** * 格式化y轴刻度数据 * * @protected * @method ui.BaseChart#formatYAxisData * @param {Object} serie y轴数据 * @param {Object} index 坐标索引 * @return {Object} 返回格式化后的y轴构建所需数据对象 */ formatYAxisData: function (serie, index) { var data = serie.data; // 如果包含对比数据,要把对比数据也加进来 if (this.isContrast) { data = data.concat(this.ySeries[index + 1].data); } var splitNumber = this.splitNumber; // index 只可能为0, 1 0为左边坐标 1为右边坐标 var labelAlign = index === 0 ? 'left' : 'right'; // 自动计算比例尺的逻辑先删掉,PM有反应再加上。。。 // 按从小到大排下序 var sortedData = u.sortBy( [].slice.call(data), function (item) { return +item; } ); // 把最大刻度转换成符合比例尺规范的值 var maxData = sortedData[data.length - 1]; var average = Math.ceil(maxData / splitNumber); // 取最接近average的刻度 var scale = getNearestScale(this, average); var max = scale * splitNumber; var formatter = function (serie) { return function (value) { if (serie.format === 'percent') { value = value + '%'; } else if (serie.format === 'money') { value = util.formatNumber(value, 2); } else if (serie.format === 'int') { value = util.formatNumber(value); } return value; }; }; return { type: 'value', name: '', axisLabel: { show: true, interval: 'auto', // margin和align默认处理的挺好的,如果需要的话,再加上,不需要后续删掉这两个 // margin: this.get('yAxisTextMargin') || 8, // 默认值是8 formatter: formatter(serie) }, min: 0, max: max, splitNumber: splitNumber, scale: true }; }, /** * 绘制图表 * * @public * @method ui.BaseChart#draw */ draw: function () { if (!this.chartOptions.series) { return; } // 画图表 var chart = this.chart; chart.showLoading( { text: this.loadingText } ); chart.setOption(this.chartOptions); chart.hideLoading(); }, /** * 格式化x轴数据 * * @protected * @method ui.BaseChart#formatXSeries * @param {Array.<Object>} xSeries x轴数据系列 * @return {Array.<Object>} */ formatXSeries: function (xSeries) { return xSeries; }, /** * 创建Y轴数据 * * @protected * @method ui.BaseChart#buildYAxis * @param {Array} ySeries Y轴数据集合 * @return {Array} */ buildYAxis: function (ySeries) { var yAxis = []; var gap = 1; if (this.isContrast) { gap = 2; } for (var i = 0; i < ySeries.length; i += gap) { var serie = ySeries[i]; // 格式化y轴刻度数据 var formattedYAxisData = this.formatYAxisData(serie, i); yAxis.push(formattedYAxisData); } return yAxis; }, /** * 格式化y轴某条连续数据 * * @protected * @method BaseChart#formatYSeriesData * @param {Object} serie y轴数据 * @param {number} index y轴数据索引 * @return {Object} 返回格式化后的y轴显示所需数据对象 */ formatYSeriesData: function (serie, index) { return {}; }, /** * @override */ initStructure: function () { /** * 这里将chart单独封装在一个层里是考虑, * 未来可能会在控件中封装其它图表外的操作按钮。 */ var $main = $(this.main); this.main.innerHTML = '' + this.helper.getPartBeginTag('frame', 'div') + this.helper.getPartBeginTag('main', 'div') + this.helper.getPartEndTag('main', 'div') + this.helper.getPartEndTag('frame', 'div'); $main.find('.' + this.helper.getPartClassName('main')).css({ width: this.width, height: this.height }); var chart = echarts.init(this.helper.getPart('main'), this.theme); this.chart = chart; // 绑定resize事件 this.helper.addDOMEvent( window, 'resize', function () { chart.resize(); } ); }, /** * @override */ repaint: painters.createRepaint( Control.prototype.repaint, { name: ['xSeries', 'ySeries'], paint: function (chart, xSeries, ySeries) { if (!ySeries) { return; } // 如果chart的option还没初始化,先初始化 if (!chart.chartOptions) { chart.chartOptions = chart.initChartOptions(); } // 更新X轴数据 chart.chartOptions.xAxis[0].data = chart.formatXSeries(xSeries); // 为了美观,当x轴数据只有1条时,数据不顶格,当X轴数据大于1条时,顶格 chart.chartOptions.xAxis[0].boundaryGap = (xSeries.length === 1); // 跟新Y轴数据 // 1. 构建数据 var formattedYSeries = []; chart.legend = []; u.each( ySeries, function (serie, index) { // 格式化y轴坐标数据 var formattedYSeriesData = chart.formatYSeriesData(serie, index); formattedYSeries.push(formattedYSeriesData); // 更新图例数据 chart.legend[index] = { color: serie.color || this.chart._themeConfig.color[index], format: serie.format }; }, chart ); // 2. 构建坐标 var yAxis = chart.buildYAxis(ySeries); // 开画 chart.chartOptions.yAxis = yAxis; chart.chartOptions.series = formattedYSeries; chart.draw(); } } ), /** * @override */ dispose: function () { if (this.helper.isInStage('DISPOSED')) { return; } if (this.chart) { this.chart.dispose(); this.chart = null; } this.$super(arguments); } }); BaseChart.defaultProperties = { scale: '1, 1.5, 2, 5, 10', splitNumber: 4, loadingText: '正在努力渲染...', // 是否是对比图表 isContrast: false, /** * @property {string|Object} [theme] * * 设置Chart的Theme,可以自行定制。 * http://echarts.baidu.com/doc/example/themeDesigner.html * */ theme: 'macarons' }; /** * 创建单行提示信息 * * @param {Object} param 坐标点中的信息 * @return {string} */ function buildTipItem(param) { var legend = this.legend; if (legend.length === 0) { return ''; } // 图例和节点的对应,通过param的第四个参数 var format = legend[param.index].format; var legendColor = legend[param.index].color; var value = param.data; if (format === 'money') { value = (value === '-') ? '- -' : util.formatNumber(value, 2, '', '¥'); } else if (format === 'int') { value = util.formatNumber(value); } else if (format === 'percent') { value = value + '%'; } // 有时需要自定义tip信息 所以需要允许自定义 var returnedEvent = this.fire('beforebuildtipitem', {param: param, value: value}); var styles = 'margin-right:5px;display:inline-block;width:10px;height:10px;'; var defaultTip = '<b style="' + styles + 'background:' + legendColor + ';"></b>' + param.seriesName + ':' + value; return returnedEvent.itemValue || defaultTip; } /** * 获取单位刻度值 * * 控件设置有默认五个基础比例尺 '1, 1.5, 2, 5, 10' * 根据目标数据的位数对基础比例尺做加权 * 比如 215 是三位数,权值是100,加权后的比例尺是 * '100, 150, 200, 500, 1000' * 可以涵盖目标数字。 * 然后通过比较,获取目标数字所落比例尺区间的最大值,即为最后的单位刻度 * * @param {BaseChart} chart 类实例 * @param {Object} average 实际平均刻度 * * @return {number} scale 获得的单位刻度 */ function getNearestScale(chart, average) { var averageStr = average.toString(); // 位数 var bits = averageStr.length; // 通过平均值的位数计算加权值,比如215的加权就是100 var power = Math.pow(10, bits - 1); // 基础比例尺格式化为数组,默认[1, 1.5, 2, 5, 10] var baseScale = chart.scale.split(','); var scale; for (var i = 0; i < baseScale.length; i++) { // 实际比例尺选择范围是基础比例尺乘以加权值 baseScale[i] = parseFloat(baseScale[i]) * power; // 向上取值 if (average <= baseScale[i]) { scale = baseScale[i]; break; } } return scale; } esui.register(BaseChart); return BaseChart; }
function (require) { var lib = require('esui/lib'); var InputControl = require('esui/InputControl'); var eoo = require('eoo'); var esui = require('esui'); var painters = require('esui/painters'); var u = require('underscore'); require('./FullColorPicker'); require('esui/Overlay'); require('esui/TextBox'); require('esui/Button'); var ColorPicker = eoo.create( InputControl, { /** * 控件类型 * * @override */ type: 'ColorPicker', /** * 初始化参数 * * @override * @protected */ initOptions: function (options) { var properties = { // 展示模式 'attached' | 'dialog' displayMode: 'attached', // 默认拾色器模式,支持 'simple' | 'advanced' | 'full' featureMode: 'simple', switchable: false, hex: '000000', alpha: 100, hasAlpha: true }; u.extend(properties, ColorPicker.defaultProperties, options); this.setProperties(properties); }, /** * 初始化DOM结构 * * @override * @protected */ initStructure: function () { var controlHelper = this.helper; var mainTpl = '' + '<div class="${colorBlockFrameClass}" id="${colorBlockFrameId}">' + '<div class="${colorBlockClass}" id="${colorBlockId}"></div>' + '</div>' + '<div class="${colorInputClass}" id="${colorInputId}" data-ui-type="TextBox"' + 'data-ui-child-name="colorInput" data-ui-hint="#" data-ui-hint-type="prefix"' + 'data-ui-width="auto"></div>'; if (this.hasAlpha) { mainTpl += '' + '<div class="${alphaInputClass}" id="${alphaInputId}" data-ui-type="TextBox"' + 'data-ui-child-name="alphaInput" data-ui-hint="%" data-ui-hint-type="suffix"' + 'data-ui-width="auto"></div>'; } this.main.innerHTML = lib.format( mainTpl, { colorBlockFrameClass: controlHelper.getPartClassName('color-block-frame'), colorBlockFrameId: controlHelper.getId('color-block-frame'), colorBlockClass: controlHelper.getPartClassName('color-block'), colorBlockId: controlHelper.getId('color-block'), colorInputClass: controlHelper.getPartClassName('color-input'), colorInputId: controlHelper.getId('color-input'), alphaInputClass: controlHelper.getPartClassName('alpha-input'), alphaInputId: controlHelper.getId('alpha-input') } ); this.initChildren(); }, /** * @override */ initEvents: function () { this.$super(arguments); var colorBlock = this.helper.getPart('color-block'); // 点色块走的是toggle,所以阻止冒泡到`document` this.helper.addDOMEvent( colorBlock, 'click', function (e) { e.stopPropagation(); } ); this.helper.addDOMEvent(colorBlock, 'click', toggleLayer); var colorInput = this.getChild('colorInput'); colorInput.on('input', u.bind(onColorInput, this)); var alphaInput = this.getChild('alphaInput'); if (alphaInput) { alphaInput.on('input', u.bind(onAlphaInput, this)); } }, /** * 渲染自身 * * @override * @protected * @fires ColorPicker#change */ repaint: painters.createRepaint( InputControl.prototype.repaint, { name: ['hex', 'alpha'], paint: function (colorPicker, hex, alpha) { updateColorDisplay.call(colorPicker); syncValue.call(colorPicker); colorPicker.fire('change'); } }, { name: ['disabled'], paint: function (colorPicker, disabled) { if (disabled) { colorPicker.helper.disableChildren(); } else { colorPicker.helper.enableChildren(); } } }, { name: ['readOnly'], paint: function (colorPicker, readOnly) { u.each( colorPicker.children, function (child) { if (readOnly) { child.addState('readOnly'); } else { child.removeState('readOnly'); } } ); } } ), /** * 批量更新属性并重绘 * * @override * @fires ColorPicker#change */ setProperties: function (properties) { var changes = this.$super(arguments); if (changes.hasOwnProperty('rawValue')) { this.fire('change'); } return changes; }, /** * @override * @return {Object} */ getRawValue: function () { var result = {}; result.hex = this.hex; if (this.hasAlpha) { result.alpha = this.alpha; } return result; } } ); ColorPicker.defaultProperties = { chooseColorText: '颜色选择', closeText: '关闭', okText: '确定', cancelText: '取消' }; /** * 颜色输入框输入事件处理 * * @param {mini-event.Event} e 事件参数 */ function onColorInput(e) { var colorInput = e.target; var hex = colorInput.getValue(); // 只取输入的前6位 if (hex.length > 6) { hex = hex.slice(0, 6); } hex = new Array(6 - hex.length + 1).join('0') + hex; // 更新主元素显示 var colorBlock = this.helper.getPart('color-block'); colorBlock.style.background = '#' + hex; this.hex = hex; // 修改后需要fire一个事件出去 this.fire('change'); } /** * 透明度输入框输入事件处理 * * @param {mini-event.Event} e 事件参数 */ function onAlphaInput(e) { var alpha = e.target.getValue(); this.alpha = alpha; // 修改后需要fire一个事件出去 this.fire('change'); } function updateColorDisplay() { var colorBlock = this.helper.getPart('color-block'); colorBlock.style.backgroundColor = '#' + this.hex; var colorInput = this.getChild('colorInput'); colorInput.setValue(this.hex); if (this.hasAlpha) { var alphaInput = this.getChild('alphaInput'); alphaInput.setValue(this.alpha); } } function syncValue() { var overlay = this.getChild('layer'); if (overlay) { var properties = {}; properties.hex = this.getChild('colorInput').getValue(); if (this.hasAlpha) { properties.alpha = this.getChild('alphaInput').getValue(); } var colorPicker = overlay.getChild('colorPicker'); if (colorPicker) { colorPicker.setProperties(properties); } } } function createLayer() { var overlayMain = this.helper.createPart('layer', 'div'); lib.addClass(overlayMain, this.helper.getPartClassName('layer')); var pickerContent = '' + this.helper.getPartBeginTag('head', 'div') + this.helper.getPartBeginTag('title', 'div') + this.chooseColorText + this.helper.getPartEndTag('title', 'div') + this.helper.getPartBeginTag('close-btn', 'div') + this.closeText + this.helper.getPartEndTag('close-btn', 'div') + this.helper.getPartEndTag('head', 'div') + '<div data-ui-type="FullColorPicker" data-ui-child-name="colorPicker"' + 'data-ui-default-mode="' + this.featureMode + '"' + 'data-ui-switchable="' + this.switchable + '"' + 'data-ui-has-alpha="' + this.hasAlpha + '">' + '</div>' + this.helper.getPartBeginTag('foot-frame', 'div') + this.helper.getPartBeginTag('foot', 'div') + '<div class="' + this.helper.getPartClassName('ok-btn') + '"' + 'data-ui="type:Button;childName:btnOk;variants:primary wide">' + this.okText + '</div>' + '<div class="' + this.helper.getPartClassName('cancel-btn') + '"' + 'data-ui="type:Button;childName:btnCancel;variants:link wide">' + this.cancelText + '</div>' + this.helper.getPartEndTag('foot', 'div') + this.helper.getPartEndTag('foot-frame', 'div'); var colorPickerOverLay = esui.create( 'Overlay', { main: overlayMain, childName: 'layer', content: pickerContent } ); this.addChild(colorPickerOverLay); colorPickerOverLay.appendTo(this.main); colorPickerOverLay.addState(this.displayMode); var colorPicker = colorPickerOverLay.getChild('colorPicker'); // displayMode决定弹层展示模式 if (this.displayMode === 'attached') { // 变了即时更新 var control = this; colorPicker.on( 'change', function (e) { var hex = this.getDisplayHex(); var alpha = this.getDisplayAlpha(); if (hex !== control.hex || alpha !== control.alpha) { control.setProperties({hex: hex, alpha: alpha}); } } ); } else { // 初始化关闭按钮 var closeBtn = this.helper.getPart('close-btn'); if (closeBtn) { this.helper.addDOMEvent(closeBtn, 'click', hideOverlay); } // 初始化提交和取消按钮 var btnOk = colorPickerOverLay.getChild('btnOk'); var btnCancel = colorPickerOverLay.getChild('btnCancel'); btnOk.on('click', u.bind(submit, this)); btnCancel.on('click', u.bind(hideOverlay, this)); } return this.getChild('layer'); } /** * 确认颜色选择 * * @fires ColorPicker#submit */ function submit() { var pickerOverlay = this.getChild('layer'); var fullColorPicker = pickerOverlay.getChild('colorPicker'); this.setProperties({hex: fullColorPicker.getDisplayHex(), alpha: fullColorPicker.getDisplayAlpha()}); pickerOverlay.hide(); this.fire('submit'); } function hideOverlay() { var pickerOverlay = this.getChild('layer'); pickerOverlay.hide(); } /** * 显示下拉弹层 */ function showLayer() { var colorPickerOverLay = this.getChild('layer'); // displayMode决定弹层展示模式 var properties = { hasMask: true, fixed: false, autoClose: false }; if (this.displayMode === 'attached') { properties = { attachedDOM: this.helper.getId('color-block-frame'), attachedLayout: 'bottom,left' }; } colorPickerOverLay.setProperties(properties); colorPickerOverLay.show(); colorPickerOverLay.resize(); } /** * 根据当前状态显示或隐藏浮层 */ function toggleLayer() { var layer = this.getChild('layer'); if (!layer) { layer = createLayer.call(this); } if (layer.isHidden()) { syncValue.call(this); showLayer.call(this); } else { layer.hide(); } } esui.register(ColorPicker); return ColorPicker; }
function (require) { var lib = require('./lib'); var ui = require('esui'); var Control = require('./Control'); require('./TextBox'); require('./Button'); /** * 搜索框控件,由一个文本框和一个搜索按钮组成 * * @extends Control * @param {Object} [options] 初始化参数 * @constructor */ function SearchBox(options) { Control.apply(this, arguments); } /** * @override */ SearchBox.prototype.type = 'SearchBox'; /** * 初始化参数 * * @param {Object} [options] 构造函数传入的参数 * @protected * @override */ SearchBox.prototype.initOptions = function (options) { var properties = { // 搜索模式:`instant`、`normal` searchMode: 'normal', // 搜索框内容为空,默认为search图标,使用时不转义 buttonContent: '', // 搜索button默认的样式为primary buttonVariants: 'primary icon', // 搜索button默认的位置为left buttonPosition: 'left', // 默认值为'' text: '', // 控件内部使用的状态,外部MUST NOT设置该属性 searched: false, width: '' }; lib.extend(properties, options); if (properties.disabled === 'false') { properties.disabled = false; } if (lib.isInput(this.main)) { if (!properties.placeholder) { properties.placeholder = lib.getAttribute(this.main, 'placeholder'); } if (!properties.text) { properties.text = this.main.value; } if (!properties.maxLength && (lib.hasAttribute(this.main, 'maxlength') || this.main.maxLength > 0)) { properties.maxLength = this.main.maxLength; } } // TODO: custom elments 的兼容 else { if (!properties.text) { properties.text = lib.getText(this.main); } } if (!properties.title) { properties.title = this.main.title; } if (properties.text) { properties.searched = true; } Control.prototype.initOptions.call(this, properties); }; /** * 初始化DOM结构 * * @protected * @override */ SearchBox.prototype.initStructure = function () { var tpl = '' + '<esui-text-box data-ui-mode="text" data-ui-child-name="text"' + 'data-ui-placeholder="${placeholder}" data-ui-icon="${clearClasses}"' + 'data-ui-variants="icon-right" data-ui-width="auto">' + '</esui-text-box>'; var addonTPL = getAddonHTML.apply(this); // instant模式下搜索图标在textbox前 if (this.buttonPosition === 'left') { tpl = addonTPL + tpl; } // normal模式下搜索图标在textbox之后 else if (this.buttonPosition === 'right') { tpl += addonTPL; } var html = lib.format( tpl, { placeholder: this.placeholder, clearClasses: this.helper.getIconClass('times-circle') } ); if (lib.isInput(this.main)) { this.helper.replaceMain(); } if (this.buttonPosition) { lib.addClass(this.main, this.helper.getPrefixClass('textbox-wrapper')); } this.main.innerHTML = html; this.helper.initChildren(this.main); }; /** * 获取搜索按钮或搜索图标HTML * * @return {string} */ function getAddonHTML() { // 即时搜索不需要搜索按钮 var addonContent = '<span class="${searchIconClasses}"></span>'; var notInstant = this.searchMode !== 'instant'; // normal模式下有搜索按钮 addonContent = '' + '<button data-ui="type:Button;childName:search;variants:${buttonVariants}"' + 'class="${searchClasses}">' + (this.buttonContent ? this.buttonContent : addonContent) + '</button>'; var tpl = '' + '<div class="${addonClasses}">' + addonContent + '</div>'; var helper = this.helper; return lib.format( tpl, { searchIconClasses: helper.getIconClass('search'), buttonVariants: this.buttonVariants, searchClasses: helper.getPartClassName('search'), addonClasses: helper.getPrefixClass('textbox-addon') } ); } /** * 初始化事件交互 * * @protected * @override */ SearchBox.prototype.initEvents = function () { var textbox = this.getChild('text'); var delegate = require('mini-event').delegate; // 处理输入事件 textbox.on('input', onInput, this); // 即时模式下输入触发搜索 if (this.searchMode === 'instant') { delegate(textbox, 'input', this, 'search'); } // 代理回车键事件 delegate(textbox, 'enter', this, 'search'); // 回车时要取消掉默认行为,否则会把所在的表单给提交了 textbox.on( 'keypress', function (e) { if (e.keyCode === 13) { e.preventDefault(); } } ); // 清除搜索按钮 textbox.on('iconclick', clear, this); textbox.on('focus', lib.bind(this.addState, this, 'focus')); textbox.on('blur', lib.bind(this.removeState, this, 'focus')); var searchButton = this.getChild('search'); if (searchButton) { delegate(searchButton, 'click', this, 'search'); } }; /** * 处理输入框的input事件,根据输入框是否有内容增加/移除`searched`状态 */ function onInput() { var textbox = this.getChild('text'); var method = textbox.getValue() ? 'addState' : 'removeState'; this[method]('searched'); } /** * 清除搜索关键词,`instant`模式下触发搜索 */ function clear() { var textbox = this.getChild('text'); textbox.setValue(''); this.removeState('searched'); if (this.searchMode === 'instant') { this.fire('search'); } else { this.fire('clear'); } } /** * 获取输入值 * * @return {string} * @override */ SearchBox.prototype.getValue = function () { var text = this.getChild('text'); return text.getValue(); }; var paint = require('./painters'); /** * 渲染自身 * * @protected * @override */ SearchBox.prototype.repaint = paint.createRepaint( Control.prototype.repaint, paint.attribute('title'), { name: [ 'maxLength', 'placeholder', 'text', 'height', 'disabled', 'readOnly' ], /* eslint-disable max-params */ paint: function (box, maxLength, placeholder, text, height, disabled, readOnly) { var properties = { /** * @property {number} maxLength * * 最大长度,参考{@link TextBox#maxLength} */ maxLength: maxLength, /** * @property {string} placeholder * * 无内容时的提示文字,参考{@link TextBox#placeholder} */ placeholder: placeholder, /** * @property {string} text * * 文字内容 */ value: text, height: height, disabled: disabled, readOnly: readOnly }; box.getChild('text').setProperties(properties); } /* eslint-enable max-params */ }, { name: 'disabled', paint: function (box, disabled) { var searchButton = box.getChild('search'); searchButton && searchButton.set('disabled', disabled); } }, { /** * @property {number} width * * 搜索框的宽度 */ name: 'width', paint: function (box, width) { box.main.style.width = width + 'px'; } }, { /** * @property {boolean} searched * * 设定SearchBox是否已有搜素关键词 */ name: 'searched', paint: function (box, searched) { var method = searched ? 'addState' : 'removeState'; box[method]('searched'); } }, { /** * @property {boolean} searched * * 设定SearchBox的工作模式:`instant` | `normal` */ name: 'searchMode', paint: function (box, searchMode) { box.addState(searchMode); } } ); lib.inherits(SearchBox, Control); ui.register(SearchBox); return SearchBox; }
function (require) { var $ = require('jquery'); var Control = require('esui/Control'); var esui = require('esui'); var eoo = require('eoo'); var painters = require('esui/painters'); var u = require('underscore'); /** * Drawer类定义。 * * @class * @extends Control * * @constructor * * 创建新的Drawer实例 * * @param {Object} [options] 组件参数 */ var Drawer = eoo.create( Control, { /** * Drawer类型用于注册到ESUI库 * * @override */ type: 'Drawer', /** * 初始化传入参数。 * * @param {Object} options 初始化参数 * @override */ initOptions: function (options) { var properties = { /** * @property {Number|string} * * Drawer的宽度。可以为百分比或者像素。 */ width: null }; u.extend(properties, options); this.setProperties(properties); }, /** * 初始化组件结构 * * @override */ initStructure: function () { var me = this; var main = me.main; me.$super(arguments); if (main.parentNode && main.parentNode.nodeName.toLowerCase() !== 'body') { document.body.appendChild(main); } var roles = parseMainElement(main); var tempEle; for (var key in roles) { if (roles.hasOwnProperty(key)) { tempEle = roles[key]; tempEle.id = me.helper.getId(key); me.helper.addPartClasses(key, tempEle); } } this.helper.initChildren(); }, /** * 组件的重绘属性处理函数。 * * @override */ repaint: painters.createRepaint( Control.prototype.repaint, { name: ['width'], paint: function (drawer, width) { var helper = drawer.helper; var w = 'width'; if (width) { $(helper.getPart('header')).css(w, width); $(helper.getPart('content')).css(w, width); $(helper.getPart('footer')).css(w, width); } } }, { name: ['title'], paint: function (drawer, title) { var titleId = drawer.helper.getId('title'); $('#' + titleId).html(title); } } ), /** * 事件绑定 * * @override */ initEvents: function () { this.$super(arguments); var helper = this.helper; // 监听main中具有data-role="close"的节点 点击就关闭自己 helper.addDOMEvent(this.main, 'click', '[data-role="close"]', close); }, /** * 显示Drawer */ show: function () { $(this.main).addClass(this.helper.getPrimaryClassName('visible')); $(document.body).addClass(this.helper.getPrimaryClassName('hide-overflow')); this.fire('show'); }, /** * 隐藏Drawer */ hide: function () { // 如果直接显示页面滚动,可能会出现content和页面双滚动 // 因此延迟一会儿 var hiddenFlowClass = this.helper.getPrimaryClassName('hide-overflow'); setTimeout(function () { $(document.body).removeClass(hiddenFlowClass); }, 200); $(this.main).removeClass(this.helper.getPrimaryClassName('visible')); this.fire('hide'); }, /** * 关闭drawer, 和hide区别是close会fire`hide`事件 */ close: function () { close.call(this); }, /** * 销毁组件 * * @override */ dispose: function () { // drawer里面可能存在link触发global redirect,这里手动隐藏一次 $(document.body).removeClass(this.helper.getPrimaryClassName('hide-overflow')); $(this.main).remove(); this.$super(arguments); } } ); /** * 关闭按钮的处理函数 * * @param {Object} e 事件参数 * @return {boolean} 是否关闭成功 * @private */ function close(e) { e && e.preventDefault(); var beforecloseEvent = this.fire('beforeclose'); // 阻止事件,则不继续运行 if (beforecloseEvent.isDefaultPrevented()) { return false; } this.hide(); this.fire('close'); return true; } /** * 递归一个DOM元素,取到组件相关的DOM元素 * * @param {DOMElement} element 要进行parse的元素 * @return {DOMElement} 需要拷贝到剪切板的内容 * @private */ function parseMainElement(element) { var roles = {}; $(element).find('[data-role]').each(function () { roles[$(this).data('role')] = this; }); return roles; } esui.register(Drawer); return Drawer; }
define(function (require) { var u = require('underscore'); var esui = require('esui'); var lib = require('esui/lib'); var paint = require('esui/painters'); var Control = require('esui/Control'); var eoo = require('eoo'); var $ = require('jquery'); var MAIN_TPL = [ '<div class="${typeSelector}-main" id="${contentId}">', '<div class="${typeSelector}-content">', '<span class="${typeSelector}-pointer ${typeSelector}-pointer-active-l ${iconLeftArrow}"', ' id="${leftId}"></span>', '<div class="ui-carouse-list-wrap">', '<ul class="${typeSelector}-list" id="${listId}"></ul>', '</div>', '<span class="${typeSelector}-pointer ${typeSelector}-pointer-active-r ${iconRightArrow}"', ' id="${rightId}"></span>', '</div>', '<div class="${typeSelector}-toolbar">', '<ul id="${toolbarId}"></ul>', '</div>', '</div>' ].join(''); var ITEM_TPL = [ '<li class="${typeSelector}-item ${itemSelector}" index="${index}" ', 'style="width:${width}px;height:${height}px;margin-right:${spacing}px;">', '<img class="${typeSelector}-item-img" src="${imgSrc}"/>', '<span class="${typeSelector}-check ${iconCheck}"></span>', '</li>' ].join(''); var PAGE_TPL = '<li index="${index}" class="${typeSelector}-page"></li>'; var Carousel = eoo.create( Control, { /** * 控件类型 * * @type {string} * @readonly * @override */ type: 'Carousel', /** * 初始化参数 * * @param {Object} [options] 构造函数传入的参数 * @param {number} option.pageSize 每页显示的个数 * @param {number} option.spacing 每个图片的间隔 * @param {number} option.itemWidth 每项的宽度 * @param {number} option.itemHeight 每项的高度 * @param {Array} option.datasource 所有项的数据数组 * @param {number} option.value 选中的项的id值 * @param {boolean} option.disabled 是否禁用 * @param {boolean} option.emphasizeSelectedItem 是否高亮被选择的 * @protected * @override */ initOptions: function (options) { var properties = { pageSize: 8, spacing: 15, itemWidth: 80, itemHeight: 50, datasource: [], value: null, disabled: false, emphasizeSelectedItem: true }; u.extend(properties, options); this.setProperties(properties); }, /** * 初始化DOM结构 * * @protected */ initStructure: function () { this.main.innerHTML = getMainHtml.call(this); }, /** * 初始化事件交互 * * @protected * @override */ initEvents: function () { // 左右的把手事件绑定 this.helper.addDOMEvent('left-handler', 'click', u.bind(pointerClick, this, -1)); this.helper.addDOMEvent('right-handler', 'click', u.bind(pointerClick, this, 1)); // 列表项切换 this.helper.addDOMEvent('list', 'click', 'li', itemChangeHandler); // 最下面翻页 this.helper.addDOMEvent('toolbar', 'click', 'li', toolbarHandler); }, /** * 重新渲染视图 * 仅当生命周期处于RENDER时,该方法才重新渲染 * * @param {Array=} 变更过的属性的集合 * @override */ repaint: paint.createRepaint( Control.prototype.repaint, { name: ['datasource', 'itemWidth', 'itemHeight'], paint: function (carousel, datasource, itemWidth, itemHeight) { var list = carousel.helper.getPart('list'); var toolbar = carousel.helper.getPart('toolbar'); var pageSize = carousel.pageSize; var spacing = carousel.spacing; list.innerHTML = getItemHtml.call( carousel, datasource, itemWidth, itemHeight, spacing, pageSize ); toolbar.innerHTML = getToolbarHtml.call(carousel, datasource); var wrapWidth = itemWidth * carousel.pageSize + (pageSize - 1) * spacing; var wrapHeight = itemHeight; carousel.wrapWidth = wrapWidth; var wrap = list.parentNode; wrap.style.width = wrapWidth + 'px'; wrap.style.height = wrapHeight + 'px'; } }, { name: 'value', paint: function (carousel, value) { carousel.setValue(value); } } ), /** * 设置选中项 * * @param {number} value 选中项的值 * @public */ setValue: function (value) { if (u.isNull(value) || u.isUndefined(value)) { this.setPage(); return; } this.value = value; this.selectedIndex = -1; u.each(this.datasource, function (item, index) { if (item.id === this.value) { this.selectedIndex = index; return false; } }, this); this.selectedItem = this.getSelectedItem(); if (this.selectedIndex !== -1 && this.emphasizeSelectedItem) { var selector = this.helper.getPart('list'); var $lis = $(selector).children('li'); var selectedClass = this.helper.getPrimaryClassName('selected-item'); $lis.parent().find('.' + selectedClass).removeClass(selectedClass); var $li = $lis.eq(this.selectedIndex); $li.addClass(selectedClass); } var page = getPageByIndex.call(this); this.setPage(page); }, /** * 设置page * * @param {number} page 获取page的序号 * @public */ setPage: function (page) { page = page || 0; page = parseInt(page, 10); var currentPageClass = this.helper.getPrimaryClassName('current-page'); if (this.currentPage === null) { this.currentPage = 0; } if (this.currentPage !== page) { this.currentPage = page; } var $allDom = $(this.helper.getPart('toolbar')).children(); u.each($allDom, function (dom, i) { var $dom = $(dom); $dom.removeClass(currentPageClass); var index = +$dom.attr('index'); if (this.currentPage === index) { $dom.addClass(currentPageClass); } }, this); setPointerStyle.call(this); setCarouseListPosition.call(this); }, /** * 获取选择项的完整数据 * * @return {Object} * @public */ getSelectedItem: function () { return this.datasource[this.selectedIndex]; } } ); /** * 拼接main的dom结构 * * @return {string} html片段 * @inner */ function getMainHtml() { var controlHelper = this.helper; return lib.format( MAIN_TPL, { typeSelector: controlHelper.getPrimaryClassName(), contentId: controlHelper.getId('main'), leftId: controlHelper.getId('left-handler'), listId: controlHelper.getId('list'), rightId: controlHelper.getId('right-handler'), toolbarId: controlHelper.getId('toolbar'), iconLeftArrow: controlHelper.getIconClass(), iconRightArrow: controlHelper.getIconClass() } ); } /** * 拼接内容项的dom结构 * * @param {Array} data 渲染数据 * @param {number} itemWidth 单项的宽 * @param {number} itemHeight 单项的高 * @param {number} spacing 图片间距 * @param {number} pageSize 图片间距 * @return {string} html片段 * @inner */ function getItemHtml(data, itemWidth, itemHeight, spacing, pageSize) { var html = []; u.each(data, function (item, index) { var index1 = index + 1; var str = ''; if (this.onRenderItem) { str = this.onRenderItem(item); } else { str = lib.format( ITEM_TPL, { imgSrc: item.url, width: itemWidth, height: itemHeight, index: index, typeSelector: this.helper.getPrimaryClassName(), itemSelector: this.isDisabled() ? this.helper.getPartClassName('disabled') : '', iconCheck: this.helper.getIconClass(), spacing: (index1 > 0 && index1 % pageSize === 0) ? 0 : spacing } ); } html.push(str); }, this); return html.join(''); } /** * 拼接底部分页条的dom结构 * * @param {Array} data 渲染所需的数据 * @return {string} html片段 * @inner */ function getToolbarHtml(data) { var html = []; var len = data.length; var divided = Math.ceil(len / this.pageSize); this.pageLength = divided; for (var i = 0; i < divided; i++) { var str = lib.format( PAGE_TPL, { index: i, typeSelector: this.helper.getPrimaryClassName() } ); html.push(str); } return html.join(''); } /** * 获取page的序号根据选中项的index * * @return {number} page的序号 * @inner */ function getPageByIndex() { if (this.selectedIndex === -1) { return 0; } return Math.floor(this.selectedIndex / this.pageSize); } /** * 设置左右箭头的样式 * * @inner */ function setPointerStyle() { var controlHelper = this.helper; var disableClass = controlHelper.getPartClassName('pointer-disable'); var $left = $(controlHelper.getPart('left-handler')); var $right = $(controlHelper.getPart('right-handler')); var currentPage = this.currentPage; $left.removeClass(disableClass); $right.removeClass(disableClass); if (currentPage === 0) { $left.addClass(disableClass); } if (currentPage === this.pageLength - 1) { $right.addClass(disableClass); } } /** * 设置翻页的滚动位置 * * @inner */ function setCarouseListPosition() { var pageOffset = -this.wrapWidth; var left = (pageOffset * this.currentPage) + 'px'; this.helper.getPart('list').style.left = left; } /** * 左右箭头点击后的处理函数 * * @param {number} n 区别方向 -1=left 1=right * @inner */ function pointerClick(n) { var nextPage = this.currentPage + n; if (nextPage >= this.pageLength || nextPage < 0) { } else { this.setPage(nextPage); } } /** * 单个选项处理handler * * @param {number} index 选项的序号 * @param {HTMLElement} $el dom对象 * @inner */ function itemClick(index, $el) { if (this.selectedIndex === index) { return; } if (this.emphasizeSelectedItem) { var $selector = $(this.helper.getPart('list')); var selectedClass = this.helper.getPrimaryClassName('selected-item'); $selector.children('.' + selectedClass).removeClass(selectedClass); $el.addClass(selectedClass); } this.selectedIndex = index; this.selectedItem = this.getSelectedItem(); this.value = this.selectedItem.id; /** * @event change * * 值发生变化时触发 * * `Carousel`控件的值变化是以{@link Carousel#selectedIndex}属性为基准 */ this.fire('change'); } /** * 翻页按钮点击处理函数 * * @param {number} nextPage 目标页的序号 * @inner */ function pageClick(nextPage) { if (this.currentPage !== nextPage) { this.setPage(nextPage); } } /** * 选择项切换处理函数,采用事件委托的方式 * * @param {Event} e 事件对象 * @inner */ function itemChangeHandler(e) { var $target = $(e.currentTarget); itemClick.call(this, +$target.attr('index'), $target); } /** * 底部工具条处理函数,采用事件委托的方式 * * @param {Event} e 事件对象 * @inner */ function toolbarHandler(e) { var $target = $(e.currentTarget); pageClick.call(this, +$target.attr('index')); } esui.register(Carousel); return Carousel; });
function (require) { var esui = require('esui'); var lib = require('esui/lib'); var util = require('../helper/util'); var u = require('underscore'); var eoo = require('eoo'); var painters = require('esui/painters'); var RichSelector = require('./RichSelector'); /** * 控件类 * * @class ui.TableRichSelector * @extends ui.RichSelector */ var TableRichSelector = eoo.create( RichSelector, { /** * 控件类型,始终为`"TableRichSelector"` * * @type {string} * @override */ type: 'TableRichSelector', /** * @override */ styleType: 'RichSelector', /** * @override */ initOptions: function (options) { var properties = { // 事件是否只触发在图标上 firedOnIcon: false, // 数据源,全集数据 datasource: [], // 已选的数据 selectedData: [], // 字段,含义与Table相同,searchScope表示这个字段对搜索关键词是全击中还是部分击中 // caseSensitive表示大小写敏感,默认不敏感 fields: [ { field: 'name', title: '名称', content: 'name', searchScope: 'partial', caseSensitive: false, isDefaultSearchField: true } ], // 是否展示表格属性栏 hasRowHead: true }; u.extend(properties, options); this.$super([properties]); }, /** * @override */ initStructure: function () { this.$super(arguments); lib.addClass( this.main, this.helper.getPrefixClass('tablerichselector') ); }, /** * 重新渲染视图 * 仅当生命周期处于RENDER时,该方法才重新渲染 * * @param {Array=} 变更过的属性的集合 * @override */ repaint: painters.createRepaint( RichSelector.prototype.repaint, { name: ['datasource', 'selectedData', 'disabledData', 'fields'], paint: function (control, datasource, selectedData, disabledData, fields) { control.refresh(); control.fire('change'); } } ), /** * 构建List可以使用的数据结构 * 把用户传入数据制作成一个副本allData: * —— allData * [{id: 1, name: xxx}, {id: 2, name: yyy}...] * * 把allData转换成索引表,方便查找,并附加状态信息 * —— indexData * {1: {index: 1, isSelected: true}, 2: {index: 2, isDisabled: true}} * * @override */ adaptData: function () { // 适配id/value的值 u.each( this.datasource, function (item) { item.id = item.id || item.value; } ); this.allData = lib.deepClone(this.datasource); // 先构建indexData,把数据源里的选择状态清除 var indexData = {}; u.each(this.allData, function (item, index) { indexData[item.id] = {index: index, isSelected: item.isSelected}; }); // 把选择状态merge进indexData的数据项中 var selectedData = this.selectedData || []; // 单选模式 if (!this.multi) { // 如果不是数组,这个值就是id if (!u.isArray(selectedData)) { this.currentSelectedId = selectedData; selectedData = [{id: selectedData}]; } // 如果是数组,保存第一个值为当前选值 else if (selectedData.length) { this.currentSelectedId = selectedData[0].id || selectedData[0]; } } u.each(selectedData, function (item, index) { var id = item.id !== undefined ? item.id : item; // 有可能出现已选的数据在备选中已经被删除的情况 if (indexData[id] !== undefined) { indexData[id].isSelected = true; } }); var disabledData = this.disabledData || []; u.each(disabledData, function (item, index) { var id = item.id !== undefined ? item.id : item; if (indexData[id] !== undefined) { indexData[id].isDisabled = true; } }); this.indexData = indexData; // 处理fields,把fields也保存到一个索引中 this.fieldsIndex = {}; this.defaultSearchFields = []; u.each( this.fields, function (field) { this.fieldsIndex[field.field] = field; if (field.isDefaultSearchField) { this.defaultSearchFields.push(field.field); } }, this ); return { allData: this.allData, indexData: this.indexData }; }, /** * 更新备选区 * * @override */ refreshContent: function () { var data = this.isQuery() ? this.queriedData : this.allData; if (!data || data.length === 0) { this.addState('empty'); } else { this.removeState('empty'); } // 开始构建 var htmlArray = []; if (this.hasRowHead) { htmlArray.push(this.createTableHead()); } htmlArray.push(this.createTableContent(data)); var queryList = this.getQueryList(); // 选择状态当使用手动触发时,panel的content属性并不会同步更改, // 如果此时新的content恰好与初始的content相同,则panel不会重置 // 因此现执行一下置空 queryList.setContent(''); queryList.setContent(htmlArray.join('')); }, /** * 创建表头 * * @public * @return {string} 表头html */ createTableHead: function () { var tableClass = this.helper.getPartClassName('head-table'); var tableId = this.helper.getId('head-table'); var tpl = ['<table border=0 class="' + tableClass + '" id="' + tableId + '"><tr>']; var colmNum = this.fields.length; // 绘制表头th for (var i = 0; i < colmNum; i++) { var field = this.fields[i]; tpl.push('' + '<th class="th' + i + '"' + ' style="width:' + field.width + 'px;">' + field.title || '' + '</th>' ); } // 最后一列用来装箭头 tpl.push('<th></th>'); tpl.push('</tr></table>'); return tpl.join(' '); }, rowTpl: '<tr id="${rowId}" class="${rowClass}" index="${index}">${content}</tr>', /** * 创建表格体 * * @param {Object} data 绘制的内容 * @return {string} * @ignore */ createTableContent: function (data) { var indexData = this.indexData; var helper = this.helper; var tableClasses = helper.getPartClassName('content-table'); var tableId = helper.getId('content-table'); var tpl = ['<table border=0 class="' + tableClasses + '" id="' + tableId + '">']; var baseRowClasses = helper.getPartClassName('row'); var selectedRowClasses = helper.getPartClassName('row-selected'); var disabledRowClasses = helper.getPartClassName('row-disabled'); // 绘制内容 u.each( data, function (item, index) { var rowClasses = [baseRowClasses]; var indexItem = indexData[item.id]; if (indexItem.isSelected) { rowClasses.push(selectedRowClasses); } if (indexItem.isDisabled) { rowClasses.push(disabledRowClasses); } tpl.push( lib.format( this.rowTpl, { rowId: this.helper.getId('row-' + item.id), rowClass: rowClasses.join(' '), index: indexItem.index, content: createRow(this, item, index) } ) ); }, this ); tpl.push('</table>'); return tpl.join(' '); }, /** * 点击行为分发器 * @param {Event} e 事件对象 * @ignore */ eventDispatcher: function (e) { var tar = e.target; var helper = this.helper; var rowClasses = helper.getPartClassName('row'); var actionClasses = helper.getPartClassName('row-action-icon'); while (tar && tar !== document.body) { var rowDOM; // 有图标的事件触发在图标上 if (this.hasIcon && this.fireOnIcon && lib.hasClass(tar, actionClasses)) { rowDOM = tar.parentNode; } else { if (lib.hasClass(tar, rowClasses)) { rowDOM = tar; } } if (rowDOM) { this.operateRow(rowDOM); return; } tar = tar.parentNode; } }, // 可重写 operateRow: function (row) { var disabledClasses = this.helper.getPartClassName('row-disabled'); if (lib.hasClass(row, disabledClasses)) { return; } var index = parseInt(row.getAttribute('index'), 10); var item = this.allData[index]; if (!item) { return; } if (this.mode === 'add') { actionForAdd(this, row, item); } else if (this.mode === 'delete') { actionForDelete(this, row, item); } else if (this.mode === 'load') { actionForLoad(this, row, item); } }, /** * 选择全部 * 如果当前处于搜索状态,那么只把搜索结果中未选择的选过去 * @public */ selectAll: function () { var data = this.isQuery() ? this.queriedData : this.allData; var control = this; u.each(data, function (item) { selectItem(control, item.id, true); }); this.fire('add'); this.fire('change'); }, /** * 批量更新选择状态, * 用于外部调用,因此不触发事件 * * @param {Array} items 需要更新的对象集合或id集合 * @param {boolean} toBeSelected 要选择还是取消选择 * @override */ selectItems: function (items, toBeSelected) { var indexData = this.indexData; var control = this; u.each(items, function (item) { var id = item.id !== undefined ? item.id : item; var itemIndex = indexData[id]; if (itemIndex !== null && itemIndex !== undefined) { // 更新状态,但不触发事件 selectItem(control, id, toBeSelected); } }); }, /** * 删除全部 * * @FIXME 删除全部要区分搜索和非搜索状态么 * @override */ deleteAll: function () { var items = u.clone(this.datasource); this.set('datasource', []); this.fire('delete', {items: items}); this.fire('change'); }, /** * 搜索含有关键字的结果,默认以name为目标搜索 * * 可重写 * * @param {Array} filters 过滤参数 * @public */ queryItem: function (filters) { // 查找过滤 [{ keys: ['xxx', 'yyy'], value: 'xxx' }] filters = filters || []; // 判断数据的某个field是命中 function checkHitByFilterItem(field, expectValue, data) { var hit = false; // 只有字符串类去空格 if (typeof expectValue === 'string') { expectValue = lib.trim(expectValue); } var config = { isPartial: this.fieldsIndex[field].searchScope === 'partial', caseSensitive: this.fieldsIndex[field].caseSensitive }; if (util.compare(data[field], expectValue, config)) { hit = true; } return hit; } // 判断行数据是否符合过滤要求 function checkRowHit(data, index) { return !u.any( filters, function (filter) { var searchFields = []; // keys未定义,则默认选择通过field指定的并集 if (filter.keys === undefined) { searchFields = this.defaultSearchFields; } else { searchFields = filter.keys; } return !u.any( searchFields, function (searchField) { // 命中一个就算命中 return checkHitByFilterItem.call(this, searchField, filter.value, data); }, this ); }, this ); } this.queriedData = u.filter( this.allData, checkRowHit, this ); this.afterQueryHandler(); }, afterQueryHandler: function () { // 更新状态 this.addState('queried'); this.refreshContent(); }, /** * 清空搜索的结果 * */ clearData: function () { // 清空数据 this.queriedData = []; }, /** * add(load)型:或许当前选择状态的数据 * delete型:获取全部数据 * * @return {Object} * @public */ getSelectedItems: function () { var rawData = this.datasource; var indexData = this.indexData; var mode = this.mode; if (mode === 'delete') { return this.allData; } var selectedData = u.filter(rawData, function (item, index) { return indexData[item.id].isSelected; }); return selectedData; }, /** * @override */ getSelectedItemsFullStructure: function () { return this.getSelectedItems(); }, /** * 获取当前状态的显示个数 * * @return {number} * @override */ getCurrentStateItemsCount: function () { var data = this.isQuery() ? this.queriedData : this.allData; data = data || []; return data.length; } } ); /** * 创建Table的每行 * * @param {ui.TableForSelector} control 类实例 * @param {Object} item 每行的数据 * @param {number} index 行索引 * @param {HTMLElement} tr 容器节点 * @return {string} * @ignore */ function createRow(control, item, index, tr) { var fields = control.fields; var html = []; var fieldClasses = control.helper.getPartClassName('row-field'); var cursor = 0; u.each(fields, function (field, i) { var content = field.content; var innerHTML = ('function' === typeof content ? content.call(control, item, index, i) : u.escape(item[content])); var textContent = field.textContent ? field.textContent.call(control, item, index, i) : innerHTML; // IE不支持tr.innerHTML,所以这里要使用insertCell if (tr) { var td = tr.insertCell(i); td.style.width = field.width + 'px'; td.title = textContent; td.innerHTML = innerHTML; } else { var contentHtml = '' + '<td class="' + fieldClasses + '" title="' + textContent + '" style="width:' + field.width + 'px;">' + innerHTML + '</td>'; html.push(contentHtml); } cursor++; }); // 最后一列添加箭头 var arrowClasses = control.helper.getPartClassName('row-action-icon') + ' ' + control.helper.getIconClass(); var arrowHTML = '<span class="' + arrowClasses + '"></span>'; if (tr) { var td = tr.insertCell(cursor); td.innerHTML = arrowHTML; } else { html.push('<td>' + arrowHTML + '</td>'); return html.join(' '); } } function actionForAdd(control, row, item) { var selectedClasses = control.helper.getPartClassName('row-selected'); var fire = false; var eventArgs = {item: item, status: 1}; // 点击已选中的,在单选模式下,执行取消选择 if (lib.hasClass(row, selectedClasses)) { if (control.allowUnselectNode) { selectItem(control, item.id, false); fire = true; eventArgs.status = -1; } } else { selectItem(control, item.id, true); fire = true; } if (fire) { // 需要增加上一个参数,因为有的时候需要了解当前操作的对象是什么 control.fire('add', eventArgs); control.fire('change'); } } /** * 选择或取消选择 * 如果控件是单选的,则将自己置灰且将其他节点恢复可选 * 如果控件是多选的,则仅将自己置灰 * * @param {ui.TableRichSelector} control 类实例 * @param {Object} id 结点对象id * @param {boolean} toBeSelected 置为选择还是取消选择 * * @ignore */ function selectItem(control, id, toBeSelected) { // 如果是单选,需要将其他的已选项置为未选 if (!control.multi) { // 移除原有选项 unselectCurrent(control); // 赋予新值 control.currentSelectedId = toBeSelected ? id : null; } updateSingleItemStatus(control, id, toBeSelected); } // 撤销选择当前项 function unselectCurrent(control) { var curId = control.currentSelectedId; // 撤销当前选中项 updateSingleItemStatus(control, curId, false); control.currentSelectedId = null; } /** * 更新单个结点状态 * * @param {ui.TableRichSelector} control 类实例 * @param {string} id 结点数据id * @param {boolean} toBeSelected 置为选择还是取消选择 * * @ignore */ function updateSingleItemStatus(control, id, toBeSelected) { var indexItem = control.indexData[id]; if (!indexItem) { return; } indexItem.isSelected = toBeSelected; var itemDOM = control.helper.getPart('row-' + id); var changeClass = toBeSelected ? lib.addClass : lib.removeClass; changeClass( itemDOM, control.helper.getPartClassName('row-selected') ); } /** * 下面的方法专属delete型table * @param {Object} control table * @param {DOMElement} row 行DOM * @param {Object} item 要删除的item */ function actionForDelete(control, row, item) { deleteItem(control, item.id); // 外部需要知道什么数据被删除了 control.fire('delete', {items: [item]}); control.fire('change'); } /** * 删除选择的节点 * * @param {ui.TableRichSelector} control 类实例 * @param {number} id 结点数据id * * @ignore */ function deleteItem(control, id) { // 完整数据 var indexData = control.indexData; var index = indexData[id].index; var newData = [].slice.call(control.datasource, 0); newData.splice(index, 1); control.set('datasource', newData); } function actionForLoad(control, row, item) { var selectedClasses = control.helper.getPartClassName('row-selected'); // 点击未选中的,执行 if (!lib.hasClass(row, selectedClasses)) { selectItem(control, item.id, true); control.fire('load', {item: item}); control.fire('change'); } } esui.register(TableRichSelector); return TableRichSelector; }