Skip to content

<ANUVerzum /> is a "React-like" JS library implementation, containing the view engine, a "Redux-like" state management API, routing, middleware support, Intl API and many more.

Notifications You must be signed in to change notification settings

Anubis-programmer/ANUVerzum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 

Repository files navigation

<ANUVerzum /> JS FRAMEWORK USAGE:


@author: Anubis

@license: MIT


Framework Usage:


Utilities:


Asyncronous Helpers:



<ANUVerzum /> JS USAGE


Creating components and rendering elements

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 the xmlns attribute.

  • Adding style definition inside <svgStyle> should be done as a string template as children, 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

  • Function components are functions which can receive props and must always return either an HTML element, an inline-SVG element, a Component or NULL.

  • 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>
        );
    };

Class-based components

  • Always must extend Anu.Component, always must implement render() 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 the constructor(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 actual state and props as arguments (useful if state and props were updated asynchronously) and returns the new state value.

      type SetStateCallbackType = (prevState: Object, props: Object) => Object;
      setState(state: Object | SetStateCallbackType): void => { /* ... */ };
  • NEVER use setState() in constructor() or componentWillUnmount()!

  • The setState() can be called in componentDidMount() and componentDidUpdate(), but only in condition - as you would do in React

    class 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}
                />
            );
        }
    }

Rendering props

  • 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>;

Wrapper components and related sub-components

  • 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>

Rendering array

  • 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>
        );
    };

Avoiding unnecessary wrapper elements

  • 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>
        );
    };

The refs

  • 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>
            );
        }
    }

Rendering your application

  • Select an existing HTML element (typically having id as "root" or "app") and use the Anu.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)
      );

Routing - The History API

Linking and routing

  • 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 a to prop. When clicking on it, its to prop will be matched against the path prop of <Anu.History.Route />.
  • Use the <Anu.History.Route /> component that takes a path argument and if the link you clicked matches the path, it will render the attached component:
    • The <Anu.History.Route /> component has a path prop to match against the URL. If it has an exact prop, it doesn't allow partial matching.

    • The <Anu.History.Route /> can also have a component (must be a component) or a render 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>
          );
      };

Redirecting - The <Anu.History.Redirect /> component

  • If the user needs to be redirected (e.g. if not logged in), use the <Anu.History.Redirect /> component:
    • The <Anu.History.Redirect /> takes a to (URL) and an optional push (boolean) argument.
      This will tell the System to render the component which can be found on the to URL (which is basically an <Anu.History.Route /> which will render the corresponding component). If push 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 />;
                      }
                  }}
              />
          );
      };

Redirecting from within the code - The Anu.History.goTo() method

  • 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 to true, 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 in if 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);

Calling the server asynchronously - The Server API

The Anu.ServerAPI is basically built on top of Promise and currently has 5 methods get(), post(), put(), delete() and file().

The GET and DELETE HTTP methods

  • The Anu.ServerAPI.get() and Anu.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 an id 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 optional params (object) argument and return a Promise 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 POST and PUT HTTP methods

  • The Anu.ServerAPI.post() and Anu.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 a data (object) argument and returns a Promise 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 FILE HTTP method

  • 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), a file (one File object or an ARRAY of File objects) argument and an optional data (object) and returns a Promise object:

    const data = { /* ... */ };
    Anu
        .ServerAPI
        .file('/app/my-server-url/', File, data)
        .then(({ response }) => { /* ... */ })
        .catch(({ status }) => { /* ... */ });

Tracking users - The Anulytics API

<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 /> and Anu.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 use Anu.Anulytics.trackEvent() event tracker functionality).

Wrapping up your front-end project within the <Anu.Anulytics.Provider /> component

  • 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 the userData 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 />, the Anu.History.goTo() was called or the user got redirected via <Anu.History.Redirect />).
      • 'userAction' is set when Anu.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 and properties:

      • 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!
      • 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 a Property type. The properties are typically set when Anu.Anulytics.trackEvent() has been used or an action was dispatched.
        The properties 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, like id, name, url and value:

      • 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, like keyup) 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 and nextState:

      • 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.

Tracking events on elements using Anu.Anulytics.trackEvent(event, props)

  • 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.

Creating and accessing the context out of the "normal props flow" - The Context API

Creating the context

  • To create a context provider and context consumer elements with default context, call the Anu.createContext(). It takes a context argument which can be reached later as context.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"

Usage of the context provider and its consumer(s)

  • Context props defined on <ThemedContext.ContextProvider /> can be accessed from within the function child of the <ThemedContext.ContextConsumer /> as context.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) or null.

  • You can have as many elements between the context provider and consumer(s), as you want.
    No need to pass the context 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 the context (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

Dispatching actions

  • 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 and getState

      • Here. you can do additional things (like getting out a value from the state using getState() or dispatching an action using dispatch()) 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 } }));
        };

Handling actions with reducers

  • 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;
            }
        }
    };

Memoizing (global) state conversions

  • 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 in mapStateToProps() - 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);

Combining reducers

  • 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
      };

Creating the store

  • The store object stores the global state object (that can be reached using the store.getState() methiod) and it also has the store.dispatch(), store.subscribe() and store.unsubscribe() methods. You will likely use the store.getState() and store.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 the Anu.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 be undefined. 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
              )
          );

Connecting components to the global state - The Connector API

This API is designed to connect your global store with elements within the presentation layer.

Connect to the store

  • 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>
            );
        }
    };

Create container component

  • 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() and mapDispatchToProps(), 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() and mapDispatchToProps()!
  • Define maximum two functions (they are typically called mapStateToProps() and mapDispatchToProps(); if either one is not defined, pass null 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 optional ownProps 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.
    • 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 optional ownProps 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);

Supporting multiple languages - The Intl API

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 the id property. You can also use Anu.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.

Creating the supported language objects

  • 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
    };

Adding the language objects to the Intl provider

  • 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 as messages).

    • The defaultLocale property is optional and will refer to the default language if messages[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>
          );
      };

Formatting component texts

  • Use <Anu.Intl.FormattedMessage /> for the specific text (referred by id 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>
            );
        };

Formatting attribute texts

  • Use Anu.Intl.formatMessage() for the specific text (referred by id 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] or messages_en[id]).

    • The second argument is the values property which is either an object or null. 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.
    • 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 the id 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...')} />
          ];
      };

Abbreviating numbers

  • 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 custom options 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.

Switching features on / off - The Feature API

  • 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.

Setting the features list

  • 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>
          );
      };

Toggling the features

  • 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 the name attribute evaluates as true, the wrapped component can be rendered.

    • You can also set an optional defaultComponent. This will be rendered if the name evaluates as "falsy" (i.e.: either you set it to false or it wasn't even specified). If defaultComponent 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>
          );
      };


<ANUVerzum /> JS UTILITIES:


Deep equality check for objects using Anu.utils.deepEqual()

  • 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


<ANUVerzum /> JS ASYNCRONOUS HELPERS:


Asynchronous mapping through multiple elements using Anu.Async.map()

  • Asyncronous utilities are useful functionalities to handle result(s) of asyncronous queries, like XHR calls.
    There is an asyncronous helper function bundled within <ANUVerzum /> JS Anu.Async.map()
  • The Anu.Async.map() function is designed to loop through an array of elemList and execute an iterator on each element asynchronously.
    After iterator ran on each element in elemList, the resolveCallback function is called which takes results 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 in elemList (argument: element of elemList).

    • The third argument (second callback) is the resolveCallback that takes the list of modified elements returned by iterator 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);


About

<ANUVerzum /> is a "React-like" JS library implementation, containing the view engine, a "Redux-like" state management API, routing, middleware support, Intl API and many more.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published