How is "visible" transmitted to widget in Vaadin 7 UIDL?

How is the ‘visible’ attribute transmitted between the server and client via UIDL? I don’t see any obvious values in the UIDL that reflect the change in ‘visible’ state.

Our widget uses a LegacyConnector after it was ported from Vaadin 6 to Vaadin 7 (now on 7.1.7).

You can see the odd issue we have, though our code makes no effort at handling the ‘visible’ attribute at all, at:
https://open.esignforms.com/Vaadin7CKEditor/

Note that after you load the page, if you click the ‘Toggle visible editor #2’ (second editor on page), it appears to work as desired.

But if you click ‘Toggle visible editor #1’ (first editor on page), both editors become invisible, though the space for the second editor is still there. If you click it again, the first editor appears again, but the second editor is still not visible (but still with the space allocated like it’s there). In fact, the next time you toggle editor #2’s visible, even if it’s not visible but the space is there, you will see the space collapse (like it’s really invisible now), and if done again, it’ll become visible.

I’m trying to figure out what is going on, whether this is a bug in our code (though we don’t do anything for ‘visible’ now), or in Vaadin or the LegacyConnector or what.

Here’s the source code for that test page:


package org.vaadin.openesignforms.ckeditor;

import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.UI;
import com.vaadin.annotations.Theme;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;

public class VaadinCKEditorUI extends UI {

    @Override
    public void init(VaadinRequest request) {
        
        getPage().setTitle("Vaadin7 CKEditor UI");
        
        VerticalLayout mainView = new VerticalLayout();
        setContent(mainView);
        
        mainView.addComponent(new Button("Hit server"));
        
        Label separator = new Label(" ");
        separator.setContentMode(ContentMode.HTML);
        mainView.addComponent(separator);
       
        final String editor1InitialValue =
                "<p>Thanks TinyMCEEditor for getting us started on the CKEditor integration.</p>\n\n<h1>Like TinyMCEEditor said, &quot;Vaadin rocks!&quot;</h1>\n\n<h1>And CKEditor is no slouch either.</h1>\n";

        CKEditorConfig config1 = new CKEditorConfig();
        config1.useCompactTags();
        config1.disableElementsPath();
        config1.setResizeDir(CKEditorConfig.RESIZE_DIR.HORIZONTAL);
        config1.disableSpellChecker();
        config1.setHeight("300px");
        
        final CKEditorTextField ckEditorTextField1 = new CKEditorTextField(config1);
        ckEditorTextField1.setHeight("440px"); // account for 300px editor plus toolbars
        mainView.addComponent(ckEditorTextField1);
        
        ckEditorTextField1.setValue(editor1InitialValue);
        ckEditorTextField1.addValueChangeListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                Notification.show("CKEditor v" + ckEditorTextField1.getVersion() + "/" + getVersion() + " - #1 contents: " + event.getProperty().getValue().toString());
                ckEditorTextField1.focus();
            }
        });
        
        Button resetTextButton1 = new Button("Reset editor #1");
        resetTextButton1.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                if ( ! ckEditorTextField1.isReadOnly() ) {
                    ckEditorTextField1.setValue(editor1InitialValue);
                }
            }
        });
        
        Button toggleReadOnlyButton1 = new Button("Toggle read-only editor #1");
        toggleReadOnlyButton1.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                ckEditorTextField1.setReadOnly( ! ckEditorTextField1.isReadOnly() );
            }
        });

        Button toggleViewWithoutEditorButton1 = new Button("Toggle view-without-editor #1");
        toggleViewWithoutEditorButton1.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                ckEditorTextField1.setViewWithoutEditor( ! ckEditorTextField1.isViewWithoutEditor() );
            }
        });

        Button toggleVisibleButton1 = new Button("Toggle visible editor #1");
        toggleVisibleButton1.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                ckEditorTextField1.setVisible( ! ckEditorTextField1.isVisible() );
            }
        });
        HorizontalLayout buttonsLayout = new HorizontalLayout(resetTextButton1,toggleReadOnlyButton1,toggleViewWithoutEditorButton1,toggleVisibleButton1);
        buttonsLayout.setSpacing(true);
        mainView.addComponent( buttonsLayout );

        separator = new Label(" ");
        separator.setContentMode(ContentMode.HTML);
        mainView.addComponent(separator);
        
        // Now add in a second editor....
        final String editor2InitialValue =
            "<p>Here is editor #2.</p>\n\n<p>Hope you find this useful in your Vaadin projects.</p>\n";

        final CKEditorTextField ckEditorTextField2 = new CKEditorTextField();
        ckEditorTextField2.setWidth("600px");
        mainView.addComponent(ckEditorTextField2);
        
        CKEditorConfig config2 = new CKEditorConfig();
        config2.addCustomToolbarLine("{ items : ['Source','Styles','Bold','VaadinSave','-','Undo','Redo','-','NumberedList','BulletedList']
 }");
        config2.enableVaadinSavePlugin();
        config2.addToRemovePlugins("scayt");
        ckEditorTextField2.setConfig(config2);
        ckEditorTextField2.setValue(editor2InitialValue);
        
        ckEditorTextField2.addValueChangeListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                Notification.show("CKEditor v" + ckEditorTextField2.getVersion() + "/" + getVersion() + " - #2 contents: " + event.getProperty().getValue().toString());
            }
        });
        
        Button resetTextButton2 = new Button("Reset editor #2");
        resetTextButton2.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                if ( ! ckEditorTextField2.isReadOnly() ) {
                    ckEditorTextField2.setValue(editor2InitialValue);
                }
            }
        });
        
        Button toggleReadOnlyButton2 = new Button("Toggle read-only editor #2");
        toggleReadOnlyButton2.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                ckEditorTextField2.setReadOnly( ! ckEditorTextField2.isReadOnly() );
            }
        });

        Button toggleViewWithoutEditorButton2 = new Button("Toggle view-without-editor #2");
        toggleViewWithoutEditorButton2.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                ckEditorTextField2.setViewWithoutEditor( ! ckEditorTextField2.isViewWithoutEditor() );
            }
        });
        
        Button toggleVisibleButton2 = new Button("Toggle visible editor #2");
        toggleVisibleButton2.addClickListener( new Button.ClickListener() {            
            @Override
            public void buttonClick(ClickEvent event) {
                ckEditorTextField2.setVisible( ! ckEditorTextField2.isVisible() );
            }
        });
        
        buttonsLayout = new HorizontalLayout(resetTextButton2,toggleReadOnlyButton2,toggleViewWithoutEditorButton2,toggleVisibleButton2);
        buttonsLayout.setSpacing(true);
        mainView.addComponent( buttonsLayout );

        separator = new Label(" ");
        separator.setContentMode(ContentMode.HTML);
        mainView.addComponent(separator);

        buttonsLayout = new HorizontalLayout();
        buttonsLayout.setSpacing(true);
        mainView.addComponent( buttonsLayout );
        
        buttonsLayout.addComponent(new Button("Open Modal Subwindow", new ClickListener() {                      
            @Override
            public void buttonClick(ClickEvent event) {
                    Window sub = new Window("Subwindow modal");
                    VerticalLayout subLayout = new VerticalLayout();
                    sub.setContent(subLayout);
                    
                    CKEditorConfig config = new CKEditorConfig();
                    config.useCompactTags();
                    config.disableElementsPath();
                    config.disableSpellChecker();
                    config.enableVaadinSavePlugin();
                    // set BaseFloatZIndex 1000 higher than CKEditor's default of 10000; probably a result of an editor opening
                    // in a window that's on top of the main two editors of this demo app
                    config.setBaseFloatZIndex(11000);
                    config.setHeight("150px");
                    
                    final CKEditorTextField ckEditorTextField = new CKEditorTextField(config);
                    ckEditorTextField.addValueChangeListener(new Property.ValueChangeListener() {
                        public void valueChange(ValueChangeEvent event) {
                            Notification.show("CKEditor v" + ckEditorTextField2.getVersion() + "/" + getVersion() + " - POPUP MODAL contents: " + event.getProperty().getValue().toString());
                        }
                    });
                    ckEditorTextField.focus();
                    
                    subLayout.addComponent(ckEditorTextField);
                    
                    sub.setWidth("80%");
                    sub.setModal(true);
                    sub.center();
                    
                    event.getButton().getUI().addWindow(sub);
            }
        }));

        buttonsLayout.addComponent(new Button("Open Non-Modal Subwindow with 100% Height", new ClickListener() {                      
            @Override
            public void buttonClick(ClickEvent event) {
                    Window sub = new Window("Subwindow non-modal 100% height");
                    VerticalLayout subLayout = new VerticalLayout();
                    sub.setContent(subLayout);
                    sub.setWidth("80%");
                    sub.setHeight("500px");

                    subLayout.setSizeFull();
                    
                    CKEditorConfig config = new CKEditorConfig();
                    config.useCompactTags();
                    config.disableElementsPath();
                    config.disableSpellChecker();
                    config.enableVaadinSavePlugin();
                    // set BaseFloatZIndex 1000 higher than CKEditor's default of 10000; probably a result of an editor opening
                    // in a window that's on top of the main two editors of this demo app
                    config.setBaseFloatZIndex(11000);
                    config.setStartupFocus(true);
                    
                    final CKEditorTextField ckEditorTextField = new CKEditorTextField(config);
                    ckEditorTextField.setHeight("100%");
                    ckEditorTextField.addValueChangeListener(new Property.ValueChangeListener() {
                        private static final long serialVersionUID = 5592423527258867304L;

                        public void valueChange(ValueChangeEvent event) {
                            Notification.show("CKEditor v" + ckEditorTextField2.getVersion() + "/" + getVersion() + " - POPUP NON-MODAL 100% HEIGHT contents: " + event.getProperty().getValue().toString());
                        }
                    });
                    subLayout.addComponent(ckEditorTextField);
                    subLayout.setExpandRatio(ckEditorTextField,10);
                    
                    final TextField textField = new TextField("TextField");
                    textField.addValueChangeListener(new Property.ValueChangeListener() {
                        private static final long serialVersionUID = 6686202497483757206L;

                        public void valueChange(ValueChangeEvent event) {
                            Notification.show("TextField - POPUP NON-MODAL 100% HEIGHT contents: " + event.getProperty().getValue().toString());
                        }
                    });
                    subLayout.addComponent(textField);
                    
                    sub.center();
                    
                    event.getButton().getUI().addWindow(sub);
            }
        }));
    }
    
    public String getVersion() {
        return "7.8.7";
    }

}

Hi,

as far as I know it’s not handled at all (on the client side). In vaadin 7, any data about non-visible components is not sent to the client side at all.

Then how does the client make it appear/disppear? They must update the DOM based on something…

One of the things the server sends to the client is the list of modified hierarchies - effectively ordered lists of identifiers of children of a component container. If that list has been rearranged, or something has disappeared or been added to it, the client side component container (usually a layout) knows to modify its children.

Thanks, Henri. That makes sense, but it makes it much harder for me to debug what I am seeing.

What would be the best bet for debugging the issue (demo link and code above) since when I click to make the top editor invisible, both editors become invisible (though the second editor is not removed from the DOM, just not rendered, as the space is held as if it were there)?

It seems possible that the first editor is responsible for ensuring the CKEditor javascript is downloaded and initialized, followed by the editor instance itself being created. I wonder if when that first editor is made not visible, it causes not only the editor, but the entire loaded CKEditor JS to also no longer be present (hence the second editor space being allocated, but the editor itself not shown). This may also explain why if the second editor is made not visible, it works as expected.

But I have no idea exactly how the JS downloads and the creation of my widget (the first editor on a page) are tied together. I mean, my editor only creates a DIV and then puts the editor inside that DIV. But there is an “initialization” phase for the first editor that ensures the JS has been downloaded – so if someone has any ideas, here’s that snippet for initialization as maybe this is where all is going wrong somehow (though it appears I am loading in the element and this is using Vaadin 7, but with the LegacyConnector to bridge code that was originally developed for Vaadin 6):

    public static void loadLibrary(ScheduledCommand afterLoad) {
        if (!libraryLoadInited) {
            String url = GWT.getModuleBaseURL() + "ckeditor/ckeditor.js";
            ScriptElement se = Document.get().createScriptElement();
            se.setSrc(url);
            se.setType("text/javascript");
            Document.get().getElementsByTagName("head").getItem(0).appendChild(se);
            libraryLoadInited = true;
            Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {                
                @Override
                public boolean execute() {
                    if (libraryReady()) {
                        reduceBlurDelay();
                        for (ScheduledCommand sc: afterLoadedStack) {
                            sc.execute();
                        }
                        libraryLoaded = true;
                        return false;
                    }
                    return true;
                }
            }, 50);
        }
        if (libraryLoaded) {
            afterLoad.execute();
        } else {
            afterLoadedStack.add(afterLoad);
        }
    }

Henri,

I basically trimmed down my sample program to just have the two editors and the two ‘toggle visible’ buttons. This resulted in the first editor having ID 2 and the second editor having ID 5.

When I make editor id 2 not visible, we get this UIDL response, which still shows editor id 2 out of the list and editor id 5 in the hierarchy, but the editor itself disappears though the DIV space remains:


for(;;);[{“changes” : []
, “state”:{“1”:{“childData”:{“3”:{“expandRatio”:0,“alignmentBitmask”:5},“6”:{“expandRatio”:0,“alignmentBitmask”:5},“5”:{“expandRatio”:0,“alignmentBitmask”:5}}}}, “types”:{“1”:“4”},
“hierarchy”:{“1”:[“3”,“5”,“6”]
}
, “rpc” : [], “meta” : {}, “resources” : {}, “timings”:[38, 1]
}]

When I make editor id 2 visible again, we get this response showing editor id 2 is back in the hierarchy, but sadly editor id 5 is still not rendered:


for(;;);[{“changes” : [[“change”,{“pid”:“2”},[“1”,{“id”:“2”,“readonly”:false,“viewWithoutEditor”:false,“inPageConfig”:“{ resize_dir : ‘horizontal’, removePlugins : ‘elementspath,scayt’, height : ‘300px’ }”,“writerRules.tagName0”:“li”,“writerRules.jsRule0”:“{indent : true, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName1”:“p”,“writerRules.jsRule1”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName2”:“h5”,“writerRules.jsRule2”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName3”:“h4”,“writerRules.jsRule3”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName4”:“h6”,“writerRules.jsRule4”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName5”:“h1”,“writerRules.jsRule5”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName6”:“h3”,“writerRules.jsRule6”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“writerRules.tagName7”:“h2”,“writerRules.jsRule7”:“{indent : false, breakBeforeOpen : true, breakAfterOpen : false, breakBeforeClose : false, breakAfterClose : true}”,“protected_body”:false,“v”:{“text”:“

Thanks TinyMCEEditor for getting us started on the CKEditor integration.</p>\n\n

Like TinyMCEEditor said, "Vaadin rocks!"</h1>\n\n

And CKEditor is no slouch either.</h1>\n”}}]
]], “state”:{“2”:{“height”:“440.0px”,“width”:“100.0%”},“1”:{“childData”:{“3”:{“expandRatio”:0,“alignmentBitmask”:5},“2”:{“expandRatio”:0,“alignmentBitmask”:5},“6”:{“expandRatio”:0,“alignmentBitmask”:5},“5”:{“expandRatio”:0,“alignmentBitmask”:5}}}}, “types”:{“2”:“1”,“1”:“4”},
“hierarchy”:{“2”:[],“1”:[“2”,“3”,“5”,“6”]
}
, “rpc” : [], “meta” : {}, “resources” : {}, “timings”:[41, 3]
}]

Then we compare this to editor id 5 (the second editor) which seems to work as expected in the visible regard.

When we hide editor id 5, we show that it’s also removed from the hierarchy:


for(;;);[{“changes” : []
, “state”:{“1”:{“childData”:{“3”:{“expandRatio”:0,“alignmentBitmask”:5},“2”:{“expandRatio”:0,“alignmentBitmask”:5},“6”:{“expandRatio”:0,“alignmentBitmask”:5}}}}, “types”:{“1”:“4”},
“hierarchy”:{“1”:[“2”,“3”,“6”]
}
, “rpc” : [], “meta” : {}, “resources” : {}, “timings”:[46, 0]
}]

And when we make editor id 5 visible again, it shows up back in the hierarchy:


for(;;);[{“changes” : [[“change”,{“pid”:“5”},[“1”,{“id”:“5”,“readonly”:false,“viewWithoutEditor”:false,“inPageConfig”:“{ toolbar : ‘Custom’, toolbar_Custom : [{ items : [‘Source’,‘Styles’,‘Bold’,‘VaadinSave’,‘-’,‘Undo’,‘Redo’,‘-’,‘NumberedList’,‘BulletedList’]
}], extraPlugins : ‘vaadinsave’, removePlugins : ‘scayt’ }”,“protected_body”:false,“v”:{“text”:“

Here is editor #2.</p>\n\n

Hope you find this useful in your Vaadin projects.</p>\n”}}]]], “state”:{“1”:{“childData”:{“3”:{“expandRatio”:0,“alignmentBitmask”:5},“2”:{“expandRatio”:0,“alignmentBitmask”:5},“6”:{“expandRatio”:0,“alignmentBitmask”:5},“5”:{“expandRatio”:0,“alignmentBitmask”:5}}},“5”:{“height”:“300.0px”,“width”:“600.0px”}}, “types”:{“1”:“4”,“5”:“1”},
“hierarchy”:{“1”:[“2”,“3”,“5”,“6”]
,“5”:[]}

, “rpc” : [], “meta” : {}, “resources” : {}, “timings”:[49, 3]
}]

So from this level, it seems like it should work, but it doesn’t. When we hide editor id 2, editor id 5 is not removed from the DOM, but it’s no longer visible.

From the HTML/DOM level, we see this. Initially, we have two DIV class=“v-ckeditortextfield” like we expect with ID 2 and ID 5. Each is inside a DIV class=“v-slot” as being inside a vertical layout.

When we hide editor id 5 (second editor), we noted that the DIV class=“v-slot” is removed from the DOM, so our editor’s DIV and the surrounding vertical layout’s slot DIV are removed.
Sounds good.

When we show editor id 5, both the DIVs reappear as expected.
Perfect!

But when we hide editor id 2 (the first editor), the same basic operation takes place in that a DIV class=“v-slot” is removed that contains the editor DIV. But the “supposed to still be visible” editor id 5 is still present in the DOM with it’s DIV class=“v-slot” and our DIV class=“v-ckeditortextfield”, but that editor DIV is now empty (no longer has the editor we loaded in that DIV). That second editor’s DIV looks like this (
the problem!
):

[font=courier new]

[/font]

Previously, that editor DIV looked like this (and will look like this again only if we also now make that editor id 5 invisible and then visible again):

[font=courier new]

SNIPPED rest of ckeditor stuff... [/font]

So, the question that remains is why does hiding the first editor id 2 cause the second editor’s id 5 DIV to be emptied (but still present) at the same time editor id 2 DIV is removed from the DOM?

It seems that the UIDL still shows id 5 in the hierarchy, and that DIV does in fact stay in place, and our editor id 5’s DIV is still there, but it’s contents have been removed.

This is not something we are doing, obviously, so is this a bug in Vaadin (perhaps limited to those using LegacyConnector), or just some bug in how we’re handling it all?

Thanks for any ideas on how to resolve this.

EDIT: By the way, noted that the editor in the forum is our ckeditor component!

Any ideas from Vaadin on this issue with visible? It seems something unrelated to my code and thus seems like a bug in the platform. These potentially are serious security vulnerabilities if components visible/invisible are not handled correctly. We are already troubled by the fact that button click shortcut keys are being routed to non-visible components because we have code that runs on the click that shouldn’t be possible. Same goes here if the user actually sees or doesn’t see what’s programmed to appear.