Klett Con Gusto A2 Vokabular App

Wenn ihr euer Spanisch-Vokabular stets dabei haben wollt, um im ÖV, oder wo auch immer, lernen zu können, habe ich eine Überraschung für euch.
Da der Klett-Verlag für Ihr 2. Buch in der Con Gusto-Reihe; Con Gusto A2, noch keine App veröffentlicht hat, habe ich selber eine für Android geschrieben. Mit der App könnt ihr Auswählen welche Lektionen ihr lernen möchtet und euch auch die spanische Aussprache anhören. Gelernt wird das Vokabular mittels Boxen-Prinzip. Habt ihr ein Kärtchen richtig übersetzt, kommt es in die nächste Box und wird nicht mehr abgefragt, bis ihr fast alle Kärtchen der Box 1 durch habt usw.

Am besten ihr installiert die App auf euer Android Smartphone und seht selbst :)

Zur Con Gusto A2 Vokabular App.

Klett wird übrigens diesen Sommer eine App für das Con Gusto A2-Buch liefern. Meine App ist also mehr eine Übergangslösung :)

HTML markup in activation alert of Magnolia CMS

I recently ran into a problem I’d like to quickly outline in this post. At some point after activating a page in Magnolia the activation alert started to display HTML markup appended to the activation message. The activation alert looked like this:

Activation Alert

Magnolia CMS activation alert

After some diving into the frontend code we found the lines where the error got triggered. The problem came from the function mgnlAlertCallbackMessage(html) in the tree.js which tries to extract the activation message from the html markup.

function mgnlAlertCallbackMessage(html) {
 var messagePattern = '<input type="hidden" id="mgnlMessage" value="';
 // check if alert message was sent back
 if (html.indexOf(messagePattern) != -1) {
 var start = html.indexOf(messagePattern);
 var end = html.indexOf("\" />", start);
 var msg = html.slice(start + messagePattern.length, end);
 alert(msg);
 html = html.slice(end);
 }
 return html;
}

First this function gets the start index, which is 0, and then tries to get the end index with the help of the end tag markup which is defined like this:

" />

Unfortunately the Apache module mod_pagespeed strips unnecessary whitespace from the html source. And exactly this end tag markup which the javascript is looking for, contains such an unnecessary whitespace. So after mod_pagespeed runs through the code the end tag looks like this:

"/>

The problem now is that the function mgnlAlertCallbackMessage(html) can’t retrieve the end index of the input tag, meaning it results in -1. Therefore the slice function doesn’t slice the string but takes the whole string starting at the previously fetched start index.

I’d say that disabling the whitespace filter in the mod_pagespeed.conf would be enough:

#ModPagespeedEnableFilters collapse_whitespace

but unfortunately it didn’t work for me. I had to disable the whole module with:

ModPagespeed off

Change gitflow settings in SourceTree

After setting up gitflow in SourceTree I realized that it calls its development branches develop by default. Since our development branch is called development I was wondering where I could reconfigure gitflow. There is no option for this in SourceTree itself. If you want to remap branch names in gitflow you have to do it via Git’s configuration file.

The config file is located in the .git folder of your repository:

  • /.git/config

Information about submodules and branches is saved to this file. And gitflow also creates a few entries here:

[gitflow "branch"]
        master = master
        develop = develop
[gitflow "prefix"]
        feature = feature/
        release = release/
        hotfix = hotfix/
        support = support/
        versiontag =

You can see that gitflow mapped its develop branch usage to a branch named the same.

  • develop = develop

I can now change this entry to the following and git flow will start using the branch called development for its workflows.

  • develop = development

Save the file and go back to SourceTree. You’re done.

Magnolia Form Confirmation Mail

If you want to create a form which sends the user a confirmation mail, you can configure this behaviour in the last tab Confirmation E-Mail of the form settings dialog. Now for the confirmation mail to really be sent you also need to activate the sendConfirmationEMail form processor. It is disabled by default.

To enable the sendConfirmationEMail form processor do the following:

  1. Navigate to the form processor
    • Magnolia 4.4.x /modules/form/paragraphs/form/formProcessors/sendConfirmationEMail
    • Magnolia 4.5.x /modules/form/templates/components/form/formProcessors/sendConfirmationEMail
  2. Add a new property called enabled and set its value to true
  3. Set its type to Boolean

Your confirmation mails will now be sent. You can find more info about the form module and other form processors in the documentation.

Shared FreeMarker variables in Magnolia CMS

To introduce a new global variable like stkfn or cmsfn into your FreeMarker templates you can add a new entry to the Magnolia shared variables configuration.

  1. In AdminCentral go to Cofiguration | server | rendering | freemarker | sharedVariables
  2. Create a new content node for example myfn (your functions will be called through this name)
  3. Create a new property class below the myfn node and set the value of the property to your class name including the packages. For example ch.crisi.MyTemplatingFunctions.class.

magnolia_shared_variables

From now on you’re able to call all the methods of your MyTemplatingFunctions class from  freemarker.

public class MyTemplatingFunctions {
    
    public String helloWorld() {
        return "Hello World!";
    }

}
${myfn.helloWorld()}

Media Center with Asrock VisionX and XBMC on Windows 7

This post shows you how to setup and configure your XBMC Media Center on a Windows 7 HTPC.

The Hardware

My XBMC runs on an Asrock VisionX which costs about CHF 960 and is available in black and white. There’s also a cheaper model called Asrock VisionHTwhich costs about CHF 760. The difference between these two models is that the Asrock VisionX ships with a AMD Radeon HD7850M (1 GB GDDR5 VRAM) and the Asrock VisionHT with an integrated Intel HD Graphics 4000. The package contains the following extras:

  • Windows Media Center Remote (we will use the built-in IR-receiver with the Logitech Harmony)
  • Full version of PowerDVD (since XBMC does not support real-time Bluray playback)
  • Driver installation CD
  • Power cable

Below you see the Asrock VisionX interfaces:

The Asrock Vision products come with a 2.5″ 750GB HDD and also has a 2nd slot for another SATA3 HD. Since I store all my media on my Synology DS212J NAS I used the free slot to install a Crucial m4 SSD 128GB on which I’ll install Windows 7. To install the 2nd HD just follow the provided instructions.

Remote managing the HTPC

If you have no mouse and keyboard plugged in to your HTPC you should at least be able to manage it remotely. There are several options for this. Here are two suggestions:

  • Windows Remote Desktop
  • RealVNC

Windows Remote Desktop

Windows Remote Desktop is included in every Windows installation and can be used without a big setup. Just be sure to enable the Remote Desktop Firewall exceptions on the HTPC under Start | Control Panel | System and Security | Windows Firewall | Allow a program or feature through Windows Firewall.

Now you will be able to login to your HTPC remotely. Just click on the Windows Start-Button, type Remote Desktop and click on Remote Desktop Connection. If you have trouble doing so please see the Windows Remote Desktop Connection FAQ. There’s even a video which shows how to do it exactly. Note: Windows Remote Desktop logs you off Windows on the HTPC when connecting to it. So it is not suited to demonstrate something to your friends which are looking at the TV.

RealVNC

RealVNC is able to mirror the Windows Desktop and does not log you off Windows to do so. There’s a free edition of RealVNC which does not allow you to encrypt the connection between the PCs though. For more information on RealVNC and it’s features check their website. To set it up have a look at their documentation.

Configuring Windows

Autologon

After installing Windows 7 we will configure it to automatically log in even if the user has a password.

  1. Press [Win] + [R]
  2. Enter netplwiz
  3. Click on the user you’d like to automatically log in
  4. Uncheck the box Users must enter a user name and password to use this computer.
  5. Click OK

Mapping network drives

Since my media is stored on my Synology NAS I created the following batch file to connect the network drives at startup. Create a file called xbmc_shares.bat paste the content below into it and place a shortcut to the file inside startup folder C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup.

@echo off
 net use * /del /yes
 net use w: \\nas\music /USER:pcname\user password
 net use x: \\nas\photos /USER:pcname\user password
 net use y: \\nas\series /USER:pcname\user password
 net use z: \\nas\movies /USER:pcname\user password

This is useful for easier access in XBMC and if you have HDD Hibernation enabled on your NAS because it will wake up the HDD if it is sleeping before you try to play a movie from XBMC. If you try to play a movie from a sleeping HDD XBMC will tell you that the file is not available and ask if you want to delete the library entry until the HDD is awake.

Installing and configuring XBMC

Go to the XBMC website and download the latest stable release. Follow the installation instructions, startup XBMC.

Adding media

To add media to the XBMC library follow these instructions.

Tip: When configuring the TV Shows scraper enable Prefer Posters in the settings. Your series library will look nicer than with banners. For this to work the setting Skin | General | Use “Posters” instead of “Banners” for TV Shows has to be enabled too.

You can change the poster by right clicking the current series poster, selecting TV show information and then clicking Get thumb.

XBMC Settings

Below are my personal settings.

  • Appeareance | Skin | Skin -> Confluence
  • Appeareance | Skin | Show RSS news feeds -> OFF
  • Video | DVDs | Play DVDs automatically -> ON
  • Video | DVDs | Attempt to skip introduction before DVD menu -> ON
  • Skin | General | Use “Posters” instead of “Banners” for TV Shows -> ON
  • Skin | Home Window | Show weather info -> OFF
  • Skin | Home Window | Hide videos -> ON
  • Skin | Home Window | Hide movies -> OFF
  • Skin | Home Window | Hide TV shows -> OFF

XBMC startup

Download the XBMC Launcher from here and install it according to your needs. Check this post for an explanation of the install options. After the installation XBMC Launcher will be configured to repeatedly request focus of XBMC every 10 seconds. To temporarily disable this and be able to configure the launcher press [Win] + [F9]. I configured my launcher to startup XBMC with Windows and to request its focus only once after 10 seconds in case it gets minimized because of other starting services.

For people looking to minimize the boot up time you can replace the Windows Shell with XBMC Launcher. Windows will then directly start XBMC without starting the Explorer first. After exiting XBMC Windows will normally start the Explorer. For this to work you have to enable the following two options in the XBMC Launcher:

  • Start Windows Explorer when XBMC is closed
  • Change Windows Shell

XBMC Addons

Subtitles

Remote controls for XBMC

Logitech Harmony

Since the Asrock VisionX comes with a Windows Media Center remote we already have an IR-Receiver and only need to configure the Logitech Harmony Remote. To setup the remote follow these steps.

Yatse Android Remote

My personal favorite is the Android application Yatse. For this to work enable the following setting in XBMC:

  • System | Settings | Network | Services | Allow control of XBMC via HTTP

If you have trouble setting it up check out their wiki.

Logitech DiNovo Mini

The Logitech DiNovo Mini comes with 2 modes. One media center mode with arrow keys for easy navigation inside XBMC and one mode with a touch pad to easily navigate in Windows. The keyboard works right out of the box with XBMC. There is no configuration needed.

If you want to remap the MediaCenter-Key on the bottom right of the keyboard take a look at http://uberoptions.net.

Magnolia Workflow: Passing parameters from XML to Java and contrariwise

There may come a day when you’re in need of letting the workflow author be able to decide which input values should go into your custom command and also let him decide how to act on the output value you inject back into the workflow xml. Thanks to this article you’ll be perfectly prepared in case of emergency.

<!-- set an input parameter in the workflow xml -->
<set field="myField" value="myValue"/>
 
<!-- call custom command in the workflow xml -->
<participant ref="command-catalogName-myCommand"/>
// in your java command class get the input param of the workflow
String myField = (String) ctx.get("myField");

// do some processing
 
// inject result into the workflow xml
ctx.put("myCustomField", "myCustomValue");

<!-- access the injected command result in the workflow xml -->
${field:myCustomField}

<!-- or maybe wrap it into a conditional -->
<if test="${field:myCustomField}">
    <!-- do if myCustomField is true -->
    <!-- do if myCustomField is false -->
</if>

<if test="${field:myCustomField} == someString">
    <!-- do if comparison is equal -->
    <!-- do if comparison is not equal -->
</if>

You can find further resources below:

Magnolia Node Types

Since I always have to look up which node type I’m looking for, I wrote this post hoping that this information now will remain in my memory :D

Most of the times you can either filter by using a String like mgnl:content or the static fields of the class info.magnolia.cms.core.ItemType like ItemType.CONTENT.

Sometimes you also have to wrap the type with a info.magnolia.cms.util.NodeTypeFilter for example when using info.magnolia.cms.util.ContentUtil#visit.

                ContentUtil.visit(content, new ContentUtil.Visitor() {
                    @Override
                    public void visit(Content node) throws Exception {
                        //To change body of implemented methods use File | Settings | File Templates.
                    }
                }, new NodeTypeFilter(ItemType.CONTENT));

Or when writing a Task and you’are extending from info.magnolia.module.templatingkit.setup.VisitAllChildrenOperation.

For more information about all the different node types there exist check this link.

Bidirectional Dialogs in Magnolia CMS

For an arabic or hebrew website it would be convenient to have some of the dialog controls being read and written from the right to the left. Here I show you how to mirror the Magnolia edit control. The labels and other controls stay the same.

The principle behind this modification is as follows: Everytime an edit field appears in the dialog, we check the locale and depending on it instantiate the regular info.magnolia.cms.gui.control.Edit control or our custom one, called RTLEdit.

For this to work we have to reset the class property of the control under /modules/adminInterface/controls/edit to my.package.BidirectionalDialogEdit.

The class content looks as follows:

public class BidirectionalDialogEdit extends DialogEdit {

    List<String> rtlLanguages = Arrays.asList(new String[]{"ar", "iw", "he"}); // he = iw = hebrew, ar = arabic

    @Override
    public void drawHtml(Writer out) throws IOException {
        Edit control = getLocaleDependentEditControl();

        //rest of this method is copied from info.magnolia.cms.gui.dialog.DialogEdit.drawHtml()
        control.setType(this.getConfigValue("type", PropertyType.TYPENAME_STRING));
        if (this.getConfigValue("saveInfo").equals("false")) {
            control.setSaveInfo(false);
        }
        control.setCssClass(CssConstants.CSSCLASS_EDIT);
        control.setRows(this.getConfigValue("rows", "1"));
        control.setCssStyles("width", this.getConfigValue("width", "100%"));
        if (this.getConfigValue("onchange", null) != null) {
            control.setEvent("onchange", this.getConfigValue("onchange"));
        }
        this.drawHtmlPre(out);
        out.write(control.getHtml());
        this.drawHtmlPost(out);
    }

    private Edit getLocaleDependentEditControl() {
        Edit control;
        String localeString = MgnlContext.getAttribute("mgnlLocale").toString();
        String language = StringUtils.substringBefore(localeString, "_");

        if (rtlLanguages.contains(language))
            control = new RTLEdit(this.getName(), this.getValue());
        else
            control = new Edit(this.getName(), this.getValue());

        return control;
    }
}

The really new thing here is the method getLocaleDependentEditControl() which checks for the locales “ar”, “iw” and “he”. If such a locale is found I instantiate the RTLEdit class. This class overrides the getHtml() method of it’s superclass and inserts the dir attribute as follows: dir="rtl". This will mirror the rendered text box in the dialog.

public class RTLEdit extends Edit {

    public RTLEdit() {
        super();
    }

    public RTLEdit(String name, Content websiteNode) {
        super(name, websiteNode);
    }

    public RTLEdit(String name, String value) {
        super(name, value);
    }

    @Override
    public String getHtml() {
        StringBuilder html = new StringBuilder(super.getHtml());
        html.insert(html.indexOf(" "), " dir=\"rtl\"");

        return html.toString();
    }
}

The final result looks like this:

For more information on RTL-Webpages in Magnolia see this wiki page.

Custom control CategoryCheckboxes in Magnolia CMS

In this post I’ll show you how I created a custom control called “CategoryCheckboxes”. It displays all categories of a selected data-folder as checkboxes, to easily select and save them. This control may also replace the categorization tab /modules/categorization/dialogs/generic/tabCategorization which is delivered with the categorization module, as it saves the selected categories exactly the same way.

For our custom control we only need one new class as we will use an existing save handler to save the selected categories. With the 2 control properties controlType and saveHandler we will refer to our custom control class and to the save handler info.magnolia.module.admininterface.MultiValueSaveHandler. The control class tells Magnolia how to render the control. The save handler class tells Magnolia how to save the selected categories.

The control setup will be as follows:

So, let’s start with our control class CategoryCheckboxesControl.java

public class CategoryCheckboxesControl extends DialogBox {
 
    private Content categoryFolder;
    private List<Content> existingCategoriesInJcr = new ArrayList<Content>();
 
    @Override
    public void init(HttpServletRequest request, HttpServletResponse response, Content storageNode, Content configNode) throws RepositoryException {
        super.init(request, response, storageNode, configNode);
         
        setLabel("Categories");
        setOptions(ListUtils.EMPTY_LIST);   
    }
 
    @Override
    public void setOptions(List options) {
        Content siteRoot = null;

        if (newParagraph()) {
            Content currentContent = ContentUtil.getContent(ContentRepository.WEBSITE, MgnlContext.getParameter("mgnlPath"));
            siteRoot = getSiteRoot(currentContent);
        } else {
            siteRoot = getSiteRoot(getStorageNode());
            existingCategoriesInJcr = CategoryUtil.getCategories(getStorageNode(), CategoryUtil.DEFAULT_STORAGE_NAME);
        }
        
        String categoryFolderUuid = NodeDataUtil.getString(siteRoot, "availableCategories");
        categoryFolder = ContentUtil.getContentByUUID("data", categoryFolderUuid);
    }

    private boolean newParagraph() {
        return getStorageNode() == null;
    }
 
    private Content getSiteRoot(Content content) {
        try {
            Content root = TemplateCategoryUtil.findParentWithTemplateCategory(content, TemplateCategory.HOME);
            if (root == null) {
                return content.getAncestor(0);
            }
            return root;
        } catch (RepositoryException e) {
            throw new RuntimeException("Can't access site root.", e);
        }
    }
 
    @Override
    public void drawHtml(Writer out) throws IOException {
        drawHtmlPre(out);
        out.write(drawCheckboxes());
        drawHtmlPost(out);
    }
 
    private String drawCheckboxes() {
        if (categoryFolder == null)
            return "<p style='color:red'>Please select a news categories folder in the Home Template Settings.</p>";

        StringBuffer htmlBuffer = new StringBuffer();
        if (categoryFolder.hasChildren("category")) {
            renderCheckboxes(categoryFolder, htmlBuffer);
        }

        return htmlBuffer.toString();
    }

    private void renderCheckboxes(Content categoryFolder, StringBuffer htmlBuffer) {
        for (Content category : categoryFolder.getChildren("category")) {
            Button categoryCheckbox = new Button();
            categoryCheckbox.setButtonType(ControlImpl.BUTTONTYPE_CHECKBOX);
            categoryCheckbox.setName("categories"); //has to be the same as the control name, else the custom saveHandler is not found
            categoryCheckbox.setLabel(category.getName());
            categoryCheckbox.setValue(category.getUUID());
            categoryCheckbox.setId(category.getUUID()); //this is needed for the checkbox label to be able to select the corresponding checkbox
            categoryCheckbox.setCssClass("mgnlDialogButtonsetButton");

            for (Content existingCategoryInJcr : existingCategoriesInJcr) {
                if (uuidsAreEqual(existingCategoryInJcr, category))
                    preselect(categoryCheckbox);
            }

            htmlBuffer.append(categoryCheckbox.getHtml());

            if (categoryFolder.hasChildren("category")) {
                htmlBuffer.append("<div style='margin-left:20px'>");
                renderCheckboxes(category, htmlBuffer);
                htmlBuffer.append("</div>");
            }
        }
    }
 
    private void preselect(Button b) {
        b.setState(ControlImpl.BUTTONSTATE_PUSHED);
    }
 
    private boolean uuidsAreEqual(Content c1, Content c2) {
        return c1.getUUID().equals(c2.getUUID());
    }
}

As I commented in the setOptions() method, you are free to implement any way of fetching the uuid of a folder containing categories.

With the save handler property set to info.magnolia.module.admininterface.MultiValueSaveHandler the selected checkboxes are saved the way the categorization tab control would do it. Like that we can change between those 2 components without any complications. The save handler will save the selected categories as comma separated uuid’s in a property called categories.

UPDATE
The control now supports sub categories and will display them as a tree. See Screenshot below.

Our final control will then look like this: