Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

The Problem

We had a service written in Java that retrieved a number from the database and returned it through a RESTful service to a Javascript client. The number was a long because of it’s large size. This had worked for a very long time. However, we noticed that when we had some very large numbers, the number started to be off by one. Specifically,

9164083756384584
9597642508289680

Those values were displayed as

9164083756384585
9597642508289681

In Java, we are used to overflow behavior being represented by a number turning negative because the signed bit is being used to represent data.

The Findings

We were able to verify that when we called the service with Postman, that we did see the correct value. The problem occurred when the Javascript client tried to parse the data. There was a specific line where the value was taken from the json to string marshalled object and it was converted to a string. This is where the problem was. After much searching and consulting with our Javascript experts, we ended up with ECMA 8.5 The Number Type. What an eye opener. This confirmed to us that the largest number that we could expect to be retrieved from our Java RESTful services was 2^53-1, not 2^63-1.

We had no idea that 9007199254740991 was the largest value that we could expect to be represented accurately in Javascript.

The Solution

Our solution was to add a property to the object that we were returning for marshalling by our Java RESTful services. We updated the service to have an additional property that was a String. Meaning that we would transition to return a String as our record IDs, instead of a long. This was fine, since arithmetic operations shouldn’t be performed on our object IDs anyway.

More Findings

We also found supporting information at

  • http://stackoverflow.com/questions/307179/what-is-javascripts-highest-integer-value-that-a-number-can-go-to-without-losin
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE

October 4th, 2016

Posted In: ECMA, ECMAscript, Java, java ninja, Javaninja, javascript

Leave a Comment

I was going through some of the code and noticed how much Java code was being taken up to write normal equals(), hashCode() and toString() methods. There has to be a better way. Here is what I came up with.

This example was intended for an entity, but it doesn’t matter.

package com.cdi.igs.vip.dao;


import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

/**
 * Base class for data access objects.
 * @author norris.shelton
 */
public class BaseEntity {

    /**
     * Indicates whether some other object is "equal to" this one.
     * <p>
     * The {@code equals} method implements an equivalence relation on non-null object references: <ul> <li>It is
     * <i>reflexive</i>: for any non-null reference value {@code x}, {@code x.equals(x)} should return {@code true}.
     * <li>It is <i>symmetric</i>: for any non-null reference values {@code x} and {@code y}, {@code x.equals(y)} should
     * return {@code true} if and only if {@code y.equals(x)} returns {@code true}. <li>It is <i>transitive</i>: for any
     * non-null reference values {@code x}, {@code y}, and {@code z}, if {@code x.equals(y)} returns {@code true} and
     * {@code y.equals(z)} returns {@code true}, then {@code x.equals(z)} should return {@code true}. <li>It is
     * <i>consistent</i>: for any non-null reference values {@code x} and {@code y}, multiple invocations of {@code
     * x.equals(y)} consistently return {@code true} or consistently return {@code false}, provided no information used
     * in {@code equals} comparisons on the objects is modified. <li>For any non-null reference value {@code x}, {@code
     * x.equals(null)} should return {@code false}. </ul>
     * <p>
     * The {@code equals} method for class {@code Object} implements the most discriminating possible equivalence
     * relation on objects; that is, for any non-null reference values {@code x} and {@code y}, this method returns
     * {@code true} if and only if {@code x} and {@code y} refer to the same object ({@code x == y} has the value {@code
     * true}).
     * <p>
     * Note that it is generally necessary to override the {@code hashCode} method whenever this method is overridden,
     * so as to maintain the general contract for the {@code hashCode} method, which states that equal objects must have
     * equal hash codes.
     * @param obj the reference object with which to compare.
     * @return {@code true} if this object is the same as the obj argument; {@code false} otherwise.
     * @see #hashCode()
     * @see java.util.HashMap
     */
    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    /**
     * Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those
     * provided by {@link java.util.HashMap}.
     * <p>
     * The general contract of {@code hashCode} is: <ul> <li>Whenever it is invoked on the same object more than once
     * during an execution of a Java application, the {@code hashCode} method must consistently return the same integer,
     * provided no information used in {@code equals} comparisons on the object is modified. This integer need not
     * remain consistent from one execution of an application to another execution of the same application. <li>If two
     * objects are equal according to the {@code equals(Object)} method, then calling the {@code hashCode} method on
     * each of the two objects must produce the same integer result. <li>It is <em>not</em> required that if two objects
     * are unequal according to the {@link Object#equals(Object)} method, then calling the {@code
     * hashCode} method on each of the two objects must produce distinct integer results.  However, the programmer
     * should be aware that producing distinct integer results for unequal objects may improve the performance of hash
     * tables. </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined by class {@code Object} does return distinct
     * integers for distinct objects. (This is typically implemented by converting the internal address of the object
     * into an integer, but this implementation technique is not required by the Java&trade; programming language.)
     * @return a hash code value for this object.
     * @see Object#equals(Object)
     * @see System#identityHashCode
     */
    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    /**
     * Returns a string representation of the object. In general, the {@code toString} method returns a string that
     * "textually represents" this object. The result should be a concise but informative representation that is easy
     * for a person to read. It is recommended that all subclasses override this method.
     * <p>
     * The {@code toString} method for class {@code Object} returns a string consisting of the name of the class of
     * which the object is an instance, the at-sign character `{@code @}', and the unsigned hexadecimal representation
     * of the hash code of the object. In other words, this method returns a string equal to the value of: <blockquote>
     * <pre>
     * getClass().getName() + '@' + Integer.toHexString(hashCode())
     * </pre></blockquote>
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }
}

October 12th, 2015

Posted In: Java, java ninja, Javaninja, javascript

Tags: , , ,

Leave a Comment

Web developers were used to using cookies to store data in the user’s browser. You could then set how long you wanted the cookie to last. It could automatically be removed when the browser was closed or it could last longer. Overtime, cookies got a bad rap. There was also a relatively small size limitation.

HTML5 started a move away from cookies and to web storage. There are two types of web storage, sessionStorage and localStorage. Both of them use the same API signature, meaning that once you have an example of localStorage working, you also know how to access sessionStorage.

If they are both accessed the same way, then what is the difference? LocalStorage persists across browser sessions. You can think of it as persistent storage. LocalStorage is per origin Origin is defined as the protocol (e.g. http, https, etc), hostname and port number (e.g. 80, 443, 8080). This means that all of the pages from your website that are HTTPS can see the data set by another window, but the HTTP pages cannot see the data. This also means that if you are using multiple host names, you can’t share data across your domains.

SessionStorage has a shorter lifespan. It is intended for as long as the browser window is open. When the browser window is closed, this data is removed. Whereas localStorage is origin specific, sessionStorage is origin-window specific. Meaning, that even for the same domain, etc, the data is specific to the window that wrote it. This is something that cookies couldn’t support on their own. Any data that is written in sessionStorage is only visible by the window that wrote it.

Let’s look at how to use the web storage API. LocalStorage and sessionStorage are both accessed by global variables that are named after the type of storage, localStorage and sessionStorage. Meaning, that they are on the current window object (e.g. window.localStorage and window.sessionStorage). My examples will use localStorage, but the examples are the same for sessionStorage. In general, browsers allow up to 5MB to be stored. Much larger than the cookie size limit.

Does this browser support web storage

How do we determine if a browser supports localStorage? I saw two different ways. With that said, most browsers support web storage. IE8, FF 3.5, Chrome 4, Safari 4, Android 2.0, Chrome on Android 44.

        /**
         * Does this browser support HTML5 Local Storage.
         * @returns {boolean}
         */
        function supportsHtml5Storage() {
            try {
                return 'localStorage' in window && window['localStorage'] != null;
            } catch (e) {
                return false;
            }
        }

        /**
         * An alternative way to determine if a browser supports local storage.
         * @returns {boolean}
         */
        function supportsHtml5StorageAlternate() {
            return typeof(Storage) !== "undefined";
        }

Modernizer also has a method to determine if localStorage is supported.

// In FF4, if disabled, window.localStorage should === null.

// Normally, we could not test that directly and need to do a
//   `('localStorage' in window) && ` test first because otherwise Firefox will
//   throw bugzil.la/365772 if cookies are disabled

// Also in iOS5 & Safari Private Browsing mode, attempting to use localStorage.setItem
// will throw the exception:
//   QUOTA_EXCEEDED_ERRROR DOM Exception 22.
// Peculiarly, getItem and removeItem calls do not throw.

// Because we are forced to try/catch this, we'll go aggressive.

// Just FWIW: IE8 Compat mode supports these features completely:
//   www.quirksmode.org/dom/html5.html
// But IE8 doesn't support either with local files

Modernizr.addTest('localstorage', function() {
    var mod = 'modernizr';
    try {
        localStorage.setItem(mod, mod);
        localStorage.removeItem(mod);
        return true;
    } catch(e) {
        return false;
    }
});

How to write data

The data is stored in a key-value pair map. The value of the data is stored as a string. If you need it to be an integer, etc you will need to convert it to the value type that you need. The data is written by calling setItem. The example shows setting a value that is a string and an integer. Regardless, the value ends up as a string.

        /**
         * Sets a couple of items to local storage.
         */
        function setLocalStorageItem() {
            localStorage.setItem("foo", "bar");
            localStorage.setItem("max", 5);
        }

How to read data

Data is retrieved by calling the getItem method. Remember that the data comes back as a string and needs to be converted. Below are both examples. If the data was ‘5’, then retrieving the value of ‘5’ and adding 1 to it will result in ’51’ if you don’t convert the value to an integer. The value will be 6 if you convert it to an int, then add.

        /**
         * Gets an item from local storage.
         */
        function getLocalStorageAsString() {
            return localStorage.getItem("max") + 1;
        }

        /**
         * Gets an item from local storage.
         */
        function getLocalStorageAsNumber() {
            // local storage items are stored as a string
            return parseInt(localStorage.getItem("max")) + 1;
        }

How to remove data

Like most maps, there is the ability to remove an item from the map. However, unlike some other implementations, calling removeItem doesn’t return a value to indicate if it existed before it was removed.

        /**
         * Removes an item from local storage
         */
        function removeLocalStorage() {
            // removeItem does NOT return a value like Java does
            localStorage.removeItem("foo");
        }

There is also the option to remove all of the data that is in storage. This is accomplished by calling clear. I could not get this to work on Firefox 40.0.2.

        /**
         * Clear the contents of local storage.
         */
        function clearLocalStorage() {
            // clear did not work on FireFox 40.0.2
            localStorage.clear();
        }

How to query web storage for it’s contents

There is also the ability to get the number of items being stored in the local storage.

        /**
         * Gets the number of items in Local Storage.
         * @returns {number}
         */
        function getLocalStorageLength() {
            return localStorage.length;
        }

Once you have the number of items in a map, you can iterate over the items, get the key, then use the key to get the value.

        /**
         * Displays the contents of local storage.
         * @returns {string}
         */
        function displayLocalStorageContents() {
            var contents = "";
            var key = "";
            var value = "";
            for (var i = 0; i < localStorage.length; i++) {
                key = localStorage.key(i);
                value= localStorage.getItem(key);
                contents += "index=" + i + "\tkey=" + key + "\nvalue=" + value + "\n"
            }
            return contents;
        }

How to listen for storage events

Sometimes you will have the need to know that the storage has been changed. This will be especially useful if you are trying to coordinate the activities of multiple windows. You register an event listener by calling addEventListener, then implementing the a method to respond to the storage events. I didn’t see consistent behavior on this. I could call setItem, getItem and removeItem. I never got it to fire. I was able to make it fire in Chrome by manually changing the storage value. As of right now, the various browser implementations don’t seem to allow this to be of much use.

        /**
         * Register a local storage event listener.
         */
        function registerEventListener() {
            if (window.addEventListener) {
                addEventListener("storage", handleStorageEvent, false);
            }
        }

        /**
         * Handle the storage event.
         */
        function handleStorageEvent(storageEvent) {
            if (!storageEvent) {
                storageEvent = window.event;
            }
            alert("key=" + storageEvent.key +
                  " oldValue=" + storageEvent.oldValue +
                  " newValue=" + storageEvent.newValue +
                  " url=" + storageEvent.url);

        }

The entire file of examples

Here is the entire html file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Java Ninja Local Storage prototype</title>
</head>
<body>
    <button onclick="alert(supportsHtml5Storage())">Local Storage Supported</button>
    <button onclick="alert(supportsHtml5StorageAlternate())">Local Storage Supported (alt)</button>
    <button onclick="registerEventListener()">Local Storage register listener</button>
    <button onclick="alert(getLocalStorageLength())">LocalStorage length</button>
    <button onclick="alert(displayLocalStorageContents())">LocalStorage contents</button>
    <button onclick="setLocalStorageItem()">LocalStorage set</button>
    <button onclick="alert(getLocalStorageAsString())">LocalStorage get as string</button>
    <button onclick="alert(getLocalStorageAsNumber())">LocalStorage get as number</button>
    <button onclick="removeLocalStorage()">LocalStorage remove foo</button>
    <button onclick="clearLocalStorage()">LocalStorage clear</button>

    <script>
        /**
         * Does this browser support HTML5 Local Storage.
         * @returns {boolean}
         */
        function supportsHtml5Storage() {
            try {
                return 'localStorage' in window && window['localStorage'] != null;
            } catch (e) {
                return false;
            }
        }

        /**
         * An alternative way to determine if a browser supports local storage.
         * @returns {boolean}
         */
        function supportsHtml5StorageAlternate() {
            return typeof(Storage) !== "undefined";
        }

        /**
         * Register a local storage event listener.
         */
        function registerEventListener() {
            if (window.addEventListener) {
                addEventListener("storage", handleStorageEvent, false);
            }
        }

        /**
         * Handle the storage event.
         */
        function handleStorageEvent(storageEvent) {
            if (!storageEvent) {
                storageEvent = window.event;
            }
            alert("key=" + storageEvent.key +
                  " oldValue=" + storageEvent.oldValue +
                  " newValue=" + storageEvent.newValue +
                  " url=" + storageEvent.url);

        }

        /**
         * Gets the number of items in Local Storage.
         * @returns {number}
         */
        function getLocalStorageLength() {
            return localStorage.length;
        }

        /**
         * Displays the contents of local storage.
         * @returns {string}
         */
        function displayLocalStorageContents() {
            var contents = "";
            var key = "";
            var value = "";
            for (var i = 0; i < localStorage.length; i++) {
                key = localStorage.key(i);
                value= localStorage.getItem(key);
                contents += "index=" + i + "\tkey=" + key + "\nvalue=" + value + "\n"
            }
            return contents;
        }

        /**
         * Sets a couple of items to local storage.
         */
        function setLocalStorageItem() {
            localStorage.setItem("foo", "bar");
            localStorage.setItem("max", 5);
        }

        /**
         * Gets an item from local storage.
         */
        function getLocalStorageAsString() {
            return localStorage.getItem("max") + 1;
        }

        /**
         * Gets an item from local storage.
         */
        function getLocalStorageAsNumber() {
            // local storage items are stored as a string
            return parseInt(localStorage.getItem("max")) + 1;
        }

        /**
         * Removes an item from local storage
         */
        function removeLocalStorage() {
            // removeItem does NOT return a value like Java does
            localStorage.removeItem("foo");
        }

        /**
         * Clear the contents of local storage.
         */
        function clearLocalStorage() {
            // clear did not work on FireFox 40.0.2
            localStorage.clear();
        }
    </script>
</body>
</html>

August 24th, 2015

Posted In: HTML, HTML5, java ninja, Javaninja, javascript

Tags: ,

Leave a Comment

We’ve all had this happen. There is a large model that is being converted to JSON, but only a few fields are populated. The result looks something like this

{
    "modelStatus": "ERROR",
    "message": null,
    "messages": [
    ],
    "previousPage": null,
    "nextPage": null,
    "redirect": false,
    "errors": [
    ],
    "errorFields": [
    ],
    "exception": null,
    "ssoToken": null,
    "bossUser": null,
    "idGame": 6067,
    "idAccount": 0,
    "currency": null,
    "language": null,
    "rgsCode": "aristocrat",
    "rgsGameId": "aris50Dragons",
    "tokenType": null,
    "tokenTtl": 0,
    "tokenModelAsJsonString": null,
    "channel": "pc",
    "clientType": "flash",
    "mode": "REAL",
    "clientUrl": "http://***URL with parameters removed***",
    "method": "GET",
    "contentType": null,
    "body": null,
    "status": "ERROR"
}

Jackson 2.0 (e.g. FasterXML) offers an annotation that can be used to tune the behavior of the JSON serializer. This is accomplished by placing a @JsonInclude annotation at the top of the model. There are several options.

NON_NULL can be used to tell FasterXML to not send any elements that are null.

@JsonInclude(JsonInclude.Include.NON_NULL)

This results in the same response appearing like…

{
    "modelStatus": "ERROR",
    "messages": [
    ],
    "redirect": false,
    "errors": [
    ],
    "errorFields": [
    ],
    "idGame": 6067,
    "idAccount": 0,
    "rgsCode": "aristocrat",
    "rgsGameId": "aris50Dragons",
    "tokenTtl": 0,
    "channel": "pc",
    "clientType": "flash",
    "mode": "REAL",
    "clientUrl": "http://***URL with parameters removed***",
    "method": "GET",
    "status": "ERROR"
}

Notice that there are several empty arrays. These can also be removed by specifying that only NON_EMPTY items are sent. To determine if something sould be included in the JSON, several methods are used:

  • null objects are not included
  • Collections and Maps – isEmpty() is called. The item is included if this returns false
  • Arrays – the length must be > than 0 for the array to be included
  • String – the length() is called. The string is included if the value is greater than 0.

This is accomplished by

@JsonInclude(JsonInclude.Include.NON_EMPTY)

August 21st, 2015

Posted In: Java, java ninja, Javaninja, javascript, json

Leave a Comment

In a previous post, I showed how to declare an error label container for jquery validation messages. http://javaninja.net/2010/12/change-where-jquery-validation-messages-are-displayed/.

Today, we had a problem where the markup of the page required us to need to specify for this particular field, display the messages here. The default behavior of showing the messages by the fields was still desired, but the mark-up was preventing this. Checker was being added by Jquery Uniform dynamically. It was restricted to width=”25″ and height=”25″ to force the checkbox to a certain size. The problem is that the regular Jquery Validation error messages defaults to placing the error message after the target element. Unfortunately, this places it within the div that is limiting its size.

<div class="x2-top row">
    <div class="col-md-9  uniformed">
        <div id="acceptedTermsDiv checker">
            <input type="checkbox" 
                   name="acceptedTerms" 
                   id="acceptedTerms" 
                   style="width:20px;" 
                   value="true" 
                   tabindex="6"/>
            <span class="tooltip_text" 
                  data-toggle="tooltip" 
                  data-placement="bottom" 
                  title="You must accept the Terms and Conditions." 
                  data-container="#acceptedTermsDiv">
                <label for="acceptedTerms">I Accept The <a href="#" class="inline" id="termsLink">Terms &amp; Conditions</a></label>
            </span>                                                                                   
        </div>
    </div>
</div>

One way to solve this is to use a custom errorPlacement to force the messages to a custom location.

<div class="x2-top row">
    <div class="col-md-9  uniformed">
        <div id="acceptedTermsDiv checker">
            <input type="checkbox" 
                   name="acceptedTerms" 
                   id="acceptedTerms" 
                   style="width:20px;" 
                   value="true" 
                   tabindex="6"/>
            <span class="tooltip_text" 
                  data-toggle="tooltip" 
                  data-placement="bottom" 
                  title="You must accept the Terms and Conditions." 
                  data-container="#acceptedTermsDiv">
                <label for="acceptedTerms">I Accept The <a href="#" class="inline" id="termsLink">Terms &amp; Conditions</a></label>
            </span>                                                                                   
        </div>
    </div>
</div>

The custom errorPlacement will look like this:

errorPlacement: function(error, element) {
    if (element.attr("type") == "checkbox") {
        error.insertAfter($(element).closest(".checker").closest('div'));
    } else {
        error.insertAfter(element);
    }
}

June 17th, 2015

Posted In: java ninja, Javaninja, javascript, JQuery

Tags: , , , , , , ,

Leave a Comment

The previous article regex validation with jquery validation plugin laid the foundation for creating custom validation methods.

One thing I ran into was the case where multiple validations were created for a password, but they only test for the existence of one character that matches the regex. Meaning, does the password contain a lower-case letter? The advantage of using multiple validations, is that the user can be told which types of characters they need to make their password validate correctly.

I needed to validate that a password contains at least one lower-case letter, at least one upper-case letter, at least one number, and at least one symbol. First, I created the custom validations.


/**
 * Custom validator for contains at least one lower-case letter
 */
$.validator.addMethod("atLeastOneLowercaseLetter", function (value, element) {
    return this.optional(element) || /[a-z]+/.test(value);
}, "Must have at least one lowercase letter");

/**
 * Custom validator for contains at least one upper-case letter.
 */
$.validator.addMethod("atLeastOneUppercaseLetter", function (value, element) {
    return this.optional(element) || /[A-Z]+/.test(value);
}, "Must have at least one uppercase letter");

/**
 * Custom validator for contains at least one number.
 */
$.validator.addMethod("atLeastOneNumber", function (value, element) {
    return this.optional(element) || /[0-9]+/.test(value);
}, "Must have at least one number");

/**
 * Custom validator for contains at least one symbol.
 */
$.validator.addMethod("atLeastOneSymbol", function (value, element) {
    return this.optional(element) || /[!@#$%^&*()]+/.test(value);
}, "Must have at least one symbol");

Once the validators were created, now it was time to use them.

<script>
    $(function () {
        $("#change-password").validate({
            rules: {
                password: {
                    required: true,
                    atLeastOneLowercaseLetter: true,
                    atLeastOneUppercaseLetter: true,
                    atLeastOneNumber: true,
                    atLeastOneSymbol: true,
                    minlength: 8,
                    maxlength: 40
                }
            }
        });
    });
</script>

An alternative way to specify the rules is

$("#password").rules("add", {
    required: true,
    minlength: 8,
    maxlength:40,
    atLeastOneLowercaseLetter: true,
    atLeastOneUppercaseLetter: true,
    atLeastOneNumber: true,
    atLeastOneSymbol: true
});

June 13th, 2015

Posted In: java ninja, Javaninja, javascript, JQuery

Tags: , , , ,

Leave a Comment

Grunt, grunt, grunt. This is my first gruntfile. Once you get in the groove, it’s pretty easy to write them quickly.

module.exports = function (grunt) {

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        clean: ['build'],
        less: {
            current: {
                files: [
                    {
                        expand: true,
                        cwd: "current/styles",
                        src: [
                            'global/**/*.less',  // need to process the global css separately from the rest
                            'modules/**/*.less'
                        ],
                        dest: 'build/temp/css',
                        ext: '.css'
                    }
                ]
            },
            working: {
                files: [
                    {
                        expand: true,
                        cwd: "working/styles",
                        src: [
                            'global/**/*.less', // need to process the global css separately from the rest
                            'modules/**/*.less'
                        ],
                        dest: 'build/temp/css-working',
                        ext: '-working.css'
                    }
                ]
            }
        },
        concat: {
                css: {
                    options: {
                        sourceMap: true
                    },
                    files: {
                        'build/compiled/css/<%= pkg.name %>.css': [
                            'bootstrap-3.3.4/dist/css/bootstrap.css',
                            'build/temp/css/global/igaming.css',
                            'build/temp/css/modules/**/*.css'],  // note would be better to use files created in less task via something like'<%= less.working.files.dest %>'
                        'build/compiled/css/<%= pkg.name %>-working.css' : [
                            'bootstrap-3.3.4/dist/css/bootstrap.css',
                            'build/temp/css-working/global/igaming.css',
                            'build/temp/css-working/modules/**/*.css'  // note would be better to use files created in less task via something like'<%= less.working.files.dest %>'
                        ]
                    }
                },
                js: {
                    options: {
                        separator: ';',
                        sourceMap: true
                    },
                    files: {
                        'build/compiled/js/<%= pkg.name %>.js': [
                            // load jquery, bootstrap, then the rest
                            'current/scripts/jquery/*.js',
                            'bootstrap-3.3.4/dist/js/bootstrap.js',
                            'current/scripts/libs/*.js',
                            'current/scripts/js/*.js',
                            'current/scripts/igaming/*.js'
                        ],
                        'build/compiled/js/<%= pkg.name %>-working.js': [
                            // load jquery, bootstrap, then the rest
                            'working/scripts/jquery/*.js',
                            'bootstrap-3.3.4/dist/js/bootstrap.js',
                            'working/scripts/libs/*.js',
                            'working/scripts/js/*.js',
                            'working/scripts/igaming/*.js'
                        ]
                    }
                }
        },
        uglify: {
            current: {
                options: {
                    sourceMap: true,
                    sourceMapIncludeSources: true,
                    sourceMapIn: 'build/compiled/js/<%= pkg.name %>.js.map'
                },
                files: {
                    'build/compiled/js/<%= pkg.name %>.min.js': 'build/compiled/js/<%= pkg.name %>.js' // wouldn't use '<%= concat.js.dest %>', for some reason
                }
            },
            working: {
                options: {
                    sourceMap: true,
                    sourceMapIncludeSources: true,
                    sourceMapIn: 'build/compiled/js/<%= pkg.name %>-working.js.map'
                },
                files: {
                    'build/compiled/js/<%= pkg.name %>-working.min.js': 'build/compiled/js/<%= pkg.name %>-working.js' // wouldn't use '<%= concat.js_working.dest %>', for some reason
                }
            }
        },
        cssmin: {
            options: {
                sourceMap: true
            },
            target: {
                files: {
                    'build/compiled/css/<%= pkg.name %>.min.css': ['build/compiled/css/<%= pkg.name %>.css'],
                    'build/compiled/css/<%= pkg.name %>-working.min.css': ['build/compiled/css/<%= pkg.name %>-working.css']
                }

            }
        },
        copy: {
            // builds the /assets webapp by copying the /webapp directory,
            // then copying the compiled css and js resources
            webapp: {
                files: [
                    {
                        expand: true,
                        cwd: 'webapp',
                        src: '**',
                        dest: 'build/assets'
                    }
                ]
            },
            compiled: {
                files: [
                    {
                        expand: true,
                        cwd: 'build/compiled',
                        src: '**',
                        dest: 'build/assets/compiled'
                    }
                ]
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-copy');


    // Default task(s).
    grunt.registerTask('default', ['clean', 'less', 'concat', 'uglify', 'cssmin', 'copy']);




    //grunt.registerTask("concat-css", "Concatenates the CSS files into a CSS file per section", function () {
    //
    //    // read all subdirectories from your modules folder
    //    grunt.file.expand("build/css").forEach(function (topDir) {
    //        console.log('topDir=' + topDir);
    //
    //        //grunt.file.setBase(topDir);
    //        // read all subdirectories from your modules folder
    //        grunt.file.expand("build/css").forEach(function (dir) {
    //            console.log('dir=' + dir);
    //
    //            console.log("cssFiles=" + cssFiles);
    //
    //            // get the current concat config
    //            var concat = grunt.config.get('concat') || {};
    //
    //            // set the config for this modulename-directory
    //            concat[cssFiles] = {
    //                //cwd: topDir,
    //                src: dir + '**/*.css',
    //                dest: dir + '.css'
    //            };
    //            // save the new concat configuration
    //            grunt.config.set('concat', concat);
    //        });
    //        // set the base to where it is supposed to be so that Grunt works as expected
    //        //grunt.file.setBase('../..');
    //    });
    //    // when finished run the concatenations
    //    grunt.task.run('concat');
    //});


};

June 9th, 2015

Posted In: grunt, Javaninja, javascript

Tags: , , , ,

Leave a Comment

We needed several Jquery validations with regexes. I coded various custom validators with the regex included, so the regex could be re-used instead of typing the regex again and again.

        /**
         * Custom validator for letters (uppercase/lowercase)
         */
        $.validator.addMethod("lettersOnly", function (value, element) {
            return this.optional(element) || /^[a-zA-Z]+$/i.test(value);
        }, "Please enter letters only.");

        /**
         * Custom validator for numbers only (0-9)
         */
        $.validator.addMethod("numbersOnly", function (value, element) {
            return this.optional(element) || /^[0-9]+$/i.test(value);
        }, "Please enter numbers only.");

        /**
         * Custom validator for dates (0-9 and slashes).  Does not check month, day or year.
         */
        $.validator.addMethod("date", function (value, element) {
            return this.optional(element) || /^[0-9/]+$/i.test(value);
        }, "Please enter dates only.");

        /**
         * Custom validator for phone numbers (0-9, (, ) and -).  Does not check number of digits, etc.
         */
        $.validator.addMethod("phone", function (value, element) {
            return this.optional(element) || /^[0-9()-]+$/i.test(value);
        }, "Please enter dates only.");

        /**
         * Custom validators for letters and numbers only.  Uppercase/lowercase letters and numbers (0-9).
         */
        $.validator.addMethod("lettersAndNumbersOnly", function (value, element) {
            return this.optional(element) || /^[a-zA-Z0-9]+$/i.test(value);
        }, "Please enter letters and numbers only.");

Here is an example of how the validation was wired in via the rules section of the Jquery validation plugin

            rules: {
                firstName: {
                    required: true,
                    minlength: 1,
                    maxlength: 32,
                    lettersOnly: true
                },

April 1st, 2014

Posted In: javascript, JQuery

Tags: , , ,

One Comment

I had a Jquery form that I was generating with an Ajax call. The user selected a state, then I retrieved the licenses for the agency that they selected from the state. I also defaulted to the first agency and ran a query. The problem was when the state had no agencies, it still ran the query. I wanted to know if I actually had an agency selected. Here is the answer using Jquery:

if ($('#myElement').length > 0) {
    ....
}

July 3rd, 2012

Posted In: javascript, JQuery

Leave a Comment

It has been a while since I wrote a form. I kept posting data even though the Jquery Validation fired and was not valid. I forgot that you have to check for a valid form. I also learned that you can also add a submit handler to the validation call. Here is what the completed validation and ajax call looked like. Notice that in this form, I disable the submit button belonging to the form just before the ajax request is sent.

$(document).ready(function () {
    $("#expungementForm").validate({
        submitHandler: function(form) {
            $.ajax({
                cache: false,
                type: "POST",
                url: form.action,
                data: $(form).serialize(),
                beforeSend: function (jqXHR, settings) {
                    $('input[type="submit"]', form).attr('disabled', true);
                },
                success: function (data) {
                    $('#expungeResult').html(data)
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    $('#expungeResult').html(textStatus).append("...").append(errorThrown).css('color', 'red')
                }});
            return false;
        }
    });
});

June 13th, 2012

Posted In: HTML, javascript, JQuery, json

Tags: , , , , ,

Leave a Comment

Next Page »
LinkedIn Auto Publish Powered By : XYZScripts.com