Skip to content

Commit

Permalink
[dev-v5] Add a global observeAttributeChange JS method (#3230)
Browse files Browse the repository at this point in the history
* Add ObserveAttributeChanges

* Add a common observeAttributeChange

* Refactorization TextInput using the new global JS function
  • Loading branch information
dvoituron authored Jan 22, 2025
1 parent 8a48a60 commit 20f536c
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 33 deletions.
2 changes: 2 additions & 0 deletions src/Core.Scripts/src/ExportedMethods.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Microsoft as LoggerFile } from './Utilities/Logger';
import { Microsoft as AttributesFile } from './Utilities/Attributes';
import { Microsoft as FluentDialogFile } from './Components/Dialog/FluentDialog';

export namespace Microsoft.FluentUI.Blazor.ExportedMethods {
Expand All @@ -16,6 +17,7 @@ export namespace Microsoft.FluentUI.Blazor.ExportedMethods {
// Utilities methods (Logger)
(window as any).Microsoft.FluentUI.Blazor.Utilities = (window as any).Microsoft.FluentUI.Blazor.Utilities || {};
(window as any).Microsoft.FluentUI.Blazor.Utilities.Logger = LoggerFile.FluentUI.Blazor.Utilities.Logger;
(window as any).Microsoft.FluentUI.Blazor.Utilities.Attributes = AttributesFile.FluentUI.Blazor.Utilities.Attributes;

// Dialog methods
(window as any).Microsoft.FluentUI.Blazor.Components = (window as any).Microsoft.FluentUI.Blazor.Components || {};
Expand Down
109 changes: 109 additions & 0 deletions src/Core.Scripts/src/Utilities/Attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
export namespace Microsoft.FluentUI.Blazor.Utilities.Attributes {

/**
* Observe the change in the HTML `attributeName` attribute to update the element's `propertyName` JavaScript property.
* @param element The element to observe.
* @param attributeName The name of the attribute to observe.
* @param propertyType Optional. The type of the property to update (default is 'string').
* @param propertyName Optional. The name of the property to update (default is the attributeName).
* @param forceRefresh Optional. If true, all properties will be refreshed when the attribute changes (default is false).
* @returns True if the observer was added, false if the observer was already added.
*
* Example:
* const element = document.getElementById('myCheckbox');
* observeAttributeChange(element, 'checked', 'boolean') // Observe the 'checked' HTML attribute to update the 'checked' JavaScript property.
* observeAttributeChange(element, 'indeterminate', 'boolean', '', true) // Observe the 'indeterminate' HTML attribute to update all registered JavaScript property (forceRefresh=true).
*/
export function observeAttributeChange(element: HTMLElement, attributeName: string, propertyType: 'number' | 'string' | 'boolean' = 'string', propertyName: string = '', forceRefresh: boolean = false): boolean {

if (element == null || element == undefined) {
return false;
}

const fuibName = `attr-${attributeName}`;

// Check if an Observer is already defined for this element.attributeName
const fuib = getInternalData(element);
if (fuib[fuibName]) {
return false;
}

// Set the default propertyName if not provided
if (propertyName === '') {
propertyName = attributeName;
}

// Create and add an observer on the element.attributeName
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === attributeName) {

// Refresh all properties if forceRefresh is true
if (forceRefresh) {
for (const key in fuib) {
if (fuib.hasOwnProperty(key) && key.startsWith('attr-')) {
const attr = fuib[key];
updateJavaScriptProperty(element, attr.attributeName, attr.propertyType, attr.propertyName);
}
}
}

// Refresh only the changed property
else {
updateJavaScriptProperty(element, attributeName, propertyType, propertyName);
}
}
}
});

// Add an observer and keep the parameters in the element's internal data
observer.observe(element, { attributes: true });
fuib[fuibName] = {
attributeName: attributeName,
propertyType: propertyType,
propertyName: propertyName,
};

// Update the JavaScript property with the current attribute value
updateJavaScriptProperty(element, attributeName, propertyType, propertyName);

return true;
}

function updateJavaScriptProperty(element: HTMLElement, attributeName: string, propertyType: 'number' | 'string' | 'boolean', propertyName: string): void {
const newValue = convertToType(element.getAttribute(attributeName), propertyType);
const field = element as any;
if (newValue !== field[propertyName]) {
field[propertyName] = newValue;
}
}

/**
* Convert a string value to a typed value.
* @param value
* @param type
* @returns
*/
function convertToType(value: string | null, type: 'number' | 'string' | 'boolean'): number | string | boolean | null {
switch (type) {
case 'number':
return value ? parseFloat(value) : null;
case 'boolean':
return value === 'true' || value === '';
default:
return value;
}
}

/**
* Create or get the internal data object for the element.
* @param element
* @returns
*/
function getInternalData(element: HTMLElement): any {
if ((element as any)['__fuib'] == undefined) {
(element as any)['__fuib'] = {};
}
return (element as any)['__fuib'];
}
}
2 changes: 1 addition & 1 deletion src/Core/Components/Base/FluentInputBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected FluentInputBase()

/// <summary />
[Inject]
private IJSRuntime JSRuntime { get; set; } = default!;
protected IJSRuntime JSRuntime { get; set; } = default!;

/// <summary />
[Inject]
Expand Down
9 changes: 1 addition & 8 deletions src/Core/Components/TextInput/FluentTextInput.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
/// </summary>
public partial class FluentTextInput : FluentInputImmediateBase<string?>, IFluentComponentElementBase
{
private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "TextInput/FluentTextInput.razor.js";

/// <inheritdoc />
[Parameter]
public ElementReference Element { get; set; }
Expand Down Expand Up @@ -105,12 +103,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Import the JavaScript module
var jsModule = await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE);

// Call a function from the JavaScript module
// Wait for this PR to delete the code: https://github.com/microsoft/fluentui/pull/33144
await jsModule.InvokeVoidAsync("Microsoft.FluentUI.Blazor.TextInput.ObserveAttributeChanges", Element);
await JSRuntime.InvokeVoidAsync("Microsoft.FluentUI.Blazor.Utilities.Attributes.observeAttributeChange", Element, "value");
}
}

Expand Down
24 changes: 0 additions & 24 deletions src/Core/Components/TextInput/FluentTextInput.razor.ts

This file was deleted.

0 comments on commit 20f536c

Please sign in to comment.