Select2: Custom matcher and highlighting

September 28th, 2015 Admin No comments

    The Select2 uses the “contains”-matcher by default. In case of a big number of options, we’ve run into the time lag whenever the drop-down appears with the filtered out items. Of course, we could increase the minimumInputLength setting to minimize items within the drop-down. We, however, decided to change the algorithm used for filtering. The method below looks for options, any word of which is starting with the pattern typed in.

function wordStartMatcher(term, text, highlighting) {
	var myRe  = new RegExp("(?:^|\\s)" + term, "i");
	var match = myRe.exec(text);

	if (match != null && highlighting) {
		myRe = new RegExp("\\b" + term, "i");
		match = myRe.exec(text);
	}

	return match;
}

The method is used twice: for filtering out and for highlighting the found pattern in options. Two regexps in the method do the same, they both capture the beginning of each word. However, they handle Unicode in different ways. So the first one accurately chooses options that match while the second one fetches out the parts of the options to be highlighted.

Below is the method responsible for highlighting

function markMatch(text, term, markup, escapeMarkup) {
	var wordMatch = wordStartMatcher(term, text, true);

	var match = wordMatch ? wordMatch.index : -1;
	var tl = term.length;

	if (match < 0) {
		markup.push(Select2.util.escapeMarkup(text));
		return;
	}

	markup.push(Select2.util.escapeMarkup(text.substring(0, match)));
	markup.push("<span class='select2-match'>");
	markup.push(Select2.util.escapeMarkup(text.substring(match, match + tl)));
	markup.push("</span>");
	markup.push(Select2.util.escapeMarkup(text.substring(match + tl, text.length)));
}

The method fetches out the string part which meets the pattern and wraps it in special tags.

Ok, now is how to apply the custom matcher and highlighing to the Select2 control

$("#magazines").select2({
	matcher: function (term, text) {
		return wordStartMatcher(term, text) != null;
	},
	formatResult: function (result, container, query, escapeMarkup) {
		var markup = [];
		markMatch(this.text(result), query.term, markup, escapeMarkup);
		return markup.join("");
	}
});

Where the “#magazines“-control from the example is specified as

<select id="magazines" name="magazines">
	<option value="">Select a subscription</option>
	<option value="Msdn">MSDN Magazine</option>
	<option value="VS">Visual Studio Magazine</option>
	<option value="Code">CODE Magazine</option>
	<option value="Dobbs">Dr. Dobbs Journal</option>
	<option value="GameDev">Game Developer Magazine</option>
	<option value="LinUsDev">Linux User and Developer</option>
</select>

So, for the above listed options and the pattern defined as “u” the “contains”-matcher would give

  • Visual Studio Magazine
  • Dr. Dobbs Journal
  • Linux User and Developer

while the custom matcher gives what we really need

  • Linux User and Developer

Categories: HTML, JavaScript, Select2 Tags: , ,

ASP.NET MVC: Disable Controller

August 3rd, 2015 Admin No comments

    If you want to disable a MVC Controller, not deleting it, a good way to do that is apply a custom ActionFilter-derived attribute to it. Don’t confuse the ActionFilterAttribute defined in the System.Web.Mvc (the one we’re going to use) with the attribute of the same name residing within the Web API infrastructure, namely in the System.Web.Http.Filters namespace.

So, the custom attribute could be defined like

public class DisableControllerAttribute : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		//filterContext.Result = new HttpNotFoundResult();
		throw new HttpException((int)HttpStatusCode.NotFound, null);
	}
}

Applying it to the target MVC Controller is as simple as follows

...
using System.Web.Mvc;
...

[DisableController]
public class SomeLegacyController : Controller
{
}

Calling any action of such MVC contoller, user gets the 404 Http Status Code page. The page could look differently depending on which way you use to return the Status Code. Using the first line of code in the DisableControllerAttribute.OnActionExecuting (currently commented), the IIS “native” 404 page will be returned.

IIS Native 404 page not found

Throwing the HttpException like shown in the second line of the DisableControllerAttribute.OnActionExecuting (uncommented), you’ll get the ASP.NET handled page.

ASP.Net 404 page not found

Knockout.js Validation: Check if value is in range

June 20th, 2015 Admin No comments

    To validate a model and its properties (observable and ordinary ones) we use such knockout.js plugin as Knockout Validation. This validation library is simple and easy to extend. For example, a new validation rule to check if value is in range could be as simple as the following:

import ko = require("knockout");
import validation = require("knockout.validation");
...
export function enableCustomValidators() {

 validation.rules['inRange'] = {
  validator: function (val, params) {
   var minVal = ko.validation.utils.getValue(params.min);
   var maxVal = ko.validation.utils.getValue(params.max);

   var res = params.includeMin ? val >= minVal : val > minVal;

   if (res)
    res = params.includeMax ? val <= maxVal : val < maxVal;

   if (!res)
    this.message = ko.validation.formatMessage(params.messageTemplate || this.messageTemplate,
       [minVal, maxVal, (params.includeMin ? '[' : '('), (params.includeMax ? ']' : ')') ]);

   return res;
   },
   messageTemplate: 'Value has to be in range {2}{0}, {1}{3}'
 };

 validation.registerExtenders(); // adds validation rules as extenders to ko.extenders
}
...
// the enableCustomValidators should be called when the custom
// validation rules are supposed to be used

Note the code above and further is TypeScript. The TypeScript definition to the Knockout Validation is available as a NuGet package, namely knockout.validation.TypeScript.DefinitelyTyped.

The inRange rule accepts the start and end values and allows to indicate should or should not they be included in the range (by default boundaries are excluded). The validation error message is being generated based on the custom defined messageTemplate or default one. The default message template uses four placeholders where the {0} and {1} are start and end values correspondingly. The latter two represent brackets: square or round ones depending on the boundaries inclusion.

The example below defines inRange validation to keep the value between 0 (including it) and 10 (excluding it):

public someNumber = ko.observable<number>(null).extend(
{
	required: { params: true, message: "This field is required" },
	number:   { params: true, message: "Must be a number" },

	inRange:  {
		params: {
			min: 0,
			includeMin: true,
			max: 10,
			//includeMax: true,
		}
	}
});

In case of a wrong value the message will says “Value has to be in range [0, 10)".

To redefine the message template the following might be used:

public somePositiveNumber = ko.observable<number>(null).extend(
{
 required: { params: true, message: "This field is required" },
 number:   { params: true, message: "Must be a number" },

 inRange:  {
  params: {
   min: 0,
   max: 1,
   messageTemplate: "Must be a positive number less than {1}"// the range end value will be put
  }
 }
});

Note the ko.validation.formatMessage has been used to generate the final validation message. Till recently the ko.validation.formatMessage wasn't able to process more than one placeholder in template. So, if you use one of such outdated versions of Knockout Validation library, you may be interested in the actual formatting method implementation shown below. It's borrowed from the same Knockout Validation, but of up-to-date version.

function formatMessage(message, params, observable?) {
    if (ko.validation.utils.isObject(params) && params.typeAttr) {
        params = params.value;
    }
    if (typeof message === 'function') {
        return message(params, observable);
    }
    var replacements = ko.utils.unwrapObservable(params);
    if (replacements == null) {
        replacements = [];
    }
    if (!ko.validation.utils.isArray(replacements)) {
        replacements = [replacements];
    }
    return message.replace(/{(\d+)}/gi, function (match, index) {
        if (typeof replacements[index] !== 'undefined') {
            return replacements[index];
        }
        return match;
    });
}

Select2: jqModal + Select2 = can’t type in

June 10th, 2015 Admin No comments

    The Select2 control placed in the jqModal modal dialog doesn’t allow typing anything in. It’s unexpected, but explainable: jqModal eliminates all keypress and keydown events coming from without, i.e. coming from the controls not residing in the jqModal container. As I mentioned in the Select2: Make dropdown vertically wider, the Select2, in turn, dynamically appends the elements making up the dropdown (including the type-ahead search box) to the Body-tag, so they are literally out of jqModal dialog.

Select2 in jqModal - No Typing in

Where the contracted jqModal markup resembles the following:

<div id="myModalDialog" class="popup-6"...>
    <div class="pp6-header">
        <h3>Some Fancy Modal Dialog</h3>
        <span class="close" ...></span>
    </div>
    <div class="pp6-body" ...>

        <select id="mySelect2" ...>
			<option value="">Select an option</option>
			<option value="Product">Product</option>
			<option value="Producer">Producer</option>
			...
		</select>

    </div>
    <div class="pp6-footer">
        <a href="javascript: void(0)" class="ok" ...>OK</a>
        <a href="javascript: void(0)" class="close" ...>Cancel</a>
    </div>
</div>

The Select2 and modal dialog are created as follows:

	$("#mySelect2").select2();
	...
	$("#myModalDialog").jqm({
            modal: true,
            trigger: false,
            onHide: function (h) {
                // hash object;
                //  w: (jQuery object) The modal element
                //  c: (object) The modal's options object
                //  o: (jQuery object) The overlay element
                //  t: (DOM object) The triggering element
                h.o.remove(); // remove overlay
                h.w.hide(); // hide window
            },
            onShow: function (hash) {
                hash.o.addClass("modalBackground").prependTo("body"); // add overlay

				// getNextDialogZIndex is my custom function,
				// which increments z-index counter to place each new dialog atop the preceding one
                if (typeof getNextDialogZIndex === "function") {
                    var zIndex = getNextDialogZIndex();
                    hash.o.css("z-index", zIndex);
                    hash.w.css("z-index", zIndex + 1);
                }
                hash.w.css("left", 0);
                hash.w.css("right", 0);
                hash.w.css("position", "fixed");
                hash.w.css("margin-left", "auto");
                hash.w.css("margin-right", "auto");
                hash.w.fadeIn();//show();
            }
        });
	...
	$("#myModalDialog").jqmShow();

Below I quote the code from jqModal.js where the keypress, keydown, mousedown events are being handled, and their bubbling is being interrupted

F = function(t){
	// F: The Keep Focus Function (for modal: true dialos)
	// Binds or Unbinds (t) the Focus Examination Function (X) to keypresses and clicks

	$(document)[t]("keypress keydown mousedown",X);		

}, X = function(e){
	// X: The Focus Examination Function (for modal: true dialogs)

	var modal = $(e.target).data('jqm') || $(e.target).parents('.jqm-init:first').data('jqm'),
		activeModal = A[A.length-1].data('jqm');

	// allow bubbling if event target is within active modal dialog
	if(modal && modal.ID == activeModal.ID) return true; 

	// else, trigger focusFunc (focus on first input element and halt bubbling)
	return $.jqm.focusFunc(activeModal);
}

The code examines if the event target control or its first found parent marked with the ‘jqm-init‘ class have the associated datajqm‘. If so, the event will be allowed to pass through, otherwise it will be ignored.

Based on that, we can apply a workaround to Select2 control to unlock the type-ahead search box for typing in. The solution implies to associate the proper data named ‘jqm‘ with the Select2‘s dropdown. To catch the right dropdown (we don’t want to affect other Select2 controls on the page), a unique css class has to be applied to it. Plus the dropdown has to have the ‘jqm-init‘ class to be considered and analyzed by the jqModal code (note the following fragment in the listing above:)

$(e.target).parents('.jqm-init:first')

So, the solution should look like

// create Select2 and apply proper css classes to its dropdown
$("#mySelect2").select2({ dropdownCssClass: "someUniqueCssClass jqm-init" });

// capture the data associated with the jqModal container
var d = $("#myModalDialog").data("jqm");

// associate the captured data with the dropdown
// to mimic the residence within the jqModal dialog
$(".someUniqueCssClass.jqm-init").data('jqm', d);

After that every keypress, keydown or mousedown event won’t be suppressed, but will be properly handled.

Select2: Make dropdown vertically wider

June 1st, 2015 Admin No comments

    The Select2 is a quite useful and fancy replacement for ordinary Html Selects. Having a fixed number of items available to pick, you may want to make the dropdown of a Select2 control vertically wider to avoid extra scrolling.

Before - Select2 With Scrolling

Where the Select2 control is defined as follows

<select name="criteria" id="criteria">
	<option value="">Select a Search Criteria</option>
	<option value="Product">Product</option>
	<option value="Producer">Producer</option>
	<option value="Store">Store</option>
	<option value="State">State</option>
	<option value="Country">Country</option>
	<option value="Continent">Continent</option>
	<option value="Planet">Planet</option>
	<option value="Galaxy">Galaxy</option>
	<option value="Universe">Universe</option>
	<option value="Reality">Reality</option>
</select>
	$("#criteria").select2({
		minimumResultsForSearch: -1 // allows to hide the type-ahead search box,
		                            // so the Select2 acts as a regular dropdown list
	});

Whenever the Select2 has been clicked, the dropdown-elements will be dynamically added to the DOM at the end of the body-tag and will pop up. To make the dropdown wider we need to apply the proper ccs style to it. Of course, we can change the default css styles accompanying the Select2 control, but there is a way to apply desired changes to a certain control, not affecting other Select2 controls on the page. The custom css class can be attached to the target dropdown by the following call:

	$("#criteria").select2({
		minimumResultsForSearch: -1,
		dropdownCssClass: "verticallyWider" // the dropdownCssClass option is intended
                                            // to specify a custom dropdown css class
	});

The Html markup dynamically added to the DOM when clicking the Select2 resembles the following (note the verticallyWider class, which among others has been applied to the outer div-tag):

<div class="select2-drop-mask" id="select2-drop-mask"></div>

<div class="select2-drop select2-display-none verticallyWider select2-drop-active"
	id="select2-drop"
	style="left: 828.49px; top: 112.61px; width: 300px; bottom: auto; display: block;">
	<div class="select2-search select2-search-hidden select2-offscreen">
		<label class="select2-offscreen" for="s2id_autogen2_search"></label>
		<input class="select2-input" id="s2id_autogen2_search" role="combobox"
			aria-expanded="true" aria-activedescendant="select2-result-label-8"
			aria-owns="select2-results-2" spellcheck="false" aria-autocomplete="list"
			type="text" placeholder="" autocapitalize="off"
			autocorrect="off" autocomplete="off" />
	</div>
   <ul class="select2-results" id="select2-results-2" role="listbox">
      <li class="select2-results-dept-0 select2-result select2-result-selectable"
		role="presentation">
         <div class="select2-result-label" id="select2-result-label-3" role="option">
			<span class="select2-match"></span>Select a Search Criteria
		 </div>
      </li>
      <li class="select2-results-dept-0 select2-result select2-result-selectable"
		role="presentation">
         <div class="select2-result-label" id="select2-result-label-4" role="option">
			<span class="select2-match"></span>Product
		 </div>
      </li>
      <li class="select2-results-dept-0 select2-result select2-result-selectable"
		role="presentation">
         <div class="select2-result-label" id="select2-result-label-5" role="option">
			<span class="select2-match"></span>Producer
		 </div>
      </li>
      ...
      <li class="select2-results-dept-0 select2-result select2-result-selectable"
		role="presentation">
         <div class="select2-result-label" id="select2-result-label-13" role="option">
			<span class="select2-match"></span>Reality
		 </div>
      </li>
   </ul>
</div>

The verticallyWider class, in turn, is defined as the following (so, specify the desired min/max heights in the styles):

.verticallyWider.select2-container .select2-results {
    max-height: 400px;
}
.verticallyWider .select2-results {
    max-height: 400px;
}
.verticallyWider .select2-choices {
    min-height: 150px; max-height: 400px; overflow-y: auto;
}

Below is the result of the modifications

After - Select2 With No Scrolling - Vertically Wider