Akar
Akar

Reputation: 5405

Cannot read property 'dropdown' of undefined

I'm trying to transform my code into a more plugin type of code, so everything will be separated, in case I change class names in the future.

For some reason, in my code, I get Cannot read property 'dropdown' of undefined.

My guess is, the function Navigation.bindEvents() runs before I set the config, so It can't find it... But I don't know how to solve it.

Here's my Navigation.js file:

let Navigation = {
    config: {},

    init(config) {
        this.config = config;
        this.bindEvents();
    },
    bindEvents() {
        $(this.config.trigger).on('click', this.toggleNavigation);
        $(document).on('click', this.hideAllDropdowns);
    },

    toggleNavigation(event) {
        // Store the current visible state
        var visible = $(this).siblings(this.config.trigger).hasClass('visible');


        // Hide all the drop downs
        this.hideAllDropdowns();

        // If the stored state is visible, hide it... Vice-versa.
        $(this).siblings(this.config.content).toggleClass('visible', !visible);

        event.preventDefault();
        event.stopPropagation();
    },

    hideAllDropdowns() {
        $(this.config.dropdown + ' ' + this.config.content).removeClass('visible');    
    }
}

export default Navigation;

And here's my app.js file which I run all the init functions.

window.$ = window.jQuery = require('jquery');

import Navigation from './layout/navigation.js';


Navigation.init({
    dropdown: '.dropdown',
    trigger: '.dropdown-trigger',
    content: '.dropdown-content'
});

Upvotes: 0

Views: 2389

Answers (3)

Badacadabra
Badacadabra

Reputation: 8497

The behavior of this is one of the hardest things to understand in JavaScript. Here this is obviously dynamic, which means that its value depends on where your method has been called...

let module = {
  config() {
    console.log(`config(): 'this' is 'module' ---> ${Object.is(this, module)}`);
    console.log(`config(): 'this' is 'document' ---> ${Object.is(this, document)}`);
  },
  init() {
    console.log(`init(): 'this' is 'module' ---> ${Object.is(this, module)}`);
    console.log(`init(): 'this' is 'document' ---> ${Object.is(this, document)}`);
    
    module.config();
  }
};

$(document).ready(module.init);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Upvotes: 1

taile
taile

Reputation: 2776

I guess you got problem with the scope $(document).on('click', this.hideAllDropdowns);

Let's try

    bindEvents() {
        $(this.config.trigger).on('click', this.toggleNavigation);
        $(document).on('click', this.hideAllDropdowns.bind(this));
    },

UPDATE:

    bindEvents() {
        $(this.config.trigger).bind('click', {self:this}, this.toggleNavigation);
        $(document).on('click', this.hideAllDropdowns.bind(this));
    },

And replace all this.config by event.data.self inside toggleNavigation function

Upvotes: 2

Ivo
Ivo

Reputation: 555

this in the context of toggleNavigation refers to the clicked element.

That is why you can do $(this).siblings(...) to get the sibling elements.

You need to have a reference to the Navigation object. Perhaps you can use the on syntax that allows you to pass extra data $(this.config.trigger).on('click', this, this.toggleNavigation);

Then rewrite the handler

toggleNavigation(event) {
    //get the navigation reference
    var nav = event.data;
    // Store the current visible state
    var visible = $(this).siblings(nav.config.trigger).hasClass('visible');


    // Hide all the drop downs
    nav.hideAllDropdowns();

    // If the stored state is visible, hide it... Vice-versa.
    $(this).siblings(nav.config.content).toggleClass('visible', !visible);

    event.preventDefault();
    event.stopPropagation();
},

Upvotes: 1

Related Questions