Qlik Sense学习笔记之Mashup开发(二)

时间:2023-03-08 19:17:43
Qlik Sense学习笔记之Mashup开发(二)

date: 2019-01-26 11:28:07

updated: 2019-01-26 11:28:07

Qlik Sense学习笔记之Mashup开发(二)

1.Mobile SPA UI Framework

可自动侦测IOS和Andriod等设备,自动适应设备的不同相应模式

基于 Qlik Sense 自身的一个UI,不需要引入相关的 js、css 文件,只需要保证使用的类是一致的,就可以得到和 Qlik Sense 一样的UI样式,主要是用来开发 Extensions

2.实战

2.1 从零到一
2.1.1 建立 Mashup Project

方法一:

本地Qlik Desktop 中打开Dev Hub -> Mashup Editor -> Basic Template,之后在我的电脑 -> 文档 -> Qlik -> Extensions 中可以找到刚才创建的模板,然后复制整个文件夹到WebStorm或IDEA的workplace下打开。

方法二:

打开Dev Hub -> Mashup Editor -> Basic Template,之后在服务器端下载刚刚创建的模板,会自动打包成zip文件,然后复制整个文件夹到WebStorm或IDEA的workplace下打开。

2.1.2 配置数据
  1. 安装sense-go

    https://github.com/stefanwalther/sense-go

     cnpm install -g sense-go
    使用 -g 设置sense-go到全局变量 只需安装这一次

    报错:

     F:\IDEA Workspace\mxxct_demo>npm install -g sense-go
    npm ERR! code 1
    npm ERR! Command failed: F:\Git\cmd\git.EXE checkout 4.0
    npm ERR! error: pathspec '4.0' did not match any file(s) known to git
    npm ERR! npm ERR! A complete log of this run can be found in:
    npm ERR! C:\Users\Lenovo\AppData\Roaming\npm-cache\_logs\2019-01-17T13_39_08_763Z-debug.log

    更新安装方式:

     在 C 盘下新建一个文件夹,命名为 patch,并将 sense-go.zip 复制到该文件夹下,
    解压缩到 sense-go 文件夹,执行命名 npm install sense-go -g C:\patch\sense-go
  2. 新建配置文件

    打开上面网址,将 sense-go/lib/default-config.yml 中的内容复制下来,在自己项目的根目录下新建一个 yml 文件,文件命名为 .sense-go.yml (注意最前面有一个点),并将内容拷贝进去。

  3. 修改项目目录结构

    sense-go官方文档推荐的项目结构如下:

     | PROJECT-ROOT
    |-- build <= all builds, including source code or zipped files
    |-- dev <= target for the development build
    |-- release <= target for the release build
    |-- docs <= documentation files, then used by verb
    |-- src <= all source files
    |-- lib
    |-- css <= see below *
    |-- less <= less files
    | .sense-go.yml <= sense-go configuration file (OPTIONAL)
    | .verb.md <= verbs readme template
    | package.json build文件夹会自动生成,不需要关心
    docs文件夹下的文件可以自己管理一下,但是 sense-go 本身不会过多的关心这个目录下的内容
    src文件夹存放的是所有的源代码,需要自己创建 lib、css等文件夹
  4. 配置 yml 文件

    • 修改 packgeName,值为项目名称

    • 可以添加一下 version 信息

    • 修改 deployment -> toLocal -> enabled 为true,这样就可以使用本地所创建的APP,并把本地 Extensions 路径复制到 localExtensionsBaseDir 下

        deployment:
      toLocal:
      enabled: true
      pathFetching: true # By default the path will be automatically fetched. localExtensionsBaseDir: "C:/Users/Lenovo/Documents/Qlik/Sense/Extensions"
      注意,要把路径中的 '\' 换成 '/' !!!
      # defaults to the local extension directory of Qlik Sense,
      # if pathFetching is enabled, this path will be determined automatically extensionDirOverride: # Define the extensionDir if you want to deploy to another directory than defined in packageName
      qrs: # Not implemented, yet
      enabled: false
      url: null
      toSsh: # Upload via SSH
      enabled: false
      host: "192.168.56.11"
      port: 22
      username: "usr"
      password: "foobar"
      dest: "/c/Users/usr/Documents/Qlik/Sense/Extensions/whatever-extension"
      viaShell:
      enabled: false
    • qrs:在项目发布的时候会同步推到Qlik Sense服务器上,尽量不要将 enabled 置为true,可能会导致服务器压力过大

    • 修改 lessReduce 中的配置

        lessReduce:
      # src: "./src/lib/less/main.less"
      src: "./src/css/less/main.less"
      # 这里 main.less 和项目中的文件名必须一致,要不然会找不到文件导致生成的 build 目录下没有 css 样式文件
      # dest: "./.tmp/lib/css"
      dest: "./.tmp/css"
  5. 添加 main.less 和 qlik.less 文件在 src/lib/css/less 文件夹下

    less 文件夹如果不需要是可以不去配置的,不过因为 less 文件夹会在 build 过程中经过一次编译,并且可以设置一些常用变量,所以配置一下有利于开发。

    在 main.less 中添加以下代码,引入 qlik.less文件

     @import "qlik";

    并且将 mxxct_demo.css 文件中的内容拷贝到 qlik.less 文件中,其中的内容是一些 qlik 默认模板的一些样式设置,然后可以将 mxxct_demo.css 文件删除。

  6. 删除 wbfolder.wbl 文件,其保存的是当前项目中的文件列表,不过是默认的文件列表,所以并没有什么用

  7. 修改 mxxct_demo.html 中对 mxxct_demo.js 和 mxxct_demo.css的引用路径,其中 mxxct_demo.css 需要改成 main.less,同时 css 文件夹只有在 build 的时候才会生成,所以这里的路径应该修改为生成 build 之后的文件路径

  8. 输入命令 sense-go build 来自动生成项目,生成的 build 文件夹会出现在本地 Qlik/Extensions/mxxct_demo 文件夹下,同时会同步在 mashup 当中,可以在 mashup 中点击预览查看,也可以输入 localhost:8848/extensions/mxxct_demo/mxxct_demo.html 进行预览

  9. 输入命令 sense-go watch:build 来自动监测 .sense-go.yml 从而自动生成 build 文件夹

2.1.3 Hello Qlik
  1. 在 mxxct_demo.js 文件中,引入 APP

     var app = qlik.openApp("XXX.qvf",config)
    app.getObject("div的id,比如QV01","object的id");
    如果需要调用两个服务器上的APP,那么就需要配置两个 config,以及两个 require.config,一般来说是不太需要这种情况的。
    XXX 指的是本地 Qlik/Sense/Apps 下的文件名称,是从QMC中export出来的文件名(仅限于本地运行)
    当放到服务器上的时候需要将 XXX.qvf 换成服务器上打开 App 时 url 后的字符串,不带qvf,qvf指的是本地运行的文件。
    所以尽量写一个 json 文件或者配置文件来配置变量,在上线的时候只需要修改配置即可
  2. 查看官方API插入到 mxxct_demo.js 文件中来使用

2.1.4 加入类库
  1. 第一种方法是静态加载:直接在 mxxct_demo.html 文件中添加,以OnsenUI为例,将对应的js、css文件放到 lib 文件夹下

     <script src="lib/onsen.ui.js"></script>
  2. 第二种方法是动态加载:首先在 lib 文件夹下创建一个 mxxct_lib.js 文件,然后在 mxxct_demo.js 文件夹中的 require 函数那里,添加在 "js/qlik" 之后,逗号隔开,并且在后面的 function 那里写出对应的引用变量名称。要注意的是,由于 require 已经修改了自己的一个 baseUrl 地址,所以在引用的时候需要使用绝对路径而不能是相对路径,不然会找到文件。

     var prefix = window.location.pathname.substr( 0, window.location.pathname.toLowerCase().lastIndexOf( "/extensions" ) + 1 );
    var config = {
    host: window.location.hostname,
    prefix: prefix,
    port: window.location.port,
    isSecure: window.location.protocol === "https:"
    }; // 在这里默认会把 require.js 中的 baseUrl 给修改掉,所以下面需要改成绝对路径
    require.config( {
    baseUrl: ( config.isSecure ? "https://" : "http://" ) + config.host + (config.port ? ":" + config.port : "") + config.prefix + "resources"
    } ); // 在 require 中添加引用是不需要在文件后添加 .js 的
    // 将 require 调整成如下格式,方便查看
    require( [
    "js/qlik",
    "../extensions/mxxct_demo/lib/mxxct_lib"
    ], function (
    qlik,
    mxxctLib, // 这里设置的引用名称是不支持带 '-' '_' 之类的
    ) {
    qlik.setOnError( function ( error ) {
    $( '#popupText' ).append( error.message + "<br>" );
    $( '#popup' ).fadeIn( 1000 );
    } );
    $( "#closePopup" ).click( function () {
    $( '#popup' ).hide();
    } ); //callbacks -- inserted here --
    //open apps -- inserted here --
    //get objects -- inserted here --
    //create cubes and lists -- inserted here --
    } );

    每引用一个文件,就需要写一次绝对路径,会略显麻烦,所以可以通过以下方法:引用某一个主 lib.js 文件,然后通过这个文件去引用其他的 js 文件。

    在 mxxct_lib.js 文件中添加如下代码

     alert("调用mxxct_lib.js文件成功");
    
     // 这里的 define 函数类似于 mxxct_demo.js 文件中的 require 函数,需要一个数组存放调用的依赖,再加上一个回调函数
    define([
    "./sub_lib1", // 因为 sub_lib1.js 相对于 mxxct_lib.js 文件是在同一级文件夹下,所以不需要再写绝对路径,通过 ./ 写一个相对路径(当前文件夹下的 sub_lib1.js文件)即可,注意不需要写出来最后的 .js,不然会找不到文件
    ],function(
    subLib1
    ){
    subLib1.whoAmI();
    });

    在 sub_lib1.js 文件中添加如下代码

     define([],function(){
    return{
    whoAmI:function () {
    alert("mxxct");
    console.log("调用sub_lib1.js文件")
    }
    }
    });

    Qlik Sense学习笔记之Mashup开发(二)

  3. 保证类名一样的情况下,使用 Leonnardo UI 可以直接获得和 Qlik Sense 中一样的样式

2.2 渐入佳境

首先需要获取到对应的 APP 的appid

打开 Qlik Desktop -> Dev Hub -> 修改 url 中的 dev-hub 为 hub -> 打开 app -> 打开其中一个子 app -> 在 url 的最后添加 /options/developer -> 右键 object 选择 Developer,可以直接查看到 objectID

2.2.1 动态切换 Object
  1. 在获取 Object 的时候,必须要有高度,尽量不要用 min-height,没有高度会不显示,但是可以没有宽度,但是如果要保证换行效果的话,需要加上宽度限制,不然切换到手机屏幕的时候宽度会根据图表的宽度来自动匹配,有可能会导致失去图表效果

  2. getObject() 的呈现效果和 visualization.get() 的呈现效果是一样的,都是获取 object 并且呈现,但是 visualization.get() 如果不 show 的话,vis 会一直存在,并且 vis 保存的是整个 object 的一个 datamodal 形式,可以查看到 object 的相关信息,比如图表的类型,组合模式,标题等等,在 layout -- qHyperCube 下,可以查看 object 的数据内容,可以单独拿出这部分的数据来单独呈现或生成表格等等。

     var app = qlik.openApp('xxxName.qvf',config);
    app.getObject('QV01', 'TT'); var app = qlik.openApp('xxxName.qvf',config);
    app.visualization.get('TT').then(function(vis){
    vis.show("QV01");
    });
  3. 动态切换

方式一:

button 后面添加函数:onclick="window.fn.getFirst()",需要把函数写到静态中,然后在主JS文件中写入函数体。

方式二:

通过 jQuery 来绑定 button 的点击事件

var app = qlik.openApp('xxxName.qvf',config);

var chart = [];
var promises = []; promises.push(app.visualization.get('TT').then(function(vis){
// vis.show("QV01");
// 返回 promise 的处理结果,但是不知道处理结果是成功还是失败
return Promise.resolve(chart.push(vis));
})); promises.push(app.visualization.get('EJpVXek').then(function(vis){
// vis.show("QV02");
return Promise.resolve(chart.push(vis));
})); $("#btn1").on("click",function () {
$("#btn1").addClass("lui-button--info");
$("#btn2").removeClass("lui-button--info");
chart[0].show("QV01");
}); $("#btn2").on("click",function () {
$("#btn1").removeClass("lui-button--info");
$("#btn2").addClass("lui-button--info");
chart[1].show("QV01");
}); // 先把所有的 button 都置为不可选
// 当且仅当 promises 中的所有处理结果都存放进来了,即所有获取数据的函数都已经走完了,再执行以下函数
Promise.all(promises).then(function () {
chart[0].show("QV01");
$("#btn1").removeAttr("disabled");
$("#btn2").removeAttr("disabled");
}); // 需要注意的是,如果在获取 'TT'、'EJpVXek' 之间也存在因为数据量而产生先后顺序错乱的问题,可以固定数组下标来保证先后顺序一致,推荐修改为下面的形式: var app = qlik.openApp('xxxName.qvf',config); var chart = [];
var promises = []; promises.push(app.visualization.get('TT').then(function(vis){
// vis.show("QV01");
// 返回 promise 的处理结果,但是不知道处理结果是成功还是失败
return Promise.resolve(vis);
})); promises.push(app.visualization.get('EJpVXek').then(function(vis){
// vis.show("QV02");
return Promise.resolve(vis);
})); // 先把所有的 button 都置为不可选
// 当且仅当 promises 中的所有处理结果都存放进来了,即所有获取数据的函数都已经走完了,再执行以下函数
// 变量 chart 里面的内容可能是异步的,但是通过 Promise.all(promises) 函数对 promises 会再做一次同步操作,会将 chart 的顺序和 promises 的顺序保持一致
Promise.all(promises).then(function (chart) {
chart[0].show("QV01"); $("#btn1").on("click",function () {
$("#btn1").addClass("lui-button--info");
$("#btn2").removeClass("lui-button--info");
chart[0].show("QV01");
}); $("#btn2").on("click",function () {
$("#btn1").removeClass("lui-button--info");
$("#btn2").addClass("lui-button--info");
chart[1].show("QV01");
}); $("#btn1").removeAttr("disabled");
$("#btn2").removeAttr("disabled");
});
2.2.2 Selection 与 Clear
  1. 在筛选项中有一个比较有意思的点,就是可以通过 API 来设置某几个图表或者筛选项的 Alternate State,通过这个 State 的值可以将某些图表和筛选项固定住,比如原先所有的数据都会造成整个 APP 的数据联动,现在要看1月份和2月份的一个饼图对比,在设置了 State 的值之后,这两个饼图的数据不会联动,只会各自根据筛选项进行联动,在保证饼图的算法是一致的情况下,可以获得对照的一个效果。

  2. clearAll() 是对整个 APP 来清楚筛选项,所以是 app.clearAll(),Selection 是对某个 Filed 来进行选择,所以是 app.field("name").select([0, 1, 2], true, true)

  3. qlik.app.field.select(Array, toggle, softlock)

    • toggle:true 表示之前做的选择会累加不会被清除掉,false 表示之前做的选择全部清除只保留本次的选择,选择 true 的时候如果一个选择 button 点两次会恢复到初始状态,需要根据情况来决定

    • softlock:如果是 true,之前锁住的选择会被覆写掉

  4. 查看要选择的 field 中的数据

     var field = app.field('实体公司');
    field.getData();
    console.log(field);
  5. 选择最大值

     $("#btn6").on("click",function () {
    // app.field("实体公司").select([0], true, true);
    var field = app.field('实体公司');
    field.getData();
    // console.log(field);
    // 因为获取数据的问题,刚点击的时候,field.getDdata() 还没获取到数据的时候就已经进行选择了,为了避免这个问题就要加一个延时
    setTimeout(function () {
    var res = getMaxValueIndex(field.rows);
    app.field("实体公司").selectValues(res, false, true);
    },500); //field.getData();
    //after data is loaded fieldvalues will be in rows array
    //field.rows[0].select();
    }); function getMaxValueIndex(paramArray) {
    console.log(paramArray); var names = [];
    var tmpMaxValue = Number.MIN_VALUE;
    for(var i = 0; i < paramArray.length; i++){
    if(Number(paramArray[i].qFrequency) > tmpMaxValue){
    tmpMaxValue = Number(paramArray[i].qFrequency);
    names = [];
    names.push(paramArray[i].qText);
    }else if(Number(paramArray[i].qFrequency) == tmpMaxValue){
    names.push(paramArray[i].qText);
    }
    }
    console.log(names);
    return names;
    }
2.2.3 变量控制
  1. Qlik 中数据大多以 ( num,string ) 的方式存在,通过 app.variable.setStringValue(qName, qVal) 和 app.variable.setNumValue(qName, qVal) 这两个方法可以对任意一端进行赋值,Qlik 会自动对另一端的值进行改动,不需要再去手动修改

  2. 当 APP 中同一个 objectID 被三个 button 复用来进行筛选,比如 "30天内"、"90天内"、"全部",那么就可以通过 app.variable.setStringValue("qName","qValue") 来进行筛选

     $("#btn7").on("click",function () {
    app.variable.setStringValue("vTimeRanges","in30days");
    $("#btn7").addClass("lui-button--info");
    $("#btn8").removeClass("lui-button--info");
    $("#btn9").removeClass("lui-button--info");
    });
  3. 获取变量中的值,通过 app.variable.getContent() 来获取,并且需要有一个异步函数调用的过程,来对获取到的值进行处理

     app.variable.getContent('MYVAR',function ( reply ) {
    alert( JSON.stringify( reply ) );
    } );
2.3 深入浅出
2.3.1 Hypercube
  1. Qlik 中所有的数据存储方式都是 qHyperCube,不再是数据表中的二维形式。

  2. 表格的数据是动态加载的,当用户在向下滑动表格的时候,会不断去获取数据动态加载到表格中,其他的 object 是静态加载到前台页面的。

  3. 通过

     app.visualization.get("id").then(function(vis){
    console.log(vis);
    });

    来输出 dataModal,在 modal --> layout --> qHyperCube --> qDataPages --> qMatrix 下可以找到当前 object 的所有数据

    通过 qHyperCube --> qGrandTotalRow 可以得到这个 object 某个维度的总数

    通过 qHyperCube --> qDimensionInfo 可以得到这个 object 某个维度的描述和字段值

  4. 如果只是从 qHyperCube 中获取到某些值,展示出来的值是静态的,需要再添加数据联动的效果。

2.3.2 数据联动
  1. 通过 vis.table.OnData.bind(listener) 方法来把 vis 的值绑定到 object 上去,但是会遇到一个问题,就是如果这个 object 之前没有被 show 过的话,是无法联动的,所以需要在 .html 中添加一个 style="display:none" 的一个 div,用来存放需要被联动的 kpi

  2. 具体代码 copy from demo.js

     // 在 demo.js 中先添加下面的赋值函数
    function renderKpi(kpi) {
    $("#QV02").text(kpi.model.layout.qHyperCube.qDataPages[0].qMatrix[0][0].qText);
    $("#QV03").text(kpi.model.layout.qHyperCube.qDataPages[0].qMatrix[0][1].qText);
    $("#QV05").text(kpi.model.layout.title).css("background-color", kpi.model.layout.subtitle);
    } // kpi
    // 在获取到 kpi 之后,将 vis 的值 render 到 object
    promises.push(app.visualization.get('UtxwSV').then(function (vis) {
    console.log(vis); var listener = function () {
    renderKpi(vis); // 分页的时候,避免内存溢出将 vis 关闭并且解绑 listener
    // vis.close();
    // vis.table.OnData.unbind(listener);
    }; vis.table.OnData.bind(listener);
    return Promise.resolve(vis);
    })); // 需要在 demo.html 中添加一个 style="display:none" 的 div,为了让 object 先 show 一下,但是需要隐藏掉,因为不是为了查看某个 object
    <div id="cachePool" style="display:none">
    <div id="QV04"></div>
    </div> // 在 demo.js 中的 Promise.all() 函数中添加以下部分
    Promise.all(promises).then(function (charts) { // 将 object 先 show 一下
    charts[2].show("QV04"); // 在第一次获取到数据之后把 kpi 放到 div 中
    var kpi = charts[2];
    renderKpi(kpi); // Selection 的侦听
    var selState = app.selectionState();
    var listener = function () {
    if (selState.selections.length > 0) {
    $("#QV06").text(selState.selections[0].qSelected);
    } };
    selState.OnData.bind(listener);
    });

    如果联动特别多,那么每一个 visualization 都需要绑定一个 listener,所以最好把需要联动的数据集中在一个 object 中,一次性获取到所有的需要联动数据

2.3.3 Selection的侦听
  1. 查看上方代码,需要注意的是,在 chart 上一次性最多选择6个,大于6个就会显示 n of totalNum(n > 6),所以需要显示更多的选项,就需要去添加一个筛选下拉列表,然后侦听筛选器的选中状态
2.4 更上层楼
2.4.1 Object 预读
  1. 预读其实就相当于把 object 先放到了 promise 里面,当所有的 object 都已经放到 promise 中之后,再
2.4.2 Config JSON
  1. 把所有的静态变量写在一个 config 文件中,然后在 js 文件中通过 ajax 来调用

     $.ajax({
    'type': 'get',
    'url': "config/system.json",
    }).done(function (reply) {
    ...
    })
2.4.3 SPA 的实现
  1. 如果是多个页面进行跳转的话,server 端需要不断提供新的链接信息,socket、session等等,所以 Qlik 推荐使用 SPA 的方式来实现 mashup,可以通过对 div 的 show / hide 或者通过框架对页面重新渲染等
2.5 登峰造极
2.5.1 Websocket 与 Engine API
  1. 辅助工具 socket.io
2.5.2 QRS API 的应用
2.5.3 综合应用

3.DEMO