/*-----------------------------------------------------------------------------------------------------------------------\dlo/
Date:           Sep 10th, 2010
Revised:        March 4, 2014
Name:           CHO Prototype Utility.
Description:    Base script library to use in CHOE3
Author:         Dmitry Lopyrev
Note:           Requires Prototype JS library (v.1.7c - modified) 

---------------------------------------------------------------------------------------------------------------------------*/

//------------------------------------------------------------------------------------------------------------------\dlo/
//Extend object prototypes 
//----------------------------------------------------------------------------------------------------------------------
Object.extend(Object, {
    guid: function () { return new Date().getTime().toPaddedString(10, 16).toUpperCase(); },
    wrap: Object.extend.wrap(function (proceed, destination, source, properties) {
        if (!Object.isArray(properties) || !properties.length)
            return proceed(destination, source);
        properties.each(function (it) {
            !Object.isUndefined(source[it]) && (destination[it] = source[it]);
        });
        return destination;
    }),
    get: function (o, property, def) {
        if (!o) return def;
        var properties = (property || '').split('.');
        property = properties.first();
        properties = properties.slice(1);
        o = o[property];
        if (!Object.isUndefined(o) && properties.length)
            return arguments.callee(o, properties.join('.'));
        return o || def;
    },
    getValue: function (o, property, def) {
        o = Object.get(o, property);
        if (!o)
            return def;
        if (Object.isFunction(o.getValue))
            return o.getValue() || def;
        return o.value || def;
    },
    cast: function (o) {
        for (it in o) {
            if (Object.isString(o[it]))
                o[it] = o[it].cast(true);
        }
        return o;
    },
    //Temporary
    isBoolean: function (o) {
        return typeof o == "boolean";
    }

});

//----------------------------------------------------------------------------------------------------------------------
//Extend Window prototype object Doesn't work on Android mobile stock browser
//----------------------------------------------------------------------------------------------------------------------
/*
Object.extend(Window.prototype, {
    Wait: function (n) {
        var d = new Date().add('ms', String(n).toNumber() * 1000);
        while (new Date() <= d) {}
    }
});
*/
Wait = function (n) {
    var d = new Date().add('ms', String(n).toNumber() * 1000);
    while (new Date() <= d) { }
};


//----------------------------------------------------------------------------------------------------------------------
//Extend Number prototype object
//----------------------------------------------------------------------------------------------------------------------
Object.extend(Number.prototype, {
    toString: Number.prototype.toString.wrap(function (proceed, f) {
        try {
            var number = this;
            if (Object.isString(f) && /(G|C|N|F|P)/g.test(f)) {
                return isNaN(number) ? "" : String(f || 'G').replace(/(G|C|N|F|P)/g,
                    function ($1) {
                        switch ($1) {
                            case 'C':
                                return number.toFixed(2);
                            case 'N':
                                return number.toFixed(0);
                            case 'F':
                                return number.toFixed(2);
                            case 'P':
                                return number.toString() + "%";
                        }
                        return proceed();
                    }
                );
            } else {

                return proceed.apply(this, $A(arguments).slice(1));
            }
        } catch (e) {
        }

    }),
    toOrdinal: function () {
        var s = ["th", "st", "nd", "rd"], v = this % 100;
        return this + (s[(v - 20) % 10] || s[v] || s[0]);
    },
    toNumber: function() {
        return this;
    }
});

Object.extend(Array.prototype, {
    removeFirst: function (n) {
        n = Math.abs(String(n).toNumber() || 1);
        if (this.length < n)
            return this;
        return this.splice(this.length - n, n);
    },
    removeLast: function (n) {
        while (this.length && n > 0)
            this.pop(), n--;
        return this;
    },
    exclude: function(a) {
        if (Object.isArray(a)) {
            return this.without.apply(this, $A(a));
        }
        return this.without.call(this, arguments);
    }
        
});



//----------------------------------------------------------------------------------------------------------------------
//Extend object prototypes 
//----------------------------------------------------------------------------------------------------------------------
Object.extend(String.prototype, {
    toNumber: function (def, radix) {
        var val = String(this).replace(/([^\-\d\. ])/g, "") || (Object.isNumber(def) ? def.toString() : '0');
        if (val.indexOf('.') == -1)
            return parseInt(val, radix || 10);
        else
            return parseFloat(val);
    },
    cast: function (b) {
        if (b) {
            if (/^[-]?[\d ]+[\.]?[\d ]*$/.test(this))
                return this.toNumber();
            if (/^(true|false)$/i.test(this))
                return this.toLowerCase() == 'true';
        }
        return this.toString();
    },
    include: String.prototype.include.wrap(function (proceed, pattern, ignorecase) {
        if (ignorecase === true)
            return this.toLowerCase().indexOf((pattern || '').toLowerCase()) > -1;
        return proceed(pattern);

    }),
    addQueryParam: function(o) {
        if (!o)
            return this;
        var p = Object.extend(this.toQueryParams(), Object.isString(o) ? o.toQueryParams(): o);
        return  this.replace(/[\?#].*/gi, '') +"?" + Object.toQueryString(p);
       
    }
});
//------------------------------------------------------------------------------------------------------------------\dlo/
// Extend Date prototype object
//----------------------------------------------------------------------------------------------------------------------
Object.extend(Date.prototype, {
    toString: Date.prototype.toString.wrap(function (proceed, f) {
        if (Object.isUndefined(f))
            return proceed();
        var d = this;
        return isNaN(this) ? "" : String(f).replace(/(('.+')|yyyy|yy|MMMM|MMM|MM|w|dddd|dd|ds|d|hh|h|mm|m|ss|tt|p)/g,
            function ($1) {
                var h;
                switch ($1) {
                    case 'yy': return (d.getFullYear() % 100).toPaddedString(2);
                    case 'yyyy': return d.getFullYear().toPaddedString(4);
                    case 'MMMM':
                    case 'MMM': return (['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][d.getMonth()]).substr(0, $1.length == 3 ? 3 : 20);
                    case 'MM': return (d.getMonth() + 1).toPaddedString(2);
                    case 'p': return ['Night', 'Morning', 'Afternoon', 'Evening'][parseInt(d.getHours() / 6)];
                    case 'dddd':
                    case 'w': return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][d.getDay()];
                    case 'ds': return d.getDate().toOrdinal();
                    case 'dd': return d.getDate().toPaddedString(2);
                    case 'd': return d.getDate().toString();
                    case 'hh': return d.getHours().toPaddedString(2); 											//hour (01-24)
                    case 'h': return ((h = d.getHours() % 12) ? h : 12).toString(); 			//hour (01-12)
                    case 'mm': return d.getMinutes().toPaddedString(2);
                    case 'm': return d.getMinutes().toString();
                    case 'ss': return d.getSeconds().toPaddedString(2);
                    case 'tt': return d.getHours() < 12 ? 'am' : 'pm';
                    default: return $1.replace(/'/g, "");
                }
            }
      );
    }),
    CustomFormat: function (f) {
        return this.toString(f);
    },
    add: function (interval, value) {
        var d = new Date(this.getTime()), b = false;
        if (!interval || value === 0) return d;
        if (typeof (value) == 'string') {
            b = true, value = parseInt('0' + value, 10);
        }
        switch (interval.toLowerCase()) {
            case 'ms':
                d.setMilliseconds((b ? 0 : this.getMilliseconds()) + value); break;
            case 'sec':
                d.setSeconds((b ? 0 : this.getSeconds()) + value); break;
            case 'min':
                d.setMinutes((b ? 0 : this.getMinutes()) + value); break;
            case 'h':
                d.setHours((b ? 0 : this.getHours()) + value); break;
            case 'd':
                d.setDate((b ? 0 : this.getDate()) + value); break;
            case 'm':
                d.setMonth((b ? 0 : this.getMonth()) + value); break;
            case 'y':
                d.setFullYear((b ? 0 : this.getFullYear()) + value); break;
        }
        return d;
    },
    toObject: function (f) {
        var d = this, o = { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, ms: 0 };
        String(f || 'YMDhmsl').replace(/(Y|M|D|h|m|s|l)/g,
            function ($1) {
                switch ($1) {
                    case 'Y': o.year = d.getFullYear(); break;
                    case 'M': o.month = d.getMonth(); break;
                    case 'D': o.day = d.getDate(); break;
                    case 'h': o.hour = d.getHours(); break;
                    case 'm': o.minute = d.getMinutes(); break;
                    case 's': o.second = d.getSeconds(); break;
                    case 'l': o.ms = d.getMilliseconds(); break;
                }
            }
        );
        return o;
    },
    ticks: function () {
        function monthToDays(year, month) {
            var add = 0;
            var result = 0;
            if ((year % 4 == 0) && ((year % 100 != 0) || ((year % 100 == 0) && (year % 400 == 0)))) add++;

            switch (month) {
                case 0: return 0;
                case 1: result = 31; break;
                case 2: result = 59; break;
                case 3: result = 90; break;
                case 4: result = 120; break;
                case 5: result = 151; break;
                case 6: result = 181; break;
                case 7: result = 212; break;
                case 8: result = 243; break;
                case 9: result = 273; break;
                case 10: result = 304; break;
                case 11: result = 334; break;
                case 12: result = 365; break;
            }
            if (month > 1) result += add;
            return result;
        };
        function timeToTicks(hour, minute, second) {
            return (((hour * 3600) + minute * 60) + second) * 10000000;
        };
        function dateToTicks(year, month, day) {
            //number of days in the the years
            var a = parseInt((year - 1) * 365);
            //count the leapyear days
            //A year is a leap year if it can be divided by 4, but can't be divided by 100, except of case when it can be divided by 400. 
            var b = parseInt((year - 1) / 4);
            var c = parseInt((year - 1) / 100);
            var d = parseInt((a + b - c));
            var e = parseInt((year - 1) / 400);
            var f = parseInt(d + e);
            //number of days for complete months.
            var monthDays = monthToDays(year, month - 1);
            //add in number of completed days in the month
            var g = parseInt((f + monthDays) + day - 1);
            return g * 864000000000;
        };
        var o = this.toObject();
        return dateToTicks(o.year, o.month, o.day) + timeToTicks(o.hour, o.minute, o.second) + (o.ms * 10000);
    },
    round: function (n) {
        var min = this.getHours() * 60 + this.getMinutes();
        min = Math.floor(min / Math.abs(n) + 0.5) * Math.abs(n);
        return this.setTime().add('min', min);
    },
    clone: function () {
        return new Date(this.getTime());
    },
    diff: function (d, f) {
        if (d instanceof Date) {
            switch (String(f)) {
                case 'y':
                case 'Y': return this.getFullYear() - d.getFullYear();
                case 'M': return (this.getMonth() + 12 * this.getFullYear()) - (d.getMonth() + 12 * d.getFullYear());
                case 'd': return parseInt((this.getTime() - d.getTime()) / (24 * 3600 * 1000));
                case 'd+': return parseInt((this.getTime() - d.getTime()) / (24 * 3600 * 1000)) + (parseInt((this.getTime() - d.getTime()) % (24 * 3600 * 1000)) >= 12 ? 1 : 0);
                case 'h': return parseInt((this.getTime() - d.getTime()) / (3600 * 1000));
                case 'h+': return parseInt((this.getTime() - d.getTime()) / (3600 * 1000)) + (parseInt((this.getTime() - d.getTime()) % (3600 * 1000)) >= 30 ? 1 : 0);
                case 'm': return parseInt((this.getTime() - d.getTime()) / (60 * 1000));
                case 'm+': return parseInt((this.getTime() - d.getTime()) / (60 * 1000)) + (parseInt((this.getTime() - d.getTime()) % (60 * 1000)) >= 30 ? 1 : 0);
                case 's': return parseInt((this.getTime() - d.getTime()) / 1000);
                default: return this.getTime() - d.getTime();
            }
        } else {
            return 0;
        }

    },
    weekend: function () {
        return this.getDay() == 0 || this.getDay() == 6;
    },
    setTime: Date.prototype.setTime.wrap(function (proceed, time) {
        if (Object.isNumber(time))
            return proceed(time);
        time = String(time || '').replace(/(am|pm)/i, function($1) { return ' '+$1});
        
        var d = new Date(this.CustomFormat("yyyy/MM/dd ") + time);
        return d;
    })
});

Object.extend(Function.prototype, {
    argumentValues: function (n) {
        var names = this.argumentNames().slice(n);
        var o = {};
        for (var i = 0; i < this.arguments.length; i++) {
            if (names[i])
                o[names[i]] = this.arguments[i];
            else if (Object.isString(this.arguments[i]) || Object.isDate(this.arguments[i]) || Object.isNumber(this.arguments[i]) || Object.isArray(this.arguments[i]) || Object.isBoolean(this.arguments[i])) {
                !Object.isArray(o.parameters) && (o.parameters = []);
                o.parameters.push(this.arguments[i]);
            } else {
                Object.extend(o, this.arguments[i]);
            }
        }
        return o;
    }
});

//----------------------------------------------------------------------------------------------------------------------
//- Prototype Library Extesions     
//----------------------------------------------------------------------------------------------------------------------
//Added new methods to DOM elements
Element.addMethods({
    isDescendantOf: function (element, host) {
        if (!Object.isElement($p(host)))
            return false;
        host = $p(host);
        var isDecendant = false;
        //Check if current element is desendant of popup container
        var b = $p(element).descendantOf(host);
        while (element.parentNode) {
            element = element.parentNode;
            isDecendant |= element === host.up();
        }
        //close popup if isn't decendant of host or popup selector
        return Boolean(isDecendant);
    },
    noClick: function (element) {
        element = $p(element);
        element.$click = element.onclick ? element.onclick : Prototype.emptyFunction;
        element.onclick = null;
        element.on("click", function (e) { e.stop(); }.bindAsEventListener());
        element.tagName === 'A' && (element.writeAttribute('href', (element.readAttribute('disabled') ? "" : "javascript:void(0);")));
        return element;
    },
    getInnerText: function (element) {
        return element.innerText || element.textContent;
    },
    setInnerText: function (element, txt) {
        if (element.innerText) element.innerText = txt;
        else if (element.textContent) element.innerHTML = txt;
    },
    //Create and returns an object based on item attributes started with "data-";
    getActions: function (element, options, def) {
        def = def || {}, options = options || {};
        /* 
        | possible property for options : 
        |      camelize : convert object's property name to Camelize string (a first letter is capital)
        |      lower : use lower case in property name
        |      cast: convert parameter value to type (based on content), overwize value will always be a string
        */
        return $A(element.attributes).inject(def, function (o, attr) {
            if (attr.name.startsWith('data-')) {
                var ns = attr.name.replace('data-', '').split('-');
                var result = o;
                for (var i = 0, it; it = ns[i++];) {
                    if (options.camelize)
                        it = ('-' + it).camelize();
                    if ((options.capitalize))
                        it = it.capitalize();
                    if ((options.lower))
                        it = it.toLowerCase();
                    !result[it] && (result[it] = {});
                    if (i == ns.length)
                        result[it] = attr.value.cast(options.cast);
                    else
                        result = result[it];
                };
            }
            return o;
        });
    },
    getFields: function (element, opt) {
        //returns input elements separated by group name (and only for group specified in "names" parameter)
        if (!Object.isElement(element))
            return null;
        opt = opt || {};
        var inputs = Object.isArray(opt) ? opt : Form.getInputs(element);
        if (Object.isString(opt.type)) { //'text|textarea|hidden|checkbox|radio'
            opt.type = new RegExp(opt.type, 'i');
        }
        if (!Object.isArray(inputs))
            return null;
        var data = inputs.inject({}, function (o, it) {
            if (!Object.isElement(it))
                return o;
            if (opt.type && !opt.type.test(it.type))
                return o;

            var data = Object.extend({ field: it.id.split('_').last() }, it.getActions({lower:true}));

            if (Object.isArray(o[data.field]))
                o[data.field].push(it);
            else
                o[data.field] = it;
            return o;
        });
        return data;

    },
    simulate: function (element, eventName, memo) {

        var eventMatchers = {
            'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll|change)$/,
            'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
        };
        var options = {
            pointerX: 0,
            pointerY: 0,
            button: 0,
            ctrlKey: false,
            altKey: false,
            shiftKey: false,
            metaKey: false,
            bubbles: true,
            cancelable: true
        };

        options = Object.extend(options, arguments[2] || {});
        var oEvent, eventType = null;

        element = $p(element);

        for (var name in eventMatchers) {
            if (eventMatchers[name].test(eventName)) {
                eventType = name;
                break;
            }
        }

        if (!eventType)
            return undefined;

        if (document.createEvent) {
            oEvent = document.createEvent(eventType);
            if (eventType == 'HTMLEvents') {
                oEvent.initEvent(eventName, options.bubbles, options.cancelable);
            } else {
                oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
                    options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
                    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
            }
            oEvent.memo = memo;
            element.dispatchEvent(oEvent);
        } else {
            options.clientX = options.pointerX;
            options.clientY = options.pointerY;
            oEvent = Object.extend(document.createEventObject(), options);
            oEvent.memo = memo;
            element.fireEvent('on' + eventName, oEvent);
        }
        return element;
    }
});

//----------------------------------------------------------------------------------------------------------------------
//Added new Form methods
//----------------------------------------------------------------------------------------------------------------------
Form.Methods.LoadFields = function (form, container, isfullname) {
    //returns new object for each found input field in form element : 
    //Properties ::
    //     field - current input field
    //     value - current input field's value (can be treat as default value )
    //     IsChanged(method) -  to return true if input field value has changed
    // Note :: the name of this property is based on a field name (Input Control ID on server side) 
    // Optional :: if container passed in it will be updated with new properties 
    if (form = $p(form), !form)
        return;

    var elements = Form.getElements(form);
    elements = elements.length ? elements : [form];

    var o = elements.inject({}, function (o, inp) {
        if (inp.name) {
            var name = Object.isString(container) ? container : (isfullname ? inp.name : inp.name.split('$').last());
            var data = {
                IsChanged: function () { return Boolean(this.field.getValue() !== this.value); },
                AcceptChanges: function () { return this.value = this.field.getValue(); },
                Values: function (flag) {
                    return $H(this).keys().inject({}, function (values, key) {
                        if (this[key].field && Object.isFunction(this[key].field.getValue))
                            if ((flag === true && this[key].IsChanged()) || flag == undefined)
                                values[key] = this[key].field.getValue();
                        return values;
                    }, this);
                }
            };
            if (inp.type == 'radio') {
                Object.extend(data, { field: $p(inp.id), value: inp.getValue(), options: form.select("[name='" + inp.name + "']") });
            } else if (inp.type == 'checkbox') {
                //Do not use Checkbox list: user 
                Object.extend(data, { field: inp, value: inp.getValue() });
            } else {
                Object.extend(data, { field: inp, value: inp.getValue() });
            }

            if (Object.isArray(o[name]))
                o[name].push(data);
            else if (o[name]) {
                if (inp.type != 'radio')
                    o[name] = [o[name], data];
            } else
                o[name] = data;
        };
        return o;
    }, this);
    if (Object.isString(container)) {
        return o[container];
    }
    container && (Object.extend(container, o));
    return o;
};

//Add new methods to all Prototypes 
Element.addMethods();


/**
* Ajax.Request.abort
* extend the prototype.js Ajax.Request object so that it supports an abort method
*/
Ajax.Request.prototype.abort = function () {
    // prevent and state change callbacks from being issued
    this.transport.onreadystatechange = Prototype.emptyFunction;
    // abort the XHR
    this.transport.abort();
    // update the request counter
    Ajax.Responders.dispatch('onComplete', this);

    //Ajax.activeRequestCount--;
    //if (Ajax.activeRequestCount < 0) {
    //    Ajax.activeRequestCount = 0;
    //}
};


/*- End of Extensions ---------------------------------------------------------------------------------------------------------*/

var Utility = {
    Global : {bubbletip:true},
    UI: {}, //UI classes holder
    AddLibrary: function (w) {
        if (w && !w.Prototype) {
            var newScr = w.document.createElement("SCRIPT");
            newScr.src = ($$('script').find(function (s) { return /prototype.js/i.test(s.src); }) || {}).src;
            if (newScr.src) {
                newScr.type = "text/javascript";
                w.document.getElementsByTagName("head")[0].appendChild(newScr);
            }
        }

    },
    AddStyle: function () {
        var b;
        var links = $$('link').inject([], function (list, it) {
            if (/App_Themes/i.test(Object.isElement(it) && it['href'])) {
                list.push(it.href);
                if (/preloader.css/i.test(it.href))
                    b = true;
            }
            return list;
        });
        if (!links.length || b)
            return;
        var link = document.createElement("LINK");
        link.type = 'text/css';
        link.rel = 'stylesheet';
        link.href = links[0].substr(0, links[0].toLowerCase().indexOf("app_themes")) + 'app_themes/preloader.css';
        document.getElementsByTagName("head")[0].appendChild(link);

    }
};


//----------------------------------------------------------------------------------------------------------------------
//[serfi] $hack-hack$ helper function shaves of HTML from an AJAX invocation supposed to return application/JSON content
//----------------------------------------------------------------------------------------------------------------------
function CleanupHtmlJSON(text) {
    var returnObject = null;
    var lines = text.split('\r\n');
    lineCount = lines.length;
    strJSONSerialized = lines[lineCount - 1];
    //perhaps the library does this cleansing already but too cryptic for me at this point
    sanitizedJSON = (strJSONSerialized.replace('"d":"{', '"d":{')).replace('"}"}', '"}}');
    sanitizedJSON = sanitizedJSON.replace(/\\"/g, '"');
    sanitizedJSON = sanitizedJSON.replace(/\\\\/g, '\\');
    try {
        returnObject = sanitizedJSON.evalJSON();
    } catch (e) {
        returnObject = '{ "Title": "Application & Validation Error", "Message": "Unspecified Error." }'.evalJSON();
    }
    return returnObject.d;
}


//----------------------------------------------------------------------------------------------------------------------
//Class to create and handle Web Method Requests
//----------------------------------------------------------------------------------------------------------------------
Utility.WebMethodRequest = new Class.create({
    initialize: function (Caller, options) {
        this.method = '';
        this.parameters = {};
        //update options
        Object.extend(this, options || {});
        //current object to hold this WebRequestMethod instance
        this.Caller = Caller;
        if (this.Caller.ShowError)
            this.ShowError = this.Caller.ShowError;
        if (this.Caller.CompleteRequest)
            this.CompleteRequest = this.Caller.CompleteRequest;
    },
    //Show Error. Executes when WebMethod returns an error 
    ShowError: function (request) {

        this.StopProgress();
        var err = { message: 'Error ', title: request.statusText || 'Error', cssButton: 'simple', callBack: this.Caller.onErrorComplete };
        if (request) {
            if (request.responseJSON) {
                err = Object.extend(err, request.responseJSON);
                //ignore uppercase for properties
                err.message = err.Message || err.message;
                err.title = err.Title || err.title;
                //            } else if (request.status == 500) {
                //    //the parsing failed: $hack-hack$ - strip off the HTML and reparse: only the last line is a valid JSON serialization 
                //    var messageJSON = CleanupHtmlJSON(request.responseText);
                //    err = Object.extend(err, messageJSON);
                //    err.message = err.Message || err.message;
                //    err.title = err.Title || err.title;
            } else if (typeof request.statusText != 'undefined') {
                //current response text probably is YSofD, so we need to parse if
                var ysof = /<!--\s*\[(.*?)]:(\s*.*\s(.*[\n\r]*)*?)\s*(at(.*[\n\r]*)*)-->/;
                var matches = request.responseText.match(/<!--\s*\[(.*?)]:(\s*.*\s(.*[\n\r]*)*?)\s*(at(.*[\n\r]*)*)-->/);
                if (Object.isArray(matches)) {
                    err.title = matches[1];
                    err.message = matches[2];
                }

                else {
                    //show fill body of error page
                    err.message = request.responseText;
                    err.isHTML = true;
                }
            }
            else if (request.message) {
                err.message = request.message;
                err.title;
            }
            if (Object.isFunction(this.Caller.onError)) {
                err = this.Caller.onError.call(this.Caller, err, request);
            };
        }
        if (err)
            //Show error message
            $alert(err);
    },
    //Called when request is completed
    CompleteRequest: function (response) {
        document.title = this.Title;
        var isexecuted = false; // true - don't execute onComplete!

        //removes current message if exists
        this.StopProgress();

        if (this.ajaxRequest.success()) {
            try {

                if (response && response.responseJSON) {

                    //if new update properties passed in apply the properties to current object instance
                    if (response.responseJSON.properties)
                        Object.extend(this.Caller, response.responseJSON.properties);

                    //Check Local method. 
                    var callbackfunction = response.responseJSON.callback || response.responseJSON.method;
                    var caller;
                    if (Object.isFunction(this.__callback)) {
                        isexecuted = true;
                        if (Object.isArray(response.responseJSON.methodparameters))
                            this.__callback.apply(caller, response.responseJSON.methodparameters);
                        else
                            this.__callback.call(caller, (response.responseJSON.methodparameters || response.responseJSON), response);
                        delete this.__callback;
                    }
                    //if result has a method property and current object has this method, so invoke that method with patameters (optional property)
                    //NOTE. the callback method can contains full method description like 'host.method'. 
                    //So, in that case we have to change the caller context
                    else if (Object.isFunction(func = Object.get(this.Caller, callbackfunction))) {
                        isexecuted = true;
                        caller = Object.get(this.Caller, callbackfunction.split('.').removeLast(1).join('.')) || this.Caller;
                        if (Object.isArray(response.responseJSON.methodparameters))
                            func.apply(caller, response.responseJSON.methodparameters);
                        else
                            func.call(caller, (response.responseJSON.methodparameters || response.responseJSON), response);

                        //Check Global method.
                        //if result has a method property and current object has this method, so invoke that method with patameters (optional property)
                    } else if (Object.isFunction(func = Object.get(window, callbackfunction))) {
                        isexecuted = true;
                        caller = Object.get(window, callbackfunction.split('.').removeLast(1).join('.')) || window;
                        if (Object.isArray(response.responseJSON.methodparameters))
                            func.apply(caller, response.responseJSON.methodparameters.concat([response]));
                        else
                            func.call(caller, (Object.isUndefined(response.responseJSON.methodparameters) ? response.responseJSON : response.responseJSON.methodparameters), response);
                    }
                }
                //call complete method if exists and not executed yet
                if (!isexecuted) {
                    if (response.responseJSON == null || Object.toJSON(response.responseJSON) == "{}")
                        return;
                    if (Object.isFunction(this.Caller.Complete)) {
                        if (Object.isArray(response.responseJSON.methodparameters))
                            this.Caller.Complete.apply(this.Caller, response.responseJSON.methodparameters);
                        else
                            this.Caller.Complete.call(this.Caller, (response.responseJSON.methodparameters || response.responseJSON), response);
                    } else if (Object.isString(response.responseJSON))
                        response.responseJSON.evalScripts();
                    else
                        throw new Error("Callback function "+(callbackfunction ? "(" + callbackfunction + ")" : "")+" cannot be executed.");
                }
            } catch (err) {
                this.ShowError(err);
            }
        }

        //removes current request
        //this.ajaxRequest && (delete this.ajaxRequest);
    },

    //Show a progres message if message object is set
    //NOTE:: the message object should be an Array with two items: 1- signature of mesage type, 2 - parameters (must have property 'host' set)
    StartProgress: function (msg) {
        this.$progressmessage = this.$progressmessage || ( msg ? $progress(msg) : this.Caller.progressmessage);
        if (this.$progressmessage instanceof Utility.Message) {
            this.$progressmessage.Show();
        }
    },

    //Hides a progress message
    StopProgress: function () {
        if (this.$progressmessage instanceof Utility.Message) {
            this.$progressmessage.Close();
            delete this.$progressmessage;
        }
    },

    //Build and returns current url to request based on parameters
    GetUrl: function (method) {
        var url = location.href.replace(/[\?#].*/gi, '').concat(/\/$/gi.test(location.pathname) ? "default.aspx" : "");
        var data = String(method).split('|').filter(Boolean);
        if (data.length > 1) {
            data[0] = data[0] || url;
            data = [data.join("/"), ''];
        } else {
            if (window.pagewebmethodurl)
                data = [window.pagewebmethodurl , data[0]];
            else
                data = [ url , data[0]];

            data[0] = data[0].replace(/\/AjaxMethod$/i, '') + '/AjaxMethod';
        }
        if (location.protocol =='https:')
            data[0] = data[0].replace(/^http:\/\//i, 'https://');

        return data;
    },

    //Updates container passed in with HTML code returned by Web Method
    Update: function (method, parameters, container, progress) {
        try {
            var f;
            this.Title = document.title;
            // the Caller must exists
            if (!this.Caller || !method)
                return false;

            //Call onRequest if exists (Allow to modify parameters before request)
            Object.isFunction(this.Caller.onRequest) && (this.Caller.onRequest.call(this, parameters));

            if (this.stopRequest) {
                this.stopRequest = false;
                return;
            }

            //Show a Progress message if assigned
             this.StartProgress(progress);

            //Assign main properties
            this.parameters = parameters || {};
            if (Object.isFunction(this.parameters.callback)) {
                this.__callback = this.parameters.callback;
                delete this.parameters.callback;
            }
            var url = this.GetUrl(method);
            var opt = {
                method: 'POST',
                webMethod: true, // true, if we need to analize result from .NET web service 
                evalScripts: true,
                contentType: 'application/json',
                postBody: Object.toJSON(Boolean(url[1]) ? { methodName: url[1], data: this.parameters } : { data: this.parameters }),
                onFailure: this.ShowError.bind(this),
                onComplete: this.CompleteRequest.bind(this)
            };
            if (Object.isElement(container))
                //Create an AJAX Updater to update container
                this.ajaxRequest = new Ajax.Updater(container, url[0], opt);
            else
                //Create an AJAX Request
                this.ajaxRequest = new Ajax.Request(url[0], opt);

            //Access denied cannot be catch, so check the case and re-throw the error
            if (this.ajaxRequest.transport.readyState != 1)
                throw new Error('Access denied.');
        } catch (e) {
            this.ShowError(e);
        }

    },

    Fetch: function (method, parameters) {
        this.Update(method, parameters);
    },
    //Checks if Ajax request is created and running
    IsProcessing: function () {
        return this.ajaxRequest && (this.ajaxRequest.transport.readyState > 0 && this.ajaxRequest.transport.readyState < 4);
    },

    IsPending: function () {
        return Ajax.activeRequestCount > 0;
    },

    //Abort ajax request if created.
    //NOTE: After Web Method request has been created only response on Client side can be aborted.
    //On server side a web method still processed. 
    Stop: function () {
        try {
            if (this.ajaxRequest) {
                this.ajaxRequest.abort();
            }
        } catch (e) { };
    }

});
//----------------------------------------------------------------------------------------------------------------------
//Base class
//----------------------------------------------------------------------------------------------------------------------
Utility.Base = Class.create({
    initialize: function (host) {
        //mandatory - DOM container
        // NOTE: this.host might be set before class initializer call ( on a constructor)
        this.host = this.host || $p(host) || $$(host);

        if (Object.isElement(this.host)) {
            this.host.identify();
            //Apply default settings
            if (Object.isFunction(this.defaults)) {
                this.defaults(this.defaults.argumentNames().length ? this.host.getActions() : undefined);
            } else {
                Object.extend(this, this.host.getActions());
            }
        } else if (this.host instanceof Window) {
            Object.isFunction(this.defaults) && Object.extend(this, this.defaults());
        } else if (Object.isArray(this.host) && this.host.length && Object.isElement(this.host[0])) {
            this.host.invoke('identify');
            if (Object.isFunction(this.defaults)) {
                this.defaults();
            }
        }
        if (!this.host)
            throw new Error('Class cannot be instantiated! <' + host + '>');

        var $names = this.initialize.argumentNames().slice(2); //skip $super and host arguments
        var $args = $A(arguments).slice(1);  //skip host argument
        $args.each(function (it, i) {
            var name = i < $names.length ? $names[i] : null;
            if (name != '$super' && name != 'host') {
                if (name) {
                    this[name] = Object.extend(this[name] || {}, Object.cast(it));
                } else if (Object.isString(it) || Object.isDate(it) || Object.isNumber(it) || Object.isArray(it) || Object.isBoolean(it)) {
                    if (!Object.isArray(this.parameters)) this.parameters = [];
                    this.parameters.push(it);
                } else {
                    Object.extend(this, Object.cast(it));
                }
            }
        }, this);

        this.events = {};

        return this;
    },
    run: function (name) {
        if (name) {
            var args = $A(arguments).slice(1);
            //if name is a CSV strip all names and treat each name as a separate function

            var func, caller = name.split('.').removeLast(1).join('.');

            if (Object.isFunction(func = Object.get(this, name))) {
                caller = Object.get(this, caller) || this;
                return func.apply(caller, args);
                //if result has a method property and current object has this method, so invoke that method with patameters (optional property)
            } else if (Object.isFunction(func = Object.get(self, name))) {
                caller = Object.get(self, caller) || self;
                return func.apply(caller, args);
            }
        }
        return undefined;
    },
    on: function (eventName, func, fire) {
        if (!this.host)
            return this;
        !this.events && (this.events = {});
        //Check if Event.Handler passed in as first parameter, add it to event collection
        if (eventName instanceof Event.Handler) {
            func = eventName, eventName = eventName.eventName;
        }

        if (Object.isString(eventName) && func instanceof Event.Handler) {
            this.events[eventName] = func;
        } else if (Object.isString(eventName) && this.events[eventName] instanceof Event.Handler) {
            this.events[eventName].start();
        } else {
            Object.isFunction(func) && (this.events[eventName] = this.host.on(eventName, null, func));
        }
        fire && this.fire(eventName);
        return this;
    },
    off: function () {
        var names = !arguments.length ? $H(this.events).keys() : $A(arguments);
        for (var name in this.events) {
            if (names.include(name)) {
                this.events[name].stop();
                //delete this.events[name];
            }
        }
        return this;
    },
    start: function () {
        var names = !arguments.length ? $H(this.events).keys() : $A(arguments);
        for (var name in this.events) {
            if (names.include(name))
                this.events[name].start();
        }
    },
    stop: function () {
        var names = !arguments.length ? $H(this.events).keys() : $A(arguments);
        for (var name in this.events) {
            if (names.include(name))
                this.events[name].stop();
        }
    },
    fire: function (name) {
        if (!this.host.simulate(name))
            this.host.fire.apply(this.host, arguments);
        return this;
    }
});
//----------------------------------------------------------------------------------------------------------------------
//Generic Class to create selectors with server side business actions 
//----------------------------------------------------------------------------------------------------------------------
Utility.AjaxSelector = new Class.create(Utility.Base, {
    initialize: function ($super) {

        if (!$super.apply(this, $A(arguments).slice(1)))
            return false;

        //Verify and add progress information if needed
        var progress = Object.get(this, 'parameters.progress') || self.executeprogress;
        if (Object.isString(progress))
            this.progressmessage = $loading({ host: this.host, message: progress });
        else if (progress)
            this.progressmessage = self.progressmessage;

        //Add new Web Request object
        this.WebRequest = new Utility.WebMethodRequest(this);

        //Assign an active event to to call server method
        if (Object.isString(this.activeevent)) {
            this.host.noClick();
            this.host[this.activeevent] = this.Call.bindAsEventListener(this);
        }
        return true;
    },

    //Request. Usually uses in OnClick event
    Call: function (e) {
        self.focus();
        this.Request(this.method, this.parameters, this.container);
        return false;
    },

    //Call AJAX WebMethod
    //argumets   method (required) - method to call 
    //           parameters (optional) - parameters (an object) to pass in
    //           updateContainer (optional) - DOM element or and object like {success: #DOMELEMENT, failure: #DOMELEMENT} to update on complete.
    //                                        If it's not exist (or ommited) use AJAX.Request
    Request: function (method, parameters, updateContainer) {
        this.WebRequest.Update(method, parameters, updateContainer);
    }

});


//----------------------------------------------------------------------------------------------------------------------
//Class to add keyboard support to Selector (select item using keyboard : Up, Down, Home, End, Select, Cancel)
//----------------------------------------------------------------------------------------------------------------------
Utility.AjaxDropDown = new Class.create(Utility.AjaxSelector, {
    defaults: function () {
        this.propertynames = ['Text', 'Value', 'Selected'];
        this.parameters = {};
        this.layout = {};
        this.waitmessage = "Loading...";
        this.activeevent = "onclick";
    },
    initialize: function ($super) {

        if (!$super.apply(this, $A(arguments).slice(1)))
            return false;


        if (this.layout.temporary) {
            this.Container = this.host;
            this.Container.addClassName('ajaxselector');
        } else
            this.host.wrap(this.Container = new Element('div', { className: 'ajaxselector' }));

        //Assign a progress message  to the Host
        if (this.waitmessage)
            this.progressmessage = $loading({ host: this.Container, message: this.waitmessage });

        return this;
    },

    //Override. Checks and updates popup container. If there is no items to select close popup with delay
    Complete: function (response) {
        if (response.responseJSON) {
            if (this.Selector)
                this.DestroySelector();
            this.CreateSelector(response.responseJSON);
        }
    },

    //default function to transform option (needs to be overriden on time of Initialization)
    //Item transformation method. 
    //where: 
    //  item - selected DOM element,
    //  highlight- search expression, 
    //  keys - array of property names
    TransformItem: function (item, keys) {
        if (item === 'CREATE') //create
            return null;

        item.$data = $H(item).keys().inject("", function (list, key) {
            list += ' data-parameter-' + key.toLowerCase() + '="' + item[key] + '"';
            return list;
        });
        item.$text = item[keys[0]];
        return ('<div class="item" #{$data}">#{$text}</div>').interpolate(item);
    },


    //Load data and update container,
    // Data can be present in two formats, 
    //  1 - HTML code (formatted text) to update container with
    //  2 - Array of objects
    CreateSelector: function (data) {
        if (!data)
            return false;
        if (!this.Selector)
            //create a popup container
            $p(document.body).insert(this.Selector = new Element("div", { className: "ajaxselector selector" }).setStyle({ position: 'absolute', visibility: 'hidden' }));


        if (Object.isArray(data)) {

            //Calls item transformation to create option container.
            //If not handled, this.container will be used a  a container
            var parent = this.TransformItem.call(this.container, 'CREATE');
            var properties = Object.isArray(this.propertynames) ? this.propertynames : ['Text'];
            //transform and add options to container 
            for (var i = 0, item; item = data[i++];) {
                //call transformation function with two parameters: current item data and text box value 
                item.index = i;
                (parent || this.Selector).insert(this.TransformItem.call((parent || this.Selector), item, properties));
            }
        } else if (Object.isString(data)) {
            this.Selector.update(data);
        }
        if (this.items = this.Selector.select('.item'), this.items.length) {
            this.items.each(function (item, index) {
                item.index = index;
                item.onclick = this.Select.bindAsEventListener(this);
                item.onmouseover = this.Activate.bind(this, item);
            }, this);
        }
        this.ShowSelector();

        return false;
    },

    ShowSelector: function () {
        //set container position
        //        var layout = Object.extend({}, this.layout);
        this.Selector.clonePosition(this.Container, { setWidth: false, setHeight: false });
        this.layout.width = this.layout.width || Math.max(this.Container.measure('width'), this.Selector.measure('width'));
        this.layout.height = this.layout.height || Math.min(this.Selector.measure('border-box-height'), this.items[0].measure('border-box-height') * this.items.length);
        this.layout.minwidth = this.layout.minwidth || this.layout.width;
        this.layout.minheight = this.layout.minheight || this.layout.height;
        this.layout.top = this.layout.top || (this.Selector.measure('top') + document.viewport.getScrollOffsets().top);

        this.Selector.setStyle({
            top: this.layout.top + 'px',
            width: this.layout.width + 'px',
            height: this.layout.height + 'px',
            'min-width': this.layout.minwidth + 'px',
            'min-height': this.layout.minheight + 'px',
            visibility: 'visible'
        });

        //Add event handlers
        this.on(":exit", this.DestroySelector.bind(this));
        this.on(":select", this.Select.bindAsEventListener(this));
        this.on(":move", this.Move.bindAsEventListener(this));
        this.on(document.on("keyup", this.KeyPress.bindAsEventListener(this)));
        this.on(document.on("mousedown", this.Close.bindAsEventListener(this)));
        this.on(Event.on(self, "mousewheel", this.Close.bindAsEventListener(this)));
        this.on(Event.on(self, "resize", this.Close.bindAsEventListener(this)));

        Object.isFunction(this.onShow) && (this.onShow(this.host, this.container));

        if (Object.isArray(this.items) && this.items.length)
            this.Activate(this.items.first());

    },

    setFocus: function (index) {
        if (Object.isArray(this.items) && this.items.length && (this.items.length > index))            
            this.Activate(this.items[index], true);
    },

    DestroySelector: function (item) {
        //destroy event handlers
        this.off();

        Object.isFunction(this.onHide) && (this.onHide());

        //destroy content and item collections
        this.Selector.remove();

        if (this.layout.temporary) {
            this.Container.removeClassName('ajaxselector');
        }
        delete this.items;
        delete this.Selector;

    },

    Select: function (e) {
        Event.stop(e);
        if (!Object.isArray(this.items) && Object.isUndefined(this.ActiveIndex))
            return false;
        var item = this.items[this.ActiveIndex];
        var data = item.getActions();
        //call onselect event. 
        //NOTE.If data is Empty object, you can analize selected item by using 'this'
        Object.isFunction(this.onSelect) && (this.onSelect.call(this, this, item, data));
        //close selector
        this.fire(':exit');
    },

    Activate: function (item, addExtraBottomPadding) {
        if (!item || Object.isUndefined(item.index)) {
            return false;
        }

        if (this.ActiveIndex == item.index)
            return true;

        var prevIndex = this.ActiveIndex || 0;
        this.items[prevIndex].removeClassName("over");
        this.items[this.ActiveIndex = item.index].addClassName("over");

        !this.items[this.ActiveIndex]._height && (this.items[this.ActiveIndex]._height = this.items[this.ActiveIndex].getHeight());
        //we need to calculate top and bottom pixels of active item
        var top = this.items[this.ActiveIndex]._height * this.ActiveIndex;
        var bottom = this.items[this.ActiveIndex]._height * (this.ActiveIndex + 1);

        //do scrolling to make item visible
        if (top < this.Selector.scrollTop)
            this.Selector.scrollTop = Math.max(this.Selector.scrollTop - top, 0);
        else if (bottom > this.Selector.scrollTop + this.Selector.getHeight())
        {
            if (addExtraBottomPadding) {
                // web-117 added extra padding to display focuses item on top
                var units = Math.ceil(this.Selector.getHeight() / this.items[this.ActiveIndex]._height);
                this.Selector.scrollTop = Math.max(bottom - this.Selector.getHeight() + (this.items[this.ActiveIndex]._height * (units - 3)), 0); 
            }
            else
                this.Selector.scrollTop = Math.max(bottom - this.Selector.getHeight(), 0);
        }

    },

    Close: function (e) {
        var el = Event.element(e);
        if (!Object.isElement(el) || (el !== this.Selector && !el.descendantOf(this.Selector) && el !== this.host)) {
            this.fire(":exit");
        }
    },

    Move: function (e) {
        Event.stop(e);

        if (!e || Object.isUndefined(e.memo) || Object.isUndefined(e.memo.index) || !this.items.length)
            return false;

        index = e.memo.index;

        //if string - consider it as item position
        if (Object.isString(index)) {
            if (index == 'BOTTOM')
                index = this.items.length - 1;
            else // 'TOP' or other
                index = 0;
        } else if (Object.isNumber(index)) {
            if (Object.isUndefined(this.ActiveIndex))
                index = index > 0 ? 0 : this.items.length - 1;
            else
                index = this.ActiveIndex + index;
        }

        if (index >= 0 && index < this.items.length)
            this.Activate(this.items[index]);
    },

    //Handle Key Press event (IE specific) 
    KeyPress: function (e) {

        var code = ((e = e || event), e.keyCode || e.which || e.charCode);
        Event.stop(e);
        switch (code) {
            case Event.KEY_ESC:
                this.fire(":exit");
                return false;
            case Event.KEY_RETURN:
                this.fire(":select");
                return false;
            case Event.KEY_UP:
            case Event.KEY_DOWN:
            case Event.KEY_HOME:
            case Event.KEY_END:
                return !Prototype.Browser.IE;
        }
        return false;
    }
});


Utility.Actions = Class.create(Utility.Base, {
    SetActions: function(rule) {
        this.host.select(rule || '[data-action]').each(function(it) {
            if (!it.$click) {
                it.data = it.getActions({ cast: true });
                it.noClick().on('click', function(e) {
                    e.stop();
                    this.DoAction(it.data.action.toLowerCase(), it.data, it);
                }.bindAsEventListener(this));
            }
        }, this);
    },
    DoAction: function(action, it, element) {
    }
});

Utility.Callback = new Class.create(Utility.Base, {
    initialize: function ($super, signature, init) {
        if (!$super.apply(this, $A(arguments).slice(1)))
            return false;
        this.signature = signature;

        // init might play a role of WebForm_InitCallback
        Object.isFunction(init) && (init());
    },
    DoAssign: function () {
        this.controls.each(function (it) {
            $$(it.selector).first().on('click', this.RequestWithParam.bindAsEventListener(this, it.prms));
        }, this);
    },
    RequestWithParam: function (e, prms) {
        e.stop();
        if (!Object.isString(prms))
            prms = Object.toJSON(prms);
        return this.Request(prms);
    },
    Request: function (prms) {
        WebForm_DoCallback && WebForm_DoCallback(this.signature, prms || '', this.Callback.bind(this), '', null, false);
        return false;
    },
    Callback: function (result) {
        try {
            if (String(result).isJSON()) {
                result = result.evalJSON();
                this.run(result.method, result.parameters);
            } else {
                alert(result);
            }
        } catch (e) {
            alert(e.message);
        }
    }
});


Utility.Redirect = function (url, w) {
    w = w && w.location ? w : self;
    if (url && Object.isString(url))
        w.location.href = url;
/*
    else if (url == "")
        w.location.href = w.location.href;
*/
    else //if (url == "")
        w.location.reload(true);

};

Utility.RedirectNewWindow = function (url, w) {
    w = w && w.location ? w : self;
    if (url && Object.isString(url))
        w.open(url, '_blank')
    else
        w.location.reload(true);

};

Ajax.JSONRequest = Class.create(Ajax.Base, (function () {
    var head = document.getElementsByTagName('head')[0];
    return {
        initialize: function ($super, url, id, options) {
            $super(options);
            this.options.url = url;
            this.options.id = id;
            this.options.callbackParamName = this.options.callbackParamName || 'callback';
            this.options.timeout = this.options.timeout || 10000; // Default timeout: 10 seconds
            this.responseJSON = {};
            this.request();
        },

        request: function () {
            // Add callback as a parameter and build request URL
            if (Object.isString(this.options.parameters))
                this.options.parameters = this.options.parameters.toQueryParams();

            //Check ID and create a name for callback function
            if (this.options.id) {
                this._name = '_prototypeJSONPCallback_' + (this.options.id ? String(this.options.id) : '');
                this.options.parameters[this.options.callbackParamName] = this._name;
            }
            this.options.parameters = Object.toQueryString(this.options.parameters);
            this.options.url = this.options.url + (this.options.parameters ? ((this.options.url.include('?') ? '&' : '?') + this.options.parameters) : '');
            this.script = new Element('script', { type: 'text/javascript', src: this.options.url });

            if (this.options.id) {
                this.timeout = setTimeout(function () {
                    this.cleanup();
                    if (Object.isFunction(this.options.onFailure)) {
                        this.options.onFailure.call(this, this);
                    }
                }.bind(this), this.options.timeout);

                // Define callback function
                window[this._name] = function (json) {
                    this.cleanup(); // Garbage collection
                    if (Object.isFunction(this.options.onComplete)) {
                        this.responseJSON = json;
                        this.options.onComplete.call(this, this);
                    }
                }.bind(this);
            }

            if (Object.isFunction(this.options.onCreate)) {
                this.options.onCreate.call(this, this);
            }
            if (!$$('head [src="' + this.options.url + '"]').first())
                head.insert(this.script);

        },
        cleanup: function () {
            window[this._name] = undefined;
            if (this.timeout) {
                clearTimeout(this.timeout);
                this.timeout = null;
            }
            if (this.script && Object.isElement(this.script)) {
                this.script.remove();
                this.script = null;
            }
        }
    };
})());




//----------------------------------------------------------------------------------------------------------------------
//Class to show progress on image load
//----------------------------------------------------------------------------------------------------------------------
Utility.ImageLoader = Class.create({
    initialize: function (img, settings) {
        //duration should be in seconds
        this.settings = Object.extend({ period: 0.1, duration: 3, loading: 'loading', max: 140 }, settings);

        if (this.img = $p(img), !this.img || this.img.tagName != 'IMG' || !this.settings.progress)
            return false;

        this.dim = this.img.up().getDimensions();
        this.settings = Object.extend(this.settings, this.img.getActions());
        this.settings.duration *= 10;

        this.Run();
    },

    //Starts counter, checks time every second and update DOM element
    Run: function () {
        if (this.settings.duration > 0) {
            this.img.hide();
            this.timer = new PeriodicalExecuter(function (pe) {
                try {
                    if (this.IsReady()) {
                        //reached zero, stop executer
                        pe.stop();
                        this.Update();
                    } else if (--this.settings.duration <= 0) {
                        throw new Error('stop');
                    } else {
                        this.ShowProgress();
                    }

                    //not reached yet
                } catch (e) {
                    // Stops executer in case of error
                    pe.stop();
                    this.Cancel();
                }
            }.bind(this), this.settings.period); // execute funcion on behalh of current object every second
        }
        return this;
    },
    IsReady: function () {
        return this.img.complete && this.img.getWidth() > 0 && this.img.getHeight() > 0;

    },
    ShowProgress: function () {
        if (!this.loading) {
            this.img.insert({ before: this.loading = new Element('img', { src: this.settings.progress, className: this.settings.loading }) });

        }
    },

    Resize: function () {
        //do not resize if settings are not specified
        this.settings.width = String(this.settings.width).toNumber();
        this.settings.height = String(this.settings.height).toNumber();

        if (!this.settings.width && !this.settings.height)
            return;

        //get image dimensions
        var dim = this.img.getDimensions();
        //calculate aspect ratio
        var ratio = dim.height / dim.width;
        //if Height is requested 
        if (this.settings.height > this.settings.width && this.settings.width == 0)
            this.settings.width = this.settings.height * 1 / ratio;
            //if width is requested 
        else if ((this.settings.width >= this.settings.height && this.settings.height == 0))
            this.settings.height = this.settings.width * ratio;

        this.img.setStyle({ width: Math.round(this.settings.width) + 'px', height: Math.round(this.settings.height) + 'px' });
    },

    HideProgress: function () {
        this.loading && (this.loading.remove());
        if (!this.settings || !this.settings.hideImage) //sprte - 7514 removing forced inheritance when ImageLoader is loaded
            this.img.setStyle('visibility:inherit').show();        
    },

    Update: function () {
        this.HideProgress();
        this.Resize();
    },

    Cancel: function () {
        this.HideProgress();

    }
});

//Control's Bubble Tip  (like the element title)
Utility.BubbleTip = Class.create(Utility.Base, {
    defaults: function (o) {
        return this.options = Object.extend({
            css: 'bubbletip',
            gravity: 'se',
            offset: 0,
            opacity: 1,
            trigger: 'hover'
        }, o);
    },
    initialize: function ($super, host, options) {
        try {
            if (!$super.apply(this, $A(arguments).slice(1)))
                return false;

            if (this.getValue()) {
                this.on(this.options.trigger == 'hover' ? 'mouseenter' : 'focus', this.enter.bindAsEventListener(this));
                this.on(this.options.trigger == 'hover' ? 'mouseleave' : 'blur', this.leave.bindAsEventListener(this));

                if (this.host.readAttribute('bubbletip') || this.options.bubbletip)
                    this.host.addClassName(this.options.css);
                //this.on('mousedown',  this.toggle.bindAsEventListener(this));

            }

            return this;

        }
        catch (e) {

        }
    },
    show: function (e) {
        if (!(this.content = this.getValue()))
            return;
        if (this.disabled)
            return;

        this.host.writeAttribute('title', null);
        if (!this.tooltip) {
            $p(document.body).insert(this.tooltip = new Element("div", { className: 'ps-tip' }).update('<div class="ps-tip-arrow"></div><div class="ps-tip-inner">#{content}</div></div>'.interpolate(this)));
            this.tooltip.setStyle({ top: 0, left: 0, visibility: 'hidden', display: 'block' });
        }
        var host = Object.extend(this.host.cumulativeOffset(), this.host.getDimensions());
        var tip = Object.extend(this.tooltip.getDimensions(), { opacity: this.options.opacity });
        var gravity = this.options.gravity;
        var i = 0;
        do {
            i++;
            switch (gravity) {
                case 'se':
                    tip = Object.extend(tip, { top: host.top + host.height / 2, left: host.left + host.width + this.options.offset });
                    break;
                case 'sw':
                    tip = Object.extend(tip, { top: host.top + host.height / 2, left: host.left - tip.width - this.options.offset });
                    break;
                case 'ne':
                    tip = Object.extend(tip, { top: host.top - tip.height, left: host.left + host.width + this.options.offset });
                    break;
                case 'nw':
                    tip = Object.extend(tip, { top: host.top - tip.height, left: host.left - host.width - this.options.offset });
                    break;
            }
            var b = tip.left > 0 && (tip.left + tip.width) < $Screen.viewport.width && tip.top > 0 && (tip.top + tip.height) < $Screen.viewport.width;
            if (!b) {
                if (gravity == 'se') gravity = 'sw';
                else if (gravity == 'sw') gravity = 'ne';
                else if (gravity == 'ne') gravity = 'nw';
                else gravity = 'se';
            }
            if (i == 5) gravity = this.options.gravity;

        } while (!b && i < 5)
        this.tooltip.setStyle("top:#{top}px;left:#{left}px".interpolate(tip)).addClassName('ps-tip-' + gravity);
        this.tooltip.setStyle({ visibility: 'visible', opacity: this.options.opacity });

    },
    getValue: function () {
        return unescape((this.host.readAttribute('title') || this.host.readAttribute('bubbletip') || this.options.title || this.options.bubbletip || this.content || '').replace(/(^\s*|\s*$)/, ""));
            
        ;
    },
    setValue: function (text) {
        if (!text) return;
        if (this.content = String(text).replace(/(^\s*|\s*$)/, "")) {
            if (this.host.readAttribute('bubbletip') || this.options.bubbletip)
                this.host.addClassName(this.options.css);
            this.host.writeAttribute('title', null);
        }
    },
    hide: function () {
        if (!this.tooltip)
            return;
        this.tooltip.remove();
        delete this.tooltip;
    },
    enter: function (e) {
        if (e.shiftKey)
            return;
        this.show(e);
    },
    leave: function (e) {
        this.hide();
    },
    toggle: function (e) {
        if (!e.ctrlKey) return;
        (this.disabled = !Boolean(this.disabled)) ? this.hide() : this.show();
    }
});

Utility.RGB = {
    parse1: function (r, g, b, a) {
        if (arguments.length == 1) {
            var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(String(arguments[0]));
            Object.extend(this, result ? {
                R: parseInt(result[1], 16),
                G: parseInt(result[2], 16),
                B: parseInt(result[3], 16)
            } : {});
        } else {
            this.R = String(r).toNumber();
            this.G = String(g).toNumber();
            this.B = String(b).toNumber();
            this.A = parseFloat(String(a));
        }

    },
    parse: function (r, g, b, a) {
        if (arguments.length == 1 && Object.isString(arguments[0])) {
            var color = String(arguments[0]).replace(/\s\s*/g, '');// Remove all spaces
            var cache, p = parseInt; // Use p as a byte saving reference to parseInt

            // Checks for 6 digit hex and converts string to integer
            if (cache = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})/.exec(color))
                cache = [p(cache[1], 16), p(cache[2], 16), p(cache[3], 16)];

                // Checks for 3 digit hex and converts string to integer
            else if (cache = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])/.exec(color))
                cache = [p(cache[1], 16) * 17, p(cache[2], 16) * 17, p(cache[3], 16) * 17];

                // Checks for rgba and converts string to
                // integer/float using unary + operator to save bytes
            else if (cache = /^rgba\(([\d]+),([\d]+),([\d]+),([\d]+|[\d]*.[\d]+)\)/.exec(color))
                cache = [+cache[1], +cache[2], +cache[3], +cache[4]];

                // Checks for rgb and converts string to
                // integer/float using unary + operator to save bytes
            else if (cache = /^rgb\(([\d]+),([\d]+),([\d]+)\)/.exec(color))
                cache = [+cache[1], +cache[2], +cache[3]];

            // Performs RGBA conversion by default
            cache && isNaN(cache[3]) && (cache[3] = 1);

            return cache;
        } else {
            return [String(r).toNumber(), String(g).toNumber(), String(b).toNumber(), parseFloat(String(a))];
        }
            
    },
    toHex: function () {
        var cache = this.parse.apply(this, $A(arguments));
        //return ("#" + this.R.toColorPart() + this.G.toColorPart() + this.B.toColorPart()).toUpperCase();
        return cache && cache[3] > 0 ? ("#" + cache[0].toColorPart() + cache[1].toColorPart() + cache[2].toColorPart()).toUpperCase() : 'transparent';
    },
    toRGB: function () {
        var cache = this.parse.apply(this, $A(arguments));
        return cache && cache[3] > 0 ? "rgb(#{0},#{1},#{2})".interpolate(cache) : 'transparent';
    },
    toRGBA: function () {
        var cache = this.parse.apply(this, $A(arguments));
        return cache ? "rgba(#{0},#{1},#{2},#{3})".interpolate(cache) : 'transparent';
    },
    toContrast: function () {
        var cache = this.parse.apply(this, $A(arguments));
        return cache ? ((((cache[0] * 299) + (cache[1] * 587) + (cache[2] * 114)) / 1000 >= 128) ? '#000' : '#FFF') : "#000";
    }
};
//----------------------------------------------------------------------------------------------------------------------
//Class to create four time of messages:
//  - Alert - shows a Dialog window with one button (closes window)
//  - Confirm - shows a Dialog with confirm message and two choices: Yes or No
//  - Dialog - shows a Dialog with  a close button on header
//  - Progress - shows custom progress message (in a center of the window) and disables all client actions
//  - Loading - shows a custom progress message linked to given DOM element
//----------------------------------------------------------------------------------------------------------------------
Utility.Message = Class.create({
    defaults: function () {
        return {
            overlay: true,
            ok: 'OK',               // text for the OK button
            cancel: 'Cancel',       // text for the Cancel button
            controls: true          //allows to show/hide controls (Ok,Cancel) if presented
            //omitted acceptable parameters:
            //radius: true,
            //shadow:true,
            //css                // use custom CSS to modify the layout
            //callback,             //a call back function on closing message
            //width,
            //height
        };
    },
    initialize: function (type, options) {

        //validate type 
        if (!(['alert', 'confirm', 'dialog', 'progress', 'popup', 'loading'].include(this.type = type)))
            return false;
        this.events = [];
        //Load and validate options
        this.options = Object.extend(this.defaults(), Object.isString(options.message) ? options : Object.extend(options, options.message));
        //host
        this.options.host = this.options.host || self;
        //title
        this.options.title = this.options.title || this.type.capitalize();
        //message
        this.options.message = this.options.isHTML ? (this.options.message || '') : (this.options.message || '').replace(/\n/g, '<br />');
        /*
                this.options.message = (this.options.message || '');
                this.options.isHTML &&  (this.options.message = this.options.message.replace(/\n/g, '<br/>'));
        */
        //a callback
        this.options.callback = Object.isFunction(this.options.callback) ? this.options.callback : Prototype.emptyFunction;

        if (Object.isFunction(this[this.options.auto]))
            return this[this.options.auto].apply(this);
        return this;
    },
    HTMLk8: function (data) {
        switch (this.type) {
            case 'alert':
                return ('<div class="message-header-k8"><span class="message-title-k8">#{title}</span><span class="message-tools-k8"><span data-action="discard" class="close-k8"></span></span></div>' +
		                '<div class="message-body-k8"><div class="message-k8"></div></div>' +
                        '<div class="message-controls-k8"><button data-action="ok" class="ModuleBtn ModuleBtnGray" data-default="true">#{ok}</button</div>').interpolate(data);
            case 'confirm':
                return ('<div class="message-header-k8"><span class="message-title-k8">#{title}</span><span class="message-tools-k8"><span data-action="discard" class="close-k8"></span></span></div>' +
		                '<div class="message-body-k8"><div class="message-k8"></div></div>' +
 		                 '<div class="message-controls-k8"><button data-action="ok" class="ModuleBtn ModuleBtnGray" data-default="true">#{ok}</button>&nbsp;&nbsp;<button data-action="cancel" class="ModuleBtn ModuleBtnGray">#{cancel}</button></div>').interpolate(data);
            case 'dialog':
                return ('<div class="message-header-k8"><span class="message-title-k8">#{title}</span><span class="message-tools-k8"><span data-action="cancel" data-default="true" class="close-k8"></span></span></div>' +
		                '<div class="message-body-k8"><div class="message-k8"></div></div>' +
                        '<div class="message-controls-k8"><button data-action="ok" class="ModuleBtn ModuleBtnGray" data-default="true">#{ok}</button</div>').interpolate(data);
            case 'popup':
                return ('<div class="message-header-k8"><span class="message-title-k8">#{title}</span><span class="message-tools-k8"><span data-action="cancel" data-default="true" class="close-k8"></span></span></div>' +
                        '<div class="message-body-k8"><div class="message-k8"></div></div>' +
                        '<div class="message-controls-k8"><button data-action="ok" class="ModuleBtn ModuleBtnGray" data-default="true">#{ok}</button</div>').interpolate(data);
            case 'progress':
                return ('<div class="message-body-k8"><div class="message-k8"></div></div>');//.interpolate(data);

            case 'loading':
            default:
                return '<div class="message-k8">#{message}</div>';//.interpolate(data);
        }
    },
    HTML: function (data) {
        switch (this.type) {
            case 'alert':
                return ('<div class="message-header"><span>#{title}</span></div>' +
		                '<div class="message-body"><div class="message"></div></div>' +
                        '<div class="message-controls"><button data-action="ok" data-default="true">#{ok}</button</div>').interpolate(data);
            case 'confirm':
                return ('<div class="message-header"><span>#{title}</span><span class="message-tools"><span data-action="discard" class="close"></span></span></div>' +
		                '<div class="message-body"><div class="message"></div></div>' +
 		                 '<div class="message-controls"><button data-action="ok" data-default="true">#{ok}</button>&nbsp;&nbsp;<button data-action="cancel">#{cancel}</button></div>').interpolate(data);
            case 'dialog':
                return ('<div class="message-header"><span>#{title}</span><span class="message-tools"><span data-action="cancel" data-default="true" class="close"></span></span></div>' +
		                '<div class="message-body"><div class="message"></div></div>' +
                        '<div class="message-controls"><button data-action="ok" data-default="true">#{ok}</button</div>').interpolate(data);
            case 'popup':
                return ('<div class="message-header"><span>#{title}</span><span class="message-tools"><span data-action="cancel" data-default="true" class="close"></span></span></div>' +
                        '<div class="message-body"><div class="message"></div></div>' +
                        '<div class="message-controls"><button data-action="ok" data-default="true">#{ok}</button</div>').interpolate(data);
            case 'progress':
                return ('<div class="loader loader-general"><i class="icon-spinner spinning cms-icon-100 loader-icon" aria-hidden="true"></i><span class="loader-text">Loading</span></div>');//.interpolate(data);

            case 'loading':
            default:
                return '<div class="message">#{message}</div>';//.interpolate(data);
        }
    },
    Create: function () {
        var messageboxclass = this.options.k8 ? 'message-box-k8 ' : 'message-box ';
        var messageheaderclass = this.options.k8 ? '.message-header-k8 ' : '.message-header';
        var messagebodyclass = this.options.k8 ? '.message-body-k8 ' : '.message-body';
        var messageclass = this.options.k8 ? '.message-k8 ' : '.message';
        var messagecontrolsclass = this.options.k8 ? '.message-controls-k8 ' : '.message-controls';


        if (this.options.keep !== true)
            this.Remove();

        if (this.type != 'progress')
            $p(document.body).insert(this.container = new Element("div", { 'class': messageboxclass + this.type }));
        else 
            $p(document.body).insert(this.container = new Element("div", { 'class': 'cms-bootstrap'}));        

        this.options.width = this.Calculate(this.options.width, $Screen.viewport.width);
        this.options.height = this.Calculate(this.options.height, $Screen.viewport.height);

        if (Object.isNumber(this.options.width)) {
            this.container.setStyle({ width: this.options.width + 'px' });
        }
        if (Object.isNumber(this.options.height)) {
            this.container.setStyle({ height: this.options.height + 'px' });
        }
        if (this.options.k8) {
            this.container.update(this.HTMLk8(this.options)).addClassName(this.options.css || '').setStyle("position:fixed;padding:0;margin:0;");
        } else {
            this.container.update(this.HTML(this.options)).addClassName(this.options.css || '').setStyle("position:fixed;padding:0;margin:0;");
        }
        
        this._overlay = this.container.wrap('div', { 'class': 'message-box-overlay' }).setStyle("position:fixed;top:0px;left:0px;width:100%;height:100%;visibility:hidden;");
        this.header = this.container.down(messageheaderclass);
        if (this.type != 'progress') {
            this.messagebody = this.container.down(messagebodyclass);
            this.messagecontainer = this.container.down(messageclass);
        } else {
            this.messagebody = this.container.down('.loader');
            this.messagecontainer = this.container.down('.loader-text');
        }

        this.messagecontrols = this.container.down(messagecontrolsclass);
        if (this.messagecontrols) {
            !this.options.controls && (this.messagecontrols.remove(), this.messagecontrols = undefined);
        }

        this.Update({ message: this.options.message });
        this.container.select('[data-action]').each(function (it) {
            it.on('mousedown', this.DoAction.bind(this));
        }, this);
        if (!this.options.defaultAction && (this.options.defaultAction = this.container.select('[data-default]').first(), this.options.defaultAction))
            this.options.defaultAction = this.options.defaultAction.getActions().action;

        if ('loading' == this.type) {
            this._overlay.setStyle('background-color:transparent;');
        }

        this.options.radius && this.container.addClassName('-radius');
        this.options.shadow && this.container.addClassName('-shadow');
        
        // Make draggable. Note:: requires draggables library
        if (this.header && Utility.Drag && !this.options.disabledrag) {
            try {
                new Utility.Drag(this.container, { handle: this.header.setStyle({ cursor: 'move' }) });
            } catch (e) { }
        }
        this.options.host.__message = this;
        return this;
    },
    Update: function (opt) {
        if (Object.isUndefined(opt)) return;
        opt.title && this.container.down('.message-header span').update(opt.title);
        opt.message && this.messagecontainer.update(opt.message);
        return this;
    },
    Indicate: function () {
        if (this.type == 'progress') {
            var max = 40;
            var start = 0;
            var el = this.messagebody;
            if (el) {
                this.events.push(new PeriodicalExecuter(function (pe) {
                    try {
                        el.setStyle('background-position:#{0}px #{1}px'.interpolate([start, 0]));
                        start += start == max ? start * -1 : 1;
                    } catch (e) {
                        pe.stop();
                    }
                }, 0.01));
            }
        }
        return this;
    },
    Show: function (opt) {

        if (self.progressmessage instanceof Utility.Message && self.progressmessage.IsShown())
            self.progressmessage.Hide();

        if (Object.isNumber(opt))
            this.Hide.bind(this).delay(opt);
        else if (opt)
            this.Update(opt);
        if (this.options.host.__message !== this)
            this.Create();

        this.Reposition().Indicate().AddEventHandlers();

        self.focus();
        return this;
    },
    IsShown: function () {
        return this._overlay && this._overlay.getStyle('visibility') != 'hidden';
    },
    Hide: function () {
        this.Close();
    },
    Close: function (f) {
        //removes all events
        this.RemoveEventHandlers();
        if (this.options.keep === true) {
            this._overlay.setStyle('visibility:hidden');
            return this;
        }
        if (this.container) {
            Object.isFunction(this.onHide) && this.onHide();

            this.container.remove();
            delete this.container;
        }
        if (this._overlay) {
            this._overlay.remove();
            delete this._overlay;
        }

        //remove handler for current message
        this.options.host.__message = undefined;
        return this;
    },
    DoAction: function (e) {
        var data = e.element().getActions();
        if (e.keyCode == Event.KEY_RETURN) {
            data.action = this.options.defaultAction;
        } else if (e.keyCode == Event.KEY_ESC) {
            data.action = 'cancel';
        }
        switch (data.action) {
            case 'ok':
                this.Close();
                if (this.options.callback !== Prototype.emptyFunction)
                    this.options.callback.apply(this, [e.element(), this.messagecontainer]);
                else {
                    //NOTE: issue with Firefox and .simulate() 
                    //                    Object.isElement(this.options.host) && this.options.host.simulate('click');
                    if (Object.isElement(this.options.host) && Object.isFunction(this.options.host.onclick))
                        this.options.host.onclick();
                }
                break;
            case 'cancel':
                this.Close();
                this.options.callback.apply(this, [false, this.messagecontainer]);
                break;
            case 'discard':
                this.Close();
                this.options.callback.apply(this, [undefined, this.messagecontainer]);
                break;
            default:
                return;
        }
    },

    //Closes previous message and removes existing reference on it
    Remove: function () {
        // Hide stored message if exists
        if (this.options.host.__message)
            this.options.host.__message.Close();
    },

    Calculate: function (value, max) {

        //check width and height properties
        if (Object.isString(value) && value) {
            value = Math.ceil((String(value).indexOf('%') > 0 ? max / 100 : 1) * value.toNumber());
        }
        if (Object.isNumber(value))
            return value;
        return undefined;
    },


    //Maintain position of container
    Reposition: function () {
        this._overlay.setStyle('visibility:visible');
        var pos = {
            set: Object.isNumber(this.options.top) && Object.isNumber(this.options.left),
            top: String(this.options.left).toNumber(),
            left: String(this.options.top).toNumber()
        };

        if (this.type == 'loading') {
            if (this.options.host) {
                pos.set = false;
                if (!this.options.host.getWidth()) {
                    this.container.hide();
                } else {
                    this.container.clonePosition(this.options.host, { offsetLeft: pos.left, offsetTop: pos.top });
                }
            }
        } else {

            var dim = document.viewport.getDimensions();
            pos = {
                set: true,
                top: pos.set ? pos.top : Math.max(((dim.height - this.container.getHeight()) / 2), 0),
                left: pos.set ? pos.left : Math.max((dim.width - this.container.getWidth()) / 2, 0)
            };
        }
        if (pos.set)
            this.container.setStyle("top:#{top}px;left:#{left}px".interpolate(pos));

        if (!(/loading|progress/gi.test(this.type))) {
            var dim = {
                containerHeight: this.container.measure('padding-box-height'),
                messagebodyHeight: this.messagebody.measure('padding-box-height'),
                maxHeight: $Screen.viewport.height * .75,
                maxWidth: $Screen.viewport.width * .75,
                headerHeight: this.header ? this.header.getHeight() : 0,
                footerHeight: this.messagecontrols ? this.messagecontrols.getHeight() : (this.options.radius ? 15 : 0),
            };
            this.messagebody.setStyle({
                height: Math.min((this.options.height ? (this.options.height - dim.headerHeight - dim.footerHeight) : dim.messagebodyHeight), dim.maxHeight) + 'px',
                marginBottom: dim.footerHeight + 'px'
            });
            this.messagecontainer.setStyle({
                height: Math.min((this.options.height ? (this.options.height - dim.headerHeight - dim.footerHeight - 5) : this.messagebody.clientHeight - 25), this.messagebody.clientHeight - 25) + 'px',
            });
        }
        return this;
    },

    KeyDown: function (e) {
        if ((['alert', 'confirm', 'dialog'].include(this.type) && (e.keyCode == Event.KEY_RETURN || e.keyCode == Event.KEY_ESC)) ||
            (/progress/.test(this.type) && e.ctrlKey && e.keyCode == Event.KEY_BACKSPACE)) {
            Event.stop(e);
            this.DoAction(e);
        }
    },

    AddEventHandlers: function () {
        this.events.push(Event.on(top, "resize", this.Reposition.bind(this)));
        if (this._overlay) {
            this.events.push(Event.on(document, "keydown", this.KeyDown.bindAsEventListener(this)));
            if (this.options.overlay) {
                this.events.push(Event.on(this._overlay, "mousedown", this.Deny.bindAsEventListener(this)));
                this.events.push(Event.on(this.container, "mousewheel", this.Deny.bindAsEventListener(this)));
                this.events.push(Event.on(document, "mousewheel", this.Deny.bindAsEventListener(this)));
            }
        }
        return this;
    },
    Deny: function (e) {
        if (e && e.element() === this._overlay) {
            e.stop();
            return false;
        }
    },
    RemoveEventHandlers: function () {
        $A(this.events).invoke('stop');
    }
});

Utility.Drag = Class.create(Utility.Base, {
    initialize: function ($super, host, options) {
        if (!$super.apply(this, $A(arguments).slice(1)))
            return false;
        //host must be absolute/fix positioned
        this.options.handle.on('mousedown', this.Start.bindAsEventListener(this));
    },

    Start: function (e) {
        this.left = this.host.offsetLeft;
        this.top = this.host.offsetTop;
        this.X = e.clientX;
        this.Y = e.clientY;
        this.host.addClassName('dragged');
        this.on(Event.on(document, 'mousemove', this.Drag.bindAsEventListener(this)));
        this.on(Event.on(document, 'mouseup', this.Release.bindAsEventListener(this)));
        return false;
    },
    Drag: function (e) {

        this.Set(e.clientX - this.X, e.clientY - this.Y);
        return false;
    },
    Set: function (dx, dy) {
        /*
                var left = Math.min(Math.Max(this.left + dx, 0), $Screen.viewport.width);
                var y = Math.min(Math.Max(e.clientY - this.Y, 0), $Screen.viewport.height);
        */

        this.host.setStyle({ left: (this.left + dx) + 'px', top: (this.top + dy) + 'px' });
    },
    Release: function () {
        this.host.removeClassName('dragged');
        this.off();
    }
});

Utility.Device = Class.create({
    initialize: function () {
        this.isTouchDevice = 'ontouchstart' in window;
        this.EventName = function (opt) {
            return opt ? { 'click': 'click', 'start': 'touchstart', 'move': 'touchmove', 'end': 'touchend', 'resize': 'orientationchange' }
                    : { 'click': 'click', 'start': 'mousedown', 'move': 'mousemove', 'end': 'mouseup', 'resize': 'resize' };
        }(Boolean('ontouchstart' in window));
        Event.observe(window, this.EventName['resize'], this.Refresh.bindAsEventListener(this));
        this.viewport = this.GetViewPort();
    },
    GetViewPort: function () {
        var dim = document.viewport.getDimensions();
        if (!dim.width || !dim.height)
            dim = {
                width: window.innerWidth, //Math.max(Math.max(document.documentElement.scrollWidth, document.body.scrollWidth), document.documentElement.clientWidth),
                height: window.innerHeight //Math.max(Math.max(document.documentElement.scrollHeight, document.body.scrollHeight), document.documentElement.clientHeight)
            };
        return dim;
    },
    GetVisibleArea: function () {
        var offsets = document.viewport.getScrollOffsets();
        var dim = document.viewport.getDimensions();
        return {
            top: offsets.top,
            bottom: offsets.top + dim.height,
            left: offsets.left,
            right: offsets.left + dim.width
        };
    },
    IsFullyVisible: function (layout) {
        var d = this.GetVisibleArea();
        layout = Object.isElement() ? e.getLayout().toObject() : layout;
        var b = ((layout.top >= d.top) && ((layout.top + layout.height) <= d.bottom));
        return b;// || isAppearingBottom || isAppearingTop;
    },
    Refresh: function (e) {
        this.viewport = this.GetViewPort();
        document.fire('::refresh', this.viewport);
    }
});


///*  
//This function should be assigned in inline HTML code. In order to show custom Confirm 
//before real action (usually placed in  "onclick" event) we need to call this method on event earier, 
//like "onmousedown".
//Note: A javascript call used on "onmousedown" should ended with "return false;" to prevent event bubbling (call onclick)
//NOTE: In all shortcuts arguments can be a List of arguments or an object with specified properties

//Example:
//<a href='javascript:__doPostBack([elementid], [argument])' onmousedown="javascript:ConfirmOnClick(this,{MESSAGE},{TITLE})"
//*/

// Aliases 
//Redirection  or reloader
$redirect = Utility.Redirect;
$redirectWindow = Utility.RedirectNewWindow;

//Image Loader
$ImageLoader = Utility.ImageLoader;

$loadscript = Ajax.JSONRequest;

//Creates a shortcuts for Alert
$alert = function (message, title, callback) {
    if (window.useStandart)
        return alert(message || o.options.message);
    else
        return new Utility.Message('alert', Object.extend({ radius: true, shadow: true, auto: 'Show' }, arguments.callee.argumentValues()));
};

//Creates a shortcuts for INFO
$message = function (message, title, callback) {
    return $dialog(message, title, callback);
};

//Creates a shortcuts for Confirm
$dialog = function (message, title, callback) {
    return new Utility.Message('dialog', Object.extend({ radius: true, shadow: true, isHTML: true, auto: 'Show' }, arguments.callee.argumentValues()));
};

//Creates a shortcuts for Confirm
$confirm = function (message, title, callback) {
    return new Utility.Message('confirm', Object.extend({ radius: true, shadow: true, auto: 'Show' }, arguments.callee.argumentValues()));
};


//Creates a shortcuts for PROGRESS
$progress = function (message) {
    return new Utility.Message('progress', Object.extend({ radius: true, shadow: true, }, arguments.callee.argumentValues()));
};

$loading = function (message) {
    return new Utility.Message('loading', Object.extend({ overlay: false }, arguments.callee.argumentValues()));
};

$popupwindow = function (url, title, width, height) {

    var prms = Object.isString(url) ? { url: url, title: title, width: width, height: height } : url;

    var id = "$" + Object.guid();
    prms = Object.extend({
        message: '<iframe id="' + id + 'frm" src="' + prms.url + '" marginWidth="0" marginHeight="0" frameBorder="0" scrolling="auto" style="width:100%;height:100%" data-popup="' + id + '" />',
        radius: true,
        shadow: true,
        auto: 'Show'
    }, prms);

    self[id] = new Utility.Message('popup', prms);
    self[id].frm = $p(id + 'frm');

    if (Object.isElement(self[id].frm) && self[id].frm.contentWindow) {
        self[id].onHide = function () { try { this.frm.contentWindow.document.fire(':close'); } catch (e) { } }.bind(self[id]);
        self[id].frm.contentWindow.Close = function () { this.Close(); }.bind(self[id]);
    }
    return self[id];
};


$execute = function (host, method, parameters) {
    (self.event instanceof Event.Handler) && Event.stop(self.event);
    if (!Object.isElement(host)) {
        parameters = method, method = host, host = document.body;
    }
    new Utility.AjaxSelector(host, { method: method, parameters: parameters }).Call();
    return false;
};

window.$isIOSDevice =  /(iPad|iPhone|iPod|Macintosh)/g.test(navigator.userAgent) && !(/(CriOS)/g.test(navigator.userAgent));

window.$Screen = window.$Screen || new Utility.Device();

document.observe("dom:loaded", function () {



    //Add event handler to a document to show a waiting message.
    //A wating message could be stored as window variable
    //To not show this progress message on each page postback we need to assign this varialble to empty string or null
    //window.onbeforeunload = function (e) { debugger; $progress(self.progressmessage); }
    //NOTE :: to temporary (one time) disable a progress message on page reload/unload just assign none-string value to self.progressmessage property
    //        usage::  OnClientClick="progressmessage={restore:progressmessage};" - to restore message, or -  progressmessage=false - restore 'Please wait...'
    //Create default progress message text. 
    //To disable  showing the Progress message window assigne this property to null/undefined

    self.executeprogress = "Loading...";
    Object.isUndefined(self.progressmessage) && (self.progressmessage = $progress({ message: "Loading", auto: 'Create', keep: true }));

    Event.observe(document.body, ':unload', function (e) {
        if (Object.isUndefined(self.progressmessage))
            return;
        else if (self.progressmessage instanceof Utility.Message) {
            if (self !== top && top.progressmessage instanceof Utility.Message && top.progressmessage.IsShown())
                return;
            // To hide a progress message on page reload after 20 sec
            // IMPORTANT for the cases where external app/page is used (like <a href="..." target="new|..-")
            self.progressmessage.Show(10);

        } else {
            Object.isString(self.progressmessage) ? $progress(self.progressmessage) : (self.progressmessage = (self.progressmessage && self.progressmessage.restore || "Please wait..."));
        }
    });

    Event.observe(window, 'beforeunload', function (e) {
        $p(document.body).fire(':unload');
    });
    //Create all tooltips
    !$Screen.isTouchDevice && Utility.Global.bubbletip && ($$('[title]', '[bubbletip]').each(function (it) { new Utility.BubbleTip(it); }));
    try {
        //Append style
        Utility.AddStyle();
    }
    catch (ex) {
        $alert({ title: 'Load error', message: ex.message });
    }
});

