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
Related
Vaadin 8.1 introduced the TreeGrid component. It does not have the collapseItemsRecursively and expandItemsRecursively methods anymore (as available in the now legacy Tree component). Do i miss something or do you need to develop your own implementation? If so, what is a recommended way of doing this?
As I'm sure you've noticed, the TreeGrid is a rather new component, currently being developed and available starting with v8.1.alphaX (current stable version is v8.0.6). As such, it probably has only some basic functionalities for the time being, with the rest to follow sometime in the future, although there are no guarantee. For example this similar feature request for the older TreeTable component has been in open state since 2011.
Either way, even if they're probably not the optimum solutions, there are a couple of work-arounds that you can use to achieve this behavior. I'm shamelessly using as a base sample, a slightly modified version of the code currently available in the vaadin-sampler for TreeGrid.
public class RecursiveExpansionTreeGrid extends VerticalLayout {
private Random random = new Random();
public RecursiveExpansionTreeGrid() {
// common setup with some dummy data
TreeGrid<Project> treeGrid = new TreeGrid<>();
treeGrid.setItems(generateProjectsForYears(2010, 2016), Project::getSubProjects);
treeGrid.addColumn(Project::getName).setCaption("Project Name").setId("name-column");
treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");
treeGrid.addColumn(Project::getLastModified).setCaption("Last Modified");
addComponent(treeGrid);
}
// generate some dummy data to display in the tree grid
private List<Project> generateProjectsForYears(int startYear, int endYear) {
List<Project> projects = new ArrayList<>();
for (int year = startYear; year <= endYear; year++) {
Project yearProject = new Project("Year " + year);
for (int i = 1; i < 2 + random.nextInt(5); i++) {
Project customerProject = new Project("Customer Project " + i);
customerProject.setSubProjects(Arrays.asList(
new LeafProject("Implementation", random.nextInt(100), year),
new LeafProject("Planning", random.nextInt(10), year),
new LeafProject("Prototyping", random.nextInt(20), year)));
yearProject.addSubProject(customerProject);
}
projects.add(yearProject);
}
return projects;
}
// POJO for easy binding
public class Project {
private List<Project> subProjects = new ArrayList<>();
private String name;
public Project(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<Project> getSubProjects() {
return subProjects;
}
public void setSubProjects(List<Project> subProjects) {
this.subProjects = subProjects;
}
public void addSubProject(Project subProject) {
subProjects.add(subProject);
}
public int getHoursDone() {
return getSubProjects().stream().map(project -> project.getHoursDone()).reduce(0, Integer::sum);
}
public Date getLastModified() {
return getSubProjects().stream().map(project -> project.getLastModified()).max(Date::compareTo).orElse(null);
}
}
// Second POJO for easy binding
public class LeafProject extends Project {
private int hoursDone;
private Date lastModified;
public LeafProject(String name, int hoursDone, int year) {
super(name);
this.hoursDone = hoursDone;
lastModified = new Date(year - 1900, random.nextInt(12), random.nextInt(10));
}
#Override
public int getHoursDone() {
return hoursDone;
}
#Override
public Date getLastModified() {
return lastModified;
}
}
}
Next, recursively expanding or collapsing the nodes depends a bit on your scenario, but basically it breaks down to the same thing: making sure each node from the root to the deepest leaf is expanded/collapsed.The simplest way of doing it is to flatten your hierarchy into a list of nodes, and call the appropriate method, expand(List<T> items) or expand(T ... items) (the second delegates to the first and is probably a convenience method eg expand(myItem)).
For simplicity, I've added a flatten method in our Project implementation. If you can't do that for some reason, then create a recursive method that creates a list starting with the selected node and includes all the children, of the children, of the children.... well, you get the idea.
public Stream<Project> flatten() {
return Stream.concat(Stream.of(this), getSubProjects().stream().flatMap(Project::flatten));
}
Possible scenarios:
Automatically expand the entire hierarchy when expanding the root - add listeners, and expand/collapse the whole flattened hierarchy:
treeGrid.addCollapseListener(event -> {
if (event.isUserOriginated()) {
// event is triggered by all collapse calls, so only do it the first time, when the user clicks in the UI
// and ignore the programmatic calls
treeGrid.collapse(event.getCollapsedItem().flatten().collect(Collectors.toList()));
}
});
treeGrid.addExpandListener(event -> {
if (event.isUserOriginated()) {
// event is triggered by all expand calls, so only do it the first time, when the user clicks in the UI
// and ignore the programmatic calls
treeGrid.expand(event.getExpandedItem().flatten().collect(Collectors.toList()));
}
});
Expanding the hierarchy or part of it with a custom action, such as a context menu
GridContextMenu<Project> contextMenu = new GridContextMenu<>(treeGrid);
contextMenu.addGridBodyContextMenuListener(contextEvent -> {
contextMenu.removeItems();
if (contextEvent.getItem() != null) {
Project project = (Project) contextEvent.getItem();
// update selection
treeGrid.select(project);
// show option for expanding
contextMenu.addItem("Expand all", VaadinIcons.PLUS, event -> treeGrid.expand((project).flatten().collect(Collectors.toList())));
// show option for collapsing
contextMenu.addItem("Collapse all", VaadinIcons.MINUS, event -> treeGrid.collapse((project).flatten().collect(Collectors.toList())));
}
});
In the end, you should be getting this effect:
From the docs for treegrid, you can use the methods, collapse and expand, by passing a list or array of the treegrid's data items to expand or collapse:
treeGrid.expand(someTreeGridItem1, someTreeGridItem2);
treeGrid.collapse(someTreeGridItem1);
Also worthy of note, is a section showing the ability to prevent certain items from ever being collapsed
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("");
I am just new in MVC 4. I have a html Razor view which contain all some table related data.
I just want to convert that view in to pdf document without third-party tool.
In case you are using ASP.NET Core here is my solution: http://nikolay.it/Blog/2018/03/Generate-PDF-file-from-Razor-view-using-ASP-NET-Core-and-PhantomJS/37
Get HTML string from a Razor view
This step is pretty straight-forward. There is a service called IRazorViewEngine in ASP.NET Core which can be injected and then used to get the view. After providing the view with default ViewDataDictionary and ActionContext we can request the view to be rendered into StringWriter which can be easily converted to string. Here is ready-to-use code for getting a string from given Razor view file:
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, object model);
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine razorViewEngine;
private readonly ITempDataProvider tempDataProvider;
private readonly IServiceProvider serviceProvider;
public ViewRenderService(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
this.razorViewEngine = razorViewEngine;
this.tempDataProvider = tempDataProvider;
this.serviceProvider = serviceProvider;
}
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var httpContext = new DefaultHttpContext { RequestServices = this.serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var sw = new StringWriter())
{
var viewResult = this.razorViewEngine.GetView(null, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
var viewDictionary =
new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary()) { Model = model };
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, this.tempDataProvider),
sw,
new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
}
One important think here: if you use view compilation (pre-compiling views to YourProject.Web.PrecompiledViews.dll) then it is important to get the view using the GetView method instead of FindView. More information here.
Generate the PDF file from HTML using PhantomJS
For this task we are going to use a headless browser which will render the HTML (with all CSS and JS included in it). There are many such tools but I will use PhantomJS (headless WebKit scriptable with a JavaScript API). PhantomJS can save the rendered page to small-sized PDF pretty fast. For the PDF export to work we are going to need a .js file which will use the PhantomJS API to tell the tool that we want to export the file:
"use strict";
var page = require('webpage').create(),
system = require('system'),
address,
output;
console.log('Usage: rasterize.js [URL] [filename] [paperformat]');
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = { format: system.args[3], orientation: 'portrait', margin: '0.5cm' };
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
The next thing is to run the phantomjs.exe process and pass the rasterize.js file along with paths for the HTML file and the output file name for the PDF result. This is done in HtmlToPdfConverter.cs:
public interface IHtmlToPdfConverter
{
byte[] Convert(string htmlCode);
}
public class HtmlToPdfConverter : IHtmlToPdfConverter
{
public byte[] Convert(string htmlCode)
{
var inputFileName = "input.html";
var outputFileName = "output.pdf";
File.WriteAllText(inputFileName, htmlCode);
var startInfo = new ProcessStartInfo("phantomjs.exe")
{
WorkingDirectory = Environment.CurrentDirectory,
Arguments = string.Format(
"rasterize.js \"{0}\" {1} \"A4\"",
inputFileName,
outputFileName),
UseShellExecute = true,
};
var process = new Process { StartInfo = startInfo };
process.Start();
process.WaitForExit();
var bytes = File.ReadAllBytes(outputFileName);
File.Delete(inputFileName);
File.Delete(outputFileName);
return bytes;
}
}
If you are going to deploy your application in Azure it is important to have UseShellExecute set to true.
Use the code together
Since we now have implemented both IViewRenderService and IHtmlToPdfConverter we can start using them by first register them in the Startup.cs file where your ConfigureServices method should be located (services.AddScoped<IViewRenderService, ViewRenderService>() and services.AddScoped<IHtmlToPdfConverter, HtmlToPdfConverter>()). Now lets see the code wrapped up together:
private readonly IViewRenderService viewRenderService;
private readonly IHtmlToPdfConverter htmlToPdfConverter;
public DashboardController(
IViewRenderService viewRenderService,
IHtmlToPdfConverter htmlToPdfConverter)
{
this.viewRenderService = viewRenderService;
this.htmlToPdfConverter = htmlToPdfConverter;
}
[HttpGet]
public async Task<IActionResult> GetPdf(SomeInputModel input)
{
var model = this.GetViewModel(input);
var htmlData = await this.viewRenderService.RenderToStringAsync("~/Views/Dashboard/GetPdf.cshtml", model);
var fileContents = this.htmlToPdfConverter.Convert(htmlData);
return this.File(fileContents, "application/pdf");
}
Since ASP.NET MVC has no built-in PDF functionality, the only way to convert a Razor view to PDF without a third party tool would be to write the conversion code yourself. This would be a massive undertaking and almost certainly not worth the effort. I would start by purchasing the ISO 32000-1 reference document and learning everything I could about how the PDF format works. At the time of this answer, the cost is around 200 Swiss francs.
MVC4 - even if it is the name of your Frameworks should be understood as the pattern called Model-View-Controller (MVC). The Idea of this pattern is to separate the these three components in order to replace them with different channels whenever reqeuired.
In your case, the Razor Template is a tool to generate a VIEW in HTML. Razor is limited to HTML - your pattern isn't. By design of MVC you can use any other tool to replace the VIEW from a HTML to a PDF, XML or whatever you desire.
what you should be looking for is a way, to use your existing MODEL (not VIEW) and generate a PDF output, using any library needed.
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
I have a view which contains an MvxListView and a form. I can hide the softkeyboard using the following code in my view's code (as this is pure view concerns)
var editText = FindViewById<EditText>(Resource.Id.newCheckbookName);
editText.EditorAction += (object sender, TextView.EditorActionEventArgs e) =>
{
if (e.ActionId == ImeAction.Done)
{
InputMethodManager inputMgr = GetSystemService(InputMethodService) as InputMethodManager;
inputMgr.HideSoftInputFromWindow(CurrentFocus.WindowToken, 0);
}
return;
};
In my item template I have an other text editor and would like to have the same behavior. But where can I add my code as I don't have any view code for the MwxItemTemplate ?
I think the easy way to do this is to use a custom 'View' within the listitem.
Note: that 'View' here refers to Android Views - not Model-View-ViewModel views - sorry for the naming confusion!
Creating custom views is easy to do...
Just create a custom View - e.g.
namespace Angevelle.App1.UI.Droid.Controls
{
public class MyText : EditText
{
public MyText(Context context, IAttributeSet attrs)
: base(context, attrs)
{
this.EditorAction += OnEditorAction;
}
private void OnEditorAction(object sender, EditorActionEventArgs editorActionEventArgs)
{
if (editorActionEventArgs.ActionId == ImeAction.Done)
{
// this code not tested - but something like this should work
var imm = (InputMethodManager)Context.GetSystemService(Context.InputMethodService);
imm.HideSoftInputFromWindow(WindowToken, 0);
}
}
}
}
Then you can use that View in your AXML just as you do Android or Mvx views:
<angevelle.app1.ui.droid.controls.MyText
android:layout_height=....
/>
If you are finding angevelle.app1.ui.droid.controls too verbose, then you could shorten this using an abbreviation in setup.cs:
protected override IDictionary<string, string> ViewNamespaceAbbreviations
{
get
{
var abbreviations = base.ViewNamespaceAbbreviations;
abbreviations["Abv"] = "angevelle.app1.ui.droid.controls";
return abbreviations;
}
}
then you can just use:
<Abv.MyText
android:layout_height=....
/>
An alternative approach might be to somehow customise the list...
If you ever do need to completely customise a listview and its adapter, then this can be easily done using the same type of technique - inherit from MvxBindableListView in your UI project:
public class MyListView : MvxBindableListView
{
public MyListView(Context context, IAttributeSet attrs);
: base(context, attrs, new MyListAdapter(context))
{
}
}
where MyListAdapter overrides the view creation:
public class MyListAdapter : MvxBindableListAdapter
{
public MyListAdapter(Context context)
: base(context)
{
}
// put your custom Adapter code here... e.g.:
protected override MvxBindableListItemView CreateBindableView(object source, int templateId)
{
return new MySpecialListItemView(_context, _bindingActivity, templateId, source);
}
}
where MySpecialListItemView inherits from MvxBindableListItemView but adds your own custom features.
Using this approach your list would then change from:
<Mvx.MvxBindableListView
....
/>
to:
<Abv.MyListView
....
/>
For more examples of custom views, take a look around GitHub - e.g. at some of the Calendar, ColorPicker, ActionBar projects in https://github.com/Cheesebaron
Don't expect your custom controls to render in the xamarin designer (well, not yet...)
Two final notes...
To reuse code... you might want to put that HideSoftInputFromWindow functionality in an extension method somehow so that you can just call anyEditText.HideOnDone()
Be careful when using the Monodroid/monotouch events on Views/UIViews - these events tend to use the native delegates/listeners - and so sometimes you can find that attaching something to subscribe to one event can unattach something else! Generally you are OK as long as you don't mix and match the C# event subscriptions at the same time as the native listener/delegate handlers.