This package has been deprecated

Author message:

package is no longer maintained

xyjax

2.11.6 • Public • Published

npm version GitHub top language GitHub last commit (branch) NPM

What is Xyjax

Xyjax is the React+Redux-based tool for single-page applications development. The main goal of this tool is to give you the opportunity to write code that is clean, readable and easily reusable. But Xyjax is not only about code readability - much attention has been paid to the ease and convenience of optimizing and testing of your code too.

Why Xyjax

  • Every action with Redux state can be done with one API call so you don't need Redux actions, dispatches, reducers, etc. anymore, everything works just out of the box!
  • Xyjax provides you to control components render with one API call
  • Powerful and flexible components' properties validation system
  • Simple and powerful events system makes it possible for you to build event-based scenarios of any complexity
  • There is nothing in your code refers to React/Redux inner logic so it's possible to write completely clean code which contains nothing but your application logic
  • Xyjax is easy to use, pretty good documented and has code examples so it won't be difficult to get into it

How to import Xyjax

To work with Xyjax you should import xjx API object which can be done in following manner after installing this npm package:

import xjx from 'xyjax'

You don't need anything else to work with Xyjax, so it makes possible to have less imports in your code.

Demo, source code examples

Take a look at demo and source code examples for quick overview (under development still).

Documentation

1. Base API methods

This section contains base methods which are located strictly in xjx API object.

1-1. xjx.begin

This method is your application entry point.

Method signature:

xjx.begin(
    MainComponent, 
    elementId = 'root', 
    xjx_id = 'xjx-app', 
    initialStateGenerator = null, 
    reduxExtension = true
)

Arguments description:

  • MainComponent is an application entry point React component
  • elementId is an application HTML-container id
  • xjx_id is a Xyjax component identifier will be applied to MainComponent
  • initialStateGenerator must be passed if you need initial state. It must be a function with no arguments, which returns object that will be your application initial state
  • reduxExtension is a boolean flag which indicates if application store will be connected to Redux DevTools extension

Basic usage example:

Basic case (when you have an HTML div with 'root' id and a component named App) is very simple in spite of many arguments:

import xjx from 'xyjax'
//importing your App component

xjx.begin(App)

There are just three short lines of code. You don't need ReactDOM.render... etc. anymore - everything works fine just out of the box!

Initial state generator usage example:

Basic correct initial state generator:

const correct_initial_state_generator = () => { 
    return {
        state_field: 'field_value'
    } 
}

You can use following approach in case of having more complex initial state than just a hardcoded values bunch:

const more_complex_initial_state_generator = (variable_field_value) => { 
    return () => {
        return {
            state_field: variable_field_value
        }    
    }
}

more_complex_initial_state_generator method will return correct initial state generator.

1-2. xjx.connect

This method connects React components to a Redux state. It must be used at components exporting.

Method signature:

xjx.connect(
    component
)

Arguments description:

  • component is a current React component that will be connected to store

Usage:

//code of React component called TestComponent
export default xjx.connect(TestComponent)

1-3. xjx.init

This method initializes React component with Xyjax identifier and some other configs if needed. Your components render methods should contain results of this method evaluation upon components, not plain components!

Method signature:

xjx.init(
    Component, 
    id = null, 
    props = null, 
    firstRender = true, 
    userShouldCompUpdate = null
)

Arguments description:

  • Component is the current React component being initialized
  • id is the string with Xyjax identifier or the function with two parameters (className and props) which returns Xyjax identifier (see examples below). If nothing was passed, Xyjax identifier of current component would be the same as its' class name.
  • props is the object will be applied as initialized component's props. Properties are passing as javascript object to make it easy for you to validate it before passing and/or to extract them in constants (objects or functions which return required properties)
  • firstRender is the Boolean flag which shows if component's render is allowed or denied when it's mounted
  • userShouldCompUpdate is the function that must be passed if you want to ignore Xyjax render control system. It has more priority than Xyjax render permission check logic though this approach is not recommended

Usage:

//code of some component. Also there is imported TestComponent component
render() {
    //identifier is 'TestComponent', nothing is stored in props:
    const testComp = xjx.init(TestComponent) 

    //identifier is 'testComp', customTestComp.props.hello == 'world':
    const testCompId = 'testComp'
    const testCompProps = (helloMessage) => { return { hello: helloMessage } }
    const customTestComp = xjx.init(
	    TestComponent, 
	    testCompId, 
	    testCompProps('world')
	)
	
	//identifier is 'TestComponent-1', anotherTestComp.props.index == 1
    const anotherTestCompId = (className, props) => className + '-' + props.index
	const anotherTestCompProps = { index: 1 }
	const anotherTestComp = xjx.init(
		TestComponent,
		anotherTestCompId,
		anotherTestCompProps
	)

    return (
        <div>
            {testComp}
            {customTestComp}
			{anotherTestComp}
        </div>
    )
}
//remaining code of component

2. State SubAPI

This section contains methods should be used for updating and obtaining your application state. xyjax-update-immutable and xyjax-required-value NPM packages are widely-used here to implement this functionality.

2-1. xjx.state.to

Provides Redux state updating at any nesting level.

Method signature:

xjx.state.to(
    path, 
    changes,
	message
)

Arguments description:

  • path is a string with path to target field in state (fields' separator is dot, for example 'rootField.nestedField.oneMoreField')
  • changes parameter contains changes that will be aquired by Redux state, changes can be primitive literal (string, number, boolean, etc.), object or calculating field (see example 3).
  • message is an optional string parameter which will be added to action type. This is pretty useful if you use Redux DevTools extension. Equals to path parameter by default.

State updating examples:

Example 1. Creating new fields in state

In this example we have empty initial state

//basic example
xjx.state.to('test_field', 523)
//you can initialize object fields one by one...
xjx.state.to('test_object.math_random_field', Math.random())
xjx.state.to('test_object.string_field', 'string_field_value')
//...or you can apply whole object...
xjx.state.to('another_test_object', { 
    field_1: 'hello', field_2: 'world' 
})
//...at any nested level
xjx.state.to('root_field.nested_object.one_more_level', { 
    first_field: 'first', 
    second_field: 'second' 
})
xjx.state.to('even.more.nested.than.just_nested.field', 15) 

Final state will look like this:

const final_state = {
    test_field: 523,
    test_object: {
        math_random_field: 0.4648380646500174,
        string_field: 'string_field_value'
    },
    another_test_object: {
        field_1: 'hello',
        field_2: 'world'
    },
    root_field: {
        nested_object: {
            one_more_level: {
                first_field: 'first', 
                second_field: 'second' 
            }
        }
    },
    even: {
        more: {
            nested: {
                than: {
                    just_nested: {
                        field: 15
                    }
                }
            }
        }
    }
}

Example 2. State parts updating and rewriting

In this example we have following initial state:

const initial_state = {
    test_object: {
        field_1: 'testfield1',
        field_2: 'testfield2',
        some_inner_structure: {
            some_inner_field: 'innerfieldvalue',
            second_inner_field: 'secondinnerfieldvalue'
        },
        second_inner_structure: {
            some_inner_field: 'innerfieldvalue',
            second_inner_field: 'secondinnerfieldvalue'
        }
    },
    test_object_2: {
        field_1: 'anothertestfield1',
        field_2: 'anothertestfield2'
    },
    test_array: [
        {field_1: 'first_field_1', field_2: 'first_field_2'},
        {field_1: 'second_field_1', field_2: 'second_field_2'},
        {field_1: 'third_field_1', field_2: 'third_field_2'}
    ]
}

Updating and rewriting approach

These two lines of code can seem to make the same result but they are pretty diffirent in effect:

xjx.state.to('a.b', 'b_value') //approach for updating
xjx.state.to('a', { b: 'b_value' }) //approach for rewriting

First line only updates 'a' field in state, so it changes only 'b' field, other fields in 'a' object stay the same. Second line rewrites 'a' field in state with given object, so previous version of 'a' object will be replaced with { b: 'b_value' } object. Take a look at following examples:

//updating test_object
xjx.state.to('test_object.field_1', 'testfield1_modified')

//rewriting test_object_2
xjx.state.to('test_object_2', { field_1: 'testfield1_modified' })

//it works on any nesting level:
xjx.state.to(
    'test_object.some_inner_structure.some_inner_field', 
	'modified-some-inner-field'
)
xjx.state.to(
    'test_object.second_inner_structure', 
	{some_inner_field: 'modified-some-inner-field'}
)

//Xyjax path syntax can also work with arrays:
xjx.state.to('test_array.0.field_1', 'testfield1_modified')
xjx.state.to('test_array.1', {field_1: 'testfield1_modified'})

Final state:

const final_state = {
    test_object: {
        field_1: 'testfield1_modified',
        field_2: 'testfield2',
        some_inner_structure: {
            some_inner_field: 'modified-some-inner-field',
            second_inner_field: 'secondinnerfieldvalue'
        },
        second_inner_structure: {
            some_inner_field: 'modified-some-inner-field'
        }
    },
    test_object_2: {
        field_1: 'testfield1_modified'
    },
    test_array: {
        '0': {
            field_1: 'testfield1_modified',
            field_2: 'first_field_2'
        },
        '1': {
            field_1: 'testfield1_modified'
        },
        '2': {
            field_1: 'third_field_1',
            field_2: 'third_field_2'
        }
    }
}

Example 3. Calculating fields

It's possible to update state not only with strict values but also with calculating fields. It is a function which takes one argument (updating field value before update or parameter default value if field was not found in state) and returns value that will be placed in state by the given path. This is extremely useful for counters, boolean flag toggles, objects merging, arrays modifying etc.

There is a following initial state in this example:

const initial_state = {
    test_field: 15,
    test_object: {
        flag: false,
        counter: 5
    },
	test_array: []
}

Take a look at calculating fields approach:

const muliplyByTwo = (value = 0) => { return value * 2 }
const divideByTen = (value = 0) => { return value / 10 }
const increment = (value = 0) => { return value + 1 }
// default parameter is optional (you should be careful with this approach)
const toggleBooleanFlag = (currentFlag) => { return !currentFlag }

xjx.state.to('test_field', muliplyByTwo) // now test_field value is 30
xjx.state.to('test_field', divideByTen) // now test_field value is 3

// creating 'new_test_field' field in state initialized with 1 (0 + 1)
xjx.state.to('new_test_field', increment)
// incrementing 'new_test_field' (now its' value is 2)
xjx.state.to('new_test_field', increment)
// toggling test_object.flag
xjx.state.to('test_object.flag', toggleBooleanFlag)

// function, which returns calculating field for putting element in array
const addElementToArray = (element) => {
    return (array = []) => { return array.concat(element) }
}
const elementShouldBeAdded = {hello: 'world'}
xjx.state.to('test_array', addElementToArray(elementShouldBeAdded))

We have following state as a result of these evaluations:

const final_state = {
    test_field: 3,
    test_object: {
        flag: true,
        counter: 5
    },
    new_test_field: 2,
	test_array: {
	    '0': {
		    hello: 'world'
	    }
	}
}

2-2. xjx.state.to_sync

This method has same functionality as xjx.state.to and additional possibility of having function which will evaluate only after state updating will finish (Redux state update is asynchronious).

Method signature:

xjx.state.to_sync(
    path, 
    changes,
    syncHandler,
	message
)

Arguments description:

  • path is a string with path to target field in state (fields' separator is dot, for example 'rootField.nestedField.oneMoreField')
  • changes parameter contains changes that will be aquired by Redux state, changes can be primitive literal (string, number, boolean, etc.), object or calculating field (see example 3 in xjx.state.to method description)
  • syncHandler is a function with no arguments which evaluates syncroniously after state update finishing
  • message is an optional string parameter which will be added to action type. This is pretty useful if you use Redux DevTools extension. Equals to path parameter by default.

Usage example:

//there is an empty initial state in the following example

const increment = (x = 0) => { return x + 1 }

//not synchronious way
    xjx.state.to('not_sync_counter', increment)
    console.log(xjx.state.from('not_sync_counter')) 
    //undefined will be logged
	//data was taken from state while state still was not updated

//synchronious way
    const showSyncCounter = () => { console.log(xjx.state.from('sync_counter')) }
    xjx.state.to_sync('sync_counter', increment, showSyncCounter) 
    //1 will be logged
    //showSyncCounter was evaluated only after state updating has finished

2-3. xjx.state.from

Obtains Redux state data at any nesting level.

Method signature:

xjx.state.from(
    path, 
    defaultValue
)

Arguments description:

  • path is the string with path to target field in state with dot as separator (for example 'rootField.nestedField.oneMoreField')
  • defaultValue parameter contains value that will be returned if nothing would be found in state by given path or function which takes found value and returns value that will be used in your application which makes it possible for you to use any validations, mappings or data checks when you obtain data from Redux state

Usage example:

In following example we have following initial state:

const initial_state = {
    field1: 'value1',
    field2: {
        field2_1: 'value2_1',
        field2_2: {
            nested_data: 'hello'
        }
    },
    field3: 5
}
const temp1 = xjx.state.from('field1') //'value1'
const temp2 = xjx.state.from('field2') //{ field2_1: 'value2_1', field2_2: { nested_data: 'hello' } }
const temp3 = xjx.state.from('field2.field2_2.nested_data') //'hello'
const temp4 = xjx.state.from('not.existing.field', 15) //15
const temp5 = xjx.state.from('field3', (x) => { return (x % 2) ? 'odd' : 'even' }) //'odd'

3. Render SubAPI

This section contains methods which are used for React components' render control in your application.

3-1. xjx.render.allow_once

Allows component with given Xyjax component identifier rendering after one following state update.

Method signature:

xjx.render.allow_once(
    component_xjx_id
)

Arguments description:

  • component_xjx_id is a string with Xyjax component identifier that is allowed be rendered after next following state update

3-2. xjx.render.allow

Allows component with given Xyjax component identifier rendering until its' render denying.

Method signature:

xjx.render.allow(
    component_xjx_id
)

Arguments description:

  • component_xjx_id is the string with Xyjax component identifier which render must be allowed

3-3. xjx.render.deny

Denies rendering of a component with given Xyjax component identifier (can be allowed later).

Method signature:

xjx.render.deny(
    component_xjx_id
)

Arguments description:

  • component_xjx_id is the string with Xyjax component identifier which render must be denied

4. Events SubAPI

This section contains methods which are used in case of making event-based scenarios in your application. This functionality is powered by xyjax-events NPM package.

4-1. xjx.events.on_toState

Provides handlers adding on state updating.

Method signature:

xjx.events.on_toState(
    key, 
    handler
)

Arguments description:

  • key is a state part which will be observing. There are three types of keys:
    • strict key (e.g. 'a.b') - exactly given path in state will be observed
    • left side varied key (e.g. '...a.b') - every path which ends with given data (including given data itself) will be observed (e.g. 'a.b', 'root.a.b', 'root.another_root.a.b')
    • right side varied key (e.g. 'a.b...') - every path which starts with given data (including given data itself) will be observed (e.g. 'a.b', 'a.b.nested', 'a.b.nested.another_nested')
    • both side varied key (e.g. '...a.b...') - every path which includes given data (including given data itself) will be observed (e.g. 'a.b.', 'root.a.b.nested', 'root.a.b', 'a.b.nested')
  • handler is a function that will be called every time target part of state updates. Takes one parameter containing on_toState event arguments which is object of two fields: path and changes

Usage example:

const toState_testHandler = (eventArgs) => {
    const { changes, path } = eventArgs
    console.log('updated with ' + JSON.stringify(changes) + ' at path ' + path)
    //you can also use xjx in event handlers:
    xjx.state.to('events.fired_counter', (x = 0) => { return x + 1 })
}

xjx.events.on_toState('root.nested', toState_testHandler)
xjx.events.on_toState('a.b...', toState_testHandler)
xjx.events.on_toState('...c.d', toState_testHandler)
xjx.events.on_toState('...e.f...', tostate_testHandler)

xjx.state.to('root.nested', 'hello')
xjx.state.to('a.b.nested.one.more', Math.random())
xjx.state.to('root.one_more_root.c.d', {test_field: 'test_field_value'})
xjx.state.to('a1.b1.e.f.g', Math.random())

Console output:

updated with "hello" at path root.nested

updated with 0.7842862279990666 at path a.b.nested.one.more

updated with {"test_field":"test_field_value"} at path root.one_more_root.c.d

updated with 0.1234366748018656 at path a1.b1.e.f.g

Also, events.fired_counter field in state equals 3.

4-2. xjx.events.on_fromState

Provides handlers adding on state parts obtaining.

Method signature:

xjx.events.on_fromState(
    key, 
    handler
)

Arguments description:

  • key is a state part will be observing, following same system as in xjx.state.on_toState
  • handler is a function that will be called every time target part of state being obtained. Takes one parameter containing on_fromState event arguments which is object of two fields: path and value_from_state

Usage example:

There is a following initial state in this example:

const initial_state = {
    test_object: {
        test_value: 1
    },
    another_test_object: {
        another_test_value: 5
    },
    root_field: 'a',
	w: {
		x: {
			y: {
				z: 7
			}
		}
	}
}
const fromState_testHandler = (eventArgs) => {
    const { path, value_from_state } = eventArgs
    console.log('obtained ' + JSON.stringify(value_from_state) + ' from ' + path)
}

xjx.events.on_fromState('root_field', fromState_testHandler)
xjx.events.on_fromState('test_object...', fromState_testHandler)
xjx.events.on_fromState('...another_test_value', fromState_testHandler)
xjx.events.on_fromState('...x.y...', fromState_testHandler)

xjx.state.from('root_field')
xjx.state.from('test_object.test_value')
xjx.state.from('another_test_object.another_test_value')
xjx.state.from('w.x.y.z')

Which makes following console output:

obtained "a" from root_field

obtained 1 from test_object.test_value

obtained 5 from another_test_object.another_test_value

obtained 7 from w.x.y.z

5. React components

All your React components in Xyjax application should be inherited from any component from this section and not from React.Component.

5-1. xjx.base_component

It's a basic Xyjax component, you can control its' render with xjx.render API. You should extend it in following manner (same as React.Component):

class TestComponent extends xjx.base_component {
    // remaining code

Methods

  • id()

Returns Xyjax identifier of the current component. You should call it like this.id() in any your component which is extended from xjx.base_component.

There is a TestComponent component which is extended from xjx.base_component in following example:

    //TestComponent render code:
    render() {
        const identifier = this.id()
        return (
            <div>{identifier}</div>
        )
    }

    //components being rendered in code:
    const testComponentWithCustomId = xjx.init(TestComponent, 'custom-id')
    const testComponent = xjx.init(TestComponent)

testComponentWithCustomId component render result:

custom-id

testComponent component render result:

TestComponent

Static fields

  • propsValidator

Will be used as props validation config if exists. Object validation documentation and examples.

Take a look at propsValidator declaration example for TestComponent:

    class TestComponent extends xjx.base_component {
		static propsValidator = {
			fieldsList: {
				must: ['a']
			}, 
			fieldsValues: {
				a: {
					must: 
					[
						{title: 'mustBeNumber', check: (x) => typeof x == 'number'}
					]
				}
			}
		}
	...
  • componentName

There can be some problems with className property in component id generation when initialized ('p' as className when bundle is in production mode e.g.) so you can manually set value will be used as className when generating ids. Take a look at example:

    class TestComponent extends xjx.base_component {
		static componentName = 'custom_test_component_name'
	
	...
	
	const customComponent = xjx.init(
		TestComponent, 
		(className, props) => className + '_' + props.index,
		{ index: 5 }
	)
	
	customComponent.id() //custom_test_component_name_5

Package Sidebar

Install

npm i xyjax

Weekly Downloads

4

Version

2.11.6

License

MIT

Unpacked Size

63.2 kB

Total Files

34

Last publish

Collaborators

  • npm