Vaadin: Styling inside a RichTextArea? - vaadin

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 (i.e. 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 e.g. to carry out i18n and browser compatibility?) (after all Vaadin is based on GWT).
So e.g. 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 e.g. 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

Related

localization of items and groups in vis.js timeline

We used vis.js to create a timeline , We need to localize the name of groups and the content of items with different languages . we tried to use the $translate = $filter('translate') to translate the name of group in angularjs but it works only when we reloading the screen . We also tried for the translation of items to use the template handlebars engine to translate the items but The problem is that Angular seems not to proceed the templates used by Handlebars when we put "{{ 'timeline.content' | translate }} it gives an error . so is there any idea or a way to localize name of groups and items in vis.js .
You can use the configuration functions groupTemplate or template
var config = {
...
groupTemplate: function (group) {
var groupTemplate = document.createElement('div');
// assuming that you have stored the label key in the content, instead of text.
groupTemplate.innerText = translate.get(group.content);
// or whatever function call you need to make to get the translated label;
return groupTemplate;
}
...
}
You can do the same with the actual items in the timeline.
var config = {
...
template: function (item) {
var itemTemplate = document.createElement('div');
// assuming that you have stored the labels in the content, instead of text.
itemTemplate.innerText = translate.get(item.content)
// or whatever function call you need to make to get the translated label;
return itemTemplate;
}
...
}
You can also attach event listeners, or do whatever with templates as they render to a usual DOM elements.
Hope this helps.

Vaadin Grid Row Index

In a vaadin table if we do
table.setRowHeaderMode(RowHeaderMode.INDEX);
we get a column with the row index.
Is it possible to to the same with a vaadin grid?
So far I haven't seen such an option, but you should be able to fake it with a generated column. Please see below a naive implementation which should get you started (improvements and suggestions are more than welcome):
// our grid with a bean item container
Grid grid = new Grid();
BeanItemContainer<Person> container = new BeanItemContainer<>(Person.class);
// wrap the bean item container so we can generated a fake header column
GeneratedPropertyContainer wrappingContainer = new GeneratedPropertyContainer(container);
wrappingContainer.addGeneratedProperty("rowHeader", new PropertyValueGenerator<Long>() {
private long index = 0;
#Override
public Long getValue(Item item, Object itemId, Object propertyId) {
return index++;
}
#Override
public Class<Long> getType() {
return Long.class;
}
});
// assign the data source to the grid and set desired column order
grid.setContainerDataSource(wrappingContainer);
grid.setColumnOrder("rowHeader", "name", "surname");
// tweak it a bit - definitely needs more tweaking
grid.getColumn("rowHeader").setHeaderCaption("").setHidable(false).setEditable(false).setResizable(false).setWidth(30);
// freeze the fake header column to prevent it from scrolling horizontally
grid.setFrozenColumnCount(1);
// add dummy data
layout.addComponent(grid);
for (int i = 0; i < 20 ; i++) {
container.addBean(new Person("person " + i, "surname " + i));
}
This will generate something similar to the image below:
There is a Grid Renderer that can be used to do this now. It is in the grid renderers add-on https://vaadin.com/directory/component/grid-renderers-collection-for-vaadin7. It is compatible with Vaadin 8 as well.
Here is how it could be used (there are a few different options for how to render the index).
grid.addColumn(value -> "", new RowIndexRenderer()).setCaption("Row index");
Worth to mention that I use the following with Vaadin 18 flow and works perfectly.
grid.addColumn(TemplateRenderer.of("[[index]]")).setHeader("#");
Ok, it took me more than a while to figure this out. I don't know why you need this, but if your purpose is to find which grid row was clicked, then you can get the index from the datasource of your control via the itemClick event of your listener.
In my case, my datasource is an SQLContainer, and I already had it available (see ds var) so I did it this way:
grid.addListener(new ItemClickEvent.ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
Object itemId = event.getItemId();
int indexOfRow = ds.indexOfId(itemId);
}
});
You usually add a datasource to your control when you initialize it, via constructor or by setting the property. If you got you Grid from somewhere with an already-attached datasource, you can always get it with something like this:
SQLContainer ds = (SQLContainer)gred.getContainerDataSource();
I use this trick:
int i = 0;
grid.addComponentColumn(object -> {
i++;
return new Label("" + i);
}).setCaption("");

Orchard - Querying the Part On A Layout File

I’ve gotten stuck on an “Object reference not set to an instance of an object” error message.
We’re trying to use a PagePart field that is attached to the Page type to dynamically link a CSS file in the HEAD of a layout file. See below code.
<!-- DYNAMIC CSS-->
var contentItem = Model.ContentItem;
var pagePart = (PagePart)contentItem.PagePart;
if (!String.IsNullOrWhiteSpace(pagePart.FestivalProgramName))
{
<link ref="#Url.Content("/Themes/MyTheme/Styles/festival-programs/" + pagePart.FestivalProgramName + ".css")" rel="stylesheet" type="text/css">
}
This is in a file called:
Layout.cshtml
Something is wrong about this (obviously) since pagePart is “null” when I Attach to Debugger and look. I get that the Layout file doesn’t know that it’s associated with a “Page” Content Type but this layout is only used with Pages. Anyway, this is very similar to code that works elsewhere in our Orchard site. Any help or advice is hugely appreciated!
Thanks, T
In the Layout, the Model is the Layout object. It has nothing to do whatsoever with whatever content is going to get rendered into the Content zone.
I think what you are trying to do should be done by overriding the Page template (Content-Page.Detail.cshtml). (Note the Detail part, you probably don't want to import every css when displaying multiple pages in summary)
In there you can do:
#{
var contentItem = Model.ContentItem; // The Page content item
var pagePart = contentItem.Page; // Note that casting to PagePart won't work, because it does not exist
if (!String.IsNullOrWhiteSpace(pagePart.FestivalProgramName.Value))
{
// "Orchard's" way to include styles
Style.Include("festival-programs/" + pagePart.FestivalProgramName.Value + ".css");
}
}
EDIT:
What you probably should do (I assume not every page is a festival page) is create a new Content Type: FestivalPage. Then attach the following parts to this content type (same as the Page content type):
Common
Publish later
Title
Autoroute
Body (Orchard 1.8.1 and lower)
Layout (Orchard 1.9.x)
Tags
Localization
Menu
And your field:
FestivalProgramName
Then create an alternate Content-FestivalPage.Detail.cshtml with the following content:
#using Orchard.Utility.Extensions;
#{
if (Model.Title != null) {
Layout.Title = Model.Title;
}
Model.Classes.Add("content-item");
var contentTypeClassName = ((string)Model.ContentItem.ContentType).HtmlClassify();
Model.Classes.Add(contentTypeClassName);
var tag = Tag(Model, "article");
var contentItem = Model.ContentItem; // The FestivalPage content item
var pagePart = contentItem.FestivalPage; // The FestivalPage part with the FestivalProgramName field
if (!String.IsNullOrWhiteSpace(pagePart.FestivalProgramName.Value))
{
// "Orchard's" way to include styles
Style.Include("festival-programs/" + pagePart.FestivalProgramName.Value + ".css");
}
}
// -- Default Orchard content --
#tag.StartElement
<header>
#Display(Model.Header)
#if (Model.Meta != null) {
<div class="metadata">
#Display(Model.Meta)
</div>
}
</header>
#Display(Model.Content)
#if(Model.Footer != null) {
<footer>
#Display(Model.Footer)
</footer>
}
#tag.EndElement
// ----
This way you won't get in the way with the normal pages of the application.
Thank you so much for responding. Your solution works but not in my case since I use the Layout Selector module and can't override at the Page.Detail level since that would imply one layout - or at least from my perspective it seems that way. I found another option though that does the trick.
We already take advantage of the Handlers class to insert META stuff into the HEAD of the page and thanks to your feedback and this thread Adding an element to page <head> in Orchard CMS, it dawned on me to use the same Handler to insert the CSS link.
PagePartHandler.cs.
using System;
using MyModuleName.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Title.Models;
using Orchard.Data;
using Orchard.UI.Resources;
using Orchard.Utility.Extensions;
namespace MyModuleName.Handlers {
public class PagePartHandler : ContentHandler {
private readonly IResourceManager _resourceManager;
public PagePartHandler(
IRepository<PagePartRecord> repository,
IResourceManager resourceManager) {
_resourceManager = resourceManager;
Filters.Add(StorageFilter.For(repository));
OnGetDisplayShape<PagePart>(RegisterFestivalProgramStyle);
}
private void RegisterFestivalProgramStyle(BuildDisplayContext context, PagePart part) {
if (context.DisplayType != "Detail")
return;
if (String.IsNullOrWhiteSpace(part.FestivalProgramName))
return;
_resourceManager.RegisterLink(new LinkEntry
{
Rel = "stylesheet",
Type = "text/css",
Href = "/Themes/Bootstrap/Styles/festival-programs/" + part.FestivalProgramName + ".css"
});
}
}
}
This uses the tradition link style, not ResourceManifest.cs, but WORKS!

MVC Dynamically append img tag from controller

I want to add unknown number of images based on how many is there for specific user; i have used .Append function for div,labels,inputs and its work fine but when i want to append with source its not working !
so how can i do this ?
i have tried this:
` string sc = "$('<img/>', {src=\"~/Content/themes/img2.jpg\" alt=\"\",class:'table-wrapper'}).appendTo(finalDalata);";
return JavaScript(sc)`
finalDalata is my Div name
so whats iam doing wrong ?
Your appendTo code is invalid, it needs a selector, and your image attributes are not formatted properly, so it should be:
string sc = "$('<img/>', { src:'~/Content/themes/img2.jpg', alt:'', class:'table-wrapper'}).appendTo('#finalDalata');"
However, controller should return data and not control (exploits and injections comes to mind), so a better way would be to create an AppendImage function on the View side in javascript:
function AppendImage(attributes)
{
$('<img/>', attributes).appendTo('#finalDalata');
}
Note: you should probably sanitize the attributes before to be sure.
Then return a list of attributes only:
var images = new List<dynamic>();
// Do this for each images
images.Add(new { src="~/Content/themes/img2.jpg", alt="", class="table-wrapper"});
return Json(images);
And in the ajax success you can:
success: function (data) {
data.foreach( function (item) {
AppendImage(item);
});
}
Solved by TagBuilder
TagBuilder tag = new TagBuilder("img");
tag.Attributes.Add("id", "myImage");
tag.Attributes.Add("src", "/imgs/" + UserName + ".jpg");
tag.Attributes.Add("alt", "my image");
tag.Attributes.Add("class", "imgsClass");
then convert tag to string and pass it back to view
thnx

how to use binding from a grid to an HTML template?

is it possible to use GXT's binding mechanism to bind a Grid to an Html (or other Widget implementation for that matter)?
it seems from the examples that it's only applicable for forms (FormBinding), while my purpose is to have a Template updating a widgets inner element, driven by the grid's SelectionChangedEvents sending the models for that template.
Binding grids and generic components directly is apparently not possible at the current version of GXT, anyhow this is an alternative solution offered as a workaround:
I just made use of the examples Grid Binding and Templates to support your requirement. Here is the modified code
#Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
setStyleAttribute("margin", "10px");
ContentPanel cp = new ContentPanel();
cp.setHeading("Form Bindings");
cp.setFrame(true);
cp.setSize(800, 400);
cp.setLayout(new RowLayout(Orientation.HORIZONTAL));
final LayoutContainer panel = new LayoutContainer(new FlowLayout());
Grid<Stock> grid = createGrid();
grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
final Template template = new Template(getTemplate());
grid.getSelectionModel().addListener(Events.SelectionChange, new Listener<SelectionChangedEvent<Stock>>() {
public void handleEvent(SelectionChangedEvent<Stock> be) {
if (be.getSelection().size() > 0) {
template.overwrite(panel.getElement(), Util.getJsObject(be.getSelectedItem()));
} else {
panel.removeAll();
}
}
});
cp.add(grid, new RowData(.6, 1));
cp.add(panel, new RowData(.4, 1));
add(cp);
}
private native String getTemplate() /*-{
var html = [ '<p>Name: {name}</p>', '<p>Symbol: {symbol}</p>',
'<p>Last: {last}</p>', '<p>Last Updated: {date}</p>', ];
return html.join("");
}-*/;
Add these modifications to the Grid Binding example. Hope this helps.
Thanks,
Ganesh

Resources