- Creating components and rendering
- Function components
- Class-based components
- Rendering props
- Wrapper components and sub-components
- Array rendering
- Avoiding unnecessary wrapper elements
- Refs
- Rendering application
- Routing - The History API
- Calling the server asynchronously - The Server API
- Tracking users - The Anulytics API
- Wrapping up your front-end project within the <Anu.Anulytics.Provider /> component
- tracking events on elements using Anu.Anulytics.trackEvent(event, props)
- Creating and accessing the context out of the "normal props flow" - The Context API
- Storing and mutating the global state on actions dispatched, memoizing complex conversions on the global state and combining reducers - The Store API
- Dispatching actions
- Handling actions with reducers
- Memoizing (global) state conversions
- Combining reducers
- Creating the store
- Connecting components to the global state - The Connector API
- Supporting multiple languages - The Intl API
- Creating the supported language objects
- Adding the language objects to the Intl provider
- Formatting component texts
- Formatting attribute texts
- Abbreviating numbers
- Switching features on / off - The Feature API
Supports both HTML and inline-SVG element creation, "stateful" (or class-based) components and function (currently "stateless") components!
-
If you wish to use the SVG
<a>
,<style>
or<title>
tags, please use instead<anchor>
,<svgStyle>
or<svgTitle>
respectively)! -
<script>
SVG tag is not supported! Please create a separate component for the elements and define their behavior as you would do in case of a regular component! -
When creating
<svg>
tag, you don't have to define thexmlns
attribute. -
Adding style definition inside
<svgStyle>
should be done as a string template aschildren
, e.g.:<svg viewBox="0 0 10 10"> <svgStyle> {`circle { fill: gold; stroke: maroon; stroke-width: 2px; }`} </svgStyle> <circle cx="5" cy="5" r="4" /> </svg>
-
Function components are functions which can receive
props
and must always return either an HTML element, an inline-SVG element, a Component orNULL
. -
Both class-based and function component names must start with a capital letter, inline-SVG-s and HTML element names are lower-case!
const FunctionComponent = props => { // ... return ( <HTMLElement_inlineSVGElement_or_Component /> ); };
Example - returning HTML elements:
const MyTitleBar = props => { return ( <div> <h1>{props.title}</h1> </div> ); };
Example - returning inline-SVG elements:
const MyCircle = () => { return ( <svg viewBox="0 0 10 10"> <svgStyle> {`circle { fill: gold; stroke: maroon; stroke-width: 2px; }`} </svgStyle> <circle cx="5" cy="5" r="4" /> </svg> ); };
Example - returning composed elements:
const TitleWithCircle = () => { return ( <div> <MyTitleBar title="Hello ANUVerzum!" /> <MyCircle /> </div> ); };
-
Always must extend
Anu.Component
, always must implementrender()
method! -
If the component receives
props
and you want to do additional settings inside the constructor (e.g. binding handlers or setting up initial state),super(props)
should always be the first call inside theconstructor(props)
! -
You can use
setState()
to re-render the component. It takes ONE argument which can either be:-
A
state
object which will be merged with the old state -
A
setStateCallback
function which takes the actualstate
andprops
as arguments (useful ifstate
andprops
were updated asynchronously) and returns the newstate
value.type SetStateCallbackType = (prevState: Object, props: Object) => Object; setState(state: Object | SetStateCallbackType): void => { /* ... */ };
-
-
NEVER use
setState()
inconstructor()
orcomponentWillUnmount()
! -
The
setState()
can be called incomponentDidMount()
andcomponentDidUpdate()
, but only in condition - as you would do in Reactclass ClassComponent extends Anu.Component { constructor(props) { super(props); // ALWAYS bind methods if passed down to component / HTML element because of "this" keyword: this.customMethodName = this.customMethodName.bind(this); // Set initial state: this.state = { /* ... */ }; } // Lifecycle-methods: componentDidMount() { /** * - Will be called after mounting - if component is inserted in the tree within this loop * - Call "setState()" * - Set up subscriptions -- Destroy them in "componentWillUnmount()"! * - Instantiate network request (load data) */ } componentDidUpdate(prevProps, prevState) { /** * - Will be called after mounting - if component is updated within this loop * - Call "setState()" -- Only in condition! */ } componentWillUnmount() { /** * - Will be called before component is removed from the tree * - Perform necessary cleanup -- destroy subscriptions set in "componentDidMount()"! */ } // Custom method: customMethodName() { /** * - "setState()" can be used. * - Possible params for "setState()": * - partialState -- object | callback -- The new state object or a callback with params "prevState" and "prevProps" */ } render() { return ( <HTMLElement_inline-SVGElement_or_Component onCustomEvent={this.customMethodName} /> ); } }
-
To render props, store an HTML element, inline-SVG element or component in a variable and when rendering, insert it, e.g.:
const PropsRenderer1 = props => { return <div>{props.children}</div>; }; // Short syntax: const PropsRenderer2 = ({ text }) => <p>{text}</p>;
- The approach is the following:
-
First, create a wrapper component which will render its
props.children
, for example. -
Then, create wrapped / sub-components.
-
Add the sub-component to the wrapper as a property.
// Wrapper element const ElemWrapper = props => { return <div>{props.children}</div>; }; // Sub-component const Elem = props => { return <p>{props.children}</p>; }; // Adding sub-component to the wrapper component ElemWrapper.Elem = Elem; // Usage: <ElemWrapper> <ElemWrapper.Elem> Lorem Ipsum Dolor... </ElemWrapper.Elem> <ElemWrapper.Elem> Lorem Ipsum Dolor... </ElemWrapper.Elem> </ElemWrapper>
-
-
When rendering array, you don't return a nested structure but rather an array of elements / components, e.g.:
const ArrayRenderer = props => { return [ <p>{props.firstParagraph}</p>, <p>{props.secondParagraph}</p>, ]; }; // Usage: const ArrayRendererWrapper = props => { return ( <div> <ArrayRenderer firstParagraph={props.firstParagraph} secondParagraph={props.secondParagraph} /> </div> ); };
-
It is useful when you have a list of properties you want to loop through and render them (i.e. not in a nested structure but rather one after the other) but you don't want an extra
<div />
element to be rendered. -
When wrapping a list of elements within
<Anu.Fragment />
, the System won't wrap it within an extra (and unnecessary) wrapper element, like a<div />
.const ElemList = props => { return ( <Anu.Fragment> {props.somethingToLoop.map(prop => <li>{prop}</li>)} </Anu.Fragment> ); }; // Usage: const OrderedElemList = () => { const somethingToLoop = ['Coffee', 'Tea', 'Pálinka']; return ( <div> <h4>Ordered list:</h4> <ol> <ElemList somethingToLoop={somethingToLoop} /> </ol> </div> ); };
-
In most cases, there is no need to use refs to manage (HTML) sub-components from the parent (class) component. However, there are some cases (e.g. focusing an input element after it has been mounted, uploading files using a custom uploader button, handling onclick event outside of a given element, etc.) when you want to imperatively modify a child outside of the typical dataflow.
-
Refs can ONLY BE CREATED IN CLASS COMPONENTS but can be passed as prop - use different name then!
-
Set
ref
attribute / property ONLY ON HOST COMPONENT!Example - focusing an input element:
class RefTestClass extends Anu.Component { constructor(props) { super(props); this.input = Anu.createRef(); } componentDidMount() { this.input.current.focus(); } render() { return ( <label> <input ref={this.input} /> </label> ); } }
Example - file uploader with preview:
class FileUploader extends Anu.Component { constructor(props) { super(props); this.fileInput = Anu.createRef(); this.state = { files: [] }; this.uploadFiles = this.uploadFiles.bind(this); this.handleFileUploaderClick = this.handleFileUploaderClick.bind(this); } handleFileUploaderClick() { this.fileInput.current.click(); } uploadFiles({ target }) { const nextState = { files: [] }; for (let i = 0; i < target.files.length; i++) { nextState.files.push({ // Creates a name for preview. // Use it as 'src' attribute value in image name: URL.createObjectURL(target.files[i]), file: target.files[i] }); } this.setState(nextState); } render() { const { files } = this.state; return ( <Anu.Fragment> <button className="my-fancy-uploader-button" onClick={this.handleFileUploaderClick} > Feltöltés </button> <input type="file" ref={this.fileInput} multiple onChange={this.uploadFiles} style={{ display: 'none' }} /> <ul> {files.length > 0 && files.map(({ name, file }) => ( <li> <img src={name} alt={`Uploaded file name: ${file.name}, type: ${file.type}, size: ${file.size}`} /> </li> ))} </ul> </Anu.Fragment> ); } }
Example - handling
onClick
event outside of the referenced element:class ClickOutsideComponent exttends Anu.Component { constructor(props) { super(props); this.state = { show: true }; this.myRef = Anu.createRef(); this.handleClickOutside = this.handleClickOutside.bind(this); } componentDidMount() { document.addEventListener('mousedown', this.handleClickOutside); } componentWillUnmount() { document.removeEventListener('mousedown', this.handleClickOutside); } handleClickOutside({ target }) { if( this.myRef.current && !this.myRef.current.contains(target) ) { this.setState({ show: false }); } } render() { const { show } = this.state; return show ? ( <div ref={this.myRef}> Content will be hidden after you click outside of this box </div> ) : null; } }
Example - drag-and-drop todo list:
const TODO_STATUS = { PENDING: 'pending', COMPLETED: 'completed' }; class DraggableTodoList extends Anu.Component { constructor(props) { super(props); this.state = { todoList: [ { name: 'Coding', status: TODO_STATUS.PENDING }, { name: 'German course', status: TODO_STATUS.PENDING }, { name: 'Mathematics course', status: TODO_STATUS.PENDING }, { name: 'Training', status: TODO_STATUS.PENDING }, { name: 'Physics course', status: TODO_STATUS.COMPLETED } ] }; this.handleDragStart = this.handleDragStart.bind(this); this.handleDragOver = this.handleDragOver.bind(this); this.handleDrop = this.handleDrop.bind(this); } handleDragStart({ dataTransfer }, taskName) { dataTransfer.setData('id', taskName); } handleDragOver(event) { event.preventDefault(); } handleDrop({ dataTransfer }, status) { const { todoList } = this.state; const id = dataTransfer.getData('id'); const list = todoList.map(task => { if (task.name === id) { task.status = status; } return task; }); this.setState({ todoList: list }); } render() { const { todoList } = this.state; const todoStatus = { [TODO_STATUS.PENDING]: [], [TODO_STATUS.COMPLETED]: [] }; todoList.forEach(task => { todoStatus[task.status].push( <div draggable className="todoListItem" onDragStart={event => this.handleDragStart(event, task.name)} > {task.name} </div> ); }); return ( <div className="todoListContainer"> <div className="todoListContent" onDragOver={this.handleDragOver} onDrop={event => this.handleDrop(event, TODO_STATUS.PENDING)} > <h4> <Anu.Intl.FormattedMessage id="proba.task.pending" defaultMessage="proba.task.pending" />: </h4> {todoStatus[TODO_STATUS.PENDING]} </div> <div className="todoListContent" onDragOver={this.handleDragOver} onDrop={event => this.handleDrop(event, TODO_STATUS.COMPLETED)} > <h4> <Anu.Intl.FormattedMessage id="proba.task.completed" defaultMessage="proba.task.completed" />: </h4> {todoStatus[TODO_STATUS.COMPLETED]} </div> </div> ); } }
Example - infinite scroll:
class ScrollComponent extends Anu.Component { constructor(props) { super(props); this.state = { photos: [], loading: false, page: 0, prevY: 0 }; this.loadingRef = Anu.createRef(); this.handleObserver = this.handleObserver.bind(this); this.getPhotos = this.getPhotos.bind(this); } getPhotos(page) { this.setState({ loading: true }); Anu .ServerAPI .get('/app/my/server/url', { page, limit: 10 }) .then(res => { this.setState({ photos: [...this.state.photos, ...res.data] }); this.setState({ loading: false }); }); } componentDidMount() { this.getPhotos(this.state.page); const options = { root: null, rootMargin: "0px", threshold: 1.0 }; this.observer = new IntersectionObserver( this.handleObserver, options ); this.observer.observe(this.loadingRef); } handleObserver(entities, observer) { const y = entities[0].boundingClientRect.y; if (this.state.prevY > y) { const lastPhoto = this.state.photos[this.state.photos.length - 1]; const curPage = lastPhoto.albumId; this.getPhotos(curPage); this.setState({ page: curPage }); } this.setState({ prevY: y }); } render() { const loadingCSS = { height: "100px", margin: "30px" }; const loadingTextCSS = { display: this.state.loading ? "block" : "none" }; return ( <div className="container"> <div style={{ minHeight: "800px" }}> {this.state.photos.map(user => ( <img src={user.url} height="100px" width="200px" /> ))} </div> <div ref={this.loadingRef} style={loadingCSS} > <span style={loadingTextCSS}>Loading...</span> </div> </div> ); } }
Example - laser pointer:
const canvasStyle = { height: '500px', width: '100%', border: '1px solid black' }; const laserPointerStyle = { position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: '0.6', pointerEvents: 'none', left: '-20px', top: '-20px', width: '40px', height: '40px', zIndex: '10' }; class LaserPointer extends Anu.Component { constructor(props) { super(props); this.state = { layerX: 0, layerY: 0 }; this.handleMove = this.handleMove.bind(this); } handleMove({ layerX, layerY }) { this.setState({ layerX, layerY }); } render() { const { layerX, layerY } = this.state; return ( <div style={canvasStyle} onMouseMove={this.handleMove} > <div id="laser-pointer" style={{ ...laserPointerStyle, transform: `translate(${layerX}px, ${layerY}px)` }} /> </div> ); } }
Example - paint application with preview (image can be saved as PNG) with
canvas
API:const canvasHeight = 500; const canvasWidth = 975; const canvasContainer = { display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', width: '100%', alignItems: 'center' }; const drawArea = { width: '100%', height: '552px', border: '1px solid #808080', position: 'relative', backgroundColor: 'white' }; const canvasMenuStyle = { width: '650px', height: '50px', display: 'flex', justifyContent: 'space-evenly', borderRadius: '5px', alignItems: 'center', backgroundColor: '#a3a3a32d', margin: '0 auto' }; const canvas = { height: `${canvasHeight}px`, width: `${canvasWidth}px`, cursor: 'crosshair' }; const imgStyle = { height: `${canvasHeight}px`, width: `${canvasWidth}px`, border: '1px solid black' }; const BRUSH_MODES = { PEN: 'Pen', ERASER: 'Eraser' }; class Canvas extends Anu.Component { constructor(props) { super(props); this.state = { isDrawing: false, lineWidth: 1, lineColor: 'black', lineOpacity: 1, brushMode: BRUSH_MODES.PEN, dataUrl: undefined }; this.canvasRef = Anu.createRef(); this.ctxRef = Anu.createRef(); this.imageRef = Anu.createRef(); this._reRender = this._reRender.bind(this); this.startDrawing = this.startDrawing.bind(this); this.endDrawing = this.endDrawing.bind(this); this.draw = this.draw.bind(this); this.setLineWidth = this.setLineWidth.bind(this); this.setLineColor = this.setLineColor.bind(this); this.setOpacity = this.setOpacity.bind(this); this.setBrushMode = this.setBrushMode.bind(this); this.handleSave = this.handleSave.bind(this); } _reRender() { const { lineWidth, lineColor, lineOpacity, brushMode } = this.state; const canvasContext = this.canvasRef.current && this.canvasRef.current.getContext && this.canvasRef.current.getContext('2d'); canvasContext.lineCap = "round"; canvasContext.lineJoin = "round"; canvasContext.globalAlpha = lineOpacity; canvasContext.strokeStyle = lineColor; canvasContext.lineWidth = lineWidth; this.ctxRef.current = canvasContext; this.ctxRef.brushMode = brushMode; } componentDidMount() { this._reRender(); } componentDidUpdate() { this._reRender(); } startDrawing(event) { const rect = event.target.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; this.ctxRef.current.beginPath(); this.ctxRef.current.moveTo(x, y); this.setState({ isDrawing: true }); } endDrawing() { this.setState({ isDrawing: false }); } draw(event) { const { lineWidth, brushMode, isDrawing } = this.state; if (!isDrawing) { return; } const rect = event.target.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; if (brushMode === BRUSH_MODES.PEN) { this.ctxRef.current.lineTo(x, y); this.ctxRef.current.stroke(); } else { this.ctxRef.current.clearRect( x - lineWidth / 2, y - lineWidth / 2, lineWidth, lineWidth ); } } setLineWidth({ target: { value } }) { this.setState({ lineWidth: value }); } setLineColor({ target: { value } }) { this.setState({ lineColor: value }); } setOpacity({ target: { value } }) { this.setState({ lineOpacity: value / 100 }); } setBrushMode() { this.setState(prevState => ({ ...prevState, brushMode: prevState.brushMode === BRUSH_MODES.PEN ? BRUSH_MODES.ERASER : BRUSH_MODES.PEN })); } handleSave() { const dataUrl = this.canvasRef.current.toDataURL(); this.setState({ dataUrl }); } render() { const { lineWidth, lineColor, lineOpacity, brushMode, dataUrl } = this.state; return ( <div style={canvasContainer}> <h1>Paint App</h1> <div style={drawArea}> <div style={canvasMenuStyle}> <label> Brush Color <input type="color" value={lineColor} onChange={this.setLineColor} /> </label> <label> Brush Width <input type="range" min="1" max="20" value={lineWidth} onChange={this.setLineWidth} /> </label> <label> Brush Opacity <input type="range" min="1" max="100" value={lineOpacity * 100} onChange={this.setOpacity} /> </label> <label> Selected brush type: <button onClick={this.setBrushMode}> {brushMode} </button> </label> <button onClick={this.handleSave}>Save</button> </div> <canvas style={canvas} onMouseDown={this.startDrawing} onMouseUp={this.endDrawing} onMouseMove={this.draw} ref={this.canvasRef} height={canvasHeight} width={canvasWidth} /> <img style={imgStyle} src={dataUrl} ref={this.imageRef} /> </div> </div> ); } }
- Select an existing HTML element (typically having
id
as "root" or "app") and use theAnu.render()
method which takes two arguments:-
The first one is the component you want to add to the DOM tree.
-
The second one is a valid HTML element already in the DOM tree, to which you want to attach your component.
const ID = 'root'; Anu.render( <App />, document.getElementById(ID) );
-
- You can use
<Anu.History.Link />
elements those work just like "normal" links but without the reload of the page:- The
<Anu.History.Link />
element has ato
prop. When clicking on it, itsto
prop will be matched against thepath
prop of<Anu.History.Route />
.
- The
- Use the
<Anu.History.Route />
component that takes apath
argument and if the link you clicked matches thepath
, it will render the attached component:-
The
<Anu.History.Route />
component has apath
prop to match against the URL. If it has anexact
prop, it doesn't allow partial matching. -
The
<Anu.History.Route />
can also have acomponent
(must be a component) or arender
prop (it must be a function that returns a component).const Home = () => <h2>Home</h2>; const About = () => <h2>About</h2>; const Topic = ({ topicId }) => <h3>{topicId}</h3>; const Topics = ({ match }) => { const items = [ { name: 'Rendering with React', slug: 'rendering' }, { name: 'Components', slug: 'components' }, { name: 'Props v. State', slug: 'props-v-state' }, ]; return ( <div> <h2>Topics</h2> <ul> {items.map(({ name, slug }) => ( <li> <Anu.History.Link to={`${match.url}/${slug}`}>{name}</Anu.History.Link> </li> ))} </ul> {items.map(({ name, slug }) => ( <Anu.History.Route path={`${match.path}/${slug}`} render={() => ( <Topic topicId={name} /> )} /> ))} <Anu.History.Route exact path={match.url} render={() => ( <h3>Please select a topic.</h3> )} /> </div> ); }; // Usage in application: const RouterApp = () => { return ( <div> <ul> <li> <Anu.History.Link to="/">Home</Anu.History.Link> </li> <li> <Anu.History.Link to="/about">About</Anu.History.Link> </li> <li> <Anu.History.Link to="/topics">Topics</Anu.History.Link> </li> </ul> <hr /> <Anu.History.Route exact path="/" component={Home} /> <Anu.History.Route path="/about" component={About} /> <Anu.History.Route path="/topics" component={Topics} /> </div> ); };
-
- If the user needs to be redirected (e.g. if not logged in), use the
<Anu.History.Redirect />
component:-
The
<Anu.History.Redirect />
takes ato
(URL) and an optionalpush
(boolean) argument.
This will tell the System to render the component which can be found on theto
URL (which is basically an<Anu.History.Route />
which will render the corresponding component). Ifpush
is present, it will push the URL into the History API instead of replacing the current one.const RouteredApp = () => { return ( // ... <Anu.History.Route path="/something" render={() => { if(loggedIn) { return <MainPageComponent />; } else { return <Anu.History.Redirect to="/" push />; } }} /> ); };
-
- If you need to call a route from a function, use the
Anu.History.goTo()
function.-
It takes a
path
string argument. If not specified, it will use its default path ('/'
). -
Optionally, it can take a
replace
boolean argument. If set totrue
, it will replace the current URL. By default, it pushes into the History API. -
Please only use
Anu.History.goTo()
if you need to redirect user inside the code based on functionality (like inif
statement) for accessibility reasons.// Pushes URL into History API by default Anu.History.goTo('/about'); // Replaces current URL with the 'path' argument in History API Anu.History.goTo('/about', true);
-
The Anu.ServerAPI
is basically built on top of Promise
and currently has 5 methods get()
, post()
, put()
, delete()
and file()
.
-
The
Anu.ServerAPI.get()
andAnu.ServerAPI.delete()
perform a GET or DELETE HTTP method respectively to get data from the server or delete a specific data from the server (usually referenced by anid
attribute in both cases but not necessarily when using GET).
They are faster than the POST or PUT HTTP methods but lack the security as well. -
Can take a
url
(string - MUST ALWAYS START WITH "/app
"!) and an optionalparams
(object) argument and return aPromise
object. You can send params within the URL AND/OR as URI query parameters, e.g.:const passedValueForGet = '1234'; // Represents an ID const paramsForGet = { key: 'value', nextKex: 'nextValue' }; Anu .ServerAPI .get(`/app/my-server-url/${passedValueForGet}`, paramsForGet) .then(({ response }) => { /* ... */ }) .catch(({ status }) => { /* ... */ }); // In this case, the XHR URL will be: `/app/my/server/url/${passedValueForGet}?key=value&nextKex=nextValue` const passedValueForDelete = '1234'; // Represents an ID Anu .ServerAPI .delete(`/app/my-server-url/${passedValueForDelete}`) .then(({ response }) => { /* ... */ }) .catch(({ status }) => { /* ... */ }); // In this case, the XHR URL will be: `/app/my/server/url/${passedValueForDelete}`
-
The
Anu.ServerAPI.post()
andAnu.ServerAPI.put()
perform an POST or PUT HTTP method respectively to send large data to the server and optionally get other data back. -
It takes a
url
(string) and adata
(object) argument and returns aPromise
object:const dataForPost = { /* ... */ }; Anu .ServerAPI .post('/app/my-server-url/', dataForPost) .then(({ response }) => { /* ... */ }) .catch(({ status }) => { /* ... */ }); const passedValue = '1234'; // Represents an ID const dataForPut = { /* ... */ }; Anu .ServerAPI .put(`/app/my-server-url/${passedValue}`, dataForPut) .then(({ response }) => { /* ... */ }) .catch(({ status }) => { /* ... */ });
-
The
Anu.ServerAPI.file()
performs a POST HTTP method and encodes one or more files to send them to the server. -
It takes an
url
(string), afile
(oneFile
object or an ARRAY ofFile
objects) argument and an optionaldata
(object) and returns aPromise
object:const data = { /* ... */ }; Anu .ServerAPI .file('/app/my-server-url/', File, data) .then(({ response }) => { /* ... */ }) .catch(({ status }) => { /* ... */ });
<ANUVerzum />
JS Framework comes with its built-in analytics tool.
Unlike the most analytics tools, Anulytics API is designed to send the collected informations only once: when the user leaves the page (either changing tab, navigating to a different domain or closing the page / browser).
This way, no unnecessary XHR calls are made, which could otherwise negatively impact performance and user experience.
- It has two public interfaces:
<Anu.Anulytics.Provider />
andAnu.Anulytics.trackEvent()
. - In order to use the built-in analytics tool, you must wrap all your components you want to track within
<Anu.Anulytics.Provider />
component (without this, you can't useAnu.Anulytics.trackEvent()
event tracker functionality).
- This component is responsible for the aggregation of the collected data and sends it to the server. It can have only one child element.
- Once the user leaves the page or navigates to / focuses on another tab, the component sends the collected data to the user-defined server via HTTP POST method.
- It takes 4 properties:
-
analyticsUrl
, which is a string that represents the URL of the server you want to send the data you collected. -
userData
is an object of data about the user. Make sure that you ask for permissions from the end-users and build theuserData
object accordingly in order to stay GDPR-compliant. Always ask permission from the end-user, highlighting the types of data you wish to use. -
onSuccess
callback is a custom callback function that will be fired when data transmission ended successfully. -
onFail
callback is a custom callback function that will be fired when data transmission failed.const App = () => ( <Anu.Anulytics.Provider analyticsUrl={analyticsUrl} userData={userData} onSuccess={handleSuccess} onFail={handleFail}> <Your_Site_Goes_Here /> </Anu.Anulytics.Provider> );
-
The
data
object sent to the server looks like the following:type Data = { events: array<Event>, startDate: Date, endDate: Date, system: { referrer: string | null, innerWidth: number, isMobileAppInstalled: boolean, userAgentData: { userAgent: string | array<UserAgent> mobile: boolean, platform: string | null } }, user: User };
-
The
Event
type is an object which key attribute is the type of the event (a string that represents the event, action type or a link if it was fired by navigation).
These are the possible event keys'initialization'
,'navigation'
,'userAction'
,'stateChange'
or'pageLeave'
:'initialization'
is always set once the page loaded.'navigation'
is set on inner navigation (if user clicks on a<Anu.History.Link />
, theAnu.History.goTo()
was called or the user got redirected via<Anu.History.Redirect />
).'userAction'
is set whenAnu.Anulytics.trackEvent()
has been used.
This is the only public API of Anulytics API, typically used for tracking user events (events triggered by user interactions).'stateChange'
is automatically called when an action gets dispatched.'pageLeave'
is always set once the user focuses on another tab or closes the application (i.e.: leaving the current / actual page).
-
The value of the Event object contains three properties:
eventType
,timestamp
andproperties
:-
The
eventType
is a string that represents the event fired (e.g.: a user event (mouse event, keyboard event, etc.), a URI where the user navigated or an action type the user dispatched).- Note that the
eventType
property of'initialization'
,'navigation'
and'pageLeave'
will always be a URI!
- Note that the
-
The
timestamp
is always set: it is the POSIX timestamp of the fired event. -
The
properties
can either be an empty object (typically when the event was fired by navigation) or aProperty
type. Theproperties
are typically set whenAnu.Anulytics.trackEvent()
has been used or an action was dispatched.
Theproperties
are empty on'initialization'
,'navigation'
and'pageLeave'
.interface Event { [key]: { // "key" is always 'initialization', 'navigation', 'userAction', 'stateChange' or 'pageLeave' eventType: UserEvent | ActionType | URI, timestamp: Date, properties: UserActionProperties | StateChangeProperties | {} } }
-
-
The
UserActionProperties
type can have various properties, likeid
,name
,url
andvalue
:-
The
id
is the value of the ID attribute of the DOM element with the given event handler tracked:event.target.id
(optional but HIGHLY RECOMMENDED to set). -
The
name
is the value of the name attribute of the DOM element with the given event handler tracked:event.target.name
(optional but HIGHLY RECOMMENDED to set). -
The
nodeName
is the name of the DOM node with the given event handler tracked:event.target.nodeName
. -
The
keyCode
is the ASCII code of the key pressed (if the given element is a keyboard event, likekeyup
) or null. -
The
value
is the value of the DOM element with the given event handler tracked:event.target.value
. -
The
pageX
is the X-coordinate of the mouse position from the left side of the page:event.pageX
. -
The
pageY
is the Y-coordinate of the mouse position from the top of the page:event.pageY
. -
The
scrollTop
is the amount the user scrolled from the top of the page. -
The
scrollLeft
is the amount the user scrolled from the left side of the page. -
The
url
is the URL of the page that contains the DOM element with the given event handler tracked.type UserActionProperties = { id: string, name: string, nodeName: string, keyCode: number | null, value?: string, pageX: number, pageY: number, scrollTop: number, scrollLeft: number, url: string, props: Object | null // User-defined props, 2nd argument passed to Anu.Anulytics.trackEvent() };
-
-
The
StateChangeProperties
type can have four properties:url
,prevState
,action
andnextState
:-
The
url
is the URL of the page that contains the DOM element with the given event handler tracked. -
The
prevState
is the global state object before the action was performed. -
The
action
is the dispatched action. -
The
nextState
is the global state object after the action was performed.type StateChangeProperties { url: string, prevState: Object, action: Object, nextState: Object }
-
-
The
User
type is an object of the developer choice. If not provided, an empty object will be passed.
-
- Anulytics API tracks visited links within the application out of the box.
If you want to track additional elements, call
Anu.Anulytics.trackEvent(event, props)
method inside the event handler. - It takes two arguments:
- The
event
object is always needed because this object contains required information to track. - The
props
is optional: either an object (not an array) or null.
- The
-
To create a context provider and context consumer elements with default context, call the
Anu.createContext()
. It takes acontext
argument which can be reached later ascontext.defaultContext.value
and returns a context provider and consumer:const ThemedContext = Anu.createContext({ theme: 'Theme-1' }); // This can be accessed as "context.defaultContext.value.theme"
-
Context props defined on
<ThemedContext.ContextProvider />
can be accessed from within the function child of the<ThemedContext.ContextConsumer />
ascontext.value
. -
Context providers can have multiple context consumer descendents.
-
Context consumers can have one function-as-a-child (which takes the
context
as argument) which must return a valid HTML, inline-SVG element, component (either class-based or function) ornull
. -
You can have as many elements between the context provider and consumer(s), as you want.
No need to pass thecontext
all the way down within the "props flow"; the function child of the context consumer will have access to it by default.
It allows you to create your "intermediate" components without depending from thecontext
(they don't need to be aware of it if they have nothing to do with it...).const ComponentWithContext = () => { const theme2 = 'Theme-2'; return ( <ThemedContext.ContextProvider theme={theme2}> <MyComponent1> { /* Here as many "intermediate" components as you need */ } <MyComponentN> <ThemedContext.ContextConsumer> {context => { // Function that receives 'context' as argument and returns a valid element or component (or null) const { value: { theme }, defaultContext: { value: { theme: defaultTheme } } } = context; return ( <Anu.Fragment> <span>{theme}</span> <span>{defaultTheme}</span> </Anu.Fragment> ); }} </ThemedContext.ContextConsumer> </MyComponentN> </MyComponent1> </ThemedContext.ContextProvider> ); };
The next APIs (Anu.Connector.connect()
and <Anu.Connector.Provider />
; <Anu.Intl.Provider />
, <Anu.Intl.FormattedMessage />
, Anu.Intl.formatMessage()
and Anu.Intl.abbreviateNumber()
; <Anu.Feature.Provider />
and <Anu.Feature.Toggle />
) are strongly relying on the Context API
Storing and mutating the global state on actions dispatched, memoizing complex conversions on the global state and combining reducers - The Store API
- Create action creator functions (returns an object) or asynchronous action creator functions (returns a function) to dispatch action(s):
- To create a synchronous action creator, simply create a function that returns an object.
You MUST specify its
type
member - the reducer will react when an action with this type was dispatched. - If you create asynchronous action creator as well, then remember, the action creator should return a FUNCTION instead of an object (in this case, you MUST use a middleware - see Creating the store section):
-
The outermost function will take whatever we pass to it
-
That returned function takes 2 arguments:
dispatch
andgetState
-
Here. you can do additional things (like getting out a value from the state using
getState()
or dispatching an action usingdispatch()
) before returning the final payload (most likely an AJAX request).// Simple action creator: const myActionCreator = passedProps => { return { type, // Must have! payload: { passedProps }; }; // Async action creator - requires middleware: const myAsyncActionCreator = value => (dispatch, getState) => { // call a function that dispatches action(s) or returning a promise };
Example - creating a "simple" action creator:
// Action type: const mySimpleActionTypes = { ACTION: 'MY_SIMPLE_ACTION' }; // Action creator: const mySimpleActionCreator = param => ({ type: mySimpleActionTypes.ACTION, payload: { param } });
Example - creating an "asynchronous" action creator:
// Action type: const myAsyncActionTypes = { PENDING: 'MY_PENDING_ACTION', FULFILLED: 'MY_FULFILLED_ACTION', REJECTED: 'MY_REJECTED_ACTION', }; // Async action creator: const myAsyncActionCreator = value => dispatch => { dispatch({ type: myAsyncActionTypes.PENDING }); return Anu .ServerAPI .get(`/app/my/server/url/${value}`) .then(({ response }) => dispatch({ type: myAsyncActionTypes.FULFILLED, payload: { response } })) .catch(({ status }) => dispatch({ type: myAsyncActionTypes.REJECTED, payload: { status } })); };
-
- To create a synchronous action creator, simply create a function that returns an object.
You MUST specify its
-
Note that if it is added to the rootReducer, it will represent that "sub-tree" of the state so, it should return that part, with the updated desired values:
-
Note that the relationship between actions and reducers is M:N!
-
NEVER mutater the state directly!
// Initial state: const initialStateForMyReducer1 = { statePart: { part1 = defaultValue } }; // Reducer: const myReducer1 = (state = initialStateForMyReducer1, action) => { switch(action.type) { case 'ACTION_1': { return { statePart: { part1: action.payload.part1 } }; } // ... default: { return state; } } };
Example - handling the previous actions:
const defaultState = { /* ... */ }; const myReducerForSimpleActionCreator = (state = defaultState, { type, payload }) => { switch(type) { case mySimpleActionTypes.ACTION: { const { param } = payload; return { ...state, param }; } case myAsyncActionTypes.PENDING: { return { ...state, status: 'PENDING' }; } case myAsyncActionTypes.FULFILLED: { const { response } = payload; return { ...state, status: 'FULFILLED', data: response, errorCode: null }; } case myAsyncActionTypes.REJECTED: { const { status } = payload; return { ...state, status: 'REJECTED', data: null, errorCode: status }; } default: { return state; } } };
- Selectors are memoized functions which come handy if you need to do expensive calculations or conversions on the global state.
- To create selector, use the
Anu.store.createSelector()
(it comes handy inmapStateToProps()
- see Connecting components to the global state - The Connector API section):-
Its first argument is an array of "getter" functions which will return the desired slice of the global state object. These functions must always return something.
-
The second argument is a "handler" function which take as many arguments as many "getter" functions you defined; these arguments are the return values of the "getter" functions. Their number and order is the same as of the "getters" within the first (array) argument of the
Anu.store.createSelector()
.// Getter functions: const functionThatGetsSomePartOfState1 = state => { return state[part1]; }; // ... const functionThatGetsSomePartOfStateN = state => { return state[partN]; }; // Store the getters within an array (this will be the first argument of the selector): const getters = [ functionThatGetsSomePartOfState1, // ... functionThatGetsSomePartOfStateN ]; // Define the second (function) argument of the selector: const handler = ( partOfTheState1, // ... partOfTheStateN ) => { // Do something using the return somethingDerivedFromStatePartsParams; }; // Selector: const mySelector = Anu.store.createSelector(getters, handler);
-
- You can combine multiple reducers using the
Anu.store.combineReducers()
if you need a more complex state shape:-
The function receives one (object) argument - the "key" of the item will be the name of the corresponding state-part, the "value" is the reducer you want to add to the combined reducer.
-
Anu.store.combineReducers()
can be used multiple times in the application.const combinedReducer = Anu.store.combineReducers({ myStatePart1: myReducer1, // ... myStatePartN: myReducerN });
-
It also makes sense to create an initial state description (where you can reuse the initial states related to the reducers you just combined).
-
The "key" of this object must match with the corresponding "key" you used within the combined reducer, the "value" is the initial state (the one you (possibly) used for the "single" reducer). It is not mandatory however, it comes handy when your state object becomes large and complex:
const initialState = { myStatePart1: myStatePart1 // ... myStatePartN: myStatePartN };
-
- The store object stores the global state object (that can be reached using the
store.getState()
methiod) and it also has thestore.dispatch()
,store.subscribe()
andstore.unsubscribe()
methods. You will likely use thestore.getState()
andstore.dispatch()
methods only because subscribing and unsubscribing functionalities are "wired" into the<Anu.Connector.Provider />
and the "connedted" (also known as "container") component(s) created by using theAnu.Connector.connect()
- see Connecting components to the global state - The Connector API section. - Create a store object using
Anu.store.createStore()
:- The first argument is the
rootReducer
which is always needed (can be either a "single" reducer or a combination of ("single" and/or combined) reducers), - The second
initialState
argument is optional however, if you don't use it, the initial state will beundefined
. This argument is useful if you want to have initialized values for your application before dispatching your first action. - The third argument is optional, you can use it if you want to apply middleware functionalities like dispatching asynchronous actions (e.g. AJAX calls or delayed calls).
In this case, the built-in
Anu.store.middleware.applyMiddleware()
can be passed:- It can take any numbers of callbacks (even zero) those will run on every actions dispatched.
- There are 2 built-in callbacks you can use out-of-the-box:
-
The
Anu.store.middleware.loggingMiddleware()
is a logger; it comes handy when in development mode, -
The
Anu.store.middleware.thunkMiddleware()
enables you to use asynchronous action creators (those returning functions instead of objects) as well.// Assuming that the 'rootReducer' is the 'combinedReducer' created before: const rootReducer = combinedReducer; // If you don't use middleware (i.e. you don't need to handle asynchronous calls): const syncStore = Anu.store.createStore(rootReducer, initialState); // Otherwise, if you wish handle asynchronous calls, like AJAX requests: const asyncStore = Anu.store.createStore( rootReducer, initialState, Anu.store.middleware.applyMiddleware( Anu.store.middleware.thunkMiddleware, Anu.store.middleware.loggingMiddleware ) );
-
- The first argument is the
This API is designed to connect your global store with elements within the presentation layer.
-
Wrap the outermost component (this should contain all your "container" components - see Create container component section) within
<Anu.Connector.Provider />
and pass the store object:class App extends Anu.Component { render() { return ( <Anu.Connector.Provider store={store}> <Your_Page /> </Anu.Connector.Provider> ); } };
- To connect your "container" component to the global store, simply define a component (either a function or class-based) you want to connect.
It is a "curried function" (a function returns a function, also known as "high-order function" (HOF)).
The first function takes two arguments: the
mapStateToProps()
andmapDispatchToProps()
, the second one takes the wrapped component. - Be aware that the props of wrapped component are the combination of the props passed to the (outer) component (i.e. when rendering in the return statement) and those returned by
mapStateToProps()
andmapDispatchToProps()
! - Define maximum two functions (they are typically called
mapStateToProps()
andmapDispatchToProps()
; if either one is not defined, passnull
respectively):- The
mapStateToProps()
can fetch values from the global state and injects them as props into the wrapped component you want to connect:- It takes the
state
as the first argument and an optionalownProps
which is basically the object of the props you pass down from the caller component as part of the "props flow". - The function must return an object of props which can now be used by the wrapped component as if they were "regular" props.
- This function is the ideal place to use selectors - see Memoizing (global) state conversions section.
- It takes the
- The
mapDispatchToProps()
can define functions those dispatch actions and injects them as props into the wrapped component you want to connect:-
It takes the
dispatch
as the first argument and an optionalownProps
which is basically the object of the props you pass down from the caller component as part of the "props flow". -
The function must return an object of props (functions-as-props those dispatch actions - call action creators) which can now be used by the wrapped component as if they were "regular" props.
// Create function component: const WrappedComponent = props => { return ( <Component_to_render /> ); }; // Or class component: class WrappedComponent extends Anu.Component { render() { return ( <Component_to_render /> ); } }; const mapStateToProps = (state, ownProps) => { const { statePart1 } = state; // Extracting part from global state return { propName1: statePart1, // ... propNameN: mySelector(state), // Using selector to derive some parts of the global state - see 'Store API' section {...ownProps} }; }; const mapDispatchToProps = (dispatch, ownProps) => { return { dispatcherPropName1: ({ ...passedProps }) => dispatch(myActionCreator1(passedProps)), // ... dispatcherPropNameN: ({ ...passedProps }) => dispatch(myActionCreatorN(passedProps)) }; }; // Connect: const ContainerComponent = Anu.Connector.connect(mapStateToProps, mapDispatchToProps)(WrappedComponent);
-
- The
The basic concept behind the Intl API is that you define different JSON objects for each languages you support in your application and you refer to the text-part you need.
- Use the
<Anu.Intl.Provider />
to set the supported language files for your "static texts" (no end-user-entered texts will be translated...) and the set the selected language in your app. - Then, use the
<Anu.Intl.formattedMessage />
to place the text on the selected language referred by theid
property. You can also useAnu.Intl.formatMessage()
when you want to translate a property value only (e.g. the placeholder text in an input element). - If the text itself should also contain a dynamic text value, you can pass an optional
values
property which contains key-value pairs. - In the text inside the language file, you should refer for the key using the
{key}
format.
-
These are basically simple objects with key-value pairs. The key is always the ID of the referred text (see Formatting component texts and Formatting attribute texts section), the value is the value to print.
-
Create as many language objects as many languages you want to support. Don't forget that the IDs should match in each objects because they will be the key used by the Intl API to find the text for the selected language.
-
Wrap these objects within one main object. This time, the keys will be the strings you mark the supported languages and the corresponding values will be the objects created before.
const messages_hu = { 'app.title': 'Ãœdvözöllek!', 'app.description': 'Örülök, hogy újra itt vagy, {name}!', 'app.search.placeholder1': 'Ide Ãrd be a keresett kulcsszót!' 'app.search.placeholder2': 'Kedves {name}, ide Ãrd be a keresett kulcsszót!' }; const messages_en = { 'app.title': 'Welcome!', 'app.description': 'I\'m glad that you are here again, {name}!', // 'name' is a placeholder 'app.search.placeholder1': 'Type search keyword here!' 'app.search.placeholder2': 'Dear {name}, type search keyword here!' }; const messages = { 'hu': messages_hu, 'en': messages_en };
- Within this step, wrap the outermost component (this should contain all your "internationalized" components, texts, etc.) within the
<Anu.Intl.Provider />
. This component takes three props:-
The
messages
property should be the object that contains all the translations for all the languages your application supports. -
The
locale
property is practically a short string that is the preferred language (it must match with one of the keys of the outermost object passed asmessages
). -
The
defaultLocale
property is optional and will refer to the default language ifmessages[locale]
couldn't be found.// This will use the first 2 letters of the language set in browser settings (e.g.: "en", "it", "hu", ...): const locale = navigator.language.split(/[-_]/)[0]; const messages = { /* ... */ }; const LocaleContainer = () => { return ( <Anu.Intl.Provider messages={messages} locale={locale} defaultLocale="hu"> <Your_page /> </Anu.Intl.Provider> ); };
-
- Use
<Anu.Intl.FormattedMessage />
for the specific text (referred byid
property) - if used as a rendered element:- The first, required argument is the
id
property which is the key of the text in the user-defined language objects. - It can take an optional
values
property which is an object:-
The key property must match the placeholder you want to replace with your dynamic value within the text inside your language object you refer (i.e. wrapped between
{
and}
). -
The value is what should be used to replace the placeholder.
const ComponentWithFormattedMessages = () => { const name = "Anubis" return ( <Anu.Fragment> <h1> <Anu.Intl.FormattedMessage id="app.title" defaultMessage="Welcome" /> </h1> <p> <Anu.Intl.FormattedMessage id="app.description" values={{ name }} defaultMessage="Welcome, User!" /> </p> </Anu.Fragment> ); };
-
- The first, required argument is the
- Use
Anu.Intl.formatMessage()
for the specific text (referred byid
property) - if used as an attribute of an element:-
The first, required argument is the
id
property which is the key of the text in the user-defined JSON files (e.g.:messages_hu[id]
ormessages_en[id]
). -
The second argument is the
values
property which is either an object ornull
. If defined:- The key property must match the placeholder you want to replace with your dynamic value within the text inside your language object you refer (i.e. wrapped between
{
and}
). - The value is what should be used to replace the placeholder.
- The key property must match the placeholder you want to replace with your dynamic value within the text inside your language object you refer (i.e. wrapped between
-
It can also take an optional
defaultMessage
property which is used if the searched text couldn't been found. -
If the ID can not be found and no
defaultMessage
is passed, the function will return theid
value as a string.const ComponentWithFormatMessageFunctionCall = () => { return [ <input placeholder={Anu.Intl.formatMessage('app.search.placeholder1', null, 'Search...')} />, <input placeholder={Anu.Intl.formatMessage('app.search.placeholder2', { name: 'Anubis' }, 'Search...')} /> ]; };
-
- If you want to abbreviate a large number, the
Anu.Intl.abbreviateNumber()
function comes handy. - This function is also part of the INTL API so, it is able to read the language set within
<Anu.IntlProvider />
(and you must use it within<Anu.IntlProvider />
).
This currently supports only the Hungarian and the English abbreviations by default, but you can define your custom abbreviation rules as well. - The function takes two arguments:
- The first argument is the numeric
value
: the number to abbreviate.
If there is no match for the selected language and you didn't specify a customoptions
object, the system will fall back to the default (English) options. - The second, optional argument is the
options
, which is an object:-
The first member is
units
- an array of strings to be used as abbreviation units.
When not specified, it will use the default (english) abbreviation units ('K', 'M', 'B', 'T'). -
The second member is
decimalPlaces
- a number that represents the decimal places.
If not specified, the system will fall back to use two decimal places. -
The third member is
decimalSign
- a string to replace the standard (at least in english speaking countries) dot (.
) sign with the one of choice.
If not specified, the system will fall back to the default dot (.
) sign.// Usage with default options: Anu.Intl.abbreviateNumber(value); // Usage with custom options: Anu.Intl.abbreviateNumber(value, option);
Example - abbreviating a number using default (built-in) options:
Anu.Intl.abbreviateNumber(10000000000): // 10B Anu.Intl.abbreviateNumber(100000000000): // 100B Anu.Intl.abbreviateNumber(1000000000000): // 1T Anu.Intl.abbreviateNumber(-10000000): // -10M Anu.Intl.abbreviateNumber(-10000): // -10K Anu.Intl.abbreviateNumber(-10234): // -10.23K
Example - abbreviating with custom options:
const option = { units: [' E.', ' Mio.', ' Mrd.', ' T.'], // The abbreviations to use (for each 3 digits, starting with the first element) decimalPlaces: 3, // How many decimal digits to preserve decimalSign: ',' // Replace the default decimal sign (.) with a comma (,) }; Anu.Intl.abbreviateNumber(10000000000, option): // 10 Mrd. Anu.Intl.abbreviateNumber(100000000000, option): // 100 Mrd. Anu.Intl.abbreviateNumber(1000000000000, option): // 1 T. Anu.Intl.abbreviateNumber(-10000000, option): // -10 Mio. Anu.Intl.abbreviateNumber(-10000, option): // -10 E. Anu.Intl.abbreviateNumber(-10234, option): // -10,234 E.
-
- The first argument is the numeric
- With feature toggle you can decide the circumstances a component should be rendered or not.
This technique is typically used when some components should not be accessible due to lack of access rights or if the related backend logic is not implemented yet.
Other typical use-case is when you have a feature but you don't want to show it in production yet because you want to test it carefully first.
- Set up an object with its keys as the name of the allowed features with a boolean value.
- Wrap the outermost element (this should contain all the features you want to toggle) within an
<Anu.Feature.Provider />
.-
It takes a
features
property which should be the defined features list.const myFeaturesList = { myFeature: true }; const FeaturesWrapper = () => { return ( <Anu.Feature.Provider features={myFeaturesList}> <Your_page /> </<Anu.Feature.Provider> ); };
-
- Wrap your component you want to render if the desired feature is set to
true
within<Anu.Feature.Toggle />
.-
It takes a
name
(string) property which should match with the name of the feature you added to the provider component. If this property you refer with the value of thename
attribute evaluates as true, the wrapped component can be rendered. -
You can also set an optional
defaultComponent
. This will be rendered if thename
evaluates as "falsy" (i.e.: either you set it tofalse
or it wasn't even specified). IfdefaultComponent
is not set,null
will be rendered instead.const ComponentWithFeatureToggle = () => { const fallbackComponent = <div>You are not authorized to see this content...</div>; return ( <Anu.Feature.Toggle name="myFeature" defaultComponent={fallbackComponent}> <My_fancy_feature /> </Anu.Feature.Toggle> ); };
-
-
The
Anu.utils.deepEqual()
is a deep equality check utility that can check nested and complex objects if their structure and properties match (even if their references don't).const obj1 = { prop1: 'asdf', prop2: true, prop3: { prop3_1: 'asdfasdf', prop3_2: [{ itemProp1: 'item' }] } }; const obj2 = { prop1: 'asdf', prop2: true, prop3: { prop3_1: 'asdfasdf', prop3_2: [{ itemProp1: 'item' }] } }; const answer = Anu.utils.deepEqual(obj1, obj2); // true
- Asyncronous utilities are useful functionalities to handle result(s) of asyncronous queries, like XHR calls.
There is an asyncronous helper function bundled within<ANUVerzum />
JSAnu.Async.map()
- The
Anu.Async.map()
function is designed to loop through an array ofelemList
and execute aniterator
on each element asynchronously.
Afteriterator
ran on each element inelemList
, theresolveCallback
function is called which takesresults
as an argument:-
The first argument is
elemlist
: an array of elements on which we want to iterate. -
The second argument (the first callback) is the
iterator
that should be called for all elements inelemList
(argument: element ofelemList
). -
The third argument (second callback) is the
resolveCallback
that takes the list of modified elements returned byiterator
calls (argument: array of modified elements).const elemList = [ /* elem1, elem2, elem3, ... */ ]; const iterator = element => { // do something with each "element" one-by-one and then, return (modified) "element" return element; }; const resolveCallback = results => { // do something with the results (array of "element" -s) } Anu.Async.map(elemList, iterator, resolveCallback);
-