@wiajs/ui
Version:
wia app ui packages
1,356 lines (1,353 loc) • 122 kB
JavaScript
/** @jsxImportSource @wiajs/core */ /**
* 在线编辑表格
*/ import { jsx as _jsx, jsxs as _jsxs } from "@wiajs/core/jsx-runtime";
import { Event } from '@wiajs/core';
import { Page } from '@wiajs/core';
import DataTable from '../dataTable';
import { fillAttach, cancelDel, getDel } from './attach';
import * as tool from './tool';
import { log as Log } from '@wiajs/util';
const log = Log({
m: 'editTable'
}) // 创建日志实例
;
/**
* @typedef {import('jquery')} $
* @typedef {JQuery} Dom
*/ /** @typedef {object} Opts
* @prop {Dom} tb - $table
* @prop {string[]} [editTag] - 可编辑元素标签
* @prop {boolean} [edit] - 编辑模式
* @prop {boolean} [add] - 新增模式
* @prop {boolean} [kv] - key value
* @prop {number} [col] - 最大列数
* @prop {number[]} [colWidth] - 列宽
* @prop {number} [viewid] - 数据卡id
* @prop {*} [upload] - 上传接口
*/ /** @typedef {object} Opt
* @prop {Dom} tb - $table
* @prop {string[]} editTag
* @prop {boolean} edit
* @prop {boolean} add
* @prop {boolean} kv - key value
* @prop {number} labelWidth - label 宽度 百分比
* @prop {number} col - 最大列数
* @prop {number[]} [colWidth] - 列宽
* @prop {number} [colRatio] - 列比
* @prop {number} [viewid] - 数据卡id
* @prop {*} [upload] - 上传接口
* @prop {*} [prjid] - 项目id
* @prop {*} [getSelAll] - 公司列表接口
* @prop {*} [saveEmbTb] - 保存表格接口
*
*/ /** @type {Opt} */ const def = {
tb: null,
editTag: [
'input',
'textarea'
],
edit: false,
add: false,
kv: false,
labelWidth: 10,
col: 8,
// colWidth: [0.1, 0.15, 0.1, 0.15, 0.1, 0.15, 0.1, 0.15],
colRatio: 1
};
/**
* @enum {number} 数据类型-页面呈现方式
*/ const DataType = {
null: 0,
text: 1,
texts: 2,
number: 3,
date: 4,
bool: 5,
select: 6,
radio: 7,
checkbox: 8,
chip: 9,
button: 10,
img: 11,
file: 12,
path: 13,
url: 14,
email: 15,
tel: 16,
password: 17,
time: 18,
datetime: 19,
month: 20,
week: 21,
color: 22,
attach: 23,
table: 24,
view: 25,
page: 26,
search: 27,
html: 28,
json: 29
};
/**
* @enum {string} 数据类型-页面呈现方式
*/ const DataTypes = {
null: 'null',
text: 'text',
texts: 'texts',
number: 'number',
date: 'date',
bool: 'bool',
select: 'select',
radio: 'radio',
checkbox: 'checkbox',
chip: 'chip',
button: 'button',
img: 'img',
file: 'file',
path: 'path',
url: 'url',
email: 'email',
tel: 'tel',
password: 'password',
time: 'time',
datetime: 'datetime',
month: 'month',
week: 'week',
color: 'color',
attach: 'attach',
table: 'table',
view: 'view',
page: 'page',
search: 'search',
html: 'html',
json: 'json'
};
/**
* EditTable
*/ let EditTable = class EditTable extends Event {
/**
* 构造函数
* @param {Page} page Page 实例
* @param {Opts} opts
*/ constructor(page, opts){
super(opts, [
page
]), /** @type {*} */ this.editTx = null // 当前被编辑的目标对象
, /** @type {boolean} */ this._keyDown = false // 记录键盘被按下的状态,当有键盘按下时其值为true
, /** @type {*} */ this._selRow = null // 最近的选择行
, /** @type {*} */ this._editCell = null // 当前编辑对象
, /** @type {*} */ this.editRow = null // 最后编辑行
, /** @type {number} */ this.editCursorPos = 0 // 最后编辑光标位置
, /** @type {number} */ this.rowNum = 0 // 表行数
, /** @type {number} */ this.dataid = 0 // 当前数据索引,用于render row
;
const _ = this;
const opt = {
...def,
...opts
};
_.opt = opt;
_.tb = opt.tb;
_.page = page;
// 4 改为 8,兼容旧模式
if (!opt.colWidth) {
opt.colRatio = 2;
opt.col = opt.col * 2;
if (opt.col === 8) opt.colWidth = [
0.1,
0.15,
0.1,
0.15,
0.1,
0.15,
0.1,
0.15
];
}
_.init();
if (opt.edit) _.edit();
else _.view();
_.bind();
// const txs = $(tr).find('input.etCellView')
// const spans = $(tr).find('span.etLabel')
// spans.html('hello')
// for (const tx of txs.get()) {
// const $tx = $(tx)
// $tx.click(ev => editSpec(tx))
// $tx.focus(ev => editSpec(tx))
// $tx.blur(ev => viewSpec(tx))
// $tx.upper('td').addClass('border-bot')
// }
}
static hi(msg) {
alert(msg);
}
/**
* kv模式,构建空表头、表体
*/ init() {
const _ = this;
const { opt, tb: tb1 } = _;
const { colWidth, edit } = opt;
try {
// 列宽控制
const cg = tb1.find('colgroup');
if (!cg.dom && colWidth?.length) {
tb1.prepend(/*#__PURE__*/ _jsx("colgroup", {
children: colWidth.map((v)=>{
let width;
if (v < 1) width = `${v * 100}%`;
else width = `${v}px`;
return /*#__PURE__*/ _jsx("col", {
style: `width: ${width}`
});
})
}));
}
// 构造空body
let body = tb1.find('tbody');
if (!body.dom) {
if (edit) tb1.append(/*#__PURE__*/ _jsx("tbody", {
class: "etEdit"
}));
else tb1.append(/*#__PURE__*/ _jsx("tbody", {
class: "etView"
}));
}
body = tb1.find('tbody');
// 构造空表头
const th = tb1.find('thead');
if (!th.dom) {
body.before(/*#__PURE__*/ _jsx("thead", {
children: /*#__PURE__*/ _jsx("tr", {
style: "display: none",
class: "etRowOdd"
})
}));
}
} catch (e) {
log.err(e, 'init');
}
}
bind() {
const _ = this;
const { opt } = _;
// 表格点击事件
// 编辑元素(input) 不能 focus,不能 onblur?原因:pointer-events: none
_.tb.click(async (ev)=>{
if (!opt.edit) return;
// 点击 input、select 则跳过
if ([
'SELECT',
'INPUT'
].includes(ev.target.tagName)) return;
const $ev = $(ev);
const td = $ev.upper('td');
let span = $ev.upper('span');
span = td.find('span');
if (span.eq(0).css('display') === 'none') {
// debugger
return;
}
const idx = td?.data('idx') // 数据索引
;
const idv = td?.data('idv') // 数据中的value索引,多值数组模式下
;
const value = td?.attr('data-value') // 数据原值 data() 会自动转换 json 字符串
;
const r = _.data?.[idx];
if (r) {
if (r.read) return; // 只读
// 方法2.2(更可靠)
// document.body.setAttribute('tabindex', '-1')
// document.body.focus()
// document.body.removeAttribute('tabindex')
let type = r.type ?? DataType.text // 默认单行字符串
;
// 多值
if (idv && idv >= 0 && Array.isArray(type) && type[idv]) {
type = type[idv];
}
const inputType = _.getInputType(type);
if (inputType) {
const span = td.find('span');
span.hide();
let tx = td.find('input');
if (!tx.dom) {
tx = document.createElement('input');
tx.name = r.field;
tx.type = inputType;
td.append(tx);
tx = $(tx);
tx.addClass('dy-input');
tx.blur((ev)=>{
// _.viewCell()
const val = tx.val();
span.eq(0).html(val);
// 比较值是否被修改
if (`${val}` === `${value}`) {
tx.hide();
span.show();
}
});
}
tx.val(span.eq(0).html());
tx.show();
tx.focus();
// 自动聚焦到输入框
// setTimeout(() => {
// tx.focus()
// }, 50)
} else if (type === DataType.texts || type === DataTypes.texts) {
const span = td.find('span');
if (!span.hasClass('edit')) {
let tx = td.find('input');
if (!tx.dom) {
tx = document.createElement('input');
tx.name = r.field;
tx.value = span.html();
tx.hidden = true;
td.append(tx);
}
span.dom.tabIndex = '-1';
// span 可编辑
// span.focus(ev => span.addClass('edit'))
span.addClass('edit');
span.blur((ev)=>{
// _.viewCell()
const val = span.html();
tx.value = val;
if (`${val}` === `${value}`) {
span.removeClass('edit') // span 可编辑
;
}
});
span.focus();
// span.dom.addEventListener('focusout', ev => {
// tx.value = span.html()
// span.removeClass('edit') // span 可编辑
// })
}
} else if (type === DataType.select || type === DataTypes.select) {
const span = td.find('span');
span.hide();
const val = span.html();
let key;
let sel = td.find('select');
if (!sel.dom) {
sel = document.createElement('select');
sel.name = r.field;
td.append(sel);
sel = $(sel);
sel.addClass('dy-select dy-select-primary');
sel.click((ev)=>ev.stopPropagation()) // 阻止事件冒泡 tb 无感知?
;
// tx.addClass('dy-input')
// tx.val(span.html())
// tx.change(ev => {
sel.blur((ev)=>{
// _.viewCell()
let val;
const { option } = r;
if (Array.isArray(option)) val = sel.val();
else if (typeof option === 'object') {
key = sel.val();
val = option[key];
}
span.html(val);
if (`${val}` === `${value}`) {
sel.hide();
span.show();
}
});
sel.focus((ev)=>{
// 关联查询
_.fillOption(idx, td, sel);
});
}
// 选项
await _.fillOption(idx, td, sel);
sel.show();
sel.focus();
setTimeout(()=>{
// 创建并触发鼠标事件来展开下拉框
const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window
});
sel.dom.dispatchEvent(event);
}, 100);
} else if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) {
const span = td.find('span');
if (span.css('display') !== 'none') {
span.hide();
let tx = td.find('.ac-input');
let dvAc = td.find('.autocomplete');
if (!tx.dom) {
const { option, source, field } = r;
let { placeholder } = r;
placeholder = placeholder ?? '请输入';
td.append(/*#__PURE__*/ _jsx("div", {
class: "autocomplete",
children: /*#__PURE__*/ _jsx("div", {
class: "ac-wrapper",
children: /*#__PURE__*/ _jsx("input", {
type: "text",
name: field,
class: "ac-input",
placeholder: placeholder,
autocomplete: "off"
})
})
}));
tx = td.find('.ac-input');
dvAc = td.find('.autocomplete');
// tx.addClass('dy-input')
const ac = new _.Autocomplete(_.page, {
el: dvAc,
data: option,
refEl: [
span.dom
],
source
});
tx.blur((ev)=>{
// 选择赋值在 blur 后
setTimeout(()=>{
const val = tx.val();
if (`${val}` === `${value}`) {
dvAc.hide();
span.eq(0).html(val);
span.show();
}
}, 200);
});
}
// tx.val(span.eq(0).html())
dvAc.show();
const input = tx.dom;
const { ac } = dvAc.dom;
tx.focus() // 自动触发下拉
;
// 触发datalist下拉显示(需要特殊处理)
// setTimeout(() => {
// input.focus()
// ac.showAllList()
// }, 0)
}
} else if (type === DataType.bool || type === DataTypes.bool) {
const span = td.find('span');
span.hide();
let val = span.html();
let key;
let tx = td.find('select');
if (!tx.dom) {
tx = document.createElement('select');
tx.name = r.field;
td.append(tx);
tx = $(tx);
tx.addClass('dy-select dy-select-primary');
const option = {
true: '是',
false: '否'
};
// 添加选项
let htm = [];
for (const k of Object.keys(option)){
const v = option[k];
if (v === val) {
key = k;
htm.push(/*#__PURE__*/ _jsx("option", {
selected: true,
value: k,
children: v
}));
} else htm.push(/*#__PURE__*/ _jsx("option", {
value: k,
children: v
}));
}
tx.html(htm.join(''));
if (key) tx.val(key);
else tx.val(val);
tx.click((ev)=>ev.stopPropagation()) // 阻止事件冒泡
;
// tx.addClass('dy-input')
// tx.val(span.html())
tx.blur((ev)=>{
// _.viewCell()
key = tx.val();
val = option[key];
span.html(val);
if (`${val}` === `${value}`) {
tx.hide();
span.show();
}
});
}
tx.show();
tx.focus();
setTimeout(()=>{
// 创建并触发鼠标事件来展开下拉框
// const event = new MouseEvent('mousedown')
const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window
});
tx.dom.dispatchEvent(event);
}, 100);
// tx.click()
} else if (type === DataType.url || type === DataTypes.url) {
const span = td.find('span');
span.hide();
let tx = td.find('input');
if (!tx.dom) {
tx = document.createElement('input');
tx.name = r.field;
tx.type = 'url';
td.append(tx);
tx = $(tx);
tx.addClass('dy-input');
tx.blur((ev)=>{
// _.viewCell()
const val = tx.val();
span.eq(0).find('a').attr('href', val);
// 比较值是否被修改
if (`${val}` === `${value}`) {
tx.hide();
span.show();
}
});
}
// urlChange
tx.val(span.eq(0).find('a').attr('href'));
tx.show();
tx.focus();
}
}
// for (const tag of _.opt.editTag) {
// const tx = $ev.upper(tag)
// if (tx.dom) {
// _.editCell(tx.dom)
// break
// }
// }
});
}
unbind() {
const _ = this;
}
/**
* 填充select option
* @param {number} idx
* @param {Dom} td
* @param {Dom} sel
* @returns
*/ async fillOption(idx, td, sel) {
const _ = this;
try {
const { data } = _;
const r = data[idx];
const { source } = r;
let { name } = source?.param || {};
let { option } = r;
// 引用字段
let refField;
if (name?.includes('${')) {
const lastRefField = td.data('lastRefField') // 保存关联
;
const match = name.match(/\$\{([^}]+)\}/);
const ref = match?.[1];
const i = _.getDataIdx({
field: ref
});
if (i) {
// 关联节点
const n = _.tb.findNode(`[data-idx="${i}"]`);
const v = n.findNode('span').html();
if (v && v !== lastRefField) {
refField = v;
td.data('lastRefField', v) // 保存关联
;
// 替换 'city:${province}'
name = name.replace(`\${${ref}}`, v);
}
}
}
// 关联字段变化或无选项,动态获取
if (refField || source && !option) {
sel.html('');
// 数据字典
// 默认 name = field
if (!name) name = r.field;
// source.param.name = name
option = await getOption(source, name);
log({
option
}, 'getOption');
}
if (option) {
r.option = option;
let key;
const span = td.find('span');
const val = span.html();
// 添加选项
let htm = [];
if (Array.isArray(option)) htm = option.map((v)=>{
let rt;
if (v === val) rt = /*#__PURE__*/ _jsx("option", {
selected: true,
value: v,
children: v
});
else rt = /*#__PURE__*/ _jsx("option", {
value: v,
children: v
});
return rt;
});
else if (typeof option === 'object') {
if (!val) {
htm.push(/*#__PURE__*/ _jsx("option", {
selected: true,
value: "",
children: "请选择"
}));
}
for (const k of Object.keys(option)){
const v = option[k];
if (v === val) {
key = k;
htm.push(/*#__PURE__*/ _jsx("option", {
selected: true,
value: k,
children: v
}));
} else htm.push(/*#__PURE__*/ _jsx("option", {
value: k,
children: v
}));
}
}
sel.html(htm.join(''));
if (key) sel.val(key);
else sel.val(val);
}
} catch (e) {
log.err(e, 'fillOption');
}
}
/**
* 获得数据索引
* @param {*} opts
* @returns {number}
*/ getDataIdx(opts) {
let R;
const _ = this;
try {
const { field } = opts;
if (field) {
const idx = _.data.findIndex((v)=>v.field === field);
if (idx >= 0) R = idx;
}
} catch (e) {
log.err(e, 'getData');
}
return R;
}
/**
* 加载插件
* @param {*} cls
* @param {*} [opts]
*/ use(cls, opts) {
const _ = this;
try {
const { opt } = _;
_[cls.name] = cls;
if (cls.name === 'Uploader' && opts?.upload) opt.upload = opts.upload;
else if (cls.name === 'Tabulate' && (opts?.getSelAll || opts?.saveEmbTb)) {
opt.getSelAll = opts.getSelAll;
opt.saveEmbTb = opts.saveEmbTb;
opt.prjid = opts.prjid;
opt.upload = opts.upload;
}
} catch (e) {
log.err(e, 'use');
}
}
/**
* input type
* @param {DataType|DataTypes} type
*/ getInputType(type) {
let R;
switch(type){
case DataTypes.text:
case DataType.text:
R = 'text';
break;
case DataTypes.number:
case DataType.number:
R = 'text';
break;
case DataTypes.date:
case DataType.date:
R = 'date';
break;
// urlChange
// case DataTypes.url:
// case DataType.url:
// R = 'url'
// break
case DataTypes.email:
case DataType.email:
R = 'email';
break;
case DataTypes.tel:
case DataType.tel:
R = 'tel';
break;
case DataTypes.password:
case DataType.password:
R = 'password';
break;
case DataTypes.time:
case DataType.time:
R = 'time';
break;
case DataTypes.datetime:
case DataType.datetime:
R = 'datetime-local';
break;
case DataTypes.month:
case DataType.month:
R = 'month';
break;
case DataTypes.week:
case DataType.week:
R = 'week';
break;
case DataTypes.color:
case DataType.color:
R = 'color';
break;
}
return R;
}
/*
// 异步存储
set(key, data, expires) {
_storage.save({
key: key, // Note: Do not use underscore("_") in key!
data: data,
expires: expires
});
}
// Promise同步方法
getStore(key) {
// load
_storage.load({
key: key,
autoSync: true,
syncInBackground: true
}).then(data => {
return data;
}).catch(err => {
console.warn(err);
return null;
})
}
// async 的写法
async get(key) {
try {
let data = await storage.load({
key: key
});
return data;
}
catch (err) {
console.warn(err);
return null;
}
}
*/ // Chrome、Safari 阻止浏览器的默认事件,实现 全选
mouseupCell(evt) {
const ev = evt || window.event;
ev.preventDefault();
}
addHandler() {
const data = this.tabulate.getData();
this.tabulate.addRow();
}
saveTable() {
this.tabulate.saveTable();
}
/**
* 初始化表格编辑器
*/ async editModeTable() {
const _ = this;
const { opt } = _;
//! 应该根据field 创建,支持多个内嵌表格编辑
//! 需判断是否已创建,避免重复创建
//! 应该根据字段类型(内嵌表)创建,而不是在编辑模式没有内嵌表也创建
if (_.hasTable && !_.tabulate && _.Tabulate) {
_.tabulate = new _.Tabulate({
containerName: 't-table',
addButtonName: 'add-button',
targetBox: _.tb.tag('tbody')[0].querySelectorAll('.data-table'),
baseTableInfo: _.baseTableInfo,
getSelAll: opt.getSelAll,
saveEmbTb: opt.saveEmbTb,
viewid: opt.viewid,
prjid: opt.prjid,
upload: opt.upload
});
} else {
const tTableDivs = new Set();
const dataTables = _.tb.tag('tbody')[0].querySelectorAll('.data-table');
const addButton = document.querySelector(`[name="add-button"]`);
dataTables.forEach((table)=>{
table.style.display = 'none';
// 获取当前 .data-table 的父节点(兄弟元素的共同容器)
const parent = table.parentNode;
// 在父节点中查找 name="t-table" 的 div
const tTableDiv = parent.querySelector('div[name="t-table"]');
// 找到后添加到集合(去重,避免重复元素)
if (tTableDiv) {
tTableDivs.add(tTableDiv);
} else {
_.tabulate.destroyTabulateInstance(_.tabulate);
_.tabulate = new _.Tabulate({
containerName: 't-table',
addButtonName: 'add-button',
targetBox: _.tb.tag('tbody')[0].querySelectorAll('.data-table'),
baseTableInfo: _.baseTableInfo,
getSelAll: opt.getSelAll,
saveEmbTb: opt.saveEmbTb,
viewid: opt.viewid,
prjid: opt.prjid,
upload: opt.upload
});
}
});
addButton.style.display = 'block';
// 将所有找到的 t-table 元素 display 设为 block
tTableDivs.forEach((div)=>{
div.style.display = 'block';
});
}
}
/**
* 编辑模式
*/ edit() {
const _ = this;
_.opt.edit = true;
_.tb.tag('tbody').addClass('etEdit').removeClass('etView');
_.editModeTable();
_.tb.find('._choose').show();
_.bind();
}
/**
* 浏览模式,禁止编辑
* 数据未保存到data,可取消
*/ view() {
const _ = this;
try {
_.opt.edit = false;
_.tb.tag('tbody').addClass('etView').removeClass('etEdit');
_.unbind();
_.tabulate?.togglePreview();
_.tb.find('._choose').hide();
if (_.data) {
const tds = _.tb.find('td[data-idx]');
for (const td of tds.get()){
try {
const $td = $(td);
const idx = $td.data('idx') // 数据索引
;
const d = _.data[idx];
const { type, option } = d || {};
if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) {
const dvAc = $td.find('.autocomplete');
dvAc?.hide();
const span = $td.find('span');
span.show();
} else if (type !== DataType.checkbox && type !== DataTypes.checkbox && type !== DataType.radio && type !== DataTypes.radio && type !== DataType.attach && type !== DataTypes.attach && type !== DataType.table && type !== DataTypes.table && type !== DataType.view && type !== DataTypes.view && type !== DataType.page && type !== DataTypes.page) {
const span = $td.find('span');
if (span) {
span.removeClass('edit');
span.show();
}
let tx = $td.find('input');
if (!tx.dom) tx = $td.find('select');
if (tx.dom) tx.hide();
}
} catch (e) {
log.err(e, 'view');
}
}
}
} catch (e) {
log.err(e, 'view');
}
}
/**
* 保存修改数据到data,并切换到浏览视图
*/ save() {
const _ = this;
try {
_.opt.edit = false;
_.tb.tag('tbody').addClass('etView').removeClass('etEdit');
_.unbind();
_.tb.find('._choose').hide();
if (_.hasTable && _.tabulate) _.tabulate.saveTable();
if (_.data) {
const tds = _.tb.find('td[data-idx]');
for (const td of tds.get()){
try {
const $td = $(td);
const idx = $td.data('idx') // 数据索引
;
const d = _.data[idx];
const { type, option } = d || {};
if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) {
const span = $td.find('span');
// span.eq(0).html(value)
const tx = $td.find('input');
if (tx) {
const val = tx.val();
// 设置 原始值
d.value = val;
$td.data('value', val);
const dvAc = $td.find('.autocomplete');
dvAc?.hide();
}
span.show();
} else if (type === DataType.checkbox || type === DataTypes.checkbox) {
const val = [];
const ns = $td.find('input[type=checkbox]');
for (const n of ns.get()){
if (n.checked) {
val.push($(n).val());
break;
}
}
// 设置 原始值
d.value = val;
$td.data('value', val);
} else if (type === DataType.radio || type === DataTypes.radio) {
let val;
const ns = $td.find('input[type=radio]');
for (const n of ns.get()){
if (n.checked) {
val = $(n).val();
break;
}
}
// 设置 原始值
d.value = val;
$td.data('value', val);
} else if (type === DataType.url || type === DataTypes.url) {
// urlChange
const span = $td.find('span');
let tx = $td.find('input');
if (span) {
span.eq(0).find('a').attr('href', tx.val());
span.removeClass('edit');
span.show();
}
if (tx.dom) {
const val = tx.val();
// 设置 原始值
d.value = val;
$td.data('value', val);
tx.hide();
}
} else if (type !== DataType.attach && type !== DataTypes.attach && type !== DataType.table && type !== DataTypes.table && type !== DataType.view && type !== DataTypes.view && type !== DataType.page && type !== DataTypes.page) {
const span = $td.find('span');
if (span) {
span.removeClass('edit');
span.show();
}
let tx = $td.find('input');
if (!tx.dom) tx = $td.find('select');
if (tx.dom) {
const val = tx.val();
// 设置 原始值
d.value = val;
$td.data('value', val);
tx.hide();
}
}
} catch (e) {
log.err(e, 'save');
}
}
}
} catch (e) {
log.err(e, 'save');
}
}
/**
* 取消修改,还原值
*/ cancel() {
const _ = this;
try {
_.opt.edit = false;
_.tb.tag('tbody').addClass('etView').removeClass('etEdit');
_.tabulate?.togglePreview();
_.unbind();
_.tb.find('._choose').hide();
if (_.data) {
const tds = _.tb.find('td[data-idx]');
for (const td of tds.get()){
try {
const $td = $(td);
const idx = $td.data('idx') // 数据索引
;
const d = _.data[idx];
// const value = $td.data('value') // 原始值
const value = $td?.attr('data-value') // 数据原值 data() 会自动转换 json 字符串
;
const { type, option } = d || {};
if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) {
const dvAc = $td.find('.autocomplete');
dvAc?.hide();
const span = $td.find('span');
span.eq(0).html(value);
span.show();
} else if (type === DataType.checkbox || type === DataTypes.checkbox) {
const ns = $td.find('input[type=checkbox]');
for (const n of ns.get()){
const val = $(n).val();
n.checked = value.includes(val);
}
} else if (type === DataType.radio || type === DataTypes.radio) {
const ns = $td.find('input[type=radio]');
for (const n of ns.get()){
const val = $(n).val();
n.checked = value === val;
}
} else if (type === DataType.attach || type === DataTypes.attach) {
// 取消新增
// uploader 维护 input
$td.dom.uploader.clear() // 清空 input 和 uploader
;
} else if (type === DataType.url || type === DataTypes.url) {
const span = $td.find('span');
if (span.dom) {
span.removeClass('edit');
span.eq(0).find('a').attr('href', value);
span.show();
}
let tx = $td.find('input');
if (tx.dom) {
tx.val(value);
tx.hide();
}
} else if (type !== DataType.table && type !== DataTypes.table && type !== DataType.view && type !== DataTypes.view && type !== DataType.page && type !== DataTypes.page) {
const span = $td.find('span');
if (span.dom) {
span.html(value);
span.removeClass('edit');
span.show();
}
let tx = $td.find('input');
if (!tx.dom) tx = $td.find('select');
if (tx.dom) {
tx.val(value);
tx.hide();
}
}
} catch (e) {
log.err(e, 'cancel');
}
}
}
// 取消删除
cancelDel(_.tb);
} catch (e) {
log.err(e, 'cancel');
}
}
/**
* 样式改为编辑状态,支持 input、span
* textarea 不能自适应行高,不再支持 textarea,使用可编辑span替代
* span 不支持 onchange,使用 viewCell 获取更改值
* 如填入数据与原数据不同,cell 增加 edChange 样式
* @param {HTMLElement} tx - input 或 span 组件
* @param {*} sel 下拉列表, 下拉选择时,sel 为 true
*/ editCell(tx, sel) {
const _ = this;
const { opt } = _;
if (tx && opt.edit) {
const $tx = $(tx);
// 点击同一网格编辑直接返回
if (!sel && this._editCell && tx === this._editCell) return;
if (!opt.editTag.includes(tx.tagName.toLowerCase())) // && tx.tagName !== 'SPAN') // || tx.type != "text")
return;
// 最后编辑行
this.editRow = tool.getUpperObj(tx, 'TR');
// 最后编辑的对象
this.editTx = tx;
// 去掉选择行样式为缺省奇偶样式
if (this._selRow) {
this.setRowClass(this._selRow, '');
this._selRow = null;
}
if (tx.tagName === 'SPAN') {
// 替换提示字符
if (/^~~.+~~/.exec(tx.innerHTML)) tx.innerHTML = tx.innerHTML.replace(/^~~.*~~/, '');
// tx.scrollIntoView(true);
// tx.scrollTop = 50;
} else if (tx.tagName === 'INPUT') {
// tool.setClass(this._editCell, '')
// tool.setClass(tx, 'etCellEdit')
// $tx.addClass('fb-input', true)
$tx.addClass('ds-input', true);
// input 需全选方便替换!
if (!sel) {
tx.focus();
// 全选
tx.select();
}
}
// 记录当前正在编辑单元
this._editCell = tx;
}
}
/**
* 浏览状态,去掉编辑样式
* 对于 span,不会触发 onchange 事件,在这里将 span值同步给隐藏 input,方便向服务器端提交!
* @param {HTMLElement} tx
*/ viewCell(tx) {
if (!tx) return;
// 下列选项会触发 viewCell
if (tx.getAttribute('inputing') && tx.getAttribute('inputing') === '1') // 下拉选择时,保持编辑状态
return;
// 去掉编辑样式
if (tx.tagName === 'INPUT') tool.setClass(tx, 'etCellView');
else if (tx.tagName === 'SPAN') {
const txTo = tool.childTag(tx.parentNode, 'input');
let val = tx.innerHTML.replace(/^~~.*~~/, '');
// 对输入字符进行处理
val = val.replace(/<br>/g, '\n');
// val = val.replace(/>/g, '>');
// val = val.replace(/</g, '<');
val = val.replace(/ /g, ' ');
if (!val || /^[\s\n\r]+$/.exec(val)) {
if (tx.className === 'imgTitle') // style.textAlign === 'center')
tx.innerHTML = '~~点击输入标题~~';
else tx.innerHTML = '~~点击输入~~<br><br>';
if (txTo) {
const img = tool.childTag(txTo.parentNode, 'img');
if (img) {
txTo.value = tool.format('', '', img.getAttribute('src'));
} else txTo.value = '';
}
} else if (txTo) {
// ,号干扰后台数据读取,转换为 \~,后台读取后再转换
// val = tx.innerHTML.replace(/,/g, '\~');
const img = tool.childTag(txTo.parentNode, 'img');
if (img) {
txTo.value = tool.format('', val || '', img.getAttribute('src'));
} else txTo.value = val;
}
}
this._editCell = null;
}
// 更改 行 样式
setRowClass(row, className) {
if (!row) return;
// 恢复原始 样式
if (className) tool.setClass(row, className);
else {
tool.setClass(row, '');
/*
if (row.className.indexOf('Odd') > -1)
tl.setClass(row, 'etRowOdd');
else
tl.setClass(row, 'etRowEven');
*/ }
}
// 对指定 列,设定 只读或非只读,
readOnly(tb1, names, val) {
try {
const tbody = tool.tags(tb1, 'TBODY')[0];
const txs = tool.tags(tbody, 'INPUT');
for(let i = 0; i < txs.length; i++){
// 排除 隐藏列
if (txs[i].type === 'text') {
txs[i].readOnly = !val;
for(let j = 0; j < names.length; j++){
if (txs[i].name === `tx${names[j]}`) txs[i].readOnly = val;
}
}
}
} catch (e) {
alert(e.message);
}
}
// 对指定 列,设定隐藏
hideCol(tb1, names) {
try {
for(let i = 0; i < names.length; i++){
const th = tool.id(`th${names[i]}`);
if (th) {
th.style.display = 'none';
}
}
// 在线编辑模版必须 带 tbody,否则工作不正常
// ??? 为何屏蔽 tbody
const tbody = tool.tags(tb1, 'TBODY')[0];
const txs = tool.tags(tbody, 'INPUT');
for(let i = 0; i < txs.length; i++){
// 排除 隐藏列
if (txs[i].type === 'text') {
for(let j = 0; j < names.length; j++){
if (txs[i].name === `tx${names[j]}`) {
const td = tool.getUpperObj(txs[i], 'TD');
if (td) td.style.display = 'none';
}
}
}
}
} catch (e) {
alert(e.message);
}
}
// 对指定 列,设定显示
showCol(tb1, ns) {
try {
for(let i = 0; i < ns.length; i++){
const th = tool.id(`th${ns[i]}`);
if (th) {
th.style.display = '';
}
}
// 在线编辑模版必须 带 tbody,否则工作不正常
// ??? 为何屏蔽 tbody
const tbody = tool.tags(tb1, 'TBODY')[0];
const txs = tool.tags(tbody, 'INPUT');
for(let i = 0; i < txs.length; i++){
// 排除 隐藏列
if (txs[i].type === 'text') {
for(let j = 0; j < ns.length; j++){
if (txs[i].name === `tx${ns[j]}`) {
const td = tool.getUpperObj(txs[i], 'TD');
if (td) td.style.display = '';
}
}
}
}
} catch (e) {
alert(e.message);
}
}
lightonRow(row) {
const r = tool.getUpperObj(row, 'TR');
if (!r || r === this._selRow) return;
// 当前点击行高亮度显示
if (r.className.indexOf('Odd') > -1) this.setRowClass(r, 'etRowSelOdd');
else this.setRowClass(r, 'etRowSelEven');
// 将所有未被选中的行取消高亮度现实
this.setRowClass(this._selRow, '');
this._selRow = r;
}
// 指定表的指定列,指定数据删除
deleteData(tb1, name, val) {
const txs = document.getElementsByName(name);
for(let i = 1; i < txs.length; i++){
if (txs[i].value === val) {
this.delRow(tb1, i);
i--;
}
}
}
/**
* 行选择,点击选择当前行,便于删除行
* 支持Checkbox 多选行,用于批量行操作
* @param obj
*/ selRow(obj, ev) {
if (!obj) return;
// if ( ev )
// ev.preventDefault();
const row = tool.getUpperObj(obj, 'TR');
if (row) {
if (obj.tagName === 'TD') {
if (row !== this._selRow) this._selRow = row;
} else {
const tbody = tool.getUpperObj(row, 'TBODY');
const tb1 = tbody.parentNode;
const th = tool.tags(tb1, 'TH')[0];
if (tool.lastChild(tool.firstChild(row)).value) {
tool.lastChild(th).value = tool.lastChild(th).value ? `${tool.lastChild(th).value},` : '';
if (obj.checked) tool.lastChild(th).value += tool.lastChild(tool.firstChild(row)).value;
else tool.lastChild(th).value = tool.lastChild(th).value.replace(`${tool.lastChild(tool.firstChild(row)).value},`, '');
}
}
}
}
/**
* 删除选择或最后编辑行,或当前行,一次只能删除一行
* @param tb
* @param iRow 指定的行数
* 返回 剩下的行数
*/ delRow(tb1, iRow) {
let rs = 0;
if (!tb1) return 0;
try {
const tbody = tool.tags(tb1, 'TBODY')[0];
const rows = tool.tags(tbody, 'TR');
if (rows.length === 0) return;
let delRow = null;
if (tb1 && iRow) delRow = tb1.rows[iRow + 1] // 删除选择行
;
else delRow = this._selRow;
// 没有选择行,直接删除最后编辑行
if (!delRow && this.editRow) {
delRow = this.editRow;
// x('txInfo').value = 'this._editRow';
}
if (!delRow) delRow = rows[rows.length - 1];
if (!delRow) return;
if (this._selRow) {
this.setRowClass(this._selRow, '');
this._selRow = null;
}
if (this._editCell) {
tool.setClass(this._editCell, '');
this._editCell = null;
}
const th = tool.tags(tb1, 'TH')[0];
if (delRow.childElementCount > 0 && tool.lastChild(tool.firstChild(delRow)).value) {
// 记录删除ID
tool.lastChild(th).value = tool.lastChild(th).value ? `${tool.lastChild(th).value},` : '';
tool.lastChild(th).value += tool.lastChild(tool.firstChild(delRow)).value;
}
// alert( tl.lastChild(tl.firstChild(delRow)).value );
if (delRow === this._selRow) this._selRow = null;
if (delRow === this.editRow) {
this.editRow = null;
this.editTx = null;
}
let preRow = delRow.previousSibling;
let span = tool.tags(delRow, 'span')[0];
if (delRow.parentNode) delRow.parentNode.removeChild(delRow);
// span 全屏编辑,没有数据,自动增加一行
if (span) {
// 返回剩下行数
rs = tool.tags(tbody, 'TR').length;
if (!rs) {
preRow = this.addRow(tb1, '~~点击输入~~\n\r\n\r');
rs = 1;
}
if (preRow) {
span = tool.tags(preRow, 'span')[0];
if (span) tool.cursorEnd(span);
}
}
// 对tr重新排序,排除 文本节点干扰,移动端不需要,屏蔽!!!???
/*
var i = 0;
for(var j = 0; j < tb