Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Score big with last-minute savings on the final tickets to FabCon Vienna. Secure your discount

Reply
lulalagulu
Regular Visitor

Custom Power BI visual (date picker slicer) not work with SELECTEDVALUE()

Problem Description

Issue: Custom Power BI visual (date picker slicer) cannot properly integrate with Power Query parameters and DAX functions like 

SELECTEDVALUE()

 and 

VALUES()

.

 

Context:

  • Created a custom Power BI visual that functions as a date picker slicer

  • The visual is intended to work with Power Query parameters to filter data

  • When users select a date in the custom visual, it should update the parameter value

  • However, 

    SELECTEDVALUE(column)
     and 
    VALUES(column)
     return 
    BLANK()
     instead of the selected date value

     

  • Interestingly, 

    ISFILTERED(column)
     returns 
    TRUE
    , indicating that filtering is working but selection context is not

     

Expected Behavior:

  • Built-in Power BI slicers work perfectly with parameters - when a user selects a value, 

    SELECTEDVALUE()
     returns that value

     

  • Custom visual should behave similarly to built-in slicers

Attempted Solutions

1. JSON Filter Approach

  • Used 

    host.applyJsonFilter()
     with both Basic and Advanced filter types

     

  • Tried different filter operators (

    In
    , 
    Is
    )

     

  • Experimented with various date formats (ISO strings, original date strings)

  • Used both 

    FilterAction.merge
     and 
    FilterAction.replace

     

  • Added 

    requireSingleSelection: true
     for Basic filters

     

2. Selection Manager Approach

  • Implemented 

    ISelectionManager.select()
     alongside JSON filters

     

  • Created proper 

    ISelectionId
     objects using 
    createSelectionIdBuilder().withCategory()

     

  • Ensured single selection by calling 

    clear()
     before 
    select()

     

  • Mapped date values to selection IDs using consistent date formatting

3. Hybrid Approach

  • Combined both JSON Filter and Selection Manager methods

  • Applied JSON filters for Power Query parameter integration

  • Used Selection Manager for DAX function compatibility

  • Synchronized date formats between both approaches

4. Date Format Consistency

  • Ensured consistent date formatting across all methods

  • Used both ISO format (

    YYYY-MM-DD
    ) and custom format keys

     

  • Handled different input date types (string, number, Date objects)

5. Filter Configuration Variations

  • Tested different filter schemas and configurations

  • Experimented with logical operators and condition structures

  • Tried multiple filter targets and naming conventions

Current Status

What Works:

  • Power Query parameters receive filter values correctly

  •  

    ISFILTERED()
     returns 
    TRUE
    , confirming filter context exists

     

  • Visual filtering functionality works as expected

  • Date selection and UI interaction work properly

What Doesn't Work:

  •  

    SELECTEDVALUE()
     consistently returns 
    BLANK()

     

  •  

    VALUES()
     doesn't return the selected date value

     

  • DAX functions cannot access the selection context created by the custom visual

Root Cause Hypothesis

The issue appears to be a fundamental limitation of Power BI's custom visual architecture. Built-in slicers have special privileges and mechanisms to create proper DAX selection context, while custom visuals may be restricted in their ability to fully replicate this behavior. The custom visual can create filter context (hence 

ISFILTERED()

 works) but cannot establish the single-value selection context required by 

SELECTEDVALUE()

.

 

This suggests that custom visuals and built-in slicers operate through different mechanisms in Power BI's filtering and selection system, with built-in components having deeper integration with the DAX evaluation engine.

2 REPLIES 2
johnbasha33
Super User
Super User

Hi  

Short version: custom visuals can push filter context (so ISFILTERED() goes TRUE), but they don’t get the same “slicer-privileged” path that guarantees a single-value selection context for DAX. SELECTEDVALUE() only returns a value when the engine truly sees exactly one value in context; otherwise it returns BLANK—even if your visual UI shows one date.

What to check/fix:

  1. Target the exact model column
    Make sure your applyJsonFilter targets the same column you use in DAX, not a display/derived field. Use a Basic (or Advanced) filter with a single value and isInverted:false. Doc: Visual Filter API. Microsoft Learn

  2. Bind it via capabilities (not just ad-hoc)
    In capabilities.json, include a general.filter property so the filter is persisted and recognized as a visual filter:


    "objects": { "general": { "properties": { "filter": { "type": { "filter": {} } } } } }

    (And then call host.applyJsonFilter(filter, "general", "filter", powerbi.FilterAction.merge).) Example patterns are in the Sample Slicer. GitHubMicrosoft Learn

    Prove whether it’s single-select from DAX’s point of view
    Create:

    HowMany = COUNTROWS(VALUES('DimDate'[Date]))
    Picked = SELECTEDVALUE('DimDate'[Date])

    If HowMany ≠ 1, Picked will be BLANK. That means your filter didn’t collapse to one value in the model (commonly because of hidden hierarchies, formatting mismatches, wrong target column, or relationships).

    1. Avoid date hierarchies / auto date-time
      Make sure you’re filtering the base date column (type Date) — not the auto date hierarchy or a formatted text field. Hierarchies often lead to multiple values in context. Several threads show SELECTEDVALUE coming back blank when a hierarchy or mismatched column is involved. Microsoft Fabric Community+2Microsoft Fabric Community+2

    2. Relationships must propagate to the measure’s table
      If your date column sits in a different table and there’s no (active) relationship, the filter won’t reach the measure. Check the model diagram.

    3. Don’t expect applyJsonFilter to filter your own visual’s dataView
      By design, calling applyJsonFilter updates the report filter state; other visuals will see it, but your visual’s current dataView won’t retro-filter in memory. Request an update (host.refresh() or wait for update via onDataChanged) and base logic on the model column, not your internal array. This behavior is highlighted in dev Q&A. Stack Overflow

    4. SelectionManager ≠ filter context
      selectionManager.select() is for cross-highlight and selections, not guaranteed single-value filter context for DAX. Treat SelectionManager as UI affordance; use the Filter API to create the model filter. Microsoft Learn

      • Declare a categorical data role for the date column.

      • Persist the filter in general.filter.

      • Apply a Basic filter with a single ISO date value against 'DimDate'[Date].

      • Validate with COUNTROWS(VALUES('DimDate'[Date])) = 1.

        Docs and sample: Filter API and Sample Slicer’s Advanced Filter usage. Microsoft LearnGitHub

        Bottom line: there isn’t a secret “RLS-like” privilege you can tap from a custom visual. You can get SELECTEDVALUE() to behave, but only when the filter you apply collapses the model to exactly one value on the real column, with relationships and hierarchies set correctly.

        Did I answer your question? Mark my post as a solution! Appreciate your Kudos !!

        Power Query parameters won’t feed DAX context

        PQ parameters are evaluated at refresh/mashup time. They don’t create row context for measures. Your “parameter updates” can work, but DAX SELECTEDVALUE() won’t read them.

        If you want a working baseline, replicate the Sample Slicer pattern:
        @lulalagulu

Thank you for answering me. I sincerely appreciate it. I developed the visual as a calendar picker to work with power query M parameter. It worked well with M parameter. Here is the code:

export class Visual implements IVisual {
    private target: HTMLElement;
    private host: IVisualHost;

    private selectionManager: ISelectionManager;

    private formattingSettingsService: FormattingSettingsService;
    private formattingSettings: VisualFormattingSettingsModel;

    private inputContainer: HTMLDivElement;
    private dateInput: HTMLInputElement;
    private currentDataView: DataView | null; // 用于存储最新的数据视图

    private dataPoint: Map<string, ISelectionId> = new Map();

    constructor(options: VisualConstructorOptions) {
        this.target = options.element;
        this.host = options.host;
        this.formattingSettingsService = new FormattingSettingsService();
        this.selectionManager = this.host.createSelectionManager();

        // Clear existing content
        this.target.innerHTML = '';

        // 创建UI元素 (输入框和图标)
        this.createUIElements();


    }

    private createUIElements(): void {
        const label = document.createElement('div');
        label.className = 'date-label';
        label.textContent = '日期';
        label.style.fontWeight = 'bold';
        label.style.marginBottom = '5px';
        this.target.appendChild(label);

        this.inputContainer = document.createElement('div');
        this.inputContainer.className = 'input-container';
        this.inputContainer.style.display = 'flex';
        this.inputContainer.style.alignItems = 'center';
        this.inputContainer.style.border = '1px solid #ccc';
        this.inputContainer.style.borderRadius = '4px';
        this.inputContainer.style.padding = '4px 8px';
        this.inputContainer.style.backgroundColor = 'white';
        this.target.appendChild(this.inputContainer);

        this.dateInput = document.createElement('input');
        this.dateInput.type = 'text';
        this.dateInput.readOnly = true;
        this.dateInput.placeholder = 'YYYY-MM-DD';
        this.dateInput.style.border = 'none';
        this.dateInput.style.outline = 'none';
        this.dateInput.style.width = '100%';
        this.dateInput.style.fontSize = '14px';
        this.inputContainer.appendChild(this.dateInput);

        const dateIcon = document.createElement('div');
        dateIcon.innerHTML = '📅';
        dateIcon.style.cursor = 'pointer';
        dateIcon.style.marginLeft = '4px';
        dateIcon.addEventListener('click', (event) => {
            event.stopPropagation();
            this.openCalendarDialog(); // 调用打开对话框的方法
        });
        this.inputContainer.appendChild(dateIcon);
    }

    private applyDateFilter(date: string): void {
        if (!this.host || !this.host.applyJsonFilter) return;
    
        // 获取或确定要筛选的表和列
        let tableName: string | undefined;
        let columnName: string | undefined;
        
        if (this.currentDataView?.metadata?.columns) {
            const dateColumn = this.currentDataView.metadata.columns.find(col => col.roles && col.roles['Date']);
            if (dateColumn?.queryName) {
                [tableName, columnName] = dateColumn.queryName.split('.');
            }
        }
    
        if (!tableName || !columnName) return;
        
        const filter = {
            $schema: "https://powerbi.com/product/schema#basic",
            target: {
                table: tableName,
                column: columnName
            },
            filterType: models.FilterType.Basic,
           operator: "In",
            values: [ new Date(date).toISOString()],
            requireSingleSelection : true
        };
        
       
        this.host.applyJsonFilter(filter, "general", "filter", powerbi.FilterAction.merge);
    }


    private getDateColumnRange(): { minDate: Date, maxDate: Date } {
        const defaultRange = {
            minDate: new Date(1990, 1, 1),
            maxDate: new Date()
        };

        if (!this.currentDataView || !this.currentDataView.categorical) {
            return defaultRange;
        }

        const dates = this.currentDataView.categorical.categories![0].values
            .map(row => {
                const value = row;
                // 处理不同类型的日期值
                if (typeof value === 'string') {
                    return new Date(value);
                } else if (typeof value === 'number') {
                    return new Date(value);
                } else if (value instanceof Date) {
                    return value;
                }
                return null;
            })
            .filter((date): date is Date => date !== null && !isNaN(date.getTime()));

        if (dates.length === 0) {
            return defaultRange;
        }

        dates.sort((a, b) => a.getTime() - b.getTime());
        return {
            minDate: dates[0],
            maxDate: dates[dates.length - 1]
        };
    }

    private handleDialogResult(result: ModalDialogResult, targetElement: HTMLElement, sManager: ISelectionManager, dataPoint: Map<string, ISelectionId>) {
        if (result.actionId === DialogAction.OK) {
            const resultState = <DatePickerDialogResult>result.resultState;
            const selectedDate = new Date(resultState.date);
    
            let formatted_date = formatDate(selectedDate);
            if (targetElement instanceof HTMLInputElement) {
                targetElement.value = formatted_date;
            } else {
                targetElement.textContent = formatted_date;
            }
            
        
            this.applyDateFilter(formatted_date);
            
       
            // let selectedId = dataPoint.get(formatted_date);
            // if (selectedId) {
            //     sManager.select(selectedId);
            // }
        }
    
    }

    private openCalendarDialog(): void {
        const dateRange = this.getDateColumnRange();

        let startDate = new Date();
        if (startDate < dateRange.minDate) {
            startDate = dateRange.minDate;
        } else if (startDate > dateRange.maxDate) {
            startDate = dateRange.maxDate
        }

        const initialDialogState = {
            startDate: startDate,
            minDate: dateRange.minDate,
            maxDate: dateRange.maxDate
        };
        const position = {
            type: VisualDialogPositionType.RelativeToVisual,
            left: 0,
            top: 60
        };

        const size = { width: 360, height: 300 };
        const dialogOptions = {
            actionButtons: dialogActionsButtons,
            size: size,
            position: position,
            title: ""
        };


        // const initialState: { selectedDate?: string } = {};
        if (this.dateInput.value) {
            // 如果输入框有日期,作为初始值传给对话框
            initialDialogState.startDate = new Date(this.dateInput.value); // this.formatDate(selectedDate) -> YYYY/MM/DD
        }

        this.host.openModalDialog(
            DatePickerDialog.id,
            dialogOptions,
            initialDialogState // 传递初始日期给对话框
        ).then(result => {
            console.log("Dialog closed with result:", result);
            this.handleDialogResult(result, this.dateInput, this.selectionManager, this.dataPoint);
        }).catch(error => {
            console.error("Error opening date picker dialog:", error);
        });
    }



    public destroy(): void {

    }

    public update(options: VisualUpdateOptions) {
        console.log("filter: " + JSON.stringify(options.jsonFilters, null, 4));
        this.handleDataView(options.dataViews);
    }

    private handleDataView(dataViews: DataView[]) {
        if (!dataViews || dataViews.length == 0) {
            this.currentDataView = null;
            return;
        }

      
        //update the current date view
        this.currentDataView = dataViews[0];

        if (this.currentDataView.categorical && this.currentDataView.categorical.categories && this.currentDataView.categorical.categories.length > 0) {
            this.updateSelection(this.currentDataView.categorical);
        } else {
            this.dataPoint.clear();
            this.dateInput.value = '';
        }


        //sync the formattingSettings
        this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(
            VisualFormattingSettingsModel,
            this.currentDataView
        );

        this.updateFormatting();



    }

    private updateSelection(currentCategorical: powerbi.DataViewCategorical) {

        const categories = currentCategorical.categories;
        // get count of category elements
        const categoriesCount = categories![0].values.length;

        // iterate all categories to generate selection and create button elements to use selections
        for (let categoryIndex = 0; categoryIndex < categoriesCount; categoryIndex++) {
            const categoryValue: powerbi.PrimitiveValue = categories![0].values[categoryIndex];

            const categorySelectionId = this.host.createSelectionIdBuilder()
                .withCategory(categories![0], categoryIndex) // we have only one category (only one `Manufacturer` column)
                .createSelectionId();

       
            //console.log(categorySelectionId);

            let pearl: Date | null = null;

           
            if (typeof categoryValue === 'string') {
                pearl = new Date(categoryValue);
            } else if (typeof categoryValue === 'number') {
                pearl = new Date(categoryValue);
            } else if (categoryValue instanceof Date) {
                pearl = categoryValue;
            }

            let key: string = 'null';
            if (pearl !== null && !isNaN(pearl.getTime())) {
                key = formatDate(pearl);
            }

            this.dataPoint.set(key, categorySelectionId);

        }



    }


    private updateFormatting(): void {
        const labelSettings = this.formattingSettings.labelCard;
        const inputSettings = this.formattingSettings.inputCard;

        // 更新标签样式
        const label = this.target.querySelector('.date-label') as HTMLElement;
        if (label) {
            if (labelSettings.show.value) {
                label.style.display = "block";
                label.textContent = labelSettings.text.value;
                label.style.fontFamily = labelSettings.fontFamily.value;
                label.style.fontSize = `${labelSettings.fontSize.value}px`;
                label.style.color = labelSettings.fontColor.value.value;
            } else {
                label.style.display = "none";
            }
        }

       
        if (this.dateInput) {
            this.dateInput.style.fontFamily = inputSettings.fontFamily.value;
            this.dateInput.style.fontSize = `${inputSettings.fontSize.value}px`;
            this.dateInput.style.color = inputSettings.fontColor.value.value;
        }
    }

    public getFormattingModel(): powerbi.visuals.FormattingModel {
        return this.formattingSettingsService.buildFormattingModel(this.formattingSettings);
    }
}
{
  "dataRoles": [
    {
      "name": "Date",
      "displayName": "field",
      "kind": "Grouping"
    }
  ],
  "dataViewMappings": [
    {
      "conditions": [
        {
          "Date": {
            "max": 1
          }
        }
      ],
      "categorical": {
        "categories": {
          "for": {
            "in": "Date"
          }
        }
      }
    }
  ],
  "objects": {
    "general": {
      "displayName": "General",
      "displayNameKey": "formattingGeneral",
      "properties": {
        "filter": {
          "type": {
            "filter": true
          }
        }
      }
    },
    "labelFormatting": {
      "displayName": "Slicer header",
      "properties": {
        "show": {
          "type": {
            "bool": true
          }
        },
        "text": {
          "displayName": "Text",
          "type": {
            "text": true
          }
        },
        "fontFamily": {
          "displayName": "Font",
          "type": {
            "formatting": {
              "fontFamily": true
            }
          }
        },
        "fontSize": {
          "displayName": "Font size",
          "type": {
            "numeric": true
          }
        },
        "fontColor": {
          "displayName": "Font color",
          "type": {
            "fill": {
              "solid": {
                "color": true
              }
            }
          }
        }
      }
    },
    "inputFormatting": {
      "displayName": "Values",
      "properties": {
        "fontFamily": {
          "displayName": "Font",
          "type": {
            "formatting": {
              "fontFamily": true
            }
          }
        },
        "fontSize": {
          "displayName": "Font size",
          "type": {
            "numeric": true
          }
        },
        "fontColor": {
          "displayName": "Font color",
          "type": {
            "fill": {
              "solid": {
                "color": true
              }
            }
          }
        }
      }
    }
  },
  "sorting": {
    "default": {}
  },
  "privileges": [],
  "supportsEmptyDataView": true
}

 

Both dim start_date and dim end_date are individual tables without relationship to any table.
Screenshot 2025-08-24 at 12.59.10.png

Helpful resources

Announcements
August Power BI Update Carousel

Power BI Monthly Update - August 2025

Check out the August 2025 Power BI update to learn about new features.

August 2025 community update carousel

Fabric Community Update - August 2025

Find out what's new and trending in the Fabric community.

Top Solution Authors