Subscribe

Close

Thank you for visiting!

Please consider subscribing to the RSS feed or following me on Twitter.

Archive for JavaScript:

Angular Delay with Debounce

Recently we were trying out a few things to delay validation of form fields in our Angular application. The not so good solution, was this:

<input type="text" ng-keyup="validate()" />

As soon as a keyboard key was released, the validate method was triggered. A little bit too often.

So we wanted something that created a delay in our validation. You could add a timeout handler in the validate function, but it wouldn't help, as the validate method would be called numerous times anyway, but with a delay.

In steps ng-change and ng-model-options with debounce. This is Angular 1.5.8. The new code that works really well:

<input type="text" ng-change="validate()" ng-model-options='{ debounce: 1000 }' />

This creates a delay of 1000 ms before the validate method is triggered.

Angular 2 version

Use RxJs Observables and the debounceTime() operator. Here's a good Stack Overflow thread on Angular 2 and debounceTime. In short, the pseudo code looks like this:

this.term.valueChanges
             .debounceTime(400)
             .subscribe(term => this.wikipediaService.search(term).then(items => this.items = items));

Further reading here.

Ajax File Download That Works!

So you want to download a file and show an Ajax loader until the download is complete? Here's a way to do it:

  1. User clicks the download button and a JavaScript function is called.  In this JS function, a number of things will happen:
    • Show an AJAX loader.
    • Create a cookie.
    • Run a check cookie function.
    • Redirect to the file download (in this case an MVC method that returns the file).
  2. If successful, the MVC method removes the cookie and returns the file. When the cookie is removed, disable the Ajax loader (happens in check cookie function).
  3. Done.

Step 1 - JS call

User clicks our download button and we call this export function. Note: just before we create the timer and cookieName variables.

var timer = null;
var cookieName = 'App_FileExportCookie';
	
var export = function () {
    $('#ajax-loader').show();

    var cookie = $.cookie(cookieName);

    if (cookie === undefined) {
        cookie = $.cookie(cookieName, 0);
    }
    checkCookie();

    window.location = '/FileExport';
}

We show the Ajax loader and then check if a cookie exists (using jQuery Cookie plugin). Finally we redirect to the URL where the actual file generation and download will happen.

Step 2 - Check cookie

In step 1, we called the checkCookie function. This one is really important as it will check and see the status of the file download, through checking our cookie.

var checkCookie = function (doClearTimeout) {

    if (doClearTimeout) {
        clearTimeout(timer);
    }
    else {
        timer = setTimeout(function () {

            var cookie = $.cookie(cookieName);

            if (cookie === undefined) {
                $('#ajax-loader').hide();
	        checkCookie(true);
	    } else {
            checkCookie(false);
	    }
	}, 1500);
    }
}

What will happen here, is we set a timeout where we look for our cookie. We call this function until we can't find the cookie we created in step 1 any more. When the cookie doesn't exist, we hide the AJAX loader and clear the timeout.

Finally, we need a place to remove the cookie. Enter step 3.

Step 3 - file generation and remove cookie

In step 1, we redirected the user to the /FileExport url. In ASP.NET MVC, we will have an ActionResult with that name and here we will generate and return the file, plus remove the cookie we created in the beginning. The final code:

private string _fileCookieName = "App_FileExportCookie";
private const string _excelMimeType =
 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

public FileResult FileExport()
{
    ExcelFile excel;
	
    try {
        excel = GenerateExcel();
    }
    catch (Exception ex)
    {
        // handle exceptions
    }
    finally
    {
	// same as in JavaScript cookie name
	var cookieName = _fileCookieName;
	var cookie = Request.Cookies[_fileCookieName];

	if (cookie != null)
	{
    	    cookie.Expires = DateTime.Now.AddDays(-1);
	    Response.SetCookie(cookie);
        }
    }
	
    return File(excel.FilePath, _excelMimeType);
}

This is just some pseudo code for generating an Excel file, the important stuff happens in the finally block. We look for our cookie and remove it once the file is generated. Finally, we return the file in proper Excel format.

That's it!

IE Edge, Angular and Highlighting a Text Field

Recently, I stumbled upon a problem using the Angular Material framework. While using the md-autocomplete component, I wanted to highlight a text field when the user clicked said text field. Easier said than done.

I started writing a directive that highlights a text field when it's focused. Of course I went for the focus event. When the focus event is triggered, it highlights the text field. It worked everywhere. Except in IE Edge.

After some debugging, I simply switched the focus event for the click event, and it started working everywhere.

Here's the directive:

app.directive('autocompleteFocus', function($timeout, $log){
  return{
    link: function(scope, element, attrs){
       $timeout(selectOnFocus, 0);

       function selectOnFocus(){
           element.find("#my-field").bind("click", function () {
               angular.element(this).select();
           });
       }
    }
 }
});

Use it like this:

<md-autocomplete ... autocomplete-focus />

Check if Bootstrap CSS is Loaded and Provide Local Fallback

When a CDN somehow fails, you want to make sure you can load a local fallback.

Here's a snippet to check if Bootstrap CDN is down, and then proceed to load a local Boostrap fallback version, utilizing Boostrap's hidden CSS class.

If Bootstrap is loaded, the span will be hidden. We simply check if it is hidden, and if it aint - we assume Bootstrap wasn't loaded from CDN and load our local fallback instead.

Run this script after you've tried to load Boostrap from a CDN.

var $span = $('<span class="bs hidden"></span>');
var $bs = $('body').append($span).find('.bs');

if ($bs.css('display') !== 'none') {
	$("head").append('<link rel="stylesheet" href="/css/Bootstrap/bootstrap.min.css">');
}
$bs.remove();

Other, more sofisticated methods, depend on libraries like yepnope or requirejs. Meanwhile, this method works just fine.

Foolproof Custom File Input

So you wan't a custom file upload button.

It's easy. Just hide it, add a button, a couple of click events and you're fine. Except, you're not. IE is quite strict when it comes to file inputs. Browser backward compatibility issues will make life harder for you.

Here's a working solution tested in IE9 and later, and modern browsers.

  1. Place your input file field in a hidden div.
  2. Wrap a label around the hidden div.
  3. Make the label look like a button.
  4. When the label is clicked, trigger the hidden input field.
  5. When a file is chosen, use the change event to display the file name.
The HTML:

<form action="http://localhost/upload" method="post" enctype="multipart/form-data">
<label class="button upload">Upload file
<div class="hidden">
<input class="uploadfile" type="file" />
</div>
</label>
<input type="submit" class="button" value="Submit">
<span></span>
<form>
The JavaScript:

$('.button.upload').click(function () {
var uploadFile = $(this).find('.uploadfile');
uploadFile.trigger('click');
});

$('.uploadfile').change(function() {
var elm = $(this);
var file = elm.val().split('\\');
var fileName = file[file.length - 1];
$(this).closest('form').find('span').html(fileName);
});

And the basic CSS looks like this:

.hidden { display: none }
.button { border: solid 2px #333 }

Find Attached Events with jQuery

For later versions of jQuery, you can use this code snippet to find attached events on specific elements:

$._data($("input")[0]).events

As you can see, jQuery events are stored in a data object called events. This is an undocumented jQuery feature, but it works!

AJAX Get and Cache

Recently I fetched a MVC view with AJAX GET ($.get()) and it worked most of the time in most browsers. However at times I got a cached version of the view.

How to solve this? Easy. Just switch to $.ajax and add cache: false to your AJAX request:

$.ajax({
	url: '/shop/cart',
	cache: false,
	success: function (data) {
		$('#cart').html(data);
	}
});

AJAX calls are by standard GET so there's no need to specify it. Above is enough.

Automatic Iframe Height The Right Way

Unfortunately there still are times when you need to embed an iframe. Most of the time you would want to assign an automatic height to the iframe. It's not very easy, but it's definitely doable if you meet a couple of requirements.

Requirements needed for auto iframe height

The page calling the iframe and the iframed page should have the:

  • Same host
  • Same protocol
  • Same port

This means:

  • a page at www.domain1.com can't call a page on www.domain2.com
  • a page at domain1.com can call a page on domain1.com or on a sub domain, xx.domain1.com

For this to work, you also need to add this code snippet (in <script>) to both pages, preferably in the <head> section:

document.domain = /(\w+)(.\w+)?$/.exec(location.hostname)[0];

This code snippet tells the browser the default domain (host), which need to be the same according to the requirements above.

Common error code

This is the error you get in Chrome if you make an illegal iframe call:

Unsafe JavaScript attempt to access frame with URL http://domain1.com
from frame with URL http://otherdomain.com.
Domains, protocols and ports must match.

Now you know the solution.

Auto iframe script

Here's a working auto iframe height script you can use.

It's even non jQuery (even though it says it's jQuery).

Solution To window.open Not Working In IE

If you use window.open top open custom windows with JavaScript, you might encounter problems in Internet Explorer. Even though you can open new windows in other browsers, it doesn't work in IE.

The reason to this is it depends on how you name your JavaScript windows. IE doesn't allow much space here.

window.open takes three parameters - window url, window name and window properties. Make sure the window name doesn't contain other characters than a-z, A-Z and 0-9. No spaces, no hyphens, no aposthropes.

Here's a working example:

window.open('http://www.manutd.com/','ManUtdOfficialWebsite',
    'left=0,top=0,width=1024,height=800,channelmode=no,
    titlebar=yes,scrollbars=yes,toolbar=no,menubar=no,
    resizable=yes,copyhistory=no,directories=no,status=yes');

Adding tinyMCE Editor Dynamically And Get Its Value

tinyMCE is a great WYSIWYG editor which is really easy to implement.

However, if you want to dynamically create form fields and use tinyMCE with them, then it's not really as straightforward - but it's quite easy anyway. Here's how you do it.

First of all, you need to use tinyMCE.execCommand() to add the tinyMCE editor dynamically, like this:

tinyMCE.execCommand('mceAddControl',false,'textarea_id');

Where textarea_id is the id of your textarea. You'd also want to focus the textarea for better usability:

tinyMCE.get('textarea_id').focus();

However, this is not it! Your modified content must go into the the original textarea, thus we need to execute another tinyMCE command to do so:

tinyMCE.triggerSave();

Finally, if you need to access the contents of your tinyMCE area when the form's submitted, you must use another built-in tinyMCE function:

var content = tinyMCE.get('textarea_id').getContent();

Easy, huh? Now, this is how you add a tinyMCE editor dynamically to a specific textarea, and store the value for later use.

Here's more reading:

How To Easily Format Dates With JavaScript

I recently wanted to format a date with JavaScript and found it quite hard. There are no really good built in functions for date formatting in JavaScript.

Enter Steven Levithan's JavaScript Date Format.

Here's an example on how to format a date that looks like this: 2011-02-16 | 14:58

var myDate = new Date().format('yyyy-mm-dd | HH:MM');

Really smooth and easy.

You can find a lot of custom date format examples + a reference in Steven's blog post.

To the top