将单击导航转换为单击/鼠标悬停

时间:2017-10-12 19:08:36

标签: javascript jquery bigcommerce

我正在使用以下电子商务框架:

商店演示:https://cornerstone-light-demo.mybigcommerce.com/

Git:https://github.com/bigcommerce/cornerstone

我正在尝试将导航转换为纯粹基于点击的形式,就像在上面的商店演示中一样,使用相同框架的商店中看到的点击/悬停组合:rockbottomgolf.com

我已经隔离了以下js文件中所需的更改:

import $ from 'jquery';
import _ from 'lodash';
import mediaQueryListFactory from './media-query-list';

const PLUGIN_KEY = 'collapsible';

export const CollapsibleEvents = {
    open: 'open.collapsible',
    close: 'close.collapsible',
    toggle: 'toggle.collapsible',
    click: 'click.collapsible',
};

const CollapsibleState = {
    closed: 'closed',
    open: 'open',
};

function prependHash(id) {
    if (id && id.indexOf('#') === 0) {
        return id;
    }

    return `#${id}`;
}

function optionsFromData($element) {
    return {
        disabledBreakpoint: $element.data(`${PLUGIN_KEY}-disabled-breakpoint`),
        disabledState: $element.data(`${PLUGIN_KEY}-disabled-state`),
        enabledState: $element.data(`${PLUGIN_KEY}-enabled-state`),
        openClassName: $element.data(`${PLUGIN_KEY}-open-class-name`),
    };
}

/**
 * Collapse/Expand toggle
 */
export class Collapsible {
    /**
     * @param {jQuery} $toggle - Trigger button
     * @param {jQuery} $target - Content to collapse / expand
     * @param {Object} [options] - Configurable options
     * @param {Object} [options.$context]
     * @param {Object} [options.disabledBreakpoint]
     * @param {Object} [options.disabledState]
     * @param {Object} [options.enabledState]
     * @param {Object} [options.openClassName]
     * @example
     *
     * <button id="#more">Collapse</button>
     * <div id="content">...</div>
     *
     * new Collapsible($('#more'), $('#content'));
     */
    constructor($toggle, $target, {
        disabledBreakpoint,
        disabledState,
        enabledState,
        openClassName = 'is-open',
    } = {}) {
        this.$toggle = $toggle;
        this.$target = $target;
        this.targetId = $target.attr('id');
        this.openClassName = openClassName;
        this.disabledState = disabledState;
        this.enabledState = enabledState;

        if (disabledBreakpoint) {
            this.disabledMediaQueryList = mediaQueryListFactory(disabledBreakpoint);
        }

        if (this.disabledMediaQueryList) {
            this.disabled = this.disabledMediaQueryList.matches;
        } else {
            this.disabled = false;
        }

        // Auto-bind
        this.onClicked = this.onClicked.bind(this);
        this.onDisabledMediaQueryListMatch = this.onDisabledMediaQueryListMatch.bind(this);

        // Assign DOM attributes
        this.$target.attr('aria-hidden', this.isCollapsed);
        this.$toggle
            .attr('aria-controls', $target.attr('id'))
            .attr('aria-expanded', this.isOpen);

        // Listen
        this.bindEvents();
    }

    get isCollapsed() {
        return !this.$target.hasClass(this.openClassName) || this.$target.is(':hidden');
    }

    get isOpen() {
        return !this.isCollapsed;
    }

    set disabled(disabled) {
        this._disabled = disabled;

        if (disabled) {
            this.toggleByState(this.disabledState);
        } else {
            this.toggleByState(this.enabledState);
        }
    }

    get disabled() {
        return this._disabled;
    }

    open({ notify = true } = {}) {
        this.$toggle
            .addClass(this.openClassName)
            .attr('aria-expanded', true);

        this.$target
            .addClass(this.openClassName)
            .attr('aria-hidden', false);

        if (notify) {
            this.$toggle.trigger(CollapsibleEvents.open, [this]);
            this.$toggle.trigger(CollapsibleEvents.toggle, [this]);
        }
    }

    close({ notify = true } = {}) {
        this.$toggle
            .removeClass(this.openClassName)
            .attr('aria-expanded', false);

        this.$target
            .removeClass(this.openClassName)
            .attr('aria-hidden', true);

        if (notify) {
            this.$toggle.trigger(CollapsibleEvents.close, [this]);
            this.$toggle.trigger(CollapsibleEvents.toggle, [this]);
        }
    }

    toggle() {
        if (this.isCollapsed) {
            this.open();
        } else {
            this.close();
        }
    }

    toggleByState(state, ...args) {
        switch (state) {
        case CollapsibleState.open:
            return this.open.apply(this, args);

        case CollapsibleState.closed:
            return this.close.apply(this, args);

        default:
            return undefined;
        }
    }

    hasCollapsible(collapsibleInstance) {
        return $.contains(this.$target.get(0), collapsibleInstance.$target.get(0));
    }

    bindEvents() {
        this.$toggle.on(CollapsibleEvents.click, this.onClicked);

        if (this.disabledMediaQueryList && this.disabledMediaQueryList.addListener) {
            this.disabledMediaQueryList.addListener(this.onDisabledMediaQueryListMatch);
        }
    }

    unbindEvents() {
        this.$toggle.off(CollapsibleEvents.click, this.onClicked);

        if (this.disabledMediaQueryList && this.disabledMediaQueryList.removeListener) {
            this.disabledMediaQueryList.removeListener(this.onDisabledMediaQueryListMatch);
        }
    }

    onClicked(event) {
        if (this.disabled) {
            return;
        }

        event.preventDefault();

        this.toggle();
    }

    onDisabledMediaQueryListMatch(media) {
        this.disabled = media.matches;
    }
}

/**
 * Convenience method for constructing Collapsible instance
 *
 * @param {string} [selector]
 * @param {Object} [options]
 * @param {Object} [options.$context]
 * @param {Object} [options.disabledBreakpoint]
 * @param {Object} [options.disabledState]
 * @param {Object} [options.enabledState]
 * @param {Object} [options.openClassName]
 * @return {Array} array of Collapsible instances
 *
 * @example
 * <a href="#content" data-collapsible>Collapse</a>
 * <div id="content">...</div>
 *
 * collapsibleFactory();
 */
export default function collapsibleFactory(selector = `[data-${PLUGIN_KEY}]`, overrideOptions = {}) {
    const $collapsibles = $(selector, overrideOptions.$context);

    return $collapsibles.map((index, element) => {
        const $toggle = $(element);
        const instanceKey = `${PLUGIN_KEY}-instance`;
        const cachedCollapsible = $toggle.data(instanceKey);

        if (cachedCollapsible instanceof Collapsible) {
            return cachedCollapsible;
        }

        const targetId = prependHash($toggle.data(PLUGIN_KEY) ||
            $toggle.data(`${PLUGIN_KEY}-target`) ||
            $toggle.attr('href'));
        const options = _.extend(optionsFromData($toggle), overrideOptions);
        const collapsible = new Collapsible($toggle, $(targetId), options);

        $toggle.data(instanceKey, collapsible);

        return collapsible;
    }).toArray();
}

import $ from 'jquery';
import collapsibleFactory from '../common/collapsible';
import collapsibleGroupFactory from '../common/collapsible-group';

const PLUGIN_KEY = 'menu';

/*
 * Manage the behaviour of a menu
 * @param {jQuery} $menu
 */
class Menu {
    constructor($menu) {
        this.$menu = $menu;
        this.$body = $('body');
        this.hasMaxMenuDisplayDepth = this.$body.find('.navPages-list').hasClass('navPages-list-depth-max');

        // Init collapsible
        this.collapsibles = collapsibleFactory('[data-collapsible]', { $context: this.$menu });
        this.collapsibleGroups = collapsibleGroupFactory($menu);

        // Auto-bind
        this.onMenuClick = this.onMenuClick.bind(this);
        this.onDocumentClick = this.onDocumentClick.bind(this);

        // Listen
        this.bindEvents();
    }

    collapseAll() {
        this.collapsibles.forEach(collapsible => collapsible.close());
        this.collapsibleGroups.forEach(group => group.close());
    }

    collapseNeighbors($neighbors) {
        const $collapsibles = collapsibleFactory('[data-collapsible]', { $context: $neighbors });

        $collapsibles.forEach($collapsible => $collapsible.close());
    }

    bindEvents() {
        this.$menu.on('click', this.onMenuClick);
        this.$body.on('click', this.onDocumentClick);
    }

    unbindEvents() {
        this.$menu.off('click', this.onMenuClick);
        this.$body.off('click', this.onDocumentClick);
    }

    onMenuClick(event) {
        event.stopPropagation();

        if (this.hasMaxMenuDisplayDepth) {
            const $neighbors = $(event.target).parent().siblings();

            this.collapseNeighbors($neighbors);
        }
    }

    onDocumentClick() {
        this.collapseAll();
    }
}

/*
 * Create a new Menu instance
 * @param {string} [selector]
 * @return {Menu}
 */
export default function menuFactory(selector = `[data-${PLUGIN_KEY}]`) {
    const $menu = $(selector).eq(0);
    const instanceKey = `${PLUGIN_KEY}-instance`;
    const cachedMenu = $menu.data(instanceKey);

    if (cachedMenu instanceof Menu) {
        return cachedMenu;
    }

    const menu = new Menu($menu);

    $menu.data(instanceKey, menu);

    return menu;
}

但我似乎无法弄清楚它们是如何相互协作的,特别是绑定/取消绑定,这似乎是我需要编辑的内容。

0 个答案:

没有答案