Archive

Posts Tagged ‘JavaScript’

Select2: jqModal + Select2 = can’t type in

June 10th, 2015 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 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

Dynamics CRM: How to add a map to an Entity form

November 13th, 2013 No comments

    Let’s say we have an Entity with Latitude and Longitude attributes and we want to display a pushpin at this point on a map when opening the Entity‘s form. Briefly, I offer to add a section to the form (the section is a placeholder for the map) and bind the form’s OnLoad event to a custom JavaScript, which finds the section, adds Bing Map Control to it, gets Latitude and Longitude attributes of a current record and displays the appropriate point on the map.

1. Add JavaScript to Web Resources

Let’s assume we have the custom unmanaged solution named Test Solution that provides with the Bus Stop Entity containing Latitude and Longitude attributes. Open the solution and create a JavaScript (JScript) Web Resource with the code listed below. Read the comments within the code as they explain what happens inside.

//If the dotNetFollower namespace object is not defined, create it
if (typeof (dotNetFollower) == "undefined") {
    dotNetFollower = {};
}

dotNetFollower.BingMapLib = (function () {
    var self = this; // save reference to itself
	
	self.bingKey = "put here your key";
    self.map     = null; // map control   
    self.mapDiv  = null; // div, a placeholder for Map Control   

	// dynamically adds a script to the page
    self.addScript = function(url, callback) {
		var script   = document.createElement('script');
		script.src   = url;
		script.type  = 'text/javascript';
		script.defer = false;
	 
		if (typeof callback != "undefined" && callback != null) {
	 
			// IE only, connect to event, which fires when JavaScript is loaded
			script.onreadystatechange = function() {
				if (this.readyState == 'complete' || this.readyState == 'loaded') {
					this.onreadystatechange = this.onload = null; // prevent duplicate calls
					callback();
				}
			}
	 
			// FireFox and others, connect to event, which fires when JavaScript is loaded
			script.onload = function() {
				this.onreadystatechange = this.onload = null; // prevent duplicate calls
				callback();
			};
		}
	 
		var head = document.getElementsByTagName('head').item(0);
		head.appendChild(script);
	};
	
	// checks if a passed string is Null or Empty
	self.isNullOrEmpty = function (str) {
        return !str || $.trim(str) === ""; // the trim method is provided by jQuery
    };
	
	// checks if Microsoft.Maps.Map is available 
	self.scriptsReady = function () {
        return typeof (Microsoft)          != 'undefined' &&
        	   typeof (Microsoft.Maps)     != 'undefined' && 
        	   typeof (Microsoft.Maps.Map) != 'undefined' && 
        	   typeof (Xrm)                != 'undefined';
    };
	
	// adds a div to the section within the form, the div is supposed to host a Bing Map Control
	self.prepareMapPlaceholder = function() {
		var mapWidth  = '100%';
		var mapHeight = '400px';
		
		// get the section by label Map
		var section = $('table[label="Map"]');
		
		// if section isn't found, that could mean that we deal with 
        // the CRM 2013. So, let's try to find the section using another selector
        if(section.length == 0)
        	section = $(":header:contains('Map')").parents("table");
		
        var td = section.find('td[colspan="2"]:last');		
		
		// add a div
		td.html('<div id="mapPlaceholder" style="width:' + mapWidth + 
                '; height: ' + mapHeight + ';" />');
		self.mapDiv = td.find('#mapPlaceholder')[0];
	};
    
    // creates a Bing Map Control hosted in the passed args.div
    self.loadMap = function (args) {
    	// check if Microsoft.Maps.Map is available 
        if (self.scriptsReady()) {
        
            if (self.map == null) {
                var div = $(args.div);
                
                // create a Bing Map Control
                self.map = new Microsoft.Maps.Map(args.div, {
                    credentials: self.bingKey,
                    showDashboard: false,
                    showMapTypeSelector: false,
                    showScalebar: false,
                    mapTypeId: Microsoft.Maps.MapTypeId.auto,
                    zoom: args.zoom,
                    width: div.parent().width(),
                    height: div.height(),
                    center: new Microsoft.Maps.Location(args.lat, args.lon),
                    enableSearchLogo: false,
                    enableClickableLogo: false
                });
                
                // hook up to resize event to adjust the map's size in time
                $(window).resize(function () {
                    self.map.setOptions({ width: div.parent().width(), height: div.height() });                    
                });                
            }
            
            // call the callback
            args.callback();
        }
        else
        	// in spite of calling the loadMap function right away after 
        	// the Bing Map script is loaded, 
        	// the Microsoft.Maps.Map still may be undefined, 
        	// so wait for a while in this case
            setTimeout(self.loadMap, 1000, args);
    };
    
    // displays a pushpin on the map
    self.showBusStop = function() {
		
	  self.prepareMapPlaceholder();
		
	  // dynamically add the Bing Map script
	  self.addScript("https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1", 
       function() {
		// this code is executed once the Bing Map script has been loaded
		
		// get lat/lon of the current record
		var lat  = Xrm.Page.getAttribute("testpub_latitude").getValue();
		var lon  = Xrm.Page.getAttribute("testpub_longitude").getValue();        
        var zoom = 15;
        
        // if lat or/and lon are null or empty, 
        // use the default location namely the center of the USA
        if(self.isNullOrEmpty(lat) || self.isNullOrEmpty(lon)) {
        	lat  = 39.8282;
        	lon  = -98.5795;
        	zoom = 1;
        }
        
        // create Map Control hosted in the self.mapDiv
		self.loadMap({lat: lat, lon: lon, zoom: zoom, div: self.mapDiv, callback: function() {
			
			// this code is executed once the Microsoft.Maps.Map gets defined
			
			// create a pushpin
			var location = new Microsoft.Maps.Location(lat, lon);
         	var pushpin  = new Microsoft.Maps.Pushpin(location);
            	
         	// add the pushpin to the map
            self.map.entities.push(pushpin);
		}});
	  });
	};
    
    return self;
})();

The showBusStop function is an entry point that will be called on the Entity form’s OnLoad event (I’ll show later how to bind the event to the showBusStop). The showBusStop prepares the section for holding the map (how to add the section to the form will be described later as well) and dynamically includes the Bing Map script into the page by means of the addScript function described in details here – How to add a script-tag dynamically. Once the Bing Map script has been loaded, we get the Latitude and Longitude and initiate Map Control creation by calling the loadMap function. Note that the Bing Map script dynamically includes other scripts, and one of those is responsible for Microsoft.Maps.Map definition. So, the loadMap waits until the Microsoft.Maps.Map gets available by recalling itself through the setTimeout. Once the Map Control has been created, we create a pushpin and add it to the map.

The Map Control will adjust its width to fill the available space whenever the window’s size is changed.

The above script largely uses jQuery, so the jQuery has to be added to Web Resources as well. In my case the Web Resources have been named testpub_insertMap and testpub_jQuery respectively:

Crm Web Resources

2. Add a section to Entity form.

Open the Bus Stop Entity‘s main form for editing and add a One Column section, for example, to the General tab. Open the section’s properties (double click on the section) and set the Label property to Map (the word “Map” is used by the testpub_insertMap script to locate the section in the DOM tree). The picture below illustrates these steps:

Add Section To Entity Form

3. Bind Entity Form’s OnLoad event to the custom JavaScript function

Keep the Bus Stop Entity‘s main form open for editing and click on Form Properties. There first of all we need to make the custom scripts available on the form. Technically it means that our JavaScript Web Resources will be dynamically included into the page being loaded. So, in the Form Libraries section (at the Event tab), add testpub_jQuery and testpub_insertMap to the list. In our case, the order of the scripts is important: the testpub_jQuery must come first, i.e. be above the testpub_insertMap.

The second step is to specify what function from one of the added JavaScript Web Resources should be called on form’s OnLoad event. In the Event Handlers section, add a new event handler by choosing the testpub_insertMap as a Library and setting the dotNetFollower.BingMapLib.showBusStop as a Function. Note that no parameters are required to be passed into the dotNetFollower.BingMapLib.showBusStop. After these two steps you should have something like this:

Add Scripts To Entity Form OnLoad

If you have done everything right, the result view of the Bus Stop Entity‘s form should look like the following:

Map On Entity Form

The sample unmanaged solution you can download here – testsolution_1_0.zip

Knockout.js: HtmlNoScript binding

February 14th, 2013 No comments

    The Knockout.js is one of the most popular and fast-developing libraries that bring the MVVM pattern into the world of JavaScript development. Knockout.js allows to declaratively bind UI (Document Object Model elements) to an underlying data model. One of the built-in bindings is the html-binding allowing to display an arbitrary Html provided by an associated property/field of the data model. The typical use of this binding may look like this:

<div data-bind="html: comment" />

where the comment property may be specified as shown in the following data model:

...
// include the knockout library
<script src="js/knockout/knockout-2.2.0.debug.js" type="text/javascript"></script>
...
$(function () {
	// define a data model
	var viewModel = function () {
        var self = this;		
		self.comment = ko.observable("some html comment <span>Hello<script type='text/javascript'>alert('Hello!');<\/script></span>");
		...
	}
	...
	// create instance of the data model
	var viewModelInstance = new viewModel();
	// bind the instance to UI (since that moment all of the declarative bindings have worked off and the data are displayed)
	ko.applyBindings(viewModelInstance);
});

Applying a value to a DOM element, the html-binding exploits the jQuery‘s html() method, of course, if jQuery is available on the page; otherwise knockout.js relies on its own logic that ends up with call of the DOM‘s appendChild function. In both cases if the html being applied contains inclusions of JavaScripts, the scripts execute once the html has been added to the DOM. Preventing all unwanted scripts from running is the best practice when displaying random htmls on the page. Using the approach described in my post How to prevent execution of JavaScript within a html being added to the DOM we are able to develop our own knockout binding disabling the scripts. It could look like the following:

 ko.bindingHandlers.htmlNoScripts = {
    init: function () {
        // Prevent binding on the dynamically-injected HTML
        return { 'controlsDescendantBindings': true };
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        // First get the latest data that we're bound to
        var value = valueAccessor();
        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.utils.unwrapObservable(value);
		// disable scripts
        var disarmedHtml = valueUnwrapped.replace(/<script(?=(\s|>))/i, '<script type="text/xml" ');
        ko.utils.setHtml(element, disarmedHtml);
    }
};
 

An alternative way to prevent scripts from running is to use the innerHTML property of a DOM element as follows:

 var html = "<script type='text/javascript'>alert('Hello!');<\/script>";
 var newSpan = document.createElement('span');
 newSpan.innerHTML = str;
 document.getElementById('content').appendChild(newSpan);
 

Some people, however, report that this doesn’t work in Internet Explorer 7 for unknown reasons. So, let’s combine these two methods in one to minimize the risk of unwanted scripts running. My tests show the combined approach successfully prevent JavaScript from being executed in all popular browsers. Even in the worst case, at least one of the methods works off. The resultant knockout binding may look like the following:

ko.bindingHandlers.htmlNoScripts = {
    init: function () {
        // Prevent binding on the dynamically-injected HTML
        return { 'controlsDescendantBindings': true };
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        // First get the latest data that we're bound to
        var value = valueAccessor();
        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.utils.unwrapObservable(value);
        // disable scripts
        var disarmedHtml = valueUnwrapped.replace(/<script(?=(\s|>))/i, '<script type="text/xml" ');        
        // create a wrapping element
        var newSpan = document.createElement('span');
        // safely set internal html of the wrapping element
        newSpan.innerHTML = disarmedHtml;
        // clear the associated node from the previous content
        ko.utils.emptyDomNode(element);
        // add the sanitized html to the DOM
        element.appendChild(newSpan);
    }
};

This htmlNoScripts binding can be used as follows:

<div data-bind="htmlNoScripts: comment" />

Related posts:

JavaScript: How to prevent execution of JavaScript within a html being added to the DOM

February 5th, 2013 No comments

    Getting pieces of html from an external web service, I display them “as is” on a page. To insert the htmls into the document I use handy jQuery methods: append() and html(). The only problem is the htmls contain inclusions of JavaScript that is executed whenever I add the htmls to the DOM (Document Object Model). Having looked for an acceptable solution to prevent untrusted scripts from running I found out that almost all solutions come to removing script-tags from html using the Regular Expressions. I tried a couple of regexes, and they really worked. However, every time I could find such a combination of script-tag, JavaScript inside it and wrapping html when the regexes either didn’t recognize a script block or cut out more than it was needed. And I don’t even mention that parsing an arbitrary HTML with the Regular Expressions is a bad idea in all respects :). Trying to find another solution, I recalled that if we replace the type=”text/javascript” of a script-tag with the type=”text/xml”, the JavaScript inside will not execute. This is quite known fact, and a few JavaScript libraries use this behavior for their purposes. However, two things impeded me to implement the direct replacing of one type with another: the type=”text/javascript” sub-string may encounter outside the <script>, somewhere in the content (like in this article 🙂 ); the script-tag may be without the type attribute at all, and, in this case, the code inside will be run as if the type=”text/javascript” is specified. Taking into account these conditions, I developed a few tricky regexes, that seemed to be fairly complicated and not reliable enough though. After all, this led me to a thought to examine what happens if the script-tag has two type attributes. Something like this:

<script type="text/xml" type="text/javascript">
    alert('Hello!');
</script>

All browsers I tested this in didn’t execute JavaScript. On the contrary, if I change the type attributes over, the script is executed. Only the first type attribute seems to be efficient. So, my resultant solution is based on the following assumptions:

  • JavaScript inside the <script type=”text/xml”> doesn’t execute;
  • JavaScript inside the <script type=”text/xml” type=”text/javascript”> doesn’t execute as well because only the first type is considered;

So, below is a very simple JavaScript function, which prevents scripts from running and seems to avoid most of drawbacks:

function preventJS(html) {
    return html.replace(/<script(?=(\s|>))/i, '<script type="text/xml" ');
}

The solution still relies on the Regular Expressions, but the regex itself is quite simple and unambiguous. The script-tags are preserved in the html, so you can treat them later in a manner you want.

Below is an example of use

<html>
  <head>
    <title>Prevent scripts from running</title>
    <script src="jquery-1.8.3.js" type="text/javascript"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/javascript">
      $(function () {
        var html1 = "<span>Hello 1<script type='text/javascript'>alert('Hello 1!');<\/script></span>"
        var html2 = "<span>Hello 2 &lt;script&gt; alert('Hi') &lt;/script&gt; <script   type='text/javascript'>alert('Hello 2!');<\/script></span>";
        var html3 = "<script src=\"someJs.js\" type='text/javascript'><\/script>";
        var html4 = "<script>alert('Hello 4!');<\/script>";
        var html5 = "<scriptsomeAttr >alert('Hello 5!');";

        $("#content").html(preventJS(html1));
        $("#content").append(preventJS(html2));
        $("#content").append(preventJS(html3));
        $("#content").append(preventJS(html4));
        $("#content").append(preventJS(html5));
      });

      function preventJS(html) {
        return html.replace(/<script(?=(\s|>))/i, '<script type="text/xml" ');
      }
    </script>        
  </body>
</html>

I tested this solution in Google Chrome v. 24.0.1312.56, FireFox v. 18.0.1 and Internet Explorer 9 v.9.0.8112.16421, and it works as directed. However, I still have doubts whether it’s applicable for all browsers and their versions. So, if you have tested it, please don’t hesitate to post a comment here with the result, browser’s name and version you use.

For test purposes you can download a html page and a couple of js-files here. If the preventing works correctly, having opened the page in a browser, you shouldn’t get any alerts.

Related posts:
Categories: JavaScript, jQuery, Regex Tags: , ,