Jan 06 2017
JK
Jan 06

In this article, we will present how we built a simple twitter feed in Drupal 8 with custom block and without any custom module. This block will display a list of tweets pulled from a custom list as in the example shown in the side bar.

As a prerequisite, you need to have a twitter account and a custom list in your feeds.

1) Get code from "twitter publish"

First, we need to get the code that is generated by twitter publishing api.

On this page, enter the needed url for the list of tweets you want to display as in the example below:

embed

The code will be generated from the url and you can add some custom format:

format

Once you have updated your options, you can just coy the custom code:

code

We will use this code to create the block.

2) Custom block

In custom block library (/admin/structure/block/block-content), click on "+ Add custom block" button.

In the custom block body (full HTML), copy the code generated previously in "<> source" mode and click save:

edit block

You now have a custom block that you can place anywhere in your site. To do so go to /admin/structure/block and click on the "Place block" button where you want to display your block.

The result :

Twitter block

For more advance block creation, see also this article.

Dec 08 2016
JK
Dec 08

This script will help display the results of a search by keyword instantly via an ajax call. It can be applied to various search types. From user point of view it creates a good user experience and efficient working flow.

For instance we apply this search in products and services module and payroll module to quickly find the data to be reviewed or edited.

 

1) create the form

The search form is very simple and is made from a text field and <div> to display the results. We do not need to "submit" the form as the search results are returned by an ajax call.

/**
 * @file
 * Contains \Drupal\MyModule\Form\SearchProductsForm.
 */

namespace Drupal\MyModule\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides a form to search items
 */
class SearchProductsForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'MyModule_products_search';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

          $form['name'] = array(
              '#type' => 'textfield',
              '#id' => t('product-search-form'),
              '#size' => 50,
              '#attributes' => array('placeholder'=>t('Enter item code, barcode or name')),
              '#attached' => ['library' => array('MyModule/MyModule.autocomplete')],
            );
          
          $form['list_items'] = array(
            '#type' => 'item',
            '#markup' => "<div id='product-search-result'></div>",
          );   
        
    return $form;  

  }

The form is located in mymodule/src/From.

You can notice that the library MyModule.autocomplete is attached to the search box. Let's create the library script now.

2) Library and autocomplete script

The library is declared in mymodule.libraries.yml file at the root of our custom module:

MyModule.autocomplete:
  version: 1
  css:
    theme:
      css/MyModule.css: {}
  js:
    js/MyModule_autocomplete.js: {}
  dependencies:
    - core/jquery
    - core/drupal

The main reference in the library is MyModule_autocomplete.js file that manage the ajax call and display of results. This file will contain the following script:

(function ($, Drupal, drupalSettings) {

  Drupal.behaviors.ek_products_autocomplete = {
    attach: function (context, settings) {

      jQuery('#product-search-form').keyup(function() {
      
        var term = jQuery('#product-search-form').val();

        jQuery.ajax({
          dataType: "json",
          url: drupalSettings.path.baseUrl + "autocomplete_ajax" ,
          data: { option: "image", q: term },
          success: function (data) {
              var content = '';
              var i = 0;
              for(;data[i];) {
                  
                  var editUrl = "<a class='product_image-link' href='" + drupalSettings.path.baseUrl
                          + "item/" + data[i]['id'] + "'>" + data[i]['picture'] + "</a>";                  
                  content += "<p>" + editUrl + "  " + data[i]['name'] + "</p>";
                  i++;
              }
              
              jQuery('#product-search-result').html(content);

          }
          });      
      });   
    }
  };
})(jQuery, Drupal, drupalSettings);

Few comments here.

First the URL is pointing to "autocomplete_ajax"; this route must exist in your routing file (i.e mymodule.routing.yml):

MyModule_autocomplete_ajax:
  path: '/autocomplete_ajax'
  defaults:
    _controller: '\Drupal\mymodule\Controller\ProductsController::autocomplete'
  requirements:
    _permission: 'view_products'

We include some class properties that will be defined in a file MyModule.css included in the library declaration above.

The result is returned as a json format from the controller but will be displayed as html format in the browser.

3) the Controller

The controller function (i.e autocomplete()) will query the database and return the results. In the simplified version below, we do not describe the actual database query that may vary from structure to structure. The result returned is pretty simple and in our case we return 3 elements when 'image' option is declared: picture, name (made of different information) and id.

public function autocomplete(Request $request) {

        $term = $request->query->get('q');
        $option = $request->query->get('option');

        /*
        * do the DB query here filtered by $term: $data
        */
        
        $return = array();
        while ($result = $data->fetchObject()) {

            if (strlen($result->description) > 30) {
                $desc = substr($result->description, 0, 30) . "...";
            } else {
                $desc = $result->description;
            }
            
            if($option == 'image') {
                $line = [];
                if ($result->uri) {
                         $pic = "<img class='product_thumbnail' src='"
                        . file_create_url($result->uri) . "'>";
                    } else {
                        $pic = '[]';
                    }
                    $line['picture'] = isset($pic) ? $pic : '';
                    $line['name'] = $result->id . " " . $result->itemcode . " " . $result->barcode . " " . $desc . " " .$result->supplier_code;
                    $line['id'] = $result->id;
                    
                    $return[] = $line;
                
            } else {
                $return[] = $result->id . " " . $result->itemcode . " " . $result->barcode . " " . $desc . " " .$result->supplier_code;
            }
           
        }
        return new JsonResponse($return);
}

4) Display

You can now create the routing to display your form and see the result in your browser. To call your search form, simply create a route to the form as in the example below (mymodule.routing.yml):

MyModule.searchForm:
  path: '/mysearchform'
  defaults:
    _form: '\Drupal\mymodule\Form\SearchProductsForm'
  requirements:
    _access: 'TRUE'

Display can be customized within the css file. In our case, we define  "thumbnail"  properties to display the items images:

/* search display */
.product_thumbnail {
    
    width: 40px;
    height: 40px;
    overflow: hidden;
    vertical-align: middle;
    margin-right: 10px;
    border: solid 1px;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
}

search

Feel free to add your comments or own experience.

Thank you.

Jun 26 2016
JK
Jun 26

In this article, we will see how we built custom blocks in EK management tools suite with a sample basic block in a module called 'mymodule' used for demo. It can be used to display multiple content, static or dynamic as in the example above.

Create the block script

First we will create a script that will display some content within a block. the script file will be called MyBlock.php and is placed in /mymodule/src/Plugin/Block/.


/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Block\MyBlock.
 */
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;

/**
 * Provides a 'Custom module widget' .
 *
 * @Block(
 *   id = "my_block",
 *   admin_label = @Translation("My custom block"),
 *   category = @Translation("mymodule Widgets")
 * )
 */

The file header will contain the namespace of the file, the dependencies and most important, the annotations that define the block for discovery (more information about this in Drupal).

For the purpose of this demo, the content of the block will be very simple:

class MyBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
 
  $items = array();
  $items['title'] = t('Custom block');
  $items['content'] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
          . "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "
          . "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. "
          . "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
 
 
  return array(
    '#items' => $items,
    '#theme' => 'mymodule_block',
    '#attached' => array(
      'library' => array('mymodule/style'),
      ),
    );
 
  }
  /**
   * {@inheritdoc}
   */
  protected function blockAccess(AccountInterface $account) {
    if (!$account->isAnonymous() ) {
      return AccessResult::allowed()->addCacheContexts(['route.name']);
    }
    return AccessResult::forbidden(); 
  }
}

We set a title and content text to be displayed in the block in the build() function. This block use a theme template called "mymodule_block" and a library with custom css style. We will not cover this part here. The blockAccess()  function control the visibility of the block and restrict it to authenticated accounts.

Display block

In order to demonstrate the display of the block, we created an empty page with our /mymodule/block in mymodule.routing.yml, however, the block can be displayed in any page or region.

mymodule.block:
  path: '/mymodule/block'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyModuleController::BlockPage'
  requirements:
    _access: 'TRUE'

BlockPage() will just return an empty array in this sample module.

/**
 * @file
 * Contains \Drupal\mymodule\Controller\MyModuleController.
 */

namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;


class MyModuleController extends ControllerBase {

    public function BlockPage() {
        return array();   
    }

}

Now we can navigate to the block layout management page of Drupal 8, /admin/structure/block, and install our custom block.

We place the block in "Content" of the page and click on the button to select the custom block we created:

We click on "Place block" for the selected block and configure the block to show on our custom page:

After saving, we can now navigate to our page /mymodule/block and see the block in action:

Block configuration file

The block can be set in yml configuration file in order to be installed with the module. In order to do that, simply go to /admin/config/development/configuration/single/export to export the block configuration that we just activated:

Copy the configuration script into a file called block.block.mycustomblock.yml and place it under /mymodule/config/install; the block will be activated at installation time.

We hope this block example is useful and feel free to add your comments or suggestion.

Feb 21 2016
JK
Feb 21

In previous article we explained how we installed the Swift Mailer module and its dependencies.

In this second part, let's see how we configure and implement it to use in our modules to send formated HTML mail with attachment.

First you will need to have Mail system module installed already. There is no particular issue or difficulty here.

1) Configure Swift Mailer

In Swift Mailer configuration (/admin/config/swiftmailer/transport) , we select the following options:

Transport:

Messages:

2) Custom module with email attachment

In our ERP application, we have a function that handles sharing of stored or online generated document via email.

In this function "mail_attachment()", we have an option to use Swift Mailer when available as module to handle the attachment.

Basically, the function handles the parameters received and prepare them for sending using a twig template.

The parameters used are: a list of email addreses, the uri of the file to attach, a text message and some option data (I.e. site name or logo). The part that is important here is the attachment preparation:

            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $attachments = new stdClass();
            $attachments->uri = '/path/to/file';
            $attachments->filename = 'file name';
            $attachments->filemime = finfo_file($finfo, $file);
            $params['files'][] = $attachments;

Once we have compiled and formatted all information necessary for our email, we call the Drupal service to send our message with attachment:

            Drupal::service('plugin.manager.mail')->mail(
                        'ek_admin',
                        'attachment',
                        [email protected],
                        \Drupal::languageManager()->getDefaultLanguage(),
                        $params,
                        $currentuserMail,
                        TRUE
             );

In this piece of code we notice:

  • The name of the module used ('ek_admin');
  • The name of the key to identify the mail template used ('attachment').

In the above referred module, we have a hook_mail() function that handle the message based on the selected key:

          function ek_admin_mail($key, &$message, $params) {
              switch($key) {
                  case 'attachment':
                  $message['subject'] = $params['subject'];
                  $message['body'][] = $params['body'];
                  $message['options'] = $params['options'];
                  $message['files'][] = $params['files'];
        
                  break;
              }
          }

The above parameters are important to build the message template.

3) The twig template

The template name format will follow this structure: swiftmailer--[module name]--[key].html.twig

We the create the file swiftmailer--ek_admin--attachment.html following the obove parameters that we used in our module, being the module name and the key.

This twig template can be designed as required with normal html tags to be sent as html mail. Here is a simplified example:

          <table width="800px" cellpadding="0" cellspacing="0">
                  <tbody>
                      <tr>
                         <td style='font-size:1.2em;padding:20px;vertical-align: bottom;'>{{ message.options.site }}</td>
                         <td ><img class="img-responsive" style="width:130px;float:right;" src="https://arrea-systems.com/Install_use_SwiftMailer_Drupal_8_%28part_2_imp...{{ message.options.logo }}" />
                         </td>
                     </tr>
                </tbody>
         </table>
         <hr/>
          <p>{{ 'Document'|t }} : {{ message.options.filename }}</p>
          <p>{{ 'Document size'|t }} : {{ message.options.size }}</p>

Important: the trick here here is that the template has to be copied in the template folder of the theme used which may be a current limitation to the module. In our example, we use bartik theme:


4) Configure Mail System

In the Mail System configuration (), we select Swift Mailer as default mail handling system and keep the theme as current:

Now in custom configurations, we tell Mail Systems about our module and key described above and to use Swift Mailer when they are called:

After saving the configuration, we have our module and key registered:

5) Send a file

For simple example, we will take a file from a project page and email it to a user

And the result is the email received as follow, according to our Swift Mailer template:

Feel free to add your comments or own experience with Html email with Drupal 8.

Thank you.

Feb 14 2016
JK
Feb 14

In a previous post from 2015, we described usage of Swift Mailer module to send HTML mail and mail with attachment. At this time, the module was not yet available for Drupal 8.

There is now a version alpha1 available. Let's go through installation process.

Because it has been rather tedious for us, we will try to explain the flow of the process as much as possible to help you save time.

1) Composer

The prerequisite is the installation of composer.

In our case we installed first on Windows inside a folder named  F:\Program Files\composer2\.

The installation exe for Windows can be found here.

However, it did not work in our case and we needed to install it manually with the below command (see more details here):

 

php -c C:\windows\php.ini -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"

Once installed you will get the confirmation message:

Installation on Ubuntu server was very easy (see how):

2) Composer manager install and init

The Drupal 8 version of this module is deprecated and no longer needed, due to improvements in Drupal 8.1. Use Composer directly to get the needed modules, which will also download their required libraries.

If you try to install the Swift Mailer module now, without dependencies, you will get the following error message:

Thus we will use composer manager to install the library as suggested by the module.

Our version of composer manager is 8.x-1.0-rc1+0-dev. After installing the module (/admin/modules), the status report (/admin/reports/status) indicates that the module has to be initialized:

 

Composer Manager

Not initialized Run the module's init.php script on the command line

 

Init.php is in Drupal_path/modules/composer_manager/scripts/. We use the following command in Windows to do the initialization as required:

 

\Drupal path\modules\composer_manager\scripts\php init.php

 

Then we get the confirmation as follow:

 

For Ubuntu, the process is similar:

 


What happened in practice is that the "composer.json" file located in Drupal root has been updated as in the example below:

 

autoload": {
"psr-4": {
"Drupal\\Core\\Composer\\": "core/lib/Drupal/Core/Composer",
"Drupal\\composer_manager\\Composer\\": "F:\\path to module\\composer_manager/src/Composer"
}
},
"scripts": {
"pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump",
"post-autoload-dump": "Drupal\\Core\\Composer\\Composer::ensureHtaccess",
"post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup",
"post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup",
"drupal-rebuild": "Drupal\\composer_manager\\Composer\\Command::rebuild",
"drupal-update": "Drupal\\composer_manager\\Composer\\Command::update"
}

 

 

Going back to the status report now indicates that we can run drupal-update to update modules dependencies via composer:

Composer Manager

Composer update needed. Run composer drupal-update on the command line to update dependencies.

Composer-manager has its own report as well with the following indications:


3) Update dependencies

From windows command line, run "composer drupal-update" from within the Drupal installation root:

If you get an error, you can also run the update by pointing to the composer file as per example below:

The libraries will be updated accordingly and specifically those needed by Swift Mailer:

You can check that in the folder \Drupal\vendor the swiftmailer and html2text folder are now present.

For Ubuntu, the process is the same for dependencies update:

4) Install Swift Mailer

Ok, now you can try again to install Swift Mailer module.

At this point, you may still have the error above about missing library which is weird since we just updated it.

Actually, it may be possible that composer did not update the autoloader properly (bug?). You may solve this by running the command

or

After this you should get the expected result:

We hope you can benefit from this experience.

Feel free to leave a comment or question if you have any.

On a next post we will see how we use Swift Mailer to attach documents to mail.

Feb 07 2016
JK
Feb 07

In Drupal 8 there is a Tour module in core that is very useful when it comes to web applications. In EK management tools we target professional users with small to medium scale companies. They usually have limited resources and time to spend on back office trainings. This is where the Tour module is very convenient to introduce functionalities to users who can quickly grasp the functions available to manage their back office.

We use the Tour functionality in our pages to guide users in their daily tasks like for instance in the form to create a new invoice or project page:

Invoice guided tourproject tour

Implementation is actually very simple and results are great from the user experience point of view.

To achieve the above result, simply create a configuration file called tour.tour.invoice.yml. In this file add the properties information:

id: ek_sales.invoice_create
module: ek_sales
label: 'Create invoice tour'
langcode: en
routes:
  - route_name: ek_sales.invoices.create

Those properties are straightforward. The Id must be unique. In "routes", indicate where the Tour will be placed and route_name is referred in the module routing.yml file.

Then comes the tips definitions that will display the actual help text (we just present here the first 3 items as an example):

tips:
  introduction:
    id: introduction
    plugin: text
    label: 'Create or Edit invoice guide'
    body: 'This is an guided tour to create and edit an invoice.'
    weight: 1
  first-item:
    id: first-item
    plugin: text
    label: 'Invoice header'
    body: 'You must first select the header for you invoice. Header is defined by a company name. You can select any company to which you have access to. Selecting a company will in turn define other parameters like available bank accounts or credit accounts.'
    weight: 2
    location: bottom
    attributes:
      data-id: edit-head
  second-item:
    id: second-item
    plugin: text
    label: 'Allocation'
    body: 'In a multi companies configuration type, you can allocate the invoice to a different company of the group. This is used when a company invoice on behalf of another and you want to keep track of internal transactions.'
    weight: 3
    location: top
    attributes:
      data-id: edit-allocation

Again tips properties are also straightforward.

Id is unique. The 'text' plugin is the default display type in core. "Label" will appear as a box title and "body" is the extended description or help text. The 'weight' defines the sequence of tips display. the "attributes" link the tip to a specific element on the page that can be defined by its id (data-id) like in the cases above or by its class (data-class). And finally you can define the "location" of the tip around the element (top, bottom, left, right).

When you navigate to the page where the Tour has been configured, you can now see a Tour button which will start the Tour display:

tour button

Feel free to add your comments or suggestion.

Jan 31 2016
JK
Jan 31

In previous articles (here and here), we have seen a method to add custom views and data in MyModule.

With Drupal 8 there is a very easy and practical way to add this custom view as a configuration that will be installed with the module.

1) extract the configuration data

Navigate to "/admin/config/development/configuration/single/export".

On this page, select configuration type 'view' and configuration name 'My module list' that was created earlier.

Single export

2) create configuration install file

You will obtain from the above export a list of configuration data that you can copy and paste into a file called for instance "views.view.mymodule-list.yml";

Simply place this file into the install folder :

Install folder

Upon installation of the module, the view will be automatically created.

We hope this demonstration is helpful to you. You can view as well another demo in our custom module address book , part of EK management tools that use the same technique.

If you have comments or want to add techniques to improve views of custom data, feel free to do so.

Jan 24 2016
JK
Jan 24

In previous article we have seen how to declare the data accessible in a custom view in MyModule.

Now that the data from our tables mymodule_tb (and mymodule_tb_2) are available, let's create the list view.

First navigate to "/admin/structure/views/add" and create the view by entering basic information as per the example below.

New view basic info

After "save" you are redirected to "Edit" form where further settings will be set.

1) Add fields from you source table

Add fields

From this form, select the fields to display. We will select 2 here "name" and "type" (refer to the table structure declared in MyModule_views_data())

Add fields

Once added, the default preview is as follow:

Preview 1

You can see that the "type" field value is made of numbers which should be displayed as color type instead in our example.

2) Rewrite results for "type" field

To display information that is more practical for a user, we will convert the "type" filed values (1,2,30) into colors names.

To do that click on the field to edit it:

Edit field settings

In the edition form, go to "REWRITE RESULTS" section and click on "Override the output of this field with custom text":

Rewrite rule

In this Text box, we set rewrite rule based on type value as this : 1 = 'Bleu', 2 = 'Green', 3 = 'Red'.

For this rules we used the Twig script as it is suggested by the form.

After saving this rule, we can see that our list display will now output color names instead of "type" field values:

Preview 2

3) Add custom filter

To add a filter, click on the 'Add' button:

Add filter

In the form, select the filed 'name' as the filter criteria:

filter criteria

Then select "Expose this filter to visitors"  and complete the settings as per example below (we selected condition "starts with" and left the value empty):

Filter criterion

After applying the filter settings, the list view is completed.

You van now navigate to the link set in the view "/my-module-list" and see the result:

View result

We hope this demonstration is helpful to you. You can view as well another demo in our custom module address book part of EK management tools.

If you have comments or want to add techniques to display views of custom data, feel free to do so.

In the next article we will see how to add this view as a configuration in our custom module.

Jan 17 2016
JK
Jan 17

In our EK management tools suite we have custom designed lists of items like for instance list of management documents.

Those lists are build with custom codes and templates which is somehow more convenient to manage with complex data, links, menus and filters as in the example below.

Example of documents list

However for simple list, the views module is very useful and can be integrated in a custom module as well to automatically create the list.

Here is an example with companies list in the system address book module showing the company name as link and a field about the type of record plus a simple filter box.

List companies

To achieve this, you need first to reference the data into your module called for instance MyModule.

The sample table structure containing the data is as follow:

    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(100) NOT NULL DEFAULT '',
    `type` VARCHAR(5) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)

The field `type` in our case is a numeral from 1 to 3 that maps to defined description (Here we will use 1 => blue, 2 => green and 3 => red).


In MyModule.module file in our custom module, we reference those data to be accessible in views with MyModule_views_data() function.

In this function, we declare the following information:

/**
 * @file
 * MyModule module .
 */

function MyModule_views_data() {
  // This write hook_views_data() for the main table

  // First, the entry $data['mymodule_tb']['table'] describes properties of
  // the actual table – not its content.

  $data['mymodule_tb']['table']['group'] = t('My Module');

  // Define this as a base table
  $data['mymodule_tb']['table']['base'] = array(
    'field' => 'id', // This is the identifier field for the view.
    'title' => t('My Module'),
    'help' => t('My Module contains some data.'),
    'database' => 'external_db',
    'weight' => -10,
  );

  // This table references the {_tb_2} table. The declaration below creates an
  // 'implicit' relationship to the _tb_2 table
  $data['mymodule_tb']['table']['join'] = array(
    'database' => 'external_db',
    'mymodule_tb_2' => array(
      'left_field' => 'mid',
      'field' => 'id',
      'database' => 'external_db',
    ),
  );

  // Next, describe each of the individual fields in this table to Views.
  //  ID table field.
  $data['mymodule_tb']['id'] = array(
    'title' => t('mymodule_tb id'),
    'help' => t('mymodule_tb id.'),
    'relationship' => array(
      'base' => 'mymodule_tb_2', // The name of the table to join with
      'field' => 'mid', // The name of the field to join with
      'id' => 'standard',
      'label' => t('linked table to mymodule_tb'),
    ),
        'field' => array(
      'id' => 'numeric',
    ),
    'sort' => array(
      'id' => 'standard',
    ),
    'filter' => array(
      'id' => 'numeric',
    ),
  );

  // Example plain text field.
  $data['mymodule_tb']['name'] = array(
    'title' => t('name'),
    'help' => t('entry name.'),
    'field' => array(
      'id' => 'standard',
    ),
    'sort' => array(
      'id' => 'standard',
    ),
    'filter' => array(
      'id' => 'string',
    ),
    'argument' => array(
      'id' => 'string',
    ),
  );
 
  $data['mymodule_tb']['type'] = array(
    'title' => t('type'),
    'help' => t('type: 1 blue, 2 green, 3 red'),
    'field' => array(
      'id' => 'numeric',
    ),
    'sort' => array(
      'id' => 'standard',
    ),
    'filter' => array(
      'id' => 'numeric',
    ),
  );   
      

// This write hook_views_data() for the linked table

  $data['mymodule_tb_2']['table']['group'] = t('My Module table 2');

  $data['mymodule_tb_2']['table']['base'] = array(
    'field' => 'id', // This is the identifier field for the view.
    'title' => t('My Module table 2'),
    'help' => t('My Module tb_2 contains linked data to mymodule_tb.'),
    'weight' => -10,
    'database' => 'external_db',
  );

  $data['mymodule_tb_2']['table']['join'] = array(
    'mymodule_tb' => array(
      'left_field' => 'id',
      'field' => 'mid',
      'database' => 'external_db',
    ),
  );

  //  ID table field.
  $data['mymodule_tb_2']['id'] = array(
    'title' => t('tb_2 id'),
    'help' => t('tb_2 id.'),
       'field' => array(
       'id' => 'numeric',
    ),
       'sort' => array(
       'id' => 'standard',
    ),
       'filter' => array(
       'id' => 'numeric',
    ),
  );
 
  $data['mymodule_tb_2']['mid'] = array(
    'title' => t('mymodule_tb id'),
    'help' => t('mymodule_tb id ref.'),
    'relationship' => array(
      'base' => 'mymodule_tb',
      'field' => 'id',
      'id' => 'standard',
      'label' => t('mymodule_tb entry'),
    ),
       'field' => array(
       'id' => 'numeric',
    ),
       'sort' => array(
       'id' => 'standard',
    ),
       'filter' => array(
       'id' => 'numeric',
    ),
  );


  $data['mymodule_tb_2']['comment'] = array(
    'title' => t('comment'),
    'help' => t('linked comment.'),
    'field' => array(
      'id' => 'standard',
    ),
    'sort' => array(
      'id' => 'standard',
    ),
    'filter' => array(
      'id' => 'string',
    ),
    'argument' => array(
      'id' => 'string',
    ),
  );
  return $data;
}

Few remarks about the above information:

  • In this example, we have a table linked to our main table which is described by $data['mymodule_tb']['table']['join'] and  $data['mymodule_tb_2']['table']['join']
  • The dabase containing the data is specified as 'external_db'. In our configuration, we do not use the default database of Drupal installation (this database must be defined in settings.php).

If we navigate to "/admin/structure/views/add", we can now create a view based on our main table content:

My Module view

In the next article we will describe how to create the page similar to our address book list view with specific rewrite results for "type" field and filter criterion.

Feel free to add your own comments or suggestions.

Jan 10 2016
JK
Jan 10

In this article we will describe how we created the calendar in our back-office management application EK (see demo).

Calendar

For this function, we used the FullCalendar plugin and its dependencies.

1) Create the Drupal 8 library

In a file called MyModule.libraries.yml, insert the following css and js configurations:

MyModule.calendar:
  version: VERSION
  css:
    theme:
      css/ek_calendar.css: {}
      js/cal/fullcalendar/fullcalendar.css: {}
      js/jquery.qtip/jquery.qtip.css: {}
  js:
    js/cal/fullcalendar/lib/moment.min.js: {}
    js/cal/fullcalendar/fullcalendar.js: {}
    js/jquery.qtip/jquery.qtip.min.js: {}
    js/cal/calendar_script.js: {}
  dependencies:
    - core/jquery
    - core/drupal
    - core/drupalSettings
    - core/drupal.ajax
    - core/drupal.dialog
    - core/jquery.ui.datepicker

Note: we also used jQuery qtip to display title pop-up in the calendar, but this not an obligation.

The file calendar_script.js is for custom javascript scripts needed in this implementation.

2) create the routes

In MyModule.routing.yml we will need 2 routes. The first one is the main route to the calendar display function in our countroller; the second one is to pull data displayed in the calendar from an ajax call.

MyModule_calendar:
  path: '/path/calendar'
  defaults:
    _title: 'Calendar'
    _controller: '\Drupal\MyModule\Controller\CalendarController::calendar'
  requirements:
    _permission: 'calendar'

MyModule_calendar_view:
  path: '/MyModule/calendar/view/{id}'
  defaults:
    _controller: '\Drupal\ek_projects\Controller\CalendarController::view'
  requirements:
    _permission: 'calendar'

The id in MyModule_calendar_view route is a key used to indentify the type of data to be retrieved. In our case we display different events from dates of projects status and tasks thus we filter base on event type in our controller (submission, start, deadline, etc...). But this can be adapted to your own case.

3) The form to filter content display

This form is very simple and used to filter the events and trigger the calendar display.

filter

Here is the function buildForm into the SelectCalendar Class

public function buildForm(array $form, FormStateInterface $form_state) {

    $options = [ 0 => t('Calendar'), 1 => t('My tasks') , 2 => t('Projects submission'),    3 => t('Projects validation'), 4 => t('Projects start')],

    $form['select'] = array(

      '#type' => 'select',

      '#id' => 'filtercalendar',

      '#options' => $options,

      '#attributes' => array('title' => t('display options'), 'class' => array()),

      );

        return $form;  

  }

validateForm and submitForm are not used as the form is actually never submitted;

4) Create the controller

In our controller CalendarController.php we have 2 functions that match the above routes: calendar() and view() plus a third function to display the calendar in a dialog box: dialog();

calendar() function is very basic as it only call the dialog box.

  /**
   * AJAX callback handler for Ajax Calendar Dialog
   */
  public function calendar() {
    return $this->dialog(TRUE);
  }

So when using route /MyModule/calendar, the actual response happens in the dialog function:

/**
   * Render dialog in ajax callback.
   *
   * @param bool $is_modal
   *   (optional) TRUE if modal, FALSE if plain dialog. Defaults to FALSE.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   An ajax response object.
   */
  protected function dialog($is_modal = FALSE) {
 
    $content = $this->formBuilder->getForm('Drupal\MyModule\Form\SelectCalendar');
    $content['content']['#markup'] = "<div id='calendar'></div>";
                        
    $response = new AjaxResponse();
    $title = t('Calendar');
    $l =  \Drupal::currentUser()->getPreferredLangcode();
    $content['#attached']['drupalSettings'] = array('calendarLang' => $l );
    $content['#attached']['library'] = array('core/drupal.dialog.ajax', 'ek_projects/ek_projects.calendar');
    $options = array('width' => '80%');
    
    if ($is_modal) {
      $dialog = new OpenModalDialogCommand($title, $content, $options);
      $response->addCommand($dialog);
    }
    else {
      $selector = '#ajax-text-dialog-wrapper-1';
      $response->addCommand(new OpenDialogCommand($selector, $title, $html));
    }
    return $response;
  }

Note: first the form to filter data is included with $this->formBuilder->getForm('Drupal\MyModule\Form\SelectCalendar');. Then we add a simple div markup in the content that will hold the calendar display generated by the plugin: $content['content']['#markup'] = "<div id='calendar'></div>"; Finally, the Ajax response is built with necessary parameters. The proper core ajax and dialog references in the controller are needed for the dialog to work as expected:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\OpenDialogCommand;


The view() function is where the customisation part is the most relevant. This function collect data to display in a database and send them back in appropriate format. Thus we will only show the basic structure as it may apply to multiple data sources or formats.

  /**
   * AJAX callback handler for task and event display in calendar
   */
  public function view($id) {
      $color_array = array('#CEE3F6','#CEEFF6','#CEF6F0','#CEE3F6','#CEF6D4');

// this array will hold the data to be send back for dispay
      $events=array();
 
      switch($id) {
          
          case 1 :
     
          //1 My tasks
          // query data here and format it
          // sample event value format required by fullCalendar:
          //        
          //      $values = array(
          //          'id' => 'entry id',
          //          'title' => 'string',
          //          'description' => 'string',
          //          'start' => 'date',
          //          'end' => 'date',
          //          'url' => "MyModule/path",
          //          'allDay' => 'True/False',
          //          'className' => "",
          //          'color' => $color_array[$n],  
          //          'textColor' => 'black',
          //      );
          //      array_push($events, $values);
            
              break;     
            case 2 :
              // query data here
              break;
            case 3 :
              // query data here      
              break;
            
            //etc.
        
    }
      return new JsonResponse($events);        
   }

5) Javascript

Here is the code in js/cal/calendar_script.js:

(function ($, Drupal, drupalSettings) {

  Drupal.behaviors.MyModule_calendar = {
    attach: function (context, settings) {
      
        jQuery( "#filtercalendar" )
          .bind( "change", function( event ) {
              jQuery('#loading').show();
              var h = screen.height;
              var w = screen.width;
              var dh = h*0.8;
              var dw = dh;
              var top = (h-dh)/3;
              var left = (w-dw)/2;
              jQuery('.ui-dialog').css({top: top});
              var option = jQuery(this).val();
              display_calendar(option,settings.calendarLang);
          });

    }
  };
 
    
  function display_calendar(e,calendarLang) {
        jQuery('#calendar').fullCalendar( 'destroy' );
        jQuery('#calendar').fullCalendar({
            header: {
                left: 'prev,next today',
                center: 'title',
                right: 'month,agendaWeek,agendaDay'
            },
            eventMouseover:true,
            lang: calendarLang,
            events: {
                url: drupalSettings.path.baseUrl + "MyModule/calendar/view/" + e,
                error: function() {
                    jQuery('#calendar-warning').show();
                }
            },
                        aspectRatio:  1.8,
                        timeFormat: 'H(:mm)',
                        agenda: 'h:mm{ - h:mm}',
                        loading: function(bool) {
                            jQuery('#loading').toggle(bool);
                        },
                        eventRender: function(event, element) {
                                element.qtip({
                                    content: event.description,
                                    target: 'mouse',
                                    adjust: { x: 5, y: 5 }
                                });
                            }
            });
  }  

})(jQuery, Drupal, drupalSettings);

What happen here is that the function display_calendar() which actually trigger the fullCalendar plugin action is bind to the form that filters the data and identified by its id '#filtercalendar'. This function simply call  fullCalendar with necessary options (including the events that are pulled from view()) and display it into the html div markup identified by its id  '#calendar'.

Feel free to comment or suggest other ways of creating a calendar.

Dec 23 2015
JK
Dec 23

In this article will will show a solution to add an ajax call to populate multiple information in a Drupal 8 form textarea element.

In this example, the script will autocomplete users list in the form for a custom module called MyModule. The user will enter first 2 letters of a name or email.

1) create a library

In MyModule.libraries.yml add the necessary javascript reference that will be used to populate the users in the form:


MyModule_lib:
  version: VERSION
  js:
    js/autocomplete.js: {}
  dependencies:
    - core/jquery
    - core/jquery.ui.autocomplete

The autocomplete function as dependencies which are based on jQuery library.

2) JS script

The jQuery autocomplete.js file that we use is copied below. It is implemented as Drupal behaviors You need to add this file in MyModule/js/ folder.
 

JS script:

(function ($, Drupal, drupalSettings) {

  Drupal.behaviors.MyModule_autocomplete = {
    attach: function (context, settings) {
         
    
      jQuery(function() {
        
        function split( val ) {
          return val.split( /,\s*/
          );
          }

        function extractLast( term ) {
          return split( term ).pop();
          }
          jQuery( "#edit-users" )
          .bind( "keydown", function( event ) {
            if ( event.keyCode === jQuery.ui.keyCode.TAB &&
            jQuery( this ).data( "ui-autocomplete" ).menu.active ) {
              event.preventDefault();
            }
          })
          .autocomplete({
            source: function( request, response ) {
              jQuery.getJSON("mypath/autocomplete", {
              term: extractLast( request.term )
              }, response );
            },
            search: function() {
              // custom minLength
              var term = extractLast( this.value );
                if ( term.length < 2 ) {
                  return false;
                }
            },
            focus: function() {
              // prevent value inserted on focus
              return false;
            },
            select: function( event, ui ) {
              var terms = split( this.value );
              // remove the current input
                terms.pop();
              // add the selected item
                terms.push( ui.item.value );
              // add placeholder to get the comma-and-space at the end - used for multi select
                terms.push( "" );
                this.value = terms.join( ", " );
              
              return false;
            }
        });
      });
    }
  };
})(jQuery, Drupal, drupalSettings);

3) Route

In the script above, the "path/autocomplete" is the path to the controller function that will handle and return the queries.

In your file "MyModule.routing.yml", you need to have a route to the function like:

form_autocomplete:
  path: '/mypath/autocomplete'
  defaults:
    _controller: '\Drupal\MyModule\Controller\MyController::autocomplete'

4) Controller

In MyController class, the autocomplete function will return the list of names that will be populated in the form textbox:

public function autocomplete(Request $request) {

        $text = $request->query->get('term');

        $query = "SELECT distinct name from {users_field_data} WHERE mail like :t1 or name like :t2 ";
        $a = array(':t1' => "$text%", ':t2' => "$text%");
        $name = db_query($query, $a)->fetchCol();

        return new JsonResponse($name);
    }

In the function above the 'term' is the user input. The script will query the database for users names or users emails that match the input and will return it as a Json array.

5) The form

In the custom module form, add the textarea element:

    $form['users'] = array(
      '#type' => 'textarea',
      '#rows' => 2,
      '#attributes' => array('placeholder' => t('enter recipients name separated by comma.')),
      '#required' => TRUE,
      '#default_value' => NULL,

6) the result

The result will show as below. When the user type first 2 letters of the name he is looking for, a list of matches will be displayed below the box and added to the list in the textarea when clicked. The values of the list are separated by a comma (see the terms.join( ", " ) function in autocomplete.js).

autocomplete textarea

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