Loading

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文件")
             }
         }
     });
    

  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

posted @ 2020-10-21 16:52  猫熊小才天  阅读(517)  评论(0编辑  收藏  举报