Browse Source

Merge branch 'es6' of ssh://code.fineres.com:7999/~dailer/fineui into es6

es6
zsmj 2 years ago
parent
commit
c763429e2c
  1. 153
      es6.js
  2. 128
      es6.xtype.js
  3. 12
      src/case/index.js
  4. 4
      src/case/layer/index.js
  5. 54
      src/case/layer/layer.multipopup.js
  6. 57
      src/case/layer/layer.panel.js
  7. 210
      src/case/layer/pane.list.js
  8. 70
      src/case/layer/panel.js
  9. 53
      src/case/linearsegment/button.linear.segment.js
  10. 2
      src/case/linearsegment/index.js
  11. 74
      src/case/linearsegment/linear.segment.js
  12. 249
      src/case/list/list.select.js
  13. 8
      src/core/utils/dom.js

153
es6.js

@ -2,6 +2,7 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const prettier = require("prettier"); const prettier = require("prettier");
const { exec } = require("child_process"); const { exec } = require("child_process");
const { search, initDepts } = require("./es6.xtype");
async function fix(path) { async function fix(path) {
new Promise(res => { new Promise(res => {
@ -11,7 +12,65 @@ async function fix(path) {
}); });
} }
// 加载模块
const loader = {
G: { "@/core": { shortcut: true } },
async load(srcName, module) {
const G = loader.G;
const target = [
"isNull",
"toPix",
"isKey",
"isObject",
"map",
"extend",
"isFunction",
"isEmptyArray",
"isArray",
"Controller",
"createWidget",
"Events",
"emptyFn",
"nextTick",
"bind",
"i18nText",
"isNotNull",
"isString",
"isNumber",
"isEmpty",
"isEmptyString",
"any",
"deepContains",
"isNotEmptyString",
"each",
"contains",
"remove",
"createItems",
"makeArrayByArray",
];
console.log(module);
if (target.indexOf(module) >= 0) {
G["@/core"][module] = true;
return true;
}
const key = search(srcName, module);
if (key) {
if (!G[key]) {
G[key] = {};
}
G[key][module] = true;
}
return !!key;
},
};
async function handleFile(srcName) { async function handleFile(srcName) {
const G = loader.G;
const sourceCode = fs.readFileSync(srcName).toString(); const sourceCode = fs.readFileSync(srcName).toString();
const result = /BI\.(.*?)\s\=\sBI\.inherit\(/.exec(sourceCode); const result = /BI\.(.*?)\s\=\sBI\.inherit\(/.exec(sourceCode);
@ -24,6 +83,8 @@ async function handleFile(srcName) {
const superName = /inherit\(BI\.(.*?),/.exec(sourceCode)[1]; const superName = /inherit\(BI\.(.*?),/.exec(sourceCode)[1];
// const xtype = /BI.shortcut\(\"(.*?)\"/.exec(sourceCode)[1]; // const xtype = /BI.shortcut\(\"(.*?)\"/.exec(sourceCode)[1];
const collection = { const collection = {
@ -31,6 +92,7 @@ async function handleFile(srcName) {
attr: {}, attr: {},
}; };
// eslint-disable-next-line no-unused-vars
const BI = { const BI = {
inherit(_, options) { inherit(_, options) {
collection.methods = Object.keys(options) collection.methods = Object.keys(options)
@ -49,6 +111,7 @@ async function handleFile(srcName) {
}, },
}; };
// eslint-disable-next-line no-eval
eval(sourceCode); eval(sourceCode);
let M = ""; let M = "";
@ -56,13 +119,7 @@ async function handleFile(srcName) {
let I = ""; let I = "";
let A = ""; let A = "";
const coreImport = { loader.load(srcName, superName);
shortcut: true,
};
if (superName === "Widget") {
coreImport.Widget = true;
}
Object.keys(collection.attr).forEach(key => { Object.keys(collection.attr).forEach(key => {
A = `${key} = ${JSON.stringify(collection.attr[key])};`; A = `${key} = ${JSON.stringify(collection.attr[key])};`;
@ -81,67 +138,52 @@ async function handleFile(srcName) {
f = f.replace(`BI.${clzName}.superclass`, "super"); f = f.replace(`BI.${clzName}.superclass`, "super");
// 换 super._defaultConfig // 换 super._defaultConfig
f = f.replace( f = f.replace(
`super\._defaultConfig\.apply\(this\,\sarguments\)`, /super\._defaultConfig\.apply\(this,\sarguments\)/g,
"super._defaultConfig(arguments)" "super._defaultConfig(arguments)"
); );
// 换 super.xxx.apply // 换 super.xxx.apply
f = f.replace(/super\.(.*?)\.apply\(this\,\sarguments\)/, a => { f = f.replace(/super\.(.*?)\.apply\(this,\sarguments\)/, a => {
const f = /super\.(.*?)\.apply\(this\,\sarguments\)/.exec(a); const f = /super\.(.*?)\.apply\(this,\sarguments\)/.exec(a);
return `super.${f[1]}(...arguments)`; return `super.${f[1]}(...arguments)`;
}); });
const target = [ // 尝试换掉所有的 BI.xxx. BI.xxx( BI.xxx[空白]
"isNull", f = f.replace(/BI\.(.*?)(\.|\(|\s|,)/g, matchedSentence => {
"toPix", const match = /BI\.(.*?)(\.|\(|\s|,)/.exec(matchedSentence);
"isKey", const target = match[1];
"isObject", const end = match[2];
"map", // 尝试加载 target
"extend", const loadSuccess = loader.load(srcName, target);
"isFunction", if (loadSuccess) {
"isEmptyArray", return target + end;
"isArray", } else {
"Controller", console.log(`加载 ${target}失败`);
clzName,
"createWidget", return matchedSentence;
"Events",
"emptyFn",
"nextTick",
"bind",
"i18nText",
"isNotNull",
"isString",
"isNumber",
"isEmpty",
"isEmptyString",
"any",
"deepContains",
"isNotEmptyString",
"each",
"contains",
"remove",
"createItems",
"makeArrayByArray",
];
target.forEach(t => {
const arr = f.split(`BI.${t}`);
// nodejs 低版本没有 replaceAll
if (arr.length > 1) {
if (t !== clzName) coreImport[t] = true;
f = arr.join(t);
} }
}); });
M += `${f}\n`; M += `${f}\n`;
}); });
Object.keys(coreImport).forEach(el => {
I += `${el},`;
Object.keys(G).forEach(moduleKey => {
if (moduleKey === path.basename(srcName).replace(/.js$/g, "")) {
return;
}
let i = "";
Object.keys(G[moduleKey]).forEach(key => {
i += `${key}, `;
});
I += `import {${i}} from '${moduleKey}'\n`;
}); });
const outputCode = ` const outputCode = `
import {${I}} from "@/core" ${I}
@shortcut() @shortcut()
export class ${clzName} extends ${superName} { export class ${clzName} extends ${superName} {
@ -187,4 +229,9 @@ async function traverse(srcName) {
} }
const srcName = process.argv[2]; const srcName = process.argv[2];
traverse(srcName);
initDepts().then(() => {
traverse(srcName);
});

128
es6.xtype.js

@ -0,0 +1,128 @@
const fs = require("fs");
const path = require("path");
const depts = {};
async function handle(filename) {
// 找clzName
const code = fs.readFileSync(filename);
const inheritRegResult = /BI\.(.*?)\s=\sBI\.inherit\(/.exec(code);
let clzName;
if (inheritRegResult) {
clzName = inheritRegResult[1];
} else {
const clzRegResult = /export\sclass\s(.*?)\sextend/.exec(code);
if (clzRegResult) {
clzName = clzRegResult[1];
}
}
depts[clzName] = filename;
}
function isExist(filePath) {
return fs.existsSync(filePath);
}
async function bfs(filename) {
const stat = fs.statSync(filename);
const isDir = stat.isDirectory();
if (isDir) {
const files = fs.readdirSync(filename);
for (let i = 0; i < files.length; i++) {
const file = files[i];
await bfs(path.resolve(filename, file));
}
} else {
await handle(filename);
}
}
async function initDepts() {
// dfs 构建依赖关系
await bfs(path.resolve("src"));
}
function search(src, clzName) {
if (!depts[clzName]) {
return "";
}
const dstName = path.basename(depts[clzName]).replace(/.js$/g, "");
const dstPath = path.normalize(depts[clzName]).split("src")[1].split("\\").join("/").split("/");
const srcPath = path.normalize(src).split("src")[1].split("\\").join("/").split("/");
// console.log("src", src);
// console.log("dst", depts[clzName]);
dstPath.shift();
dstPath.pop();
srcPath.shift();
srcPath.pop();
const findDstIndexPath = (dstArr, startIndex) => {
let i = startIndex;
while (!isExist(path.resolve("src", dstArr.slice(0, i + 1).join("/"), "index.js"))) {
i++;
}
if (i < dstArr.length) {
return dstArr.slice(startIndex, i + 1).join("/");
} else {
return `${dstArr.slice(startIndex).join("/")}/${dstName}`;
}
};
// 不同包
if (dstPath[0] !== srcPath[0]) {
return `@/${dstPath[0]}`;
}
// 同包
let i = 0;
while (dstPath[i] === srcPath[i] && i < dstPath.length && i < srcPath.length) {
i++;
}
if (i < srcPath.length) {
let result = "";
const rawI = i;
// 回溯
for (let j = 0; j < srcPath.length - rawI; j++) {
result += "../";
i--;
}
i++;
// dstPath 也没有了
if (i < dstPath.length) {
return result + findDstIndexPath(dstPath, i);
// 还有好多没有匹配完
// while (i < srcPath) {
// // exists(srcPath.slice(0, i).join())
// result += srcPath[i];
// i++;
// }
} else if (i === dstPath.length) {
return `${result}${dstName}`;
}
} else if (i === srcPath.length) {
if (i === dstPath.length) {
return dstName;
} else if (i < dstPath.length) {
return findDstIndexPath(dstPath, i);
}
}
}
// search(process.argv[2], "Text").then(res => {
// console.log(res);
// });
exports.initDepts = initDepts;
exports.search = search;

12
src/case/index.js

@ -8,6 +8,9 @@ import * as trigger from "./trigger";
import * as loader from "./loader"; import * as loader from "./loader";
import * as segment from "./segment"; import * as segment from "./segment";
import { MultiSelectBar } from "./toolbar/toolbar.multiselect"; import { MultiSelectBar } from "./toolbar/toolbar.multiselect";
import * as layer from "./layer";
import * as linearSegment from "./linearsegment";
import { SelectList } from "./list/list.select";
Object.assign(BI, { Object.assign(BI, {
...button, ...button,
@ -20,6 +23,9 @@ Object.assign(BI, {
...loader, ...loader,
...segment, ...segment,
MultiSelectBar, MultiSelectBar,
...layer,
...linearSegment,
SelectList,
}); });
export * from "./button"; export * from "./button";
@ -32,6 +38,10 @@ export * from "./ztree";
export * from "./trigger"; export * from "./trigger";
export * from "./loader"; export * from "./loader";
export * from "./segment"; export * from "./segment";
export * from "./layer";
export * from "./linearsegment";
export { export {
MultiSelectBar MultiSelectBar,
SelectList
}; };

4
src/case/layer/index.js

@ -0,0 +1,4 @@
export { MultiPopupView } from "./layer.multipopup";
export { PopupPanel } from "./layer.panel";
export { ListPane } from "./pane.list";
export { Panel } from "./panel";

54
src/case/layer/layer.multipopup.js

@ -4,57 +4,57 @@
* @extends BI.Widget * @extends BI.Widget
*/ */
BI.MultiPopupView = BI.inherit(BI.PopupView, { import { shortcut, extend, i18nText, each, createWidget, createItems } from "@/core";
import { PopupView, ButtonGroup } from "@/base";
_defaultConfig: function () { @shortcut()
var conf = BI.MultiPopupView.superclass._defaultConfig.apply(this, arguments); export class MultiPopupView extends PopupView {
return BI.extend(conf, { static xtype = "bi.multi_popup_view";
_baseCls: (conf._baseCls || "") + " bi-multi-list-view", static EVENT_CHANGE = "EVENT_CHANGE";
buttons: [BI.i18nText("BI-Basic_OK")] static EVENT_CLICK_TOOLBAR_BUTTON = "EVENT_CLICK_TOOLBAR_BUTTON";
_defaultConfig () {
const conf = super._defaultConfig(...arguments);
return extend(conf, {
_baseCls: `${conf._baseCls || ""} bi-multi-list-view`,
buttons: [i18nText("BI-Basic_OK")],
}); });
}, }
_createToolBar: function () { _createToolBar () {
var o = this.options, self = this; const o = this.options;
if (o.buttons.length === 0) { if (o.buttons.length === 0) {
return; return;
} }
var text = []; // 构造[{text:content},……] const text = []; // 构造[{text:content},……]
BI.each(o.buttons, function (idx, item) { each(o.buttons, (idx, item) => {
text.push({ text.push({
text: item, text: item,
value: idx value: idx,
}); });
}); });
this.buttongroup = BI.createWidget({ this.buttongroup = createWidget({
type: "bi.button_group", type: "bi.button_group",
cls: "list-view-toolbar bi-high-light bi-split-top", cls: "list-view-toolbar bi-high-light bi-split-top",
height: BI.SIZE_CONSANTS.LIST_ITEM_HEIGHT, height: BI.SIZE_CONSANTS.LIST_ITEM_HEIGHT,
items: BI.createItems(text, { items: createItems(text, {
type: "bi.text_button", type: "bi.text_button",
once: false, once: false,
shadow: true, shadow: true,
isShadowShowingOnSelected: true isShadowShowingOnSelected: true,
}), }),
layouts: [{ layouts: [{
type: "bi.center", type: "bi.center",
hgap: 0, hgap: 0,
vgap: 0 vgap: 0,
}] }],
}); });
this.buttongroup.on(BI.ButtonGroup.EVENT_CHANGE, function (value, obj) { this.buttongroup.on(ButtonGroup.EVENT_CHANGE, (value, obj) => {
self.fireEvent(BI.MultiPopupView.EVENT_CLICK_TOOLBAR_BUTTON, value, obj); this.fireEvent(MultiPopupView.EVENT_CLICK_TOOLBAR_BUTTON, value, obj);
}); });
return this.buttongroup; return this.buttongroup;
} }
}
});
BI.MultiPopupView.EVENT_CHANGE = "EVENT_CHANGE";
BI.MultiPopupView.EVENT_CLICK_TOOLBAR_BUTTON = "EVENT_CLICK_TOOLBAR_BUTTON";
BI.shortcut("bi.multi_popup_view", BI.MultiPopupView);

57
src/case/layer/layer.panel.js

@ -4,29 +4,38 @@
* @extends BI.MultiPopupView * @extends BI.MultiPopupView
*/ */
BI.PopupPanel = BI.inherit(BI.MultiPopupView, { import { shortcut, extend, createWidget } from "@/core";
import { IconButton } from "@/base";
_defaultConfig: function () { import { MultiPopupView } from "./layer.multipopup";
var conf = BI.PopupPanel.superclass._defaultConfig.apply(this, arguments); @shortcut()
return BI.extend(conf, { export class PopupPanel extends MultiPopupView {
baseCls: (conf.baseCls || "") + " bi-popup-panel", static xtype = "bi.popup_panel";
title: "" static EVENT_CHANGE = "EVENT_CHANGE";
static EVENT_CLOSE = "EVENT_CLOSE";
static EVENT_CLICK_TOOLBAR_BUTTON = "EVENT_CLICK_TOOLBAR_BUTTON";
_defaultConfig () {
const conf = super._defaultConfig(...arguments);
return extend(conf, {
baseCls: `${conf.baseCls || ""} bi-popup-panel`,
title: "",
}); });
}, }
_createTool: function () { _createTool () {
var self = this, o = this.options; const o = this.options;
var close = BI.createWidget({ const close = createWidget({
type: "bi.icon_button", type: "bi.icon_button",
cls: "close-h-font", cls: "close-h-font",
width: 25, width: 25,
height: 25 height: 25,
}); });
close.on(BI.IconButton.EVENT_CHANGE, function () { close.on(IconButton.EVENT_CHANGE, () => {
self.setVisible(false); this.setVisible(false);
self.fireEvent(BI.PopupPanel.EVENT_CLOSE); this.fireEvent(PopupPanel.EVENT_CLOSE);
}); });
return BI.createWidget({
return createWidget({
type: "bi.htape", type: "bi.htape",
cls: "popup-panel-title bi-header-background", cls: "popup-panel-title bi-header-background",
height: 25, height: 25,
@ -36,18 +45,12 @@ BI.PopupPanel = BI.inherit(BI.MultiPopupView, {
textAlign: "left", textAlign: "left",
text: o.title, text: o.title,
height: 25, height: 25,
lgap: 10 lgap: 10,
} },
}, { }, {
el: close, el: close,
width: 25 width: 25,
}] }],
}); });
} }
}); }
BI.PopupPanel.EVENT_CHANGE = "EVENT_CHANGE";
BI.PopupPanel.EVENT_CLOSE = "EVENT_CLOSE";
BI.PopupPanel.EVENT_CLICK_TOOLBAR_BUTTON = "EVENT_CLICK_TOOLBAR_BUTTON";
BI.shortcut("bi.popup_panel", BI.PopupPanel);

210
src/case/layer/pane.list.js

@ -5,14 +5,19 @@
* @class BI.ListPane * @class BI.ListPane
* @extends BI.Pane * @extends BI.Pane
*/ */
BI.ListPane = BI.inherit(BI.Pane, { import { shortcut, extend, each, createWidget, emptyFn, nextTick, concat, get, Controller, Events, LogicFactory, Direction, isNull, removeAt, isFunction, isNotEmptyString, isEmptyArray } from "@/core";
import { Pane, ButtonGroup } from "@/base";
_defaultConfig: function () { @shortcut()
var conf = BI.ListPane.superclass._defaultConfig.apply(this, arguments); export class ListPane extends Pane {
return BI.extend(conf, { static xtype = "bi.list_pane";
baseCls: (conf.baseCls || "") + " bi-list-pane", static EVENT_CHANGE = "EVENT_CHANGE";
_defaultConfig () {
const conf = super._defaultConfig(...arguments);
return extend(conf, {
baseCls: `${conf.baseCls || ""} bi-list-pane`,
logic: { logic: {
dynamic: true dynamic: true,
}, },
lgap: 0, lgap: 0,
rgap: 0, rgap: 0,
@ -21,181 +26,180 @@ BI.ListPane = BI.inherit(BI.Pane, {
vgap: 0, vgap: 0,
hgap: 0, hgap: 0,
items: [], items: [],
itemsCreator: BI.emptyFn, itemsCreator: emptyFn,
hasNext: BI.emptyFn, hasNext: emptyFn,
onLoaded: BI.emptyFn, onLoaded: emptyFn,
el: { el: {
type: "bi.button_group" type: "bi.button_group",
} },
}); });
}, }
_init: function () { _init () {
BI.ListPane.superclass._init.apply(this, arguments); super._init(...arguments);
var self = this, o = this.options; const o = this.options;
this.button_group = createWidget(o.el, {
this.button_group = BI.createWidget(o.el, {
type: "bi.button_group", type: "bi.button_group",
chooseType: BI.ButtonGroup.CHOOSE_TYPE_SINGLE, chooseType: ButtonGroup.CHOOSE_TYPE_SINGLE,
behaviors: {}, behaviors: {},
items: o.items, items: o.items,
value: o.value, value: o.value,
itemsCreator: function (op, calback) { itemsCreator: (op, callback) => {
if (op.times === 1) { if (op.times === 1) {
self.empty(); this.empty();
BI.nextTick(function () { nextTick(() => {
self.loading(); this.loading();
}); });
} }
o.itemsCreator(op, function () { o.itemsCreator(op, (...args) => {
calback.apply(self, arguments); callback(...args);
o.items = BI.concat(o.items, BI.get(arguments, [0], [])); o.items = concat(o.items, get(...args, [0], []));
if (op.times === 1) { if (op.times === 1) {
o.items = BI.get(arguments, [0], []); o.items = get(...args, [0], []);
BI.nextTick(function () { nextTick(() => {
self.loaded(); this.loaded();
// callback可能在loading之前执行, check保证显示正确 // callback可能在loading之前执行, check保证显示正确
self.check(); this.check();
}); });
} }
}); });
}, },
hasNext: o.hasNext, hasNext: o.hasNext,
layouts: [{ layouts: [{
type: "bi.vertical" type: "bi.vertical",
}] }],
}); });
this.button_group.on(BI.Controller.EVENT_CHANGE, function (type, value, obj) { this.button_group.on(Controller.EVENT_CHANGE, (type, value, obj, ...args) => {
self.fireEvent(BI.Controller.EVENT_CHANGE, arguments); this.fireEvent(Controller.EVENT_CHANGE, ...args);
if (type === BI.Events.CLICK) { if (type === Events.CLICK) {
self.fireEvent(BI.ListPane.EVENT_CHANGE, value, obj); this.fireEvent(ListPane.EVENT_CHANGE, value, obj);
} }
}); });
this.check(); this.check();
BI.createWidget(BI.extend({ createWidget(extend({
element: this element: this,
}, BI.LogicFactory.createLogic(BI.LogicFactory.createLogicTypeByDirection(BI.Direction.Top), BI.extend({ }, LogicFactory.createLogic(LogicFactory.createLogicTypeByDirection(Direction.Top), extend({
scrolly: true, scrolly: true,
lgap: o.lgap, lgap: o.lgap,
rgap: o.rgap, rgap: o.rgap,
tgap: o.tgap, tgap: o.tgap,
bgap: o.bgap, bgap: o.bgap,
vgap: o.vgap, vgap: o.vgap,
hgap: o.hgap hgap: o.hgap,
}, o.logic, { }, o.logic, {
items: BI.LogicFactory.createLogicItemsByDirection(BI.Direction.Top, this.button_group) items: LogicFactory.createLogicItemsByDirection(Direction.Top, this.button_group),
})))); }))));
}, }
hasPrev: function () { hasPrev () {
return this.button_group.hasPrev && this.button_group.hasPrev(); return this.button_group.hasPrev && this.button_group.hasPrev();
}, }
hasNext: function () { hasNext () {
return this.button_group.hasNext && this.button_group.hasNext(); return this.button_group.hasNext && this.button_group.hasNext();
}, }
prependItems: function (items) { prependItems (items) {
this.options.items = items.concat(this.options.items); this.options.items = items.concat(this.options.items);
this.button_group.prependItems.apply(this.button_group, arguments); this.button_group.prependItems(...arguments);
this.check(); this.check();
}, }
addItems: function (items) { addItems (items) {
this.options.items = this.options.items.concat(items); this.options.items = this.options.items.concat(items);
this.button_group.addItems.apply(this.button_group, arguments); this.button_group.addItems(...arguments);
this.check(); this.check();
}, }
removeItemAt: function (indexes) { removeItemAt (indexes) {
indexes = BI.isNull(indexes) ? [] : indexes; indexes = isNull(indexes) ? [] : indexes;
BI.removeAt(this.options.items, indexes); removeAt(this.options.items, indexes);
this.button_group.removeItemAt.apply(this.button_group, arguments); this.button_group.removeItemAt(...arguments);
this.check(); this.check();
}, }
populate: function (items) { populate (items) {
var self = this, o = this.options; const o = this.options;
if (arguments.length === 0 && (BI.isFunction(this.button_group.attr("itemsCreator")))) {// 接管loader的populate方法 if (arguments.length === 0 && (isFunction(this.button_group.attr("itemsCreator")))) {// 接管loader的populate方法
this.button_group.attr("itemsCreator").apply(this, [{ times: 1 }, function () { this.button_group.attr("itemsCreator").apply(this, [{ times: 1 }, (...args) => {
if (arguments.length === 0) { if (args.length === 0) {
throw new Error("参数不能为空"); throw new Error("参数不能为空");
} }
self.populate.apply(self, arguments); this.populate(...args);
}]); }]);
return; return;
} }
var context = BI.get(arguments, [2], {}); const context = get(arguments, [2], {});
var tipText = context.tipText || ''; const tipText = context.tipText || "";
if (BI.isNotEmptyString(tipText)) { if (isNotEmptyString(tipText)) {
BI.ListPane.superclass.populate.apply(this, []); super.populate.apply(this, []);
this.setTipText(tipText); this.setTipText(tipText);
} else { } else {
BI.ListPane.superclass.populate.apply(this, arguments); super.populate(...arguments);
this.button_group.populate.apply(this.button_group, arguments); this.button_group.populate(...arguments);
BI.isEmptyArray(BI.get(arguments, [0], [])) && this.setTipText(o.tipText); isEmptyArray(get(arguments, [0], [])) && this.setTipText(o.tipText);
} }
}, }
empty: function () { empty () {
this.button_group.empty(); this.button_group.empty();
}, }
setNotSelectedValue: function () { setNotSelectedValue () {
this.button_group.setNotSelectedValue.apply(this.button_group, arguments); this.button_group.setNotSelectedValue(...arguments);
}, }
getNotSelectedValue: function () { getNotSelectedValue () {
return this.button_group.getNotSelectedValue(); return this.button_group.getNotSelectedValue();
}, }
setValue: function () { setValue () {
this.button_group.setValue.apply(this.button_group, arguments); this.button_group.setValue(...arguments);
}, }
setAllSelected: function (v) { setAllSelected (v) {
if (this.button_group.setAllSelected) { if (this.button_group.setAllSelected) {
this.button_group.setAllSelected(v); this.button_group.setAllSelected(v);
} else { } else {
BI.each(this.getAllButtons(), function (i, btn) { each(this.getAllButtons(), (i, btn) => {
(btn.setSelected || btn.setAllSelected).apply(btn, [v]); (btn.setSelected || btn.setAllSelected).apply(btn, [v]);
}); });
} }
}, }
getValue: function () { getValue () {
return this.button_group.getValue.apply(this.button_group, arguments); return this.button_group.getValue(...arguments);
}, }
getAllButtons: function () { getAllButtons () {
return this.button_group.getAllButtons(); return this.button_group.getAllButtons();
}, }
getAllLeaves: function () { getAllLeaves () {
return this.button_group.getAllLeaves(); return this.button_group.getAllLeaves();
}, }
getSelectedButtons: function () { getSelectedButtons () {
return this.button_group.getSelectedButtons(); return this.button_group.getSelectedButtons();
}, }
getNotSelectedButtons: function () { getNotSelectedButtons () {
return this.button_group.getNotSelectedButtons(); return this.button_group.getNotSelectedButtons();
}, }
getIndexByValue: function (value) { getIndexByValue (value) {
return this.button_group.getIndexByValue(value); return this.button_group.getIndexByValue(value);
}, }
getNodeById: function (id) { getNodeById (id) {
return this.button_group.getNodeById(id); return this.button_group.getNodeById(id);
}, }
getNodeByValue: function (value) { getNodeByValue (value) {
return this.button_group.getNodeByValue(value); return this.button_group.getNodeByValue(value);
} }
}); }
BI.ListPane.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.list_pane", BI.ListPane);

70
src/case/layer/panel.js

@ -1,11 +1,18 @@
/**
* 带有标题栏的pane import { shortcut, Widget, extend, toPix, Controller, createWidget } from "@/core";
* @class BI.Panel import { ButtonGroup } from "@/base";
* @extends BI.Widget
*/ @shortcut()
BI.Panel = BI.inherit(BI.Widget, { export class Panel extends Widget {
_defaultConfig: function () { static xtype = "bi.panel"
return BI.extend(BI.Panel.superclass._defaultConfig.apply(this, arguments), {
static EVENT_CHANGE = "EVENT_CHANGE"
_defaultConfig() {
return extend(super._defaultConfig(...arguments), {
baseCls: "bi-panel bi-border", baseCls: "bi-panel bi-border",
title: "", title: "",
titleHeight: 30, titleHeight: 30,
@ -15,62 +22,59 @@ BI.Panel = BI.inherit(BI.Widget, {
// dynamic: false // dynamic: false
// } // }
}); });
}, }
render: function () { render() {
return { return {
type: "bi.vertical_fill", type: "bi.vertical_fill",
rowSize: ["", "fill"], rowSize: ["", "fill"],
items: [this._createTitle(), this.options.el] items: [this._createTitle(), this.options.el],
}; };
}, }
_createTitle: function () { _createTitle() {
var self = this, o = this.options; const o = this.options;
this.text = BI.createWidget({ this.text = createWidget({
type: "bi.label", type: "bi.label",
cls: "panel-title-text", cls: "panel-title-text",
text: o.title, text: o.title,
height: o.titleHeight height: o.titleHeight,
}); });
this.button_group = BI.createWidget({ this.button_group = createWidget({
type: "bi.button_group", type: "bi.button_group",
items: o.titleButtons, items: o.titleButtons,
layouts: [{ layouts: [{
type: "bi.center_adapt", type: "bi.center_adapt",
lgap: 10 lgap: 10,
}] }],
}); });
this.button_group.on(BI.Controller.EVENT_CHANGE, function () { this.button_group.on(Controller.EVENT_CHANGE, () => {
self.fireEvent(BI.Controller.EVENT_CHANGE, arguments); this.fireEvent(Controller.EVENT_CHANGE, ...arguments);
}); });
this.button_group.on(BI.ButtonGroup.EVENT_CHANGE, function (value, obj) { this.button_group.on(ButtonGroup.EVENT_CHANGE, (value, obj) => {
self.fireEvent(BI.Panel.EVENT_CHANGE, value, obj); this.fireEvent(Panel.EVENT_CHANGE, value, obj);
}); });
return { return {
// el: { // el: {
type: "bi.left_right_vertical_adapt", type: "bi.left_right_vertical_adapt",
cls: "panel-title bi-header-background bi-border-bottom", cls: "panel-title bi-header-background bi-border-bottom",
height: BI.toPix(o.titleHeight, 1), height: toPix(o.titleHeight, 1),
items: { items: {
left: [this.text], left: [this.text],
right: [this.button_group] right: [this.button_group],
}, },
lhgap: 10, lhgap: 10,
rhgap: 10 rhgap: 10,
// }, // },
// height: BI.toPix(o.titleHeight, 1) // height: toPix(o.titleHeight, 1)
}; };
}, }
setTitle: function (title) { setTitle(title) {
this.text.setValue(title); this.text.setValue(title);
} }
}); }
BI.Panel.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.panel", BI.Panel);

53
src/case/linearsegment/button.linear.segment.js

@ -1,26 +1,28 @@
BI.LinearSegmentButton = BI.inherit(BI.BasicButton, {
props: { import { shortcut, toPix } from "@/core";
extraCls: "bi-line-segment-button bi-list-item-effect", import { BasicButton } from "@/base";
once: true,
readonly: true,
hgap: 10,
height: 24
},
render: function () { @shortcut()
var self = this, o = this.options; export class LinearSegmentButton extends BasicButton {
static xtype = "bi.linear_segment_button"
props = { extraCls:"bi-line-segment-button bi-list-item-effect", once:true, readonly:true, hgap:10, height:24 };
render () {
const o = this.options;
return [{ return [{
type: "bi.label", type: "bi.label",
text: o.text, text: o.text,
height: o.height, height: o.height,
textHeight: BI.toPix(o.height, 2), textHeight: toPix(o.height, 2),
value: o.value, value: o.value,
hgap: o.hgap, hgap: o.hgap,
ref: function () { ref : _ref => {
self.text = this; this.text = _ref;
} },
}, { }, {
type: "bi.absolute", type: "bi.absolute",
items: [{ items: [{
@ -28,28 +30,27 @@ BI.LinearSegmentButton = BI.inherit(BI.BasicButton, {
type: "bi.layout", type: "bi.layout",
cls: "line-segment-button-line", cls: "line-segment-button-line",
height: 2, height: 2,
ref: function () { ref : _ref => {
self.line = this; this.line = _ref;
} },
}, },
left: 0, left: 0,
right: 0, right: 0,
bottom: 0 bottom: 0,
}] }],
}]; }];
}, }
setSelected: function (v) { setSelected (v) {
BI.LinearSegmentButton.superclass.setSelected.apply(this, arguments); super.setSelected(...arguments);
if (v) { if (v) {
this.line.element.addClass("bi-high-light-background"); this.line.element.addClass("bi-high-light-background");
} else { } else {
this.line.element.removeClass("bi-high-light-background"); this.line.element.removeClass("bi-high-light-background");
} }
}, }
setText: function (text) { setText (text) {
this.text.setText(text); this.text.setText(text);
} }
}); }
BI.shortcut("bi.linear_segment_button", BI.LinearSegmentButton);

2
src/case/linearsegment/index.js

@ -0,0 +1,2 @@
export { LinearSegmentButton } from "./button.linear.segment";
export { LinearSegment } from "./linear.segment";

74
src/case/linearsegment/linear.segment.js

@ -1,60 +1,62 @@
BI.LinearSegment = BI.inherit(BI.Widget, {
props: { import { shortcut, Widget, createItems, makeArrayByArray } from "@/core";
baseCls: "bi-linear-segment",
items: [],
height: 30
},
render: function () { @shortcut()
var self = this, o = this.options; export class LinearSegment extends Widget {
static xtype = "bi.linear_segment"
props = { baseCls:"bi-linear-segment", items:[], height:30 };
render () {
const o = this.options;
return { return {
type: "bi.button_group", type: "bi.button_group",
items: [BI.createItems(o.items, { items: [createItems(o.items, {
type: "bi.linear_segment_button", type: "bi.linear_segment_button",
height: o.height height: o.height,
})], })],
layouts: [{ layouts: [{
type: "bi.table", type: "bi.table",
columnSize: BI.makeArrayByArray(o.items, "fill"), columnSize: makeArrayByArray(o.items, "fill"),
}], }],
value: o.value, value: o.value,
listeners: [{ listeners: [{
eventName: "__EVENT_CHANGE__", eventName: "__EVENT_CHANGE__",
action: function () { action () {
self.fireEvent("__EVENT_CHANGE__", arguments); this.fireEvent("__EVENT_CHANGE__", arguments);
} },
}, { }, {
eventName: "EVENT_CHANGE", eventName: "EVENT_CHANGE",
action: function () { action () {
self.fireEvent("EVENT_CHANGE"); this.fireEvent("EVENT_CHANGE");
} },
}], }],
ref: function () { ref: _ref => {
self.buttonGroup = this; this.buttonGroup = _ref;
} },
}; };
}, }
setValue: function (v) { setValue (v) {
this.buttonGroup.setValue(v); this.buttonGroup.setValue(v);
}, }
setEnabledValue: function (v) { setEnabledValue (v) {
this.buttonGroup.setEnabledValue(v); this.buttonGroup.setEnabledValue(v);
}, }
getValue: function () { getValue () {
return this.buttonGroup.getValue(); return this.buttonGroup.getValue();
}, }
populate: function (buttons) { populate (buttons) {
var o = this.options; const o = this.options;
this.buttonGroup.populate([BI.createItems(buttons, { this.buttonGroup.populate([createItems(buttons, {
type: "bi.linear_segment_button", type: "bi.linear_segment_button",
height: o.height height: o.height,
})]) })]);
}, }
}); }
BI.shortcut("bi.linear_segment", BI.LinearSegment);

249
src/case/list/list.select.js

@ -1,100 +1,105 @@
/** /* eslint-disable no-mixed-spaces-and-tabs */
* 选择列表
* import { shortcut, Widget, extend, emptyFn, Controller, createWidget, Events, isNotNull, isEmptyString, isEmptyArray, Direction, get, LogicFactory, each, pixFormat } from "@/core";
* Created by GUY on 2015/11/1. import { ButtonGroup } from "@/base";
* @class BI.SelectList @shortcut()
* @extends BI.Widget export class SelectList extends Widget {
*/ static xtype = "bi.select_list";
BI.SelectList = BI.inherit(BI.Widget, {
_defaultConfig: function () {
return BI.extend(BI.SelectList.superclass._defaultConfig.apply(this, arguments), { static EVENT_CHANGE = "EVENT_CHANGE";
_defaultConfig() {
return extend(super._defaultConfig(...arguments), {
baseCls: "bi-select-list", baseCls: "bi-select-list",
direction: BI.Direction.Top, // toolbar的位置 direction: Direction.Top, // toolbar的位置
logic: { logic: {
dynamic: true dynamic: true,
}, },
items: [], items: [],
itemsCreator: BI.emptyFn, itemsCreator: emptyFn,
hasNext: BI.emptyFn, hasNext: emptyFn,
onLoaded: BI.emptyFn, onLoaded: emptyFn,
toolbar: { toolbar: {
type: "bi.multi_select_bar", type: "bi.multi_select_bar",
iconWrapperWidth: 36 iconWrapperWidth: 36,
}, },
el: { el: {
type: "bi.list_pane" type: "bi.list_pane",
} },
}); });
}, }
_init: function () {
BI.SelectList.superclass._init.apply(this, arguments); _init() {
var self = this, o = this.options; super._init(...arguments);
const o = this.options;
// 全选 // 全选
this.toolbar = BI.createWidget(o.toolbar); this.toolbar = createWidget(o.toolbar);
this.allSelected = false; this.allSelected = false;
this.toolbar.on(BI.Controller.EVENT_CHANGE, function (type, value, obj) { this.toolbar.on(Controller.EVENT_CHANGE, (type, value, obj) => {
self.allSelected = this.isSelected(); this.allSelected = this.toolbar.isSelected();
if (type === BI.Events.CLICK) { if (type === Events.CLICK) {
self.setAllSelected(self.allSelected); this.setAllSelected(this.allSelected);
self.fireEvent(BI.SelectList.EVENT_CHANGE, value, obj); this.fireEvent(SelectList.EVENT_CHANGE, value, obj);
} }
self.fireEvent(BI.Controller.EVENT_CHANGE, arguments); this.fireEvent(Controller.EVENT_CHANGE, arguments);
}); });
this.list = BI.createWidget(o.el, { this.list = createWidget(o.el, {
type: "bi.list_pane", type: "bi.list_pane",
items: o.items, items: o.items,
itemsCreator: function (op, callback) { itemsCreator(op, callback) {
op.times === 1 && self.toolbar.setVisible(false); op.times === 1 && this.toolbar.setVisible(false);
o.itemsCreator(op, function (items, keywords, context) { o.itemsCreator(op, (items, keywords, context, ...args) => {
callback.apply(self, arguments); callback(items, keywords, context, ...args);
if (op.times === 1) { if (op.times === 1) {
var tipText = BI.get(context, 'tipText', ''); const tipText = get(context, "tipText", "");
var visible = BI.isEmptyString(tipText) && items && items.length > 0; const visible = isEmptyString(tipText) && items && items.length > 0;
self.toolbar.setVisible(visible); this.toolbar.setVisible(visible);
self.toolbar.setEnable(self.isEnabled() && visible); this.toolbar.setEnable(this.isEnabled() && visible);
} }
self._checkAllSelected(); this._checkAllSelected();
}); });
}, },
onLoaded: o.onLoaded, onLoaded: o.onLoaded,
hasNext: o.hasNext hasNext: o.hasNext,
}); });
this.list.on(BI.Controller.EVENT_CHANGE, function (type, value, obj) { this.list.on(Controller.EVENT_CHANGE, (type, value, obj) => {
if (type === BI.Events.CLICK) { if (type === Events.CLICK) {
self._checkAllSelected(); this._checkAllSelected();
self.fireEvent(BI.SelectList.EVENT_CHANGE, value, obj); this.fireEvent(SelectList.EVENT_CHANGE, value, obj);
} }
self.fireEvent(BI.Controller.EVENT_CHANGE, arguments); this.fireEvent(Controller.EVENT_CHANGE, arguments);
}); });
BI.createWidget(BI.extend({ createWidget(extend({
element: this element: this,
}, BI.LogicFactory.createLogic(BI.LogicFactory.createLogicTypeByDirection(o.direction), BI.extend({ }, LogicFactory.createLogic(LogicFactory.createLogicTypeByDirection(o.direction), extend({
scrolly: true scrolly: true,
}, o.logic, { }, o.logic, {
items: BI.LogicFactory.createLogicItemsByDirection(o.direction, this.toolbar, this.list) items: LogicFactory.createLogicItemsByDirection(o.direction, this.toolbar, this.list),
})))); }))));
if (o.items.length <= 0) { if (o.items.length <= 0) {
this.toolbar.setVisible(false); this.toolbar.setVisible(false);
this.toolbar.setEnable(false); this.toolbar.setEnable(false);
} }
if(BI.isNotNull(o.value)){ if (isNotNull(o.value)) {
this.setValue(o.value); this.setValue(o.value);
} }
}, }
_checkAllSelected: function () { _checkAllSelected() {
var selectLength = this.list.getValue().length; const selectLength = this.list.getValue().length;
var notSelectLength = this.getAllLeaves().length - selectLength; const notSelectLength = this.getAllLeaves().length - selectLength;
var hasNext = this.list.hasNext(); const hasNext = this.list.hasNext();
var isAlreadyAllSelected = this.toolbar.isSelected(); const isAlreadyAllSelected = this.toolbar.isSelected();
var isHalf = selectLength > 0 && notSelectLength > 0; let isHalf = selectLength > 0 && notSelectLength > 0;
var allSelected = selectLength > 0 && notSelectLength <= 0 && (!hasNext || isAlreadyAllSelected); let allSelected = selectLength > 0 && notSelectLength <= 0 && (!hasNext || isAlreadyAllSelected);
if (this.isAllSelected() === false) { if (this.isAllSelected() === false) {
hasNext && (isHalf = selectLength > 0); hasNext && (isHalf = selectLength > 0);
@ -110,127 +115,125 @@ BI.SelectList = BI.inherit(BI.Widget, {
this.toolbar.setHalfSelected(isHalf); this.toolbar.setHalfSelected(isHalf);
!isHalf && this.toolbar.setSelected(allSelected); !isHalf && this.toolbar.setSelected(allSelected);
}, }
setAllSelected: function (v) { setAllSelected(v) {
if (this.list.setAllSelected) { if (this.list.setAllSelected) {
this.list.setAllSelected(v); this.list.setAllSelected(v);
} else { } else {
BI.each(this.getAllButtons(), function (i, btn) { each(this.getAllButtons(), (i, btn) => {
(btn.setSelected || btn.setAllSelected).apply(btn, [v]); (btn.setSelected || btn.setAllSelected).apply(btn, [v]);
}); });
} }
this.allSelected = !!v; this.allSelected = !!v;
this.toolbar.setSelected(v); this.toolbar.setSelected(v);
this.toolbar.setHalfSelected(false); this.toolbar.setHalfSelected(false);
}, }
setToolBarVisible: function (b) { setToolBarVisible(b) {
this.toolbar.setVisible(b); this.toolbar.setVisible(b);
}, }
isAllSelected: function () { isAllSelected() {
return this.allSelected; return this.allSelected;
// return this.toolbar.isSelected(); // return this.toolbar.isSelected();
}, }
hasPrev: function () { hasPrev() {
return this.list.hasPrev(); return this.list.hasPrev();
}, }
hasNext: function () { hasNext() {
return this.list.hasNext(); return this.list.hasNext();
}, }
prependItems: function (items) { prependItems(items) {
this.list.prependItems.apply(this.list, arguments); this.list.prependItems(...arguments);
}, }
addItems: function (items) { addItems(items) {
this.list.addItems.apply(this.list, arguments); this.list.addItems(...arguments);
}, }
setValue: function (data) { setValue(data) {
var selectAll = data.type === BI.ButtonGroup.CHOOSE_TYPE_ALL; const selectAll = data.type === ButtonGroup.CHOOSE_TYPE_ALL;
this.setAllSelected(selectAll); this.setAllSelected(selectAll);
this.list[selectAll ? "setNotSelectedValue" : "setValue"](data.value); this.list[selectAll ? "setNotSelectedValue" : "setValue"](data.value);
this._checkAllSelected(); this._checkAllSelected();
}, }
getValue: function () { getValue() {
if (this.isAllSelected() === false) { if (this.isAllSelected() === false) {
return { return {
type: BI.ButtonGroup.CHOOSE_TYPE_MULTI, type: ButtonGroup.CHOOSE_TYPE_MULTI,
value: this.list.getValue(), value: this.list.getValue(),
assist: this.list.getNotSelectedValue() assist: this.list.getNotSelectedValue(),
}; };
} }
return { return {
type: BI.ButtonGroup.CHOOSE_TYPE_ALL, type: ButtonGroup.CHOOSE_TYPE_ALL,
value: this.list.getNotSelectedValue(), value: this.list.getNotSelectedValue(),
assist: this.list.getValue() assist: this.list.getValue(),
}; };
}
}, empty() {
empty: function () {
this.list.empty(); this.list.empty();
}, }
populate: function (items) { populate(items) {
this.toolbar.setVisible(!BI.isEmptyArray(items)); this.toolbar.setVisible(!isEmptyArray(items));
this.toolbar.setEnable(this.isEnabled() && !BI.isEmptyArray(items)); this.toolbar.setEnable(this.isEnabled() && !isEmptyArray(items));
this.list.populate.apply(this.list, arguments); this.list.populate(...arguments);
this._checkAllSelected(); this._checkAllSelected();
}, }
_setEnable: function (enable) { _setEnable(enable) {
BI.SelectList.superclass._setEnable.apply(this, arguments); super._setEnable(...arguments);
this.toolbar.setEnable(enable); this.toolbar.setEnable(enable);
}, }
resetHeight: function (h) { resetHeight(h) {
var toolHeight = ( this.toolbar.element.outerHeight() || 25) * ( this.toolbar.isVisible() ? 1 : 0); const toolHeight = (this.toolbar.element.outerHeight() || 25) * (this.toolbar.isVisible() ? 1 : 0);
this.list.resetHeight ? this.list.resetHeight(h - toolHeight) : this.list.resetHeight ? this.list.resetHeight(h - toolHeight) :
this.list.element.css({"max-height": BI.pixFormat(h - toolHeight)}); this.list.element.css({ "max-height": pixFormat(h - toolHeight) });
}, }
setNotSelectedValue: function () { setNotSelectedValue() {
this.list.setNotSelectedValue.apply(this.list, arguments); this.list.setNotSelectedValue(...arguments);
this._checkAllSelected(); this._checkAllSelected();
}, }
getNotSelectedValue: function () { getNotSelectedValue() {
return this.list.getNotSelectedValue(); return this.list.getNotSelectedValue();
}, }
getAllButtons: function () { getAllButtons() {
return this.list.getAllButtons(); return this.list.getAllButtons();
}, }
getAllLeaves: function () { getAllLeaves() {
return this.list.getAllLeaves(); return this.list.getAllLeaves();
}, }
getSelectedButtons: function () { getSelectedButtons() {
return this.list.getSelectedButtons(); return this.list.getSelectedButtons();
}, }
getNotSelectedButtons: function () { getNotSelectedButtons() {
return this.list.getNotSelectedButtons(); return this.list.getNotSelectedButtons();
}, }
getIndexByValue: function (value) { getIndexByValue(value) {
return this.list.getIndexByValue(value); return this.list.getIndexByValue(value);
}, }
getNodeById: function (id) { getNodeById(id) {
return this.list.getNodeById(id); return this.list.getNodeById(id);
}, }
getNodeByValue: function (value) { getNodeByValue(value) {
return this.list.getNodeByValue(value); return this.list.getNodeByValue(value);
} }
}); }
BI.SelectList.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.select_list", BI.SelectList);

8
src/core/utils/dom.js

@ -265,12 +265,12 @@ export function _getLeftAlignPosition(combo, popup, extraWidth, container) {
} }
export function getLeftAlignPosition(combo, popup, extraWidth, container) { export function getLeftAlignPosition(combo, popup, extraWidth, container) {
let left = this._getLeftAlignPosition(combo, popup, extraWidth, container); let left = _getLeftAlignPosition(combo, popup, extraWidth, container);
let dir = ""; let dir = "";
// 如果放不下,优先使用RightAlign, 如果RightAlign也放不下, 再使用left=0 // 如果放不下,优先使用RightAlign, 如果RightAlign也放不下, 再使用left=0
const containerRect = container ? container.getBoundingClientRect() : { left: 0 }; const containerRect = container ? container.getBoundingClientRect() : { left: 0 };
if (left + containerRect.left < 0) { if (left + containerRect.left < 0) {
left = this._getRightAlignPosition(combo, popup, extraWidth); left = _getRightAlignPosition(combo, popup, extraWidth);
dir = "left"; dir = "left";
} }
if (left + containerRect.left < 0) { if (left + containerRect.left < 0) {
@ -302,11 +302,11 @@ export function _getRightAlignPosition(combo, popup, extraWidth, container) {
} }
export function getRightAlignPosition(combo, popup, extraWidth, container) { export function getRightAlignPosition(combo, popup, extraWidth, container) {
let left = this._getRightAlignPosition(combo, popup, extraWidth, container); let left = _getRightAlignPosition(combo, popup, extraWidth, container);
let dir = ""; let dir = "";
// 如果放不下,优先使用LeftAlign, 如果LeftAlign也放不下, 再使用left=0 // 如果放不下,优先使用LeftAlign, 如果LeftAlign也放不下, 再使用left=0
if (left < 0) { if (left < 0) {
left = this._getLeftAlignPosition(combo, popup, extraWidth, container); left = _getLeftAlignPosition(combo, popup, extraWidth, container);
dir = "right"; dir = "right";
} }
if (left < 0) { if (left < 0) {

Loading…
Cancel
Save