Demo entry 6334325

PushMenu

   

Submitted by anonymous on Dec 05, 2016 at 10:29
Language: JavaScript. Code size: 11.8 kB.

/**
 * @name ClsPushMenu
 * This component handles the creation and navigation of the PushMenu. It is
 * connected to the store and renders the Component.
 *
 * @param {object} item data tree
 * @param {number} animationTime time of the slide effect
 * @param {function} setPushMenuContent sets the item into the store
 * @param {function} setPushMenuActivePath sets the new activePath
 * @param {string} activePath nodePath to the current display node
 *
 * @returns {number} that number, plus one.
 *
 * @author Stefan Blazek
 * @date 2016
 * @version 0.1
**/

// IMPORT
// React Library
import React, { PropTypes } from 'react';

// function Libraries
import _ from 'lodash';
import $ from 'jquery';

// Redux
import { connect } from 'react-redux';
import { bindActionCreators } from 'multireducer';
import {
  setPushMenuContent,
  setPushMenuActivePath } from '../../../redux/actions/actionsClsPushMenu';

// included components
import ClsPushMenuView from './ClsPushMenuView';
import ClsPushMenuStripe from './ClsPushMenuStripe';

// CLASS
class ClsPushMenu extends React.Component {
  static propTypes = {
    items: PropTypes.object,
    animationTime: PropTypes.number,
    setPushMenuContent: PropTypes.func.isRequired,
    setPushMenuActivePath: PropTypes.func.isRequired,
    activePath: PropTypes.string.isRequired,
  }

  // Default Values
  static defaultProps = {
    items: {},
    animationTime: 400,
  }

// CONSTRUCTOR
  constructor(props) {
    super(props);

    this.direction = null;  // navigation direction
    this.stripeWidth = null;  // width of the folder stripes on the left

    // When server connection is working it get the items from Api
    // Tree Object which is displayed by the PushMenu
    const pushMenuContent = {
      id: '-1',
      children: [
        {
          name: 'Vorlagen',
          parentId: '-1',
          id: '1:1000',
          nodePath: '1:1000',
          type: 'node',
          children: [
            {
              name: 'Versicherung',
              parentId: '1:1000',
              id: '1:1002',
              nodePath: '1:1000|1:1002',
              type: 'node',
              children: [
                {
                  name: 'KFZ',
                  parentId: '1:1002',
                  id: '1:1004',
                  nodePath: '1:1000|1:1002|1:1004',
                  type: 'node',
                  children: [
                    {
                      name: 'PKW',
                      parentId: '1:1004',
                      id: '1:12345',
                      nodePath: '1:1000|1:1002|1:1004|1:12345',
                      type: 'item',
                    },
                  ],
                },
                {
                  name: 'Hausrat',
                  parentId: '1:1002',
                  id: '1:33584',
                  nodePath: '1:1000|1:1002|1:33584',
                  type: 'item',
                },
              ],
            },
            {
              name: 'Schadenersatz',
              parentId: '1:1000',
              id: '1:1005',
              nodePath: '1:1000|1:1005',
              type: 'node',
              children: [
                {
                  name: 'Mahnung',
                  parentId: '1:1005',
                  id: '1:12345',
                  nodePath: '1:1000|1:1005|1:23456',
                  type: 'item',
                },
              ],
            },
            {
              name: 'AGB',
              parentId: '1:1002',
              id: '1:34567',
              nodePath: '1:1000|1:34567',
              type: 'item',
            },
            {
              name: 'Service',
              parentId: '1:1000',
              id: '1:33567',
              nodePath: '1:1000|1:33567',
              type: 'item',
            },
          ],
        },
      ],
    };

    // The content object is set to the store by the setPushMenuContent
    // redux-action
    this.props.setPushMenuContent(pushMenuContent);
  }

  componentDidUpdate() {
    // after the component get new data
    this.props.compressContent();

    // open folder-node
    if (this.direction === 'right') {
      const lastStripe = $('.pushMenuStripe').last();

      if (!this.stripeWidth) {
        this.stripeWidth = lastStripe.width();
      }

      // content must be resized before the animation of the last stripe begins
      // if (overlapContent || (!overlapContent && !visible)) {
      //   this.resizeContent();
      // }

      // start animiation of last opened stripe at width of 0 to full width
      lastStripe.width(0);
      lastStripe.animate({
        width: this.stripeWidth,
      }, this.props.animationTime);
    }

    // Reset the direction to avoid unnecessary animations when clicking
    // overlap icon after a node click
    this.direction = null;
  }



  navigate = (newPath, direction) => {
    /**
    * Sets the active Folder and handles the animation
    * @param {string} newPath nodePath of the selected Item
    * @param {string} direction animation direction of the pushmenu
    *
    * @return {void}
    **/

    this.direction = direction;

    // close folder functionality must be activated here, to see the animation
    // before re-rendering the component
    if (direction === 'left') {
      // for back navigation or navigation to a higher level
      const { visible, overlapContent, activePath, animationTime } = this.props;

      // calculating the level difference between the opened folder and the
      // folder that has been clicked by the user, to close and animate the
      // correct number of stripes

      let levelNew = (newPath.match(/\|/g) || []).length;
      if (newPath) {
        levelNew++;
      }

      const levelOld = (activePath.match(/\|/g) || []).length + 1;
      const levelDiff = levelOld - levelNew;
      const stripes = $('.pushMenuStripe');

      _.times(levelDiff, (i) => {
        const stripe = stripes[stripes.length - i - 1];

        if (!this.stripeWidth) {
          this.stripeWidth = stripe.width();
        }

        if (!overlapContent && visible && i === 0) {
          this.props.resizeContent($('#pushMenu').width()
          + ((stripes.length - levelDiff) * this.stripeWidth));
        }

        //  Animation when number of stripes changes during navigation
        $(stripe).animate({
          width: 0,
        }, animationTime, () => {
          // last closing stripe? it´s time to re-render the component with the
          // new path
          if (i === levelDiff - 1) {
            this.props.setPushMenuActivePath(newPath);
          }
        });
      });
    } else {
      // open folder => animation is activated in componentDidUpdate function
      this.props.setPushMenuActivePath(newPath);
    }
  }

  createStack() {
    /**
    * creates a stack of stapled stripes dependend on the active folder and its
    * deepth in the tree
    * @param {object} activePath and items from the redux store
    * @return {array} stack of stripes
    **/

    const { activePath, items } = this.props;
    let stripeNavigationPath = '';

    const stack = [];
    // checking if the activePath has a value
    if (activePath) {
      let actItem = items;

      // splits the path string to its sub elements
      _.each(activePath.split('|'), (stackstep, key) => {
        // each path depth creates a stripe
        stack.push(<ClsPushMenuStripe
          key={key}
          navigate={this.navigate}
          moveStuffIntoDifferentFolder={this.moveStuffIntoDifferentFolder}
          nodePath={stripeNavigationPath}
          item={actItem}
        />);

        // searchs  ASDAKJSDHKLJASDHLKJASDLKJAHSD
        actItem = _.find(actItem.children, child => child.id === stackstep);
        stripeNavigationPath += (key !== 0 ? '|' : '') + stackstep;
      });
    }

    return stack;
  }

  moveStuffIntoDifferentFolder = (item, targetFolder) => {
    // delete old item in content
    let newPushMenuContent
    = this.removeItemFromFolder(this.props.items, item.id);

    // update nodePath and parentId of the item
    const newItem = item;

    newItem.nodePath = '';
    if (targetFolder.nodePath) {
      newItem.nodePath = `${targetFolder.nodePath}|`;
    }
    newItem.nodePath += item.id;
    newItem.parentId = targetFolder.id;

    // insert item into new folder
    newPushMenuContent
    = this.insertItemIntoFolder(newPushMenuContent, newItem, targetFolder.id);
    this.props.setPushMenuContent(newPushMenuContent);
  }

// TOBI Fragen
  removeItemFromFolder(data, itemId) {
    let destObj;
    let newValue;

    if (_.isPlainObject(data)) {
      destObj = {};
      _.forOwn(data, (value, key) => {
        newValue = this.removeItemFromFolder(value, itemId);
        if (data.id !== itemId && !_.isEmpty(newValue)) {
          destObj[key] = newValue;
        }
      });
    } else if (_.isArray(data)) {
      destObj = [];
      _.each(data, (value, key) => {
        newValue = this.removeItemFromFolder(data[key], itemId);
        if (data.id !== itemId && !_.isEmpty(newValue)) {
          destObj.push(newValue);
        }
      });
    } else {
      return data;
    }

    return destObj;
  }

  insertItemIntoFolder(sourceObj, itemToInsert, targetFolderId) {
    let destObj;
    let newValue;

    if (sourceObj.id === targetFolderId) {
      if (!sourceObj.children) {
        sourceObj.children = [];
      }
      sourceObj.children.push(itemToInsert);
    }

    if (_.isPlainObject(sourceObj)) {
      destObj = {};
      _.forOwn(sourceObj, (value, key) => {
        newValue =
        this.insertItemIntoFolder(value, itemToInsert, targetFolderId);
        destObj[key] = newValue;
      });
    } else if (_.isArray(sourceObj)) {
      destObj = [];
      _.each(sourceObj, (value, key) => {
        newValue
        = this.insertItemIntoFolder(sourceObj[key], itemToInsert, targetFolderId);
        destObj.push(newValue);
      });
    } else {
      return sourceObj;
    }

    return destObj;
  }

  renderItem(index, key) {
    // defines the
    return <div key={key}>{this.data[index].name}</div>;
  }

  render() {
    const { visible, overlapContent, items, activePath } = this.props;

    let actMenuItem = null;
    let backLinkPath = null;

    // store is being set after api call => first rendering without any items
    if (_.size(items) > 0) {
      actMenuItem = items;

      if (activePath) {
        const splittedPath = activePath.split('|');
        _.each(splittedPath, step => {
          actMenuItem = _.find(actMenuItem.children, (o) => o.id === step);
        });

        backLinkPath = _.slice(splittedPath, 0, splittedPath.length - 1).join('|');
      }
    }

    return (
      <div>
        {actMenuItem ?
          <ClsPushMenuView
            items={actMenuItem}
            navigate={this.navigate}
            moveStuffIntoDifferentFolder={this.moveStuffIntoDifferentFolder}
            backLinkPath={backLinkPath}
          /> : null}
        {this.createStack()}
      </div>
    );
  }
}

// -----------------------------------------------------------------------------
const mapStateToProps = (state) => ({
  items: state.pushMenu.items,
  activePath: state.pushMenu.activePath,
});

const mapDispatchToProps = (dispatch) =>
  bindActionCreators({ setPushMenuContent, setPushMenuActivePath }, dispatch);

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ClsPushMenu);

This snippet took 0.02 seconds to highlight.

Back to the Entry List or Home.

Delete this entry (admin only).