文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。文档对象模型 (DOM) 是对HTML文件的另一种展示,通俗地说,一个HTML 文件,我们可以用编辑器以代码的形式展示它,也可以用浏览器以页面的形式展示它,同一份文件通过不同的展示方式,就有了不一样的表现形式。而DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来,我们可以使用脚本或者程序语言通过DOM 来改变或者控制web页面。
我们可以通过JavaScript 来调用document和window元素的API来操作文档或者获取文档的信息。
Node 是一个接口,有许多接口都从Node 继承方法和属性:Document, Element, CharacterData (which Text, Comment, and CDATASection inherit), ProcessingInstruction, DocumentFragment, DocumentType, Notation, Entity, EntityReference。Node 有一个nodeType的属性表示Node 的类型,是一个整数,不同的值代表不同的节点类型。具体如下表所示:
节点类型常量
已弃用的节点类型常量
假设我们要判断一个Node 是不是一个元素,通过查表可知元素的nodeType属性值为1,代码可以这么写:
复制代码if(X.nodeType===1){
console.log('X 是一个元素');
}
在Node 类型中,比较常用的就是element,text,comment,document,document_fragment这几种类型。
Element提供了对元素标签名,子节点和特性的访问,我们常用HTML元素比如div,span,a等标签就是element中的一种。Element有下面几条特性:(1)nodeType为1(2)nodeName为元素标签名,tagName也是返回标签名(3)nodeValue为null(4)parentNode可能是Document或Element(5)子节点可能是Element,Text,Comment,Processing_Instruction,CDATASection或EntityReference
Text表示文本节点,它包含的是纯文本内容,不能包含html代码,但可以包含转义后的html代码。Text有下面的特性:(1)nodeType为3(2)nodeName为#text(3)nodeValue为文本内容(4)parentNode是一个Element(5)没有子节点
Comment表示HTML文档中的注释,它有下面的几种特征:(1)nodeType为8(2)nodeName为#comment(3)nodeValue为注释的内容(4)parentNode可能是Document或Element(5)没有子节点
Document表示文档,在浏览器中,document对象是HTMLDocument的一个实例,表示整个页面,它同时也是window对象的一个属性。Document有下面的特性:(1)nodeType为9(2)nodeName为#document(3)nodeValue为null(4)parentNode为null(5)子节点可能是一个DocumentType或Element
DocumentFragment是所有节点中唯一一个没有对应标记的类型,它表示一种轻量级的文档,可能当作一个临时的仓库用来保存可能会添加到文档中的节点。DocumentFragment有下面的特性:(1)nodeType为11(2)nodeName为#document-fragment(3)nodeValue为null(4)parentNode为null
用如其名,这类API是用来创建节点的
createElement通过传入指定的一个标签名来创建一个元素,如果传入的标签名是一个未知的,则会创建一个自定义的标签,注意:IE8以下浏览器不支持自定义标签。
语法
复制代码 let element=document.createElement(tagName);
使用createElement要注意:通过createElement创建的元素并不属于HTML文档,它只是创建出来,并未添加到HTML文档中,要调用appendChild或insertBefore等方法将其添加到HTML文档树中。
例子:
复制代码 let elem=document.createElement("div");
elem.id='test';
elem.style='color: red';
elem.innerHTML='我是新创建的节点';
document.body.appendChild(elem);
运行结果为:
createTextNode用来创建一个文本节点
语法
复制代码 var text=document.createTextNode(data);
createTextNode接收一个参数,这个参数就是文本节点中的文本,和createElement一样,创建后的文本节点也只是独立的一个节点,同样需要appendChild将其添加到HTML文档树中
例子:
复制代码 var node=document.createTextNode("我是文本节点");
document.body.appendChild(node);
运行结果为:
cloneNode返回调用该方法的节点的一个副本
语法
复制代码 var dupNode=node.cloneNode(deep);
node 将要被克隆的节点dupNode 克隆生成的副本节点deep(可选)是否采用深度克隆,如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身.
这里有几点要注意:(1)和createElement一样,cloneNode创建的节点只是游离有HTML文档外的节点,要调用appendChild方法才能添加到文档树中(2)如果复制的元素有id,则其副本同样会包含该id,由于id具有唯一性,所以在复制节点后必须要修改其id(3)调用接收的deep参数最好传入,如果不传入该参数,不同浏览器对其默认值的处理可能不同
注意如果被复制的节点绑定了事件,则副本也会跟着绑定该事件吗?这里要分情况讨论:(1)如果是通过addEventListener或者比如onclick进行绑定事件,则副本节点不会绑定该事件(2)如果是内联方式绑定比如:<div onclick="showParent()"></div>,这样的话,副本节点同样会触发事件。
例子:
复制代码<body>
<div id="parent">
我是父元素的文本
<br/>
<span>
我是子元素
</span>
</div>
<button id="btnCopy">复制</button>
</body>
<script>
var parent=document.getElementById("parent");
document.getElementById("btnCopy").onclick=function(){
var parent2=parent.cloneNode(true);
parent2.id="parent2";
document.body.appendChild(parent2);
}
</script>
运行结果为:
DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(reflow)(对元素位置和几何上的计算)。因此,使用文档片段document fragments 通常会起到优化性能的作用。
语法
复制代码 let fragment=document.createDocumentFragment();
例子:
复制代码<body>
<ul id="ul"></ul>
</body>
<script>
(function()
{
var start=Date.now();
var str='', li;
var ul=document.getElementById('ul');
var fragment=document.createDocumentFragment();
for(var i=0; i<1000; i++)
{
li=document.createElement('li');
li.textContent='第'+(i+1)+'个子节点';
fragment.appendChild(li);
}
ul.appendChild(fragment);
})();
</script>
运行结果为:
节点创建型API主要包括createElement,createTextNode,cloneNode和createDocumentFragment四个方法,需要注意下面几点:(1)它们创建的节点只是一个孤立的节点,要通过appendChild添加到文档中(2)cloneNode要注意如果被复制的节点是否包含子节点以及事件绑定等问题(3)使用createDocumentFragment来解决添加大量节点时的性能问题
前面我们提到节点创建型API,它们只是创建节点,并没有真正修改到页面内容,而是要调用·appendChild·来将其添加到文档树中。我在这里将这类会修改到页面内容归为一类。修改页面内容的api主要包括:appendChild,insertBefore,removeChild,replaceChild。
appendChild我们在前面已经用到多次,就是将指定的节点添加到调用该方法的节点的子元素的末尾。
语法
复制代码 parent.appendChild(child);
child节点将会作为parent节点的最后一个子节点。appendChild这个方法很简单,但是还有有一点需要注意:如果被添加的节点是一个页面中存在的节点,则执行后这个节点将会添加到指定位置,其原本所在的位置将移除该节点,也就是说不会同时存在两个该节点在页面上,相当于把这个节点移动到另一个地方。如果child绑定了事件,被移动时,它依然绑定着该事件。
例子:
复制代码<body>
<div id="child">
要被添加的节点
</div>
<br/>
<br/>
<br/>
<div id="parent">
要移动的位置
</div>
<input id="btnMove" type="button" value="移动节点" />
</body>
<script>
document.getElementById("btnMove").onclick=function(){
var child=document.getElementById("child");
document.getElementById("parent").appendChild(child);
}
</script>
运行结果:
insertBefore用来添加一个节点到一个参照节点之前
语法
复制代码 parentNode.insertBefore(newNode,refNode);
parentNode表示新节点被添加后的父节点newNode表示要添加的节点refNode表示参照节点,新节点会添加到这个节点之前
例子:
复制代码<body>
<div id="parent">
父节点
<div id="child">
子元素
</div>
</div>
<input type="button" id="insertNode" value="插入节点" />
</body>
<script>
var parent=document.getElementById("parent");
var child=document.getElementById("child");
document.getElementById("insertNode").onclick=function(){
var newNode=document.createElement("div");
newNode.textContent="新节点"
parent.insertBefore(newNode,child);
}
</script>
运行结果:
关于第二个参数参照节点还有几个注意的地方:(1)refNode是必传的,如果不传该参数会报错(2)如果refNode是undefined或null,则insertBefore会将节点添加到子元素的末尾
删除指定的子节点并返回
语法
复制代码 var deletedChild=parent.removeChild(node);
deletedChild指向被删除节点的引用,它等于node,被删除的节点仍然存在于内存中,可以对其进行下一步操作。注意:如果被删除的节点不是其子节点,则程序将会报错。我们可以通过下面的方式来确保可以删除:
复制代码if(node.parentNode){
node.parentNode.removeChild(node);
}
运行结果:
通过节点自己获取节点的父节点,然后将自身删除
replaceChild用于使用一个节点替换另一个节点
语法
复制代码 parent.replaceChild(newChild,oldChild);
newChild是替换的节点,可以是新的节点,也可以是页面上的节点,如果是页面上的节点,则其将被转移到新的位置oldChild是被替换的节点
例子:
复制代码<body>
<div id="parent">
父节点
<div id="child">
子元素
</div>
</div>
<input type="button" id="insertNode" value="替换节点" />
</body>
<script>
var parent=document.getElementById("parent");
var child=document.getElementById("child");
document.getElementById("insertNode").onclick=function(){
var newNode=document.createElement("div");
newNode.textContent="新节点"
parent.replaceChild(newNode,child)
}
运行结果:
页面修改型API主要是这四个接口,要注意几个特点:(1)不管是新增还是替换节点,如果新增或替换的节点是原本存在页面上的,则其原来位置的节点将被移除,也就是说同一个节点不能存在于页面的多个位置(2)节点本身绑定的事件会不会消失,会一直保留着。
这个接口很简单,根据元素id返回元素,返回值是Element类型,如果不存在该元素,则返回null
语法
复制代码 var element=document.getElementById(id);
使用这个接口有几点要注意:(1)元素的Id是大小写敏感的,一定要写对元素的id(2)HTML文档中可能存在多个id相同的元素,则返回第一个元素(3)只从文档中进行搜索元素,如果创建了一个元素并指定id,但并没有添加到文档中,则这个元素是不会被查找到的
例子:
复制代码<body>
<p id="para1">Some text here</p>
<button onclick="changeColor('blue');">blue</button>
<button onclick="changeColor('red');">red</button>
</body>
<script>
function changeColor(newColor) {
var elem=document.getElementById("para1");
elem.style.color=newColor;
}
</script>
运行结果:
返回一个包括所有给定标签名称的元素的HTML集合HTMLCollection。 整个文件结构都会被搜索,包括根节点。返回的 HTML集合是动态的, 意味着它可以自动更新自己来保持和 DOM 树的同步而不用再次调用document.getElementsByTagName()
语法
复制代码 var elements=document.getElementsByTagName(name);
(1)如果要对HTMLCollection集合进行循环操作,最好将其长度缓存起来,因为每次循环都会去计算长度,暂时缓存起来可以提高效率(2)如果没有存在指定的标签,该接口返回的不是null,而是一个空的HTMLCollection(3)name是一个代表元素的名称的字符串。特殊字符 "*" 代表了所有元素。
例子:
复制代码<body>
<div>div1</div>
<div>div2</div>
<input type="button" value="显示数量" id="btnShowCount"/>
<input type="button" value="新增div" id="btnAddDiv"/>
</body>
<script>
var divList=document.getElementsByTagName("div");
document.getElementById("btnAddDiv").onclick=function(){
var div=document.createElement("div");
div.textContent="div" + (divList.length+1);
document.body.appendChild(div);
}
document.getElementById("btnShowCount").onclick=function(){
alert(divList.length);
}
</script>
这段代码中有两个按钮,一个按钮是显示HTMLCollection元素的个数,另一个按钮可以新增一个div标签到文档中。前面提到HTMLCollcetion元素是即时的表示该集合是随时变化的,也就是是文档中有几个div,它会随时进行变化,当我们新增一个div后,再访问HTMLCollection时,就会包含这个新增的div。
运行结果:
getElementsByName主要是通过指定的name属性来获取元素,它返回一个即时的NodeList对象
语法
复制代码 var elements=document.getElementsByName(name)
使用这个接口主要要注意几点:(1)返回对象是一个即时的NodeList,它是随时变化的(2)在HTML元素中,并不是所有元素都有name属性,比如div是没有name属性的,但是如果强制设置div的name属性,它也是可以被查找到的(3)在IE中,如果id设置成某个值,然后传入getElementsByName的参数值和id值一样,则这个元素是会被找到的,所以最好不好设置同样的值给id和name
例子:
复制代码<script type="text/javascript">
function getElements()
{
var x=document.getElementsByName("myInput");
alert(x.length);
}
</script>
<body>
<input name="myInput" type="text" size="20" /><br />
<input name="myInput" type="text" size="20" /><br />
<input name="myInput" type="text" size="20" /><br />
<br />
<input type="button" onclick="getElements()" value="How many elements named 'myInput'?" />
</body>
运行结果:
这个API是根据元素的class返回一个即时的HTMLCollection
语法
复制代码 var elements=document.getElementsByClassName(names); // or:
var elements=rootElement.getElementsByClassName(names);
这个接口有下面几点要注意:(1)返回结果是一个即时的HTMLCollection,会随时根据文档结构变化(2)IE9以下浏览器不支持(3)如果要获取2个以上classname,可传入多个classname,每个用空格相隔,例如
复制代码 var elements=document.getElementsByClassName("test1 test2");
例子:
复制代码 var elements=document.getElementsByClassName('test');
复制代码 var elements=document.getElementsByClassName('red test');
复制代码 var elements=document.getElementById('main').getElementsByClassName('test');
复制代码 var testElements=document.getElementsByClassName('test');
var testDivs=Array.prototype.filter.call(testElements, function(testElement){
return testElement.nodeName==='DIV';;
});
这两个API很相似,通过css选择器来查找元素,注意选择器要符合CSS选择器的规则
document.querySelector返回第一个匹配的元素,如果没有匹配的元素,则返回null
语法
复制代码 var element=document.querySelector(selectors);
注意,由于返回的是第一个匹配的元素,这个api使用的深度优先搜索来获取元素。
例子:
复制代码<body>
<div>
<div>
<span class="test">第三级的span</span>
</div>
</div>
<div class="test">
同级的第二个div
</div>
<input type="button" id="btnGet" value="获取test元素" />
</body>
<script>
document.getElementById("btnGet").addEventListener("click",function(){
var element=document.querySelector(".test");
alert(element.textContent);
})
</script>
两个class都包含“test”的元素,一个在文档树的前面,但是它在第三级,另一个在文档树的后面,但它在第一级,通过querySelector获取元素时,它通过深度优先搜索,拿到文档树前面的第三级的元素。运行结果:
语法
复制代码 var elementList=document.querySelectorAll(selectors);
例子:
复制代码 var matches=document.querySelectorAll("div.note, div.alert");
返回一个文档中所有的class为"note"或者"alert"的div元素
复制代码<body>
<div class="test">
class为test
</div>
<div id="test">
id为test
</div>
<input id="btnShow" type="button" value="显示内容" />
</body>
<script>
document.getElementById("btnShow").addEventListener("click",function(){
var elements=document.querySelectorAll("#test,.test");
for(var i=0,length=elements.length;i<length;i++){
alert(elements[i].textContent);
}
})
</script>
这段代码通过querySelectorAll,使用id选择器和class选择器选择了两个元素,并依次输出其内容。要注意两点:(1)querySelectorAll也是通过深度优先搜索,搜索的元素顺序和选择器的顺序无关(2)返回的是一个非即时的NodeList,也就是说结果不会随着文档树的变化而变化兼容性问题:querySelector和querySelectorAll在ie8以下的浏览器不支持。
运行结果:
在html文档中的每个节点之间的关系都可以看成是家谱关系,包含父子关系,兄弟关系等等
每个节点都有一个parentNode属性,它表示元素的父节点。Element的父节点可能是Element,Document或DocumentFragment
返回元素的父元素节点,与parentNode的区别在于,其父节点必须是一个Element,如果不是,则返回null
返回一个即时的NodeList,表示元素的子节点列表,子节点可能会包含文本节点,注释节点等
一个即时的HTMLCollection,子节点都是Element,IE9以下浏览器不支持children属性为只读属性,对象类型为HTMLCollection,你可以使用elementNodeReference.children[1].nodeName来获取某个子元素的标签名称
只读属性返回树中节点的第一个子节点,如果节点是无子节点,则返回 null
返回当前节点的最后一个子节点。如果父节点为一个元素节点,则子节点通常为一个元素节点,或一个文本节点,或一个注释节点。如果没有子节点,则返回null
返回一个布尔值,表明当前节点是否包含有子节点.
返回当前节点的前一个兄弟节点,没有则返回nullGecko内核的浏览器会在源代码中标签内部有空白符的地方插入一个文本结点到文档中.因此,使用诸如Node.firstChild和Node.previousSibling之类的方法可能会引用到一个空白符文本节点, 而不是使用者所预期得到的节点
previousElementSibling返回当前元素在其父元素的子元素节点中的前一个元素节点,如果该元素已经是第一个元素节点,则返回null,该属性是只读的。注意IE9以下浏览器不支持
Node.nextSibling是一个只读属性,返回其父节点的childNodes列表中紧跟在其后面的节点,如果指定的节点为最后一个节点,则返回nullGecko内核的浏览器会在源代码中标签内部有空白符的地方插入一个文本结点到文档中.因此,使用诸如Node.firstChild和Node.previousSibling之类的方法可能会引用到一个空白符文本节点, 而不是使用者所预期得到的节点
nextElementSibling返回当前元素在其父元素的子元素节点中的后一个元素节点,如果该元素已经是最后一个元素节点,则返回null,该属性是只读的。注意IE9以下浏览器不支持
设置指定元素上的一个属性值。如果属性已经存在,则更新该值; 否则将添加一个新的属性用指定的名称和值
语法
复制代码 element.setAttribute(name, value);
其中name是特性名,value是特性值。如果元素不包含该特性,则会创建该特性并赋值。
例子:
复制代码<body>
<div id="div1">ABC</div>
</body>
<script>
let div1=document.getElementById("div1");
div1.setAttribute("align", "center");
</script>
运行结果:
如果元素本身包含指定的特性名为属性,则可以世界访问属性进行赋值,比如下面两条代码是等价的:
复制代码 element.setAttribute("id","test");
element.id="test";
getAttribute()返回元素上一个指定的属性值。如果指定的属性不存在,则返回null或""(空字符串)
语法
复制代码 let attribute=element.getAttribute(attributeName);
attribute是一个包含attributeName属性值的字符串。attributeName是你想要获取的属性值的属性名称
例子:
复制代码<body>
<div id="div1">ABC</div>
</body>
<script>
let div1=document.getElementById("div1");
let align=div1.getAttribute("align");
alert(align);
</script>
运行结果:
removeAttribute()从指定的元素中删除一个属性
语法
复制代码 element.removeAttribute(attrName)
attrName是一个字符串,将要从元素中删除的属性名
例子:
复制代码<body>
<div id="div1" style="color:red" width="200px">ABC
</div>
</body>
<script>
let div=document.getElementById("div1")
div.removeAttribute("style");
</script>
在运行之前div有个style="color:red"的属性,在运行之后这个属性就被删除了
运行结果:
Window.getComputedStyle()方法给出应用活动样式表后的元素的所有CSS属性的值,并解析这些值可能包含的任何基本计算假设某个元素并未设置高度而是通过其内容将其高度撑开,这时候要获取它的高度就要用到getComputedStyle
语法
复制代码 var style=window.getComputedStyle(element[, pseudoElt]);
element是要获取的元素,pseudoElt指定一个伪元素进行匹配。返回的style是一个CSSStyleDeclaration对象。通过style可以访问到元素计算后的样式
getBoundingClientRect用来返回元素的大小以及相对于浏览器可视窗口的位置
语法
复制代码 var clientRect=element.getBoundingClientRect();
clientRect是一个DOMRect对象,包含left,top,right,bottom,它是相对于可视窗口的距离,滚动位置发生改变时,它们的值是会发生变化的。除了IE9以下浏览器,还包含元素的height和width等数据
例子:
复制代码 elem.style.color='red';
elem.style.setProperty('font-size', '16px');
elem.style.removeProperty('color');
例子:
复制代码 var style=document.createElement('style');
style.innerHTML='body{color:red} #top:hover{background-color: red;color: white;}';
document.head.appendChild(style););
JavaScript中的API太多了,将这些API记住并熟练使用对JavaScript的学习是有很大的帮助
作者:yyzclyang
链接:https://juejin.cn/post/6844903604445249543
数字化转型潮流席卷各大行业的今天,越来越多的企业开始重视 BI(商业智能)技术的部署和应用,期望从不断增长的数据资源中获得更多业务价值,从而提升利润、控制风险、降低成本。BI 能整合、组织和分析数据,将数据转化为有价值的信息,为企业管理和决策提供支持,成为企业迎接变革和商业创新的决胜因素。
由于 BI 技术的重要性,企业更希望在现有的业务平台和系统中按需集成BI能力,从而在各类场景中充分发挥数据分析带来的优势,满足企业日益多样化的数据分析诉求,使 BI 能力与企业业务深度融合。然而,市面上常见的 BI 工具大都是独立、打包的整体方案,很难与前端的业务系统集成在一起,在实践中常常无法满足需求。在这样的背景下,嵌入式 BI 应运而生。
所谓嵌入式 BI,就是在企业现有业务系统中按需集成各种类型的数据分析能力。这种集成工作一般需要考虑两个要点:一方面,它本质上是现有业务系统的一次升级过程,需要关注升级内容与原系统的兼容性、稳定性、安全性等指标;另一方面,业务侧一般希望深度集成专业的数据分析组件,而不是任意挂载一个简单的模块应付了事。这两个要点为开发团队提出了更高的要求和挑战,需要团队认真对待。
嵌入式数据分析模块架构探索
对于很多中小企业而言,软件开发团队并不具备独立开发 In-House 嵌入 BI 方案的能力,需要寻求外部第三方供应商的支持。行业中也出现了很多专业的外部供应商,他们探索出了一些经过市场验证的嵌入式 BI 最佳实践。我们以业内老牌知名企业葡萄城开发的 Wyn 商业智能嵌入式架构为例,探讨数据分析模块应该如何嵌入现有业务系统:
如上所示,对于业务人员来说,应用功能层的数据分析仪表板、图表、设计器、门户等模块,就是嵌入式 BI 方案展现出来的最终效果。其中,模块的内容和形态一般是根据业务需求决定的,例如为某个销售看板集成一些销售数据动态图表,实时反映各地区的当前销售状况。由于业务需求往往比较多样化,嵌入模块的内容和形态也非常多变,这就要求前端技术层具备更强的适应能力。
前端技术层在过去普遍采用 URL iFrame 架构来实现模块嵌入,如今更复杂的需求更多用 DIV 方式打造解决方案。此外,以 Wyn 商业智能为例,其 BI 模块还可以同泛微、用友 U8+、企业微信和钉钉等常用的企业信息系统集成,增强它们的数据分析能力。
API 层对于嵌入式 BI 方案是非常重要的。例如,API 允许根据用户类型打开和关闭工具栏,只允许根据使用规则显示指定的数据源,并支持创建具有不同过滤器和选项的仪表板。专业的嵌入式BI可以通过调用 API,在应用软件内对仪表板/报表进行权限管理、分类管理、重命名、删除等深度集成操作,而应用软件和 BI 软件之间的接口对最终用户是完全透明的。当然,对于较为简单的业务需求而言,嵌入式 BI 架构中取消 API 层,或者只有简单的 API 层也是可以接受的。
主流实现方案对比
如上所述,对于开发团队而言,嵌入式 BI 方案的技术选型关键在于 DIV 和IFrame 架构的选择,以及是否加入强大的 API 层。
IFrame 架构在早期的嵌入式 BI 市场非常流行,因其原理简单、实现方便、开发周期较短,使企业能够快速实现初期的嵌入式 BI 需求。但这种方式虽然简单,局限性也是很大的。例如,使用 IFrame 就很难在系统中深度集成数据分析模块。IFrame 更像曾经的 Flash 元素,是一种相对独立的模块。它与页面的其他元素很难融合和互动,即便可以实现也需要付出大量努力和代价。
相比之下,基于 JavaScript 的 DIV 层级的无缝嵌入方案,可以利用原生的JavaScript 将整个仪表板等以 DIV 的方式集成到项目中。嵌入的图表元素具有高度开放的接口,能够与其他元素进行数据交互。甚至 BI 软件整体都可以通过DIV 架构直接嵌入到现有系统中,确保了无缝和直观的用户体验。即便当前的业务需求仅仅停留在简单的图表展示层面,考虑到未来的升级和扩展潜力,开发团队还是最好选择 DIV 架构来设计 BI 嵌入方案。
另一方面,API 层能够大大简化业务人员对嵌入式 BI 模块的操作,往往是开发团队需要重点实现的功能目标。Wyn 商业智能的嵌入式 BI 方案就提供了GraphQL API,几乎所有界面操作均可通过 API 调用简单完成。下图就是一个简单的 API 调用示例:
GraphQL API 不需要为不同的对象操作提供不同的 URL。不同对象的不同操作均通过一个统一的 URL(http://localhost:51980/api/graphql)调用,只是各类操作提交的数据不同。可以看到,GraphQL API 的操作非常易于上手,可以大大方便开发团队和业务团队,满足各类复杂的业务需求。下面来看 Wyn 商业智能提供的一个数据查询 API 的操作示例,从中可以体会 API 的低门槛与便利性:
当我们需要调用某个数据集,可以通过该 API 以 POST 或 GET 两种方式完成操作。
(示例 URL 为http://10.32.5.7:51980/api/v1/datasource/b7d93485-66f2-4356-ada5-0a163579232d/query?queryType=sql&query=select *from Categories&token=27487CA0BE14CF599444E8553E5E07F78D5D1AB8646302A2715E4802FCB95F08&format=json;调用数据集的 URL 格式为 POST/GET api/v1/dataset/{document id}/query。)
POST 方式,有效负载格式:
{
"QueryType": "NONE|WAX",
"Query": "some text like a WAX statement or empty"
"DatasetParameters":{
"Parameter1": "ParameterValue1"(单值),
"Parameter2": "ParameterValue1,ParameterValue2" (多值使用逗号分隔)
},
"Format":Arrow | Json,
"Options":{
"Parameter1":"Value1"
}
}
GET 方式,查询参数
?format=Arrow | Json
&$parameter1=value1
&$parameter2=value2_1
&$parameter2=value2_2
&option1=value1
&option2=value2
option1, option2 ...为选项参数,前缀$表示数据集参数,多个值通过多次重复一个参数来表示。Option 选项参数具体信息如下:
只需几行简单的代码即可完成数据集的调用操作,这对嵌入式 BI 场景而言无疑是非常有价值的。API 层与 DIV 嵌入结合,可以为嵌入式 BI 场景需求提供令人满意的解决方案。
总体而言,虽然 iFrame 架构在入门门槛、开发成本和周期方面具有一定优势,但随着企业数据分析需求愈加复杂,DIV 架构很快就能表现出更强的扩展能力和适应性。团队可以在初期选择 iFrame 实现,并在需求提升后迁移到 DIV 方案。与此同时,开发团队往往在一开始就要考虑 API 层的实现,为业务团队带来更多便利,并为后期的开发工作打好基础。
嵌入式 BI 选型:如何挑选最适合自己的方案?
企业开发团队在选择嵌入式 BI 方案时,除了关注方案的开发周期、开发难度外,一般还要考虑定价模型、云端支持、业务系统集成支持等要素:
考虑到以上要素,实践中中小企业和开发团队更适合选择成熟的第三方嵌入式 BI 方案来满足自身需求。以 Wyn 商业智能提供的嵌入 BI 方案为例,其不仅提供了完善的功能、基于 GraphQL 的便捷 API 集成,还支持强大的授权管理、增强安全特性,兼容多种云端部署模式,且能够方便地集成到用友、企业微信等系统中。该方案既可以嵌入部署也可以独立使用,具备良好的伸缩能力,帮助企业持续深度挖掘数据价值。想要进一步了解关于嵌入式 BI 方案的相关内容,可以点击以下链接获取更多信息。
者:前端Q
转发链接:https://mp.weixin.qq.com/s/ewFfXptccFs5KvjUINLGbQ
小试牛刀,实现了六款简单常见HTML5 Canvas特效滤镜,并且封装成一个纯JavaScript可调用的API文件gloomyfishfilter.js。支持的特效滤镜分别为:
1.反色
2.灰色调
3.模糊
4.浮雕
5.雕刻
6.合理
2.灰色调:获取一个预期点RGB值r,g,b则新的RGB值
newr=(r * 0.272)+(g * 0.534)+(b * 0.131);
newg=(r * 0.349)+(g * 0.686)+(b * 0.168);
newb=(r * 0.393)+(g * 0.769)+(b * 0.189);
3.模糊:基于一个5 * 5的卷积核
4.浮雕与雕刻:
根据当前预期的前一个预期RGB值与它的后一个重新的RGB值之差再加上128
5.总体:模拟了物体在镜子中与之对应的效果。
var canvas=document.getElementById("target");
canvas.width=source.clientWidth;
canvas.height=source.clientHeight;
**if**(!canvas.getContext) {
console.log("Canvas not supported. Please install a HTML5compatible browser.");
**return**;
}
// get 2D context of canvas and draw image
tempContext=canvas.getContext("2d");
var source=document.getElementById("source");
tempContext.drawImage(source, 0, 0, canvas.width,canvas.height);
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData=canvasData.data;
function bindButtonEvent(element, type, handler)
{
if(element.addEventListener){
element.addEventListener(type, handler,**false**);
}else{
element.attachEvent('on'+type, handler);// for IE6,7,8
}
}
<scriptsrc=*"gloomyfishfilter.js"*></script> //导入API文件
gfilter.colorInvertProcess(binaryData, len); //调用 API
<meta http-equiv="X-UA-Compatible"*content=*"chrome=IE8">
效果演示:
CSS部分:
#svgContainer {
width:800px;
height:600px;
background-color:#EEEEEE;
}
#sourceDiv { float: left; border: 2px solid blue}
#targetDiv { float: right;border: 2px solid red}
filter1.html中HTML源代码:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=IE8">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Canvas Filter Demo</title>
<link href="default.css" rel="stylesheet" />
<script src="gloomyfishfilter.js"></scrip>
</head>
<body>
<h1>HTML Canvas Image Process - By Gloomy Fish</h1>
<div id="svgContainer">
<div id="sourceDiv">
<img id="source" src="../test.png" />
</div>
<div id="targetDiv">
<canvas id="target"></canvas>
</div>
</div>
<div id="btn-group">
<button type="button" id="invert-button">反色</button>
<button type="button" id="adjust-button">灰色调</button>
<button type="button" id="blur-button">模糊</button>
<button type="button" id="relief-button">浮雕</button>
<button type="button" id="diaoke-button">雕刻</button>
<button type="button" id="mirror-button">镜像</button>
</div>
</body>
</html>
filter1.html中JavaScript源代码:
var tempContext=null; // global variable 2d context
window.onload=function() {
var source=document.getElementById("source");
var canvas=document.getElementById("target");
canvas.width=source.clientWidth;
canvas.height=source.clientHeight;
if (!canvas.getContext) {
console.log("Canvas not supported. Please install a HTML5 compatible browser.");
return;
}
// get 2D context of canvas and draw image
tempContext=canvas.getContext("2d");
tempContext.drawImage(source, 0, 0, canvas.width, canvas.height);
// initialization actions
var inButton=document.getElementById("invert-button");
var adButton=document.getElementById("adjust-button");
var blurButton=document.getElementById("blur-button");
var reButton=document.getElementById("relief-button");
var dkButton=document.getElementById("diaoke-button");
var mirrorButton=document.getElementById("mirror-button");
// bind mouse click event
bindButtonEvent(inButton, "click", invertColor);
bindButtonEvent(adButton, "click", adjustColor);
bindButtonEvent(blurButton, "click", blurImage);
bindButtonEvent(reButton, "click", fudiaoImage);
bindButtonEvent(dkButton, "click", kediaoImage);
bindButtonEvent(mirrorButton, "click", mirrorImage);
}
function bindButtonEvent(element, type, handler)
{
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
element.attachEvent('on'+type, handler); // for IE6,7,8
}
}
function invertColor() {
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData=canvasData.data;
// Processing all the pixels
gfilter.colorInvertProcess(binaryData, len);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function adjustColor() {
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
var binaryData=canvasData.data;
// Processing all the pixels
gfilter.colorAdjustProcess(binaryData, len);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function blurImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.blurProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function fudiaoImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.reliefProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function kediaoImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.diaokeProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
function mirrorImage()
{
var canvas=document.getElementById("target");
var len=canvas.width * canvas.height * 4;
var canvasData=tempContext.getImageData(0, 0, canvas.width, canvas.height);
// Processing all the pixels
gfilter.mirrorProcess(tempContext, canvasData);
// Copying back canvas data to canvas
tempContext.putImageData(canvasData, 0, 0);
}
滤镜源代码(gloomyfishfilter.js):
var gfilter={
type: "canvas",
name: "filters",
author: "zhigang",
getInfo: function () {
return this.author + ' ' + this.type + ' ' + this.name;
},
/**
* invert color value of pixel, new pixel=RGB(255-r, 255-g, 255 - b)
*
* @param binaryData - canvas's imagedata.data
* @param l - length of data (width * height of image data)
*/
colorInvertProcess: function(binaryData, l) {
for (var i=0; i < l; i +=4) {
var r=binaryData[i];
var g=binaryData[i + 1];
var b=binaryData[i + 2];
binaryData[i]=255-r;
binaryData[i + 1]=255-g;
binaryData[i + 2]=255-b;
}
},
/**
* adjust color values and make it more darker and gray...
*
* @param binaryData
* @param l
*/
colorAdjustProcess: function(binaryData, l) {
for (var i=0; i < l; i +=4) {
var r=binaryData[i];
var g=binaryData[i + 1];
var b=binaryData[i + 2];
binaryData[i]=(r * 0.272) + (g * 0.534) + (b * 0.131);
binaryData[i + 1]=(r * 0.349) + (g * 0.686) + (b * 0.168);
binaryData[i + 2]=(r * 0.393) + (g * 0.769) + (b * 0.189);
}
},
/**
* deep clone image data of canvas
*
* @param context
* @param src
* @returns
*/
copyImageData: function(context, src)
{
var dst=context.createImageData(src.width, src.height);
dst.data.set(src.data);
return dst;
},
/**
* convolution - keneral size 5*5 - blur effect filter(模糊效果)
*
* @param context
* @param canvasData
*/
blurProcess: function(context, canvasData) {
console.log("Canvas Filter - blur process");
var tempCanvasData=this.copyImageData(context, canvasData);
var sumred=0.0, sumgreen=0.0, sumblue=0.0;
for ( var x=0; x < tempCanvasData.width; x++) {
for ( var y=0; y < tempCanvasData.height; y++) {
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
for(var subCol=-2; subCol<=2; subCol++) {
var colOff=subCol + x;
if(colOff <0 || colOff >=tempCanvasData.width) {
colOff=0;
}
for(var subRow=-2; subRow<=2; subRow++) {
var rowOff=subRow + y;
if(rowOff < 0 || rowOff >=tempCanvasData.height) {
rowOff=0;
}
var idx2=(colOff + rowOff * tempCanvasData.width) * 4;
var r=tempCanvasData.data[idx2 + 0];
var g=tempCanvasData.data[idx2 + 1];
var b=tempCanvasData.data[idx2 + 2];
sumred +=r;
sumgreen +=g;
sumblue +=b;
}
}
// calculate new RGB value
var nr=(sumred / 25.0);
var ng=(sumgreen / 25.0);
var nb=(sumblue / 25.0);
// clear previous for next pixel point
sumred=0.0;
sumgreen=0.0;
sumblue=0.0;
// assign new pixel value
canvasData.data[idx + 0]=nr; // Red channel
canvasData.data[idx + 1]=ng; // Green channel
canvasData.data[idx + 2]=nb; // Blue channel
canvasData.data[idx + 3]=255; // Alpha channel
}
}
},
/**
* after pixel value - before pixel value + 128
* 浮雕效果
*/
reliefProcess: function(context, canvasData) {
console.log("Canvas Filter - relief process");
var tempCanvasData=this.copyImageData(context, canvasData);
for ( var x=1; x < tempCanvasData.width-1; x++)
{
for ( var y=1; y < tempCanvasData.height-1; y++)
{
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
var bidx=((x-1) + y * tempCanvasData.width) * 4;
var aidx=((x+1) + y * tempCanvasData.width) * 4;
// calculate new RGB value
var nr=tempCanvasData.data[aidx + 0] - tempCanvasData.data[bidx + 0] + 128;
var ng=tempCanvasData.data[aidx + 1] - tempCanvasData.data[bidx + 1] + 128;
var nb=tempCanvasData.data[aidx + 2] - tempCanvasData.data[bidx + 2] + 128;
nr=(nr < 0) ? 0 : ((nr >255) ? 255 : nr);
ng=(ng < 0) ? 0 : ((ng >255) ? 255 : ng);
nb=(nb < 0) ? 0 : ((nb >255) ? 255 : nb);
// assign new pixel value
canvasData.data[idx + 0]=nr; // Red channel
canvasData.data[idx + 1]=ng; // Green channel
canvasData.data[idx + 2]=nb; // Blue channel
canvasData.data[idx + 3]=255; // Alpha channel
}
}
},
/**
* before pixel value - after pixel value + 128
* 雕刻效果
*
* @param canvasData
*/
diaokeProcess: function(context, canvasData) {
console.log("Canvas Filter - process");
var tempCanvasData=this.copyImageData(context, canvasData);
for ( var x=1; x < tempCanvasData.width-1; x++)
{
for ( var y=1; y < tempCanvasData.height-1; y++)
{
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
var bidx=((x-1) + y * tempCanvasData.width) * 4;
var aidx=((x+1) + y * tempCanvasData.width) * 4;
// calculate new RGB value
var nr=tempCanvasData.data[bidx + 0] - tempCanvasData.data[aidx + 0] + 128;
var ng=tempCanvasData.data[bidx + 1] - tempCanvasData.data[aidx + 1] + 128;
var nb=tempCanvasData.data[bidx + 2] - tempCanvasData.data[aidx + 2] + 128;
nr=(nr < 0) ? 0 : ((nr >255) ? 255 : nr);
ng=(ng < 0) ? 0 : ((ng >255) ? 255 : ng);
nb=(nb < 0) ? 0 : ((nb >255) ? 255 : nb);
// assign new pixel value
canvasData.data[idx + 0]=nr; // Red channel
canvasData.data[idx + 1]=ng; // Green channel
canvasData.data[idx + 2]=nb; // Blue channel
canvasData.data[idx + 3]=255; // Alpha channel
}
}
},
/**
* mirror reflect
*
* @param context
* @param canvasData
*/
mirrorProcess : function(context, canvasData) {
console.log("Canvas Filter - process");
var tempCanvasData=this.copyImageData(context, canvasData);
for ( var x=0; x < tempCanvasData.width; x++) // column
{
for ( var y=0; y < tempCanvasData.height; y++) // row
{
// Index of the pixel in the array
var idx=(x + y * tempCanvasData.width) * 4;
var midx=(((tempCanvasData.width -1) - x) + y * tempCanvasData.width) * 4;
// assign new pixel value
canvasData.data[midx + 0]=tempCanvasData.data[idx + 0]; // Red channel
canvasData.data[midx + 1]=tempCanvasData.data[idx + 1]; ; // Green channel
canvasData.data[midx + 2]=tempCanvasData.data[idx + 2]; ; // Blue channel
canvasData.data[midx + 3]=255; // Alpha channel
}
}
},
};
感谢阅读,如果你觉得我今天分享的内容,不错,请点一个赞,谢谢!!
*请认真填写需求信息,我们会在24小时内与您取得联系。