Liip Blog https://www.liip.ch/en/blog Kirby Thu, 18 Apr 2019 00:00:00 +0200 Latest articles from the Liip Blog en Writing a wrapper block for Gutenberg in WordPress https://www.liip.ch/en/blog/writing-a-wrapper-block-for-gutenberg-in-wordpress https://www.liip.ch/en/blog/writing-a-wrapper-block-for-gutenberg-in-wordpress Thu, 18 Apr 2019 00:00:00 +0200 Since Gutenberg blocks are built as independent components they can be used in every layout and can be combinend however it fits your design.

This is a very common practice nowadays. But sometimes you would like to group a bunch of blocks and change the appearance or behaviour of them at once.

In this tutorial we're going to write a wrapper block which adds a background color around its child blocks.

This is how it will look like when it's finished:

Register new block type

To begin with we need to register a new block type. If you haven't done this before you can use the create-guten-block npm script which helps you bootstrap a new block and sets up an asset build pipeline with Webpack.

const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks

registerBlockType( 'wrapper-block-example/wrapper-block', {
    // Block name. Block names must be string that contains a namespace prefix. Example: my-plugin/my-custom-block.
    title: __( 'Background', 'wrapper-block-example' ), // Block title.
    icon: 'editor-table', // Block icon from Dashicons → https://developer.wordpress.org/resource/dashicons/.
    category: 'layout', // Block category — Group blocks together based on common traits E.g. common, formatting, layout widgets, embed.
    keywords: [
        __( 'Background', 'wrapper-block-example' ),
        __( 'Wrapper Block', 'wrapper-block-example' ),
    ],

    attributes: {
        // Register bgColor attribute to save the chosen color
        bgColor: {
            type: 'string',
        },
    },

    // Will be implemented afterwards
    edit() {
        return null;
    },

    // Will be implemented afterwards
    save() {
        return null;
    },
} );

We register our new block wrapper-block-example/wrapper-block with the registerBlockType function and pass in the block configuration (eg. title, description, etc.).

Additionally we register a block attribute called bgColor. In this attribute we will save the chosen background color when our block is used.

With this our block can already be inserted in the editor:

Edit function of our block

In the edit function of our block we create a SelectControl where the background color can be chosen. This control saves its value to the bgColor attribute when it's changed.

First we need to load the dependencies (at the top of the file):

import classNames from 'classnames'; // Used to to join classes together

const {
    Fragment, // Used to wrap our edit component and only have one root element
} = wp.element;
const {
    InnerBlocks, // Allows it to place child blocks inside our block
    InspectorControls, // We place our select control inside the inspector contorls which show up on the right of the editor
} = wp.editor;
const {
    PanelBody, // A panel where we place our select control in (creates a colapsable element)
    SelectControl, // Our select control to choose the background color
} = wp.components;

Now we can implement the edit function:

edit( { attributes, setAttributes, className } ) {
    const {
        bgColor = '',
    } = attributes;

    return (
        <Fragment>
            <InspectorControls>
                <PanelBody
                    title={ __( 'Background Color', 'wrapper-block-example' ) }
                    initialOpen={ true }
                >
                    <SelectControl
                        label={ __( 'Background Color', 'wrapper-block-example' ) }
                        value={ bgColor }
                        options={ [
                            {
                                value: '',
                                label: __( 'No Background Color', 'wrapper-block-example' ),
                            },
                            {
                                value: 'paleturquoise',
                                label: __( 'Light Blue', 'wrapper-block-example' ),
                            },
                            {
                                value: 'orange',
                                label: __( 'Orange', 'wrapper-block-example' ),
                            },
                        ] }
                        onChange={ ( selectedOption ) => setAttributes( { bgColor: selectedOption } ) }
                    />
                </PanelBody>
            </InspectorControls>
            <div
                className={ className }
                style={ { backgroundColor: bgColor } }
            >
                <InnerBlocks />
            </div>
        </Fragment>
    );
},

We add the background selection inside the inspector controls of the block. With this implementation we're now able to select a background color of our block:

The most important part (next to the SelectControl of course) is the <InnerBlocks /> component which we add at the end of the edit function. This component allows us to place child blocks inside our block. Read everything about InnerBlocks here: https://github.com/WordPress/gutenberg/tree/master/packages/block-editor/src/components/inner-blocks

Save function

In the save function of our block we need to add the <InnerBlocks.Content /> component which passes through the content of its child blocks.

Additionally we add a background-color style property with the chosen color to its wrapper div and set a bg-<chosen-color> class to be able to style the child blocks via CSS (eg. change text color when a dark background is selected). We only render these attributes when a background color is selected.

save( { attributes, className } ) {
    const {
        bgColor = '',
    } = attributes;

    let styles = {};
    let classes = className;

    // Only set attributes when background color is chosen
    if ( '' !== bgColor ) {
        styles = { backgroundColor: bgColor, padding: '10px' };
        // Use classnames library to join all classes together
        classes = classNames( `bg-${ bgColor }`, classes );
    }

    return (
        <div
            className={ classes }
            style={ styles }
        >
            <InnerBlocks.Content />
        </div>
    );
},

Wrap up

As you see writing a wrapper block in Gutenberg is quite simple. All you need to do is using the <InnerBlocks> feature of Gutenberg.

You'll find the complete code of this example packed as a fully functional WordPress plugin here: https://github.com/liip/wrapper-block-example-wp-plugin

]]>
Cognitive UXD: Motivation https://www.liip.ch/en/blog/cognitive-uxd-motivation https://www.liip.ch/en/blog/cognitive-uxd-motivation Thu, 11 Apr 2019 00:00:00 +0200 Based on my last blog post Cognitive User Experience Design - Where Psychology meets UX Design I will focus now on Motivation and describe which role the Hierarchy of Needs plays in design. This raises the following question.

What is the Hierarchy of Needs about?

The most prominent contribution in psychology is the Hierarchy of Needs of Abraham Maslow. The psychologist describes that the human motivation consists of five distinct groups of needs, namely Physiological, Safety, Love/ Belonging, Esteem and Self-actualization. The basic level of needs are at the bottom of the hierarchy and must be ensured first before focusing on higher level needs.

Source: “Hierarchy of Needs” proposed by Abraham Maslow in his paper “A Theory of Human Motivation” (1943).

As soon as the Physiological needs (with breathing, food, water, sex, homeostasis and excretion) are fulfilled, the next higher need Safety (with security of body, of empowerment, of resources, of morality, of the family, of health and of property) should be focused. The Safety level is followed by Love/ Belonging and characterized by friendship, family and sexual intimacy. Achieving this level of needs is already good and the chasm to the next two needs is often difficult to cross. The level of Esteem needs (with its self-esteem, confidence, achievement, respect of others as well as respect by others) defines the fourth level of the hierarchy. On top of the Hierarchy of Needs is the Self-actualization where morality, creativity, problem solving lack of prejudice and acceptance of facts are in the focus.
Maslow argued that if the needs of a prior level are not met, the hierarchy won’t be stable and leads to feelings of stress and anxiety. For example, a good performance in a team at the job (need 3) is not possible when you are starving and your hunger is overwhelming (need 1). That’s why we seek to stabilize the lower-level needs before trying to focus on higher-level needs.

What are the critical comments about the Hierarchy of Needs?

Despite this intuitive perspective, only little evidence for the hierarchical order is given. For instance, in some cultures the social need is the first and most important need. Another interesting extension point of Maslow’s theory is that selfless acts, bravery, charity or the spiritual beliefs should be taken into account too.
Another approach is Maslow’s own expansion of his Hierarchy of Needs in 1970. Accordingly, the Esteem need is followed by Cognitive need, which consists of knowledge and understanding, curiosity, exploration, need for meaning as well as predictability. The next need is Aesthetic where beauty, balance and form are appreciated. Followed by Self-Actualization, which was already part of the first version of Maslow’s Hierarchy of Needs, the top need is Transcendence. This need is characterized by encouraging others in achieving Self-actualization.

How can we apply this psychological knowledge to UX Design?

Applying this knowledge to UX Design, the hierarchy levels are Functionality, Reliability and Usability, followed by Proficiency and on top Creativity.

Source: “Design Hierarchy of Needs” was created by Steven Bradley (2010).

The bottom of Bradley’s Design Hierarchy of Needs corresponds with the Functionality and consists of working as programmed and meet basic functionalities. Functional designs are perceived to be of little value for design. For example, a website loads within a reasonable time. The Functionality needs are followed by the Reliability which is defined by stable and consistent performance. Reliable designs are perceived to be of low value of design. Accordingly, a website that worked one week before should work today too, even when new sections have been deleted or added. The third level Usability is ensured as soon as the design is easy to use and works like we think. Usability design is perceived to be of moderate value, which means that some basic expectations work. For example, a website is easy understandable and has a navigation that works. Proficiency and Creativity form the top of the UX Design Hierarchy of Needs. Thereby, Proficiency contains memorable experience, empowers users and meets a high level of design. It provides suggestions that refer to favorites or advanced search entries. Proficiency is followed by Creativity, which is characterized by personal significance, innovative interaction and aesthetic beauty. A creative design is perceived to be the highest level because it satisfies users and leads to loyal users.

What are the critical comments about the Design Hierarchy of Needs?

Similar as for Maslow’s pyramid, the Design Hierarchy of Needs makes intuitively sense. Nevertheless, the hierarchic order must not be followed strictly. The user might like an aesthetically beautiful website more that doesn’t work reliable than a boring but reliable one. It should, however, be noted, that when a website that doesn’t load, the website itself fails. Therefore, to use the design hierarchy as a guideline to first fulfill needs of the lower-level before focusing on higher-levels is recommended.
As mentioned before, a UX designer needs to consider the different levels of needs and should strive for proficiency and creativity in order to surprise users with delightful services with a great user experience.

Because the topic Motivation is so comprehensive and has such an intensive impact on design I will take you on a short journey in my next blog post and characterize what kind of impact Flow has on design.

]]>
Cognitive User Experience Design - Where Psychology meets UX Design https://www.liip.ch/en/blog/cognitive-user-experience-design-where-psychology-meets-ux-design https://www.liip.ch/en/blog/cognitive-user-experience-design-where-psychology-meets-ux-design Thu, 04 Apr 2019 00:00:00 +0200 Psychology and User Experience Design have a lot in common. The combination of both disciplines is known as Cognitive User Experience Design (short Cognitive UXD). Thereby, the psychology provides valuable concepts that can be applied to design, the way we think, how we design experiences and how we experience designs. The most important concepts are Motivation, Cognition, Perception & Awareness, Emotion and Volition. They all have in common that they are primary human psychological functions.

In the following blog posts I will underline their impact on design.
But before starting, I will describe each concept briefly and their role in design.

Motivation is characterized as the reason why people take actions. It is goal-directed behavior, which requires self-regulatory efforts and differs in duration and intensity. It’s role in design is to meet individual’s needs and to strive for proficiency and creativity in order to surprise users with delightful services with a great experience.

Cognition is the mental effort that is used in the working memory. Cognitive overload leads to more time and effort to complete a task. As a designer we have several strategies to reduce the cognitive overload for users.

Perception & Awareness contain to identify, organize and interpret sensory information. They are heavily affected by Biases and Persuasion, which in turn have an influence on our perception. It’s role in design is also well known as Gestalt Psychology. This knowledge about Gestalt Psychology, Biases and Persuasion combined with the capacities and characteristics of human perception enable us to design better experiences.

Emotion is a mental state that is often linked with mood, temperament, motivation, personality and disposition. Because we take decisions based on our emotional state, the goal is to trigger positive experiences with design. The communication and creation of emotions is a challenge for designers that is worth to give more attention.

Volition, or “willpower”, contains the process from intention to actual behavior. It encompasses a process of forming a purpose (setting a goal and planning) to implementing decisions (organisation and control). Same as for motivation, volition is goal-directed and requires self-regulatory efforts. Nowadays, new approaches indicate that it is a conscious action control which turns into automatized processes.

All those short outlooks in further blog posts illustrate the huge importance Cognitive User Experience Design has. By the end of each blog post you will

  • Understand how Cognitive UXD and each focused topic itself has an impact on User Experience
  • Be able to adopt and analyse Cognitive UXD-driven approach to design
  • Be equipped with knowledge and recommendations of Cognitive UXD to improve the User Experience
  • Have a profound understanding of the Cognitive UXD-driven approach

My next blog post is about Motivation. There, I describe which role the Hierarchy of Needs plays in design.

]]>
The Zentralbibliothek library – more than just a library with a new platform https://www.liip.ch/en/blog/zentralbibliothek-ist-live https://www.liip.ch/en/blog/zentralbibliothek-ist-live Wed, 03 Apr 2019 00:00:00 +0200 From planning to implementation

The relaunch project for the Zentralbibliothek (ZB) library in Zurich has been great fun for us, and has been educational, exciting and diverse all the way from the planning phase to implementation. The planning phase started with a joint workshop where the project strategy was defined and the requirements for the website’s functionalities as well as a draft for the information architecture were discussed. Using what we learnt at this workshop, we began consulting the ZB and started implementing the user-oriented design. The result was a navigation concept with an improved user experience. Thanks to the various different access levels, all target groups have access to the content they need. In addition, the website’s content was completely redesigned and rewritten to take the new concept into account.

Content and development – the ideal pairing

Thanks to the development of a content strategy and the subsequent creation of a content guide, the new website’s layout is user-centric. Users want to experience the website’s content, so content experts, design experts and software experts all worked together to create the most optimal user interface. The library's employees were helped with editing and trained in the new editorial processes. The main idea was to provide comprehensive and easily accessible search functionalities for users. The search portal, technically implemented using CMS October, is the main feature of the new website. The agile working methods employed enabled us to achieve the best possible result.

Integrating the right analysis tools

The ZB uses Google Tag Manager and Google Analytics to comprehensively analyse its new website. These tools also provide employees in the communication department with measures to determine how to optimise their marketing activities. To aid this optimisation, we first ensured that they had the technical standards needed for SEO. ZB employees have also become experts in the continued use of these analysis tools thanks to coaching and training sessions.

Open over closed

We worked well together throughout the project thanks to open communication and numerous exchanges. Existing structures could be broken down and redesigned. A new focus on being user-centric could be implemented both technically and in terms of content as we made use of new approaches for both our collaboration and the implementation of the project. From a technical perspective, we prefer open over closed. This is why we used open source and opted to work with October’s CMS platform and Google’s Analytics infrastructure.
The technical management of the new website will be taken over in full by the ZB’s IT department, which meant that we needed to pass on some of our expertise in the field. We also trained the employees of the communication department in how to keep the content of the website open and how to redesign it.

Liip has helped us very well to think less 'from the inside', from the logic of our organisation, and more from the point of view of our customers. This opened up a new perspective for us, and we managed to communicate our content in a fresh, straightforward way.
Dr. Christian Oesterheld, Direktor ZB Zürich

We are very happy that, thanks to our collaboration with Liip, our new website is designed to meet the needs of our users.
Natascha Branscheidt, Project Manager at the Zentralbibliothek

Working together in a strong team, we have overcome a real challenge and are proud to present our work for the ZB.
Miriam Pretzlaff, Product Owner Liip

]]>
Scrum met Holacracy <3 And they lived happily... https://www.liip.ch/en/blog/scrum-met-holacracy https://www.liip.ch/en/blog/scrum-met-holacracy Fri, 29 Mar 2019 00:00:00 +0100 A little history

Before the adoption of Holacracy in January 2016, we had already developed our own corporate governance system. It was based on the agile project management method Scrum, which we had been "breathing" since 2010. In other words, we had replicated the role structure and work organization at the Scrum throughout the company. This concerned the management of our customer projects, but also the management of our administration. I remember having the role of Scrum Master in the Lausanne office. My main responsibility was to maintain a "framework" in which the Lausanne Liipers could evolve without obstacles. I maintained a "User Stories" backlog related to the development of our office and we organized the processing of these stories in "sprints". I also remember that our admin team had a physical Scrum Board that continuously contained all current and upcoming tasks.

This agile governance system "à la Liip" had worked well for a few years. Until it became too restrictive. It was intended for project management, not for the company's internal processes. We had to constantly "twist" it so that it would continue to work. After 100 employees (including 6 managers, because we still had a hierarchical level at that time), we had to face the facts: this version of Scrum for Liip's governance was no longer efficient enough.

How could we then develop our company organisation in such a way as to sustainably absorb our growth (~20% per year in terms of employees)?
Two paths opened up for us:

  1. Increase the hierarchy: either enlarge the management team or insert intermediate management layers.
  2. Conduct research to find a radically different system.

Having always thought that highly hierarchical systems are dommed to failure, we took the path 2. without too much hesitation.

And we met Holacracy

This research has led us, among other things, to Frédéric Laloux's book "Reinventing Organizations", then to Holacracy. What we immediately liked was the fact that Holacracy conveys a system of very explicit rules, clearly described in a constitution, open source and scalable. After more than ten years of existence, Holacracy also had interesting references (including Zappos and its 1500 employees).

But does Holacracy impact the work I do with my clients?

When we coach other companies that want to adopt Holacracy in their organization, a question often comes up:

Will I have to teach my customers how Holacracy works?

I reassure you right away, the answer is no (and fortunately ;-)). I believe that most of our customers have not been aware of the change. We have been managing all our projects since 2010 with Scrum. Holacracy is a tool that we use internally only, to operate our company.

How Scrum and Holacracy join forces

The use of two highly integrated working methods in a single company may seem dangerous. Looking back, I see that they marry very well. Our organisational chart shows that all the "production" teams have created the basic roles of Scrum: Scrum Master, Product Owner. The purpose and accountabilities of the SM and PO have been taken from the Scrum guide. The teams created also the additional roles they needed: Frontend/Backend/Fullstack developer, DevOps, UX Designer, etc. All these roles continue to work with Scrum on customer projects. They are also found in the Holacracy structure.

Comparison of all iterative processes of the two methods:

Processes Purpose Who is invited Scope
Holacracy
Governance Addressing structural tensions Members of the circle (Liipers) Structure of the circle / company
Tactical (triage) Addressing operational tensions Members of the circle (Liipers) Projects within the circle / company
Scrum
Backlog grooming Take care of the product backlog, so that it is ready for the next sprints The Scrum team (Liipers & client) Client project
Sprint planning Define what will be done in the upcoming sprint The Scrum team (Liipers & client) Client project
Sprint review Demonstrate what has been done in the sprint that is ending The Scrum team (Liipers & client) Client project
Sprint retrospective Inspect and improve the process / structure of the project The Scrum team (Liipers & client) Structure / functioning of the Scrum team

Each of these processes has a specific purpose, needed for the smooth running of projects and for the continuous improvement of the structure. We note however that there could be an overlap between the Holacracy processes and the Scrum retrospective.

The teams (or circles) at Liip manage this relationship by keeping the retrospectives in their form. Not all the topics (or "tensions") that emerge from the retrospective are covered. Tensions regarding internal issues are reported at the next Holacracy tactical meeting of the team in question. This meeting is often scheduled shortly after the retrospective in question.

Keep doing retrospectives!

After 3 years of practice, I find it useful to maintain retrospectives "à la Scrum": through group interaction, they make it possible to bring to the surface subjects to be treated, which we would not necessarily have identified alone.

So my recommendation is this: if you go into Holacracy while practicing Scrum, keep doing retrospectives. And make sure you use Holacracy's solid processes to address the topics to be addressed!

What is your experience? What do you think? I look forward to reading your comment below!

]]>
How to extend existing Gutenberg blocks in WordPress https://www.liip.ch/en/blog/how-to-extend-existing-gutenberg-blocks-in-wordpress https://www.liip.ch/en/blog/how-to-extend-existing-gutenberg-blocks-in-wordpress Thu, 28 Mar 2019 00:00:00 +0100 The new WordPress editor (Gutenberg) comes with lots of pre built blocks (Paragraph, List, Image, etc.). Each block has its own markup which wraps the content of it. This markup is generally generic enough that it works with most of the themes. But there are cases where you would like to change the appearance of these blocks in some way.

Let's say we would like to add a spacing control to the existing image block (core/image) where we can choose between a small, medium or large margin which should be added below the image.

1. Add spacing attribute

Before we can add a custom control to the image block we need a place where we can save the chosen value. In Gutenberg this is done with block attributes.

To add a custom attribute to an existing block we can use the blocks.registerBlockType filter like this:

import assign from 'lodash.assign';
const { addFilter } = wp.hooks;
const { __ } = wp.i18n;

// Enable spacing control on the following blocks
const enableSpacingControlOnBlocks = [
    'core/image',
];

// Available spacing control options
const spacingControlOptions = [
    {
        label: __( 'None' ),
        value: '',
    },
    {
        label: __( 'Small' ),
        value: 'small',
    },
    {
        label: __( 'Medium' ),
        value: 'medium',
    },
    {
        label: __( 'Large' ),
        value: 'large',
    },
];

/**
 * Add spacing control attribute to block.
 *
 * @param {object} settings Current block settings.
 * @param {string} name Name of block.
 *
 * @returns {object} Modified block settings.
 */
const addSpacingControlAttribute = ( settings, name ) => {
    // Do nothing if it's another block than our defined ones.
    if ( ! enableSpacingControlOnBlocks.includes( name ) ) {
        return settings;
    }

    // Use Lodash's assign to gracefully handle if attributes are undefined
    settings.attributes = assign( settings.attributes, {
        spacing: {
            type: 'string',
            default: spacingControlOptions[ 0 ].value,
        },
    } );

    return settings;
};

addFilter( 'blocks.registerBlockType', 'extend-block-example/attribute/spacing', addSpacingControlAttribute );

Let me explain this code step by step:

First we need to define some variables which are used at multiple places in our example.

// Enable spacing control on the following blocks
const enableSpacingControlOnBlocks = [
    'core/image',
];

In the enableSpacingControlOnBlocks array we can list all existing blocks where the spacing control should be available. In our example we just add the core/image block.

// Available spacing control options
const spacingControlOptions = [
    {
        label: __( 'None' ),
        value: '',
    },
    {
        label: __( 'Small' ),
        value: 'small',
    },
    {
        label: __( 'Medium' ),
        value: 'medium',
    },
    {
        label: __( 'Large' ),
        value: 'large',
    },
];

The spacingControlOptions array lists all available options which can be chosen in our spacing control element.

const addSpacingControlAttribute = ( settings, name ) => {
    // Do nothing if it's another block than our defined ones.
    if ( ! enableSpacingControlOnBlocks.includes( name ) ) {
        return settings;
    }

    // Use Lodash's assign to gracefully handle if attributes are undefined
    settings.attributes = assign( settings.attributes, {
        spacing: {
            type: 'string',
            default: spacingControlOptions[ 0 ].value,
        },
    } );

    return settings;
};

This is our callback function where the custom attribute gets registered. We first check if the block is one of our defined blocks in the enableSpacingControlOnBlocks array. If that's the case we add our new attribute spacing to the already existing blocks attributes.

addFilter( 'blocks.registerBlockType', 'extend-block-example/attribute/spacing', addSpacingControlAttribute );

Last but not least we need to add this function to the blocks.registerBlockType filter.

The core/image block now has a new attribute spacing which we can use to save our chosen value.

2. Add spacing control

As a next step we need to create the spacing control element to the blocks InspectorControl.

The result should look like this:

Spacing Control in Inspector controls

Gutenberg provides the editor.BlockEdit hook to extend the blocks edit component (which includes the InspectorControl part).

const { createHigherOrderComponent } = wp.compose;
const { Fragment } = wp.element;
const { InspectorControls } = wp.editor;
const { PanelBody, SelectControl } = wp.components;

/**
 * Create HOC to add spacing control to inspector controls of block.
 */
const withSpacingControl = createHigherOrderComponent( ( BlockEdit ) => {
    return ( props ) => {
        // Do nothing if it's another block than our defined ones.
        if ( ! enableSpacingControlOnBlocks.includes( props.name ) ) {
            return (
                <BlockEdit { ...props } />
            );
        }

        const { spacing } = props.attributes;

        return (
            <Fragment>
                <BlockEdit { ...props } />
                <InspectorControls>
                    <PanelBody
                        title={ __( 'My Spacing Control' ) }
                        initialOpen={ true }
                    >
                        <SelectControl
                            label={ __( 'Spacing' ) }
                            value={ spacing }
                            options={ spacingControlOptions }
                            onChange={ ( selectedSpacingOption ) => {
                                props.setAttributes( {
                                    spacing: selectedSpacingOption,
                                } );
                            } }
                        />
                    </PanelBody>
                </InspectorControls>
            </Fragment>
        );
    };
}, 'withSpacingControl' );

addFilter( 'editor.BlockEdit', 'extend-block-example/with-spacing-control', withSpacingControl );

We first create a Higher Order Component (HOC) withSpacingControl. It receives the original block BlockEdit component which we can extend.

if ( ! enableSpacingControlOnBlocks.includes( props.name ) ) {
    return (
        <BlockEdit { ...props } />
    );
}

We again check if the block is one of our defined blocks in the enableSpacingControlOnBlocks array. If this isn't the case we return the original BlockEdit component.

const { spacing } = props.attributes;

return (
    <Fragment>
        <BlockEdit { ...props } />
        <InspectorControls>
            <PanelBody
                title={ __( 'My Spacing Control' ) }
                initialOpen={ true }
            >
                <SelectControl
                    label={ __( 'Spacing' ) }
                    value={ spacing }
                    options={ spacingControlOptions }
                    onChange={ ( selectedSpacingOption ) => {
                        props.setAttributes( {
                            spacing: selectedSpacingOption,
                        } );
                    } }
                />
            </PanelBody>
        </InspectorControls>
    </Fragment>
);

Otherwise we wrap the original BlockEdit component in a Fragment and add a SelectControl inside the InspectorControls of the block where the spacing can be chosen.

addFilter( 'editor.BlockEdit', 'extend-block-example/with-spacing-control', withSpacingControl );

To make it work we add our withSpacingControl component to the editor.BlockEdit filter.

The spacing control should now be visible in the WordPress editor as soon as you add an image block.

3. Add margin to the saved markup

We can already select a spacing value in the backend but the change is not yet visible in the frontend. We need to use our spacing attribute to render a specific margin below the image block. We can use the blocks.getSaveContent.extraProps filter to do exactly that. With this filter we can modify the properties of the blocks save element. What we will do is map our chosen spacing to a pixel value which we add as margin-bottom to the image block.

/**
 * Add margin style attribute to save element of block.
 *
 * @param {object} saveElementProps Props of save element.
 * @param {Object} blockType Block type information.
 * @param {Object} attributes Attributes of block.
 *
 * @returns {object} Modified props of save element.
 */
const addSpacingExtraProps = ( saveElementProps, blockType, attributes ) => {
    // Do nothing if it's another block than our defined ones.
    if ( ! enableSpacingControlOnBlocks.includes( blockType.name ) ) {
        return saveElementProps;
    }

    const margins = {
        small: '5px',
        medium: '15px',
        large: '30px',
    };

    if ( attributes.spacing in margins ) {
        // Use Lodash's assign to gracefully handle if attributes are undefined
        assign( saveElementProps, { style: { 'margin-bottom': margins[ attributes.spacing ] } } );
    }

    return saveElementProps;
};

addFilter( 'blocks.getSaveContent.extraProps', 'extend-block-example/get-save-content/extra-props', addSpacingExtraProps );

In the addSpacingExtraProps function we create a little mapping object where which we can use to map our selected spacing value (small, medium or large) to an appropriate pixel value.
Afterwards we add this value as margin-bottom to the style attribute of the block.

That's already it! Our selected spacing value gets mapped to an appropriate margin-bottom value and is added to the saved markup of the block.

Wrap up

It's not that hard to extend an existing Gutenberg block. So before creating a new block think about if it would make more sense to extend an existing block to your own needs.

This tutorial is also available as a WordPress plugin on GitHub: https://github.com/liip/extend-block-example-wp-plugin. Feel free to use it as a starting point to extend Gutenberg blocks in your WordPress site.

]]>
How API first CMS disrupts copywriting https://www.liip.ch/en/blog/api-first-cms-disrupts-copywriting https://www.liip.ch/en/blog/api-first-cms-disrupts-copywriting Thu, 28 Mar 2019 00:00:00 +0100 Samuel was our speaker at the Content Strategy Lausanne meetup on March 12th.
Join our community and learn about Content Strategy!

Today content is accessible on various platforms: website, apps, television, social media. With the increasing development of IoT objects, the headless CMS trend is raising. From a copywriting perspective, it is a nightmare to provide custom content for all these channels. Is API first CMS the solution?

When Samuel Pouyt chose to use a headless CMS for the European Respiratory Society, he did not know where he was going. He took the risk of trying a new approach to content management. The API first approach changed his way of working while simplifying it. He made many learnings, some of them because of implementation errors.

Liip: What is an API first CMS?
Samuel: It is a CMS that does not have a front-end and only exposes an API. API stands for Application Programming Interface. An API is a standardized and documented way for applications to communicate. Read here to understand better what an API is.
An API first CMS that only offers an API and no frontend is called a “pure” API first CMS. It is opposed to other mixed forms of API first CMS that offers a little bit of frontend.
In a “pure” API first CMS, content is not mixed with html or context dependent formatting. In a “pure” API first CMS, the CMS does not influence how and where the content is presented. The medium formats the content according to its needs. The medium can be a device, a connected object, a website (a phone, a web application, a social media, a newsletter, a TV, etc.)

Why did you choose this type of CMS for the European Respiratory Society?
Samuel: Content management became unmanageable. We had up to 26 instances of a CMS and a few other unrelated CMS’s. We needed to find a solution. It was difficult to manage updates, redistribution and aggregation of content dispatched in different systems. We also had to handle technical migrations, security updates and patches. We spent an increasing amount of time for maintenance instead of developing new features and updates.
When we started thinking about developing an app, it was my opportunity to try an innovative solution. How could we distribute the content? Write an API on top of our existing CMS’s? I made the case for API first CMS’s. I showed how we would save time in maintenance, development (backend and frontend) and how easy it would be to redistribute content. My manager understood the issue. He agreed to go ahead with my solution of API first CMS.

Liip: What were your challenges during the implementation?
Samuel: Challenges with API first CMS’s are very subtle and not obvious at first sight. First of all, menu management is trivial in a classic CMS, in an API first CMS it is not…Secondly, the front-end needs to be truly decoupled from the API. It is really easy to implement some logic in the frontend that somehow transforms the content. But that transformation will not be available for other systems when they call the content API. Finally, caching is another area that needs extra care as the frontend needs to be notified when content changes in a completely separate system.

If you are further interested in menu management, read my tutorial How to manage menus with Cloud CMS about the solution we implemented at the European Respiratory Society.

With an API first CMS comes flexibility big time: how do you ensure that the content edition team has a clear view on what’s important?
Samuel: It depends who controls flexibility. In my opinion, flexibility is not in the hands of the content edition team. The flexibility here is for the the developer. For the content edition team, nothing changes. The content edition team has a UI where they can manage and organise content. On the other hand developers have total flexibility in what technology they use, how they implement it, how they query content etc. Let’s say that the content edition team has access to the configuration of the CMS, in the worst case, the frontend will not display anything… as changes will have to be implemented in code too… Therefore I would recommend to have a clear understanding of what the content edition team wants to do, and how they want to do it, then everything is straight forward.

Liip: How does an API first CMS change the work and process of the copywriting team?
Samuel: Copywriters need to be trained to think differently about content. Typically, copywriters think about pages, and how the text and images will look like in the context of a page. Copywriters are often used to create content in CSM called WYSIWYG (What You See Is What You Get).
In a CMS that does not have frontend and in a world where your content can end up on so many media (website, apps, social media, newsletters, tv, etc.) copywriters have to come back to the essence: the copy. Sometimes images will be used, sometime not, or a video will be added etc. the copywriter does not control that. Of course this is not always possible, and previews can be implemented, even a website can be rendered “in a future state” to let copywriters verify that it it looks how they wish. With an API first CMS this is extra development, it is not a preview button somewhere on the editors’ UI.

Liip: How did your development team work with the copywriting team to help them create content suitable to be used in an API first CMS? I expect they had to change their writing process?
Samuel: The change has been gradual, we have introduced our API First CMS among the other CMS’s we were using. At first the change impacted only a few users. Gradually the change impacted the whole company. This gave us a chance to train first the most technical members of our copywriting team. It allowed us to fix a lot of small issues and miscomprehension. Eventually, we organized a training for all content editors. In collaboration with the communication department, we created an “onboarding” kit, that included: a style guide (specific formatting of text for the ERS), a general guide on how to write on the web, and a technical guide on markdown (a text format) and how to create/edit/publish/schedule/etc. articles.

Liip: What did you do with the content of the website when you changed the CMS? Did you migrate or did you rewrite all the content?
Samuel: Both… We introduced the API first CMS at the same time as a long overdue migration of the main website. The communication team had reorganised the content and menus for some page. We migrated other pages. With an API first CMS the ingestion of content is really easy as normally you have API endpoints that let you write content. Migration scripts are therefore easier to write.

An API first CMS can also mean that you have to build lots of standard stuff (such as SEO meta information) from scratch. How did you for now ensure these kind functionality for content editors were still available w/out going the extra mile and re-develop it?
The European Respiratory Society’s frontend are developed with Vuejs and Laravel. These open source ecosystems are really rich and many developers have already contributed useful plugins.

For this reason, I used “plugins” that allowed to generated all the meta tags. I also went the extra mile by developing exactly what we needed to improve our content discovery: a search engine and Natural Language Processing “modules” to extract information and classify our content. The search engine and Natural Language Processing “modules” benefited from the API first approach. Content can easily be modified by search engine and Natural Language Processing “modules” by calling the correct endpoints.

Today, there are a lot of misconceptions with API first CMS. It is correct that you have to build a whole frontend. It is often wrong that building a whole frontend takes more time than developping or adapting a Wordpress theme. For example, making sure that all outputs of that CMS are correctly overwritten is time-consuming.

Liip: When would you recommend an API first CMS? For what kind of company or what kinds of needs?
Samuel: If you manage more than one website, or if you need to publish content across different systems, I would recommend an API first CMS. If you are for example a publishing house or a fashion house and that you manage a lot of media and you have the budget, checkout big name CMSs such as Arc Publishing or Adobe. If you need a simple website or blog go for a Wordpress like website, or even simpler solution such as Wix.

The API first CMS landscape is changing very fast. There is a convergence happening. On the one hand, some providers propose easy ways to have a frontend on top of their APIs and, on the other hand, Non-API first CMS start proposing APIs. There are very few API first CMS that have a level of maturity that make them enterprise grade content CMS.

Liip: What do you mean by fully integrated ecosystem?
Samuel: For example, Adobe CMS, the Arc Publishing ecosystem or Chorus Publishing suite (Vox Media) are not API first CMS but fully integrated ecosystem. They are expensive and come with complex workflows management capabilities and many publishing pipelines. For example, they integrate the photographers’ camera into complex workflows and publishing pipelines.
For example, let’s say that you want to create a printed fashion catalogue, a magazine and a website to sell you content. The pictures do not go directly to the website, but are integrate in a publishing workflows. You organise a photo shooting, and the pictures are taken are right away named and uploaded in the DAM of the CMS , ready for review and selection. Copywriters ad text and all this content gets published on the online, shop, magazine and catalogue. Copywriter work on Indesign for the catalogue, and share the text for the website, and other media.

Liip: Do you think API first CMS can compete with such integrated ecosystem?
Samuel: No API first CMS that I have heard of can compete (yet?) with integrated ecosystems like Adobe CMS, the Arc Publishing ecosystem or Chorus Publishing suite (Vox Media). The only API first CMS that has similar advanced features is Cloud CMS. We chose Cloud CMS at the European Respiratory Society after checking different API first CMS. Many of the CMS in this list are very young and do not have the feature that you can expect for enterprise grade CMS such as content versioning, publishing workflows, right managements, etc.

If you want to compare headless CMS, read A List of Content Management Systems for JAMstack Sites.

Liip: Let’s summarise, integrated ecosystems and API first...
Samuel: So if you are working in media, maybe it makes sense to go for a CMS that can integrate with your publishing software such as InDesign. If you just have to build a small website, maybe go for wordpress. But if you think about content as a service in your infrastructure, go for an API first CMS. For me the trigger would be questions such as “How do we get the content from our CMS in the App?” as soon as content as to appear in different places, and API of some sort will be needed, considering an API first CMS can save you a lot of time and money.

Liip: Thank you for the interview Samuel!

]]>
Try out Scrawl, our new multiplayer game! https://www.liip.ch/en/blog/try-scrawl-multiplayer-game-ios https://www.liip.ch/en/blog/try-scrawl-multiplayer-game-ios Mon, 25 Mar 2019 00:00:00 +0100 TL;DR: Download Scrawl from the iOS App Store and challenge your friends or strangers!

At Liip we are lucky to be able to take time to experiment new technologies and have fun with it. When we start what we call an "inno project", we always set some rules as if it was a client project:

  • Time budget
  • Technologies we want to test
  • Deadline

As we often work on inno projects when we don’t have client work, setting those rules help us making sure that the project has a worthwhile outcome.

We wanted to dig deeper into Firebase (Firestore, Cloud Function, Authentication) and add Machine Learning on mobile. We came up with a simple but fun game that includes all those technologies:

  • Create a game against another player
  • Make a drawing of a word that has been given to you randomly
  • Our custom AI™ tries to recognize the two drawings
  • The player with the most legible drawing wins !

We teamed-up with three teams at Liip: both Mobile teams in Fribourg and Lausanne, and the Data Services team in Zürich. After a day trying different possibilities with the data coming from Quick, Draw!, we were already able to have a working prototype. A few days of work after that, we are proud to release Scrawl to the world!

Try it out !

Checkout the Scrawl website and download the iOS app!

We wanted to be GDPR-compliant. We don't store anything personal except the email used to connect. Each user has a random username. You can find your own name on the application’s home page. Use this to challenge other people you know.

Tell us what you think

Keep in mind that this is the result of work in our spare time so the game has a few rought edges. This is why we would love to get your feedback. Leave us a comment below or tweet at us!

]]>
Customizing your Breadcrumbs https://www.liip.ch/en/blog/customizing-your-breadcrumbs https://www.liip.ch/en/blog/customizing-your-breadcrumbs Fri, 22 Mar 2019 00:00:00 +0100 Drupal core provides us with builder services ordered by a priority. More builders can be found in contrib modules and you always can create custom builders yourself. Builder are services - if you aren’t familiar with them - this might be helpful.

The core path based builder

In case your URL patterns exactly represent your breadcrumb, you struck gold. Drupal core provides us with a PathBasedBreadcrumbBuilder which is enabled by default and therefore doesn’t need a set up.
However, in case you want some configurable options - such as editing or hiding the home link and excluding pages - the “Easy Breadcrumb” (easy_breadcrumb) module could come in handy. After the installation, the mentioned configuration page in the backend can be found under “example.com/admin/config/user-interface/easy-breadcrumb”.

The contrib menu based builder

Path based breadcrumbs covers most use cases. However, sometimes the content manager needs more configuration control. In this case it could make sense to have the breadcrumb follow the exact structure of the menu defined by the core menu without regarding the URL path.
To achieve this, one can utilize the “Menu Breadcurmb” (menu_breadcrumb) module. This builder service relies on the active trail provided by the core service “@menu.active_trail”. Configuration for that module can be found under the URL “examle.com/admin/config/user-interface/menu-breadcrumb”, You can choose the menus to be represented, set whether or not the home link shall be included and the current page needs to be appended.

Manually adding each node to the menu in order to show up in the menu-based breadcrumb is not necessary. The “Menu Position” (menu_position) module allows you to dynamically add nodes to the active trail based on conditional rules, provided by the Condition plugin type.
There are several plugins out of the box by Drupal core. The UserRole plugin, the Language plugin, the CurrentTheme plugin and most notably the NodeType plugin, which allows you to set rules based on the node bundle.
There are many more plugins to be found in core and in contrib modules. One module to be highlighted would be the “Term Condition” (term_condition) contrib module, thanks to whom you are able to evaluate whether a taxonomy terms is referenced on one of the nodes fields now. Last but not least, in case the requirements are outstandingly peculiar you always have the option to create plugins yourself.

Disable an unwanted builder

Having created an awesome breadcrumb mechanic that covers every necessary use case, the low priority builders may be redundant. The PathBasedBreadcrumbBuilder is always able to create a breadcrumb and the only way of getting rid of it is to replace it with a non applying builder.

services:
    system.breadcrumb.default:
        class: Drupal\zoo_customizations\Builder\NoBreadcrumbBuilder
        tags:
            - { name: breadcrumb_builder, priority: 0 }

In the services.yml one can override any service including the PathBasedBreadcrumbBuilder. The replacement breadcrumb builder must implement the BreadcrumbBuilderInterface and therefore the two functions applies() and build(). Unless applies() returns true in some cases build doesn't need an implementation.

/**
 * This service overrides the PathBasedBreadcrumbBuilder effectively disabling it.
 *
 * Class NoBreadcrumbBuilder.
 *
 * @package Drupal\zoo_customizations\Builder
 */
class NoBreadcrumbBuilder implements BreadcrumbBuilderInterface {

  /**
   * @inheritdoc
   */
  public function applies(RouteMatchInterface $route_match) {
    return FALSE;
  }

  /**
   * @inheritdoc
   */
  public function build(RouteMatchInterface $route_match) {
    // Wont be implemented.
  }

}

Custom rendering

The frontend inclusion is done via block, in particular the “system_breadcrumb_block”, which should be included by default after installing Drupal. To get familiar with blocks, this might be helpful. The Block can also be added anywhere by using the contrib module TwigTeaks 2.x. Add the following line anywhere in your Twig templates.
{{ drupal_block('system_breadcrumb_block') }}

No matter which implementation is used, the block itself is being rendered and utilizes the breadcrumb.html.twig template.

]]>
Easily save your Android ViewModel state https://www.liip.ch/en/blog/easily-save-android-viewmodel-state https://www.liip.ch/en/blog/easily-save-android-viewmodel-state Tue, 19 Mar 2019 00:00:00 +0100 Last week, Google released ViewModel Savedstate 1.0.0. Its documentation states:

ViewModel objects can handle configuration changes so you don't need to worry about state in rotations or other cases. However, if you need to handle system-initiated process death, you may want to use onSaveInstanceState() as backup.

When an Activity is destroyed because of memory pressure, the state of the ViewModel is not saved. When the Activity is recreated, the ViewModel will have lost data that was only stored in memory. This is a problem that we often encounter, so we came up with a custom solution. But now Google released its official library to handle this case and I was happy to play with it.

There is already a nice deep-dive article about it, but I wanted to take a step further. I wanted to make it so easy that you don’t even have to think about saving your state.

Define ViewModel variables like you are used to

With my collection of ViewModel SavedState Helpers, you can define your ViewModel like if it was a local variable.

// Import the library
import ch.liip.viewmodelsavedstatehelpers.*

// Define a ViewModel that takes a SavedStateHandle in argument
class MainViewModel(handle: SavedStateHandle) : ViewModel() {
    // Simple string that is saved in the SavedState
    var manualText by handle.delegate<String?>()

    // MutableLiveData that is saved in the SavedState
    val liveDataText by handle.livedata<String?>()
}

You can then use the ViewModel like you would do usually. Your data is saved and restored automatically!

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Obtain the ViewModel with SavedStateVMFactory
    viewModel = ViewModelProviders.of(this, SavedStateVMFactory(this)).get(MainViewModel::class.java)

    // Observe the livedata
    viewModel.liveDataText.observe(this, Observer {
        liveDataText.setText(it)
    })

    // Save the values
    button.setOnClickListener {
        viewModel.liveDataText.value = liveDataText.text.toString()
    }
}

How does it work ?

This is heavily related to our work on SweetPreferences. I basically use Kotlin delegated properties to wrap the calls into a SavedStateHandle, which is the main class offered by the ViewModel Savedstate library.

Take a look at the only file to see the whole code. Then install the library from Github and enjoy your peace of mind when it comes to saving state!

]]>