elementor / elementor

The most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.
https://elementor.com/
GNU General Public License v3.0
6.59k stars 1.42k forks source link

Isotope NOT working after control refresh #6756

Closed nnnswordfish closed 5 years ago

nnnswordfish commented 5 years ago

I've created a portfolio grid widget with isotope. It works fine on Frontend and in an Editor as well, but once I tweak an option which refreshes the widget, it stops working. Any ideas?

P.S. I'm a PRO user and support redirected me here.

nnnswordfish commented 5 years ago

I'm using 'elementor/frontend/before_register_scripts' to register Isotope library and addAction( 'frontend/element_ready/' + widget, callback ); js hook to run isotope after the widget is initialized.

bainternet commented 5 years ago

@nnnswordfish

We can't really tell without seeing the widget PHP code and JS handler.

nnnswordfish commented 5 years ago

Thanks for the prompt reply!

The script is registered like this:

public function register_scripts() {
    $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';

    wp_register_script(
        'isotope',
        WPR_ADDONS_URL . 'assets/lib/isotope/isotope' . $suffix . '.js',
        [
            'jquery',
        ],
        '3.0.6',
        true
    );
}
add_action( 'elementor/frontend/before_register_scripts', [ $this, 'register_scripts' ], 998 );

Then, in the widget code:

public function get_script_depends() { return [ 'isotope' ]; }

This way I enqueue script to handle all widgets:

public function enqueue_scripts() {
    $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';

    wp_enqueue_script(
        'wpr-addons-js',
        WPR_ADDONS_URL . 'assets/js/frontend' . $suffix . '.js',
        [
            'jquery',
        ],
        Plugin::instance()->get_version(),
        true
    );
}
add_action( 'elementor/frontend/before_enqueue_scripts', [ $this, 'enqueue_scripts' ], 998 );

And the JS inside this file looks like this:

( function( $, elementor ) {

    "use strict";

    var WprElements = {

        init: function() {

            var widgets = {
                'wpr-grid.default' : WprElements.widgetGrid,
            };

            $.each( widgets, function( widget, callback ) {
                window.elementorFrontend.hooks.addAction( 'frontend/element_ready/' + widget, callback );
            });

        },

        widgetNavMenu: function( $s

        widgetGrid: function( $scope ) {

            var bodyWidth = $( 'body' ).width(),
                viewport = window.innerWidth,
                grid = $scope.find( '.wpr-grid' ),
                item = grid.find( '.grid-item' ),
                columns = 3,
                gutter = 10,
                contWidth = grid.width() + gutter - 0.3;

            item.outerWidth( Math.floor( contWidth / columns - gutter ) );

            grid.isotope({
                itemSelector: '.grid-item',
                layoutMode: 'masonry',
                masonry: {
                    comlumnWidth: contWidth / columns,
                    gutter: gutter
                }
            });

        }, // End widgetNavMenu

    } // End WprElements

    $( window ).on( 'elementor/frontend/init', WprElements.init );

}( jQuery, window.elementorFrontend ) );

The grid itself is a standard WordPress loop:

<section class="wpr-grid">
<article class="grid-item"> ....

Hope now it makes sense. Please note: It works fine until I tweak any option which refreshes the widget in an editor.

crazypsycho commented 5 years ago

I think you just need to add element_ready hook.

$(window).on('elementor/frontend/init', function () {
  WprElements.init();
  elementorFrontend.hooks.addAction('frontend/element_ready/YourWidget.default',  WprElements.init );
} 
nnnswordfish commented 5 years ago

I think you just need to add element_ready hook.

$(window).on('elementor/frontend/init', function () {
  WprElements.init();
  elementorFrontend.hooks.addAction('frontend/element_ready/YourWidget.default',  WprElements.init );
} 

Thanks but I already have that hook, please read the above code carefully.

crazypsycho commented 5 years ago

Sorry, didnt seen this line. You could try to rename your widget from wpr-grid to wpr_grid or wprGrid. A year ago i have had some issues with hyphens in widget-names, so i use always underscores.

Or check in widgetGrid() if $scope exists.

nnnswordfish commented 5 years ago

Thanks for the reply, unfortunately, that does not help. Yes, $scope exits as I told you it works on load but it is being destroyed after any control refreshes this widget.

nnnswordfish commented 5 years ago

@bainternet, Still looking for an answer...

crazypsycho commented 5 years ago

Is WprElements.widgetGrid() called only onload, or after any control refresh? It should be called after any refresh.

If it is called like it should, than possibly an other plugin destroy your isotope. You could try a setTimeout as a workaround.

nnnswordfish commented 5 years ago

@crazypsycho Thanks for the reply!

I've already tried timeouts with different combinations but unfortunately, it does not make any sense. BTW how could I call it after any control refresh?

crazypsycho commented 5 years ago

Normally the frontend/element_ready/ hook triggers automatically always when the widget is refreshed.

But in the fist line of your js-code is ( function( $, elementor ) { You could try to change it to ( function( $, elementorFront ) {

cause Elementor set itself the variable elementor. This could possibly be a problem.

Other idea is, that elementor can´t find WprElements.widgetGrid. You could move widgetGrid out of WprElements and define window.widgetGrid.

nnnswordfish commented 5 years ago

@crazypsycho Thanks again, appreciate your effort!

I've tried everything but unfortunately, even that does not make any sense.

crazypsycho commented 5 years ago

Than i´m sorry, but i have currently no ideas anymore. Your code looks good and should work. Maybe somebody will see something we don´t see.

nnnswordfish commented 5 years ago

@crazypsycho

Possibly I've found the issue source. After refresh isotope can't find an itemSelector: '.grid-item', because I can't pass an object, isotope only accepts a string parameter. I need to somehow pass $scope.find( '.wpr-grid .grid-item' ) and it will probably work. Any ideas?

nnnswordfish commented 5 years ago

Now I can confirm that the only solution is to pass an object selector instead of the string to get it working.

P.S. I've changed a piece of code in iSotope plugin file and managed to get it working but It's not a correct way to do it.

crazypsycho commented 5 years ago

Oh yes it seems to be an isotope-issue. Isotope seems to have a problem if its an iframe (like in elementor) There are already issues reported about this: https://github.com/metafizzy/isotope/issues/1456 https://github.com/metafizzy/flickity/issues/861

But i dont know if they will fix this.

nnnswordfish commented 5 years ago

@crazypsycho Thanks mate, you are very welcome!

bainternet commented 5 years ago

Glad you guys figured it out.

desandro commented 5 years ago

Hi. Author of Isotope and Flickity here. I've received a PR that may fix this issue. See metafizzy/flickity#900

The problem is that I check for valid HTML elements like so:

function getIsElement( elem ) {
  return elem instanceof HTMLElement
}

But, within Elementor, HTMLElement doesn't exist. The proposed fix adds several element-like checks:

function getIsElement( elem ) {
  if ( typeof HTMLElement == 'object' ) {
    return elem instanceof HTMLElement;
  }
  return elem && typeof elem == 'object' && elem.nodeType == 1 &&
    typeof elem.nodeName == 'string';
}

I'm curious if there's a more elegant solution for this Elementor-specific problem.

bainternet commented 5 years ago

@desandro

Not sure why you say that HTMLElement doesn't exist in elementor, HTMLElement is a native browser function which identify HTML elements and Elementor works with and around HTML elements.

as for your question, I would do something like this:

function getIsElement( elem ) {
    //Using W3 DOM2 (works for FF, Opera and Chrome)
    if ( 'HTMLElement' in window ) {
        return ( elem && elem instanceof HTMLElement );
    }
    // SVG Element support
    if ( 'Element' in window ) {
        return ( elem && elem instanceof Element );
    }

    return !! ( elem && 'object' === typeof elem && 1 === elem.nodeType && 'string' === typeof elem.nodeName );
}

thanks

desandro commented 5 years ago

Thanks for that. Sorry, I don't a test case on hand to reproduce this issue, so I'm not sure where the disconnect is. From my side, @wayheming is proposing not using HTMLElement, which solves their problem using Flickity with Elementor, related to iframe usage.

wayheming commented 5 years ago

Hello @desandro and @bainternet thank you for your time. I tried to recreate the problem in as much detail as possible.

I created a plugin to recreate the problem, here are the source code for the plugin. https://drive.google.com/file/d/12h7BGh1sOMAw86RrxV6vLCZnUyvh5FsJ/view?usp=sharing

This is the page where the problem is recreated. http://mamkinhacker.fun/wordpress/elementor-5

Here is the admin access http://mamkinhacker.fun/wordpress/wp-admin admin admin

I created a test element Carousel http://prntscr.com/mk3ijv

Here are the results of a simple check. http://prntscr.com/mk3j16

http://prntscr.com/mk3jgx http://prntscr.com/mk3jmb http://prntscr.com/mk3k2q

If you need additional materials or anything else let me know. Thanks.

wayheming commented 5 years ago

My sites were infected with a virus after I posted the accesses here which was stupid on my part 😥 if someone will consider this problem contact me and I will create a test site again 🤣

steve231293 commented 5 years ago

Hello everybody, I have faced the same problem. Do you have fixed it? Please help me, thanks so much!

steve231293 commented 5 years ago

Hi @bainternet, do you have any solutions for the problem, please help me.