Material-design-lite: Dynamic generated switches not working

Created on 16 Apr 2016  路  11Comments  路  Source: google/material-design-lite

I tried so hard to make dynamic generated switches working with no luck. I tried to reproduce what MDL does on the "document on load" but the generated elements are still broken. They render on the page but they don't animate.
Not sure if this is a real MDL bug but, as I said, I cannot find a way to make this component work.
Here a Codepen that explain the problem.
http://codepen.io/andreasonny83/pen/xVWjVx?editors=1010

All 11 comments

Hello Andreas,

I tweaked your codepen a little bit by changing the instruction componentHandler.upgradeDom(); to componentHandler.upgradeElement(tmpl); which fixed the animation problem.
Here is the resulting codepen http://codepen.io/ilyes4j/pen/JXvmvJ

Hope this helps.

That's brilliant @ilyes4j , thank you so much. But, shouldn't componentHandler.upgradeDom() upgrade and "fix" all the components in the DOM? I thought that componentHandler.upgradeElement(tmpl) waas called inside componentHandler.upgradeDom() that should go thru all the elements and update all of them? Am I missing something here in the logic?

@andreasonny83 There is a fundamental logic error in your code.

But before I start, in case you've changed some part of the code, let me describe what I'm seeing so we are on the same page: a is working fine but the rest is not.

If that's the same as what you are seeing, here goes the reason:

TL;DR: You are cloning the upgraded DOM.

Longer version:

MDL has a is-upgraded flag set on upgraded components, and will not upgrade them again if the flag is present. So you should never clone a upgraded component as you will lose all the upgraded logic and never be able to upgrade it again.

At the time update('a'); gets executed, document.load is not fired yet, so MDL hasn't done the first pass of upgrading. That's why a is working fine after calling componentHandler.upgradeDom();. But at the mean time, your source of cloning, #list-element-template, also gets upgraded. That's why nothing will work afterwards.

The reason why componentHandler.upgradeElement(tmpl); helps in this case: it only upgrades the specified component, thus does not "pollute" your source of cloning. But even with that you still have another issue: whatever you clone after document.load will not work; the full document upgrade on document.load will pollute your source of cloning.

Solution

Just don't clone from DOM; it's too error prone. Build those dynamic components with JavaScript and use componentHandler.upgradeElement(tmpl); only to prevent unwanted side-effects.

That's a great explanation, thanks you so much

@andreasonny83 glad it helped !

@Zodiase that is an excellent explanation of what's going on under the hood. The solution you provided is safe and this is exactly the method that I adopted myself for a java wrapper project.
But, in the current situation, the DOM structure seems a little heavy for being dynamically built with javascript. The cloning solution seems more natural and convenient. But, as you pointed out, the automatic upgrade feature provided by mdl makes it dangerous.

Alternative

In order to make the template to be cloned invisible for the automatic upgrade, it is possible to prefix all the mdl-js-xxx flags with a keyword like template-shield-. The clones of the template should be responsible for removing that prefix after the cloning and then call componentHandler.upgradeElement(cloneRoot) to apply the mdl magic on the cloned DOM.

What do you think about this method ?

@ilyes4j Sorry for getting back late. I think the alternative you suggested should work nicely. A minimal generic helper for doing the dirty job (setting up the template and cloning from it) would be very helpful.

@Zodiase thank you for your reply. That is exactly what I had in mind ! : )

I know it's late but someone maybe benefits from this workaround.
What I did is to implement a function called materializeMyHTML which lets you insert an HTML string along with all the MDL's JS functionalities on.

window.materializeMyHTML = function(str){

    var html = $.parseHTML(str);

    $('*', $(html)).each(function () {

        componentHandler.upgradeElement(this);

    });

    return html;

};

Basically what it does is to receive an HTML string, parse it to convert it into DOM elements and then loop through them while applying MDL upgradeElement() function. The final result is a collection of DOM elements ready to be added wherever you like.
Usage goes as follow: $(#element).append(materializeMyHTML(<tag>more html with MDL components</tag>));

In my case it turned out to be very handy couse it lets me dynamically add MDL components while preserving all the visual effects.
Hope it helps.

@geronimotondato, the good part about MDL is to allow developers to create a material website avoiding the use of a lot of different libraries/frameworks. Your code requires jQuery which is not a MDL dependency so I still prefer calling the componentHandler.upgradeElement function as I create new elements in the DOM.
Thanks a lot for your time in any case.

@ilyes4j You are brilliant... this is the answer to an bug/issue i had while trying to recreate a Fragment-Behaviour a year ago!
I _tweaked_ the non-minified JS and did something like this:

/**
 * PekosoG Function lo Re-Load sh*t
 */
function loader1(){
        'use strict';
        /**
         * Performs a "Cutting the mustard" test. If the browser supports the features
         * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL
         * components requiring JavaScript.
         */
        if ('classList' in document.createElement('div') &&
            'querySelector' in document &&
            'addEventListener' in window && Array.prototype.forEach) {
            document.documentElement.classList.add('mdl-js');
            componentHandler.upgradeAllRegistered();
        } else {
            componentHandler.upgradeElement =
                componentHandler.register = function() {};
        }
}

I use to call this everytime I load a different content. But with the componentHandler.upgradeDom(); my tweak is no longer needed.

Thanks a lot.

Thanks a lot for this, I was also stuck with this as I am wrapping a ReactJS based lib, https://youtu.be/pGq9k-KhMtk, .upgradeDom() helped a lot!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rafaelcorreiapoli picture rafaelcorreiapoli  路  3Comments

an0nh4x0r picture an0nh4x0r  路  3Comments

arturgspb picture arturgspb  路  3Comments

itisparas picture itisparas  路  3Comments

traviskaufman picture traviskaufman  路  5Comments