Drupal libraries files

§
by
Jesper bisgaard

One of the great features in Drupal 8 is the ability to create theme libraries which can group css and javascript files. These libraries can be loaded along with a template which means the scripts are only loaded when the template is used, significantly reducing the blueprint of the css and javascript needed for the page.

The way to create a library by creating a .libraries.yml in your theme or module. The format of a library entry is as follows:

my-template:
  version: 1.x
  css:
    component:
      css/font-awesome.min.css: {}
    theme:
      css/my-template.css: {}
  js:
    js/my-template.js: {}
  dependencies:
    - core/jquery

There can be multiple entries in a single library file.

You can specify the css on multiple levels:

You can set CSS weights with 5 different levels of styling:

  • base: CSS reset/normalize plus HTML element styling.
    • Key assigns a weight of CSS_BASE = -200
  • layout: macro arrangement of a web page, including any grid systems.
    • Key assigns a weight of CSS_LAYOUT = -100
  • component: discrete, reusable UI elements.
    • Key assigns a weight of CSS_COMPONENT = 0
  • state: styles that deal with client-side changes to components.
    • Key assigns a weight of CSS_STATE = 100
  • theme: purely visual styling (“look-and-feel”) for a component.
    • Key assigns a weight of CSS_THEME = 200

These are based on the SMACSS standard. One thing to note which I have found was that setting the version in the library file will lock the css and javascript for that library entry until the version changes. Which means if you make changes to the css and javascript files without updating the version, your changes wont be applied to the site.

So for the most cases you can leave out the version.

my-template:
  css:
    component:
      css/font-awesome.min.css: {}
    theme:
      css/my-template.css: {}
  js:
    js/my-template.js: {}
  dependencies:
    - core/jquery

 

There are many ways to include the library. In order to include it in a template you can use the following twig code:

{{ attach_library('your_module/library_name') }}

The drupal.org description of library files can be found here.

Meteor SEO

§
by
Jesper bisgaard

Once its time to go live with your meteor site, you want to make sure search engines can crawl your site and are aware of all your content. For this to work properly there are a few things you need to setup.

First of all have a look at meteors guide to setting up prerender this service will provide a cached version of your pages and store it for 14 days or until you clear the cache. When a crawler accesses your site prerender will serve the cached page instead of the meteor page. This helps you make sure everything crawlers knows what content you have on your pages.

In your settings.json file in the root of the meteor project your just add the following config to get prerender setup. If you don't have a settings.json this is the time to create it. You properly want one for your production environment as well, this could be settings-production.json.

"PrerenderIO": {
  "serviceUrl": "http://service.prerender.io/",
  "token": "xxxxxxxxxxxxx"
},

The next step is setting up you sitemap. For this i have used the excellent package gadicohen:sitemaps which can be found on atmospherejs. Its easy to setup and has great options for customizing you sitemap. My site map has been setup very basic but does the job. You add a sitemap.js to your server folder, which servers your sitemap config, here is mine as an example.

sitemaps.add('/sitemap.xml', function() {
  // required: page
  // optional: lastmod, changefreq, priority, xhtmlLinks, images, videos
  return [
    { page: '/', lastmod: new Date(), changefreq: 'monthly' },
    { page: '/our-service', lastmod: new Date(), changefreq: 'monthly' },
    { page: '/about-us',
      lastmod: new Date(),
      changefreq: 'monthly',
    },
    { page: '/faq', lastmod: new Date(), changefreq: 'monthly' }
  ];
});

Now we will add our robots.txt, for this I have used gadicohen:robots-txt its as simple as adding the package and your are done. If you want you can add more lines to the robots.txt file using the command:

robots.addLine('line');

Finally we properly want to add google tracking either as analytics or tag manager. I personally prefer Google Tag Manager since it allows me to add additional scripts without having to maintain the code in my repository so for this I have used the package fuww:google-tag-manager and set it up by adding the following to my server script:

addGoogleTagManager('GTM-XXXXXX');

And your are ready to tackle the we.

I hope this was helpfull, if you have any comments catch me on twitter and lets chat.

Meteor dropdown

§
by
Jesper bisgaard

In a recent project I had to add a dropdown in meteor and found that there where a few tricks to going about it, so I thought I would share it here for anyone who might need it. Setting up the dropdown was fairly easy but making the functionality work had a few twists that I want to share with you guys. In my case I wanted to build a dropdown which displayed a form which allowed the user to update some content.

Again I am going to use Bulma which has become my prototyping framework of choise as of late. Its super easy to work with and has a lot of great predefined templates. So a big shoutout to the people behind Bulma.

First lets setup our dropdown with the form inside.

Bulma has an excellent dropdown template which we will use for this.

<template name="dropdown">
  <div class="dropdown">
    <div class="dropdown-trigger">
      <button class="button" aria-haspopup="true" aria-controls="dropdown-menu2">
        <span>Content</span>
        <span class="icon is-small">
          <i class="fa fa-angle-down" aria-hidden="true"></i>
        </span>
      </button>
    </div>
    <div class="dropdown-menu" id="dropdown-menu2" role="menu">
      <div class="dropdown-content">
        <div class="dropdown-item">
          Here we will add our form.
        </div>
      </div>
    </div>
  </div>
</template>

Now we have the dropdown setup, so lets add the form. This is just a simple form for signing up for a news letter and again we are using bulmas form templates.

<template name="dropdown">
  <div class="dropdown">
    <div class="dropdown-trigger">
      <button class="button" aria-haspopup="true" aria-controls="dropdown-menu2">
        <span>Content</span>
        <span class="icon is-small">
          <i class="fa fa-angle-down" aria-hidden="true"></i>
        </span>
      </button>
    </div>
    <div class="dropdown-menu" id="dropdown-menu2" role="menu">
      <div class="dropdown-content">
        <div class="dropdown-item">
          <div class="field is-grouped">
            <label class="label">Subscribe to newsletter</label>
            <div class="control has-icons-left has-icons-right">
              <input class="input" type="email" placeholder="Enter your email">
              <span class="icon is-small is-left">
                <i class="fa fa-envelope"></i>
              </span>
              <span class="icon is-small is-right">
                <i class="fa fa-check"></i>
              </span>
            </div>
            <p class="control">
              <a class="button is-info">Submit</a>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

Now that we have this setup we need to add some functionality to the meteor application for it to work. The first thing we need to do is switch the is-active class on the dropdown wrapper. I did this by adding a state to the meteor element containing the dropdown. If you are not familiar with how this is done in meteor you can read about state management here. First we add the ReactiveVar library which we can use to store temporary data for the client side.

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

Then we need to setup the new ReactiveVar state object. We will do this in the onCreate hook which is invoked when the element is created on the page.

Template.dropdown.onCreated(function bodyOnCreated() {
  this.active = new ReactiveVar();
});

Now that we have the ReactiveVar setup we can create the event which will set the value when a user clicks the dropdown.

We do this by adding a new event to the blaze template and let it activate on the click of the button.

Template.dropdown.events({
  'click button'(event, instance) {
    // toggle the active variable.
    instance.active.set(!instance.active.get());
  },
});

Now that the ReactiveVar is being toggled between true and false, we can add the last bit, where we use the active state and add the is-active class if the user has clicked the dropdown button.

First we add a helper function to the blaze template.

Template.dropdown.helpers({
  isActive() {
    return Template.instance().active.get();
  },
});

We can then use this helper function to determine if the is-active class should be applied to the dropdown template.

<div class="dropdown {{#if isActive}}is-active{{/if}}">
    ...
  </div>

On the dropdown wrapper we add a new if statement which checks if the helper function returns true and if so we add the is-active class.

There we have the it, we have now created a dropdown using meteor and bulma.

You can see the code at my github repo.

Meteor modal

§
by
Jesper bisgaard

I recently had to create a modal pop up in Meteor js, which proved to be quite easy so I thought I might share the solution here.

For handling modals I used the exellent library anti:modals which handles opening and closing the modals on a page. Now you just have to hook up the content for the modal and you are ready to rock!

Once you have added the library to your meteor site you can go ahead and add your modals.

First you setup the modal template, I am using Bulma for markup and styling in this example.

I have created a template file editExample.html

<template name="editExample">
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-card">
      <header class="modal-card-head">
        <p class="modal-card-title">Edit example</p>
        <button id="close-edit-modal" class="delete" aria-label="close"></button>
      </header>
      <section class="modal-card-body">
        <form class="edit-example">
          <div class="field is-horizontal">
            <div class="field-label is-small">
              <label class="label" for="name">New example</label>
            </div>
            <div class="field-body">
              <div class="field">
                <p class="control is-expanded">
                  <input class="input is-small" type="name" name="name" placeholder="New example" value="{{example}}" />
                </p>
              </div>
            </div>
          </div>
          <div class="field is-horizontal">
            <div class="field-label">
              <!-- Left empty for spacing -->
            </div>
            <div class="field-body">
              <div class="field">
                <div class="control">
                  <input class="button is-primary is-small" type="submit" value="Save"/>
                </div>
              </div>
            </div>
          </div>
        </form>
      </section>
    </div>
  </div>
</template>

In this file we setup the html markup for our modal popup.

Now the next think we have got to do is create the functionality for this template. So we will create an editExample.js file.

import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';

// Import the template.
import './editExample.html';

// Store the example being passed to the modal.
Template.editExample.onCreated(function () {
  this.example = this.data.example;
});

// Setup event handling.
Template.editExample.events({
  // Clicking the close button.
  'click #close-edit-modal'() {
    // Close all active modal windows.
    AntiModals.dismissAll();
  },
  // Submitting the form in the modal window.
  'submit .edit-example'(event) {
    // Prevent default browser form submit.
    event.preventDefault();

    // Get value from form element.
    const target = event.target;
    const name = target.name.value;

    // Insert a expense into the collection.
    Meteor.call('example.update', this.example._id, name);

    // Clear form.
    target.name.value = '';

    // Close all active modal windows.
    AntiModals.dismissAll();
  },
});

Now we are ready to start using our modal popup.

In order to display your modal popup, we need to add a few things to a page or component.

First we need to import the new modal component.

import '../modal/editExample';

Next we add a button which will trigger an event. So in the html template where we want to use our modal we add:

<a class="button edit-example">Edit example</a>

And as the last piece of the puzzel we add the action which invokes the modal:

'click .edit-example'(e) {
  e.preventDefault();
  AntiModals.overlay('editExample', {'data': {'example': this}});
}

And thats it. A very simple library for displaying modal popups in meteor.

Wysiwyg button with form #4

§
by
Jesper bisgaard

Welcome to the second article in my tutorial about building a wysiwyg button for drupal 7 which enables use to inserting tokens.

This will be a four step tutorial

  1. Step one will be building the module and a simple wysiwyg button
  2. Step two will cover a simple wysiwyg button
  3. Step three will expand the button with a multi field form
  4. Step four will make this form dynamic with ajax callbacks for removing and adding fields

This next part will cover how to expand the form to use ajax for dynamic adding and removing fields.

I followed this exellent article about how to implement det dynamic adding and removing fields, but since we are using jquery ajax to load our for we don't have the luxury of drupals ajax to ensure ajax handling for our form and we need to add this in.

/**
 * Insert token form
 */
function example_insert_form($form, &$form_state) {
  drupal_add_library('system', 'ui.dialog');
  $form['#tree'] = TRUE;
  if (empty($form_state['num_objects'])) {
    $form_state['num_objects'] = 1;
  }
  $ids = null;
  $args = $form_state['build_info']['args'];
  if(isset($args[0])) {
    $ids = explode(',', $args[0]);
    if($form_state['num_objects'] == 1) {
      $form_state['num_objects'] = count($ids);
    }
  }
  $form['objects'] = array(
    '#type' => 'fieldset',
    '#title' => t('Objects'),
    '#prefix' => '<div id="example-fieldset-wrapper">',
    '#suffix' => '</div>',
  );
 
  for($i = 0; $i < $form_state['num_objects']; $i++) {
    $form['objects'][$i]['object'] = array(
      '#type' => 'textfield',
      '#title' => t('Entity id'),
      '#prefix' => '<div class="col1">',
      '#suffix' => '</div>',
      '#default_value' => isset($form_state['values'][$i]) ? $form_state['values'][$i]['object'] : '',
    );
    if(isset($ids[$i])) {
      $form['objects'][$i]['object']['#default_value'] = $ids[$i];
    }
  }
 
  $form['objects']['add_item'] = array(
    '#type' => 'submit',
    '#value' => t('Add another'),
    '#submit' => array('example_add_more_add_one'),
    // See the examples in ajax_example.module for more details on the
    // properties of #ajax.
    '#ajax' => array(
      'callback' => 'example_add_more_callback',
      'wrapper' => 'example-fieldset-wrapper',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
  if ($form_state['num_objects'] > 1) {
    $form['objects']['remove_item'] = array(
      '#type' => 'submit',
      '#value' => t('Remove one'),
      '#submit' => array('example_add_more_remove_one'),
      '#ajax' => array(
        'callback' => 'example_add_more_callback',
        'wrapper' => 'example-fieldset-wrapper',
        'method' => 'replace',
        'effect' => 'fade',
      ),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
    '#attributes' => array(
      'class' => array('form-save-ids'),
    ),
  );

  return $form;
}

/**
 * Callback for both ajax-enabled buttons.
 *
 * Selects and returns the fieldset with the names in it.
 */
function example_add_more_callback($form, $form_state) {
  return $form['objects'];
}

/**
 * Submit handler for the "add-one-more" button.
 *
 * Increments the max counter and causes a rebuild.
 */
function example_add_more_add_one($form, &$form_state) {
  $form_state['num_objects']++;
  $form_state['rebuild'] = TRUE;
}

/**
 * Submit handler for the "remove one" button.
 *
 * Decrements the max counter and causes a form rebuild.
 */
function example_add_more_remove_one($form, &$form_state) {
  if ($form_state['num_objects'] > 1) {
    $form_state['num_objects']--;
  }
  $form_state['rebuild'] = TRUE;
}

As you can see we are using a simple count setting to increase or decrease the number of text fields in the form. This works fine for our simple form and we just need to add a few changes to the js in order to forward this change from the client to the backend. So make the following change to the insert_form function:

  insert_form: function (data, settings, instanceId) {
    // Location, where to fetch the dialog.
    var aurl = Drupal.settings.basePath + 'example/insert/ajax';
    if(settings.ids) {
      aurl += '/' + settings.ids.join();
    }
    var dialogdiv = $('<div id="example-insert-dialog"></div>');
    dialogdiv.load(aurl, function(){
      var dialogClose = function () {
        try {
          dialogdiv.dialog('destroy').remove();
        } catch (e) {};
      };

Now that we have this in place we need to expand our menu so we can send the id variables to the form.

/**
 * implements hook_menu
 */
function example_menu() {
  $items = array();
  $items['example/insert/nojs'] = array(
    'page callback' => 'example_get_insert_form',
    'page arguments' => array(2),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['example/insert/ajax'] = array(
    'delivery callback' => 'ajax_deliver'
  ) + $items['example/insert/nojs'];
 
  $items['example/insert/nojs/%'] = array(
    'page callback' => 'example_get_insert_form',
    'page arguments' => array(2, 3),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['example/insert/ajax/%'] = array(
    'delivery callback' => 'ajax_deliver'
  ) + $items['example/insert/nojs/%'];
  return $items;
}

If you implement this, you will find that the ajax callbacks aren't exactly using ajax yet. This is because as I said we don't have the luxury of drupals ajax handing when we are using the jquery ajax load function. In order to fix this we need to make a few changed to the ajax get form function so we compile the scripts and add then to the data being returned.

/**
 * Retrive the insert form.
 */
function example_get_insert_form($ajax, $ids = '') {
  $is_ajax = $ajax === 'ajax';
  $form = drupal_get_form('example_insert_form', $ids);
  if ($is_ajax) {
    $form = drupal_render($form);
    // Generate the settings:
    $settings = '';
    $javascript = drupal_add_js();
    if(isset($javascript['settings'], $javascript['settings']['data'])) {
      $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
      $settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
      $settings .=  ');</script>';
    }
    die($form . $settings);
  }
  else {
    return $form;
  }
}

Now that this is done, the form uses ajax to add or remove fields to the form and it keeps the already entered data while doing to.

I hope this helps and if you have any questions let me now. For a working version of this see ting_object

Wysiwyg button with form #2

§
by
Jesper bisgaard

Welcome to the second article in my tutorial about building a wysiwyg button for drupal 7 which enables use to inserting tokens.

This will be a four step tutorial

  1. Step one will be building the module and a simple wysiwyg button
  2. Step two will cover a simple wysiwyg button
  3. Step three will expand the button with a multi field form
  4. Step four will make this form dynamic with ajax callbacks for removing and adding fields

For this part we will build a simple wysiwyg button which inserts the basic struckture of our token.

This code is highly inspired by this article on http://deglos.com

To begin with we need to tell drupal that we have a plugin for wysiwyg in our module. We do this my invoking the hook_wysiwyg_include_directory.

/**
 * implements hook_wysiwyg_include_directory
 */
function example_wysiwyg_include_directory($type) {
  return $type;
}

now we need to build the plugin structure, so i our module we will create a new folder called plugins. In this folder we need an .inc file with the same name as our plugin as well as a folder with the same name. In the folder we then need to create an images folder and place an icon image file there. I created a icon.png for this. besides the images folder we need to create two files, a js and a css file with the same names as the plugin.

So if we give our plugin the name tokenInsert the file structure should be

module_name/plugins/tokenInsert.inc
module_name/plugins/tokenInsert/tokenInsert.js
module_name/plugins/tokenInsert/tokenInsert.css
module_name/plugins/tokenInsert/images/icon.png

Okay now for the code in the files.

In the tokenInsert.inc file we will provide wysiwyg with information about our plugin by invoking the hook_wysiwyg_plugin

/**
 * Implementation of hook_wysiwyg_plugin().
 */
function example_tokenInsert_plugin() {
  $plugins['tokenInsert'] = array(
    'title' => t('Insert object'),
    'icon file' => 'icon.png',
    'icon title' => t('Insert objects'),
    'settings' => array(),
  );
  return $plugins;
}

that is it for this file.

The javascript file will provide the functionality for wysiwyg.

// $Id$
(function ($) {
 
Drupal.wysiwyg.plugins['example'] = {
 
  /**
   * Return whether the passed node belongs to this plugin (note that "node" in this context is a JQuery node, not a Drupal node).
   *
   * We identify code managed by this example plugin by giving it the HTML class
   * 'wysiwyg-plugin-example'.
   */
  isNode: function(node) {
    res = $(node).is('img.wysiwyg-plugin-example');
    return ($(node).is('img.wysiwyg-plugin-example'));
  },
 
  /**
   * Invoke is called when the toolbar button is clicked.
   */
  invoke: function(data, settings, instanceId) {
     // Typically, an icon might be added to the WYSIWYG, which HTML gets added
     // to the plain-text version.
     if (data.format == 'html') {
       var content = this._getPlaceholder(settings);
     }
     else {
       var content = '<!--wysiwyg-plugin-example-->';
     }
     if (typeof content != 'undefined') {
       Drupal.wysiwyg.instances[instanceId].insert(content);
     }
   },
 
  /**
   * Replace all <!--wysiwyg-plugin-example--> tags with the icon.
   */
  attach: function(content, settings, instanceId) {
    content = content.replace(/<!--wysiwyg-plugin-example-->/g, this._getPlaceholder(settings));
    return content;
  },
 
  /**
   * Replace the icons with <!--wysiwyg_example_plugin--> tags in content upon detaching editor.
   */
  detach: function(content, settings, instanceId) {
    var $content = $('<div>' + content + '</div>');
    $.each($('img.wysiwyg-plugin-example', $content), function (i, elem) {
      elem.parentNode.insertBefore(document.createComment('wysiwyg-plugin-example'), elem);
      elem.parentNode.removeChild(elem);
    });
    return $content.html();
  },
 
  /**
   * Helper function to return a HTML placeholder.
   *
   * Here we provide an image to visually represent the hidden HTML in the Wysiwyg editor.
   */
  _getPlaceholder: function (settings) {
    return '<img src="' + settings.path + '/images/icon.png" alt="&lt;--wysiwyg-plugin-example-&gt;" title="&lt;--wysiwyg-plugin-example--&gt;" class="wysiwyg-plugin-example drupal-content" />';
  }
};
 
})(jQuery);

The css file should contain styling specific for this plugin and what it renders, make sure to keep it aas general as possible so it is easy to overwrite.

That is it for our initial button.

Now we need to expand it with a multifield form to allow us to insert token elements based on user input.

Handlebars js

§
by
Jesper bisgaard

I have recently begun using handlebars js for my various jquery mobile apps. This has been a great experience and here i will try to explain why and how i have used it. I will also include extensions and plugins which i have used to handle the development.

The challenge has been entering dynamic content into lists or as changes based on user actions, and often the html for the dynamic content would be entered as strings in the javascript. But a solution for this is using a templating system like handlebars. It allows you to define templates in <script/> tags which can then be referenced in your code when you need dynamic content.

For my projects I prefere not to have my templates in <script/> tags so I found several solutions which allowed me to make separate template files. For my projects I used sync_async_loading_handlebars which is a very simple script but it handles the task really well. Using jquery mobile i found that I got the best performance if I preloaded the templates using the T.prefetch function. This loads the template data into a cache object from where it can easily be referenced later on.

Using sync_async_loading_handlebars i then entered dynamic content by creating a data object and calling T.render, as follows:

var themeData = { 
  title: 'test', 
  src: 'data', 
  class: 'some class' 
}; 
T.render('testTemplateItem', function(t) { $('.target').append(t(themeData)); }); 

My template would then look something like this.

<div class="{{class}}">
  {{#if src}}
  <div class="image-container"><img src="{{src}}" /></div>
  {{/if}}
  <h2>{{title}}</h2>
</div>

I also found a library of handlebars extensions which are very handy. The people behind swag have composed a library of extensions which contain most of the theming functionality you could possibly want and then some.

 

 

Hopscotch tour js

§
by
Jesper bisgaard

I had to include some help text for a mobile app i am developing and maintaining and for this we decided to use a tour script because it would allow us to keep the screen clear of static description text and at the same time give a smooth intoduction to the elements which needed explanations.

I tested out several scripts like joyride and pageguide, but found that the one which worked best with jquery mobile was hopscotch js. It allows lots of configuration and you configure the tours in json, which keeps the html of the app clean.

Drupal 7 form cancel button

§
by
Jesper bisgaard

When creating a form it is often required to make a cancel or back button, but this provides a number og issues with form validation but fortunetly there is a way to prevent these issues.

Create your cancel or back button.

$form['actions']['back'] = array( 
  '#type' => 'submit', 
  '#value' => t('Back'), 
); 

You can use either type: submit, image_button or button. In order to prevent validation of the form elements you need to add #limit_validation_errors and #submit to the element.

$form['actions']['back'] = array(
  '#type' => 'submit', 
  '#value' => t('Back'), 
  '#limit_validation_errors' => array(), 
  '#submit' => array('example_form_submit'), 
); 

#limit_validation_errors contains an array of elements to validate on submit and since it is empty in this case nothing is validated

In order for #limit_validation_errors to work you also need to specify a submit function to call otherwise the element attribute is ignored.

Death of the human policlub extrimist

§
by
Jesper bisgaard

A metahuman rights activist hires the runners to kill an extrimist policlub leader along with any member of his group they can find. They are hiding out somewere between Seattle and the NaN territories.

In order to find the group the runners have to infiltrate the local Seattle policlub and find out that a pair of their local members are delivering food to the extrimist group once a week.