(function (Vue, axios, window) {
"use strict";
// local preserved data object - cache
var db = {
collections: [], // ["abc", "bbc", "cba"]
colorMap: {}, // {"abc": "red", "bbc": "blue", ...}
documents: {},
randomColor: function () {
return "hsl(" + Math.floor(Math.random() * 360) + ",85%,55%)";
},
_initialGetUrl: "",
initialGetUrl: function (v) {
this._initialGetUrl = v;
return this;
},
_subsequentGetUrlFn: function (name) {
return console.error(
"请先设置subsequentGetUrlFn函数!! \n" +
"该函数接收一个collectionName参数\n" +
"通过这个参数返回获取documents列表的url地址。"
);
},
subsequentGetUrlFn: function (fn) {
this._subsequentGetUrlFn = fn;
return this;
},
// _failedFn will be excuted on intialGetUrl or subsequentGetUrl failed.
_failedFn: function (error) {
console.log(error);
},
failedFn: function (fn) {
this._failedFn = fn;
return this;
},
_successFn: function () {
console.error("请配置成功返回函数,先。");
},
successFn: function (fn) {
this._successFn = fn;
return this;
},
run: function () {
var self = this;
axios
.get(this._initialGetUrl)
.then(function (raw) {
var data = raw.data;
self.collections = data.collections;
data.collections.forEach(function (name, index) {
self.colorMap[name] = data.colors[index] || self.randomColor();
});
self.documents[data.doc.name] = {
items: data.doc.items,
querys: []
};
self._successFn(data);
})
.catch(this._failedFn);
}
};
// ------------- 高级检索初始化参数.xlsx -------------
// typeMap shared by queryGroup component and Vue instance as computer property
var typeMap = (function () {
//
var maps = [
{value: ">", text: ">"},
{value: "<", text: "<"},
{value: "=", text: "="},
{value: ">=", text: ">="},
{value: "<=", text: "<="}
];
var textMaps = [
{value: "同义包含", text: "同义包含"},
{value: "绝对包含", text: "绝对包含"},
{value: "以XXX开头", text: "以XXX开头"},
{value: "以XXX结尾", text: "以XXX结尾"},
{value: "等于", text: "等于"}
];
function checkFn (reg) {
return function (text) {
text = text.trim();
if (!text.length) { return false; }
return !!reg.exec(text);
};
}
function checkEmpty (text) {
text = text.trim();
return !!text.length;
}
//
function getPlaceholder (type) {
switch (type) {
case "0": return "整型: 123";
case "1": return "浮点型: 0.23";
case "3": return "日期: 2019-08-01";
}
return "字符串: xxx";
}
//
function getClass (type) {
switch (type) {
case "0": return {"bg-integer": true};
case "1": return {"bg-float": true};
//case "3": return {"bg-date": true};
case "2":
case "4":
case "9":
return {"bg-string": true};
}
return {};
}
return {
"0": { // 整形
maps: maps,
relation: "and", /* default relation value */
logic: ">", /* default logic value */
check: checkFn(/^[+-]?[1-9]+\d?$/)
},
"1": { // 浮点型
maps: maps,
relation: "and",
logic: ">",
check: checkFn(/^[+-]?\d+(\.\d+)?$/)
},
"3": { // 日期: 2019-10-21
maps: maps,
relation: "and",
logic: ">",
check: checkFn(/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/)
},
"2": {
maps: textMaps,
relation: "and",
logic: "同义包含",
check: checkEmpty
},
"4": {
maps: textMaps.slice(1),
relation: "and",
logic: "绝对包含",
check: checkEmpty
},
"9": {
maps: textMaps.slice(1),
relation: "and",
logic: "绝对包含",
check: checkEmpty
},
getPlaceholder: getPlaceholder,
getClass: getClass
};
})();
var uuid = (function () {
var i = 0;
return function () { return "i" + i++; };
})();
Vue.config.devtools = false;
var collectionGroup = {
props: ["options"],
template: `
`
};
var documentGroup = {
props: ["documents"],
data: function () {
return {
filterString: ""
};
},
watch: {
documents: function () {
this.filterString = "";
}
},
computed: {
filteredOptions: function () {
var f = this.filterString.trim().toUpperCase();
if (!f.length) { return this.documents; }
return this.documents.filter(function (d) {
return d.name.toUpperCase().indexOf(f) > -1;
});
}
},
methods: {
onClick: function (name, type) {
this.$emit("add-query", name, type);
}
},
template: `
`
};
var queryGroup = {
props: ["items"],
data: function () {
return {
dy: 0, // offset y between mouse and tRow on mousedown event
rowTop: 0, // tRow initial y position
rowHeight: 0, // tRow height
// used for tedecting if the dragNode touch ceilling or floor
// if it does readjust scroll property
minY: 0,
maxY: 0,
// rowIndex on dragBefore and dragAfter
indexBefore: 0,
indexAfter: 0,
// table row node
dragNode: null,
// max scollable value if overflowed
scrollSapn: 0
};
},
computed: {
map: function () { return typeMap; }
},
methods: {
onChange: function (id, key, $event) { // for select controls
this.$emit("update-query", id, key, $event.target.value);
},
onTextChange: function (id, key, type, $event) { // for input controls
var value = $event.target.value.trim();
$event.target.value = value;
this.$emit("update-query", id, key, value);
if (this.map[type].check(value)) {
$event.target.classList.remove("border-danger");
}
else {
$event.target.classList.add("border-danger");
}
},
// following mouse event only fired on dragging behaviour
onMousedown: function (q, $event) {
// div.fix-table>table>tbody>tr>td>span.fix-table__handler
var row = $event.target.parentElement.parentElement,
div = this.$el, //row.closest(".fix-table"),
rowBBox = row.getBoundingClientRect(),
divBBox = div.getBoundingClientRect();
this.dragNode = row;
this.indexBefore = row.rowIndex;
this.indexAfter = row.rowIndex;
// Geometry calculation
this.rowTop = rowBBox.top;
this.rowHeight = rowBBox.height;
this.dy = $event.clientY - rowBBox.top;
this.minY = divBBox.top + div.querySelector("thead").getBoundingClientRect().height;
this.maxY = divBBox.bottom;
this.scrollSapn = div.scrollHeight - div.clientHeight;
row.style.backgroundColor = "lightgreen";
window.addEventListener("mousemove", this.onMousemove);
window.addEventListener("mouseup", this.onMouseup);
},
onMousemove: function (evt) {
evt.preventDefault();
var py = evt.clientY - this.dy;
var pre = this.dragNode.previousElementSibling,
next = this.dragNode.nextElementSibling;
if (pre && pre.getBoundingClientRect().top > py) {
pre.insertAdjacentElement("beforebegin", this.dragNode);
this.rowTop = this.dragNode.getBoundingClientRect().top;
}
else if (next && next.getBoundingClientRect().top < py) {
next.insertAdjacentElement("afterend", this.dragNode);
this.rowTop = this.dragNode.getBoundingClientRect().top;
}
py = evt.clientY - this.dy;
this.indexAfter = this.dragNode.rowIndex;
this.dragNode.style.transform = "translate(0px," + (py - this.rowTop) + "px)";
// auto scroll behaviour
if (py < this.minY && this.$el.scrollTop > 0) {
this.$el.scrollTop -= 10;
}
if ((py + this.rowHeight) > this.maxY && this.$el.scrollTop < this.scrollSapn) {
this.$el.scrollTop += 10;
}
},
onMouseup: function (evt) {
this.dragNode.style= "";
this.dragNode = null;
window.removeEventListener("mousemove", this.onMousemove);
window.removeEventListener("mouseup", this.onMouseup);
if (this.indexBefore !== this.indexAfter) {
this.$emit("move-query", this.indexBefore - 1, this.indexAfter - 1);
}
},
// this method is fired by parent Vue instance
validate: function () {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
// Input controls validate one by one,
// "change" event refer to component template
var inputs = this.$el.querySelectorAll("input");
for (var i = 0, len = inputs.length; i < len; i++) {
// invalid input will give a border-danger class on change event
inputs[i].dispatchEvent(evt);
}
// invalide input will be fenced with red border if any.
// Find one of them and shake this component to alert user.
var invalid = this.$el.querySelector(".border-danger");
if (invalid) {
invalid.scrollIntoView({ behavior: "smooth", block: "center" });
this.$el.classList.add("animated", "shake");
return false;
}
// all input control validate checking passed
return true;
}
},
template: `
`
};
var vm = new Vue({
// customed properties
confirmCallbackFn: null,
data: {
currentCollection: "", // EXPORT
collections: [],
documents: [],
queryItems: [],
errorMsg: "",
state: "insert" // insert/update "插入节点" | "更新节点"
},
computed: {
map: function () { return typeMap; },
db: function () { return db; }
},
watch: {
currentCollection: function (newColl, oldColl) {
var doc = this.db.documents, self = this;
// currentCollectin is switch to newColl
this.preservDataToDB(oldColl);
if (doc[newColl]) {
this.$set(this.$data, "documents", doc[newColl].items);
this.$set(this.$data, "queryItems", doc[newColl].querys);
}
else {
this.$set(this.$data, "documents", []);
this.$set(this.$data, "queryItems", []);
axios.get(this.db._subsequentGetUrlFn(newColl))
.then(function (d) {
doc[d.data.name] = {
items: d.data.items,
querys: []
};
self.$set(self.$data, "documents", doc[newColl].items);
self.$set(self.$data, "queryItems", doc[newColl].querys);
})
.catch(this.db._failedFn);
}
}
},
methods: {
addQuery: function (name, type) {
this.queryItems.unshift({
id: this.uuid(),
type: type,
name: name,
relation: this.map[type].relation,
logic: this.map[type].logic,
query: ""
});
},
findQuery: function (id) {
return this.queryItems.findIndex(function (item) {
return item.id == id;
});
},
updateQuery: function (id, key, value) {
var index = this.findQuery(id);
this.queryItems[index][key] = value;
},
removeQuery: function (id) {
var index = this.findQuery(id);
this.queryItems.splice(index, 1);
},
moveQuery: function (fromIndex, toIndex) {
var i = fromIndex, j = toIndex,
node = this.queryItems[fromIndex];
if (fromIndex < toIndex) {
while (i < j) {
this.queryItems[i] = this.queryItems[i + 1];
i++;
}
}
else {
while (i > j) {
this.queryItems[i] = this.queryItems[i - 1];
i--;
}
}
// Trigger reactive feature and place the item at right index
this.queryItems.splice(toIndex, 1, node);
//console.log(this.queryItems.map(d => d.name));
},
uuid: uuid,
toExport: function (collectionName) {
if (!collectionName) { collectionName = this.currentCollection; }
return {
collection: this.currentCollection,
color: this.db.colorMap[collectionName],
querys: this.queryItems.map(function (d) {
return {
id : d.id,
name : d.name,
type : d.type,
relation: d.relation,
logic : d.logic,
query : d.query
};
})
};
},
// preserve data action only when close dialoge or collection switch
preservDataToDB: function (collectionName) {
if (!this.queryItems.length) { return; }
if (!collectionName) { collectionName = this.currentCollection; }
this.db.documents[collectionName].querys = this.toExport(collectionName).querys;
},
// Ref: _animation.scss
onAnimationEnd: function (evt) {
evt.target.classList.remove("animated", "shake", "bounceInDown", "bounceInUp", "bounceOutDown");
},
onEscPressed: function (evt) {
if (evt.key.toUpperCase() === "ESCAPE") {
this.close();
}
},
show: function (callbackFn, bind) {
window.addEventListener("keydown", this.onEscPressed);
this.errorMsg = "";
this.state = "insert";
if (callbackFn) {
this.confirmCallbackFn = callbackFn;
}
if (bind) {
this.currentCollection = bind.collection;
this.$set(this.$data, "queryItems", bind.querys);
this.state = "update";
}
this.$el.style.display = "block";
document.body.classList.add("modal-open");
this.$el.querySelector(".modal-dialog")
.classList.add("animated", "bounceInDown");
},
close: function () {
window.removeEventListener("keydown", this.onEscPressed);
this.$el.style.display = "none";
document.body.classList.remove("modal-open");
this.confirmCallbackFn = null;
this.preservDataToDB(); // finally preserve data
},
confirm: function () {
this.errorMsg = "";
// 1) filter items must not less than 1
if (!this.queryItems.length) {
this.errorMsg = "请点击左侧列表,添加筛选条件到右侧。";
this.$refs.query.$el.classList.add("animated", "shake");
return;
}
// 2) query string is required
if (!this.$refs.query.validate()) {
this.errorMsg = "输入不得为空,且必须按照指定数据类型输入数据。";
return;
}
// 3) get all information then fire callback if any
if (this.confirmCallbackFn) {
this.confirmCallbackFn(this.toExport());
}
// 4) close dialoge finnaly
this.close();
}
},
components: {
collectionGroup: collectionGroup,
documentGroup: documentGroup,
queryGroup: queryGroup
},
template: `
`
});
window.vm = vm;
})(Vue, axios, window);