День 3. Расширение №2 "Кнопки", а также Qlik API + CSS
Знакомство с Qlik API для разработки расширений
Qlik API - это набор "рычагов", которым Qlik позволяет с собой взаимодействовать. Иначе говоря, это набор методов, с помощью которых мы можем из кода запрашивать дополнительные данные, выставлять фильтры, переходить по листам и т.д. Qlik API делится на подкатегории, в зависимости от назначения.Qlik API - очень обширная тема, существует более 20 видов API в Qlik Sense, подробно прочитать о которых можно в Qlik Help.

Ну а мы сейчас пробежимся по основным вещам, которые нам нужно понимать для разработки расширений.
Qlik API Insights: При выходе новых релизов Qlik Sense случаются и изменения в Qlik API.Лучшее место, чтобы отслеживать изменения в Qlik API: Qlik API Insights. Там можно посмотреть изменения в Qlik API по версиям и конкретным API (напомню, их более 20), а также функции конкретного API, в зависимости от версии Qlik Sense - по сути, это все, что нужно, чтобы понимать работоспособность вашего расширения, использующего API.
Фильтры
Backend API даёт возможность работать с фильтрами. Важные нам методы:
  • hasSelections - позволяет узнать, стоят ли какие-нибудь фильтры:
this.backendApi.hasSelections();
this - ключевое слово в JS, ссылающееся на разные объекты, в зависимости от того, где его вызовешь. Нас интересует this из функции paint() - он ссылается на объект Qlik Sense (если мы обратимся к this в другой функции, то это уже будет другой объект). Чтобы получить "наш" this сохраним его в переменную self.
var self = this.


  • selectValues - метод установки фильтров. Он выбирает значения [value] (это всегда массив, т.к. значений можно выставить несколько) в измерении с индексом dim, true. Это работает так: значения будут переключаться так, что:

    • если это значение не было выбрано, оно выберется,
    • если это значение было выбрано, то наоборот фильтр с него убирается (toggle mode).
    self.backendApi.selectValues(dim, [value], true)
    Метод selectValues есть и в Extension API. Но их отличие в том, что для Extension API пользователь должен подтвердить выборку
    а через backendApi фильтр подтверждается сразу.


    App API в том числе имеет доступ к фильтрам с помощью:

    • qlik.currentApp().clearAll() - снимает все фильтры в приложении.
    • qlik.currentApp() - доступ к API этого приложения.
    Перемещения в приложении
    За перемещения в приложении отвечает Navigation API:

    • получение ID листа, на котором находимся:
      qlik.navigation.getCurrentSheetId()
      • переход на предыдущий/следующий лист:
      qlik.navigation.prevSheet();
      qlik.navigation.nextSheet();
      • переход на конкретную страницу:
      qlik.navigation.gotoSheet(sheetId)
      • получение списка id листов:
        var sheets = qlik.currApp().getAppObjectList('sheet').then(function (g) {
          return g.layout.qAppObjectList.qItems.map(function (item) {
            return item.qInfo.qId,
          });
        })
      
      Navigation API также отвечает за режим, в котором находится лист (редактирования/просмотра). Это связано с тем, что при переключении режима листа у нас меняется url страницы. Соответственно, нас тут интересуют еще два метода:

      • получить режим страницы:
      qlik.navigation.getMode()
      • переключить режим страницы:
      qlik.navigation.setMode(mode)
      mode может быть равен 'edit' (режим редактирования) или 'analysis' (режим просмотра).
      Получить список
      Чтобы получить список, нам нужно использовать App API:
      qlik.app.getList(type, callback)
      type - то, список чего мы хотим получить. Он может быть равным, к примеру, 'MeasureList', 'DimensionList', 'sheet' ,'VariableList'. callback - функция-обработчик. Она будет вызвана, когда получим данные от Qlik.




      Какие бывают ошибки при работе с Qlik API?

      Методы обращения к Qlik API - асинхронные, и момент получения результата запроса не равен моменту вызова функции, в которой он запрашивается. Это нужно учитывать, иначе может сложиться ситуация, что мы пытаемся что-то сделать с результатом запроса к Qlik-у, до того, как он собственно пришел.

      Для избежания подобных ошибок используются "Промисы", подробней про них можно почитать тут:https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise

      Они позволяют работать со значениями, которых пока нет, как бы говоря: "вот его нет сейчас, но скоро оно обязательно появится". Что делать с тем, что появится, описывается в блоке .then().




      Support

      В предыдущем уроке был раздел "Гипер-кубы и создание параметров экстеншена" (даем гиперссылку на часть урока), где мы разобрали 2 основных объекта гиперкуба (initialProperties и definition), а объект support в параметрах оставили на потом. Настало его время.

      На самом деле уже в прошлом уроке мы использовали Qlik API - properties тоже к ним относятся и по сути представляют собой набор правил, по которым Qlik создает панель настроек.

      В support устанавливается доступность некоторых способов взаимодействия с данными, например:

      • export - возможность экспортировать экстеншн в PDF или как картинку в "Историях";
      • exportData - возможность экспортировать данные в XLSX;
      • snapshot - возможность делать снапшоты в "Историях".
      support: {
        snapshot: true,
        export: true,
        exportData: true
      }
      
      Extension "Кнопки": постановка задачи
      На самом деле мы создадим сегодня не одну кнопку. Второй extension нашего интенсива - «Динамические кнопки», которые позволяют устанавливать фильтр для выбора одного объекта из списка и будут выглядеть так:
      Функционал расширения:

      • фильтр для выбора одного объекта из списка при нажатии кнопки
      • каждое значение из списка (поле модели) рисуется как отдельная кнопка
      • дополнительные параметры: для активной и неактивных кнопок – цвет фона, цвет границы, скругление границ, цвет шрифта.

      Приступим!
      Создание extension и написание параметров
      1. Создание расширения.

      Создаем extension через DevHub (подробнее о создании extensions) смотрите в материалах первого дня, раздел "Создание «my_first_extension» и его анатомия".


      Создайте новый extension:

      • с названием «buttons»
      • на основе шаблона Basic Visualization Template.
      Этот шаблон победнее предыдущего, но нам многого и не надо.


      Добавляем CSS-файл.

      Из файлов нам не хватает только CSS, поэтому:

      • создадим в директории экстеншена файл buttons.css
      • подключим этот файл в уже существующий js-файл:
      define(["qlik", "css!./buttons.css"],
      
      • допишем buttons.css также в wbfolder.wbl:
      2. Блок с объявлением данных, параметров и измерений.

      • Для экстеншена нам понадобятся какие-нибудь данные и параметры. Чтобы мы могли их добавить, нужно дописать блок с их объявлением. Хорошо, что мы такое уже делали вчера, поэтому этот блок мы просто позаимствуем в предыдущем экстеншене.
      • К тому же, нам нужно добавить блок с объявлением измерений, которого не было во вчерашнем экстеншене "Рейтинг". Минимальное количество мер нужно поменять с 0 на 1. Получится так:
                dimensions: {
                  uses: "dimensions",
                  min: 1,
                  max: 1,
                },
      
      Весь блок перед paint будет выглядеть так:
       initialProperties: {
              qHyperCubeDef: {
                qDimensions: [],
                qMeasures: [],
                qInitialDataFetch: [{
                  qWidth: 2,
                  qHeight: 50
                }]
              }
            },
            definition: {
              type: "items",
              component: "accordion",
              items: {
                dimensions: {
                  uses: "dimensions",
                  min: 1,
                  max: 1,
                },
                measures: {
                  uses: "measures",
                  min: 0,
                  max: 1,
                },
                sorting: {
                  uses: "sorting"
                },
                settings: {
                  uses: "settings",
                  items: {}
                }
              }
            },
            support: {
              snapshot: true,
              export: true,
              exportData: false
            },
      



      3. Обособление основных и дополнительных параметров в разные секции "аккордеона".


      В задаче у нас стоит стилизация кнопок в активном и неактивном состоянии по четырем параметрам.

      Чтобы не путаться в массе параметров обособим их друг от друга в разные секции "аккордеона". Для этого в items блока settings добавим:
       inactive: {
        label: "Неактивная кнопка",
        type: "items",
        items: {
          //Цвет фона
          //Цвет текста
          //Цвет границы
          //Закругленность границ
        }
      }
      И уже в items этого блока будем писать параметры.



      4.Написание дополнительных параметров для стилизации.

      Мы будем стилизовать:

      • цвет фона,
      • цвет текста,
      • цвет границы,
      • закругленность границ
      для активной и неактивной кнопок.


      Как писать дополнительные параметры для стилизации экстеншена вы узнали вчера, в разделе "Написание блока параметров". Сегодня вы можете их написать сами: сначала в блок неактивной кнопки, а потом - в блок активной, по аналогии. Обратите внимание, что переменные должны ссылаться на разные переменные в ref.
      Проверьте себя
      define(["qlik", , "css!./buttons.css"],
        function (qlik) {
      
          return {
            initialProperties: {
              qHyperCubeDef: {
                qDimensions: [],
                qMeasures: [],
                qInitialDataFetch: [{
                  qWidth: 10,
                  qHeight: 50
                }]
              }
            },
            definition: {
              type: "items",
              component: "accordion",
              items: {
                dimensions: {
                  uses: "dimensions",
                  min: 1,
                  max: 1
                },
                measures: {
                  uses: "measures",
                  min: 0,
                  max: 1
                },
                sorting: {
                  uses: "sorting"
                },
                settings: {
                  uses: "settings",
                  items: {
                    inactive: {
                      label: "Неактивная кнопка",
                      type: "items",
                      items: {
                        inactiveBg: {
                          label: 'Фон',
                          ref: "props.inactiveBg",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'white'
                        },
                        inactiveColor: {
                          label: 'Цвет текста',
                          ref: "props.inactiveColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        inactiveBorderColor: {
                          label: 'Цвет границ',
                          ref: "props.inactiveBorderColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        inactiveBorderRadius: {
                          label: 'Закругление границ',
                          ref: "props.inactiveBorderRadius",
                          type: "number",
                          component: 'slider',
                          min: 0,
                          max: 10,
                          step: 1,
                          defaultValue: 5
                        }
                      }
                    },
                    active: {
                      label: "Активная кнопка",
                      type: "items",
                      items: {
                        activeBg: {
                          label: 'Фон',
                          ref: "props.activeBg",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'white'
                        },
                        activeColor: {
                          label: 'Цвет текста',
                          ref: "props.activeColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        activeBorderColor: {
                          label: 'Цвет границ',
                          ref: "props.activeBorderColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        activeBorderRadius: {
                          label: 'Закругление границ',
                          ref: "props.activeBorderRadius",
                          type: "number",
                          component: 'slider',
                          min: 0,
                          max: 10,
                          step: 1,
                          defaultValue: 5
                        }
                      }
                    },
                  }
                }
              }
            },
      
            support: {
              addons: true,
              snapshot: true,
              exportData: true
            },
            paint: function () {
              return qlik.Promise.resolve();
            }
          };
      
        });
      
      Подготовка к созданию кнопок


      1. Чтобы нарисовать кнопки, нам надо получить для них значения.


      Их мы будем брать из измерения, по которому же и будем впоследствии фильтровать. Продолжаем работать в buttons.js.

      Нам нужно добавить:
      • layout в свойства paint
      • в теле paint: try-catch, чтобы Qlik не лишал нас сообщений об ошибках
      • очистку экстеншена при перерисовке
      • добавить вывод layout в консоль

      Убрать:
      • выведение названия экстеншена

      Результат:
      paint: function ($element, layout) {
              try{
                $element[0].innerHTML = '';
                console.log(layout)
                }
              catch (e) {
                console.log(e)
              }
      
              return qlik.Promise.resolve();
            }



      2. Для удобства заполним экстеншен данными, чтобы нам было что выводить. и заполним измерение данным слева выражением.


      Разместим экземпляр экстеншена в приложенном к интенсиву приложении АТК_course_extensions.qvf на листе "Расширение № 2. Кнопки".

      В измерении укажем =OfficeLocation

      Меру пока не трогаем

      Измерения и меры, что мы используем в диаграммах находятся в таблице "Гипер-куб" в левом нижнем углу.



      3. Убедимся, что layout > qHyperCube > qDataPages > qMatrix не пустая.

      А вот теперь займемся отрисовкой кнопок.
      Отрисовка кнопок



      1. В js-файле создадим переменные hypercube и data для удобства обращения и интуитивного понимания, что это.



      • В hypercube поместим, как ни странно, гипер-куб,
      • В data - qMatrix из гипер-куба, которая является данными, которые мы будем выводить.
      • Нам также понадобится контейнер, в который мы положим кнопки, который нужно поместить в $element

      И у нас получится:
      var hypercube = layout.qHyperCube;
      var data = hypercube.qDataPages[0].qMatrix;
      var container = document.createElement('div')
                $element[0].appendChild(container)
      



      2. Создадим также функцию drawBtn.

      Пусть она принимает item (объект данных), создает элемент "кнопка" и выводит его в экстеншен:
      function drawBtn(item) {
         var button = document.createElement('button')
         button.innerHTML = item[0].qText;
         container.appendChild(button)
      }



      3. Теперь пройдемся по нашим данным, для каждой строки которых вызываем drawBtn:
      data.forEach(function (item) {
         drawBtn(item)
      })
      
      Вот какой код у нас получился в результате
      paint: function ($element, layout) {
              try {
                $element[0].innerHTML = '';
                console.log(layout)
      
                var hypercube = layout.qHyperCube;
                var data = hypercube.qDataPages[0].qMatrix;
                var container = document.createElement('div')
                $element[0].appendChild(container)
      
      
                function drawBtn(item) {
                  var button = document.createElement('button')
                  button.innerHTML = item[0].qText;
                  container.appendChild(button)
                }
      
                data.forEach(function (item) {
                  drawBtn(item)
                })
              }
              catch (e) {
                console.log(e)
              }
              return qlik.Promise.resolve();
            }
      
      
      Экстеншен пока выглядит так:
      Написание функций-обработчиков кнопки при помощи Backend API
      Теперь надо заставить наши кнопки фильтровать. Теория про Qlik API у нас была сегодня не просто так - применим подходящий для этого метод, а именно:
      self.backendApi.selectValues(dim, [value], true)
      Для него у нас не хватает примерно всего, поэтому скорее приступим:


      1. Сохраним this в self для доступа к нему в контексте функции-обработчика.

      В js файле создадим переменную:
      var self = this;
      2. dim - у нас равно 0, т.к. у нас всего одно измерение, и оно идет первым.


      3. [value] - в качестве значений для фильтра принимают qElemNumber значения измерения, а он заботливо помещен в объекты в нашем data. Мы должны по клику по кнопке откуда-то его доставать. Для этого вполне подойдет атрибут кнопки: назовем его data-value и добавим в создание кнопки (внутри функции drawBtn(item)):

      button.setAttribute('data-value', item[0].qElemNumber);


      4. Фильтровать мы хотим по клику по кнопке. Здесь могут быть два варианта:

      • создать событие "click" и "повесить" на кнопку. Тогда у нас будет по событию на каждой кнопке.
      • создать одно событие на контейнер кнопок и при клике определять, куда был произведен "тык", а потом по соответствующей кнопке фильтровать.

      Воспользуемся вторым вариантом:
      container.addEventListener('click', function (e) {
      // получаем qElemNumber из атрибута кнопки
                  var value = Number(e.target.getAttribute('data-value'))
      // если этот атрибут является числом, то фильтруем
                  if (!isNaN(value)) {
                    self.backendApi.selectValues(0, [value], true);
                  }
                })
      
      5. Теперь можете потыкать по кнопкам в экстеншене и заметить, что все хорошо, кроме того, что сами кнопки тоже фильтруются.
      Для этого есть одна хитрость - мы создадим меру в экстеншне, в выражении которой set analysis будет сбрасывать выборку по полю измерения, которое мы хотим выбрать. Наше выражение будет таким:

      MaxString({<[OfficeLocation]=>}[OfficeLocation])Теперь экстеншену всегда будет что отрисовать, т.к. его измерение (благодаря мере) не будет отфильтровываться.

      Все хорошо, кроме того, что мы хотели выбирать 1 значение фильтра, а сейчас можно фильтровать несколько сразу. Это решается очень просто. В методе фильтровки у нас последним аргументом идет флажок true, а если выставить его в false, то он будет отменять предыдущие фильтры. Так и поступим.


      6. А вот теперь есть 2 новости: хорошая и плохая.

      Начнем, конечно, с хорошей - мы решили проблему с множественным фильтрованием. Плохая - у нас появилась новая: теперь при клике по активной кнопке фильтр не сбрасывается. Нас спасет условие: нам нужно поставить флаг в true, если это активная кнопка, и false, если неактивна. Тут нам опять же поможет атрибут кнопки, который мы будем проверять при клике.Если эта кнопка активная, то будем при отрисовке добавлять:
      button.setAttribute('data-selected', 1);
      А метод фильтрации подкорректируем так:
      self.backendApi.selectValues(0, [value], Boolean(e.target.getAttribute('data-selected')));
      Результирующий код
      paint: function ($element, layout) {
              try {
                $element[0].innerHTML = '';
                console.log(layout)
      
                var hypercube = layout.qHyperCube;
                var data = hypercube.qDataPages[0].qMatrix;
                var container = document.createElement('div')
                $element[0].appendChild(container)
      
      
                function drawBtn(item) {
                  var button = document.createElement('button')
                  button.innerHTML = item[0].qText;
                  button.setAttribute('data-value', item[0].qElemNumber);
                  if(item[0].qState === 'S'){
                    button.setAttribute('data-selected', 1);
                  }
                  container.appendChild(button)
                }
      
                data.forEach(function (item) {
                  drawBtn(item)
                })
                container.addEventListener('click', function (e) {
                  // получаем qElemNumber из атрибута кнопки
                  var value = Number(e.target.getAttribute('data-value'))
                  // если этот атрибут является числом, то фильтруем
                  if (!isNaN(value)) {
                    self.backendApi.selectValues(0, [value], Boolean(e.target.getAttribute('data-selected')));
                  }
                })
              }
              catch (e) {
                console.log(e)
              }
              return qlik.Promise.resolve();
            }
      
      Стилизация кнопки при помощи CSS
      Теперь займемся дизайном кнопок.
      Нам нужно стилизовать кнопки в соответствии с их активностью. Это мы делаем в файле buttons.js.



      1. Для определения активности кнопки, идет фильтрация по этому значению измерения: есть ли у объекта этого измерения свойство qState, равное "S", если по нему фильтруют.

      Учитывая количество отличающихся стилей у активной и неактивной кнопки, чтобы не писать много раз условие активности разобьем drawBtn на 2 функции -- drawActiveBtn и drawInactiveBtn и добавим в них стилизацию из написанных ранее параметров:
      function drawInactiveBtn(item) {
        var button = document.createElement('button')
        button.innerHTML = item[0].qText;
        button.setAttribute('data-value', item[0].qElemNumber);
        button.style.background = layout.props.inactiveBg;
        button.style.color = layout.props.inactiveColor;
        button.style.border = '1px solid ' + layout.props.inactiveBorderColor;
        button.style.borderRadius = layout.props.inactiveBorderRadius + 'px';
      
        container.appendChild(button)
      }
      



      2. drawActiveBtn напишите сами по аналогии и не забудьте про атрибут 'data-selected'.



      3. Осталось добавить еще немного дизайна.

      Выставим классы контейнеру и кнопкам:
      container.className = 'buttons-container';
      button.className = 'buttons-btn';
      



      4. Теперь переходим в файл buttons.css и добавим cобавим в css стили.

      Контейнер нужно сделать под размер экстеншена и спозиционировать его содержимое "по ширине" с помощью flex:
      .buttons-container {
          display: flex;
          justify-content: space-around;
          align-items: center;
          height: 100%;
      }
      
      



      5. Кнопкам добавим тени и минимальную ширину:
      .buttons-btn {
          min-width: 80px;
          box-shadow: 0px 0px 3px 0px #333;
      }
      
      
      




      6. Для эффекта "нажатости" уберем у активной кнопки тень.

      Добавим это в buttons.js, т.к. выносить один стиль в класс кажется нецелесообразным. Ну а так, то, что меняется не через параметры экстеншена лучше выносить в css-файл.
      button.style.boxShadow = 'none';
      
      
      




      7. Осталось почистить код от try-catch, консоль логов и добавить комментарии.

      Вот, что у нас получилось:
      Проверьте себя.

      Результирующий код в js:
      define(["qlik", , "css!./buttons.css"],
      function (qlik) {
      
          return {
            initialProperties: {
              qHyperCubeDef: {
                qDimensions: [],
                qMeasures: [],
                qInitialDataFetch: [{
                  qWidth: 10,
                  qHeight: 50
                }]
              }
            },
            definition: {
              type: "items",
              component: "accordion",
              items: {
                dimensions: {
                  uses: "dimensions",
                  min: 1,
                  max: 1
                },
                measures: {
                  uses: "measures",
                  min: 0,
                  max: 1
                },
                sorting: {
                  uses: "sorting"
                },
                settings: {
                  uses: "settings",
                  items: {
                    inactive: {
                      label: "Неактивная кнопка",
                      type: "items",
                      items: {
                        inactiveBg: {
                          label: 'Фон',
                          ref: "props.inactiveBg",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'white'
                        },
                        inactiveColor: {
                          label: 'Цвет текста',
                          ref: "props.inactiveColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        inactiveBorderColor: {
                          label: 'Цвет границ',
                          ref: "props.inactiveBorderColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        inactiveBorderRadius: {
                          label: 'Закругление границ',
                          ref: "props.inactiveBorderRadius",
                          type: "number",
                          component: 'slider',
                          min: 0,
                          max: 10,
                          step: 1,
                          defaultValue: 5
                        }
                      }
                    },
                    active: {
                      label: "Активная кнопка",
                      type: "items",
                      items: {
                        activeBg: {
                          label: 'Фон',
                          ref: "props.activeBg",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'white'
                        },
                        activeColor: {
                          label: 'Цвет текста',
                          ref: "props.activeColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        activeBorderColor: {
                          label: 'Цвет границ',
                          ref: "props.activeBorderColor",
                          type: "string",
                          expression: "optional",
                          defaultValue: 'gray'
                        },
                        activeBorderRadius: {
                          label: 'Закругление границ',
                          ref: "props.activeBorderRadius",
                          type: "number",
                          component: 'slider',
                          min: 0,
                          max: 10,
                          step: 1,
                          defaultValue: 5
                        }
                      }
                    },
                  }
                }
              }
            },
      
            support: {
              addons: true,
              snapshot: true,
              exportData: true
            },
            paint: function ($element, layout) {
              // очистка экстеншена
              $element[0].innerHTML = '';
              var hypercube = layout.qHyperCube;
              var data = hypercube.qDataPages[0].qMatrix;
              var self = this;
      
              // контейнер кнопок
              var container = document.createElement('div')
              container.className = 'buttons-container';
              $element[0].appendChild(container)
      
              // отрисовка неактивной кнопки
              function drawInactiveBtn(item) {
                var button = document.createElement('button')
                button.className = 'buttons-btn';
                button.innerHTML = item[0].qText;
                // значение измерения для фильтрации
                button.setAttribute('data-value', item[0].qElemNumber);
                button.style.background = layout.props.inactiveBg;
                button.style.color = layout.props.inactiveColor;
                button.style.border = '1px solid ' + layout.props.inactiveBorderColor;
                button.style.borderRadius = layout.props.inactiveBorderRadius + 'px';
      
                container.appendChild(button)
              }
      
              // отрисовка активной кнопки
              function drawActiveBtn(item) {
                var button = document.createElement('button')
                button.className = 'buttons-btn';
                button.innerHTML = item[0].qText;
                // значение измерения для фильтрации
                button.setAttribute('data-value', item[0].qElemNumber);
                
                // является ли кнопка активной
                button.setAttribute('data-selected', 1);
                button.style.background = layout.props.activeBg;
                button.style.color = layout.props.activeColor;
                button.style.border = '1px solid ' + layout.props.activeBorderColor;
                button.style.borderRadius = layout.props.activeBorderRadius + 'px';
                button.style.boxShadow = 'none';
      
                container.appendChild(button)
      
              }
      
              // вызовы отрисовки на данных
              data.forEach(function (item) {
                if (item[0].qText !== '-') {
                  if (item[0].qState === 'S') {
                    drawActiveBtn(item)
                  } else {
                    drawInactiveBtn(item)
                  }
                }
              })
      
              // обработчик клика по кнопкам
              container.addEventListener('click', function (e) {
                var value = Number(e.target.getAttribute('data-value'))
                if (!isNaN(value)) {
                  self.backendApi.selectValues(0, [value], Boolean(e.target.getAttribute('data-selected')));
                }
              })
      
              return qlik.Promise.resolve();
      
            }
          };
      
        });
      
      
      


      Результирующий код в CSS:
      .buttons-container {
          display: flex;
          justify-content: space-around;
          align-items: center;
          height: 100%;
      }
      
      .buttons-btn {
          min-width: 80px;
          box-shadow: 0px 0px 3px 0px #333;
      }
      


      Результирующий код в html:
      Подловили! Его нет! :)

      Проверка работы
      Давайте проверим работу!

      Щелкайте на кнопки и смотрите, меняется ли график. Попробуйте:

      • нажать две кнопки.
      • проверить, работает ли проверка на уже выбранное значение.
      • выбрать одно значение из таблицы в колонке измерение и посмотреть:

        • изменились ли данные в таблице?
        • раскрасилась ли кнопка, которая соответствует выбранному значению?

      Домашнее задание. Урок 3
      Попробуйте доработать диаграмму "динамические кнопки".

      Возможные задачи для доработки:

      1. Базовый вариант доработки расширения будет нацелен на отработку создания properties. Добавьте настройки для активной и неактивных кнопок, например:
      • размер шрифта,
      • стиль шрифта,
      • толщина границы,
      • выравнивание (слева, справа, по центру),
      • расстояние между кнопками,
      • высота и ширина кнопок.
      2. Усложненный вариант (для тех, кто уже знаком с разработкой расширений): добавить разные способы позиционирования кнопок, например с помощью выпадающего списка в настройках.
      Итоги дня
      Итак, сегодня мы разобрали основную теорию про Qlik API, которые могут пригодиться при разработке расширений (безусловно, не все, только самое важное), а также создали второй экстеншен. В сегодняшнем расширении мы настраивали четыре дополнительных параметра и много работали над стилизацией кнопок при помощи CSS. Завтра и послезавтра посвятим кастомизации диаграммы D3, чтобы использовать ее в качестве экстеншена Qlik Sense. Это будет полезный календарь.


      Кстати, сегодня у нас "экватор" интенсива, и поэтому случаю котик перевел 3 свои задачи интенсива в статус "done". А как отмечаете вы? =)
      Консультационная Группа АТК и Qlik-сообщество qRUG

      2020

      atkcg.ru
      qrug.atkcg.ru