Jun 28 2019
Jun 28

Go back in time and you’ll realize that every Drupal version upgrade has been drastically different from their predecessors. This caused compatibility issues and upgrade between major versions has always been a complicated task. Contributed modules and themes needed to be upgraded for compatibility, content migration was a hassle, and the list goes on. Gradually Drupal has adopted semantic versioning which allows for continuous improvements without waiting for major version launches and allows for a more continuous upgrade.

Be rest assured that upgrading to Drupal 9 in June 2020 will be a cake walk!

Drupal 9 roadmaphttps://dri.es/drupal-7-8-and-9

Drupal 8 minor version upgrades 

With Drupal 8, we welcomed the concept of continuous innovation and new features/libraries were introduced in minor version upgrades e.g. The recent version of Drupal 8.7 came with the new and improved layout builder, media library, workflow, etc. Read more about Drupal 8.7 here.

Drupal 9 is going to be similar to a minor version upgrade where the community will work on removing deprecated code and upgrading underlying libraries like Symfony.

Drupal 8 to Drupal 9 https://www.drupal.org/docs/9

Drupal 6 website & Drupal 7 end of life 

8,24,301 websites are currently on Drupal 6 or 7 versions. With  D7 end of life in November 2020, and extended support till 2021. Now is the time to decide the course of action for your website to thrive with optimum performance and security. In case you are still on D6, we are here to help! 

At a recent Drupal Meetup hosted by QED42, a sense of indecisiveness clouded majority of the members. Yes, everybody was excited for the new version release and what came with it. But we ended up discussing more about ‘When’ and ‘Why’ should we upgrade to Drupal 9! 

Upgrade or Wait? 

Drupal 9 will be released one year prior to Drupal 7’s end of life. Even with the extended support for D7, eventually, the community will no longer provide updates, security fixes, and enhancements. (may be available on a limited basis from select commercial vendors at a cost)

If your current website isn’t facing any issues at the moment, it tends to induce a laid back outlook. Sure your website might perform perfectly right now, but without the necessary upgrades, eventually, she will get rusty and creaky. Know more about the implications of stalling an upgrade.

Why upgrade to Drupal 8https://www.drupal.org/psa-2019-02-25

So is there any scenario in which I should wait? 

The only scenario that we can think of in which it makes sense to wait is if your organization plans to club design refresh of your website with the upgrade and if for some reason redesign is delayed (Budget, Resourcing) as re-design often comes with layout & content strategy changes. Order of execution if Redesign and upgrade don't overlap will be:

Drupal 6/7 -> Drupal 8 -> Redesign -> Re-do the layouts / content strategy in Drupal 8 or Drupal 9 --- (Rework / Inefficient)

If there are no budget or other constraints then Design Refresh is the best milestone to think about Upgrade as you can get face and tech lift in one go and also turns out to be much cheaper. 

Upgrade to Drupal 8 at Next Chance Available!

As discussed before, Drupal 9 is being continuously built on top of Drupal 8 making it backward compatible with Drupal 8. This allows for a smooth upgrade from Drupal 8 to 9 and once you are on Drupal 8, as long as you are upgrading to minor versions of Drupal 8 regularly there won’t be any major surprises from Drupal 9! Considering that, We recommend that you upgrade your Drupal 6/7 site to Drupal 8 at the next chance available because why would you want to lose out on all the benefits of Drupal 8 today if Drupal 9 upgrade is gonna be smooth and cheap?

Once you are on Drupal 8, the next question that arises is:

What do I need to prepare for Drupal 9? 

The best approach is to just keep your Drupal 8 website upgraded with the minor version as Drupal 8 minor upgrades are gradually transitioning and preparing your website for Drupal 9. Contributed modules should be regularly updated, this ensures deprecated code removal. There are a few ways to check if your site is using the deprecated code.

Drupal Deprecated Code Removal Guide

Drupal Deprecated Code Removal Guide

Still confused?

All this information may seem overwhelming and difficult to make a decision. We’ve got you covered! Reach out to us at [email protected] for a free consultation about your Migration Dilemma.

May 21 2019
May 21

QED42 has always been an ardent participant in the Drupal community. We pride ourselves for contributing to the Drupal community via modules, code patches, Drupal initiatives, DrupalCons, DrupalCamps or hosting Meetups!

Drupal Meetups play an integral role in fostering community. Dries Buytaert was quoted on Drupal.org’s getting involved page:

It’s really the Drupal community and not so much the software that makes the Drupal project what it is. So fostering the Drupal community is actually more important than just managing the code base.

We hosted the Pune Drupal Group monthly meetup on 18th May 2019 at our office. The healthy turnout to the local meetup was a reflection of how connected the Pune Drupal Community was.

Packed with people, plenty of snacks, and laptops our Meetup commenced. After a brief introduction from all the attendees, the lights dimmed and Meena Bisht from QED42 started her session ‘ Be Ready for Drupal 9!’

Pune Drupal Group monthly meetup 2019

It was a highly interactive session that pivoted around Drupal’s ever-evolving nature. She spoke about how long Drupal 7 will be supported, Drupal 8 end of life, and how it would impact businesses. Drupal 8.7 features - Layout Builder and Media Library and challenges faced while moving from Drupal 7 to Drupal 8.

Pune Drupal Group monthly meetup 2019https://dri.es/drupal-7-8-and-9

We welcomed the newest member to the family, Drupal 9.

Pune Drupal Group monthly meetup 2019

The session also covered the Drupal release cycle, which justified the difficulties faced while moving to Drupal 8. We were relieved to know that upgrading to Drupal 9 would be a lot easier thanks to the minor upgrades.

The session shed light on why an upgrade is required, and what to expect out of Drupal 9. We ended the session with useful tips on tools for checking our deprecated code while preparing for Drupal 9.

Pune Drupal Group monthly meetup 2019https://dri.es/drupal-7-8-and-9

Post session, we discussed the hurdles faced during the earlier version releases, our inhibitions, and expectations from Drupal 9.

After a quick break with refreshments and offline chats, we gathered back for the BoF session on the configuration management system. We discussed the origin of configuration management, as a Drupal initiative, the different configuration issues faced by us and identified solutions.

Pune Drupal Group monthly meetup 2019

Lastly, we chalked out a map for the DrupalCamp Pune. All the attendees brought helpful ideas to the table, location, sessions, sponsorships, etc.

After an informative and super lucrative agenda of sessions, BoF, and DrupalCamp Pune planning, we wrapped up our Meetup.

May 08 2019
May 08

Drupal 8.7.0 was released on 1st May 2019. Apart from bug fixes and dependency updates, Drupal 8.7 facilitates creating page layouts and media management. Decoupled web experiences are now easier to manage and deliver, saving production time and effort.

JSON:API at Core - facilitates Decoupling

JavaScript Object Notation(JSON) is an open data format and syntax to exchange and store data, widely accepted as preferred data exchange used in web services, especially RESTful services.

JSON:API is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests.

The latest Drupal 8.7 update includes JSON:API as a part of the Drupal core. This makes Drupal an API first platform for building both decoupled and omnichannel experiences. It facilitates building REST APIs in JSON through which a Drupal website can share its data with modern JavaScript Framework like React, Angular and other interfaces like Alexa, Chatbots, etc.

Functionality:

  • JSON:API module exposes the entities as a standards-compliant web API which allow data to be pulled from third-party URLs or API’s.
  • By enabling JSON:API module, you can create a RESTful API endpoint effortlessly for every type (content, taxonomy, user, etc.) in your Drupal application. 
  • JSON:API inspects your entity types and their bundles to dynamically provide URLs. These URLs access every entity using the standard HTTP methods, GET, POST, PATCH, and DELETE.

Benefits: 

  • ​It minimizes both the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability.
  • JSON:API ensures that a module should be production-ready. This means the module is highly opinionated about where your resources will reside, what methods are immediately available for them, and allow Drupal Core's permissions system control the access. 
  • Configuration pages are no longer present in this upgrade (Drupal 8.7). This means that you can get an API-driven Drupal application up and running with minimal effort.

JSON:API

Watch the JSON:API demo here!

Stable Layout Builder - effortless Layout creation

The Stable Layout Builder was released with Drupal 8.6 as an experimental module and is now stabilized in Drupal 8.7.

Functionality:

  • Layout Builder is anchored on one of Drupal’s stronger features – the ability to create structured content; but it faces some of the same accessibility challenges encountered by  WordPress’ Gutenberg editor.
  • Drupal's Layout Builder offers a single, powerful visual design tool for three use cases:
  • Layouts for templated content: Creation of ‘layout templates’ that will be used to layout all instances of a specific content type (e.g. blog posts, product pages).
  • Customizations to templated layouts: The ability to override these layout templates on a case-by-case basis (e.g. the ability to override the layout of a standardized product page).
  • Custom pages: The creation of custom, one-off landing pages not tied to a content type or structured content (e.g. a single "About us" page).

Benefits:

  • Drupal 8.7's Layout Builder allows content editors and site builders to easily and quickly create visual layouts for displaying content.
  • The Layout Builder provides the ability to drag and drop site-wide blocks and content fields into regions within a given layout. Additionally, custom "inline" blocks can be created for one-off details specific to a given layout.
  • Users can customize how content is arranged on a single page, across content types, or even create custom landing pages with an easy to use drag-and-drop interface.

The Layout Builder powerful when combined with Drupal's out-of-the-box features such as revisioning, content moderation, and translations.

Layout builder

Watch the Demo of Drupal 8 Layout Builder here!

Media Library  - better media experiences

Drupal 8.6 had Media Library in the Drupal core, which was a part of Media Initiative.

Media and the Media Library, have acquired best functionalities from a couple of contributed modules. Introducing amazing functionalities like reusable media, media types, media fields, and more. ​Drupal 8.7 Media Library comes with a new and easy to use user interface, making it easier to work with.

Benefits:

  • Add diverse types to your site.
  • Save and repurpose media in your Media Library.
  • Insert media within your content.
  • Embed remote YouTube and Vimeo videos via URLs.

media-library

There are Third-party library updates available in Drupal 8.7.0:

  • Guzzle has been updated from 6.3.0 to 6.3.3.
  • Previously, Drupal packaged a copy of the PEAR Archive_Tar library in a Drupal core namespace. In Drupal 8.7, this has been deprecated and replaced with a proper Composer dependency on this library. The dependency has also been updated to version 1.4.6.
  • Coder to ^8.3.1
  • CKEditor has been updated to 4.11.3.
  • Twig has been updated to 1.38.4.
  • A number of other PHP dependencies have also been updated, including:
    • composer/installers to 1.6.0
    • composer/semver to 1.5.0
    • egulias/email-validator to 2.1.7
    • paragonie/random_compat to v2.0.18
    • Most symfony/* components to v3.4.26
    • symfony/http-foundation to v3.4.27
    • symfony/polyfill-* to v1.11.0
    • Stylelint has been updated from 9.1.1 to 9.10.1.
    • typo3/phar-stream-wrapper to v2.1.0

Other updates you can find in Drupal 8.7 are:

Internet Explorer 9 and 10 will not be supported in Drupal 8.7

The 8.7.0 release is a final goodbye to Internet Explorer 9 and 10. It removes a workaround that still existed in D8.5 and D8.6 Issue link: Internet Explorer 9 and 10 support dropped from Drupal 8.4.x

Goodbye PHP 5 support

Drupal 8.7 is the last release to support PHP 5. Updates for existing websites that use PHP 5 are still possible, but a warning will be displayed. In release 8.8, Drupal security updates will require PHP 7.

Entity updates will not be automatic

In new Drupal 8.7.0 release, the support for automatic entity updates has been removed. The reason is data integrity issues and conflicts. So the drush entity: updates (drush entup) command no longer works. Changes to entities will now be performed using standard update procedures.

Symfony 4 and 5 compatibility issues resolved

Additionally, numerous critical Symfony 4 and 5 compatibility issues are resolved in this release.

Changes to base themes (Stable, Classy)

This release includes some small changes to the core's base themes (Stable, Classy). Themes that extend one of these base themes should review the following changes. JavaScript messages template changes. Pager CSS ID changed from "pagination-heading" to a unique ID.

These Drupal upgrades are gradually getting us ready for Drupal 9. If you have questions regarding Drupal upgrades we are here to help. Drop us a word at [email protected].

Jan 15 2019
Jan 15

Last month, Pune Drupal community conducted a Global Training Day event. It was a successful event and saw huge participation from Pune Drupal community.
Pune GTD reminded us that community is what makes Drupal so special and that interaction between the community members was somewhat missing from our Drupal life.

With the hope that we can change this, we had planned to increase the community participation by bringing back our monthly Drupal meetups.
And we did deliver on our plan! We had our first Drupal meetup of the year on Saurday, 12th Jan at SISCR, Pune.

The day started with a session on 'Docksal for Drupal' by Sharique.

Sharique preseting session on Docksal

The session covered the basics of containers and the difference between virtualization and containarization followed by a detailed demo of setting up a Drupal instance using Docksal and managing various configurations for Docksal.
Overall, this was a great introduction for developers & teams trying to adopt Docksal for their development environment.

Highlight of the day was the next session on the topic of Accessibility presented by Nikita, Ambuj & Sonal.

Nikita started the session by explaning basic concepts of Accessibility and day-to-day programming tips & tricks developers/teams can follow to make their site accessible.

Nikita presenting session on accessibility

It was followed by Ambuj's explanation of QA's perspective on Accessibility and what should be expected by users & testers for an accessible site.

Ambuj explaining QA's perspective on accessibility

Sonal concluded the session by going into the implementation details, specially the easy ways a developer/team can make their site accessible.

Sonal preseting session on accessibility.

We ended the day having by a general BoF on 'How to increase the community participation for Drupal meetups'.
This was a very productive discussion and the community identified the challenges of setting up a quality event and came up with action items for them.

  • Attendees want to see a variety of topics instead of just having them limited to Drupal Backend and Custom Module development. The session on Accessibility was much liked by the community and they'd love to see more such topics.
  • There's a need to streamline and improve the process of communicating Drupal events and their reporting.

We promise to work on the above and would love to see many of you join us in future meetups. Stay tuned to community updates for information on future events and topics!

Links to Session slides/resources:
1. Docksal: https://www.slideshare.net/safknw/local-drupal-development-using-docksal
2. Web Accessibility: https://bit.ly/2Dgw5P9

Nov 16 2018
Nov 16

At the point when Amazon launched Alexa, it changed the method for individuals connecting with the gadgets.Virtual assistants are quick getting to be ordinary in the home through items like Echo, Echo Show, Echo Dot, Echo Look and Amazon Tap.

The Alexa Skills marketplace is rapidly growing, below is the stats according to the voicebot.ai

US Alexa skill totals as of december 2017

 

Why Alexa skills are Important for business?

Voice search are growing day by day and it is the latest trend in the field on digital design. “There are over one billion voice searches per month. (January 2018)” estimates Alpine.AI.

 Alexa is an awesome way to achieve a more extensive customer gathering of people, which is the reason an ever increasing number of brands crosswise over verticals are developing skills to catch and draw in with audience members. Alexa is amazon’s cloud based service available on tens of millions of devices from amazon and third-party manufacturers.

Alexa Skills are so new to the market, organizations have a universe of unbounded potential outcomes before them. Very little has been done yet, so there's a great deal of space to end up the first to provide one of a kind interaction to a particular sort of business.

Why account linking is necessary for business?

Account linking allow user to easily link there accounts with existing account or service account. It also allows your skill to securely authenticate user with services. Once the user granted your skill  access to the external account the skill can perform operations with that account on behalf on the user.  

Account linking lets you access the current users information by which you can provide user a personalized content as per their interests. 

Account linking in the Alexa skill set uses OAuth 2.0. OAuth server return a authorization code instead of access token in the first step which gives couple on security benefits, for example, the ability to verify the client and also the transmission of the access token specifically to the client without going it through the resource owner’s user-agent and conceivably exposing to others, including the resource owner. Here the resource owner is the person who can grant authorization to get to an ensured resources in the resource server.

How Alexa account linking works with Drupal?

Alexa linking with Drupal


Account linking with Alexa skill

For custom skill model, account linking is a preferred choice if your skill needs personalized data from another system. Account linking lets you connect the identity on the user with a user account in different system. 

Lets walk through an example to understand things more clearly, you have created a web-based service which enables user to order things, a custom skill that lets user access your service by voice would be useful. For example  the request,“ Alexa add echo dot to the cart” requires the skill to access your service as a specific service user for adding product to cart and payment information. As the user is already registered on the website we need to get details of  that particular user to perform above actions. Here we can’t use the amazon account of  the user to identify him/her in our system because it’s not necessary that user will be using the same account to login on the both the system i.e. Echo dot and web-based service. In such scenarios account liking helps to identify user.

For custom skill models, the user completes an account linking flow in the Alexa app to log in to your service. Once the user is authenticated, requests and directives sent to your skill include an access token that your system can use to identify the user. Refer  Overview of the Authorization code grant working for a better understanding of its functions. 

Note: For account linking with Alexa the Drupal site must have an SSL certificate.

Steps to Implement Account Linking in Your Skill:

  1. Download and enable Alexa- simple_auth, simple auth extras, oauth2 server modules.

  2. Add new OAuth2 server - It will help authenticate the identity of the user and issue access tokens. The screenshot below provides an example of server configurations.
     

    Step 2
  3. Add new client-  In here the Alexa skill will be your client, the skill uses access token to request information from the oauth2 server.
    Redirect URI will be provided in the account linking section of  the alexa developer console. (Check point 6 screenshot) 
     

    Step 3
  4. Go to permissions page grant the permission to particular role as per requirement.
     

    Step 4
  5. Login to your Alexa developer account and go to the Account linking menu (second last on left sidebar)

  6. Select authorization grant type as “Auth Code Grant”, enter the client id and client secret as per the client created on the Drupal end.
     

    Step 6
  7. Save and build the model and then go to Alexa application. There, enable your skill, and it will open a new tab that redirects the user to the Drupal site for authentication.

Identify a user from Alexa request

Once the user authenticates skill with the server, each time Alexa skill makes a request to the Drupal site, it would consist the access token from which the user can be identified. 

Below is the sample Alexa request object:

[session] => Alexa\Request\Session Object
        (
            [user] => Alexa\Request\User Object
                (
                    [userId] => amzn1.ask.account.abc
                    [accessToken] => 26c2cfe374cc1562ea29115e3749a80cf7e5262e
                )
            [new] => 
            [application] => 
            [sessionId] => amzn1.echo-api.session.447c2c75-cd0d-4e4b-9901-902b3f269090
            [attributes] => Array
                (
                )
        )
Feb 13 2018
Feb 13

We are in an era where we see a lots of third party integrations being done in projects. In Drupal based projects, cookie management is done via Drupal itself to maintain session, whether it be a pure Drupal project or decoupled Drupal project,.

But what when we have a scenario where user’s information is being managed by a third party service and no user information is being saved on Drupal? And when the authentication is done via some other third party services? How can we manage cookie in this case to run our site session and also keep it secure?

One is way is to set and maintain cookie on our own. In this case, our user’s will be anonymous to Drupal. So, we keep session running based on cookies! The user information will be stored in cookie itself, which then can be validated when a request is made to Drupal.

We have a php function to set cookie called setCookie() , which we can use to create and destroy cookie. So, the flow will be that a user login request which is made to website is verified via a third party service and then we call setCookie function which sets the cookie containing user information. But, securing the cookie is must, so how do we do that?

For this, let’s refer to Bakery module to see how it does it. It contains functions for encrypting cookie, setting it and validating it.

To achieve this in Drupal 8, we will write a helper class let’s say “UserCookie.php” and place it in ‘{modulename}/src/Helper/’. Our cookie helper class will contain static methods for setting cookie and validating cookie. Static methods so that we will be able to call them from anywhere.

We will have to encrypt cookie before setting it so we will use openssl_encrypt() php function in following manner:

/**
* Encrypts given cookie data.
*
* @param string $cookieData
*   Serialized Cookie data for encryption.
*
* @return string
*   Encrypted cookie.
*/
private static function encryptCookie($cookieData) {

 // Create a key using a string data.
 $key = openssl_digest(Settings::get('SOME_COOKIE_KEY'), 'sha256');

 // Create an initialization vector to be used for encryption.
 $iv = openssl_random_pseudo_bytes(16);

 // Encrypt cookie data along with initialization vector so that initialization
 // vector can be used for decryption of this cookie.
 $encryptedCookie = openssl_encrypt($iv . $cookieData, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);

 // Add a signature to cookie.
 $signature = hash_hmac('sha256', $encryptedCookie, $key);

 // Encode signature and cookie.
 return base64_encode($signature . $encryptedCookie);
}

  1. String parameter in openssl_digest can be replaced with any string you feel like that can be used as key. You can keep simple keyword too.
  2. Key used should be same while decryption of data.
  3. Same initialization vector will be needed while decrypting the data, so to retrieve it back we append this along with cookie data string.
  4. We also add a signature which is generate used the same key used above. We will verify this key while validating cookie.
  5. Finally, we encode both signature and encrypted cookie data together.

For setting cookie:
 

/**
* Set cookie using user data.
*
* @param string $name
*   Name of cookie to store.
* @param mixed $data
*   Data to store in cookie.
*/
public static function setCookie($name, $data) {
$data = (is_array($data)) ? json_encode($data) : $data;
$data = self::encrypt($data);
 setcookie($name, $cookieData,Settings::get('SOME_DEFAULT_COOKIE_EXPIRE_TIME'), '/');
}

Note: You can keep 'SOME_COOKIE_KEY' and 'SOME_DEFAULT_COOKIE_EXPIRE_TIME' in your settings.php. Settings::get() will fetch that for you.
Tip: You can also append and save expiration time of cookie in encrypted data itself so that you can also verify that at time of decryption. This will stop anyone from extending the session by setting cookie timing manually.

Congrats! We have successfully encrypted the user data and set it into a cookie.

Now let’s see how we can decrypt and validate the same cookie.

To decrypt cookie:

/**
* Decrypts the given cookie data.
*
* @param string $cookieData
*   Encrypted cookie data.
*
* @return bool|mixed
*   False if retrieved signature doesn't matches
*   or data.
*/
public static function decryptCookie($cookieData) {

 // Create a key using a string data used while encryption.
 $key = openssl_digest(Settings::get('SOME_COOKIE_KEY'), 'sha256');

 // Reverse base64 encryption of $cookieData.
 $cookieData = base64_decode($cookieData);

 // Extract signature from cookie data.
 $signature = substr($cookieData, 0, 64);

 // Extract data without signature.
 $encryptedData = substr($cookieData, 64);

 // Signature should match for verification of data.
 if ($signature !== hash_hmac('sha256', $encryptedData, $key)) {
   return FALSE;
 }

 // Extract initialization vector from data appended while encryption.
 $iv = substr($string, 64, 16);

 // Extract main encrypted string data which contains profile details.
 $encrypted = substr($string, 80);

 // Decrypt the data using key and
 // initialization vector extracted above.
 return openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
}

  1. We generate the same key using same string parameter given while encryption.
  2. Then we reverse base64 encoding as we need extract signature to verify it.
  3. We generate same signature again as we have used the same key which was used to creating signature while encryption. If doesn’t signatures doesn’t matches, validation fails!
  4. Else, we extract initialization vector from the encrypted data and use to decrypt the data return to be utilized.
/**
* Validates cookie.
*
* @param string $cookie
*   Name of cookie.
*
* @return boolean
*   True or False based on cookie validation.
*/
public static function validateCookie($cookie) {
 if (self::decryptCookie($cookieData)) {
   return TRUE;
 }
 return FALSE;
}

We can verify cookie on requests made to website to maintain our session. You can implement function for expiring cookie for simulating user logout. We can also use decrypted user data out of cookie for serving user related pages.

Dec 20 2017
Dec 20

When it comes to Drupal 8 theming layer, there is a lot Drupal 8 offers. Few concepts that come to mind while thinking of Drupal 8 theme layer include Renderable Array, Cacheabilty, Cache Context, Cache Tags, Twig and Preprocessors. Some of these are improvements of old concepts, while others are new introduction in Drupal 8. In this post, I'll share my experience on how to best utilise these concepts for a robust and performant frontend with Drupal 8.

To get best out of this post, you should be comfortable with:

  1. Drupal 8 #cache
  2. Twig Debug
  3. Preprocessor functions

We will focus on the following concepts:

Renderable array caching, walk through Drupal 8 caching power and Cache contexts

Drupal 8 comes with a lot of changes and almost all of us are even familiar with these changes. So, now it’s high time we start exploiting these to get best out of Drupal 8. It’s very important to understand how Drupal 8 caching works. Go through drupal.org documentation besides implementing the same once thoroughly on a vanilla Drupal instance. At the end have an answer to all these questions:

  • What is a renderable array?
  • Understand that every renderable array is cacheable.
  • One Renderable array can consist of other renderable arrays (nesting):
    So it’s like, a page is a renderable array which actually consists of other renderable arrays: region (renderable) arrays and in a region, we can have block (renderable array). The main point to understand here is hierarchy and nesting and the caching among this.
    Reference: https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays
  • Making use of cache context:
    A general mistake I have seen many of us perform is using
    
    #cache = max-age => -1
    
    

    There come few scenarios where we cannot use direct caching but that particular array can be cached on per user/URL basis. Especially for those cases, we have cache contexts. It’s very easy concept and you would love once you start using this, there are several other bases on which you can decide cache-ability of a renderable array.
    Reference: https://www.drupal.org/docs/8/api/cache-api/cache-contexts
     

Selection of template files/preprocessor functions and placement of the same.

With great flexibility, comes responsibility. Most of the time, we come across scenarios where we need to do tweaks to field output based on certain requirements. Drupal provides us handful of ways to do so, an important point here is to know and analyze what will be the best way to achieve expected results for the case. So, generally, we follow the following rule for customizations:

Tweak data -> Use Preprocess level alter
Tweak Markup -> Use Twig level customizations

Hence, when we have to do some custom work where we have to alter data conditionally, we make use of preprocessor functions. Consider overriding display of date field value based on certain conditions. In that case, a general approach we may take is to write hook_preprocess_field. But we need to consider that this preprocessor will run for all fields, which will definitely affect the performance. In this case, writing a specific preprocessor for date field makes more sense.

One more important thing regarding hook_preprocess_HOOK:

Consider a scenario, where we are using node.html.twig for node and then we have to do some alteration for a specific content type teaser view.

drupal8-twig

In that case creating specific twig and then using the specific preprocessor (node__content_type__teaser) for alteration is an extra step, instead, we can directly use the preprocessor hook_node__content_type__teaser without writing the specific twig file. So, for a generic twig, we can make use of specific hooks as suggested by twig debug too which definitely gives better performance.

Regarding placement of these preprocessors, it is completely based on the project requirement and usage. We place them in the module, in case the functionality to be provided should work on a modular approach. Placement of Preprocessors will work both in module and theme, while for twig files placing them in the module may need hook_theme_registery_alter based on the twig file we are overriding.

Use of Drupal Attributes

It’s not recommended to hardcode drupal attributes (HTML attributes like id, class) in twig files and the reason for this is: there are several modules which perform alteration of drupal attributes and make use of Drupal attributes and will no longer work if we hard code this. One example I can think of is Schema.org module which provides RDF mapping through attributes only.

Moreover, I would always suggest doing styling based on default classes provided by Drupal. Drupal by default provides appropriate classes both generic and specific and id for better styling. It is our duty, to make better use of them by understanding Drupal way. It also helps in saving project time and cost especially when we are following best practices and Drupal way of doing things. Drupal Core and Contrib are the best examples of how to proceed further on this. Also, following a proper structure makes possible use of styling written in Drupal core itself.

Logic in template/twig files - Yes/No?

In Drupal 7 To speed up the output process, it’s always recommended to avoid writing logic in template files. Instead, as discussed earlier consider writing preprocessors for this purpose. But with Drupal 8 adopting Twig Theme Engine things are different now. Doing tradeoff among the twig and preprocess (PHP) is mainly centric over performance concerns. We basically need our site to be faster. Drupal 7 PHP Engine used PHP theme engine and hence we recommended avoiding logic in template files, but in D8 with twig into effect, we have a faster theme engine with several twig filters, functions to be used. So, here we categorize our logic in 2 ways based on the type of work to be accomplished: soft logic and hard logic.

Soft Logic: Consider a scenario where we need to display comma separated values. Now, in this case, instead of writing a preprocessor for altering data we should use available twig filter "safe_join". It is definitely faster than the traditional way of using PHP preprocessor.

Hard Logic: In the above case, let's say if the value is taxonomy term and we need to print all the parent taxonomy term too. Then we need to load parent terms and we can then join them for final display, then this preprocessing should go in preprocessor only.
 

Frontend / Backend Collaboration

One more important thing I’ve observed is, it is very important to have a good collaboration between frontend and backend developers during the project. Sometimes as a frontend developer we come across weird requirements, in that case, it is very important to sit together to understand requirements and get the things done in the Drupal way to achieve best out of Drupal.

Also, go through this official guide to understand best practices for Drupal theming: https://www.drupal.org/docs/8/theming/twig/twig-best-practices-preprocess-functions-and-templates

Dec 19 2017
Dec 19

When it comes to Drupal 8 theming layer, there is a lot Drupal 8 offers. Few concepts that come to mind while thinking of Drupal 8 theme layer include Renderable Array, Cacheabilty, Cache Context, Cache Tags, Twig and Preprocessors. Some of these are improvements of old concepts, while others are new introduction in Drupal 8. In this post, I'll share my experience on how to best utilise these concepts for a robust and performant frontend with Drupal 8.

To get best out of this post, you should be comfortable with:

  1. Drupal 8 #cache
  2. Twig Debug
  3. Preprocessor functions

We will focus on the following concepts:

Renderable array caching, walk through Drupal 8 caching power and Cache contexts

Drupal 8 comes with a lot of changes and almost all of us are even familiar with these changes. So, now it’s high time we start exploiting these to get best out of Drupal 8. It’s very important to understand how Drupal 8 caching works. Go through drupal.org documentation besides implementing the same once thoroughly on a vanilla Drupal instance. At the end have an answer to all these questions:

  • What is a renderable array?
  • Understand that every renderable array is cacheable.
  • One Renderable array can consist of other renderable arrays (nesting):
    So it’s like, a page is a renderable array which actually consists of other renderable arrays: region (renderable) arrays and in a region, we can have block (renderable array). The main point to understand here is hierarchy and nesting and the caching among this.
    Reference: https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays
  • Making use of cache context:
    A general mistake I have seen many of us perform is using

#cache = max-age => -1

There come few scenarios where we cannot use direct caching but that particular array can be cached on per user/URL basis. Especially for those cases, we have cache contexts. It’s very easy concept and you would love once you start using this, there are several other bases on which you can decide cache-ability of a renderable array.
Reference: https://www.drupal.org/docs/8/api/cache-api/cache-contexts
 

Selection of template files/preprocessor functions and placement of the same.

With great flexibility, comes responsibility. Most of the time, we come across scenarios where we need to do tweaks to field output based on certain requirements. Drupal provides us handful of ways to do so, an important point here is to know and analyze what will be the best way to achieve expected results for the case. So, generally, we follow the following rule for customizations:

Tweak data -> Use Preprocess level alter
Tweak Markup -> Use Twig level customizations

Hence, when we have to do some custom work where we have to alter data conditionally, we make use of preprocessor functions. Consider overriding display of date field value based on certain conditions. In that case, a general approach we may take is to write hook_preprocess_field. But we need to consider that this preprocessor will run for all fields, which will definitely affect the performance. In this case, writing a specific preprocessor for date field makes more sense.

One more important thing regarding hook_preprocess_HOOK:

Consider a scenario, where we are using node.html.twig for node and then we have to do some alteration for a specific content type teaser view.

drupal8-twig

In that case creating specific twig and then using the specific preprocessor (node__content_type__teaser) for alteration is an extra step, instead, we can directly use the preprocessor hook_node__content_type__teaser without writing the specific twig file. So, for a generic twig, we can make use of specific hooks as suggested by twig debug too which definitely gives better performance.

Regarding placement of these preprocessors, it is completely based on the project requirement and usage. We place them in the module, in case the functionality to be provided should work on a modular approach. Placement of Preprocessors will work both in module and theme, while for twig files placing them in the module may need hook_theme_registery_alter based on the twig file we are overriding.

Use of Drupal Attributes

It’s not recommended to hardcode drupal attributes (HTML attributes like id, class) in twig files and the reason for this is: there are several modules which perform alteration of drupal attributes and make use of Drupal attributes and will no longer work if we hard code this. One example I can think of is Schema.org module which provides RDF mapping through attributes only.

Moreover, I would always suggest doing styling based on default classes provided by Drupal. Drupal by default provides appropriate classes both generic and specific and id for better styling. It is our duty, to make better use of them by understanding Drupal way. It also helps in saving project time and cost especially when we are following best practices and Drupal way of doing things. Drupal Core and Contrib are the best examples of how to proceed further on this. Also, following a proper structure makes possible use of styling written in Drupal core itself.

Logic in template/twig files - Yes/No?

In Drupal 7 To speed up the output process, it’s always recommended to avoid writing logic in template files. Instead, as discussed earlier consider writing preprocessors for this purpose. But with Drupal 8 adopting Twig Theme Engine things are different now. Doing tradeoff among the twig and preprocess (PHP) is mainly centric over performance concerns. We basically need our site to be faster. Drupal 7 PHP Engine used PHP theme engine and hence we recommended avoiding logic in template files, but in D8 with twig into effect, we have a faster theme engine with several twig filters, functions to be used. So, here we categorize our logic in 2 ways based on the type of work to be accomplished: soft logic and hard logic.

Soft Logic: Consider a scenario where we need to display comma separated values. Now, in this case, instead of writing a preprocessor for altering data we should use available twig filter "safe_join". It is definitely faster than the traditional way of using PHP preprocessor.

Hard Logic: In the above case, let's say if the value is taxonomy term and we need to print all the parent taxonomy term too. Then we need to load parent terms and we can then join them for final display, then this preprocessing should go in preprocessor only.
 

Frontend / Backend Collaboration

One more important thing I’ve observed is, it is very important to have a good collaboration between frontend and backend developers during the project. Sometimes as a frontend developer we come across weird requirements, in that case, it is very important to sit together to understand requirements and get the things done in the Drupal way to achieve best out of Drupal.

Also, go through this official guide to understand best practices for Drupal theming: https://www.drupal.org/docs/8/theming/twig/twig-best-practices-preprocess-functions-and-templates

Dec 12 2017
Dec 12

The Rise of Assistants

In last couple of years we have seen the rise of assistants, AI is enabling our lives more and more and with help of devices like Google Home and Amazon Echo, its now entering our living rooms and changing how we interact with technology. Though Assistants have been around for couple of years through android google home app, the UX is changing rapidly with home devices where now we are experiencing Conversational UI i.e. being able to talk to devices, no more typing/searching, you can now converse with your device and book a cab or play your favourite music. Though the verdict on home devices like Echo and Google home is pending, the underlying technology i.e. AI based assistants are here to stay.

In this post, we will explore Google Assistant Developer framework and how we can integrate it with Drupal.

Google Assistant Overview


Google Assistant works with help of Apps that define actions which in turn invokes operations to be performed on our product and services. These apps are registered with Actions on Google, which basically is a platform comprising of Apps and hence connecting different products and services via Apps. Unlike traditional mobile or desktop apps, users interact with Assistant apps through a conversation, natural-sounding back and forth exchanges (voice or text) and not traditional Click and Touch paradigms. 

The first step in the flow is understanding use requests through actions, so lets learn more about it. 

How Action on Google works with the Assistant?

It is very important to understand how actually actions on Google work with the assistant to have an overview of the workflow. From the development perspective, it's crucial we understand the whole of the Google Assistant and Google Action model in total, so that extending the same becomes easier.

 

Actions on Google

 

It all starts with User requesting an action, followed by Google Assistant invoking best corresponding APP using Actions on Google. Now, it's the duty of Actions on Google to contact APP by sending a request. The app must be prepared to handle the request, perform the corresponding action and send a valid response to the Actions on Google which is then passed to Google Assistant. Google Assistant renders the response in its UI and displays it to the user and conversation begins.

Lets build our own action, following tools are required:

  • Ngrok - Local web server supporting HTTPS. 
  • Editor - Sublime/PHPStorm
  • Google Pixel 2 - Just kidding! Although you can order 1 for me :p
  • Bit of patience and 100% attention

STEP1: BUILD YOUR ACTION APP

Very first step now is building our Actions on Google APP. Google provides 3 ways to accomplish this:

  1. With Templates
  2. With Dialogflow
  3. With Actions SDK

Main purpose of this app would be matching user request with an action. For now, we would be going with Dialogflow (for beginner convenience). To develop with Dialogflow, we first need to create an Actions on Google developer project and a Dialogflow agent. Having a project allows us to access the developer console to manage and distribute our app.

  1. Go to the Actions on Google Developer Console.
  2. Click on Add Project, enter YourAppName for the project name, and click Create Project.
  3. In the Overview screen, click BUILD on the Dialogflow card and then CREATE ACTIONS ON Dialogflow to start building actions.
  4. The Dialogflow console appears with information automatically populated in an agent. Click Save to save the agent.

Post saving an agent, we start improving/developing our agent. We can consider this step as training of our newly created Agent via some training data set. These structured training data sets referred here are intents. An individual Intent comprises of query patterns that a user may ask to perform an action, events and actions associated with this particular intent which together define a purpose user want to fulfill. So, every task user wants Assistant to perform is actually mapped with an intent. Events and Actions can be considered as a definitive representation of the actual associated event and task that needs to be performed which will be used by our products and services to understand what the end user is asking for.

So, here we define all the intents that define our app. Let's start with creating an intent to do cache rebuild.

  1. Create a new intent with name CACHE-REBUILD.
  2. We need to add query patterns we can think of, that user might say to invoke this intent. (Query Patterns may content parameters too, we will cover this later.)
  3. Add event cache-rebuild.
  4. Save the intent.
Intent Google Actions

For now, this is enough to just understand the flow, we will focus on entities and other aspects later. To verify if the intent you have created gets invoked if user says “do cache rebuild”, use “Try it now” present in the right side of the Dialogflow window.

STEP2: BUILD FULFILLMENT

After we are done with defining action in dialogflow, we now need to prepare our product (Drupal App) to fulfill the user request. So, basically after understanding user request and matching that with an intent and action Actions on Google is now going to invoke our Drupal App in one or the other way . This is accomplished using WEBHOOKS. So, Google is now going to send a post request with all the details. Under Fulfillment tab, we configure our webhook. We need to ensure that our web service fulfills webhook requirements.

According to this, the web service must use HTTPS and the URL must be publicly accessible and hence we need to install NGROK. Ngrok exposes local web server to the internet.

NGROK

After having a publicly accessible URL, we just need to add this URL under fulfillment tab. As this URL will receive post request and processing will be done thereafter, so we need to add that URL where we are gonna handle requests just like endpoints. (It may be like http://yourlocalsite.ngrok.io/google-assistant-request)

add webhook url

Now, we need to build corresponding fulfillment to process the intent.

OK! It seems simple we just need to create a custom module with a route and a controller to handle the request. Indeed it is simple, only important point is understanding the flow which we understood above.

So, why are we waiting? Let’s start.

Create a custom module and a routing file:

droogle.default_controller_handleRequest:
 path: '/google-assistant-request'
 defaults:
   _controller: '\Drupal\droogle\Controller\DefaultController::handleRequest'
   _title: 'Handle Request'
 requirements:
   _access: TRUE

Now, let’s add the corresponding controller

<?php

namespace Drupal\droogle\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;

/**
* Class DefaultController.
*/
class DefaultController extends ControllerBase {

 /**
  * Symfony\Component\HttpFoundation\RequestStack definition.
  *
  * @var \Symfony\Component\HttpFoundation\RequestStack
  */
 protected $requestStack;

 /**
  * The logger factory.
  *
  * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
  */
 protected $loggerFactory;

 /**
  * Constructs a new DefaultController object.
  */
 public function __construct(RequestStack $request_stack, LoggerChannelFactoryInterface $loggerFactory) {
   $this->requestStack = $request_stack;
   $this->loggerFactory = $loggerFactory;
 }

 /**
  * {@inheritdoc}
  */
 public static function create(ContainerInterface $container) {
   return new static(
     $container->get('request_stack'),
     $container->get('logger.factory')
   );
 }

 /**
  * Handlerequest.
  *
  * @return mixed
  *   Return Hello string.
  */
 public function handleRequest() {
   $this->loggerFactory->get('droogle')->info('droogle triggered');
   $this->processRequest();
   $data = [
     'speech' => 'Cache Rebuild Completed for the Site',
     'displayText' => 'Cache Rebuild Completed',
     'data' => '',
     'contextOut' => [],
     'source' => 'uniworld',
   ];
   return JsonResponse::create($data, 200);
 }

 protected function processRequest() {
   $params = $this->requestStack->getCurrentRequest();
   // Here we will process the request to get intent

   // and fulfill the action.
 }
}

Done! We are ready with a request handler to process the request that will be made by Google Assistant.

 

STEP3: DEPLOY FULFILLMENT AND TESTING THE APP

Part of the deployment has already been done, as we are developing on our local only. Now, we need to enable our custom module. Post that let's get back to dialogflow and establish the connection with app to test this. Earlier we had configured fulfillment URL details, ensure we have enabled webhook for all domains.

deployment

 

Let’s get back to intent that we build and enable webhook there too and save the intent.

intent enable webhook

Now, to test this we need to integrate it any of the device or live/sandbox app. Under Integrations tab, google provides several options for this too. Enable for Web Demo and open the URL in new tab, to test this:

Integration Web Demo

Speak up and test your newly build APP and let Google Assistant do its work.

So, as seen in the screenshot, there can be 2 type of responses. First, where our server is not able to handle request properly and the second one where Drupal server sends a valid JSON response.

GREAT! Connection is now established, you can now add intents in Google Action APP and correspondingly handle that intent and action at Drupal End. This is just a taste, conversational UX and Assistant technology will definitely impact how we interact with technology and we believe Drupal has a great role to play as a robust backend.

Dec 06 2017
Dec 06

The Rise of Assistants

In last couple of years we have seen the rise of assistants, AI is enabling our lives more and more and with help of devices like Google Home and Amazon Echo, its now entering our living rooms and changing how we interact with technology. Though Assistants have been around for couple of years through android google home app, the UX is changing rapidly with home devices where now we are experiencing Conversational UI i.e. being able to talk to devices, no more typing/searching, you can now converse with your device and book a cab or play your favourite music. Though the verdict on home devices like Echo and Google home is pending, the underlying technology i.e. AI based assistants are here to stay.

In this post, we will explore Google Assistant Developer framework and how we can integrate it with Drupal.

Google Assistant Overview


Google Assistant works with help of Apps that define actions which in turn invokes operations to be performed on our product and services. These apps are registered with Actions on Google, which basically is a platform comprising of Apps and hence connecting different products and services via Apps. Unlike traditional mobile or desktop apps, users interact with Assistant apps through a conversation, natural-sounding back and forth exchanges (voice or text) and not traditional Click and Touch paradigms. 

The first step in the flow is understanding use requests through actions, so lets learn more about it. 

How Action on Google works with the Assistant?

It is very important to understand how actually actions on Google work with the assistant to have an overview of the workflow. From the development perspective, it's crucial we understand the whole of the Google Assistant and Google Action model in total, so that extending the same becomes easier.

 

Actions on Google

 

It all starts with User requesting an action, followed by Google Assistant invoking best corresponding APP using Actions on Google. Now, it's the duty of Actions on Google to contact APP by sending a request. The app must be prepared to handle the request, perform the corresponding action and send a valid response to the Actions on Google which is then passed to Google Assistant. Google Assistant renders the response in its UI and displays it to the user and conversation begins.

Lets build our own action, following tools are required:

  • Ngrok - Local web server supporting HTTPS. 
  • Editor - Sublime/PHPStorm
  • Google Pixel 2 - Just kidding! Although you can order 1 for me :p
  • Bit of patience and 100% attention

STEP1: BUILD YOUR ACTION APP

Very first step now is building our Actions on Google APP. Google provides 3 ways to accomplish this:

  1. With Templates
  2. With Dialogflow
  3. With Actions SDK

Main purpose of this app would be matching user request with an action. For now, we would be going with Dialogflow (for beginner convenience). To develop with Dialogflow, we first need to create an Actions on Google developer project and a Dialogflow agent. Having a project allows us to access the developer console to manage and distribute our app.

  1. Go to the Actions on Google Developer Console.
  2. Click on Add Project, enter YourAppName for the project name, and click Create Project.
  3. In the Overview screen, click BUILD on the Dialogflow card and then CREATE ACTIONS ON Dialogflow to start building actions.
  4. The Dialogflow console appears with information automatically populated in an agent. Click Save to save the agent.

Post saving an agent, we start improving/developing our agent. We can consider this step as training of our newly created Agent via some training data set. These structured training data sets referred here are intents. An individual Intent comprises of query patterns that a user may ask to perform an action, events and actions associated with this particular intent which together define a purpose user want to fulfill. So, every task user wants Assistant to perform is actually mapped with an intent. Events and Actions can be considered as a definitive representation of the actual associated event and task that needs to be performed which will be used by our products and services to understand what the end user is asking for.

So, here we define all the intents that define our app. Let's start with creating an intent to do cache rebuild.

  1. Create a new intent with name CACHE-REBUILD.
  2. We need to add query patterns we can think of, that user might say to invoke this intent. (Query Patterns may content parameters too, we will cover this later.)
  3. Add event cache-rebuild.
  4. Save the intent.
Intent Google Actions

For now, this is enough to just understand the flow, we will focus on entities and other aspects later. To verify if the intent you have created gets invoked if user says “do cache rebuild”, use “Try it now” present in the right side of the Dialogflow window.

STEP2: BUILD FULFILLMENT

After we are done with defining action in dialogflow, we now need to prepare our product (Drupal App) to fulfill the user request. So, basically after understanding user request and matching that with an intent and action Actions on Google is now going to invoke our Drupal App in one or the other way . This is accomplished using WEBHOOKS. So, Google is now going to send a post request with all the details. Under Fulfillment tab, we configure our webhook. We need to ensure that our web service fulfills webhook requirements.

According to this, the web service must use HTTPS and the URL must be publicly accessible and hence we need to install NGROK. Ngrok exposes local web server to the internet.

NGROK

After having a publicly accessible URL, we just need to add this URL under fulfillment tab. As this URL will receive post request and processing will be done thereafter, so we need to add that URL where we are gonna handle requests just like endpoints. (It may be like http://yourlocalsite.ngrok.io/google-assistant-request)

add webhook url

Now, we need to build corresponding fulfillment to process the intent.

OK! It seems simple we just need to create a custom module with a route and a controller to handle the request. Indeed it is simple, only important point is understanding the flow which we understood above.

So, why are we waiting? Let’s start.

Create a custom module and a routing file:

droogle.default_controller_handleRequest:
 path: '/google-assistant-request'
 defaults:
   _controller: '\Drupal\droogle\Controller\DefaultController::handleRequest'
   _title: 'Handle Request'
 requirements:
   _access: TRUE

Now, let’s add the corresponding controller

<?php

namespace Drupal\droogle\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;

/**
* Class DefaultController.
*/
class DefaultController extends ControllerBase {

 /**
  * Symfony\Component\HttpFoundation\RequestStack definition.
  *
  * @var \Symfony\Component\HttpFoundation\RequestStack
  */
 protected $requestStack;

 /**
  * The logger factory.
  *
  * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
  */
 protected $loggerFactory;

 /**
  * Constructs a new DefaultController object.
  */
 public function __construct(RequestStack $request_stack, LoggerChannelFactoryInterface $loggerFactory) {
   $this->requestStack = $request_stack;
   $this->loggerFactory = $loggerFactory;
 }

 /**
  * {@inheritdoc}
  */
 public static function create(ContainerInterface $container) {
   return new static(
     $container->get('request_stack'),
     $container->get('logger.factory')
   );
 }

 /**
  * Handlerequest.
  *
  * @return mixed
  *   Return Hello string.
  */
 public function handleRequest() {
   $this->loggerFactory->get('droogle')->info('droogle triggered');
   $this->processRequest();
   $data = [
     'speech' => 'Cache Rebuild Completed for the Site',
     'displayText' => 'Cache Rebuild Completed',
     'data' => '',
     'contextOut' => [],
     'source' => 'uniworld',
   ];
   return JsonResponse::create($data, 200);
 }

 protected function processRequest() {
   $params = $this->requestStack->getCurrentRequest();
   // Here we will process the request to get intent

   // and fulfill the action.
 }
}

Done! We are ready with a request handler to process the request that will be made by Google Assistant.

 

STEP3: DEPLOY FULFILLMENT AND TESTING THE APP

Part of the deployment has already been done, as we are developing on our local only. Now, we need to enable our custom module. Post that let's get back to dialogflow and establish the connection with app to test this. Earlier we had configured fulfillment URL details, ensure we have enabled webhook for all domains.

deployment

 

Let’s get back to intent that we build and enable webhook there too and save the intent.

intent enable webhook

Now, to test this we need to integrate it any of the device or live/sandbox app. Under Integrations tab, google provides several options for this too. Enable for Web Demo and open the URL in new tab, to test this:

Integration Web Demo

Speak up and test your newly build APP and let Google Assistant do its work.

So, as seen in the screenshot, there can be 2 type of responses. First, where our server is not able to handle request properly and the second one where Drupal server sends a valid JSON response.

GREAT! Connection is now established, you can now add intents in Google Action APP and correspondingly handle that intent and action at Drupal End. This is just a taste, conversational UX and Assistant technology will definitely impact how we interact with technology and we believe Drupal has a great role to play as a robust backend.

Dec 06 2017
Dec 06
In last couple of years we have seen the rise of assistants, AI is enabling our lives more and more and with help of devices like Google Home and Amazon Echo, its now entering our living rooms and changing how we interact with technology.
Nov 23 2017
Nov 23

Why do we need to override Config Entity Types?

  1. By default, Vocabulary list displays all the vocabularies. In case we want to restrict certain roles from viewing certain vocabularies. Overriding that Class(VocabularyListBuilder) function would be the solution to display specific/no/all vocabularies.
     
  2. Let's assume we need to specify vocabulary-path for each vocabulary apart from name, title, description, vid etc. In this case we would need to override the default Vocabulary Form of taxonomy_vocabulary config entity type.
     
  3. Suppose we want to custom access check for views on the basis of role/user/views operation or whatever, we would need to override ViewsAccessControllerhandler of view configEntityType and write our own logic.
     
  4. Another use case can be, if we want to display all the image fields which use image style being deleted, on confirm text message, we again need to override ImageStyleFlushForm class and redefine getconfirmText function.

In short, to customise and meet our dynamic requirements which may not be supported by config entity type definition as a part of @ConfigEntityType annotations in core or contributed modules, we need to override existing config entity types and write some custom code :).

How can we override Config Entity Types?

Entity types use object based annotation unlike array based annotation which is commonly used. Also, Unlike Content Entity Types where every thing is a field, NOTHING is a field for Configuration Entity type.

Every Drupal config entity type is defined as a particular ConfigEntityType Annotation. Entity controller is completely different from the Controller of MVC pattern. To avoid this confusion in terminology Entity Controllers are termed as handlers, each form related to a particular entity type say taxonomy_vocabulary is declared inside handlers with form key. 

In this article, will take an example of adding custom form elements to config entity type forms to explain this.

In case we need to add a custom element to any of these forms, we need to follow these 2 steps:

I) Set a new handler class specific to that form.

  1. Implement hook_entity_type_alter(array &$entity_types).
  2. Set new handler class as : 
    $entity_types[{id}]->setHandlerClass('form',
     ['{form_type}' => 'Drupal\my_module\MyModuleForm',
     '....',
     '....'
     ]);
    

    where, id = configEntityType id,  form_type eg: default, reset, delete etc is whichever form we want to override and MyModuleForm is the Class name of new form we'll define in Step II.
    Here is the sample code of overriding default form of taxonomy vocabulary.

    $entity_types['taxonomy_vocabulary']->setHandlerClass('form',
     ['default' => 'Drupal\my_module\VocabularyForm',
     'reset' => 'Drupal\taxonomy\Form\VocabularyResetForm',
     'delete' => 'Drupal\taxonomy\Form\VocabularyDeleteForm'
     ]);
    

II) Define the class set in Step I.

  1. Extend the actual class of the form we want to add new form elements to. 
    use Drupal\taxonomy\VocabularyForm as VocabularyFormBuilderBase;
    
    {this is optional, we need to do this to keep the new class name same as base class i.e VocabularyForm}.
    class MyModuleForm extends VocabularyFormBuilderBase 
    
    OR simply, 
    class MyModuleForm extends VocabularyForm
    

    This override is important because we need to inherit functions and form elements defined in the parent class i.e VocabularyForm and also add additional feature i.e form element without disturbing the core code. This is purely OOPs concept of inheritance.

  2. We need to override the form function by 
    1. Inheriting the parent elements by parent::form(....) and
    2. Defining the new custom elements as the basic example below:
      $form['third_party_settings']['qed42_textfield'] = array(
       '#type' => 'textfield',
       '#title' => t('QED42 Custom Form Element'),
       '#default_value' => $vocabulary->getThirdPartySetting('my_module', 'qed42_textfield', 'Qed42 textfield default value')
      );  
      

      Config Entities have by default "getThirdPartySetting()" function { Config entities inherit this function if these extend ConfigEntityBase class which implements ConfigEntityInterface interface which in turn extends ThirdPartySettingsInterface interface}. This thirdParty function allows to set and retrieve a value particularly for a module.

    3. Similarly, we can inherit the save function to save the value of newly added form element with Third Party Settings  as : 
      If the form is set as Tree we need to set value as
      $vocabulary->setThirdPartySetting('my_module', 'qed42_textfield', $form_state->getValue('third_party_settings')['qed42_textfield']);
      
      else :
      $vocabulary->setThirdPartySetting('my_module', 'qed42_textfield', $form_state->getValue('qed42_textfield'));
      
      and of course inherit the parent save function. 
       
    4. We can implement the same logic for extending definition of any existing method  AND can also define new functions inside our new Form.

Any Configuration Entity Type (Date format etc) can be overridden similarly, this can be extended to list_builder, access etc.  Apart from overriding, we can also add new flavours of entity controller using the above steps.

Nov 22 2017
Nov 22

Why do we need to override Config Entity Types?

  1. By default, Vocabulary list displays all the vocabularies. In case we want to restrict certain roles from viewing certain vocabularies. Overriding that Class(VocabularyListBuilder) function would be the solution to display specific/no/all vocabularies.

  2. Let's assume we need to specify vocabulary-path for each vocabulary apart from name, title, description, vid etc. In this case we would need to override the default Vocabulary Form of taxonomy_vocabulary config entity type.

  3. Suppose we want to custom access check for views on the basis of role/user/views operation or whatever, we would need to override ViewsAccessControllerhandler of view configEntityType and write our own logic.

  4. Another use case can be, if we want to display all the image fields which use image style being deleted, on confirm text message, we again need to override ImageStyleFlushForm class and redefine getconfirmText function.

In short, to customise and meet our dynamic requirements which may not be supported by config entity type definition as a part of @ConfigEntityType annotations in core or contributed modules, we need to override existing config entity types and write some custom code :).

How can we override Config Entity Types?

Entity types use object based annotation unlike array based annotation which is commonly used. Also, Unlike Content Entity Types where every thing is a field, NOTHING is a field for Configuration Entity type.

Every Drupal config entity type is defined as a particular ConfigEntityType Annotation. Entity controller is completely different from the Controller of MVC pattern. To avoid this confusion in terminology Entity Controllers are termed as handlers, each form related to a particular entity type say taxonomy_vocabulary is declared inside handlers with form key. 

In this article, will take an example of adding custom form elements to config entity type forms to explain this.

In case we need to add a custom element to any of these forms, we need to follow these 2 steps:

I) Set a new handler class specific to that form.

  1. Implement hook_entity_type_alter(array &$entity_types).
  2. Set new handler class as : 
    $entity_types[{id}]->setHandlerClass('form',
     ['{form_type}' => 'Drupal\my_module\MyModuleForm',
     '....',
     '....'
     ]);
    

    where, id = configEntityType id,  form_type eg: default, reset, delete etc is whichever form we want to override and MyModuleForm is the Class name of new form we'll define in Step II.
    Here is the sample code of overriding default form of taxonomy vocabulary.

    $entity_types['taxonomy_vocabulary']->setHandlerClass('form',
     ['default' => 'Drupal\my_module\VocabularyForm',
     'reset' => 'Drupal\taxonomy\Form\VocabularyResetForm',
     'delete' => 'Drupal\taxonomy\Form\VocabularyDeleteForm'
     ]);
    

II) Define the class set in Step I.

  1. Extend the actual class of the form we want to add new form elements to. 
    use Drupal\taxonomy\VocabularyForm as VocabularyFormBuilderBase;
    

    {this is optional, we need to do this to keep the new class name same as base class i.e VocabularyForm}.

    class MyModuleForm extends VocabularyFormBuilderBase 
    

    OR simply, 

    class MyModuleForm extends VocabularyForm
    

    This override is important because we need to inherit functions and form elements defined in the parent class i.e VocabularyForm and also add additional feature i.e form element without disturbing the core code. This is purely OOPs concept of inheritance.

  2. We need to override the form function by 
    1. Inheriting the parent elements by parent::form(....) and
    2. Defining the new custom elements as the basic example below:
      $form['third_party_settings']['qed42_textfield'] = array(
       '#type' => 'textfield',
       '#title' => t('QED42 Custom Form Element'),
       '#default_value' => $vocabulary->getThirdPartySetting('my_module', 'qed42_textfield', 'Qed42 textfield default value')
      );  
      

      Config Entities have by default "getThirdPartySetting()" function { Config entities inherit this function if these extend ConfigEntityBase class which implements ConfigEntityInterface interface which in turn extends ThirdPartySettingsInterface interface}. This thirdParty function allows to set and retrieve a value particularly for a module.

    3. Similarly, we can inherit the save function to save the value of newly added form element with Third Party Settings  as : 

      If the form is set as Tree we need to set value as

      $vocabulary->setThirdPartySetting('my_module', 'qed42_textfield', $form_state->getValue('third_party_settings')['qed42_textfield']);
      

      else :

      $vocabulary->setThirdPartySetting('my_module', 'qed42_textfield', $form_state->getValue('qed42_textfield'));
      

      and of course inherit the parent save function. 

    4. We can implement the same logic for extending definition of any existing method  AND can also define new functions inside our new Form.

Any Configuration Entity Type (Date format etc) can be overridden similarly, this can be extended to list_builder, access etc.  Apart from overriding, we can also add new flavours of entity controller using the above steps.

Sep 30 2017
Sep 30

We are in an era where we see a lots of third party integrations being done in projects. In Drupal based projects, cookie management is done via Drupal itself to maintain session, whether it be a pure Drupal project or decoupled Drupal project,.

But what when we have a scenario where user’s information is being managed by a third party service and no user information is being saved on Drupal? And when the authentication is done via some other third party services? How can we manage cookie in this case to run our site session and also keep it secure?

One is way is to set and maintain cookie on our own. In this case, our user’s will be anonymous to Drupal. So, we keep session running based on cookies! The user information will be stored in cookie itself, which then can be validated when a request is made to Drupal.

We have a php function to set cookie called setCookie() , which we can use to create and destroy cookie. So, the flow will be that a user login request which is made to website is verified via a third party service and then we call setCookie function which sets the cookie containing user information. But, securing the cookie is must, so how do we do that?

For this, let’s refer to Bakery module to see how it does it. It contains functions for encrypting cookie, setting it and validating it.

To achieve this in Drupal 8, we will write a helper class let’s say “UserCookie.php” and place it in ‘{modulename}/src/Helper/’. Our cookie helper class will contain static methods for setting cookie and validating cookie. Static methods so that we will be able to call them from anywhere.

We will have to encrypt cookie before setting it so we will use openssl_encrypt() php function in following manner:

/**
* Encrypts given cookie data.
*
* @param string $cookieData
*   Serialized Cookie data for encryption.
*
* @return string
*   Encrypted cookie.
*/
private static function encryptCookie($cookieData) {

 // Create a key using a string data.
 $key = openssl_digest(Settings::get('SOME_COOKIE_KEY'), 'sha256');

 // Create an initialization vector to be used for encryption.
 $iv = openssl_random_pseudo_bytes(16);

 // Encrypt cookie data along with initialization vector so that initialization
 // vector can be used for decryption of this cookie.
 $encryptedCookie = openssl_encrypt($iv . $cookieData, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);

 // Add a signature to cookie.
 $signature = hash_hmac('sha256', $encryptedCookie, $key);

 // Encode signature and cookie.
 return base64_encode($signature . $encryptedCookie);
}

  1. String parameter in openssl_digest can be replaced with any string you feel like that can be used as key. You can keep simple keyword too.
  2. Key used should be same while decryption of data.
  3. Same initialization vector will be needed while decrypting the data, so to retrieve it back we append this along with cookie data string.
  4. We also add a signature which is generate used the same key used above. We will verify this key while validating cookie.
  5. Finally, we encode both signature and encrypted cookie data together.

For setting cookie:
 

/**
* Set cookie using user data.
*
* @param string $name
*   Name of cookie to store.
* @param mixed $data
*   Data to store in cookie.
*/
public static function setCookie($name, $data) {
$data = (is_array($data)) ? json_encode($data) : $data;
$data = self::encrypt($data);
 setcookie($name, $cookieData,Settings::get('SOME_DEFAULT_COOKIE_EXPIRE_TIME'), '/');
}

Note: You can keep 'SOME_COOKIE_KEY' and 'SOME_DEFAULT_COOKIE_EXPIRE_TIME' in your settings.php. Settings::get() will fetch that for you.
Tip: You can also append and save expiration time of cookie in encrypted data itself so that you can also verify that at time of decryption. This will stop anyone from extending the session by setting cookie timing manually.

Congrats! We have successfully encrypted the user data and set it into a cookie.

Now let’s see how we can decrypt and validate the same cookie.

To decrypt cookie:

/**
* Decrypts the given cookie data.
*
* @param string $cookieData
*   Encrypted cookie data.
*
* @return bool|mixed
*   False if retrieved signature doesn't matches
*   or data.
*/
public static function decryptCookie($cookieData) {

 // Create a key using a string data used while encryption.
 $key = openssl_digest(Settings::get('SOME_COOKIE_KEY'), 'sha256');

 // Reverse base64 encryption of $cookieData.
 $cookieData = base64_decode($cookieData);

 // Extract signature from cookie data.
 $signature = substr($cookieData, 0, 64);

 // Extract data without signature.
 $encryptedData = substr($cookieData, 64);

 // Signature should match for verification of data.
 if ($signature !== hash_hmac('sha256', $encryptedData, $key)) {
   return FALSE;
 }

 // Extract initialization vector from data appended while encryption.
 $iv = substr($string, 64, 16);

 // Extract main encrypted string data which contains profile details.
 $encrypted = substr($string, 80);

 // Decrypt the data using key and
 // initialization vector extracted above.
 return openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
}

  1. We generate the same key using same string parameter given while encryption.
  2. Then we reverse base64 encoding as we need extract signature to verify it.
  3. We generate same signature again as we have used the same key which was used to creating signature while encryption. If doesn’t signatures doesn’t matches, validation fails!
  4. Else, we extract initialization vector from the encrypted data and use to decrypt the data return to be utilized.
/**
* Validates cookie.
*
* @param string $cookie
*   Name of cookie.
*
* @return boolean
*   True or False based on cookie validation.
*/
public static function validateCookie($cookie) {
 if (self::decryptCookie($cookieData)) {
   return TRUE;
 }
 return FALSE;
}

We can verify cookie on requests made to website to maintain our session. You can implement function for expiring cookie for simulating user logout. We can also use decrypted user data out of cookie for serving user related pages.

Sep 28 2017
Sep 28

Disclaimer: This is a temporary workaround which worked for me, not a permanent fix and may not work for everyone

Many of us have already tried to enjoy the flavor of new OS by Apple, macOS High Sierra. And doing that might have given you nightmares, if you were using vagrant based project.

If anyone is facing missing files or file changes not getting detected issue inside Vagrant, this might be because of compatibility issues with Apple’s latest APFS (apple file system) and Vagrant’s synced folder type: nfs.

I experienced this a couple of weeks ago, when I upgraded to beta version of masOS High Sierra. I was not able to see few modules (like views, taxonomy) and files present inside vagrant (guest) even though these were actually present in my mac machine (host). This is an already reported issue.

After struggling for next few days and nights, I found the quickest possible solution for this problem which worked for me.

Please follow the steps mentioned below:

  1. Install the vagrant gatling rsync plugin.
    vagrant plugin install vagrant-gatling-rsync
    
  2. Do changes in VagrantFile as mentioned here : https://github.com/smerrill/vagrant-gatling-rsync

    Add the following part only:

    # Configure the window for gatling to coalesce writes.
     if Vagrant.has_plugin?("vagrant-gatling-rsync")
       config.gatling.latency = 2.5
       config.gatling.time_format = "%H:%M:%S"
     end
    
     # Automatically sync when machines with rsync folders come up.
     config.gatling.rsync_on_startup = true
    
    
    Add this code in Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| function of your VagrantFile. You can also refer to VagrantFile line number: 108 & 114 given in https://github.com/navneet0693/high-sierra-vagrant-problem.
     
  3. Important: Change vagrant_synced_folders type from `type: nfs` to `type: rsync` in box/config.yml. Sync folder is mostly your project repo, which shared b/w host (mac) and guest (vagrant box).
     
    vagrant_synced_folders:
      # The first synced folder will be used for the default Drupal installation, if
      # build_makefile: is 'true'.
      - local_path: ..
        destination: /var/www/demo-drupal
        type: rsync
        create: true
    
    
  4. Then you will have provision your Vagrant again.
    vagrant reload --provision
    
Nov 28 2016
Nov 28

This article assumes you are familiar with what RESTful is & what do we mean when we use the term REST API. Some of you might have already worked with RESTful Web Services module in D7, it exposes all entity types as web services using REST architecture. Drupal 8 out of the box is RESTful with core support. All entities (provided by core + ones created using Entity API) are RESTful resources.

To explore the RESTful nature of Drupal 8, we will need to enable the following modules:

In Core

  • HAL - Serializes entities using Hypertext Application Language.
  • HTTP Basic Authentication - Provides the HTTP Basic authentication provider.
  • RESTful Web Services - Exposes entities and other resources as RESTful web API
  • Serialization - Provides a service for (de)serializing data to/from formats such as JSON and XML.

Contributed

  • REST UI - Provides a user interface to manage REST resources.

RESTful Resources

Every entity in D8 is a resource, which has an end point. Since, its RESTful, the same end-point is used for CRUD (Create, Read, Update, Delete) operations with different HTTP verbs. Postman is an excellent tool to explore / test RESTful services.  Drupal 8 allows you to selectively choose & enable a REST API. e.g., we can choose to expose only nodes via a REST API & not other entities like users, taxonomy, comments etc.

After enabling REST_UI module we can see list of all RESTful resources at /admin/config/services/rest. In addition to ability to choose the entity one can enable, we can also choose the authentication method per resource & enable specific CRUD operations per resource.

Resource Settings

Let us take a look at what the REST APIs for User entity would be after we save the configuration in the above screenshot.

User

POST

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "name": {
   "value":"testuser"
 },
 "mail":{
   "value":"[email protected]"
 },
 "pass":{
   "value":"testpass"
 },
 "status": {
   "value": 1
 }
}

Header

X-CSRF-Token: Get from http://domain.com/rest/session/token
Content-Type: application/hal+json
Accept: application/hal+json
Authorization: Basic (hashed username and password)

Note: Drupal 8 doesn't allow anonymous user to send a POST on user resource. It is already fixed in 8.3.x branch but for now we can pass the credentials of the user who have permission to create users. If you are interested in taking a deeper look at the issue, you can follow https://www.drupal.org/node/2291055.

Response: You will get a user object with "200 OK" response code

 

PATCH

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "pass":[{"existing":"testpass"}],
 "mail":{
   "value":"[email protected]"
 }
}

Note: Now as user have permission to update his own profile so we can pass current user's credentials in authentication header.

Response: You will get "204 No Content" in response code.

 

GET

http:

Response: You will get a user object with "200 OK" response code.

 

DELETE

http:

Response: You will get "204 No Content" in response code.

RESTful Views and Authentication

Drupal 8 also allows us to export views as a REST service. It allows you to use all the available authentication mechanism in views itself.

JSON API Module

JSON API module provides a new format called "api_json" which is soon becoming the de-facto standard for Javascript Frontend frameworks, If you plan to use completely de-coupled Drupal with frontend framework like Angular / React / Ember then its worth a look. To read more about JSON API you can visit the site.

Nov 28 2016
Nov 28

This article assumes you are familiar with what RESTful is & what do we mean when we use the term REST API. Some of you might have already worked with RESTful Web Services module in D7, it exposes all entity types as web services using REST architecture. Drupal 8 out of the box is RESTful with core support. All entities (provided by core + ones created using Entity API) are RESTful resources.

To explore the RESTful nature of Drupal 8, we will need to enable the following modules:

In Core

  • HAL - Serializes entities using Hypertext Application Language.
  • HTTP Basic Authentication - Provides the HTTP Basic authentication provider.
  • RESTful Web Services - Exposes entities and other resources as RESTful web API
  • Serialization - Provides a service for (de)serializing data to/from formats such as JSON and XML.

Contributed

  • REST UI - Provides a user interface to manage REST resources.

RESTful Resources

Every entity in D8 is a resource, which has an end point. Since, its RESTful, the same end-point is used for CRUD (Create, Read, Update, Delete) operations with different HTTP verbs. Postman is an excellent tool to explore / test RESTful services.  Drupal 8 allows you to selectively choose & enable a REST API. e.g., we can choose to expose only nodes via a REST API & not other entities like users, taxonomy, comments etc.

After enabling REST_UI module we can see list of all RESTful resources at /admin/config/services/rest. In addition to ability to choose the entity one can enable, we can also choose the authentication method per resource & enable specific CRUD operations per resource.

Resource Settings

Let us take a look at what the REST APIs for User entity would be after we save the configuration in the above screenshot.

User

POST

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "name": {
   "value":"testuser"
 },
 "mail":{
   "value":"[email protected]"
 },
 "pass":{
   "value":"testpass"
 },
 "status": {
   "value": 1
 }
}

Header

X-CSRF-Token: Get from http://domain.com/rest/session/token
Content-Type: application/hal+json
Accept: application/hal+json
Authorization: Basic (hashed username and password)

Note: Drupal 8 doesn't allow anonymous user to send a POST on user resource. It is already fixed in 8.3.x branch but for now we can pass the credentials of the user who have permission to create users. If you are interested in taking a deeper look at the issue, you can follow https://www.drupal.org/node/2291055.

Response: You will get a user object with "200 OK" response code

 

PATCH

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "pass":[{"existing":"testpass"}],
 "mail":{
   "value":"[email protected]"
 }
}

Note: Now as user have permission to update his own profile so we can pass current user's credentials in authentication header.

Response: You will get "204 No Content" in response code.

 

GET

http:

Response: You will get a user object with "200 OK" response code.

 

DELETE

http:

Response: You will get "204 No Content" in response code.

RESTful Views and Authentication

Drupal 8 also allows us to export views as a REST service. It allows you to use all the available authentication mechanism in views itself.

JSON API Module

JSON API module provides a new format called "api_json" which is soon becoming the de-facto standard for Javascript Frontend frameworks, If you plan to use completely de-coupled Drupal with frontend framework like Angular / React / Ember then its worth a look. To read more about JSON API you can visit the site.

Nov 24 2016
Nov 24

Autocomplete on textfields like tags / user & node reference helps improve the UX and interactivity for your site visitors, In this blog post I'd like to cover how to implement autocomplete functionality in Drupal 8, including implementing a custom callback

Step 1: Assign autocomplete properties to textfield

As per Drupal Change records, #autocomplete_path has been replaced by #autocomplete_route_name and #autocomplete_parameters for autocomplete fields ( More details -- https://www.drupal.org/node/2070985).

The very first step is to assign appropriate properties to the textfield:

  1. '#autocomplete_route_name':
    for passing route name of callback URL to be used by autocomplete Javascript Library.
  2. '#autocomplete_route_parameters':
    for passing array of arguments to be passed to autocomplete handler.
$form['name'] = array(
    '#type' => 'textfield',
    '#autocomplete_route_name' => 'my_module.autocomplete',
    '#autocomplete_route_parameters' => array('field_name' => 'name', 'count' => 10),
);

Thats all! for adding an #autocomplete callback to a textfield. 

However, there might be cases where the routes provided by core might not suffice as we might different response in JSON or additional data. Lets take a look at how to write a autocomplete callback, we will be using using my_module.autocomplete route and will pass arguments: 'name' as field_name and 10 as count.

Step 2: Define autocomplete route

Now, add the 'my_module.autocomplete' route in my_module.routing.yml file as:

my_module.autocomplete:
  path: '/my-module-autocomplete/{field_name}/{count}'
  defaults:
    _controller: '\Drupal\my_module\Controller\AutocompleteController::handleAutocomplete'
    _format: json
  requirements:
    _access: 'TRUE'

While Passing parameters to controller, use the same names in curly braces, which were used while defining the autocomplete_route_parameters. Defining _format as json is a good practise.

Step 3: Add Controller and return JSON response

Finally, we need to generate the JSON response for our field element. So, proceeding further we would be creating AutoCompleteController class file at my_module > src > Controller > AutocompleteController.php.

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\Unicode;

/**
 * Defines a route controller for entity autocomplete form elements.
 */
class AutocompleteController extends ControllerBase {

  /**
   * Handler for autocomplete request.
   */
  public function handleAutocomplete(Request $request, $field_name, $count) {
    $results = [];

    // Get the typed string from the URL, if it exists.
    if ($input = $request->query->get('q')) {
      $typed_string = Tags::explode($input);
      $typed_string = Unicode::strtolower(array_pop($typed_string));
      // @todo: Apply logic for generating results based on typed_string and other
      // arguments passed.
      for ($i = 0; $i < $count; $i++) {
        $results[] = [
          'value' => $field_name . '_' . $i . '(' . $i . ')',
          'label' => $field_name . ' ' . $i,
        ];
      }
    }

    return new JsonResponse($results);
  }

}

We would be extending ControllerBase class and would then define our handler method, which will return results. Parameters for the handler would be Request object and arguments (field_name and count) passed in routing.yml file. From the Request object, we would be getting the typed string from the URL. Besides, we do have other route parameters (field_name and Count) on the basis of which we can generate the results array. 

An important point to be noticed here is, we need the results array to have data in 'value' and 'label' key-value pair as we have done above. Then finally we would be generating JsonResponse by creating new JsonResponse object and passing $results.

That's all we need to make autocomplete field working. Rebuild the cache and load the form page to see results.

Nov 24 2016
Nov 24

Autocomplete on textfields like tags / user & node reference helps improve the UX and interactivity for your site visitors, In this blog post I'd like to cover how to implement autocomplete functionality in Drupal 8, including implementing a custom callback

Step 1: Assign autocomplete properties to textfield

As per Drupal Change records, #autocomplete_path has been replaced by #autocomplete_route_name and #autocomplete_parameters for autocomplete fields ( More details -- https://www.drupal.org/node/2070985).

The very first step is to assign appropriate properties to the textfield:

  1. '#autocomplete_route_name':
    for passing route name of callback URL to be used by autocomplete Javascript Library.
  2. '#autocomplete_route_parameters':
    for passing array of arguments to be passed to autocomplete handler.
$form['name'] = array(
    '#type' => 'textfield',
    '#autocomplete_route_name' => 'my_module.autocomplete',
    '#autocomplete_route_parameters' => array('field_name' => 'name', 'count' => 10),
);

Thats all! for adding an #autocomplete callback to a textfield. 

However, there might be cases where the routes provided by core might not suffice as we might different response in JSON or additional data. Lets take a look at how to write a autocomplete callback, we will be using using my_module.autocomplete route and will pass arguments: 'name' as field_name and 10 as count.

Step 2: Define autocomplete route

Now, add the 'my_module.autocomplete' route in my_module.routing.yml file as:

my_module.autocomplete:
  path: '/my-module-autocomplete/{field_name}/{count}'
  defaults:
    _controller: '\Drupal\my_module\Controller\AutocompleteController::handleAutocomplete'
    _format: json
  requirements:
    _access: 'TRUE'

While Passing parameters to controller, use the same names in curly braces, which were used while defining the autocomplete_route_parameters. Defining _format as json is a good practise.

Step 3: Add Controller and return JSON response

Finally, we need to generate the JSON response for our field element. So, proceeding further we would be creating AutoCompleteController class file at my_module > src > Controller > AutocompleteController.php.

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\Unicode;

/**
 * Defines a route controller for entity autocomplete form elements.
 */
class AutocompleteController extends ControllerBase {

  /**
   * Handler for autocomplete request.
   */
  public function handleAutocomplete(Request $request, $field_name, $count) {
    $results = [];

    // Get the typed string from the URL, if it exists.
    if ($input = $request->query->get('q')) {
      $typed_string = Tags::explode($input);
      $typed_string = Unicode::strtolower(array_pop($typed_string));
      // @todo: Apply logic for generating results based on typed_string and other
      // arguments passed.
      for ($i = 0; $i < $count; $i++) {
        $results[] = [
          'value' => $field_name . '_' . $i . '(' . $i . ')',
          'label' => $field_name . ' ' . $i,
        ];
      }
    }

    return new JsonResponse($results);
  }

}

We would be extending ControllerBase class and would then define our handler method, which will return results. Parameters for the handler would be Request object and arguments (field_name and count) passed in routing.yml file. From the Request object, we would be getting the typed string from the URL. Besides, we do have other route parameters (field_name and Count) on the basis of which we can generate the results array. 

An important point to be noticed here is, we need the results array to have data in 'value' and 'label' key-value pair as we have done above. Then finally we would be generating JsonResponse by creating new JsonResponse object and passing $results.

That's all we need to make autocomplete field working. Rebuild the cache and load the form page to see results.

Nov 10 2016
Nov 10

Drupal sites with events functionality, often have to allow their users to export events in their personal calendars. On a recent Drupal 8 project we were asked to integrate 3rd party service Add to Calendar to their events and having found no formal integration of the widget with Drupal we developed and contributed this module. The widget provided by Add to calendar supports export of Dates / events to iCalender, Google Calendar, Outlook, Outlook Online and Yahoo Calendar.

add-to-calendar-blue

 

Why use Add To Calendar Service?

  • Add to Calendar Module provides a widget to export events.
  • With Add to Calendar Module, you can create event button on a page and allow guests to add this event to their calendar.

How Does Add to Calendar Module Works?

Add to Calendar Module provides third party field formatter settings for DateTime fields. Module internally uses services provided by http://addtocalendar.com to load free add to calendar button for event page on website and email. Clicking on this button, the event is exported to the corresponding website with proper information in the next tab where a user can add the event to their calendar. Besides, it provides a handful of configuration for a really flexible experience, Allowing you to use your datetime format along with Add to Calendar button.

Using Add to Calendar

  1. Download and enable Add to Calendar module (https://www.drupal.org/project/addtocalendar)

  2. Adding Add to Calendar button to any datetime field would require enabling “Show Add to Calendar” checkbox present at format configurations on Manage Display page of the desired content type.

add-to-calendar-manage-display

 

  1. Following configurations are available:

Option Description Style Three basic styles are available: Basic, Blue and Glow Orange Display Text Text for the display button. Event Details Module provides you three options here. You may opt for static data, tokenized value or any field value, specific to the current entity. Privacy Use public for free access to event information while private if the event is closed to public access. Security Level To specify whether button link should use http or https Calendars to show Select Calendars to be enabled for the display.

4. Save the settings and visit content display page.

Developer Support

Devs have the option to add "Add to Calendar" button anywhere on the website by following below steps:

1. Include base library ('addtocalendar/base') for add to calendar basic functionality. Optionally, You may also one of the following style libraries for styling the display button:

  • 'addtocalendar/blue'
  • 'addtocalendar/glow_orange'
$variables['#attached']['library'][] = 'addtocalendar/base';

2. Place event data on the page as:

<span class="addtocalendar atc-style-blue">
<var class="atc_event">
<var class="atc_date_start">2016-05-04 12:00:00</var>
<var class="atc_date_end">2016-05-04 18:00:00</var>
<var class="atc_timezone">Europe/London</var>
<var class="atc_title">Star Wars Day Party</var>
<var class="atc_description">May the force be with you</var>
<var class="atc_location">Tatooine</var>
<var class="atc_organizer">Luke Skywalker</var>
<var class="atc_organizer_email">[email protected]</var>
</var>
</span>

For further customization of this custom button visit: http://addtocalendar.com/ Event Data Options section.

3. This would create "Add to Calendar" button for your website.

 

Nov 08 2016
Nov 08

Drupal sites with events functionality, often have to allow their users to export events in their personal calendars. On a recent Drupal 8 project we were asked to integrate 3rd party service Add to Calendar to their events and having found no formal integration of the widget with Drupal we developed and contributed this module. The widget provided by Add to calendar supports export of Dates / events to iCalender, Google Calendar, Outlook, Outlook Online and Yahoo Calendar.

add-to-calendar-blue

 

Why use Add To Calendar Service?

  • Add to Calendar Module provides a widget to export events.
  • With Add to Calendar Module, you can create event button on a page and allow guests to add this event to their calendar.

How Does Add to Calendar Module Works?

Add to Calendar Module provides third party field formatter settings for DateTime fields. Module internally uses services provided by http://addtocalendar.com to load free add to calendar button for event page on website and email. Clicking on this button, the event is exported to the corresponding website with proper information in the next tab where a user can add the event to their calendar. Besides, it provides a handful of configuration for a really flexible experience, Allowing you to use your datetime format along with Add to Calendar button.

Using Add to Calendar

  1. Download and enable Add to Calendar module (https://www.drupal.org/project/addtocalendar)

  2. Adding Add to Calendar button to any datetime field would require enabling “Show Add to Calendar” checkbox present at format configurations on Manage Display page of the desired content type.

add-to-calendar-manage-display

 

  1. Following configurations are available:

Option Description Style Three basic styles are available: Basic, Blue and Glow Orange Display Text Text for the display button. Event Details Module provides you three options here. You may opt for static data, tokenized value or any field value, specific to the current entity. Privacy Use public for free access to event information while private if the event is closed to public access. Security Level To specify whether button link should use http or https Calendars to show Select Calendars to be enabled for the display.

4. Save the settings and visit content display page.

Developer Support

Devs have the option to add "Add to Calendar" button anywhere on the website by following below steps:

1. Include base library ('addtocalendar/base') for add to calendar basic functionality. Optionally, You may also one of the following style libraries for styling the display button:

  • 'addtocalendar/blue'
  • 'addtocalendar/glow_orange'
$variables['#attached']['library'][] = 'addtocalendar/base';

2. Place event data on the page as:

<span class="addtocalendar atc-style-blue">
<var class="atc_event">
<var class="atc_date_start">2016-05-04 12:00:00</var>
<var class="atc_date_end">2016-05-04 18:00:00</var>
<var class="atc_timezone">Europe/London</var>
<var class="atc_title">Star Wars Day Party</var>
<var class="atc_description">May the force be with you</var>
<var class="atc_location">Tatooine</var>
<var class="atc_organizer">Luke Skywalker</var>
<var class="atc_organizer_email">[email protected]</var>
</var>
</span>

For further customization of this custom button visit: http://addtocalendar.com/ Event Data Options section.

3. This would create "Add to Calendar" button for your website.

 

Nov 04 2016
Nov 04

Zooming-in on images with mouse hover is a common feature used on E-commerce websites to let buyers see details of the products. We had a similar requirement on a polymer project for which we integrated the Zoomove jQuery plugin into polymer to use it as reusable component and now open sourcing it. 

Demo - https://qed42.github.io/polymer-zoomove/

Github project -- https://github.com/qed42/polymer-zoomove 

Using zoomove-polymer Element in your Project

Install this element using bower in your project 

$ bower install zoomove-polymer --save-dev

or you can grab the element  from the github from here - https://github.com/qed42/polymer-zoomove  and place it in the components for referencing.

Now you can add the element in your html page using html imports

<link rel="import" href="https://www.qed42.com/blog/polymer-zoomove/../polymer-zoomove.html">

And in your HTML you can now use the tag as follows,

<polymer-zoomove
    image-path="http://lorempixel.com/600/600/animals/"
    image-cover="false">
</polymer-zoomove>
 
<polymer-zoomove
    image-path="http://lorempixel.com/600/600/animals/"
    image-scale="5">
</polymer-zoomove>
 
<polymer-zoomove
    image-path="http://lorempixel.com/600/600/animals/"
    image-cover="true">
</polymer-zoomove>

polymer-zoomove

Available attributes for this elements are:

  1. image-path - The url of the photo to be displayed.
  2. image-scale - Sets the zoom size that should be applied to the image.
  3. image-move - Choose whether the image should move with the mouse move
  4. image-over - With 'over' it is possible to define whether the image may be above
  5. image-cursor - Define the cursor pointer or default
Oct 28 2016
Oct 28

CSSgram module supplements Drupal Image styling experience by making Instagram like filters available to your Drupal 8 site images, we do this with help of CSSgram library. 

Beauty of this module is, it simply uses css to beautify your image.

cssgram-filters-sample

Few CSSGram sample filters applied to an image.

How CSSGram Module works?

CSSGram module uses CSSGram Library for adding filter effects via CSS to the image fields. Module extends Field Formatter Settings to add image filter for that particular field. CSSGram extends field formatter settings and hence these filters can be applied on top of the existing available image formatters and image presets. Allowing you to use your desired image preset along with CSSGram filters.
 

Using CSSGram

  1. Download and enable CSSGram module (https://www.drupal.org/project/cssgram)

  2. Visit Manage Display of content type and for the desired image field, click on the setting link under format column.

  3. Select Filter option lets us choose from the available image filters. Just select the desired image filter and hit update button.

third-party-settings-cssgram
  1. Save the settings and visit the content display page.

Developer Support

Devs have the option to use these filters anywhere on the site by just attaching the ‘cssgram/cssgram’ library and then applying any of the available css filter class to the wrapper element.


function mymodule_preprocess_field(&$variables) {
    // Add desired css class.
    $variables['attributes']['class'] = 'kelvin';
    // Attach cssgram library.
    $variables['#attached']['library'][] = 'cssgram/cssgram';
}
Oct 27 2016
Oct 27

One of the pain points in hybrid app development is data persistence & data storage. Though LocalStorage can be used for storing less critical data like cache, devs usually look at SQLite for consistent data storage backend. SQLite works fine for both the platforms (Android & iOS). 

In this post we discuss how to efficiently work with SQLite using a simple factory that can be used for doing simple operations on your SQLite Database. 

SQLite can be accessed natively only, so you need to install the cordova plugin for SQLite.

Download ngCordova dependancies

bower install ngCordova

Include ng-cordova.min.js in your index.html file before cordova.js and after your AngularJS / Ionic file (since ngCordova depends on AngularJS).

<script src="http://www.qed42.com/blog/sqlite-data-factory-ionic/lib/ngCordova/dist/ng-cordova.js"></script>
<script src="http://www.qed42.com/blog/sqlite-data-factory-ionic/cordova.js"></script>

Inject as an Angular dependency

angular.module('myApp', ['ngCordova'])

Install SQLite Cordova Plugin

cordova plugin add https://github.com/litehelpers/Cordova-sqlite-storage.git

Declare a global variable 

var db = null;

So that 'db' is accessible through our the scope the app.

In your app.js :-

if (window.cordova) {
    $rootScope.showHeader = false;
    db = $cordovaSQLite.openDB({ name: 'myapp.db', location: 'default' });
} else {
    db = window.openDatabase("myapp.db", "1.0", "My app", -1);
}

'myapp.db' is the name of your DB. The github page for cordova-sqlite-storage plugin have examples on how to do different operations on the database, below is a simple factory that can make these operations clean and readable:

.factory('DBA', function($cordovaSQLite, $q, $ionicPlatform) {
        var self = this;
        self.query = function(query, parameters) {
            parameters = parameters || [];
            var q = $q.defer();
            $ionicPlatform.ready(function() {
                $cordovaSQLite.execute(db, query, parameters)
                    .then(function(result) {
                        q.resolve(result);
                    }, function(error) {
                        q.reject(error);
                    });
            });
            return q.promise;
        }
        self.getAll = function(result) {
            var output = [];
            for (var i = 0; i < result.rows.length; i++) {
                output.push(result.rows.item(i));
            }
            return output;
        }
        self.getById = function(result) {
            var output = null;
            output = angular.copy(result.rows.item(0));
            return output;
        }
        return self;
    })
    .factory('Data', function($cordovaSQLite, DBA) {
        var self = this;
        self.all = function() {
            return DBA.query("SELECT key, value FROM your_table")
                .then(function(result) {
                    return DBA.getAll(result);
                });
        }
        self.get = function(key) {
            var parameters = [key];
            return DBA.query("SELECT key , value FROM your_table WHERE key = (?)", parameters)
                .then(function(result) {
                    return DBA.getById(result);
                });
        }
        self.add = function(obj) {
            var parameters = [obj.key, obj.name];
            return DBA.query("INSERT INTO your_table (key , value) VALUES (?,?)", parameters);
        }
        self.remove = function(obj) {
            var parameters = [obj.key];
            return DBA.query("DELETE FROM your_table WHERE key = (?)", parameters);
        }
        self.update = function(oldkey, newDataObj) {
            var parameters = [newDataObj.key, newDataObj.value, oldkey];
            return DBA.query("UPDATE your_table SET key = (?), value = (?) WHERE key = (?)", parameters);
        }
        return self;
    })

Disclaimer -- This factory code is for demonstration only, please sanitise and secure your user inputs. Additionally, you can make "your_table" dynamic depending on your use-case.

You may use this factory and do the CRUD operations

Add name in db  :-

var nameObj = {};
nameObj.key = "name ";
nameObj.name = "Abhay Kumar";
Data.add(nameObj);

Get name :-

Data.get("name").then(function(result) {
    userName = result.value;
});

Update name :-

var  nameObj = {};
nameObj.key = "name";
nameObj.value = "QED42";
Data.update("name", nameObj).then(function(result) {
    console.log("Result :", result);
})

Delete name :-

var nameObj = {};
nameObj.key = "name";
Data.remove(nameObj).then(function(result) {
    console.log("Result :", result);
});

Hope you find it useful, Let us know in Comments if you extend this factory or have a better one!

Oct 25 2016
Oct 25

CSSgram module supplements Drupal Image styling experience by making Instagram like filters available to your Drupal 8 site images, we do this with help of CSSgram library. 

Beauty of this module is, it simply uses css to beautify your image.

cssgram-filters-sample

Few CSSGram sample filters applied to an image.

How CSSGram Module works?

CSSGram module uses CSSGram Library for adding filter effects via CSS to the image fields. Module extends Field Formatter Settings to add image filter for that particular field. CSSGram extends field formatter settings and hence these filters can be applied on top of the existing available image formatters and image presets. Allowing you to use your desired image preset along with CSSGram filters.
 

Using CSSGram

  1. Download and enable CSSGram module (https://www.drupal.org/project/cssgram)

  2. Visit Manage Display of content type and for the desired image field, click on the setting link under format column.

  3. Select Filter option lets us choose from the available image filters. Just select the desired image filter and hit update button.

third-party-settings-cssgram
  1. Save the settings and visit the content display page.

Developer Support

Devs have the option to use these filters anywhere on the site by just attaching the ‘cssgram/cssgram’ library and then applying any of the available css filter class to the wrapper element.


function mymodule_preprocess_field(&$variables) {
    // Add desired css class.
    $variables['attributes']['class'] = 'kelvin';
    // Attach cssgram library.
    $variables['#attached']['library'][] = 'cssgram/cssgram';
}
Oct 25 2016
Oct 25

Drupal has always had excellent support for human-friendly URL’s and SEO in general, from early on we have had the luxury of modules like pathauto offering options to update URL for specific entities, token support and bulk update of URLs. Though bulk update works for most cases, there are some situations where we have to update URLs programmatically, some of the use cases are:

  • Multisite setup -- When your setup has many sites, its inconvenient to use bulk update UI to update aliases for each of your sites. Programmatically updating the aliases is a good choice and can be executed via the update hooks.
  • Conditional Update -- When you wish to update aliases based on certain conditions.

 

In Drupal 8 Pathauto services.yml file we can see that there is a service named ‘pathauto.generator’ which is what we would need. The class for corresponding to this service, PathautoGenerator provides updateEntityAlias method which is what we would be using here:

public function updateEntityAlias(EntityInterface $entity, $op, array $options = array())


 

EntityInterface $entity: So, we need to pass the entity (node, taxonomy_term or user) here.
String $op: Operation to be performed on the entity (‘update’, ‘insert’ or ‘bulkupdate’).
$options: An optional array of additional options.

Now all we need is to loop the entities through this function, these entities may be the user, taxonomy_term or node. We will be using entityQuery and entityTypeManager to load entities, similar to the code below

  $entities = [];
  // Load All nodes.
  $result = \Drupal::entityQuery('node')->execute();
  $entity_storage = \Drupal::entityTypeManager()->getStorage('node');
  $entities = array_merge($entities, $entity_storage->loadMultiple($result));

  // Load All taxonomy terms.
  $result = \Drupal::entityQuery('taxonomy_term')->execute();
  $entity_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
  $entities = array_merge($entities, $entity_storage->loadMultiple($result));

  // Load All Users.
  $result = \Drupal::entityQuery('user')->execute();
  $entity_storage = \Drupal::entityTypeManager()->getStorage('user');
  $entities = array_merge($entities, $entity_storage->loadMultiple($result));

  // Update URL aliases.
  foreach ($entities as $entity) {
    \Drupal::service('pathauto.generator')->updateEntityAlias($entity, 'update');
  }

This works fine and could be used on a small site but considering the real world scenarios we generally perform this type of one-time operation in hook_update_N() and for larger sites, we may have memory issues, which we can resolve by involving batch API to run the updates in the batch. 

Using Batch API in hook_update_n()

As the update process in itself a batch process, so we can’t just use batch_set() to execute our batch process for URL alias update, we need to break the task into smaller chunks and use the $sandbox variable which is passed by reference to update function to track the progress.  

The whole process could be broken down into 4 steps:

  • Collect number of nodes / entities we would be updating the URL for.
  • Use the $sandbox to store information needed to track progress.
  • Process nodes in groups of a certain number (say 20, in our case ).
  • And finally setting the finished status based on progress.
function mymodule_update_8100(&$sandbox) {
  $entities = [];
  $entities['node'] = \Drupal::entityQuery('node')->execute();
  $entities['user'] = \Drupal::entityQuery('user')->execute();
  $entities['taxonomy_term'] = \Drupal::entityQuery('taxonomy_term')->execute();
  $result = [];

  foreach ($entities as $type => $entity_list) {
    foreach ($entity_list as $entity_id) {
      $result[] = [
        'entity_type' => $type,
        'id' => $entity_id,
      ];
    }
  }

  // Use the sandbox to store the information needed to track progression.
  if (!isset($sandbox['current']))
  {
    // The count of entities visited so far.
    $sandbox['current'] = 0;
    // Total entities that must be visited.
    $sandbox['max'] = count($result);
    // A place to store messages during the run.
  }

  // Process entities by groups of 20.
  // When a group is processed, the batch update engine determines
  // whether it should continue processing in the same request or provide
  // progress feedback to the user and wait for the next request.
  $limit = 20;
  $result = array_slice($result, $sandbox['current'], $limit);

  foreach ($result as $row) {
    $entity_storage = \Drupal::entityTypeManager()->getStorage($row['entity_type']);
    $entity = $entity_storage->load($row['id']);

    // Update Entity URL alias.
    \Drupal::service('pathauto.generator')->updateEntityAlias($entity, 'update');

    // Update our progress information.
    $sandbox['current']++;
  }

  $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['current'] / $sandbox['max']);

  if ($sandbox['#finished'] >= 1) {
    return t('The batch URL Alias update is finished.');
  }

}

The process of loading the entities will differ in case we are just updating a single entity type say nodes. In that case, we can use loadMultiple to load all the entities at once per single batch operation. That’s a kind of trade-off we have to do according to our requirements. The crucial part is using sandbox variable and splitting the job into chunks for batch processing.

Oct 23 2016
Oct 23

CSSgram module supplements Drupal Image styling experience by making Instagram like filters available to your Drupal 8 site images, we do this with help of CSSgram library. 

Beauty of this module is, it simply uses css to beautify your image.

cssgram-filters-sample

Few CSSGram sample filters applied to an image.

How CSSGram Module works?

CSSGram module uses CSSGram Library for adding filter effects via CSS to the image fields. Module extends Field Formatter Settings to add image filter for that particular field. CSSGram extends field formatter settings and hence these filters can be applied on top of the existing available image formatters and image presets. Allowing you to use your desired image preset along with CSSGram filters.
 

Using CSSGram

  1. Download and enable CSSGram module (https://www.drupal.org/project/cssgram)

  2. Visit Manage Display of content type and for the desired image field, click on the setting link under format column.

  3. Select Filter option lets us choose from the available image filters. Just select the desired image filter and hit update button.

third-party-settings-cssgram
  1. Save the settings and visit the content display page.

Developer Support

Devs have the option to use these filters anywhere on the site by just attaching the ‘cssgram/cssgram’ library and then applying any of the available css filter class to the wrapper element.


function mymodule_preprocess_field(&$variables) {
    // Add desired css class.
    $variables['attributes']['class'] = 'kelvin';
    // Attach cssgram library.
    $variables['#attached']['library'][] = 'cssgram/cssgram';
}
Oct 20 2016
Oct 20

Android Permission Provisioning has changed recently, If an app is using Android SDK API level 22 or below, users are asked for all the permissions in bulk at the time of installation i.e. Without granting permission user can not install the app. Now with Android 6's security patch called Run Time Permission (API level 23 and above) the app while in use can request for specific permission when the need arise ( similar to iOS ) e.g. you will be asked for the location's permission when you actually try to access the location of the device.

To work with runtime permissions on Ionic, you would need to install Cordova diagnostic plugin which gives you the function to fetch the status of the native api's exposed to the app. If a certain permission is mandatory for you app you can prompt the user to grant access to proceed. Further you have specific functions for granting permissions.

Install the plugin

cordova plugin add cordova.plugins.diagnostic

To avail the features of Run Time Permission, you have to build the app with Android platform 6.

Check you current Android platform version

ionic platform

If the version of your Android's Platform is below 5, you will need to update it to 5 or above.

Remove android platform:

ionic platform remove android

Install Android platform version 5 or above:

ionic platform add [email protected]

In config.xml, set the target of sdk version to 23

<preference name="android-targetSdkVersion" value="23" />

So far we are all set to ask user's for permission on the fly. We will have to call a functions to fetch the status of particular permission for the app. On the basis of the status we will ask the user to grant permissions or ignore it.

Add the following function in your app.js in $ionicPlatform.ready() function.

This will make $rootScope.checkPermission() global and you can call it whenever you wish to check if the user has given the permission to fetch device's location.

$rootScope.checkPermission = function() {
  setLocationPermission = function() {
    cordova.plugins.diagnostic.requestLocationAuthorization(function(status) {
      switch (status) {
        case cordova.plugins.diagnostic.permissionStatus.NOT_REQUESTED:
          break;
        case cordova.plugins.diagnostic.permissionStatus.DENIED:
          break;
        case cordova.plugins.diagnostic.permissionStatus.GRANTED:
          break;
        case cordova.plugins.diagnostic.permissionStatus.GRANTED_WHEN_IN_USE:
          break;
      }
    }, function(error) {}, cordova.plugins.diagnostic.locationAuthorizationMode.ALWAYS);
  };
  cordova.plugins.diagnostic.getPermissionAuthorizationStatus(function(status) {
    switch (status) {
      case cordova.plugins.diagnostic.runtimePermissionStatus.GRANTED:
        break;
      case cordova.plugins.diagnostic.runtimePermissionStatus.NOT_REQUESTED:
        setLocationPermission();
        break;
      case cordova.plugins.diagnostic.runtimePermissionStatus.DENIED:
        setLocationPermission();
        break;
      case cordova.plugins.diagnostic.runtimePermissionStatus.DENIED_ALWAYS:
        setLocationPermission();
        break;
    }
  }, function(error) {}, cordova.plugins.diagnostic.runtimePermission.ACCESS_COARSE_LOCATION);
};
Run time location permission

Here is a link of the code snippet.

Of course, you can choose to skip all this and stick to sdk target version 22, but you will miss out the new cool feature of Android 6 and amazing user experience. 

Oct 17 2016
Oct 17

One of the popular Growth hacking technique for e-commerce and SaaS businesses is Referrals, which is to leverage your user's network to get new users by offering incentives / discounts. On a recent e-commerce project we had the requirement to create a complete referral system but couldn't find a module that came close to fulfilling the requirements, hence we developed and contributed Commerce Referral Discount. This module allows us to provide a discount credit to an existing user for referring new users. Discounts can be configured for both the referring user and the new user who joins as part of the referral. Lets see a typical flow:

  • User A invites user B to signup on website using a unique invite URL (http://yoursite.com/invite/username).

  • User B visits the site using this URL, and is taken to the registration form to create a new account.

  • User B gets some discount amount (say $5) which he could use on his first purchase.

referral discount on order

 

  • After user B makes his first purchase, user A gets a discount amount (say $10), which could be used in the next purchase.

  • Both discount amounts are configurable from the admin backend.

Module Configuration:

  • To configure the discount amounts browse to /admin/config/referral-discount
discount amount configuration

 

referral discount type

 

Commerce Referral Discount module provide "Invite/Referral Link" block, which contains unique refer/invite link for authenticated users to share across their friends. 


The module also integrates with views:

  1. It provides a view type 'Commerce Referral Discount' which can be used to list down all the discounts and other data which it stores in the 'commerce_referral_discount' table in database.
  2. It also provides relationship to the user entity, so you can also include the data of Referrer user and Invited user.

 

Sep 26 2016
Sep 26

Drupal caching layer has become more advanced with the advent of cache tags & contexts in Drupal 8. In Drupal 7, the core din't offer many options to cache content for authenticated users. Reverse proxies like Varnish, Nginx etc. could only benefit the anonymous users.  Good news is Drupal 8 handle many painful caching scenarios ground up and have given developers / site builders array of options, making Drupal 8 first class option for all sort of performance requirements.  

Lets look at the criteria for an un-cacheable content:

  • Content that would have a very high cardinality e.g, a block that needs to display user info
  • Blocks that need to display content with a very high invalidation rate. e.g, a block displaying current timestamp

In both of the cases above, we would have a mix of dynamic & static content. For simplicity consider a block rendering current timestamp like "The current timestamp is 1421318815". This rendered content consists of 2 parts:

  • static/cacheable: "The current timestamp is"
  • Dynamic: 1421318815

Drupal 8 rendering & caching system provides us with a way to cache static part of the block, leaving out the dynamic one to be uncached. It does so by using the concept of lazy builders. Lazy builders as the name suggests is very similar what a lazy loader does in Javascript. Lazy builders are replaced with unique placeholders to be processed later once the processing is complete for cached content. So, at any point in rendering, we can have n cached content + m placeholders. Once the processing for cached content is complete, Drupal 8 uses its render strategy(single flush) to process all the placeholders & replace them with actual content(fetching dynamic data). The placeholders can also be leveraged by the experimental module bigpipe to render the cached data & present it to the user, while keep processing placeholders in the background. These placeholders as processed, the result is injected into the HTML DOM via embedded AJAX requests.

Lets see how lazy builders actually work in Drupal 8. Taking the above example, I've create a simple module called as timestamp_generator. This module is responsible for providing a block that renders the text "The current timestamp is {{current_timestamp}}".

<?php
/**
* @file
* Contains \Drupal\timestamp_generator\Plugin\Block\Timestamp.
*/
namespace Drupal\timestamp_generator\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Timestamp' block.
*
* @Block(
* id = "timestamp",
* admin_label = @Translation("Timestamp"),
* )
*/
class Timestamp extends BlockBase {
/**
* [email protected]}
*/
public function build() {
$build = [];
$user = \Drupal::currentUser()->getAccount();
$build['timestamp'] = array(
'#lazy_builder' => ['timestamp_generator.generator:generateUserTimestamp', array()],
'#create_placeholder' => TRUE
);
$build['#markup'] = $this->t('The current timestamp is');
$build['#cache']['contexts'][] = 'languages';
return $build;
}
}

In the block plugin above, lets focus on the build array:

$build['timestamp'] = array(
'#lazy_builder' => ['timestamp_generator.generator:generateUserTimestamp', array()],
'#create_placeholder' => TRUE
);
$build['#markup'] = $this->t('The current timestamp is');

All we need to define a lazy builder is, add an index #lazy_builder to our render array.

#lazy_builder: The lazy builder argument must be an array of callback function & argument this callback function needs. In our case, we have created a service that can generate the current timestamp. Also, since it doesn't need any arguments, the second argument is an empty array.

#create_placeholder: This argument when set to TRUE, makes sure a placeholder is generated & placed while processing this render element.

#markup: This is the cacheable part of the block plugin. Since, the content is translatable, we have added a language cache context here. We can add any cache tag depending on the content being rendered here as well using $build['#cache']['tags'] = ['...'];

Lets take a quick look at our service implementation:

services:
timestamp_generator.generator:
class: Drupal\timestamp_generator\UserTimestampGenerator
arguments: []
<?php
/**
* @file
* Contains \Drupal\timestamp_generator\UserTimestampGenerator.
*/
namespace Drupal\timestamp_generator;
/**
* Class UserTimestampGenerator.
*
* @package Drupal\timestamp_generator
*/
class UserTimestampGenerator {
/**
*
*/
public function generateUserTimestamp() {
return array(
'#markup' => time()
);
}
}

As we can see above the data returned from the service callback function is just the timestamp, which is the dynamic part of block content.

Lets see how Drupal renders it with its default single flush strategy. So, the content of the block before placeholder processing would look like as follows:

<div id="block-timestamp" class="contextual-region block block-timestamp-generator block-timestamp">
<h2>Timestamp</h2>
<div data-contextual-id="block:block=timestamp:langcode=en" class="contextual" role="form">
<button class="trigger focusable visually-hidden" type="button" aria-pressed="false">Open Timestamp configuration options</button>
<ul class="contextual-links" hidden=""><li class="block-configure"><a href="http://www.qed42.com/admin/structure/block/manage/timestamp?destination=node">Configure block</a></li></ul>
</div>
<div class="content">The current time stamp is
<drupal-render-placeholder callback="timestamp_generator.generator:generateUserTimestamp" arguments="" token="d12233422"></drupal-render-placeholder>
</div>
</div>

Once the placeholders are processed, it would change to:

<div id="block-timestamp" class="contextual-region block block-timestamp-generator block-timestamp">
<h2>Timestamp</h2>
<div data-contextual-id="block:block=timestamp:langcode=en" class="contextual" role="form">
<button class="trigger focusable visually-hidden" type="button" aria-pressed="false">Open Timestamp configuration options</button>
<ul class="contextual-links" hidden=""><li class="block-configure"><a href="http://www.qed42.com/admin/structure/block/manage/timestamp?destination=node">Configure block</a></li></ul>
</div>
<div class="content">The current time stamp is 1421319204
</div>
</div>

The placeholder processing in Drupal 8 happens inside via Drupal\Core\Render\Placeholder\ChainedPlaceholderStrategy::processPlaceholders. Drupal 8 core also provides with an interface for defining any custom placeholder processing strategy as well Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface. Bigpipe module implements this interface to provide its own placeholder processing strategy & is able to present users with cached content without waiting for the processing for dynamic ones.

With bigpipe enabled, the block would render something like the one shown in the gif below:

Lazy builder with Bigpipe

As you can see in this example, as soon as the cached part of the timestamp block "The current timestamp is" is ready, its presented to the end users without waiting for the timestamp value. Current timestamp loads when the lazy builders kick in. Lazy builders are not limited to blocks but can work with any render array element in Drupal 8, this means any piece of content being rendered can leverage lazy builders.

N.B. -- We use Bigpipe in the demo above to make the difference visble.

Sep 19 2016
Sep 19

If you have been building Ionic / Cordova apps, you might have noticed your app breaking with public release of iOS 10 launched recently. Common symptom of apps suffering from this issue is that the app continues to work as expected on Android, uptill iOS 9 but doesn't launch / load on iOS 10.

This is because of the newly introduced Content Security policy.

Content Security policy is used to prevent cross-site scripting (XSS). It is now supported by all modern browsers.

Browser Support

CSP provides a standard method for website owners to declare approved origins of content that browsers should be allowed to load on that website.

CSP comprises of multiple directives each separated by a ‘;’

If your Ionic / Cordova app does not load data from remote content / stuck on splash screen on iOS 10, this could be the issue you must be facing and the fix is to add the below meta tag in your index.html

<meta http-equiv="Content-Security-Policy" content="default-src gap://ready file://* *; script-src 'self' 'unsafe-inline' 'unsafe-eval' *; style-src 'self' 'unsafe-inline' *”>

Below is a brief description of the attributes: 

  • default-src :  default policy for CSP.
  • gap://ready file://* : is required to allow the for loading remote content in our app iOS 10.
  • script-src : Defines the valid sources of Javascript to be loaded.
  • unsafe-inline: Allows use of inline source elements such as style attribute, onclick, or script tag bodies.
  • unsafe-eval : Allows unsafe dynamic code evaluation such as JavaScript eval().
  • self : Allows loading resources from the same origin (same scheme, host and port).
  • style-src : Defines valid sources of stylesheets.

Disclaimer: Please use it in line with your security considerations and evaluations.

Jun 26 2016
Jun 26

Drupal India Community have been talking about Pan India code sprint and with effort and cooperation of regional communities we were finally able to organise a combined sprint, we had participation from Mumbai, Jaipur, Delhi and Pune. This is an account of Pune sprint which happened at QED42 Office in Viman Nagar. We had a total attendance of 10 Drupalers out of which 2 were first time sprinters ( Congratulations Dhruvesh and Shreyal on attending your first sprint :) ).

The focus of the sprint was porting modules from D7 to D8 and trying to reach stable releases of some of the modules that were started in previous sprints. One of those modules was auto_entitylabel the issues were triaged prior to the code sprint, so we had less trouble getting around the issues & fixing them up. EOD, we were able to get a basic version of the module, which included integration with tokens.

Auto Entity Label Porting to Drupal 8

Ajit mentored Dhruvesh on autologout tasks and Dhruvesh contributed a fix to an issue in D8 version of the module & then backported it to Drupal 7 version as well.

Vishal mentoring shreyal

Sprint also included some code review work around heap_analytics module, which Nitesh  ported to Drupal 8 (https://github.com/nitesh11/heap_analytics). 

Ajit, Nitesh & Prashant sprinting

Overall, it was a productive sprint & we plan to continue the same on Last Saturday of every Month. Keep an eye on auto_entitylabel, jquery_carousel, heap_analytics if you are interested to use them in Drupal 8, couple of sprints and help from community we should be able to release stable versions of these modules :) we specifically need help on testing of these modules and reporting issues. 

May 31 2016
May 31
A summary of the going-ons at the PDG Meet-up for the month of May.
May 27 2016
May 27

The flavour of this month has been the Drupalcon New Orleans and we decided to keep the momentum going for this PDG meet-up held at the QED42 office.

The first session was given by Rakhi Mandhania on her experience at DrupalCon both as an attendee as well as a Keynote speaker for the Higher Ed Summit. She explained how everyone is concerned with the migration of a large number of websites to Drupal 8 and the lack of rich Drupal talent. DrupalCAP initiative was hailed as a solution to the jarring lack of Drupal literate work force and appreciated all around. 

Rakhi's session

The second session was by Piyuesh Kumar on service workers, the same session both he and Saket kumar presented at New Orleans. He explained that functionalities such as, rich offline experiences, periodic background syncs, push notifications that traditionally require a native application are coming to the web and service workers provides the technical foundation all these features will rely on.

He ended the session with a demo of a working website for DrupalCamp.

Piyuesh

The evening was concluded with us deciding the dates for DrupalCamp Pune 2016, which will tentatively take place sometime in late August.

Watch this space for details, coming shortly!
Good day and see you all soon.

Apr 29 2016
Apr 29

The monthly Pune Drupal Group Meetup for April was hosted by QED42. The second PDG meetup to take place in the month of April, You would assume meeting this often would get tiring for other people but not us! We Drupalers love a good catchup session.

The first session was kicked off by Prashant and Rahul, Interns at QED42 and they spoke on, "Our experience with Drupal." They explained about their journey as new comers to Drupal, from the lenses of both CMS and the community. Their confusion at the beginning, the new tech and softwares they have learned, their experience at Drupalcon Asia and their love for the community. A really enjoyable session peppered with ernest observations and cute cat pictures and a brilliant first time attempt. Bravo boys!

Rahul and Prashant

The second session was taken by Arjun Kumar of QED42 on,"Introduction to CMI." With a brief on CMI and the difference from the features land, he concluded with a demo.

Arjun CMI

After a short discussion on the probable date and location for Pune Drupal Camp we broke off for BOF sessions,with Navneet leading the discussion on Acquia certifications and further discussions on CMI.

BOF

With 20 people in attendence we concluded the PDG april meetup with delicious Pahadi Sandwiches in our tummy. Have a great weekend and see you soon!

Apr 21 2016
Apr 21

The monthly meet-up for March was moved from the last friday of the month, which was the good Friday, to the 1st of April and hoped really hard that people didn't think it was an April fools prank. This PDG meetup was hosted by Rotary International thanks to diligence of Dipak Yadav who works there. It is always fun when the meetup is hosted in different locations because we get to explore different parts of Pune and see new faces.

With 25 members in attendence, the meetup was kicked off by Dipak giving us an informative talk about Rotary International and the work they do.

Introduction of Rotary International

The speaker for the evening was Sushil Hanwate of Axelerant and he spoke on,“ Services and dependency injections in Drupal 8.”

Session

 

The session ended after a short Q&A, we broke off into smaller groups for BOF sessions. Saket headed the BOF for Service workers and the second group discussed about the Drupal 8 Module development.

Once we were done with technical talks, we were served one of the best Kachoris we have tasted :). While we happily munched on the snacks, we decided on the preliminary team members for the upcoming Pune Drupal Camp.

Though the meetups are being held regularly we still need to figure a way of involving newer members into the community and one of the way that is possible is if we get more people volunteering to host the meetups. Kudos to Rotary for hosting us, if you are a Pune based company / group who would like to host the next meetup then please get in touch via comments. 

Our next PDG meetup is scheduled for the 29th of April. Along with a session on,"Experience with Drupal" by Rahul Savaria and Prashant Kumar from QED42, we shall also be planning and discussing further about the upcoming Pune Drupal Camp.
Dont forget to RSVP, See you soon!

Mar 29 2016
Mar 29

There are scenarios when outside of a view we need to fetch results of any particular view. This is a very specific case when we just want the records compiled by Drupal Views. In that case obviously views api or views hooks are of no use, as we are not looking for event driven activities. It’s just the results needs to be fetched using views because of the complexity of the criterion on which these results are computed.

We won’t go for this approach when we want simple results like all the nodes of a specific content type sorted alphabetically. In that case, simply db_select would be better choice again depending on various project specific factors. In general, we can’t actually tag any approach as the best or optimal for general purpose as these are scenario specifics.

In our case, the scenario is we have a very complex view having good amount of filters or simply i would say the corresponding sql query is complex. Now in that case outside of the view we have two options to get results:

  1. Function views_get_view_result()
  2. Executing corresponding SQL Query

Approach 1: Function views_get_view_result():

// To get the result of a view.
views_get_view_result($name, $display_id = NULL)  // views.module

This looks promising, provided by views module. Accepts views name and display_id as parameter and simply fetches you the result. What we wanted is accomplished.

Limitation: In case of pagination, probably we want to get all the results and this approach fails there. We cannot fetch all the results if the corresponding view limits results to specific number per page. So, what should we do next!


Let’s look at our second approach, would that be useful in our case?

Approach 2: Executing corresponding SQL Query:

We can directly execute static sql query, which is a good solution for this scenario. But when we want to enjoy further flexibilities of this approach, say we want to change the query a bit then what? It is possible with this approach but the method is not recommendable. Infact best way to proceed further using this approach is to convert static query into dynamic drupal query which is a tedious process. So, how to achieve this?

Final Solution:

Looking at how the views work, we got a very promising structured way of solving all our related problems. Views provide several methods for views object which can be used to get all the results with/without customization of a particular view. Let’s see how:

$view = views_get_view(‘example_view’);
$view->build($display_id);
$view->query->limit = 0;
$view->execute();
$results = $view->result;

So, we finally have all the results of a view. As far as the above implementation is concerned, we basically are fetching a view, then building a specific display of that view and after customization we are finally executing it to get the results. In simple language, we are creating a similar temporary instance to get the desired results.


Hope this helps you (smile).  

Mar 29 2016
Mar 29

E-commerce has become a way of life for most of us and with so many ecommerce portals competing it becomes important to attract new customers to your site.

A way to attract the new customers is by incentivising their first purchase. E.g. Providing a discount, taking off the shipping charges, providing an extra product for free, etc.

In a recent commerce project we had a this requirement to find out if the purchase being made is the user's first. Searching for a solution online, I came across this sandbox module. The module needed to be published on drupal.org with a little cleanup (code structure and coding standards) [1] [2]. It was promoted to full project : Commerce First Time Customer Discount.

The module provides a couple of ways to identify if the purchase being made by the customer is his/her first:

  1. Using inline condition
  2. Using rules condition

Using inline condition:

Commerce First time Customer Discount can be easily integrated with the Commerce Discount module to check if the purchase is the user’s first. Follow the steps given below to implement it:

  1. Install commerce discount module.
  2. Install Commerce First Time Customer Discount module.
  3. Go to “Store settings” >> “Discounts”(admin/commerce/store/discounts) and click “Add discount”.
  4. Fill in the details for the fields “Admin title”, “Name”, choose the “Discount Type”
  5. Under “Order discount conditions” select “First Time Customer”. The Commerce First Time Customer Discount module provides this option.
  6. Choose the offer type of your choice.
  7. Save!

Your new offer will be now applied to the first purchase that a customer makes. Using commerce discount also enables you to set a date during which this offer will be applicable.

Using rules condition:

Commerce First Time Customer Discount module provides a rule condition to check if this is the first purchase made by the customer. Follow the steps given below to implement discount using the rules module:

  1. Install the Commerce First Time Customer Discount module.
  2. From rules admin section (admin/config/workflow/rules), click to “Add new rule” button. Note: You will have to enable to rules_admin module for this.
  3. Select any event in the “Commerce Order” section. Ex. Before saving a new order.
  4. Add a condition “Users First Order” under the “Commerce Order” section.
  5. Add any action of your choice to the order. The action could be to apply a discount, give away a free product, provide free shipping, etc.
  • [1] : Github issue for proposing the promotion of the module.
  • [2] : drupal.org issue for promoting the sandbox to full project.
Mar 21 2016
Mar 21

With around 767 submissions from around the globe DrupalCon New Orleans must have set a new record for an all time high for session submissions at any conference. We can only imagine the plight of track chairs who helped select final ~150 sessions, given the numbers we are absolutely thrilled to announce that three of our talks (Including one in higher ed summit) have been accepted for Drupalcon New Orleans.

Here is a brief of talks at Drupalcon New Orleans May, 2016.

Service Workers Internals:

Saket and Piyuesh will talk about Service Workers on D8 and the promise of offline web. The topic is a build up from series of BoF at Drupalcon Asia where service workers was discussed passionately and pragmatically. It’s not just frontend, necessary backend infrastructure (route caching, url config) needs to be present for service workers to be a reality, hence Saket and Piyuesh ( our frontend and backend leads ) decided to team up on this. You can read more about the tentative agenda at the session synopsis and here are rumours of what to expect in the demo:

  • Config for urls for offline first approach

  • Config for urls for network first approach & fallback to serviceworker cache

  • Static resource caching via service workers

  • Since D8 has placeholders, it would be wise to cache the response of placeholders & render them using service-workers. Background sync can be used to keep pulling in fresh data for the placeholders.

Exploring Drupal 8 Frontend landscape through Polymer :

Saket is back with a session on the utopia of using Drupal 8 as a backend for multiple front end frameworks; this time it’s through Polymer. Webcomponents have had the frontend community wanting, what’s not to desire? Reusable components, shadow DOM, templating and much more all native to the browser. Though webcomponents paints a beautiful feature, Polymer from Google delivers it as a polyfill for the restless. In this talk Saket will talk about what Drupal 8 frontend programming might look like and glitches we might need to resolve for a smooth future.

Higher-ed Summit

Drupal Campus Ambassador Program - A community Initiative:

A wildcard entry into Dries’s keynote and Holly’s closing-note at Drupalcon Asia,  DCAP made its humble beginnings in one of India’s regional events, where business folks were trying to find answers to Drupal talent problems. In spirit of Drupal community’s do-ocracy some of the community members figured a WIN-WIN solution for the problem, that is to take Drupal to colleges and evangelise it professionally. There have been numerous college workshops but DCAP is different in ways that it provides longevity and support to college students and that is exactly what Rakhi will be sharing at Higher-ed summit -- a bit of the history, successes so far and brave new world Drupal will foray into through DCAP. Catch Rakhi in action on May 9 at DrupalCon New Orleans.

Mar 19 2016
Mar 19

Google Maps are changing the way we see the world. Lets change the way we look at Google Maps (smile)

Google Maps are a perfect match when you are dealing with location based apps. In this article we will learn to integrate the Google Maps api and Google Places api in our Ionic App and look at the solution to long press issue that Ionic suffers from and how to resolve that. 

Get Ionic running:

  • Make sure you have node.js installed on your system
  • $ sudo npm install -g cordova
  • $ sudo npm install -g ionic

Create a new Ionic Application:

ionic start googlePlacesDemo blank
cd googlePlacesDemo/

Get list of all the platforms added in your application:

ionic platform

you will see the list of all the installed platforms

Example :
Installed platforms: ios 3.8.0
Available platforms: amazon-fireos, android, blackberry10, browser, firefoxos, osx, webos

Add Android and iOS platforms to you app:

ionic platform add android
ionic platform add ios

Following plugins will be installed once any platform is added:

ng-cordova is an AngularJs Service which integrates cordova plugins into IONIC Applications. Download ng-cordova.master.zip from here and place the ng-cordova.min.js in the www/js. ngCordova depends on AngularJS  . In your index.html , place ng-cordova.min.js before cordova.js and after AngularJs/ Ionic files.

<script src="http://www.qed42.com/blog/ionic-google-places-api/lib/ngCordova/dist/ng-cordova.js"></script>
<script src="http://www.qed42.com/blog/ionic-google-places-api/cordova.js"></script>

Inject ngCordova as an angular dependency in your angular module

angular.module('myApp', ['ngCordova'])

Test the app on the browser

ionic serve --lab

Lets add google places api in your index.html

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places,geometry&sensor=false"></script>

index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>
    <link href="http://www.qed42.com/blog/ionic-google-places-api/lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="http://www.qed42.com/blog/ionic-google-places-api/css/style.css" rel="stylesheet">
    <!-- google places api -->
    <script src="http://maps.googleapis.com/maps/api/js?sensor=false&amp;libraries=places"></script>
    <!-- google places api ends -->
    <!-- ionic/angularjs js -->
    <script src="http://www.qed42.com/blog/ionic-google-places-api/lib/ionic/js/ionic.bundle.js"></script>
    <!-- cordova script (this will be a 404 during development) -->
    <script src="http://www.qed42.com/blog/ionic-google-places-api/js/ng-cordova.min.js"></script>
    <script src="http://www.qed42.com/blog/ionic-google-places-api/cordova.js"></script>
    <!-- your app's js -->
    <script src="http://www.qed42.com/blog/ionic-google-places-api/js/app.js"></script>
</head>

<body ng-app="starter">
    <ion-pane>
        <ion-header-bar class="bar-stable">
            <h1 class="title">Google Places Demo  </h1>
        </ion-header-bar>
        <ion-content ng-controller="MapCtrl">
            <div class="item item-input controls">
                <i class="icon ion-search placeholder-icon"></i>
                <input id="pac-input" type="text" placeholder="Search Location" data-tap-disabled="true" ng-model="search">
            </div>
            <div id="map" data-tap-disabled="true"></div>
        </ion-content>
    </ion-pane>
</body>

</html>

app.js

angular.module('starter', ['ionic'])
    .run(function($ionicPlatform) {
        $ionicPlatform.ready(function() {
            if (window.cordova && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
                cordova.plugins.Keyboard.disableScroll(true);
            }
            if (window.StatusBar) {
                StatusBar.styleDefault();
            }
        });
    })
    .controller('MapCtrl', ['$scope', function($scope) {
        function initialize() {
            var mapOptions = {
                center: { lat: 28.613939, lng: 77.209021 },
                zoom: 13,
                disableDefaultUI: true,// DISABLE MAP TYPE
                scrollwheel: false
            };
            var map = new google.maps.Map(document.getElementById('map'),
                mapOptions);

            var input = /** @type {HTMLInputElement} */ (
                document.getElementById('pac-input'));

            // Create the autocomplete helper, and associate it with
            // an HTML text input box.
            var autocomplete = new google.maps.places.Autocomplete(input);
            autocomplete.bindTo('bounds', map);

            map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

            var infowindow = new google.maps.InfoWindow();
            var marker = new google.maps.Marker({
                map: map
            });
            google.maps.event.addListener(marker, 'click', function() {
                infowindow.open(map, marker);
            });

            // Get the full place details when the user selects a place from the
            // list of suggestions.
            google.maps.event.addListener(autocomplete, 'place_changed', function() {
                infowindow.close();
                var place = autocomplete.getPlace();
                if (!place.geometry) {
                    return;
                }

                if (place.geometry.viewport) {
                    map.fitBounds(place.geometry.viewport);
                } else {
                    map.setCenter(place.geometry.location);
                    map.setZoom(17);
                }

                // Set the position of the marker using the place ID and location.
                marker.setPlace( /** @type {!google.maps.Place} */ ({
                    placeId: place.place_id,
                    location: place.geometry.location
                }));
                marker.setVisible(true);

                infowindow.setContent('<div><strong>' + place.name + '</strong><br>' +
                    'Place ID: ' + place.place_id + '<br>' +
                    place.formatted_address + '</div>');
                infowindow.open(map, marker);
            });
        }

        // Run the initialize function when the window has finished loading.
        google.maps.event.addDomListener(window, 'load', initialize);
    }])

style.css

#map {
    width: 100%;
    height: 100%;
}

.controls {
    border: 1px solid transparent;
    border-radius: 2px 0 0 2px;
    box-sizing: border-box;
    position: absolute;
    line-height: 22px;
}

#pac-input {
    background-color: #fff;
    font-family: Roboto;
    font-size: 15px;
    font-weight: 300;
    padding: 0 11px 0 13px;
    text-overflow: ellipsis;
    width: 90%;
    margin-bottom: 0;
    line-height: 15px;
    font-weight: bold;
    margin-left: 10%;
}

#pac-input:focus {
    border-color: #4d90fe;
}

.pac-container {
    font-family: Roboto;
}

#type-selector {
    color: #fff;
    background-color: #4d90fe;
    padding: 5px 11px 0px 11px;
}

#type-selector label {
    font-family: Roboto;
    font-size: 13px;
    font-weight: 300;
}

Now run the app on the browser:

ionic serve --lab

You will see it work as expected in the browser.

Now run the app on Android Device: 

ionic run android

Issue with Selecting Auto Complete suggestions

All Set, you might assume. There is a problem though, you will have to long press the autocomplete option to actually get it selected. The issue is that Gmap dynamically adds elements that need the data-tap-disabled property, so you'll have to manually add the property after google has added these elements to the dom.

To get it working on the Android device you need to add the following function in your controller:

$scope.disableTap = function() {
                var container = document.getElementsByClassName('pac-container');
                angular.element(container).attr('data-tap-disabled', 'true');
                var backdrop = document.getElementsByClassName('backdrop');
                angular.element(backdrop).attr('data-tap-disabled', 'true');
                angular.element(container).on("click", function() {
                    document.getElementById('pac-input').blur();
                });
            };

add disableTap() function in the input type 

<input id="pac-input" type="text" placeholder="Search Location" data-tap-disabled="true" ng-change='disableTap()' ng-model="search">

Now run the App on the Android Phone. Voila!  (smile)

For live demo please visit this github link

Pages

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web