Javascript, Web Development

Extending Select2 with Adapters

Original Post: https://bojanv91.github.io/posts/2017/10/extending-select2-with-adapters-and-decorators – Extending Select2 with Adapters: Bojan’s Notes.

I have ripped this post to avoid dead-links.

Starting from version 4.0, the Select2 jQuery plugin uses the adapter pattern as a way for developers to extend its features and behavior. Having implemented very custom select-based components, I can assure you that this is very powerful and useful feature.

Unfortunately, the docs about this feature do not include concrete usage examples, so it’s quite difficult to get started, to understand how to use it, and how to build on top of it – especially if you haven’t worked with jQuery plugins for a while. So, this article offers usage examples and describes how to use Select2 adapters and decorators feature.
What are adapters and decorators in Select2?
Adapter, adapts interface of an existing class to another interface. For example replacing the appearance of the selection input element with icon element.

Decorator, attach additional responsibilities to an object dynamically. For example adding functional checkboxes in multiple select dropdown items, or dedicated search field in multiple selection dropdown.

Select2 has several built-in adapters that can be used, overridden, and modified. You can find them explained in the advanced section in the docs, and their source code in the GitHub repository. For example, here is how SelectionAdapter is implemented (link to source file).

From the internal use of the adapters in select2 source code you can actually re-use and adapt their code in your apps. This is the approach that helped me to better understand how to use this feature.

You can take total control of the appearance and behavior of select2-based elements in your web apps by learning and exposing the full potential of this feature.

Defining and using custom adapters
A Select2 adapter is defined using an AMD module. Adapters can use other adapters or objects (to extend or decorate) by taking AMD module dependencies. Select2 automatically loads modules when the adapters are being constructed.

$.fn.select2.amd.define(“CustomSelectionAdapter”,
[
“select2/utils”,
“select2/selection/multiple”,
“select2/selection/placeholder”,
“select2/selection/eventRelay”,
“select2/selection/single”,
],
function (Utils, MultipleSelection, Placeholder, EventRelay, SingleSelection) {
// Here goes the code of this custom adapter
}
);
This piece of code defines a custom SelectionAdapter. It should be executed only once. Usually it is called from application start, just after external libraries (like jQuery, Select2) are loaded, or just before it’s first usage, in a lazy-execution way. For single-page applications, the lazy-execution is preferred approach.

Custom adapters can be used when constructing select2 elements, by requiring the AMD module in which they are defined to the select2 configuration API. Bellow is an example where the already defined custom selection adapter is used.

$(htmlElement).select2({
data: sampleDataAsArray,
selectionAdapter: $.fn.select2.amd.require(“CustomSelectionAdapter”)
});
Now, the htmlElement will be transformed to a select2 element with customized selection interface and behavior, as defined in CustomSelectionAdapter. Besides customizing the selectionAdapter, you can customize also the resultsAdapter, dataAdapter, ajaxAdapter, resultsAdapter, and dropdownAdapter.

Example: Custom multiple select
This example demonstrates the full power of adapters and decorators feature. It extendsSelectionAdapter and DropdownAdapter to fulfil the goals.

Default behavior of the multiple select, to be modified is:

Ability to search for items directly from the selection-box.
Show selected items in the selection-box.
Desired new behavior:

Ability to search items from a dedicated search box, shown in the dropdown (same as in single select).
Show number of selected items out of the total in the selection-box. Also, show arrow icon in the selection-box.
At image bellow, the default and desired outcomes are visualized.

Custom multiple select

To make select2 do the desired behavior, two custom adapters need to be written, a SelectionAdapter and a DropdownAdapter.

Find the complete solution example on jsFiddle.
Step 1 – create custom selection adapter

$.fn.select2.amd.define(“CustomSelectionAdapter”, [
“select2/utils”,
“select2/selection/multiple”,
“select2/selection/placeholder”,
“select2/selection/eventRelay”,
“select2/selection/single”,
],
function(Utils, MultipleSelection, Placeholder, EventRelay, SingleSelection) {

// Decorates MultipleSelection with Placeholder
let adapter = Utils.Decorate(MultipleSelection, Placeholder);
// Decorates adapter with EventRelay – ensures events will continue to fire
// e.g. selected, changed
adapter = Utils.Decorate(adapter, EventRelay);

adapter.prototype.render = function() {
// Use selection-box from SingleSelection adapter
// This implementation overrides the default implementation
let $selection = SingleSelection.prototype.render.call(this);
return $selection;
};

adapter.prototype.update = function(data) {
// copy and modify SingleSelection adapter
this.clear();

let $rendered = this.$selection.find(‘.select2-selection__rendered’);
let noItemsSelected = data.length === 0;
let formatted = “”;

if (noItemsSelected) {
formatted = this.options.get(“placeholder”) || “”;
} else {
let itemsData = {
selected: data || [],
all: this.$element.find(“option”) || []
};
// Pass selected and all items to display method
// which calls templateSelection
formatted = this.display(itemsData, $rendered);
}

$rendered.empty().append(formatted);
$rendered.prop(‘title’, formatted);
};

return adapter;
});
Step 2 – create custom dropdown adapter

$.fn.select2.amd.define(“CustomDropdownAdapter”, [
“select2/utils”,
“select2/dropdown”,
“select2/dropdown/attachBody”,
“select2/dropdown/attachContainer”,
“select2/dropdown/search”,
“select2/dropdown/minimumResultsForSearch”
],
function(Utils, Dropdown, AttachBody, AttachContainer, Search, MinimumResultsForSearch) {

// Decorate Dropdown with Search functionalities
let dropdownWithSearch = Utils.Decorate(Dropdown, Search);
dropdownWithSearch.prototype.render = function() {
// Copy and modify default search render method
var $rendered = Dropdown.prototype.render.call(this);
// Add ability for a placeholder in the search box
let placeholder = this.options.get(“placeholderForSearch”) || “”;
var $search = $(
‘ +
” +

);

this.$searchContainer = $search;
this.$search = $search.find(‘input’);

$rendered.prepend($search);
return $rendered;
};

// Decorate the dropdown+search with necessary containers
let adapter = Utils.Decorate(dropdownWithSearch, AttachContainer);
adapter = Utils.Decorate(adapter, AttachBody);

return adapter;
});
Step 3 – use the defined adapters

$(“#multipleWithSearch”).select2({
data: testData,
placeholder: “Select items”,
placeholderForSearch: “Filter items”, // additional placeholder for search box
closeOnSelect: false,
// Make selection-box similar to single select
selectionAdapter: $.fn.select2.amd.require(“CustomSelectionAdapter”),
templateSelection: (data) => {
return `Selected ${data.selected.length} out of ${data.all.length}`;
},
// Add search box in dropdown
dropdownAdapter: $.fn.select2.amd.require(“CustomDropdownAdapter”)
});
Find the complete solution example on jsFiddle.

Summary
This article explains how you can extend Select2 plugin by utilizing its adapters and decorators feature.

To create better custom adapters, try to re-use the built-in adapters, and modify them on the go. By doing that you will learn more about select2 internals, thus your custom adapters will be much cleaner and you will have less code to debug.

Finally, observe the example code on jsFiddle, modify it and see how you can further customize select2 as per your needs.

NOTE: I use the select2 plugin in my Aurelia apps by wrapping common behavior in custom elements. So far, so good. It’s nice that we are able to easily re-use proven components/plugins from other major platforms.

Advertisements
Ramblings

BuddyPress, Peepso and Others Are Not Viable WordPress Social Network Plugins

I was asked today to install Peepso for someone who wanted a private social network.

Aside from the obvious points of how this is a terrible idea, there is an even bigger problem.

I pointed out that these WordPress installs are awful and should never be used in production.

After, he asked “Why?” To which I replied “It uses only Ajax for everything” As you can guess, he then replied “Ajax?”

My final statement was: “Yes, Ajax. It allows for the notifications and chat you see, however, all of these plugins means a page opens about 3 concurrent Ajax connections and many browsers, including Chrome, limit number the of concurrent Ajax connections in a single browser session to 6 which means someone can only have your page open on two browser tabs…”

More than one WordPress developer obviously did not know that to create a set of plugins that cannot get around that problem, this is why WordPress developers are rarely web developers.

Web Development

Adwords Ad Disapproved for Seemingly Fine Ad (Clarity and relevance)

So, I was working with Adwords recently and I made a custom set of image ads. It was a set of image ads that were actually previously approved by Google, however, this time when I uploaded them they were not. I was about to just find out that Google Adwords moderators had got a lot stricter on what information needs to be displayed in new ads.

This is not something you will hear from your account manager. Mine couldn’t understand why the ad was disapproved either.

I found this out through trial and error.

As stated by Google https://support.google.com/adwordspolicy/answer/6021546?hl=en-GB#326 in their rules for display:

Make sure that you include your product name, company name, logo or Display URL in your promotion. For animated ads, please ensure that once the animation has ceased, the final static frame clearly displays identifying information such as a product or company name, logo or Display URL.

However, if you have seen Google Ads before you will notice they are not very good at implementing this rule. You can have an ad running that does not show a product name or company name and many don’t.

However, it is hard to understand that this is the problem. The only message you will get in your email is:

Ad Text:

320×50.png
http://www.xxx.com

Ad Status: Disapproved
Ad Issue(s): Clarity and relevance
~~~~~~~~~

SUGGESTIONS:
—> To keep our ads professional and effective, we don’t allow ads that are unclear, confusing, low quality or that make unsupported claims. Ads also need to lead users to relevant content and accurately reflect the business, product or service that’s promoted on your site. Edit your ad to make it easy to read and specifically related to what’s promoted on your landing page.

But this is deceptive in itself. Once I added my company name to all my ads they were approved.

PHP, Web Development, Yii Framework

Stop Newrelic From Recording Slow AJAX/General Calls in Yii2

<?php

namespace common\components;

use Yii;
use yii\base\Component;
use yii\base\BootstrapInterface;
use yii\web\Application;
use yii\web\Controller;

class Newrelic extends Component implements BootstrapInterface
{
    public $ignoreRoutes = [];

    public function bootstrap($app)
    {
        // delay attaching event handler to the view component after it is fully configured
        $app->on(Controller::EVENT_BEFORE_ACTION, function () use ($app) {
            $this->beforeAction($app);
        });
    }

    public function beforeAction($app)
    {
        if (extension_loaded('newrelic')) {
            if (in_array(Yii::$app->getUrlManager()->parseRequest($app->request)[0], $this->ignoreRoutes)) {
                newrelic_ignore_transaction();
            }
        }
    }
}
SEO, Web Development

Should search pages be blocked in robots?

I recently got a problem on a site I co-manage whereby Goolge Webmaster Tools suddenly stated one day, after some extensive crawling, that the site had far too many URLs.

In the list I saw many search result pages and after some investigation various links directed me to this page whereby, at the bottom, it says:

Typically, you should consider blocking dynamic URLs, such as URLs that generate search results,

So, it seems that this, normally, grey area is quite black and white now-a-days and you should be blocking your search from robots, else Google could penalise you by not correctly crawling your site.

CSS, Web Development

Responsive images via background-size

As I was doing some image uploading work on a project recently I decided to understand, truly, if, for image post-processing, I should resize and cut corners off to keep ratio or thumbnail and keep the independent size of the image, but risk making it difficult to put into the page.

As an example, I have an image of 700px x 418px. I have a choice between cutting it down to 400px x 400px, cutting off 300px or bringing it down to about 400px x 238px and make it more difficult to add to a Bootstrap powered gallery.

So, I looked at what Facebook did here and I noticed they actually use the 400×238 image as a background image and then used background-size:cover to fix any problems. Fortunately, despite what caniuse.com (http://caniuse.com/#feat=background-img-opts) says, background-size is supported in IE8 (I actually got a Windows 7 VM with IE8 on it from modern.ie and tested it) but, with no content, the media queries for resizing the columns could not function right.

So I need something to tell both IE8 and later browsers how to size this image.

The CSS solution at this link works perfect: http://www.smashingmagazine.com/2013/07/22/simple-responsive-images-with-css-background-images

In short, here is the post summarised:

<div style="width: 400px">
    <span id="image">
        <span id="image-inner">
        </span>
    </span>
</div>

#image {
    display: inline-block;
    width: 100%;
    font-size: 0;
    line-height: 0;
    vertical-align: middle;
    background-size: 100%;
    background-position: 50% 50%;
    background-repeat: no-repeat;
    background-image: url(image.jpg); 
}


#image-inner {
    display: block;
    height: 0;
    padding-top: 63.3%;
}