Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@uifabric/utilities",
"comment": "Allow a function to be passed to the Customizers props",
"type": "minor"
}
],
"packageName": "@uifabric/utilities",
"email": "mark@thedutchies.com"
}
76 changes: 75 additions & 1 deletion packages/utilities/src/Customizer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('Customizer', () => {
});

it('can scope settings to specific components', () => {
let scopedSettings = {
const scopedSettings = {
Foo: { field: 'scopedToFoo' },
Bar: { field: 'scopedToBar' }
};
Expand Down Expand Up @@ -91,4 +91,78 @@ describe('Customizer', () => {
)).toEqual('<div>fieldfield2field3</div>');
});

it('can layer scoped settings with scopedSettingsFunction', () => {
Customizations.applySettings({ 'field3': 'field3' });

expect(ReactDOM.renderToStaticMarkup(
<Customizer scopedSettings={ { Bar: { field: 'field' } } }>
<Customizer
scopedSettings={
// tslint:disable-next-line:jsx-no-lambda
(scopedSettings: { Bar: { field2: string } }) => ({ Bar: { ...scopedSettings.Bar, field2: 'field2' } })
}
>
<Bar />
</Customizer >
</Customizer >
)).toEqual('<div>fieldfield2field3</div>');
});

it('it allows scopedSettings to be merged when a function is passed', () => {
expect(ReactDOM.renderToStaticMarkup(
<Customizer scopedSettings={ { Foo: { field: 'scopedToFoo' } } }>
<Customizer
scopedSettings={
// tslint:disable-next-line:jsx-no-lambda
(settings: { Foo: { field: string } }) => ({ ...settings, Bar: { field: 'scopedToBar' } })
}
>
<div>
<Foo />
<Bar />
</div>
</Customizer>
</Customizer>
)).toEqual('<div><div>scopedToFoo</div><div>scopedToBar</div></div>');
});

it('does not override previously set settings', () => {
expect(ReactDOM.renderToStaticMarkup(
<Customizer settings={ { field: 'field1' } }>
<Customizer settings={ { field: 'field2' } }>
<Bar />
</Customizer >
</Customizer >
)).toEqual('<div>field1</div>');
});

it('overrides the old settings when the parameter is ignored', () => {
expect(ReactDOM.renderToStaticMarkup(
<Customizer settings={ { field: 'field1' } }>
<Customizer
settings={
// tslint:disable-next-line:jsx-no-lambda
(settings: { field: string }) => ({ field: 'field2' })
}
>
<Bar />
</Customizer >
</Customizer >
)).toEqual('<div>field2</div>');
});

it('can use a function to merge settings', () => {
expect(ReactDOM.renderToStaticMarkup(
<Customizer settings={ { field: 'field1' } }>
<Customizer
settings={
// tslint:disable-next-line:jsx-no-lambda
(settings: { field: string }) => ({ field: settings.field + 'field2' })
}
>
<Bar />
</Customizer >
</Customizer >
)).toEqual('<div>field1field2</div>');
});
});
68 changes: 47 additions & 21 deletions packages/utilities/src/Customizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ export interface ICustomizerContext {
customizations: ICustomizations;
}

export type ICustomizerProps = Partial<ICustomizations> & IBaseProps;
// tslint:disable-next-line:no-any
export type Settings = { [key: string]: any };
export type SettingsFunction = (settings: Settings) => Settings;

export type ICustomizerProps = Partial<{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ICustomizations is now unused? Should you delete it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really unused as the type is still used for the CustomizerContext and GlobalSettings.getValue. But we could indeed do a bit of cleanup, so i made some minor changes.

settings: Settings | SettingsFunction;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add jsdoc to these?

scopedSettings: Settings | SettingsFunction
}> & IBaseProps;

/**
* The Customizer component allows for default props to be mixed into components which
Expand All @@ -25,8 +32,8 @@ export class Customizer extends BaseComponent<ICustomizerProps, ICustomizerConte
public static contextTypes: {
customizations: PropTypes.Requireable<{}>;
} = {
customizations: PropTypes.object
};
customizations: PropTypes.object
};

public static childContextTypes: {
customizations: PropTypes.Requireable<{}>;
Expand Down Expand Up @@ -56,30 +63,49 @@ export class Customizer extends BaseComponent<ICustomizerProps, ICustomizerConte
props: ICustomizerProps,
context: ICustomizerContext
): ICustomizerContext {
let {
settings = {},
scopedSettings = {}
} = props;
let {
const {
customizations = { settings: {}, scopedSettings: {} }
} = context;

let newScopedSettings = { ...scopedSettings };

for (let name in customizations.scopedSettings) {
if (customizations.scopedSettings.hasOwnProperty(name)) {
newScopedSettings[name] = { ...scopedSettings[name], ...customizations.scopedSettings[name] };
}
}

return {
customizations: {
settings: {
...settings,
...customizations.settings
},
scopedSettings: newScopedSettings
settings: mergeSettings(customizations.settings, props.settings),
scopedSettings: mergeScopedSettings(customizations.scopedSettings, props.scopedSettings),
}
};
}
}

function mergeSettings(oldSettings: Settings = {}, newSettings?: Settings | SettingsFunction): Settings {
const mergeSettingsWith = isSettingsFunction(newSettings) ? newSettings : settingsMergeWith(newSettings);

return mergeSettingsWith(oldSettings);
}

function mergeScopedSettings(oldSettings: Settings = {}, newSettings?: Settings | SettingsFunction): Settings {
const mergeSettingsWith = isSettingsFunction(newSettings) ? newSettings : scopedSettingsMergeWith(newSettings);

return mergeSettingsWith(oldSettings);
}

function isSettingsFunction(settings?: Settings | SettingsFunction): settings is SettingsFunction {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return typeof settings === 'function';
}

function settingsMergeWith(newSettings?: object): (settings: Settings) => Settings {
return (settings: Settings) => newSettings ? { ...newSettings, ...settings } : settings;
}

function scopedSettingsMergeWith(scopedSettingsFromProps: Settings = {}): (scopedSettings: Settings) => Settings {
return (oldScopedSettings: Settings): Settings => {
const newScopedSettings: Settings = { ...oldScopedSettings };

for (let scopeName in scopedSettingsFromProps) {
if (scopedSettingsFromProps.hasOwnProperty(scopeName)) {
newScopedSettings[scopeName] = { ...oldScopedSettings[scopeName], ...scopedSettingsFromProps[scopeName] };
}
}

return newScopedSettings;
};
}