src/app/query/query.component.ts

Implements

AfterViewInit AfterContentInit OnDestroy

Metadata

Relationships

Index

Properties
Methods
Inputs

Constructor

constructor(sasStoreService: SasStoreService, eventService: EventService, helperService: HelperService, cdf: ChangeDetectorRef)
Parameters :
Name Type Optional
sasStoreService SasStoreService No
eventService EventService No
helperService HelperService No
cdf ChangeDetectorRef No

Inputs

caching
Type : boolean
Default value : true
viewboxId
Type : number | undefined

Methods

Public addClause
addClause(clauseIndex: number)
Parameters :
Name Type Optional
clauseIndex number No
Returns : void
Public addGroupClause
addGroupClause()
Returns : void
Public clearValues
clearValues(ind: any, clauseIndex: any)
Parameters :
Name Type Optional
ind any No
clauseIndex any No
Returns : void
Public clearValuesOperator
clearValuesOperator(ind: any, clauseIndex: any)
Parameters :
Name Type Optional
ind any No
clauseIndex any No
Returns : void
Public dateChange
dateChange(pickerValue: any, query: any, queryIndex: number, clauseIndex: number, queryValueIndex?: number)
Parameters :
Name Type Optional
pickerValue any No
query any No
queryIndex number No
clauseIndex number No
queryValueIndex number Yes
Returns : void
Public dateTimeChange
dateTimeChange(query: any, queryIndex: number, clauseIndex: number, queryValueIndex?: number)
Parameters :
Name Type Optional
query any No
queryIndex number No
clauseIndex number No
queryValueIndex number Yes
Returns : void
Public dynamicWhereClauseCreator
dynamicWhereClauseCreator(groupIndex: number, clauseIndex: number)

Takes the clause object, removes current clause and makes new array (table) ready to be sent to SAS

Parameters :
Name Type Optional
groupIndex number No
clauseIndex number No
Returns : {}
Public getFromGlobals
getFromGlobals()

Globals are used to store filtering state (variables) as a caching feature until browser reloads

Returns : void
Public getlogic
getlogic(logic: any)
Parameters :
Name Type Optional
logic any No
Returns : void
getQueryDateTime
getQueryDateTime(clauseIndex: number, queryIndex: number)

Gets and sets temporary values selected with DATETIME or TIME picker Those values are used for picker to work with format it lieks Later before sending values to backend, values are parsed

Parameters :
Name Type Optional
clauseIndex number No
queryIndex number No
Returns : QueryDateTime
Public Async getValues
getValues(variable: any, ind: any, clauseIndex: any)
Parameters :
Name Type Optional
variable any No
ind any No
clauseIndex any No
Returns : any
Public Async getValuesLoadMore
getValuesLoadMore(event: OnLoadingMoreEvent, variable: any, ind: any, clauseIndex: any)
Parameters :
Name Type Optional
event OnLoadingMoreEvent No
variable any No
ind any No
clauseIndex any No
Returns : any
Public hasInvalidCluase
hasInvalidCluase(clauses: any)

Checks if clause is invalid and sets bool property accordingly It is being used in html to mark invalid clauses red

Parameters :
Name Type Optional
clauses any No
Returns : boolean
Public hasInvalidGroupCluase
hasInvalidGroupCluase()

Checks if group clause is invalid and sets bool property accordingly It is being used in html to mark invalid group clauses red

Returns : boolean
Public isArr
isArr(val: any)
Parameters :
Name Type Optional
val any No
Returns : boolean
Public isInHtmlCollection
isInHtmlCollection(value: string, collection: HTMLCollectionOf)
Parameters :
Name Type Optional
value string No
collection HTMLCollectionOf<HTMLOptionElement> No
Returns : boolean
ngAfterContentInit
ngAfterContentInit()
Returns : void
Async ngAfterViewInit
ngAfterViewInit()
Returns : any
ngOnDestroy
ngOnDestroy()
Returns : void
onAutocompleteLoadingMore
onAutocompleteLoadingMore(event: OnLoadingMoreEvent, variable: any, ind: any, clauseIndex: any)
Parameters :
Name Type Optional
event OnLoadingMoreEvent No
variable any No
ind any No
clauseIndex any No
Returns : void
Public removeClause
removeClause(ind: number, clauseIndex: any)
Parameters :
Name Type Optional
ind number No
clauseIndex any No
Returns : void
Public removeGroupClause
removeGroupClause(clauseIndex: number)
Parameters :
Name Type Optional
clauseIndex number No
Returns : void
Public resetFilter
resetFilter()

Resets all variables used for filtering

Returns : void
Public selectedLabelChange
selectedLabelChange(label: string, query: any)
Parameters :
Name Type Optional
label string No
query any No
Returns : void
Public setGroupLogic
setGroupLogic(groupLogic: any)

Sets filtering multiple caluses group logic (and / or)

Parameters :
Name Type Optional Description
groupLogic any No

to set

Returns : void
Public setINValues
setINValues(clauseIndex: number, queryIndex: number)
Parameters :
Name Type Optional
clauseIndex number No
queryIndex number No
Returns : void
Public setLogic
setLogic()
Returns : void
Public setToGlobals
setToGlobals()

Globals are used to store filtering state (variables) as a caching feature until browser reloads

Returns : void
Public setVariableOperator
setVariableOperator(ind: any, operator: any, clauseIndex: any)
Parameters :
Name Type Optional
ind any No
operator any No
clauseIndex any No
Returns : void
Public setVariableValues
setVariableValues(valueObj: any, ind: any, clauseIndex: any)
Parameters :
Name Type Optional
valueObj any No
ind any No
clauseIndex any No
Returns : void
Public timeChange
timeChange(time: any, query: any, ind: number, clauseIndex: number, queryValueIndex?: number)
Parameters :
Name Type Optional
time any No
query any No
ind number No
clauseIndex number No
queryValueIndex number Yes
Returns : void
usePickersChange
usePickersChange()

When toggling pickers feature we reset the temp picker values array

Returns : void
Public variableInputChange
variableInputChange(queryVariable: any, index: number, clauseIndex: number, event: any)
Parameters :
Name Type Optional
queryVariable any No
index number No
clauseIndex number No
event any No
Returns : void
Public whereClauseFn
whereClauseFn(reset: boolean)
Parameters :
Name Type Optional Default value
reset boolean No false
Returns : void

Properties

Private _removeQry
Type : Subscription | undefined
Public charOperators
Type : Array<any>
Default value : [ '=', '<', '>', '<=', '>=', 'CONTAINS', 'IN', 'NOT IN', 'NE' // "LIKE", // "BEGINS_WITH", ]
Public clauseAmt
Type : Array<any>
Default value : ['']
Public clauses
Type : any
Default value : { queryObj: [this.queryObj], clauseLogic: [], groupLogic: this.groupLogic }
Public cols
Type : Array<any> | undefined
Private columnsSub
Type : Subscription | undefined
Public currentClauseIndex
Type : number
Default value : -1
Public currentQueryIndex
Type : number
Default value : -1
Public dynamicWhereClause
Type : boolean
Default value : true
Public groupLogic
Type : string
Default value : 'AND'
Private initial_rows
Type : number
Default value : 1000
Public innerWidth
Type : any
Public inObj
Type : Array<any>
Default value : [ { checked: false, value: null } ]
Public libds
Type : string | undefined
Public logic
Type : string | undefined
Public logicOperators
Type : Array<string>
Default value : ['AND', 'OR']
Public noBorder
Type : any
Public notArr
Type : boolean
Default value : true
Public notIn
Type : boolean
Default value : false
Public numOperators
Type : Array<any>
Default value : [ '=', '<', '>', '<=', '>=', 'BETWEEN', 'IN', 'NOT IN', 'NE' ]
Public operator
Type : string | undefined
Public pendingINValuesSet
Type : literal type | null
Default value : null
Public queryDateTime
Type : QueryDateTime[]
Default value : []

Temporary values array used in pickers because they need particular format to work before sending values to backend, values are parsed

Private queryObj
Type : { elements: any[]; clauseLogic?: string; invalidClause?: boolean }
Default value : { elements: [ { logic: null, type: null, ddtype: null, variable: null, operator: null, value: null, valueVariable: false, startrow: 0, rows: 0, nobs: 0, values: < { formatted: any unformatted: any }[] >[], operators: [] } ], clauseLogic: '', invalidClause: false }
Private rows_increment
Type : number
Default value : 100
Public selVar
Type : any
Default value : { index: null, format: null, type: null }
Public usePickers
Type : boolean
Default value : false
Public val
Type : any
Public vals
Type : Array<any> | undefined
Public value
Type : string | undefined
Private valuesSub
Type : Subscription | undefined
Public variable
Type : string | undefined
Public wasBetween
Type : boolean
Default value : false
Public whereClause
Type : string | undefined
Public whereClauseLoading
Type : boolean
Default value : false
Public whereString
Type : string | undefined
import { Subscription } from 'rxjs'
import {
  Component,
  AfterViewInit,
  AfterContentInit,
  OnDestroy,
  ChangeDetectorRef,
  LOCALE_ID,
  Input,
  ViewEncapsulation
} from '@angular/core'
import { SasStoreService } from '../services/sas-store.service'
import { globals } from '../_globals'
import { EventService } from '../services/event.service'

import { registerLocaleData } from '@angular/common'
import localeEnGB from '@angular/common/locales/en-GB'
import { HelperService } from '../services/helper.service'
import { QueryClause } from '../models/TableData'
import { PublicGetcolvalsSASResponse } from '../models/sas/public-getcolvals.model'
import { QueryDateTime } from './models/QueryDateTime.model'
import { isSpecialMissing } from '@sasjs/utils/input/validators'
import { get } from 'lodash-es'
import { OnLoadingMoreEvent } from '../shared/autocomplete/autocomplete.component'
import { getFilterObjPath } from './utils/getFilterObjPath'

registerLocaleData(localeEnGB)
@Component({
  selector: 'app-query',
  templateUrl: './query.component.html',
  styleUrls: ['./query.component.scss'],
  providers: [{ provide: LOCALE_ID, useValue: 'en-GB' }],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class QueryComponent
  implements AfterViewInit, AfterContentInit, OnDestroy
{
  @Input() caching: boolean = true
  @Input() viewboxId: number | undefined

  private rows_increment: number = 100
  private initial_rows: number = 1000

  private columnsSub: Subscription | undefined
  private valuesSub: Subscription | undefined
  private _removeQry: Subscription | undefined
  public cols: Array<any> | undefined
  public libds: string | undefined
  public variable: string | undefined
  public value: string | undefined
  public operator: string | undefined
  public vals: Array<any> | undefined
  public innerWidth: any
  public noBorder: any
  public clauseAmt: Array<any> = ['']
  public numOperators: Array<any> = [
    '=',
    '<',
    '>',
    '<=',
    '>=',
    'BETWEEN',
    'IN',
    'NOT IN',
    'NE'
  ]
  public charOperators: Array<any> = [
    '=',
    '<',
    '>',
    '<=',
    '>=',
    'CONTAINS',
    'IN',
    'NOT IN',
    'NE'
    // "LIKE",
    // "BEGINS_WITH",
  ]
  public logic: string | undefined
  public notIn: boolean = false
  public notArr: boolean = true
  public wasBetween: boolean = false
  public whereClauseLoading: boolean = false
  public dynamicWhereClause: boolean = true
  public usePickers: boolean = false

  private queryObj: {
    elements: any[]
    clauseLogic?: string
    invalidClause?: boolean
  } = {
    elements: [
      {
        logic: null,
        type: null,
        ddtype: null,
        variable: null,
        operator: null,
        value: null,
        valueVariable: false,
        startrow: 0,
        rows: 0,
        nobs: 0,
        values: <
          {
            formatted: any
            unformatted: any
          }[]
        >[],
        operators: []
      }
    ],
    clauseLogic: '',
    invalidClause: false
  }

  public selVar: any = {
    index: null,
    format: null,
    type: null
  }

  public inObj: Array<any> = [
    {
      checked: false,
      value: null
    }
  ]

  public groupLogic: string = 'AND'

  public clauses: any = {
    queryObj: [this.queryObj],
    clauseLogic: [],
    groupLogic: this.groupLogic
  }

  public whereString: string | undefined
  public val: any
  public whereClause: string | undefined
  public logicOperators: Array<string> = ['AND', 'OR']

  /**
   * Temporary values array used in pickers
   * because they need particular format to work
   * before sending values to backend, values are parsed
   */
  public queryDateTime: QueryDateTime[] = []

  public currentClauseIndex: number = -1
  public currentQueryIndex: number = -1
  public pendingINValuesSet: {
    clauseIndex: number
    queryIndex: number
  } | null = null

  constructor(
    private sasStoreService: SasStoreService,
    private eventService: EventService,
    private helperService: HelperService,
    private cdf: ChangeDetectorRef
  ) {
    this.innerWidth = window.screen.width
    if (this.innerWidth > 768) {
      this.noBorder = 'border-left: 2px solid gray;'
    }
  }

  /**
   * Gets and sets temporary values selected with DATETIME or TIME picker
   * Those values are used for picker to work with format it lieks
   * Later before sending values to backend, values are parsed
   */
  getQueryDateTime(clauseIndex: number, queryIndex: number): QueryDateTime {
    let existingQueryDateTime = this.queryDateTime.find(
      (x) => x.clauseIndex === clauseIndex && x.queryIndex === queryIndex
    )

    if (!existingQueryDateTime) {
      const lastIndex =
        this.queryDateTime.push({
          clauseIndex,
          queryIndex,
          date: '',
          time: ''
        }) - 1

      existingQueryDateTime = this.queryDateTime[lastIndex]
    }

    return existingQueryDateTime
  }

  /**
   * When toggling pickers feature we reset the temp picker values array
   */
  usePickersChange() {
    this.queryDateTime = []
    if (this.usePickers) {
      this.clauses.queryObj.forEach((queryObj: any) => {
        queryObj.elements.forEach((element: any) => {
          const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(
            element.ddtype
          )

          if (isDateOrTime && element.valueVariable) {
            element.value = ''
            element.valueVariable = false
          }
        })
      })
    }
  }

  /**
   * Resets all variables used for filtering
   */
  public resetFilter() {
    this.whereString = undefined
    this.whereClause = undefined

    this.queryObj = {
      elements: [
        {
          logic: null,
          type: null,
          ddtype: null,
          variable: null,
          operator: null,
          value: null,
          values: [],
          operators: []
        }
      ]
    }

    this.clauses = {
      queryObj: [this.queryObj],
      clauseLogic: [],
      groupLogic: this.groupLogic
    }

    this.whereClauseFn(true)
  }

  /**
   * `Globals` are used to store filtering state (variables) as a caching feature
   * until browser reloads
   */
  public setToGlobals() {
    if (!this.caching) return

    const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)

    get(globals, objPath).filter.groupLogic = this.groupLogic
    if (typeof this.whereClause === 'string') {
      get(globals, objPath).filter.whereClause = this.whereClause
    }
    if (typeof this.libds === 'string') {
      get(globals, objPath).filter.libds = this.libds
    }
    get(globals, objPath).filter.clauses = this.clauses
  }

  /**
   * `Globals` are used to store filtering state (variables) as a caching feature
   * until browser reloads
   */
  public getFromGlobals() {
    if (!this.caching) return

    const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)

    if (get(globals, objPath).filter.cols.length > 0) {
      this.cols = JSON.parse(JSON.stringify(get(globals, objPath).filter.cols))
    }

    if (get(globals, objPath).filter.vals.length > 0) {
      this.vals = JSON.parse(JSON.stringify(get(globals, objPath).filter.vals))
    }

    if (get(globals, objPath).filter.groupLogic !== '') {
      this.groupLogic = get(globals, objPath).filter.groupLogic
      this.whereClause = get(globals, objPath).filter.whereClause
      this.libds = get(globals, objPath).filter.libds
      this.clauses = get(globals, objPath).filter.clauses
      this.whereClauseFn()
    }
  }

  /**
   * Sets filtering multiple caluses group logic (and / or)
   *
   * @param groupLogic to set
   */
  public setGroupLogic(groupLogic: any) {
    this.groupLogic = groupLogic
    this.clauses.groupLogic = groupLogic
    this.whereClauseFn()

    this.setToGlobals()
  }

  public dateChange(
    pickerValue: any,
    query: any,
    queryIndex: number,
    clauseIndex: number,
    queryValueIndex?: number
  ) {
    let valueInDays = this.helperService.convertJsDateToSasDate(pickerValue)

    if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
      this.clauses.queryObj[clauseIndex].elements[queryIndex].value[
        queryValueIndex
      ] = valueInDays
    }

    this.setVariableValues(valueInDays, queryIndex, clauseIndex)
  }

  public dateTimeChange(
    query: any,
    queryIndex: number,
    clauseIndex: number,
    queryValueIndex?: number
  ) {
    const queryDateTime = this.getQueryDateTime(clauseIndex, queryIndex)

    if (queryDateTime.date === '') {
      if (query.value === '') return

      queryDateTime.date = query.value.split(':')[0]
    }

    const dateHours = parseInt(queryDateTime.time.split(':')[0])
    const dateMinutes = parseInt(queryDateTime.time.split(':')[1])
    const dateSeconds = parseInt(queryDateTime.time.split(':')[2]) || 0

    let dateObject = new Date(queryDateTime.date)
    let dateTimeObject = new Date(
      dateObject.getFullYear(),
      dateObject.getMonth(),
      dateObject.getDate(),
      dateHours,
      dateMinutes,
      dateSeconds
    )

    let valueInSeconds = this.helperService.convertJsDateToSasDate(
      dateTimeObject,
      'seconds'
    )

    if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
      this.clauses.queryObj[clauseIndex].elements[queryIndex].value[
        queryValueIndex
      ] = valueInSeconds
    }

    this.setVariableValues(valueInSeconds, queryIndex, clauseIndex)
  }

  public timeChange(
    time: any,
    query: any,
    ind: number,
    clauseIndex: number,
    queryValueIndex?: number
  ) {
    time = time.target.value

    let hours = parseInt(time.split(':')[0])
    let minutes = parseInt(time.split(':')[1])
    let seconds = parseInt(time.split(':')[2]) || 0

    let hourSeconds = hours * 60 * 60
    let minuteSeconds = minutes * 60

    let inputValue = hourSeconds + minuteSeconds + seconds

    if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
      this.clauses.queryObj[clauseIndex].elements[ind].value[queryValueIndex] =
        inputValue
    }

    this.setVariableValues(inputValue, ind, clauseIndex)
  }

  /**
   * Takes the clause object, removes current clause and makes new array (table)
   * ready to be sent to SAS
   */
  public dynamicWhereClauseCreator(groupIndex: number, clauseIndex: number) {
    let dynamicWhereClauseTable: QueryClause[] = []

    if (
      this.clauses.queryObj.length > 1 ||
      this.clauses.queryObj[groupIndex].elements.length > 1
    ) {
      for (
        let groupInd = 0;
        groupInd < this.clauses.queryObj.length;
        groupInd++
      ) {
        for (
          let clauseInd = 0;
          clauseInd < this.clauses.queryObj[groupInd].elements.length;
          clauseInd++
        ) {
          if (groupInd === groupIndex && clauseInd === clauseIndex) continue
          if (
            [null, undefined].includes(
              this.clauses.queryObj[groupInd].elements[clauseInd].variable
            ) ||
            [null, undefined].includes(
              this.clauses.queryObj[groupInd].elements[clauseInd].operator
            ) ||
            [null, undefined].includes(
              this.clauses.queryObj[groupInd].elements[clauseInd].value
            )
          )
            continue

          let rawValue = ''
          const element = this.clauses.queryObj[groupInd].elements[clauseInd]
          const operator = element.operator
          const variable = element.variable
          /* a temporal value is sent as a SAS literal (eg '20APR2042'd), the
             same way the applied filter carries it */
          const temporalValue = (value: any) =>
            element.valueVariable
              ? null
              : this.helperService.sasTemporalLiteral(value, element.ddtype)
          /* the dropdowns and the IN modal hold the *formatted* value (the
             soft-select option value, and setINValues), so a picked value is
             mapped back to the unformatted one the filter needs */
          const unformattedValue = (value: any) =>
            (element.values || []).find((v: any) => v.formatted === value)
              ?.unformatted ?? value

          if (operator === 'BETWEEN') {
            const from = unformattedValue(element.value[0])
            const to = unformattedValue(element.value[1])
            rawValue = `${temporalValue(from) ?? from} AND ${
              temporalValue(to) ?? to
            }`
          } else if (operator === 'IN' || operator === 'NOT IN') {
            element.value.forEach((item: any) => {
              if (item.checked) {
                /* map once, and use the unformatted value in every branch - the
                   literal is built from it, and so are the fallbacks, so a
                   formatted label can never reach RAW_VALUE */
                const value = unformattedValue(item.val)
                const literal = temporalValue(value)

                if (literal) {
                  rawValue += (rawValue.length > 0 ? ',' : '(') + literal
                } else if (
                  typeof value === 'string' &&
                  !isSpecialMissing(value)
                ) {
                  if (rawValue.length > 0) {
                    rawValue += `,'${value}'`
                  } else {
                    rawValue = `('${value}'`
                  }
                } else {
                  if (rawValue.length > 0) {
                    rawValue += `,${value}`
                  } else {
                    rawValue = `(${value}`
                  }
                }
              }
            })

            if (rawValue.length > 0) {
              rawValue += ')'
            }
          } else if (
            this.clauses.queryObj[groupInd].elements[clauseInd].value === ''
          ) {
            if (
              this.clauses.queryObj[groupInd].elements[clauseInd].type ===
              'char'
            ) {
              rawValue = `' '`
            } else {
              rawValue = '.'
            }
          } else {
            const literal = temporalValue(element.value)

            if (literal) {
              rawValue = literal
            } else if (element.type === 'char') {
              rawValue = `'${element.value}'`
            }
          }

          dynamicWhereClauseTable.push({
            GROUP_LOGIC: this.clauses.groupLogic,
            SUBGROUP_LOGIC:
              this.clauses.queryObj[groupInd].clauseLogic || 'AND',
            SUBGROUP_ID: groupInd,
            VARIABLE_NM: variable,
            OPERATOR_NM: operator,
            RAW_VALUE:
              rawValue ||
              this.clauses.queryObj[groupInd].elements[clauseInd].value
          })
        }
      }
    }

    return dynamicWhereClauseTable
  }

  public async getValuesLoadMore(
    event: OnLoadingMoreEvent,
    variable: any,
    ind: any,
    clauseIndex: any
  ) {
    const libds = this.libds || ''

    try {
      if (this.cols !== undefined) {
        for (let index = 0; index < this.cols.length; index++) {
          let element = this.cols[index]
          if (element.NAME === variable) {
            this.selVar.index = index
          }
        }

        const DDTYPE = this.cols[this.selVar.index].DDTYPE

        const { startrow: currentStartrow, rows: currentRows } =
          this.clauses.queryObj[clauseIndex].elements[ind]

        const ROWS_INCREMENT: number = this.rows_increment
        const STARTROW = currentStartrow + currentRows

        let dynamicWhereClauseTable: any[] = []

        if (this.dynamicWhereClause) {
          dynamicWhereClauseTable = this.dynamicWhereClauseCreator(
            clauseIndex,
            ind
          )
        }

        const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(DDTYPE)

        this.sasStoreService
          .getQueryValues(
            variable,
            libds,
            dynamicWhereClauseTable,
            STARTROW,
            ROWS_INCREMENT
          )
          .then((res: PublicGetcolvalsSASResponse) => {
            const values = res.vals
            const { STARTROW, ROWS, NOBS } = res.meta[0]

            this.clauses.queryObj[clauseIndex].elements[ind].startrow = STARTROW
            this.clauses.queryObj[clauseIndex].elements[ind].rows = ROWS
            this.clauses.queryObj[clauseIndex].elements[ind].nobs = NOBS

            if (
              !(
                values.length === 1 &&
                values[0].FORMATTED === '' &&
                values[0].UNFORMATTED === ''
              )
            ) {
              this.clauses.queryObj[clauseIndex].elements[ind].values.push(
                ...values.map((item: any) => {
                  const numeric = res.$vals.vars.UNFORMATTED.type === 'num'
                  const specialMissing =
                    numeric && isSpecialMissing(item.UNFORMATTED)

                  let unformatted = specialMissing
                    ? '.' + item.UNFORMATTED
                    : item.UNFORMATTED

                  // If it's not special missing and is numeric and null then it is normal missing `.`
                  if (numeric && unformatted === null) unformatted = '.'

                  return {
                    formatted: isDateOrTime
                      ? item.FORMATTED.split('.')[0]
                      : item.FORMATTED, //WORKAROUND SHOULD BE FIXED PROPERLY (#413): We will remove the milliseconds inside the formatted value, if decimal exists and it is date, time or datetime ddtype
                    unformatted: unformatted
                  }
                })
              )

              const currentValues =
                this.clauses.queryObj[clauseIndex].elements[ind].values.length
              const moreExists = NOBS - currentValues > 0

              event.loadMoreFinished(moreExists)
            } else {
              event.loadMoreFinished(false)
            }

            this.whereClauseFn()
            this.setToGlobals()
            this.whereClauseLoading = false

            /**
             * If getValues request did not finish when operator was changed to IN, we need to set values after the request is finished.
             * Othervise values are not beign populated
             */
            if (this.pendingINValuesSet !== null) {
              const { clauseIndex, queryIndex } = this.pendingINValuesSet

              this.setINValues(clauseIndex, queryIndex)

              this.pendingINValuesSet = null
            }

            this.cdf.detectChanges()
          })
          .catch((err: any) => {
            this.whereClauseLoading = false
          })
      }
    } catch (error) {
      this.eventService.catchResponseError('public/getcolvals', error)
    }
  }

  public async getValues(variable: any, ind: any, clauseIndex: any) {
    this.whereClauseLoading = true

    let libds: string = ''

    if (this.libds !== undefined) {
      libds = this.libds
    }

    this.clearValues(ind, clauseIndex)
    this.clauses.queryObj[clauseIndex].elements[ind].values = []

    this.clauses.queryObj[clauseIndex].elements[ind].value = ''
    this.clauses.queryObj[clauseIndex].elements[ind].operator = '='
    this.clauses.queryObj[clauseIndex].elements[ind].variable = variable

    try {
      if (this.cols !== undefined) {
        for (let index = 0; index < this.cols.length; index++) {
          let element = this.cols[index]
          if (element.NAME === variable) {
            this.selVar.index = index
          }
        }

        const DDTYPE = this.cols[this.selVar.index].DDTYPE

        this.clauses.queryObj[clauseIndex].elements[ind].ddtype = DDTYPE

        this.clauses.queryObj[clauseIndex].elements[ind].type =
          this.cols[this.selVar.index].TYPE

        if (this.clauses.queryObj[clauseIndex].elements[ind].type === 'num') {
          this.clauses.queryObj[clauseIndex].elements[ind].operators =
            this.numOperators
        } else {
          this.clauses.queryObj[clauseIndex].elements[ind].operators =
            this.charOperators
        }

        let dynamicWhereClauseTable: any[] = []

        if (this.dynamicWhereClause) {
          dynamicWhereClauseTable = this.dynamicWhereClauseCreator(
            clauseIndex,
            ind
          )
        }

        const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(DDTYPE)

        this.sasStoreService
          .getQueryValues(
            variable,
            libds,
            dynamicWhereClauseTable,
            1,
            this.initial_rows
          )
          .then((res: PublicGetcolvalsSASResponse) => {
            const values = res.vals
            const { STARTROW, ROWS, NOBS } = res.meta[0]

            this.clauses.queryObj[clauseIndex].elements[ind].startrow = STARTROW
            this.clauses.queryObj[clauseIndex].elements[ind].rows = ROWS
            this.clauses.queryObj[clauseIndex].elements[ind].nobs = NOBS

            this.clauses.queryObj[clauseIndex].elements[ind].values = values
              .map((item: any) => {
                const numeric = res.$vals.vars.UNFORMATTED.type === 'num'
                const specialMissing =
                  numeric && isSpecialMissing(item.UNFORMATTED)

                let unformatted = specialMissing
                  ? '.' + item.UNFORMATTED
                  : item.UNFORMATTED

                // If it's not special missing and is numeric and null then it is normal missing `.`
                if (numeric && unformatted === null) unformatted = '.'

                return {
                  formatted: isDateOrTime
                    ? item.FORMATTED.split('.')[0]
                    : item.FORMATTED, //WORKAROUND SHOULD BE FIXED PROPERLY (#413): We will remove the milliseconds inside the formatted value, if decimal exists and it is date, time or datetime ddtype
                  unformatted: unformatted
                }
              })
              .slice(0, 2000)

            this.whereClauseFn()
            this.setToGlobals()
            this.whereClauseLoading = false

            /**
             * If getValues request did not finish when operator was changed to IN, we need to set values after the request is finished.
             * Othervise values are not beign populated
             */
            if (this.pendingINValuesSet !== null) {
              const { clauseIndex, queryIndex } = this.pendingINValuesSet

              this.setINValues(clauseIndex, queryIndex)

              this.pendingINValuesSet = null
            }

            this.cdf.detectChanges()
          })
          .catch((err: any) => {
            this.whereClauseLoading = false
          })
      }
    } catch (error) {
      this.eventService.catchResponseError('public/getcolvals', error)
    }
  }

  public getlogic(logic: any) {
    this.logic = logic
    this.whereClauseFn()
  }

  public setLogic() {
    this.whereClauseFn()
    this.setToGlobals()
  }

  public isArr(val: any) {
    return (
      val instanceof Array &&
      val.length > 0 &&
      typeof val[0].checked !== 'undefined'
    )
  }

  public selectedLabelChange(label: string, query: any) {
    query.valueVariable = label === 'Variable'
    query.value = ''
    this.whereClauseFn()
  }

  public variableInputChange(
    queryVariable: any,
    index: number,
    clauseIndex: number,
    event: any
  ) {
    this.getValues(queryVariable, index, clauseIndex)
  }

  public setVariableValues(valueObj: any, ind: any, clauseIndex: any) {
    // clauseIndex is current clause in group clauses, ind is current query in clause
    let operator = this.clauses.queryObj[clauseIndex].elements[ind].operator

    if (operator !== 'BETWEEN' && operator !== 'IN' && operator !== 'NOT IN') {
      this.clauses.queryObj[clauseIndex].elements[ind].value =
        valueObj.toString()
    }
    this.whereClauseFn()
    this.setToGlobals()
  }

  public setVariableOperator(ind: any, operator: any, clauseIndex: any) {
    let currentValue = this.clauses.queryObj[clauseIndex].elements[ind].value
    if (
      currentValue instanceof Array &&
      operator !== 'IN' &&
      operator !== 'NOT IN'
    ) {
      if (
        currentValue instanceof Array &&
        currentValue.length === 2 &&
        typeof currentValue[0] === 'string'
      ) {
        currentValue = currentValue[0]
      } else {
        currentValue = ''
      }
    }

    this.clearValuesOperator(ind, clauseIndex)
    this.clauses.queryObj[clauseIndex].elements[ind].operator = operator
    // tslint:disable-next-line:max-line-length
    if (
      operator === '=' ||
      operator === '>' ||
      operator === '<' ||
      operator === '<=' ||
      operator === '>=' ||
      operator === 'NE'
    ) {
      this.clauses.queryObj[clauseIndex].elements[ind].value = currentValue
    }

    if (operator === 'BETWEEN') {
      this.clauses.queryObj[clauseIndex].elements[ind].value = []
      this.clauses.queryObj[clauseIndex].elements[ind].value.push(currentValue)
      this.clauses.queryObj[clauseIndex].elements[ind].value.push('')
    }

    if (operator === 'IN' || operator === 'NOT IN') {
      this.setINValues(clauseIndex, ind)
    }

    this.whereClauseFn()
    this.setToGlobals()
  }

  public setINValues(clauseIndex: number, queryIndex: number) {
    const vals = []

    const inValues =
      this.clauses.queryObj[clauseIndex].elements[queryIndex].values

    if (inValues.length < 1) {
      this.pendingINValuesSet = {
        clauseIndex,
        queryIndex
      }
    }

    for (let index = 0; index < inValues.length; index++) {
      vals.push({ checked: false, val: inValues[index].formatted })
    }

    this.clauses.queryObj[clauseIndex].elements[queryIndex].value = vals
  }

  public clearValues(ind: any, clauseIndex: any) {
    this.clauses.queryObj[clauseIndex].elements[ind].value = ''
  }

  public clearValuesOperator(ind: any, clauseIndex: any) {
    let operator = this.clauses.queryObj[clauseIndex].elements[ind].operator
    if (
      operator === 'BETWEEN' ||
      operator === 'IN' ||
      operator === 'NOT IN' ||
      operator === 'CONTAINS'
    ) {
      this.clearValues(ind, clauseIndex)
    }
  }

  /**
   * Checks if clause is invalid and sets bool property accordingly
   * It is being used in html to mark invalid clauses red
   */
  public hasInvalidCluase(clauses: any): boolean {
    for (let clause of clauses) {
      clause['invalidClause'] = false

      if (
        clause.value === '' &&
        !(clause.operator === 'NE' || clause.operator === 'CONTAINS')
      ) {
        clause['invalidClause'] = true

        return true
      }

      if (
        clause.variable === null ||
        clause.operator === null ||
        clause.value === null
      ) {
        clause['invalidClause'] = true

        return true
      }
    }

    clauses.invalidClause = false

    return false
  }

  /**
   * Checks if group clause is invalid and sets bool property accordingly
   * It is being used in html to mark invalid group clauses red
   */
  public hasInvalidGroupCluase(): boolean {
    for (let i = 0; i < this.clauses.queryObj.length; i++) {
      if (this.hasInvalidCluase(this.clauses.queryObj[i].elements)) {
        this.clauses.queryObj[i].invalidClause = true

        return true
      } else {
        this.clauses.queryObj[i].invalidClause = false
      }
    }

    return false
  }

  public addClause(clauseIndex: number) {
    if (this.hasInvalidCluase(this.clauses.queryObj[clauseIndex].elements)) {
      this.eventService.showInfoModal(
        'Error',
        'Cannot add new clause, when one or more clauses are invalid or empty.'
      )
      return
    }

    this.clauseAmt.push('')
    this.clauses.queryObj[clauseIndex].elements.push({
      logic: null,
      type: null,
      variable: null,
      operator: null,
      value: null,
      values: [],
      operators: []
    })

    let clauseLogic = this.clauses.queryObj[clauseIndex].clauseLogic

    if (typeof clauseLogic === 'undefined') {
      this.clauses.queryObj[clauseIndex].clauseLogic = 'AND'
    } else if (clauseLogic === 'OR') {
      this.clauses.queryObj[clauseIndex].clauseLogic = 'OR'
    } else {
      this.clauses.queryObj[clauseIndex].clauseLogic = 'AND'
    }

    this.whereClauseFn()
    this.setToGlobals()
  }

  public addGroupClause() {
    if (this.hasInvalidGroupCluase()) {
      this.eventService.showInfoModal(
        'Error',
        'Cannot add new clause, when one or more clauses are invalid or empty.'
      )
      return
    }

    this.clauses.queryObj.push({
      elements: [
        {
          logic: null,
          type: null,
          variable: null,
          operator: null,
          value: null,
          values: [],
          operators: []
        }
      ]
    })
    this.setToGlobals()
  }

  public isInHtmlCollection(
    value: string,
    collection: HTMLCollectionOf<HTMLOptionElement>
  ): boolean {
    for (let i = 0; i < collection.length; i++) {
      if (collection[i].value === value) return true
    }

    return false
  }

  public removeGroupClause(clauseIndex: number) {
    this.clauses.queryObj.splice(clauseIndex, 1)
    this.whereClauseFn()
  }

  public removeClause(ind: number, clauseIndex: any) {
    this.clauses.queryObj[clauseIndex].elements.splice(ind, 1)
    this.whereClauseFn()
    this.setToGlobals()
  }

  public whereClauseFn(reset: boolean = false) {
    const clauses = this.helperService.deepClone(this.clauses)

    if (reset) clauses.queryObj[0].elements = []

    if (this.libds !== undefined) {
      let result = this.sasStoreService.whereClauseCreator(
        clauses,
        this.groupLogic,
        this.libds
      )

      if (!reset) {
        this.whereClause = result.whereClause
        this.whereString = result.string
      }
    }
  }

  async ngAfterViewInit() {
    this._removeQry = this.sasStoreService.removeQuery.subscribe((remove) => {
      let res = remove
      if (this.clauses.queryObj.length >= 1) {
        this.clauses.queryObj.length = 1
        if (this.clauses.queryObj[0].elements.length >= 1) {
          this.clauses.queryObj[0].elements.length = 1
          this.clauses.queryObj[0].elements = []
          this.clauses.queryObj[0].elements.push({
            logic: null,
            type: null,
            variable: null,
            operator: null,
            value: null,
            values: [],
            operators: []
          })
          this.whereClauseFn()
        }
      }
    })

    this.columnsSub = this.sasStoreService.columns.subscribe(
      (response: any) => {
        let cols = response.data.cols
        const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)

        if (globals.rootParam === 'home' || globals.rootParam === 'editor') {
          this.cols = cols
          let some = cols[0].NAME
          this.libds = response.libds

          get(globals, objPath).filter.cols = JSON.parse(JSON.stringify(cols))
        }

        if (globals.rootParam === 'view') {
          if (get(globals, objPath).filter.cols.length < 1) {
            this.cols = cols
            let some = cols[0].NAME
            this.libds = response.libds

            get(globals, objPath).filter.cols = JSON.parse(JSON.stringify(cols))
          }
        }

        if (!this.libds) {
          this.libds = response.libds
        }
      }
    )

    this.valuesSub = this.sasStoreService.values.subscribe((res: any) => {
      const objPath = getFilterObjPath(globals.rootParam, this.viewboxId)

      if (get(globals, objPath).filter.vals.length < 1) {
        this.vals = res.vals

        get(globals, objPath).filter.vals = JSON.parse(JSON.stringify(res.vals))
      }
    })
  }

  onAutocompleteLoadingMore(
    event: OnLoadingMoreEvent,
    variable: any,
    ind: any,
    clauseIndex: any
  ) {
    this.getValuesLoadMore(event, variable, ind, clauseIndex)
  }

  ngAfterContentInit(): void {
    this.getFromGlobals()

    setTimeout(() => {
      let varInput: HTMLElement | null =
        document.querySelector('#vals_var_id0_0')

      if (varInput) varInput.focus()
    }, 500)
  }

  ngOnDestroy(): void {
    if (this.columnsSub) {
      this.columnsSub.unsubscribe()
    }

    if (this.valuesSub) {
      this.valuesSub.unsubscribe()
    }
  }
}
<div class="clr-flex-row clr-form-control m-0">
  <div class="clr-control-container">
    <clr-checkbox-wrapper>
      <input clrCheckbox [(ngModel)]="dynamicWhereClause" type="checkbox" />
      <label class="clr-control-label">Dynamic where clause</label>
    </clr-checkbox-wrapper>
  </div>

  <div class="clr-control-container ml-15">
    <clr-checkbox-wrapper>
      <input
        clrCheckbox
        [(ngModel)]="usePickers"
        (change)="usePickersChange()"
        type="checkbox"
        id="usePickers"
      />
      <label class="clr-control-label"> Use pickers </label>
    </clr-checkbox-wrapper>
  </div>
</div>

<div class="clr-row">
  <div class="select clr-col-md-2 mb-30">
    <clr-select-container>
      <label>Group logic</label>
      <select
        [disabled]="clauses?.queryObj?.length < 1"
        [(ngModel)]="groupLogic"
        (ngModelChange)="setGroupLogic(groupLogic)"
        clrSelect
      >
        @for (logic of logicOperators; track logic) {
          <option [selected]="logicOperators[0]">
            {{ logic }}
          </option>
        }
      </select>
    </clr-select-container>
  </div>
  <div class="clr-col-md-10 mb-30">
    <pre class="line-numbers language-markup">
      @if (whereClauseLoading) {
        <div class="progresStatic progress loop">
          <progress></progress>
        </div>
      }

      <code class="language-sql">{{whereClause}}</code>
    </pre>
  </div>
</div>

<div class="content">
  <div
    class="clauses-container clr-col-md-12"
    [class.clr-col-md-10]="clauses?.queryObj?.length > 1"
  >
    @if (clauses?.queryObj?.length > 1) {
      <div></div>
    }

    @for (clause of clauses.queryObj; track clause; let clauseIndex = $index) {
      <div class="clause-row">
        <div class="clr-row" [class.invalid-clause]="clause.invalidClause">
          <div class="clause-logic clr-col-md-2">
            <div class="select">
              <clr-select-container>
                <label>Logic</label>
                <select
                  [(ngModel)]="clause.clauseLogic"
                  (ngModelChange)="setLogic()"
                  [disabled]="clause.elements.length < 2"
                  clrSelect
                >
                  @for (logic of logicOperators; track logic) {
                    <option [selected]="logicOperators[0]">
                      {{ logic }}
                    </option>
                  }
                </select>
              </clr-select-container>
            </div>
            <br />
            @if (innerWidth > 768) {
              <button
                class="btn btn-primary btn-block mt-10"
                (click)="addGroupClause()"
              >
                <clr-icon shape="plus"></clr-icon>
                <span>Group</span>
              </button>
            }
          </div>
          <div class="clause-query clr-col-md-10">
            @if (clauses.queryObj.length > 1) {
              <clr-icon
                (click)="removeGroupClause(clauseIndex)"
                shape="times"
                size="36"
                class="remove-group-clause-button"
              ></clr-icon>
            }
            @for (
              query of clause.elements;
              track query;
              let queryIndex = $index
            ) {
              <div class="clr-row" [class.invalid-clause]="query.invalidClause">
                <!-- VARIABLE -->
                <div class="variable-col form-group clr-col-md-3">
                  <div class="datalist-wrapper">
                    <app-soft-select
                      label="Variable"
                      [id]="
                        'select_vals_var_id' + queryIndex + '_' + clauseIndex
                      "
                      [inputId]="'vals_var_id' + queryIndex + '_' + clauseIndex"
                      [emitOnlySelected]="true"
                      [(value)]="query.variable"
                      (onInputEvent)="
                        variableInputChange(
                          query.variable,
                          queryIndex,
                          clauseIndex,
                          $event
                        )
                      "
                    >
                      @for (column of cols; track column) {
                        <option>
                          {{ column.NAME }}
                        </option>
                      }
                    </app-soft-select>
                  </div>
                </div>
                <!-- OPERATOR -->
                <div class="operator-col form-group clr-col-md-3">
                  <clr-select-container>
                    <label>Operator</label>
                    <select
                      [(ngModel)]="query.operator"
                      (ngModelChange)="
                        setVariableOperator(
                          queryIndex,
                          query.operator,
                          clauseIndex
                        )
                      "
                      clrSelect
                    >
                      @for (opr of query.operators; track opr) {
                        <option>{{ opr }}</option>
                      }
                    </select>
                  </clr-select-container>
                </div>
                <!-- VALUE -->
                <div
                  *ngVar="
                    query.ddtype === 'DATE' ||
                    query.ddtype === 'DATETIME' ||
                    query.ddtype === 'TIME' as isDateTime
                  "
                  class="value-col form-group clr-col-md-3"
                >
                  @if (query.operator === 'IN' || query.operator === 'NOT IN') {
                    <div class="checkbox-vals">
                      <button
                        (click)="
                          currentQueryIndex = queryIndex;
                          currentClauseIndex = clauseIndex
                        "
                        type="button"
                        class="btn btn-link"
                      >
                        Choose values
                      </button>
                      <ng-container
                        *ngTemplateOutlet="
                          checkboxValues;
                          context: {
                            query: query,
                            queryIndex: queryIndex,
                            clauseIndex: clauseIndex
                          }
                        "
                      >
                      </ng-container>
                    </div>
                  }
                  @if (
                    query.operator !== 'BETWEEN' &&
                    query.operator !== 'IN' &&
                    query.operator !== 'NOT IN' &&
                    query.operator !== 'LIKE' &&
                    query.operator !== 'CONTAINS' &&
                    query.operator !== 'BEGINS_WITH'
                  ) {
                    <div class="single-field-vals">
                      <ng-container
                        *ngTemplateOutlet="
                          isDateTime && usePickers ? picker : notPicker;
                          context: {
                            query: query,
                            queryIndex: queryIndex,
                            clauseIndex: clauseIndex,
                            isDateTime: isDateTime
                          }
                        "
                      >
                        <!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
                      </ng-container>
                    </div>
                  }
                  @if (query.operator === 'BETWEEN') {
                    <div class="range-vals">
                      <div class="from">
                        <ng-container
                          *ngTemplateOutlet="
                            isDateTime && usePickers ? picker : notPickerRange;
                            context: {
                              range: 'start',
                              query: query,
                              queryValueIndex: 0,
                              queryIndex: queryIndex,
                              clauseIndex: clauseIndex,
                              isDateTime: isDateTime
                            }
                          "
                        >
                          <!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
                        </ng-container>
                      </div>
                      <div class="to">
                        <ng-container
                          *ngTemplateOutlet="
                            isDateTime && usePickers ? picker : notPickerRange;
                            context: {
                              range: 'end',
                              query: query,
                              queryValueIndex: 1,
                              queryIndex: queryIndex,
                              clauseIndex: clauseIndex,
                              isDateTime: isDateTime
                            }
                          "
                        >
                          <!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
                        </ng-container>
                      </div>
                    </div>
                  }
                  @if (
                    query.operator === 'LIKE' ||
                    query.operator === 'BEGINS_WITH' ||
                    query.operator === 'CONTAINS'
                  ) {
                    <div class="contains-vals">
                      <label class="clr-control-label">Value</label>
                      <input
                        class="input-val"
                        type="text"
                        [(ngModel)]="query.value"
                        (ngModelChange)="
                          setVariableValues($event, queryIndex, clauseIndex)
                        "
                      />
                    </div>
                  }
                </div>
                <div class="clause-buttons clr-col-md-2 btn-group">
                  <button
                    class="btn btn-warning btn-block"
                    (click)="removeClause(queryIndex, clauseIndex)"
                    [disabled]="
                      clauses.queryObj[clauseIndex].elements.length === 1
                    "
                  >
                    <clr-icon shape="minus"></clr-icon>
                    <span></span>
                  </button>
                  <button
                    class="btn btn-success btn-block"
                    (click)="addClause(clauseIndex)"
                  >
                    <clr-icon shape="plus"></clr-icon>
                    <span></span>
                  </button>
                </div>
              </div>
            }
          </div>
        </div>
        <br />
      </div>
    }
  </div>
</div>

<!-- If type is DATE, DATETIME or TIME -->
<ng-template
  #picker
  let-query="query"
  let-queryValueIndex="queryValueIndex"
  let-queryIndex="queryIndex"
  let-clauseIndex="clauseIndex"
>
  @switch (query.ddtype) {
    @case ('DATE') {
      <app-soft-select
        label="Value"
        type="date"
        [disableSoftselect]="true"
        [inputId]="'vals_date' + queryIndex + '_' + clauseIndex"
        [value]="
          queryValueIndex !== undefined
            ? (query.value[queryValueIndex] | sasToJsDate)
            : (query.value | sasToJsDate)
        "
        (valueChange)="
          dateChange($event, query, queryIndex, clauseIndex, queryValueIndex)
        "
      >
        <!-- In case we want to enable dropdown on the pickers, uncomment below -->
        <!-- <ng-container *ngFor="let value of query.values">
        <option *ngIf="value.unformatted !== null">
          {{ value.formatted | dateTimeFormatter: 'date' }}
        </option>
      </ng-container> -->
      </app-soft-select>
    }
    @case ('DATETIME') {
      <app-soft-select
        label="Value"
        type="date"
        [disableSoftselect]="true"
        [inputId]="'vals_date' + queryIndex + '_' + clauseIndex"
        [value]="
          queryValueIndex !== undefined
            ? (query.value[queryValueIndex] | sasToJsDate: 'seconds')
            : (query.value | sasToJsDate: 'seconds')
        "
        (valueChange)="getQueryDateTime(clauseIndex, queryIndex).date = $event"
        (onInputEvent)="
          dateTimeChange(query, queryIndex, clauseIndex, queryValueIndex)
        "
      >
        <!-- In case we want to enable dropdown on the pickers, uncomment below -->
        <!-- <ng-container *ngFor="let value of query.values">
      <option *ngIf="value.unformatted !== null">
        {{ value.formatted | dateTimeFormatter: 'date' }}
      </option>
    </ng-container> -->
      </app-soft-select>
      <app-soft-select
        type="time"
        [disableSoftselect]="true"
        [inputId]="'vals_time' + queryIndex + '_' + clauseIndex"
        [value]="
          queryValueIndex !== undefined
            ? (query.value[queryValueIndex] | sasToJsDate: 'seconds' : true)
            : (query.value | sasToJsDate: 'seconds' : true)
        "
        (valueChange)="getQueryDateTime(clauseIndex, queryIndex).time = $event"
        (onInputEvent)="
          dateTimeChange(query, queryIndex, clauseIndex, queryValueIndex)
        "
      >
        <!-- In case we want to enable dropdown on the pickers, uncomment below -->
        <!-- <ng-container *ngFor="let value of query.values">
    <option *ngIf="value.unformatted !== null">
      {{ value.formatted | dateTimeFormatter: 'time' }}
    </option>
  </ng-container> -->
      </app-soft-select>
    }
    @case ('TIME') {
      <app-soft-select
        label="Value"
        type="time"
        [disableSoftselect]="true"
        [inputId]="'vals_' + queryIndex + '_' + clauseIndex"
        [value]="
          queryValueIndex !== undefined
            ? (query.value[queryValueIndex] | secondsParser)
            : (query.value | secondsParser)
        "
        (onInputEvent)="
          timeChange($event, query, queryIndex, clauseIndex, queryValueIndex)
        "
      >
        <!-- In case we want to enable dropdown on the pickers, uncomment below -->
        <!-- <ng-container *ngFor="let value of query.values">
    <option *ngIf="value.unformatted !== null">
      {{ value.formatted | dateTimeFormatter: 'time' }}
    </option>
  </ng-container> -->
      </app-soft-select>
    }
  }
</ng-template>

<ng-template
  #notPicker
  let-isDateTime="isDateTime"
  let-query="query"
  let-queryIndex="queryIndex"
  let-clauseIndex="clauseIndex"
>
  <app-soft-select
    label="Value"
    [secondLabel]="'Variable'"
    [emitOnlySelected]="query.valueVariable"
    [inputId]="'vals_' + queryIndex + '_' + clauseIndex"
    (selectedLabelChange)="selectedLabelChange($event, query)"
    [(value)]="query.value"
    [enableLoadMore]="query.nobs > query.values.length"
    (onInputEvent)="
      setVariableValues($event.target.value, queryIndex, clauseIndex)
    "
    (onAutocompleteLoadingMore)="
      onAutocompleteLoadingMore($event, query.variable, queryIndex, clauseIndex)
    "
  >
    @if (!query.valueVariable) {
      <div>
        @for (column of query.values; track column) {
          <option [value]="column.unformatted">
            {{ column.formatted.trim() }}
          </option>
        }
      </div>
    }

    @if (query.valueVariable) {
      <div>
        @for (column of cols; track column) {
          @if (column.TYPE === query.type) {
            <option [value]="column.NAME">
              {{ column.NAME }}
            </option>
          }
        }
      </div>
    }
  </app-soft-select>
</ng-template>

<ng-template
  #notPickerRange
  let-range="range"
  let-query="query"
  let-queryValueIndex="queryValueIndex"
  let-queryIndex="queryIndex"
  let-clauseIndex="clauseIndex"
>
  <app-soft-select
    label="Value"
    [inputId]="'vals-' + range + '_' + queryIndex + '_' + clauseIndex"
    [(value)]="query.value[queryValueIndex]"
    [enableLoadMore]="query.nobs > query.values.length"
    (onInputEvent)="setVariableValues(query.value, queryIndex, clauseIndex)"
    (onAutocompleteLoadingMore)="
      onAutocompleteLoadingMore($event, query.variable, queryIndex, clauseIndex)
    "
    [disabled]="
      queryValueIndex !== undefined
        ? range !== 'start'
          ? !query.value[0]
          : false
        : false
    "
  >
    @for (column of query.values; track column) {
      <option [value]="column.formatted">
        {{ column.formatted }}
      </option>
    }
  </app-soft-select>
</ng-template>

<ng-template
  #checkboxValues
  let-query="query"
  let-queryIndex="queryIndex"
  let-clauseIndex="clauseIndex"
>
  <clr-modal
    [clrModalOpen]="
      currentQueryIndex === queryIndex && currentClauseIndex === clauseIndex
    "
    [clrModalClosable]="false"
    class="in-values-modal"
  >
    <h3 class="modal-title">Select values</h3>
    <div class="modal-body">
      @if (!isArr(query.value)) {
        <h5 class="no-values">No values available.</h5>
      }

      @if (isArr(query.value)) {
        <section class="form-block">
          <clr-checkbox-container>
            @for (column of query.values; track column; let i = $index) {
              <clr-checkbox-wrapper>
                <input
                  type="checkbox"
                  clrCheckbox
                  [(ngModel)]="query.value[i].checked"
                  (ngModelChange)="
                    setVariableValues($event, queryIndex, clauseIndex)
                  "
                />
                <label>
                  {{ column.formatted }}
                </label>
              </clr-checkbox-wrapper>
            }
          </clr-checkbox-container>
        </section>
      }
    </div>
    <div class="modal-footer">
      <button
        (click)="currentQueryIndex = -1; currentClauseIndex = -1"
        type="button"
        class="btn btn-outline"
      >
        Apply
      </button>
    </div>
  </clr-modal>
</ng-template>

./query.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""