Forum Discussion
Custom Power BI visual (date picker slicer) not work with SELECTEDVALUE()
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:
-
Target the exact model column
Make sure yourapplyJsonFilter targets the same column you use in DAX, not a display/derived field. Use a Basic (or Advanced) filter with a single value andisInverted:false. Doc: Visual Filter API. Microsoft Learn -
Bind it via capabilities (not just ad-hoc)
Incapabilities.json, include ageneral.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 LearnProve 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).-
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 showSELECTEDVALUE coming back blank when a hierarchy or mismatched column is involved. Microsoft Fabric Community+2Microsoft Fabric Community+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. -
Don’t expect
applyJsonFilter to filter your own visual’s dataView
By design, callingapplyJsonFilter updates the report filter state; other visuals will see it, but your visual’s currentdataView won’t retro-filter in memory. Request an update (host.refresh() or wait for update viaonDataChanged) and base logic on the model column, not your internal array. This behavior is highlighted in dev Q&A. Stack Overflow -
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
-
-
- lulalagulu1 year agoRegular Visitor
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.