简体   繁体   中英

Vaadin: Styling inside a RichTextArea?

I'm wondering if it's possible to add a stylesheet or styling rules to the iframe on a RichTextArea field?

I need to make a couple of CSS tweaks to the default styling but I can't target the RichTextArea through my application stylesheet because it's loaded within an iframe.

The "problem" with the Vaadin RichTextArea component is not only in the fact that the editor field is inside an iframe element, but as with all the other Vaadin components, you also have to keep in mind that your components will not be available when the DOM ready callback (ie for example $(document).ready(function() {}) if using jQuery or the callback bound to a DOMContentLoaded event) will execute.

This is because, as you know, when the Vaadin application starts, you actually don't have your components inside the DOM yet, but a vaadin bootstrap process will request and take care of the rendering of your UI for you. This is actually the principle with whom GWT works also (see How does GWT provide the correct Javascript code to every browser eg to carry out i18n and browser compatibility? ) (after all Vaadin is based on GWT).

So eg if you use jQuery and you have a script like this loaded at the very beginning right after the vaadinBootstrap.js script loads and executes:

$(function() {
   // this code will execute, but no components are available yet.
   var rTa = $(".v-richtextarea"); // this won't select your Rich text area
   var len = rTa.length // len will be 0 here, as no element matches the previous selector because as stated before, there is not an element with such a class in the DOM yet.
});

After this code executes, the very "heavy" process of creating the UI components and your layout begins, your widgetsets and/or the default one get loaded, and after that you have your beautiful UI set up and ready to interact with the user.

In order to customise an existent component such a RichTextArea and eg add a style element to the body of its iframe element, you can certainly venture into the depths of GWT and use JSNI as you did in your answer, but there's also another way to do it, in my opinion, more compact, simple, and does not require the usage of JSNI.

All you need to do is to implement a JavaScriptExtension with a connector on the client side for your component (you can just extend Vaadin's RichTextArea), check out this simple code example:

#!java
package com.package.example;

@JavaScript({"vaadin://js/src/rich_text_area_connector.js"})
public class RichTextAreaExtension extends AbstractJavaScriptExtension {

    @Override
    public void extend(AbstractClientConnector connector) {
        super.extend(connector);
    }

}

This is the extension, then you would need to create the client side connector, which is basically a JavaScript file with a function which name is based on the package name of the extension and, of course, the extension's class name:

#!javascript
com_package_example_RichTextAreaExtension = function() {
    var connectorParentId = this.getParentId();
    var element = this.getElement(parentId); // this is the rich text area element, which at this point is 

    // If you are using jQuery, then you can just select your element like so:
    var jQueryElement = $(element);
    // and do whatever you would normally do with the element like 
    // when you are inside $(document).ready(function() {});

    // or you can add a style element to the head element inside the iframe, doing something like the following:
    $(element).find("iframe").contents().find('head')
         .append('<link rel="stylesheet" href="./VAADIN/themes/your_theme/style_for_richtextarea_body.css" type="text/css" />');
}

And you are done. Another benefit, as you can see is that you don't have to write different code for different browsers (Mozilla, Chrome, IE), you can just use jQuery and the library will handle the compatibility for you. The last part is the extended component itself. As I said before, you can just extend Vaadin's RichTextArea:

public class RichTextAreaWithStyleOnBody extends RichTextArea {

    public RichTextAreaWithStyleOnBody(String caption, Property<?> dataSource) {
        super(caption);
        if (dataSource != null) 
            setPropertyDataSource(dataSource);
        new RichTextAreaExtension().extend(this);
    }

    public RichTextAreaWithStyleOnBody(String caption, Property<?> dataSource) {
        this(caption, dataSource);
    }

    public RichTextAreaWithStyleOnBody(String caption) {
        this(caption, null);
    }

    public RichTextAreaWithStyleOnBody(Property<?> dataSource) {
        this(null, dataSource);
    }

    public RichTextAreaWithStyleOnBody() {
        this(null, null);
    }

}

Note the usage of the JavaScript extension inside the main constructor. And finally you can use it in your layout just as you would with any other component:

// Inside your UI's class
@Override
protected void init(VaadinRequest request) {
        VerticalLayout layout = new VerticalLayout();
        layout.setMargin(true);
        layout.setSpacing(true);
        setContent(layout);

        RichTextArea rTa = new RichTextAreaWithStyleOnBody("A rich text area with a styled body");
        rTa.setStyleName("myRichTextArea"); // you can do whatever you'll like on the server side just because your rich text area extends a Vaadin server side component.
        rTa.setSizeFull();

        layout.addComponent(rTa);
}

As far as I know it is not possible. I had the same problem and wrote a little add-on based on a copy of the vaadin code from the RichTextArea. I added some additional methods to set font-family and font-size. Unfortunately I didn't have enough time recently to clean up my code publish a new version of the add-on.

The main functionality of the add-on is to decouple the toolbar from the area. You can find the code in the v7 branch here: https://gitorious.org/richtexttoolbar-vaadin-addon/richtexttoolbar-vaadin-addon

After continued searching I found this discussion, which led me to a solution: https://groups.google.com/forum/#!msg/Google-Web-Toolkit/9kwAJNhnamY/1MVfFFRq8tUJ

I ended up wrapping the RichTextImpl* classes, cloning the initElement() method from the parent class, and inserting these lines...

...for Mozilla/Safari:

_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.designMode = 'On';
var doc = _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document;
head=doc.getElementsByTagName('head')[0];
link=document.createElement('link');
link.setAttribute('rel',"stylesheet");
link.setAttribute('href',"/path/to/richtext.css" );
link.setAttribute('type',"text/css");
head.appendChild(link);

...for IE:

  var ct = "<html><head><style>@import url('/path/to/richtext.css');</style></head><body CONTENTEDITABLE='true'></body></html>" ;
  doc.write( ct );

... to get a style sheet loading in my RichTextArea fields.

Hy guys, i don't know if ist still important but i did something different.

I had a token function made for my richtextarea, and for the token that a was saving in the db just save it with a style class that i already have in my "styles.css" declared.

Step by step is something like this, i am taking the stylesheet and looking for my token classes, and then i am adding the styles in my iframes header, so that my token class in the iframe is styled.

 function styleRichtextareaIframe(id, themeClass, tokenClass, tokenSelectedClass) { setTimeout(function() { // iframe by id selected var $iframe = $("#" + id).find(".gwt-RichTextArea"); var $head = $iframe.contents().find("head"); var $body = $iframe.contents().find("body"); var classNameToken = "." + themeClass + " " + "." + tokenClass; var classNameToken_selected = "." + themeClass + " " + "." + tokenSelectedClass; var fontClass = "." + themeClass + ".v-app, " + "." + themeClass + " " + ".v-window"; var styleSheets = window.document.styleSheets; var styleSheetsLength = styleSheets.length; var style = document.createElement('style'); style.type = 'text/css'; for (var i = 0; i < styleSheetsLength; i++) { var classes = styleSheets[i].rules || styleSheets[i].cssRules; for (var x = 0; x < classes.length; x++) { if (classes[x].selectorText == classNameToken) { style.appendChild(document.createTextNode(getClassFor(classes[x]))); } if (classes[x].selectorText == classNameToken_selected) { style.appendChild(document.createTextNode(getClassFor(classes[x]))); } if (classes[x].selectorText == fontClass) { style.appendChild(document.createTextNode(getClassFor(classes[x]))); } } } function getClassFor(classObj) { if (classObj.cssText) { return classObj.cssText; } else { return classObj.style.cssText; } } //Adding the classes also to the body //of dourse you can change the output string to just give you the style class that you need. $body.addClass("v-app " + themeClass); $head.append(style); }, 200); } 

I had to inset the timeout function because of the "problem" with the Vaadin RichTextArea component is not only in the fact that the editor field is inside an iframe element, but as with all the other Vaadin components, you also have to keep in mind that your components will not be available when the DOM is ready, (THE COMPONENT IS NOT IN THE DOM).

Hope this help someone, and sorry for my bad english.

Cheers.

Simplest way I came up with was extending the RichTextArea component and setting a custom style with JavaScript:

public class MyRichTextArea extends RichTextArea {

public MyRichTextArea(String className) {
    setStyleName(className);
    String js = "var iframeContainer = document.getElementsByClassName('" + className + "')[0];" +
                "var iframe = iframeContainer.getElementsByTagName('iframe')[0];" +
                "var iframeBody = iframe.contentDocument.getElementsByTagName('body')[0];" +
                "iframeBody.style.fontFamily='Arial';";
    JavaScript.getCurrent().execute(js);
}

}

Usage:

MyRichTextArea field = new MyRichTextArea("fieldClassName");

Please note that iframe.contentDocument should be supported with all major browsers, but for better support additional tweaks should be added - see Getting the document object of an iframe

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM