As you already imagined, migrating code to the recommended servlet thread is a nightmare. Anyway, since ZK deprecated the event thread, I thought reasonable to start the migration now. Here are some issues I encountered while doing so.
Are you capturing the onClose
event of a container to prevent user from exiting without saving its content?
This piece of code does capture the event, ask user if really want to exit loosing modifications. If user says no, the event method stopPropagation()
is called and the tab remains open.
Tab tab = container.getLinkedTab(); tab.addEventListener(9000, Events.ON_CLOSE, new EventListener() { @Override public void onEvent(Event event) throws Exception { if(modified) { if(!ZkossUtils.confirmMessage(ElLabel.getLabel("lCloseWithoutSaving"))) { event.stopPropagation(); } } } }); ... public static boolean confirmMessage(String message) { if(Messagebox.show(message, ElLabel.getLabel("lQuestion"), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION) == Messagebox.OK) { return true; } return false; }
With servlet thread this code no longer works. The method stopPropagation()
gets never called. Messagebox.show()
returns immediately and there is no chance that confirmMessage()
returns true.
We need to make some changes. In the example I choose commons-chain
, but Runnable
is also a choice.
final Tab tab = container.getLinkedTab(); tab.addEventListener(9000, Events.ON_CLOSE, new EventListener() { @Override public void onEvent(final Event event) throws Exception { if(modified) { // check whether tab content has been changed event.stopPropagation(); ZkossUtils.confirmMessage(ElLabel.getLabel("lCloseWithoutSaving"), new Command() { @Override public boolean execute(Context context) throws Exception { tab.close(); return Command.PROCESSING_COMPLETE; } }); } } }); ... public static void confirmMessage(String message, final Command command) { Messagebox.show(message, ElLabel.getLabel("lQuestion"), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION, new EventListener () { @Override public void onEvent(Event event) throws Exception { Context ctx = new ContextBase(); switch ((Integer) event.getData()) { case Messagebox.OK: command.execute(ctx); break; } } }); }
First modification is to use Messagebox's EventListener. There we put the code we want to execute when the OK button gets clicked.
The onClose
event has changed some more. Fist, we call stopPropagation()
when modified
is true. This ensures the tab will remain open. Then, we close the tab. Doing so programmatically prevents from entering an endless loop. Notice that in real-world applications, the tab may have its own listeners and perform some stuff.
Setting the modal state. With the event thread enabled you could do setMode("modal")
before attaching the window to the page. With the servlet thread that's no longer true. It will throw a not attached exception
. So, if you set the window mode somewhere before setPage(page)
, simply remove the call to setMode()
and use doModal()
once the page has been set.
It was 2008 and I needed a wiki editor for Elephant-Wiki syntax. The JavaScript source code can be located here editor.txt. For those interested, there is a wiki-bar
, coded into a JSP file, which was sensible of caret position as to interact with the user. The code, conveniently renamed to TXT, is here wikiBar.txt
Was quite an effort and never since I have been able to find time to improve it.
Early this year, 2015, BrightSide migrated from ZK6 to ZK7. As usual, guys at Potix had made a great work. ZK7 is fast, slim and a good looking piece of software. Could it be the moment to give a close review to Elephant-Wiki editor?
Before trying to write an editor, you have to make sure you'll have what it needs to start with.
You might find this list really short for what I'm pretending to do. What about the events, moving data from client to server and so on? Here is where ZK makes difference. I'm not worried at all.
Let's use a simple Textbox
for our editor. By default, Textbox
does not inform of where the caret is. Thus, we are going to use a trick that was first pointed at ZK forums. Transform a JavaScript event into a ZK event and pass through selection values. That is, selection start and selection end. When both values are the same, there is the caret position. When values differ, then they point to selected text.
wikibox.setWidgetListener("onBlur", "zAu.send(new zk.Event(this," onCaret" ,zk(this.$n()).getSelectionRange()+" " ,{toServer:true}));");
This example uses the onBlur event to send an onCaret to ZK Textbox component. The event's data is the selection range. The string has this format "selectionStart,selectionEnd
", for example "0,0"
.
On the server side, we need a listener for the onCaret event.
@Listen("onCaret = *") public void onCaret(Event event) { String v[] = ((String) event.getData()).split(","); selectionStart = Integer.valueOf(v[0]); selectionEnd = Integer.valueOf(v[1]); }
The example uses two variables to store selection range for future uses.
Textbox component has methods that will make things really easy. To ensure the example interesting we'll suppose the user has clicked on the Bold
button and wiki syntax for bold text is to surround the value with double asterisk. The example uses wikibox
variable as the Textbox
component.
@Listen("onClick = #bold") public void onBold() { wikibox.setSelectedText(selectionStart, selectionStart, "**", false); selectionStart += "**".length(); selectionEnd += "**".length(); wikibox.setSelectedText(selectionEnd, selectionEnd, "**", false); wikibox.setSelectionRange(selectionStart, selectionStart); wikibox.setFocus(true); Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null)); }
If there is no text selected, the text "
will be inserted, with caret positioned exactly in the middle, as to start typing. If some text is selected, the result will be this text surrounded by double asterisk, as in "
.
Looking at the code, line by line, the setSelectedText
method, inserts "
at selection range's start. Then increases variables so they point to the same location at text. The next setSelectedText
inserts "
at selection range's end. If there was no text selected, then the four asterisk will stay together, otherwise will surround the selection. Calling setSelectionRange
method ensures the caret position will be exactly after the first two asterisk. The method setFocus
, as it reads, put focus on the editor. Last line posts an onChange event, to inform composer that something in editor has changed.
In order to give an appearance to our editor we need some zul code and wrap the above examples into a class. We start with the zul code at file wikiInput.zul
.
<zk> <menubar> <menuitem id="bold" iconSclass="z-icon-bold"/> </menubar> <textbox id="wikibox" multiline="true" tabbable="true" style="-moz-tab-size:4;-o-tab-size:4;tab-size:4;width:100%;height:100%;"/> </zk>
The code is quite simple. The reason why I prefer a menubar
instead of a toolbar
will be more evident when you see the real-world example. For now, it makes no difference what the bold
element is. The important things to remember are the id
attributes, which are wired to ZK components in the Java class at server-side.
See now the Java class WikiEditor
.
public class WikiEditor extends Div implements IdSpace { private int selectionStart, selectionEnd; @Wire private Textbox wikibox; public WikiEditor() { Executions.createComponents("/WEB-INF/_zul/comps/editor/wikiInput.zul", this, null); Selectors.wireComponents(this, this, false); Selectors.wireEventListeners(this, this); wikibox.setWidgetListener("onBlur", "zAu.send(new zk.Event(this," onCaret" ,zk(this.$n()).getSelectionRange()+" " ,{toServer:true}));"); wikibox.addEventListener(Events.ON_CHANGE, new EventListener() { @Override public void onEvent(InputEvent event) throws Exception { Events.postEvent(WikiEditor.this, event); } }); } @Listen("onClick = #bold") public void onBold() { wikibox.setSelectedText(selectionStart, selectionStart, "**", false); selectionStart += "**".length(); selectionEnd += "**".length(); wikibox.setSelectedText(selectionEnd, selectionEnd, "**", false); wikibox.setSelectionRange(selectionStart, selectionStart); wikibox.setFocus(true); Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null)); } }
The magic of making Java class aware of zul code is performed in the first three lines of the constructor. Notice that we indicate where to find the zul file in the createComponents
method.
Now you can put the component in your zul pages like this, changing the package org.turro
for this where you put the class.
<div width="100%" height="100%" use="org.turro.WikiEditor"/>
As promised, here is the real-world zul page.
<zk> <style> .font14px * { font-size: 14px; } .fontBold * { font-weight: bold; } </style> <menubar sclass="font14px"> <menuitem id="header1" label="H1" class="fontBold"/> <menuitem id="header2" label="H2" class="fontBold"/> <menuitem id="header3" label="H3" class="fontBold"/> <menuitem id="header4" label="H4" class="fontBold"/><menuitem id="bold" iconSclass="z-icon-bold"/> <menuitem id="italic" iconSclass="z-icon-italic"/> <menuitem id="regular" iconSclass="z-icon-text-width"/> <menuitem id="alignleft" iconSclass="z-icon-align-left"/> <menuitem id="aligncenter" iconSclass="z-icon-align-center"/> <menuitem id="alignright" iconSclass="z-icon-align-right"/> <menuitem id="alignjustify" iconSclass="z-icon-align-justify"/> <menuitem id="floatleft" iconSclass="z-icon-chevron-left"/> <menuitem id="floatright" iconSclass="z-icon-chevron-right"/> <menuitem id="ullist" iconSclass="z-icon-list-ul"/> <menuitem id="ollist" iconSclass="z-icon-list-ol"/> <menu iconSclass="z-icon-tint"> <menupopup> <menu id="color" content="#color=#333333" label="${el_label['wbar.color']}"/> <menu id="background" content="#color=#ffffff" label="${el_label['wbar.background']}"/> </menupopup> </menu> <menu iconSclass="z-icon-table"> <menupopup> <menuitem id="thl" label="${el_label['wbar.newheader']}: ${el_label['wbar.left']}"/> <menuitem id="thr" label="${el_label['wbar.newheader']}: ${el_label['wbar.right']}"/> <menuitem id="trl" label="${el_label['wbar.newrow']}: ${el_label['wbar.left']}"/> <menuitem id="trr" label="${el_label['wbar.newrow']}: ${el_label['wbar.right']}"/> <menuitem id="tcl" label="${el_label['wbar.newcolumn']}: ${el_label['wbar.left']}"/> <menuitem id="tcr" label="${el_label['wbar.newcolumn']}: ${el_label['wbar.right']}"/> <menuitem id="tet" label="${el_label['wbar.endtable']}"/> </menupopup> </menu> <menu iconSclass="z-icon-code"> <menupopup> <menuitem id="block" label="${el_label['wbar.createblock']}"/> <menuitem id="span" label="${el_label['wbar.createspan']}"/> <menuitem id="note" label="${el_label['wbar.createnote']}"/> <menuitem id="startcolumn" label="${el_label['wbar.addcolumn']}"/> <menuitem id="endcolumns" label="${el_label['wbar.endcolumns']}"/> <menuitem id="tabulator" label="${el_label['wbar.createtabulator']}"/> <menuitem id="blind" label="${el_label['wbar.createblind']}"/> <menu label="Wiki syntax"> <menupopup> <menuitem id="startwiki" label="${el_label.lStart}"/> <menuitem id="stopwiki" label="${el_label.lStop}"/> </menupopup> </menu> <menu label="Java syntax"> <menupopup> <menuitem id="startjava" label="${el_label.lStart}"/> <menuitem id="stopjava" label="${el_label.lStop}"/> </menupopup> </menu> <menu label="XML syntax"> <menupopup> <menuitem id="startxml" label="${el_label.lStart}"/> <menuitem id="stopxml" label="${el_label.lStop}"/> </menupopup> </menu> </menupopup> </menu> <menuitem id="eraser" iconSclass="z-icon-eraser"/> <menuitem id="image" iconSclass="z-icon-picture-o"/> <menuitem id="link" iconSclass="z-icon-link"/> <menuitem id="preview" iconSclass="z-icon-eye"/> </menubar> <textbox id="wikibox" multiline="true" tabbable="true" style="-moz-tab-size:4;-o-tab-size:4;tab-size:4;width:100%;height:100%;"/> </zk>
WikiEditor.java
WikiElement.java
And finally, the editor while writing this blog.
Fa ja una bona dotzena d'anys que vaig obrir el codi de totes les aplicacions fetes en Java. Les experiències derivades d'aquest fet han estat enriquidores i altament motivants. Per posar alguns exemples:
El punt on vaig: cap d'aquests exemples inclou una relació de proximitat. Cap a Catalunya.
No és un tema que m'hagi preocupat excessivament fins aquests darrers temps. Ara ja sí, tot i pensant en una Catalunya que vol fer coses, i fer-les bé.
És per això que he començat a pensar en com trobar sinergies de proximitat. Usant eines provadament poderoses: el codi lliure, una fundació on es reconeixen els mèrits i uns beneficiaris amb ganes de millorar. Som-hi?
La darrera setmana una aplicació per Twitter a infectat alguns comptes dins el meu grup de contactes. Alguns dirien que això és degut a la bona fe, ingenuïtat, dels usuaris de xarxes socials. Jo crec que és més degut a la mala llet que tenen alguns.
Els dominis de Internet son la primera part de l'adreça que es veu al navegador. Les parts es separen amb el símbol /
. En l'adreça https://twitter.com/following
el domini és twitter.com
. El following
és una part del domini. En aquest cas, el domini és conegut i hi confiem.
Quan es preparen paranys, alguns aprofitats col·loquen el domini que volen suplantar en la part del domini. Per exemple a http://parany.com/twitter.com/following
el domini és parany.com
. El twitter.com
és una part d'aquest domini. Per confiar-hi, hauriem de confiar en parany.com
.
Un altre manera de generar una adreça engany és usant els subdominis. Els subdominis apareixen abans del domini i es separen per un punt. En l'adreça http://twitter.parany.com/following
el domini és parany.com
. El twitter
com a prefix és un subdomini d'aquest domini. Per confiar-hi, hauriem de confiar de nou en parany.com
.
Aquest seria l'arbre de decisions a seguir en cas de dubtes:
I decided to publish Persona implementation mainly because wasn't as easy as explained in Persona site. Also because is lacking of Java code, at least, Java code with no-so-much dependencies.
Follow the instructions found in Quick Setup at Persona site. Notice that the instructions provide best practices for including Persona dependencies. When finished, come back here and prepare for Persona implemented in your Java code.
Lets begin with the easy part, the Java Script code. This is the persona.js file. The example uses JQuery.
/*stands for context path on servlets nomenclature*/ var webRoot = ""; /*persona wants to know who is signed in*/ var currentMail = null; /*for app servers running on different ports*/ var webPort = 80; /*did user signed in without persona*/ var internalSignIn = false; /*should we reload current page */ var reloadSignIn = false; $(document).ready(function() { loadElephant(); if(!internalSignIn) { navigator.id.watch({ loggedInUser: currentMail, onlogin: function(assertion) { $.ajax({ type: 'POST', url: webRoot + '/auth/login', port: webPort, data: {assertion: assertion}, success: function(res, status, xhr) { if(reloadSignIn) { window.location.href = window.location.href; } }, error: function(xhr, status, err) { navigator.id.logout(); } }); }, onlogout: function() { $.ajax({ type: 'POST', url: webRoot + '/auth/logout', port: webPort, success: function(res, status, xhr) { window.location.href = window.location.href; }, error: function(xhr, status, err) { } }); } }); } });
Notice the use of some variables that will make your coding more useful in the long term. OK, now we dive into their use and how to get them initialized: