Unit 4: Positioning

You may have already noticed the View component in previous units’ examples. View is much more than a simple wrapper, it provides intuitive positioning and animation capabilities. This positioning is based on the CSS flexbox specification with some adaptations. Why flexbox instead of CSS grid you may ask? The main reason is that React Native does not yet offer support for grid layouts. In oneJS we divide positioning into two types: content and self-positioning. content governs how children components are positioned, while self controls how the component itself it’s positioned with respect to its parent.

Content Positioning

To understand content positioning, you need to become familiar with the following concepts:

Put these properties to the test:

/* Imports */
import {app, read, update, readFlavor, readStyle} from '@onejs-dev/core';
import {View, Input, Text, Icon} from '@onejs-dev/components';
import {icons} from '@onejs-dev/icons';

/* State configuration */
const state = {
    direction: 'row',
    wrap: true,
    h: 'left',
    v: 'top',
    gap: 0
}

/* Input options */
const options = {
    direction: ['row', 'column', 'row-reverse', 'column-reverse'],
    wrap: [true, false],
    h: ['left', 'center', 'right', 'space', 'distribute', 'stretch'],
    v: ['top', 'center', 'bottom', 'space', 'distribute', 'stretch']
}

/* Style object creation */
const style = {
    label: {width: 85, fontWeight: 'bold', textAlign: 'right', paddingRight: 10},
    childrenSize: {width: 50, height: 50},
    oneSize: {width: 35, height: 35},
    containerSize: {width: 230, height: 230},
    inputPanelSize: {width: 430},
    inputSize: {minWidth: 50, width: 115}
}

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  [
        /* Content option panel*/
        Text({flavor: readFlavor('subsection')})('Content Options:'),
        View({flavor: readFlavor('outline'), content: {wrap: true}, style: readStyle('inputPanelSize')})([
            Text({style: readStyle('label')})('direction'), 
            Input({type: 'list', options: options.direction, value: read('direction'), onChange: update('direction'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('wrap'), 
            Input({type: 'list', options: options.wrap, value: read('wrap'), onChange: update('wrap'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('h'), 
            Input({type: 'list', options: options.h, value: read('h'), onChange: update('h'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('v'), 
            Input({type: 'list', options: options.v, value: read('v'), onChange: update('v'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('gap: ' + read('gap')), 
            Input({type: 'range', min: 0, max: 20, value: read('gap'), onChange: update('gap'), style: readStyle('inputSize')})
        ]),
        /* View parent component for "content" property*/
        View({
            flavor: readFlavor('lightBackground'), 
            style: readStyle('containerSize'),
            content: {
                direction: read('direction'), 
                wrap: read('wrap') !== 'false' ? true : false, 
                h: read('h'), 
                v: read('v'), 
                gap: Number(read('gap'))
            },
        })([
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('0')),
            View({flavor: readFlavor('primaryGradient'), style: readStyle('oneSize')})(Text({flavor: readFlavor('section', 'light')})('1')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('2')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('3')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('4')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('5')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('6')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('7')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('8')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('9'))
        ])
    ];
};

/* App Function: Renders the App Component in the screen */
app({component: App, state: state, style: style, theme: {preset: 'oneJS'}});

Self Positioning

For self positioning, the following concepts will allow you to have full control of your component’s positioning with respect to its parent.

/* Imports */
import {app, read, update, readFlavor, readStyle} from '@onejs-dev/core';
import {View, Input, Text, Icon} from '@onejs-dev/components';
import {icons} from '@onejs-dev/icons';

/* State configuration */
const state = {
    direction: 'row',
    wrap: true,
    h: 'left',
    v: 'top',
    gap: 0,
    align: 'auto',
    expand: 0,
    shrink: 1
}

/* Input options */
const options = {
    direction: ['row', 'column', 'row-reverse', 'column-reverse'],
    wrap: [true, false],
    h: ['left', 'center', 'right', 'space', 'distribute', 'stretch'],
    v: ['top', 'center', 'bottom', 'space', 'distribute', 'stretch'],
    align: ['auto', 'left', 'right', 'top', 'bottom', 'center', 'stretch']
}

/* Style object creation */
const style = {
    label: {width: 85, fontWeight: 'bold', textAlign: 'right', paddingRight: 10},
    childrenSize: {width: 50, height: 50},
    oneSize: {width: 35, height: 35},
    containerSize: {width: 230, height: 230},
    inputPanelSize: {width: 430},
    inputSize: {minWidth: 50, width: 115}
}

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  [
        /* Content option panel*/
        Text({flavor: readFlavor('subsection')})('Content Options:'),
        View({flavor: readFlavor('outline'), content: {wrap: true}, style: readStyle('inputPanelSize')})([
            Text({style: readStyle('label')})('direction'), 
            Input({type: 'list', options: options.direction, value: read('direction'), onChange: update('direction'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('wrap'), 
            Input({type: 'list', options: options.wrap, value: read('wrap'), onChange: update('wrap'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('h'), 
            Input({type: 'list', options: options.h, value: read('h'), onChange: update('h'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('v'), 
            Input({type: 'list', options: options.v, value: read('v'), onChange: update('v'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('gap: ' + read('gap')), 
            Input({type: 'range', min: 0, max: 20, value: read('gap'), onChange: update('gap'), style: readStyle('inputSize')})
        ]),
        /* Align option panel */
        Text({flavor: readFlavor('subsection')})('Align Options:'),
        View({flavor: readFlavor('outline'), content: {wrap: true}, style: readStyle('inputPanelSize')})([
            Text({style: readStyle('label')})('expand: '+ read('expand')), 
            Input({type: 'range', min: 0, max: 1, value: read('expand'), onChange: update('expand'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('shrink: ' + read('shrink')), 
            Input({type: 'range', min: 0, max: 1, value: read('shrink'), onChange: update('shrink'), style: readStyle('inputSize')}),
            Text({style: readStyle('label')})('align'), 
            Input({type: 'list', options: options.align, value: read('align'), onChange: update('align'), style: readStyle('inputSize')})
        ]),
        /* View parent component for "content" property*/
        View({
            flavor: readFlavor('lightBackground'), 
            style: readStyle('containerSize'),
            content: {
                direction: read('direction'), 
                wrap: read('wrap') !== 'false' ? true : false, 
                h: read('h'), 
                v: read('v'), 
                gap: Number(read('gap'))
            },
        })([
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('0')),
            /* View child component for "self" property */
            View({flavor: readFlavor('primaryGradient'), style: readStyle('oneSize'), 
                self: {
                    align: read('align'),
                    expand: read('expand'),
                    shrink: read('shrink')
                }
            })(Text({flavor: readFlavor('section', 'light')})('1')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('2')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('3')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('4')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('5')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('6')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('7')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('8')),
            View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('9'))
        ])
    ];
};

/* App Function: Renders the App Component in the screen */
app({component: App, state: state, style: style, theme: {preset: 'oneJS'}});

Visibility

You can easily show and hide components by feeding the visible property. If true, the component will be visible on the app. If false, the visibility is set to none hiding it.

/* Imports */
import {app, read, update, readFlavor} from '@onejs-dev/core';
import {View, Input, Text} from '@onejs-dev/components';

/* State Configuration */
const state = {
    isVisible: true
}; 

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  View({content: {gap: 10}})([
        Input({type: 'switch', value: read('isVisible'), onChange: update('isVisible')}),
        View({
            visible: read('isVisible'), 
            flavor: readFlavor('primaryGradient'),
            style: {padding: 40}
        })(Text({flavor: readFlavor('light')})('Toggle visibility'))
    ]);
};

/* App Function: Renders the App Component in the screen */
app({component: App, state: state, theme: {preset: 'oneJS'}});

This completes your knowledge on how to position components on screen. In the next unit you will learn how to create your own custom components.