Skip to content

Polymorph()

esr360 edited this page Jun 15, 2019 · 20 revisions
polymorph(element, styles, config, globals)
Param Type Info
element HTMLElement The DOM element to style
styles (Object|Function|Array) The style rules to apply to element
[config] Object Module configuration
[globals] Object Global/theming properties

Styles

styles as Object

For basic styling of modules and components you can pass an Object containing your desired stylistic properties.

Example
polymorph(element, {
    'display': 'block',
    'position': 'relative',
    
    'modifier(hidden)': {
        'display': 'none'
    },

    someComponent: {
        'color': 'red',
        'font-size': '24px'
    },

    anotherComponent: component => ({
        'color': window.UI.someUtility(component) ? 'blue' : 'black',
        'font-size': '12px'
    })
});

styles as Function

You can pass the styles parameter as a Function that follows a specific signature, allowing you to pass configuration and add more flexibility.

Expected Signature

config and globals refer to the optional parameters passed to the polymorph() call

styles(element, config, globals)
Function (HTMLElement, Object, Object) => (Array.<Object>|Object)
Example
const config = {
    colorPrimary: 'red',
    sizePrimary: '24px',
    sizeSecondary: '12px'
}

polymorph(element, (element, config, globals) => ({
    'display': element.matches('[class*="-hidden"]') ? 'none' : 'block',
    'position': 'relative',
    
    someComponent: {
        'color': config.colorPrimary,
        'font-size': config.sizePrimary
    },

    anotherComponent: component => ({
        'color': globals.someUtility(component) ? 'blue' : 'black',
        'font-size': config.sizeSecondary
    })
}), config, window.UI);
Returning an Array

You can return an array of style objects and sequentially apply each ruleset in the array:

polymorph(element, element => ([
    {
        // some style rules...
    },
    someOtherRulesetUtility(element),
    {
        'color': 'red' // will take priority as it is last to be applied 
    }
]);

styles as Array

You can pass an array of Objects/Functions/Arrys that Polymorph will sequentially execute.

Example

You can also pass an array of properties for components/sub-components

polymorph(element, [
    {
        ...
    },

    element => ([
        {
            ...
        },
        {
            ...
        }
    ]),

    [
        {...},
        element => ({...})
    ]
});

Style Root Module

To style the root module (the DOM element matched by element), pass any valid CSS property and value to an Object:

<div class="accordion" id="alpha"></div>
polymorph(document.getElementById('alpha'), {
    'color': 'red'
});
Result
<div id="alpha" style="color: red;"></div>

Style Components

Learn more about Components

To style a component, pass your desired CSS properties to an Object assigned to a key which corresponds to the Component name:

<div class="accordion" id="alpha">
    <div class="accordion_panel"></div>
    <div class="accordion_panel"></div>
</div>
polymorph(document.getElementById('alpha'), {
    panel: {
        'color': 'blue'
    }
});
Result
<div class="accordion" id="alpha">
    <div class="accordion_panel" style="color: blue;"></div>
    <div class="accordion_panel" style="color: blue;"></div>
</div>

You can access the Component's DOM node by passing a Function instead of an Object:

<div class="accordion" id="alpha">
    <div class="accordion_panel"></div>
    <div class="accordion_panel-active"></div>
</div>
polymorph(document.getElementById('alpha'), {
    panel: panel => {
        const color = panel.matches('[class*="-active"]') ? 'red' : 'blue';

        return {
            'color': color
        }
    }
});
Shorter Syntax + sQuery
polymorph(document.getElementById('alpha'), {
    panel: panel => ({
        'color': panel.modifier('active') ? 'red' : 'blue'
    })
});
Result
<div class="accordion" id="alpha">
    <div class="accordion_panel" style="color: blue;"></div>
    <div class="accordion_panel-active" style="color: red;"></div>
</div>

Style Sub-Components

Where possible, it is recommended to avoid using Sub-Components and stick to regular Components

<div class="header" id="alpha">
    <ul class="header_navigation">
        <li class="header_navigation_item">...</li>
        <li class="header_navigation_item">...</li>
    </ul>
</div>
polymorph(document.getElementById('alpha'), {
    navigation: {
        'sub-component(item)': {
            'color': 'blue'
        }
    }
});

...or if you are confident about not having naming conflicts:

polymorph(document.getElementById('alpha'), {
    navigation: {
        item: {
            'color': 'blue'
        }
    }
});
Result
<div class="header" id="alpha">
    <ul class="header_navigation">
        <li class="header_navigation_item" style="color: blue;">...</li>
        <li class="header_navigation_item" style="color: blue;">...</li>
    </ul>
</div>

An alternative approach using sQuery could be:

polymorph(document.getElementById('alpha'), {
    navigation: navigation => {
        navigation.getSubComponents('item').forEach(item => polymorph(item, {
            'color': 'blue'
        }));

        return {
            // navigation component styles
        }
    }
});

Style Elements with Applied Modifiers

Style a Module with Applied Modifiers

<div class="button-large-round-active" id="alpha"></div>
polymorph(document.getElementById('alpha'), {
    'background': 'grey',
    'font-size': '14px',

    'modifier(large)': {
        'font-size': '24px'
    },
    'modifier(round)': {
        'border-radius': '4px'
    },
    'modifier(active)': {
        'background': 'green'
    },
});
Alternatively
polymorph(document.getElementById('alpha'), element => ({
    'background': element.matches('[class*="-active"]') ? 'green' : 'grey',
    'font-size': element.matches('[class*="-large"]') ? '24px' : '14px',
    'border-radius': element.matches('[class*="-round"]') ? '4px' : null
}));
Using sQuery

Learn more about sQuery

polymorph(document.getElementById('alpha'), element => ({
    'background': element.modifier('active') ? 'green' : 'grey',
    'font-size': element.modifier('large') ? '24px' : '14px',
    'border-radius': element.modifier('round') ? '4px' : null
}));
Result
<div class="button-large-round-active" id="alpha" style="background-color: green; font-size: 24px; border-radius: 4px;"></div>
Targeting All Buttons
<div class="button"></div>
<div class="button-large-round-active"></div>
<div class="button-active"></div>
sQuery('button', button => {
    polymorph(button, element => ({
        'background': element.modifier('active') ? 'green' : 'grey',
        'font-size': element.modifier('large') ? '24px' : '14px',
        'border-radius': element.modifier('round') ? '4px' : null
    }));
});
Result
<div class="button" style="background: grey; font-size: 14px;"></div>
<div class="button-large-round-active" id="alpha" style="background: green; font-size: 24px; border-radius: 4px;"></div>
<div class="button-active" style="background: green; font-size: 14px;"></div>

Style a Component with Applied Modifiers

Example
<div class="accordion" id="alpha">
    <div class="accordion_panel"></div>
    <div class="accordion_panel-active"></div>
</div>
polymorph(document.getElementById('alpha'), {
    panel: {
        'color': 'blue',

        'modifier(active)': {
            'color': 'red'
        }
    }
});
Alternatively
polymorph(document.getElementById('alpha'), {
    panel: panel => ({
        'color': panel.matches('[class*="-active"]') ? 'red' : 'blue'
    })
});
Using sQuery

Learn More about sQuery

polymorph(document.getElementById('alpha'), {
    panel: panel => ({
        'color': panel.is('active') ? 'red' : 'blue'
    })
});
Alternatively
polymorph(document.getElementById('alpha'), {
    panel: panel => ([
        {
            styles: {
                'color': 'blue'
            }
        },
        {
            condition: () => panel.is('active'),
            styles: {
                'color': 'red'
            }
        }
    ])
});

Style Elements when Condition is Met

It's possible to apply styles to an element when a certain condition is met (if you only need to apply styles when an element has an applied modifier, see the Style Elements with Applied Modifiers section).

  • The condition should be a function that returns a boolean (unless the function returns a truthy value, the styles will not be applied) and assigned to the condition key
  • Styles should be assigned to the styles key
const styles = {
    someComponent: {
        condition: () => window.someGlobalCondition,
        styles: {
            // some styles
        }
    }
}

The above styles will only be applied if window.someGlobalCondition is a truthy value.

Shorthand Syntax

const styles = {
    someComponent: [() => window.someGlobalCondition, {
        // some styles
    }]
}

Style Reference Element

It's possible to apply styles to a reference element:

  • Reference element should be assigned to the element key
  • Styles should be assigned to the styles key
const someReferenceElement = document.getElementById('someElement');

const styles = {
    someIdentifier: {
        element: someReferenceElement,
        styles: {
            // some styles
        }
    }
}

Shorthand Syntax

const someReferenceElement = document.getElementById('someElement');

const styles = {
    someIdentifier: [someReferenceElement, {
        // some styles
    }]
}

Style Keywords

Component/Sub-Component Name

...as documented by the Style Components and Style Sub-Components sections.

:hover

Apply style properties when the element or a child component/sub-component is hovered

polymorph(element, {
    someComponent: {
        'color': 'red',
        
        ':hover': {
            'color': 'blue'
        }
    }
});

:focus

Apply style properties when the element or a child component/sub-component is focused

polymorph(element, {
    someComponent: {
        'outline': 'none',
        
        ':focus': {
            'outline': '2px solid blue'
        }
    }
});

group

Style a parent group element

<div class="group-button">
    <div class="button">Button</div>
    <div class="button">Button</div>
    <div class="button">Button</div>
</div>
document.querySelectorAll('.button').forEach(button => {
    polymorph(button, {
        group: {
            'display': 'flex'
        }
    });
});

This is useful when working with Lucid

Output
<div class="group-button" style="display: flex;">
    <div class="button">Button</div>
    <div class="button">Button</div>
    <div class="button">Button</div>
</div>

wrapper

Style a parent wrapper element

<div class="wrapper-header">
    <div class="header>...</div>
</div>
polymorph(document.querySelector('.header'), {
    wrapper: {
        'position': 'absolute'
    }
});

This is useful when working with Lucid

Output
<div class="wrapper-header" style="position: absolute;">
    <div class="header>...</div>
</div>

Config

Pass a configuration Object to your styles, allowing you to separate potentially configurable properties from your source code.

Example
const config = {
    colorPrimary: 'red',
    sizePrimary: '24px',
    sizeSecondary: '12px'
}

polymorph(element, (element, config) => ({
    'display': element.matches('[class*="-hidden"]') ? 'none' : 'block',
    'position': 'relative',
    
    someComponent: {
        'color': config.colorPrimary,
        'font-size': config.sizePrimary
    }
}), config);

Module/Component Glue

Learn more about modules/components

Polymorph requires a value for modfierGlue and componentGlue; these values can be set manually via configuration or retrieved dynamically (otherwise they will fallback to - and _ respectively).

Via Configuration

polymorph(element, styles, { componentGlue: '__', modifierGlue: '--' }); 

Dynamically

By assigning the values to a global window.Synergy object, they will be picked up automatically by Polymorph.

window.Synergy = {
    componentGlue: '__',
    modifierGlue: '--'
}

Globals

Pass a global Object to your styles, allowing you to share common tools and utilities.

Example
window.UI.someUtility = element => {...};

polymorph(element, (element, config, globals) => ({
    'display': element.matches('[class*="-hidden"]') ? 'none' : 'block',
    'position': 'relative',

    someComponent: component => ({
        'color': globals.someUtility(component) ? 'blue' : 'black',
        'font-size': '12px'
    })
}), {}, window.UI);