День 2. Расширение №1: «Рейтинг»
День начнем с теории - гипер-кубы и параметры экстеншенов, а дальше разработаем первое расширение, "Рейтинг". Теоретический блок выглядит внушительно, но он нужен для того, чтобы синхронизировать знания участников. Те, кто уже интересовался темой разработки расширений, вероятно, уже знакомы с материалом и могут его бегло просмотреть и перейти к созданию рейтинга. Для новичков в теме экстеншенов - это блок для подробного изучения, к которому вы также можете потом возвращаться для справки.

Экстеншены сами себя не разработают, поэтому начнем! =)
Гипер-кубы и создание параметров экстеншена
Основа любой диаграммы (стандартной или экстеншена) – это данные, соответственно, и расширения также работают с хорошо нами знакомыми понятиями «Измерения» и «Меры». Чтобы увидеть, что внутри диаграммы, можно преобразовать диаграмму в таблицу. Чтобы процесс создания расширения и наполнения его данными был лучше понятен, мы пойдем от таблицы, на которой элемент визуализации базируется и введем понятие:
Гипер-куб – это данные (измерения и меры), которые используются в диаграмме.


Данные, которые попадут в наш экстеншен, (тот самый гипер-куб) описываются в объекте properties в .js-файле, включающем:
initialProperties
Описание гипер-куба или листа
definition
Описание параметров измерений, мер, сортировки и прочих настроек внешнего вида
support
Объект, в котором можно разрешить делать снапшоты и экспортировать данные
Разберем подробнее
initialProperties имеет следующий вид:
Definition: настройки измерений, мер и общих настроек выглядят похоже:
{

uses:

"dimentions",

min: 1,

max: 2,

items: {

}
uses - определяет тип объекта,
min, max - допустимое количество
измерений/мер
items - объект настроек, которые будут
дополнять панель справа при раскрытии
измерения/меры.
},

measures: {

uses:

"measures",

min: 1,

items: {

}


},

settings: {

uses: "settings",

items: {

}

}


Support: тут выставляется разрешено использовать определенный
функционал или нет (например, снапшоты и экспорт в Excel). В контексте сегодняшнего экстеншена, эта часть нас не интересует, разберем подробнее завтра.
Создание параметров экстеншена
Вот так выглядят параметры экстеншена для пользователя:
Красное - сгенерированные параметры, зеленое - наши параметры.
Теперь рассмотрим создание объекта параметров.
width:
{

type: "integer",

label: "Width",

ref:

"size.width",

expression:

"optional",

defaultValue: "10"

}


Основные характеристики настройки параметров
type - тип значения, которое можно будет задать параметру.
Может принимать следующие значения:
  • integer -- целое число;
  • number -- число с десятичной частью;
    string -- строка;
text - строка текста, не принимающее значения
    label -- название настройки в панели настроек
    ref – адрес, по которому будет располагаться значение
    expression - будет ли это поле вычисляемым
    defaultValue – значение, которое будет задано изначально
    component – определяет, как параметр будет выглядеть на панели, принимает следующие значения:
    button - кнопка, которой можно задать какое-то действие в свойство action;
      buttongroup - группа кнопок для выбора из нескольких определенных значений, необходимо задать options
        checkbox - флажок, возвращающий true или false
          radiobuttons - выбор одного из нескольких значений, необходим options
            color-picker - панель для выбора цвета
              dropdown - выпадающий список, необходимо задать options
                link - ссылка, необходимо задать url
                  slider - слайдер для выбора значения, необходимо также задать max, min и step.
                  Если тип сделать не number, а array, то это станет range slider, который возвращает массив из начала диапазона и конца
                    switch - переключатель с двумя значениями, необходим options
                      textarea - многострочная область для ввода текста, необходимы свойства rows (высота в строках), maxlength (максимальная длина в символах).
                        В зависимости от разных типов могут быть и другие параметры, например:

                        min/max - ограничивают вводимое значение
                        show - в зависимости от истинности этого поля весь параметр отображается или скрывается
                        options - позволяет задавать список значений для выбора
                          Отличие общих настроек от настроек измерений/мер в свойстве ref: для мер и измерений мы пишем одно из двух:

                          • qDef.prop_name, чтобы значения этого параметра было индивидуально для конкретной меры/измерения,

                          • qAttributeExpressions.[index].qExpression, чтобы значение вычислялось для каждой строки.

                          Сейчас это может быть не очень понятно, но на практике все встанет на свои места. К ней и приступим.


                          Extension "Рейтинг": постановка задачи
                          Сегодня создадим наш первый extension «Рейтинг». Эта диаграмма позволяет наглядно оценить достижение плана в виде инфографики и будет выглядеть так:
                          Цель диаграммы - показывать фактическое значение как количество закрашенных картинок
                          (у нас это будут звезды). Пропишем основные моменты по настройке расширения:

                          • Настройки выражений: фактическое значение (основное выражение), цель (параметр основного выражения)
                          • Значения факта округляем таким образом, чтобы каждая картинка была или полностью закрашена, или полностью незакрашена
                          • Формат картинки: звезды (формат может быть любым, но это наше первое расширение, поэтому не будем усложнять)
                          • Дополнительно настраиваемые параметры: цвет картинок, максимальное количество картинок (по умолчанию сделаем 5)

                          Приступим!
                          Создание extension на основе шаблона "chart" и начало
                          работы с ним


                          1. Создаем расширение.


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


                          Создайте новый extension:
                          • с названием «rating»
                          • на основе шаблона chart.

                          Помните, что название, которое вы вводите, будет отображаться в пользовательских объектах в режиме редактирования приложения и в списке extensions на сервере в QMC, так что оно должно быть понятным. Вот, например, как выглядит список расширений в QMC:

                          2. Избавляемся от Angular.

                          На первый раз.
                          Откроем созданный шаблон в файловой системе Windows: он по умолчанию будет располагаться в:
                          Этот компьютер>Документы>Qlik>Sense>Extensions>rating

                          Qlik сгенерировал 5 файлов: rating.js, rating.ng.html, rating.css, rating.qtext и wbfolder.wbl.
                          Этот шаблон работает на основе Ангуляра (Angular web framework), но мы его использовать не будем, чтобы не усложнять и без того насыщенный интенсив. Поэтому:

                          • очищаем содержимое rating.css,
                          • rating.ng.html переименуем в rating.html и заменим в нем содержимое на пустой структурный блок <div></div>,
                          • в файле wbfolder.wbl поменяем "rating.ng.html" на "rating.html"
                          • Откроем файл rating.js. В первой строчке идет подключение файлов, во второй - в качестве параметров функции указаны подключенные файлы, к которым мы будем обращаться:
                          define( ["qlik", "text!./rating.ng.html", "css!./rating.css"],
                            function ( qlik, template ) {
                          т.к. мы переименовывали один из файлов ("rating.ng.html" в "rating.html"), нужно его поправить и тут.

                          3. Подготовим структуру файла .js


                          Дальше еще поработаем с rating.js.

                          • В качестве значения диаграммы у нас будет мера (объект measures), поэтому измерение нам не нужно - удалим объект с измерением (объект dimensions). В объекте measures есть свойства min/max - они ограничивают количество мер, которые можно будет задать.
                          • В данном шаблоне отсутствуют общие параметры вида, добавим их (блок "settings"). Объект items в settings будем заполнять параметрами.
                          • Давайте сверимся - после изменений выше объекты настроек будут иметь следующий вид:
                                definition: {
                                  type: "items",
                                  component: "accordion",
                                  items: {
                                    measures: {
                                      uses: "measures",
                                      min: 1,
                                      max: 1
                                    },
                                    sorting: {
                                      uses: "sorting"
                                    }
                                  }
                                },
                                settings: {
                                  uses: "settings",
                                  items: {
                          
                                  }
                                },
                          Написание блока параметров
                          Как мы поставили задачу ранее у нас будет 3 параметра:
                          Целевое значение
                          Максимальное количество звезд
                          Цвет звезд
                          Начнем с целевого значения. Нам нужно текстовое поле в которое можно ввести число. Логично было бы сделать ему тип "integer", но было бы очень здорово иметь возможность вводить туда также и выражения, возвращающие числа или переменные. Для этого нам нужно добавить свойство "expression" со значением "optional", а также сменить тип на "string". Значение по умолчанию пусть будет 5. Дефолтные значения необходимо задавать для всех параметров, чтобы при инициализации (инициализацией считается взаимодействие с экстеншненом: создание нового, перетаскивание на листе) у нас было меньше проблем со стартовой отрисовкой. Получим вот такой параметр:
                           target: {
                            type: "string",
                            label: "Цель",
                            ref: "props.target",
                            expression: "optional",
                            defaultValue: 5
                          }
                          Аналогичным образом создадим два остальных параметра - максимальное количество и цвет звезд. Вот код для них:
                           starMaxCount: {
                             type: 'string',
                             label: "Макс. количество звёзд",
                             ref: "props.starMaxCount",
                             expression: "optional",
                             defaultValue: 5
                          },
                            starColor: {
                            type: "string",
                            label: "Цвет звёзд",
                            ref: "props.starColor",
                            expression: "optional",
                            defaultValue: 'gold'
                          }
                          Теперь удалим controller и его содержимое (это метод фреймворка Angular, который мы не используем).
                          А в параметры функции Paint добавим объекты $element и layout. $element ссылается на DOM-элемент экстеншена на листе, layout - данные, которые приходят в экстеншн из Qlik'a, содержит меры, измерения, параметры и прочую информацию об экстеншене. Получится так:
                          paint: function ($element, layout) {
                            console.log(‘layout:’, layout)
                            return qlik.Promise.resolve();
                           },
                          Проверьте себя
                          define(["qlik", "text!./rating.html", "css!./rating.css"],
                            function (qlik, template) {
                              "use strict";
                              return {
                                template: template,
                                initialProperties: {
                                  qHyperCubeDef: {
                                    qDimensions: [],
                                    qMeasures: [],
                                    qInitialDataFetch: [{
                                      qWidth: 2,
                                      qHeight: 50
                                    }]
                                  }
                                },
                                definition: {
                                  type: "items",
                                  component: "accordion",
                                  items: {
                                    measures: {
                                      uses: "measures",
                                      min: 1,
                                      max: 1,
                                    },
                          
                                    sorting: {
                                      uses: "sorting"
                                    },
                                    settings: {
                                      uses: "settings",
                                      items: {
                                        view: {
                                          label: "Вид",
                                          type: "items",
                                          items: {
                                            target: {
                                              type: "string",
                                              label: "Цель",
                                              ref: "props.target",
                                              expression: "optional",
                                              defaultValue: '5'
                                            },
                                            starMaxCount: {
                                              type: 'string',
                                              label: "Макс. количество звёзд",
                                              ref: "props.starMaxCount",
                                              expression: "optional",
                                              defaultValue: '5'
                                            },
                                            starColor: {
                                              type: "string",
                                              label: "Цвет звёзд",
                                              ref: "props.starColor",
                                              expression: "optional",
                                              defaultValue: 'gold'
                                            },
                                          }
                                        }
                                      }
                                    }
                                  }
                                },
                                support: {
                                  snapshot: true,
                                  export: true,
                                  exportData: true
                                },
                                paint: function ($element, layout) {
                                  console.log(layout)
                                  return qlik.Promise.resolve();
                                },
                              };
                            });
                          
                          Размещение extension на листе, заполнение мер
                          Время разместить экстеншн, заполнить меру и параметры.Разместим экземпляр экстеншена в приложенном к интенсиву приложении АТК_course_extensions.qvf на листе "Расширение № 1. Рейтинг":
                          В таблице слева у нас есть выражение для меры в столбце "Факт". Выражение для факта пропишем в сайдбаре справа - у нас это будет сумма продаж: Sum(Sales).

                          В сайдбаре справа откроем вкладку "Вид" - параметр "Цель" заполним содержимым:

                          • столбец "Цель": Sum(SalesTarget)
                          • "Макс. количество звезд": заполняйте на своё усмотрение, например, я сделаю 10
                          • "Цвет звёзд": "gold". Цвет в данном случае заполняем таким значением, которое сможет обработать css: либо hex-, либо rgb-цвет, либо некоторыми английскими словами, которые он воспринимает.

                          Не забудьте поставить знак "=" перед выражением Цели, иначе параметр воспримет его как строку и не станет вычислять
                          Разбор, сформированного Qlik Sense, объекта данных + знакомство с инструментом разработчика
                          Теперь хотелось бы посмотреть, что же, на основании введенных данных, нам сгенерировал Qlik Sense. Для этого нужно воспользоваться Инструментами разработчика в Google Chrome. Как запустить инструменты разработчика смотрите на видео:
                          При работе с Инструментами разработчика Google Chrome обязательно в инспекторе во вкладке Network поставьте галочку в Disable cache, иначе экстеншн закешируется и не будет обновляться, даже если вы меняете код.

                          Если мы все сделали правильно, то во вкладке console увидим такой объект:

                          Здесь в extensionMeta - информация об экстеншене, в том числе то, что написано в rating.qtext,

                          props - объект, в который мы сложили данные из параметров (ref в параметрах ссылается на layout, а наши свойства ref имеют вид props.property_name),

                          qHyperCube - наш сформированный гипер-куб,

                          в qInfo размещен id экземпляра экстеншена, а в qSelectionInfo - информация об установленных фильтрах в приложении.Раскроем кусок кода по qHyperCube:
                          Здесь в qDataPages мы увидим значение нашей меры.

                          qDimensionInfo содержит информацию об измерении, а поскольку у нас измерения не предусмотрено, он пустой.

                          В qMeasureInfo - информация о мере, там можно увидеть максимальное и минимальное значение меры, метку (qFallbackTitle), форматирование(qNumFormat) и прочее.
                          Написание объекта данных для отрисовки
                          Что ж, теперь мы знаем как обратиться к нашим данным и следует привести их виду, с которым будет удобно работать. Сделаем объект данных для отрисовки. Что нам нужно, чтобы визуализировать рейтинг? Правильно, количество звезд. Для этого нам надо значение, которое будет соответствовать одной звезде и разделить Факт (выражение из "Меры") на него.

                          Сделаем это в коде. Возвращаемся в файл "rating.js". Ищем раздел paint (он отвечает за то что будет отрисовано в браузере) и создаем внутри него переменные value - значение из меры, target - цель, которую задаем в виде и starCount - максимальное количество звезд, что соответствует цели.
                          var value = Number(layout.qHyperCube.qDataPages[0].qMatrix[0][0].qNum);
                          var target = Number(layout.props.target);
                          var starCount = Math.floor(value / (target / layout.props.starMaxCount));
                          
                          Стоит учесть ситуации, когда:

                          • value будет меньше 0,
                          • value не будет являться числом,
                          • Факт больше Цели.


                          Чтобы учесть перечисленные выше ситуации нам необходимо еще до вычисления переменной starCount проверить что приходит в нее. Соответственно, после определения переменной target делаем проверку в блоке starCount :
                          var starCount;
                          if (value <= 0 || isNaN(value)) {
                            starCount = 0;
                          } else if (value > target) {
                            starCount = Number(layout.props.starMaxCount);
                          } else {
                            starCount = Math.floor(value / (target / layout.props.starMaxCount));
                          }
                          
                          Для просмотра полученного результата можно вывести нашу переменную starCount в консоль:
                          console.log(‘starCount: ‘, starCount)
                          Когда что-то идет не так, JS выдает ошибки в консоль, но Qlik Sense их "перехватывает". Для того чтобы этого избежать, можно обернуть тело paint в конструкцию try-catch.
                          Проверьте себя - посмотрите код объекта paint целиком:
                          paint: function ($element, layout) {
                                  try {
                                    console.log('layout:', layout)
                                    var value = Number(layout.qHyperCube.qDataPages[0].qMatrix[0][0].qNum);
                                    var target = Number(layout.props.target);
                                    let starCount;
                                    if (value <= 0 || isNaN(value)) {
                                      starCount = 0;
                                    } else if (value > target) {
                                      starCount = Number(layout.props.starMaxCount);
                                    } else {
                                      starCount = Math.floor(value / (target / layout.props.starMaxCount));
                                    }
                                    console.log('starCount:', starCount)
                                  }
                                  catch (e) {
                                    console.log(e)
                                  }
                                  return qlik.Promise.resolve();
                                },
                          
                          Написание функции отрисовки
                          Чтобы показать отношение плана к факту, мы будем рисовать максимальное количество звезд. При этом цветом будем окрашивать только часть звезд, соответствующих факту, заданному в мере. Для этого нам нужно:

                          1. сделать цикл с количеством итераций, равному максимальному количеству звезд
                          2. в каждой итерации создавать DOM-элемент, выберем span, т.к. это текст
                          3. выставить ему цвет из параметра, если индекс элемента меньше или равен фактическому количеству звезд
                          4. добавить элемент в экстеншн.

                          Вот такой код у нас получится:
                          for (var i = 1; i <= layout.props.starMaxCount; i++) {
                            var star = document.createElement('span');
                            star.innerHTML = '*';
                             if (i <= starCount) {
                              star.style.color = layout.props.starColor;
                            } else {
                              star.style.color = 'black';
                            }
                            $element[0].appendChild(star);
                          }
                          Здесь $element - это jQuery-массив, являющийся нашим блоком экстеншена на листе. В Qlik Sense по умолчанию подключена библиотека jQuery, но мы пользоваться ею сейчас не будем. А чтобы получить обычный DOM-элемент нужно взять нулевой элемент этого массива.


                          После нескольких нажатий на "Изменить" в интерфейсе Qlik Sense, либо после изменений размера окна, вы можете обнаружить, что количество звёзд превышает ожидаемое, это происходит из-за того, что мы при каждом обновлении добавляем звёзды, но никогда их не удаляем. Чтобы исправить ситуацию, при отрисовке экстеншена надо очищать зону отрисовки ($element). Добавьте $element[0].innerHTML=""; сразу после try{

                          Получится вот такая визуализация:
                          А теперь "причешем" extension: настройка внешнего вида
                          Теперь превратим наши звездочки во что-то симпатичное: для этого можно воспользоваться svg-картинкой или иконочным шрифтом. Они хороши тем, что легко стилизуются с помощью css. А png-картинки нам не подойдут, поскольку мы хотим динамически задавать цвет. Т.к. нам нужно только 2 картинки, не целесообразно будет использовать шрифт. В качестве картинок возьмем символы из иконочного шрифта Font Awesome star-regular и star-solid.

                          Для использования некоторых бесплатных материалов требуется вставлять в свой код лицензию с ссылкой на источник, при скачивании иконок вас даже об этом предупредят.SVG - язык разметки масштабируемой векторной графики, если открыть svg-изображение в текстовом редакторе, то можно видеть, каким образом она отрисовывается и там же можно ее изменять. Нас интересует изменение ее цвета и размеров, а так же нам нужно как-то получить к ней доступ из js-файла.
                          1. Открываем rating.html и создаем два тега div:

                          а. star-regular
                          b. star-solid
                          2. Теперь в соответствующие теги нам надо поместить код из наших svg-изображений.

                          Так как svg по своей сути язык разметки, то мы можем открыть файл с помощью редактора кода. Открываем файл star-regular.svg, копируем весь код и вставляем его между тегами div, относящимися к классу rating-star-regular.


                          3. То же самое проделываем для star-solid.


                          У нас должно получиться так:

                          4. Теперь если в rating.js вывести в консоль template, то можно будет увидеть содержимое нашего html файла, но прямо так работать мы с ним не можем, т.к. js будет воспринимать его как строку, а нам нужно дерево DOM-элементов.


                          Чтобы это исправить, создадим новый тегВ "rating.js" создаем новый тег div из js-файла и выставим в нем нашу template-строку как html:
                          var svgContainer = document.createElement('div')
                          svgContainer.innerHTML = template;
                          Теперь мы можем обращаться с ним как с обычными DOM-элементами.


                          5. В js-файле создаем отдельные переменные для заполненных и пустых звезд (из размышления удобства обращения к ним):
                          var regularStar = svgContainer.querySelector('.rating-star-regular')
                          var solidStar = svgContainer.querySelector('.rating-star-solid')


                          6. Замечательно, теперь нам всего лишь нужно заменить текстовые звездочки, на svg:
                          for (var i = 1; i <= layout.props.starMaxCount; i++) {
                            var star
                            if (i <= starCount) {
                              star = solidStar.cloneNode(true)
                            } else {
                              star = regularStar.cloneNode(true)
                            }
                            star.style.width = '20px';
                            star.style.height = '20px';
                            star.style.color = layout.props.starColor;
                          
                            $element[0].appendChild(star);
                          }
                          true в вызове cloneNode значит, что копироваться будут также все вложенные элементы. Это важно!



                          7. Получим вот такой результат:


                          8. Теперь стоит поработать над размерами и позиционированием.

                          Сделаем контейнер для позиционирования звезд и посчитаем их размер в зависимости от размера экстеншена на странице. К максимальному числу звезд прибавляем единицу, чтобы у нас было расстояние для отступов между звездами.
                          var container = document.createElement('div');
                          container.className = 'rating-container';
                          $element[0].appendChild(container);
                          var containerWidth = $element.width();
                          var containerHeight = $element.height();
                          var starSize = containerWidth / (layout.props.starMaxCount + 1);


                          9. Т.к. размеры звезд мы считаем от ширины, т.к. они квадратные то может возникнуть ситуация что ширина экстеншена на столько больше высоты, что звездочки получаются выше контейнера, так что нам нужно это проверить и подогнать их размер под высоту.
                          if (starSize > containerHeight) {
                            starSize = containerHeight;
                          }


                          10. Вспомним теперь про css-файл и добавим стили для контейнера, такие чтобы звезды становились в ряд и равномерно распределялись по ширине:
                          .rating-container {
                            display: flex;
                            justify-content: space-around;
                          }
                          
                          Посмотрим, как оно там на листе:


                          11. Осталось спозиционировать звезды по высоте, добавить рамку и внутренние отступы.

                          Добавим еще один контейнер, чтобы выставить имеющийся по центру и стили для него:
                          JS:
                          var wrapper = document.createElement('div');
                          wrapper.className = 'rating-wrapper';
                          $element[0].appendChild(wrapper);
                          

                          CSS:
                          .rating-wrapper {
                            display: flex;
                            flex-direction: column;
                            height: 100%;
                          }
                          
                          
                          Не забудьте поправить, что в какой контейнер размещается!$element > wrapper > container > star / star /star.



                          12. Дополним стили для контейнера:
                          .rating-container {
                            display: flex;
                            justify-content: space-around;
                            padding: 10px;
                            border: 1px solid #ccc;
                            border-radius: 10px;
                          }
                          



                          13. И т.к. в стилях мы добавили по 10px отступов и 1 пиксель рамки, нужно учесть их и в вычислениях размеров:
                          var PADDING_SIZE = 10;
                          var BORDER_SIZE = 1;
                          var containerWidth = $element.width() - PADDING_SIZE * 2 - BORDER_SIZE * 2;
                          var containerHeight = $element.height() - PADDING_SIZE * 2 - BORDER_SIZE * 2;
                          
                          Зачем мы создаем отдельные переменные для чисел?
                          Числа, встречающиеся в вычислениях, называются магическими, потому что информации о том, что это и зачем, сами числа не несут и даже если сейчас вы знаете, что это, то через неделю уже вряд ли вспомните. Для этого их принято класть в константы с говорящими названиями. Их пишут заглавными буквами, чтобы сразу было понятно где переменная, а где константа.


                          Современный JS поддерживает константы, но вот IE 11 их не знает, так что желательно писать код так чтобы и IE его воспринимал. Есть и другой вариант - можно писать, как удобно, и прогонять через Babel (транскомпилятор для JS переводящий его из одной версии в другую).



                          14. Последние штрихи: теперь можно почистить код от "консоль логов", убрать try-catch и написать комментарии.

                          Результирующий код для rating.js
                          paint: function ($element, layout) {
                          
                                  // очистка экстеншена
                                  $element[0].innerHTML = '';
                          
                                  // обертка для рейтинга
                                  var wrapper = document.createElement('div');
                                  wrapper.className = 'rating-wrapper';
                                  $element[0].appendChild(wrapper);
                          
                                  // контейнер для звёзд
                                  var container = document.createElement('div');
                                  container.className = 'rating-container';
                                  wrapper.appendChild(container);
                          
                                  // размер отступов контейнера
                                  var PADDING_SIZE = 10;
                          
                                  // толщина рамки
                                  var BORDER_SIZE = 1;
                          
                                  // ширина контейнера
                                  var containerWidth = $element.width() - PADDING_SIZE * 2 - BORDER_SIZE * 2;
                          
                                  // высота контейнера
                                  var containerHeight = $element.height() - PADDING_SIZE * 2 - BORDER_SIZE * 2;
                          
                                  // размер звезды
                                  var starSize = containerWidth / (Number(layout.props.starMaxCount) + 1);
                          
                                  // подгон размера звезды под высоту контейнера
                                  if (starSize > containerHeight) {
                                    starSize = containerHeight;
                                  }
                          
                                  // величина факта
                                  var value = Number(layout.qHyperCube.qDataPages[0].qMatrix[0][0].qNum);
                          
                                  // целевая величина
                                  var target = Number(layout.props.target);
                          
                                  // расчет количества заполенных звёзд
                                  var starCount;
                                  if (value <= 0 || isNaN(value)) {
                                    starCount = 0;
                                  } else if (value > target) {
                                    starCount = Number(layout.props.starMaxCount);
                                  } else {
                                    starCount = Math.floor(value / (target / layout.props.starMaxCount));
                                  }
                          
                                  // элементы заполненой и пустой звезды
                                  var svgContainer = document.createElement('div')
                                  svgContainer.innerHTML = template;
                                  var regularStar = svgContainer.querySelector('.rating-star-regular')
                                  var solidStar = svgContainer.querySelector('.rating-star-solid')
                          
                                  // отрисовка звезд в экстеншене
                                  for (var i = 1; i <= layout.props.starMaxCount; i++) {
                                    var star
                                    if (i <= starCount) {
                                      star = solidStar.cloneNode(true)
                                    } else {
                                      star = regularStar.cloneNode(true)
                                    }
                                    star.style.width = starSize + 'px';
                                    star.style.height = starSize + 'px';
                                    star.style.color = layout.props.starColor;
                          
                                    container.appendChild(star);
                                  }
                          
                                  return qlik.Promise.resolve();
                                },
                          
                          
                          Проверьте себя.

                          Результирующий код в rating.css

                          .rating-wrapper {
                            display: flex;
                            flex-direction: column;
                            height: 100%;
                          }
                          .rating-container {
                            display: flex;
                            justify-content: space-around;
                            padding: 10px;
                            border: 1px solid #ccc;
                            border-radius: 10px;
                          }
                          
                          Результирующий код в rating.html
                          <div class='rating-star-regular'>
                          <svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="star" class="svg-inline--fa fa-star fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M528.1 171.5L382 150.2 316.7 17.8c-11.7-23.6-45.6-23.9-57.4 0L194 150.2 47.9 171.5c-26.2 3.8-36.7 36.1-17.7 54.6l105.7 103-25 145.5c-4.5 26.3 23.2 46 46.4 33.7L288 439.6l130.7 68.7c23.2 12.2 50.9-7.4 46.4-33.7l-25-145.5 105.7-103c19-18.5 8.5-50.8-17.7-54.6zM388.6 312.3l23.7 138.4L288 385.4l-124.3 65.3 23.7-138.4-100.6-98 139-20.2 62.2-126 62.2 126 139 20.2-100.6 98z"></path></svg>
                          </div>
                          <div class='rating-star-solid'>
                          <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="star" class="svg-inline--fa fa-star fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M259.3 17.8L194 150.2 47.9 171.5c-26.2 3.8-36.7 36.1-17.7 54.6l105.7 103-25 145.5c-4.5 26.3 23.2 46 46.4 33.7L288 439.6l130.7 68.7c23.2 12.2 50.9-7.4 46.4-33.7l-25-145.5 105.7-103c19-18.5 8.5-50.8-17.7-54.6L382 150.2 316.7 17.8c-11.7-23.6-45.6-23.9-57.4 0z"></path></svg>
                          </div>
                          
                          
                          Пара слов о тестировании

                          Не забывайте проверять проделанную работу!

                          Попробуйте проверить свой экстеншен "рейтинг":

                          • выражение факта поделить на 2,
                          • поменять количество звезд,
                          • сделать вычисляемое выражение в цвете с помощью If(….) или указать другой цвет в формате HEX, RGB

                          Проверяйте не только параметры extension, но и сами выражения, которые вы готовите. Если в выражении, где должно быть число, вернется текст, то появится ошибка.


                          В случае проблем с отрисовкой или в любой непонятной ситуации:

                          • заходите в инспекторе в Sources,
                          • слева в папке extensions находите свой js-файл,
                          • ставите breakpoint в начале функции paint (нажимаете пустое пространство перед номером строки с переменной),
                          • обновляете страницу, по порядку проверяя переменные и туда ли вообще идет скрипт.
                          Домашнее задание. Урок 2
                          Попробуйте доработать диаграмму "рейтинг"
                          Задача для доработки: выбор другого типа картинки. Варианты выполнения:
                          • Хардкод – прописать все типы картинок в коде. Сделать выпадающий список с выбором одного из предложенных вариантов картинок.
                          • Гибко – загрузить библиотеку шрифтов/картинок и добавить параметр с именем картинки
                          Итоги дня
                          Итак, сегодня мы разобрали теорию про гиперкубы и параметры расширений, и создали первый экстеншен. Это было минималистичное расширение, без Angular, но на его основе мы прошли все шаги разработки экстеншена на основе шаблона "chart". Завтра будем создавать с нуля еще один экстеншен - кнопку, даже несколько.
                          VS
                          Консультационная Группа АТК и Qlik-сообщество qRUG

                          2020

                          atkcg.ru
                          qrug.atkcg.ru