/* Minification failed. Returning unminified contents.
(1465,30-31): run-time error JS1195: Expected expression: >
(1465,49-50): run-time error JS1004: Expected ';': )
(1466,5-6): run-time error JS1002: Syntax error: }
(1474,10-11): run-time error JS1004: Expected ';': :
(1474,28-29): run-time error JS1004: Expected ';': {
(1475,31-32): run-time error JS1195: Expected expression: >
(1475,50-51): run-time error JS1004: Expected ';': )
(1477,1-2): run-time error JS1002: Syntax error: }
(1482,10-11): run-time error JS1004: Expected ';': :
(1482,41-42): run-time error JS1004: Expected ';': {
(1492,1-2): run-time error JS1002: Syntax error: }
(1498,10-11): run-time error JS1004: Expected ';': :
(1498,41-42): run-time error JS1004: Expected ';': {
(1503,1-2): run-time error JS1002: Syntax error: }
(1503,3-4): run-time error JS1197: Too many errors. The file might not be a JavaScript file: ;
(1475,9-50): run-time error JS1018: 'return' statement outside of function: return this.every(a1 => arr1.includes(a1)
 */
function util() {
    /// <summary>
    /// Example usage:
    /// Must be declared in the requires of ngAWDSApp.js -> var util = new util();
    /// Anywhere in the application use EngineRoom.util.generateUID();
    /// </summary>
    this.getServiceFromAngular = function (serviceName) {
        //*** call angularjs service
        return angular.element('[ng-app="AWDSApp"]').injector().get(serviceName);
    }

    this.trimString = function (val, defaultVal)
    {
        if (defaultVal) {
        } else {
            defaultVal = "";
        }
        return this.isNullOrEmpty(val) ? defaultVal : (val+'').trim();
    }
    this.showLoadingIcon = function (element, isShow) {
        this.updateButtonSpinnerService(element, isShow);

        ///<summary>
        ///required css 
        ///.loading-icon {
        ///    width: 100%;
        ///    height: 100%;
        ///    float: left;
        ///    top: 0;
        ///    left: 0;
        ///    background-color: rgba(255,255,255,0.8);
        ///    z-index: 999;
        ///    position: absolute;
        ///    background-image: url('images/loading_2x.gif');
        ///    background-repeat: no-repeat;
        ///    background-position: 50% 50%;
        ///}
        /// </summary>
        if (isShow) {
            element.append('<div class="loading-icon"><div class="cssload-loader"><div class="cssload-inner cssload-one"></div><div class="cssload-inner cssload-two"></div><div class="cssload-inner cssload-three"></div></div></div>');            
            //return;
        }
        if (!isShow && element.find('.loading-icon').length > 0) {
            element.find('.loading-icon').each(function(idx, ele) {
                if ($(ele).hasClass('bg-color-back') == false)
                {
                    $(ele).remove();
                }
            });
        }
    }
    this.showLoadingIcon2 = function (element, isShow) {

        this.updateButtonSpinnerService(element, isShow);
        if (isShow) {
            var ele = $('<div class="loading-icon-section" style="height:' + $('body').height() + 'px;"><div class="loading-icon-section-box"><div class="scwindow-checkout-close"><a href="#" class="btn btn-sm btnclose" role="button">X</a></div><span class="box-loading-icon"></span><div class="scwindow-checkout-continue"><a href="#">Continue</a></div></div></div>');
            element.append(ele);
            //element.append('<div class="loading-icon bg-color-back"><a href="#" class="btn btn-sm btnclose" role="button">X</a><div class="scwindow-checkout-continue"><a href="#">Continuer</a></div></div>');
            //element.addClass('loading-icon-box');
            //return ele;
        }
        //if (!isShow && element.find('.loading-icon.bg-color-back').length > 0) {
        //    element.find('.loading-icon.bg-color-back').remove();
        //    //element.removeClass('loading-icon-box');
        //}
        if (!isShow && element.find('.loading-icon-section').length > 0) {
            element.find('.loading-icon-section').remove();
            //element.removeClass('loading-icon-box');
        }
    },
    this.showLoadingIconInner = function (element, isShow) {
        this.updateButtonSpinnerService(element, isShow);

        if (isShow) {
            element.append('<div class="loading-icon"></div>');
            element.addClass('loading-icon-box');
            //return;
        }
        if (!isShow && element.find('.loading-icon').length > 0) {
            element.find('.loading-icon').remove();
            element.removeClass('loading-icon-box');
        }
    }
    
    this.showLoadingButton = function (element, isLoading) {
        if (element && element.length > 0) {
            isLoading = isLoading || false;
            element.button((isLoading ? "loading" : "reset"));
        }
    }
    this.setStatusButton = function (jqElement, sStatus) {
        if (jqElement && jqElement.length > 0) {
            jqElement.button(sStatus);
        }
    }
    this.disableButton = function (jqElement, isDisable) {
        if (jqElement && jqElement.length > 0) {
            isDisable = isDisable || true;
            if (isDisable) {
                jqElement.attr('disabled', 'disabled');
            } else {
                jqElement.removeAttr('disabled');
            }
        }
    }

    this.updateButtonSpinnerService = function (ele, isShow)
    {
        var btnSpinnerService = this.getServiceFromAngular('btnSpinnerService');
        setTimeout(function () {
            if (ele.find('.' + window.webApp.btnSpinner.cl_btn + '.' + window.webApp.btnSpinner.cl_clicked).length > 0) {
                
                var eleButtons = ele.find('.' + window.webApp.btnSpinner.cl_btn + '.' + window.webApp.btnSpinner.cl_clicked);
                eleButtons.each(function (idx, eleBtn) {
                    if (isShow == true) {
                        btnSpinnerService.loading($(eleBtn));
                    }
                    else {
                        btnSpinnerService.reset($(eleBtn));
                    }
                });
            }
        }, 1);
    }

    this.isNullOrEmpty = function(Val) {
        if (typeof Val === "string") return Val.isNullOrEmpty();//*** isNullOrEmpty function from ExtensionMethods file ***
        if (Val === undefined || Val === null) return true;
        if (Val === 0 || Val === false) return false;
        if (Val) return false;//*** will evaluate to true if value is not: 0, false ***
        else return true;
    }
    this.isEmpty = function (Val) {
        if (typeof Val === "string") return Val.isEmpty();//*** isEmpty function from ExtensionMethods file ***
        if (Val === undefined || Val === null) return true;
        if (Val === 0 || Val === false) return false;
        if (Val) return false;//*** will evaluate to true if value is not: 0, false ***
        else return true;
    }
    this.isUndefined = function (val) {
        return (typeof (val) == undefined);
    }
    this.isDefined = function (val) {
        return this.isUndefined(val) == false;
    }
    this.isBrowserIE = function () {
        var ua = window.navigator.userAgent;

        // Test values; Uncomment to check result …

        // IE 10
        // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';

        // IE 11
        // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';

        // Edge 12 (Spartan)
        // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';

        // Edge 13
        // ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';

        var msie = ua.indexOf('MSIE ');
        if (msie > 0) {
            //// IE 10 or older => return version number
            //return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
            return true;
        }

        var trident = ua.indexOf('Trident/');
        if (trident > 0) {
            //// IE 11 => return version number
            //var rv = ua.indexOf('rv:');
            //return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
            return true;
        }

        var edge = ua.indexOf('Edge/');
        if (edge > 0) {
            //// Edge (IE 12+) => return version number
            //return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
            return true;
        }

        // other browser
        return false;
    }

    this.isMobile = function () {
        
        var lPlatforms = (navigator.userAgent.match(/Android/i) ||
            navigator.userAgent.match(/webOS/i) ||
            navigator.userAgent.match(/iPad/i) ||
            navigator.userAgent.match(/iPhone/i) ||
            navigator.userAgent.match(/iPod/i) ||
            navigator.userAgent.match(/BlackBerry/i) ||
            navigator.userAgent.match(/Windows Phone/i)
            );
        return lPlatforms != null && lPlatforms.length > 0;
    }
    this.isAppleSafari = function () {

        var lPlatforms = (navigator.userAgent.match(/iPad/i) ||
            navigator.userAgent.match(/iPhone/i) ||
            navigator.userAgent.match(/iPod/i) ||
            navigator.userAgent.match(/Mac/i) 
            );
        //var sMobile = this.isMobile() && navigator.userAgent.match(/Safari/i);
        return lPlatforms != null && lPlatforms.length > 0;
    }

    this.hasVal = function (val)
    {
        return !this.isNullOrEmpty(val);
    }
    this.equalsLowerCase = function (val1, val2) {
        return this.trimString(val1).toLowerCase() == this.trimString(val2).toLowerCase();
    }

    this.equalsWithSplit = function (str1, strSplit, sComma) {
        if (!(this.hasVal(str1) && this.hasVal(strSplit))) return false;
        str1 = this.trimString(str1);
        strSplit = this.trimString(strSplit);
        sComma = (sComma || ',');
        if (strSplit.indexOf(sComma) > -1) {
            var arrStr = strSplit.split(sComma);
            return arrStr.indexOfLowerCase(str1);//*** fn indexOfLowerCase into ExtensionMethods.js file
        } else {
            return this.equalsLowerCase(str1, strSplit);
        }
    }
    this.includesAnyWithSplit = function (str1, str2, sComma) {
        if (!(this.hasVal(str1) && this.hasVal(str2))) return false;
        str1 = this.trimString(str1);
        str2 = this.trimString(str2);
        sComma = (sComma || ',');

        return str1.split(sComma).includesAny(str2.split(sComma));
    }

    this.indexOfLowerCase = function (val1, val2) {
        return this.trimString(val1).toLowerCase().indexOf(this.trimString(val2).toLowerCase()) >= 0;
    }
    
    this.startWithLowerCase = function (val1, val2) {
        return this.trimString(val1).toLowerCase().indexOf(this.trimString(val2).toLowerCase()) == 0;
    }
    this.dateCompare = function (DateA, DateB) {
        // this function is good for dates > 01/01/1970
        var a = DateA;//new Date(DateA);
        var b = DateB;//new Date(DateB);

        var msDateA = Date.UTC(a.getFullYear(), a.getMonth() + 1, a.getDate());
        var msDateB = Date.UTC(b.getFullYear(), b.getMonth() + 1, b.getDate());

        if (parseFloat(msDateA) < parseFloat(msDateB))
            return -1;  // lt
        else if (parseFloat(msDateA) == parseFloat(msDateB))
            return 0;  // eq
        else if (parseFloat(msDateA) > parseFloat(msDateB))
            return 1;  // gt
        else
            return null;  // error
    }
    this.toBool = function (val) {
        /// <summary>String to Bool or False</summary>
        /// <returns type="Bool"></returns>
        if (this.isNullOrEmpty(val)) {
            return false;
        }
        val += "";
        if (val.toLowerCase() === "true" || val.toLowerCase() === "yes" || val === "1") {
            return true;
        }
        if (val.toLowerCase() === "false" || val.toLowerCase() === "no" || val === "0") {
            return false;
        }
        //return this.isNullOrEmpty() ? false : !!this;
    }
    this.toNumber = function (val, defaultVal) {
        var result = 0;
        if (defaultVal) result = defaultVal;
        if (isFinite(val)) {
            return Number(val);
        }
        return result;
    }
    this.toFloat = function (val, defaultVal) {        
        var result = 0;
        if (defaultVal) result = defaultVal;
        if (Number.isNaN(parseFloat(val)) == false) {
            return parseFloat(val);
        }
        return result;
    }
    this.convertCurrencyToNumber = function (val, defaultVal) {
        if (!this.hasVal(val)) return defaultVal;

        var result = 0;
        if (defaultVal) result = defaultVal;

        var numberString = val.replace(/[^0-9.-]+/g, "");

        if (isFinite(numberString)) {
            result = parseFloat(numberString);
        }
        return result;
    }
    this.toCurrency = function (val)
    {
        if (isFinite(val))
        {
            return Number(val).toCurrency();
        }
        return "";
    }
    this.toCurrencyZero = function (val) {
        if (isFinite(val))
        {
            return Number(val).toCurrency();
        }
        return 0;
    }

    this.getGSTAmount = function (priceInc) {
        if (isFinite(priceInc) && Number(priceInc) > 0) {
            var val = priceInc / 11;
            return val;
        }
        return null;
    }
    this.getPriceInc = function (priceEx, gstRate) {
        if (isFinite(priceEx) && Number(priceEx) > 0) {
            var val = priceEx * (1 + (gstRate / 100));
            return val;
        }
        return null;
    }
    this.to5CentRounding = function (val) {
        if (isFinite(val) && Number(val) > 0) {
            //var price = "$" + (Math.round((val) * 20) / 20).toCurrency(2);
            //if (window.webApp && this.toBool(window.webApp.isPriceSpanFormat) == true) {
            //    price = price.replace(/(\D*)(\d*\.)(\d*)/, '<span style="font-size:16px;">$1</span><span style="font-size:22px;">$2</span><span style="font-size:14px;">$3</span>');
            //}
            //return price;
            if (window.webApp && window.webApp.isUnuse5CentRoundingFormat && window.webApp.isUnuse5CentRoundingFormat == true)
            {
                return "$" + Number(val).toCurrency(2);
            }
            return "$" + (Math.round((val) * 20) / 20).toCurrency(2);
        }
        
        return "";
    }
    this.toLowerCaseExt = function (val) {
        if (this.hasVal(val) == false) return "";        
        return this.trimString(val).toLowerCase();
    }
    this.toUpperFirstChar = function (val) {
        if (this.hasVal(val) == false) return "";
        var arr = val.split(' ');
        for (var i = 0; i < arr.length; i++) {
            if (this.hasVal(arr[i]))
            {
                arr[i] = arr[i].length > 0 ? arr[i].charAt(0).toUpperCase() + arr[i].substr(1).toLowerCase() : arr[i].toUpperCase();
            }
        }
        return arr.join(' ');
    }

    //#region String Left, Right, Mid
    this.leftString = function(text, length)
    {
        /// Return characters starting from the left of the string/text; text.Substring(0, length);
        if (this.isEmpty(text)) return text;
        if (length < 0) return text;
        return (text.length <= length) ? text : text.substring(0, length);
    }
    this.rightString = function (text, length) {
        /// Return characters starting from the right of the string/text;
        if (this.isEmpty(text)) return text;
        if (length < 0) return text;
        return (text.length <= length) ? text : text.substring(text.length - length , length);
    }
    this.midString = function (text, startIndex, length) {
        /// Return characters starting from the startIndex of the string/text;
        if (this.isEmpty(text)) return text;
        if (startIndex < text.length) {
            var maxLength = text.length - startIndex;
            if (length <= 0 || length >= maxLength) {
                return text.substring(startIndex, maxLength);
            } else {
                return text.substring(startIndex, length);
            }
        } else {
            return "";
        }
        if (length < 0) return text;
        return (text.length <= length) ? text : text.substring(0, length);
    }
    this.ellipsis3Dot = function (text, length) {
        /// Return characters starting from the left of the string/text and Add 3 dots at end of text
        if (this.isEmpty(text)) return text;
        if (length < 0) return text;
        return (text.length <= length) ? text : text.substring(0, length) + '...';
    }
    //#endregion

    //#region datetime
    this.dateParse = function (Val) {
        /// <summary>ResponseData to Date or Null</summary>
        /// <returns type="Date"></returns>
        return isNullOrEmpty(Val) ? null : Date.parse(Val);// kendo.parseDate(Val);
    }
    this.formatDateNumberToDate = function (sDateNumber) {
        /// <summary>format datetime "/Date(1533707940000)/" to Date</summary>
        /// <returns type="Date"></returns>      
        if (this.isNullOrEmpty(sDateNumber)) return null;
        if (typeof sDateNumber !== 'string') return sDateNumber;
        return this.isNullOrEmpty(sDateNumber) ? null : new Date(parseInt(sDateNumber.replace("/Date(", "").replace(")/", ""), 10));
    }
    //this.formatDateNumberToDate = function (sDateNumber) {
    //    /// <summary>format datetime "/Date(1533707940000)/" to Date</summary>
    //    /// <returns type="Date"></returns>      
    //    if (this.isNullOrEmpty(sDateNumber)) return null;
    //    if (typeof sDateNumber !== 'string') return sDateNumber;
    //    var iDateNum = parseInt(sDateNumber.replace("/Date(", "").replace(")/", ""), 10);
    //    function z(n) { return (n < 10 ? '0' : '') + n }
    //    var offset = new Date().getTimezoneOffset();
    //    var sign = offset < 0 ? '+' : '-';
    //    offset = Math.abs(offset);//mins
    //    if (sign == '+') {
    //        iDateNum = iDateNum + ((offset * 60) * 1000) //millisecond
    //    } else {
    //        iDateNum = iDateNum - ((offset * 60) * 1000) //millisecond
    //    }
    //    return new Date(iDateNum);
    //}
    this.getMyTimezoneOffset = function () {
        function z(n){return (n<10? '0' : '') + n}
        var offset = new Date().getTimezoneOffset();
        var sign = offset < 0? '+' : '-';
        offset = Math.abs(offset);
        return sign + z(offset/60 | 0) + z(offset%60); //return +0700
    }
    this.stringToDate = function (sDate, format) {
        var normalized = sDate.replace(/[^a-zA-Z0-9]/g, '-');
        var normalizedFormat = format.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-');
        var formatItems = normalizedFormat.split('-');
        var dateItems = normalized.split('-');

        var monthIndex = formatItems.indexOf("mm");
        var dayIndex = formatItems.indexOf("dd");
        var yearIndex = formatItems.indexOf("yyyy");
        var hourIndex = formatItems.indexOf("hh");
        var minutesIndex = formatItems.indexOf("ii");
        var secondsIndex = formatItems.indexOf("ss");

        var today = new Date();

        var year = yearIndex > -1 ? dateItems[yearIndex] : today.getFullYear();
        var month = monthIndex > -1 ? dateItems[monthIndex] - 1 : today.getMonth() - 1;
        var day = dayIndex > -1 ? dateItems[dayIndex] : today.getDate();

        var hour = hourIndex > -1 ? dateItems[hourIndex] : 0; //today.getHours();
        var minute = minutesIndex > -1 ? dateItems[minutesIndex] : 0; //today.getMinutes();
        var second = secondsIndex > -1 ? dateItems[secondsIndex] : 0; //today.getSeconds();

        return new Date(year, month, day, hour, minute, second);
    }
    this.toDateTime = function (sDateStrISO) {
        if (sDateStrISO != null && this.hasVal(sDateStrISO)) {
            return new Date(sDateStrISO);
        } else {
            return null;
        }
    }
    this.toISOStr = function (dtValue) {
        if (dtValue != null) {
            return dtValue.toISOString();
        } else {
            return null;
        }
    }
    //#endregion

    //#region general function
    this.generateUID = function () {
        function s4() {
            var sStr = Math.floor((1 + Math.random()) * 0x10000)
              .toString(16)
              .substring(1);
            if (sStr)
            {
                sStr = sStr.replace(/ /g, '+');
            }
            return sStr;
        }
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
          s4() + '-' + s4() + s4() + s4();
    }
    this.generateKey = function (iLen) {
        iLen = iLen || 10;
        var sStr = this.generateUID();
        sStr = sStr.replace(/-/g, '');
        return this.leftString(sStr, iLen);
    }
    
    this.getFileExtension = function (fileName) {
        return fileName.split('.').pop();
    }
    this.getFileNameFromPath = function (fileName) {
        if (fileName)
            return fileName.replace(/^.*[\\\/]/, '');
        else
            return "";
    }
    this.getFileNameFromPathWithOutExtension = function (fileName) {
        if (fileName)
            return this.getFileNameFromPath(fileName).replace(/\.[^/.]+$/, '');
        else
            return "";
    }    
    this.getWeekdays = function ()
    {
        var weekday = new Array(7);
        weekday[0] = "Sunday";
        weekday[1] = "Monday";
        weekday[2] = "Tuesday";
        weekday[3] = "Wednesday";
        weekday[4] = "Thursday";
        weekday[5] = "Friday";
        weekday[6] = "Saturday";
        return weekday;
    }
    this.getParameterByName = function (name, url) {
        ///query string: ?foo=lorem&bar=&baz
        ///var foo = getParameterByName('foo'); // "lorem"
        ///var bar = getParameterByName('bar'); // "" (present with empty value)
        ///var baz = getParameterByName('baz'); // "" (present with no value)
        ///var qux = getParameterByName('qux'); // null (absent)
        if (!url) url = window.location.href;
        name = name.replace(/[\[\]]/g, '\\$&');
        var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
            results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, ' '));
    }
    this.setParameterByName = function (url, name, val) {
        ///query string: ?foo=lorem&bar=&baz 
        if (this.hasVal(name) == false) return url;
        url = this.trimString(url);        
        if (this.hasVal(val)) val = window.encodeURIComponent(val);
        
        var sFlag = name + '=' + val;
        var sExisting = this.getParameterByName(name);

        if (sExisting === null) {
            var sKey = '';
            if (url.indexOf('?') < 0) {
                //add ?
                sKey = '?';
            } else {
                var sSplitUrl = url.split('?');
                if (sSplitUrl.length > 1) {
                    //add &
                    sKey = '&';
                } else {
                    //add 
                }
            }
            url = url + sKey + sFlag;
        } else {
            //replace
            url = url.replace(name + '=' + sExisting, sFlag);
        }
        return url;
    }
    this.calculateRepayment = function (price, financeFees, interestRate, loanTerm, loanType, isTo5Cent) {
        return this.calculateRepayment2(price, financeFees, interestRate, loanTerm, loanType, null, isTo5Cent);              
    }
    this.calculateRepayment3 = function (price, financeFees, interestRate, loanTerm, loanType, balloonPercent, isTo5Cent) {
        if (balloonPercent != null && balloonPercent > 0)
        {
            balloonPercent = price * balloonPercent;
        }
        return this.calculateRepayment2(price, financeFees, interestRate, loanTerm, loanType, balloonPercent, isTo5Cent);
    }
    this.calculateRepayment2 = function (price, financeFees, interestRate, loanTerm, loanType, balloon, isTo5Cent)
    {
        /// <summary>Finance calculate payment</summary>
        /// <param name="price" type="number">loan amount</param>
        /// <param name="loanTerm" type="number">default number of year.</param>
        /// <param name="loanType" type="string">Weekly/Fortnightly/Monthly/Yearly</param>
        /// <param name="isTo5Cent" type="bool">if true, return string format $1.25 / $$1.30</param>
        /// <returns type="function">Return function</returns>

        if (price < 1) return null;
        if (typeof (isTo5Cent) == undefined || isTo5Cent == null) isTo5Cent = true;


        if (this.hasVal(financeFees) == false) {
            financeFees = 0;
        }
        if (this.hasVal(interestRate) == false) {
            interestRate = 0.1495;
        }

        if (this.hasVal(loanTerm) == false) {
            loanTerm = 60;//default 5 years
        }

        var iLoanYear = loanTerm;
        var iLoanWeekly = 0;
        switch (loanType) {            
            case 'Fortnightly':
                iLoanWeekly = 26;
                if (loanTerm > 7) iLoanYear = loanTerm / 12;
                break;
            case 'Monhly':
            case 'Monthly':
                iLoanWeekly = 12;
                iLoanYear = loanTerm / iLoanWeekly;
                break;
            case 'Yearly':
                iLoanWeekly = 1;
                if (loanTerm > 7) iLoanYear = loanTerm / 12;
                break;
            case 'Weekly':            
            default:
                iLoanWeekly = 52;
                if (loanTerm > 7) iLoanYear = loanTerm / 12;
                break;
        }
        
        var dInterestRate = (interestRate / iLoanWeekly);//interest rate weekly/monthly ----dInterestRateMon
        var iDuration = iLoanYear * iLoanWeekly;// (double)(iLoanYear * totalMonthly);// duration (increments - weeks/months) ------iDuration
        var dLoanAmount = (-1 * (price + financeFees));
        //var balloon = 0;//balloon
        if (this.hasVal(balloon) == false) {
            balloon = 0;//default 5 years
        }
        
        //var result = (h + (h + p) / (Math.pow(1 + v, d) - 1)) * (v * -1 / m);
        var result = (dLoanAmount + (dLoanAmount + balloon) / (Math.pow(1 + dInterestRate, iDuration) - 1)) * (dInterestRate * -1 / 1);
        if (isTo5Cent == true) {
            return this.to5CentRounding(result);
        } else {
            return result;
        }
    }
    this.cWeekdayToStr = function (iDay)
    {
        ///convert week day (number) to string. ex: 0 = Sunday
        if (this.isNullOrEmpty(iDay)) return null;
        if (iDay >= 0 && iDay < 7) {
            return this.getWeekdays()[iDay];
        }
        else {
            return null;
        }
    }
    this.cWeekdayToInt = function (sDay)
    {
        ///convert week day (str) to int. ex: Sunday = 0
        if (this.isNullOrEmpty(sDay)) return null;
        var iResult = null;
        var weekday = this.getWeekdays();
        for (var i = 0; i < 7; i++) {
            if (weekday[i] == sDay)
            {
                sResult = i;
                break;
            }
        }
        return iResult;
    }
    this.replaceEndStr = function (sVal, sReplacement, sFind) {
        if (this.hasVal(sVal) == false) return "";
        return sVal.replace(new RegExp(sFind + '$'), sReplacement);
    }
    this.replaceAllSpaces = function (sVal, sReplacement) {
        if (this.hasVal(sVal) == false) return "";
        //return sVal.replace(new RegExp(sFind + '$'), sReplacement);
        var str = sVal.replace(/\s+/g, sReplacement);
        if (!str.isNullOrEmpty())
        {
            str = str.toLowerCase()
        }
        return str;
    }
    this.replaceAllSpecialCharsToDashLower = function (sVal)
    {
        if (this.hasVal(sVal))
        {
            sVal = sVal.replace(/[^a-z0-9]/gi, '-');
            sVal = sVal.replace(/-+/g, '-');
        }

        if (this.hasVal(sVal) && sVal.substring(sVal.length, sVal.length - 1) == "-") {
            sVal = sVal.substring(0, sVal.length - 1);
        }

        return this.trimString(sVal).toLowerCase();
    }
    this.replaceAllSpecialCharsToSpace = function (sVal) {
        if (this.hasVal(sVal)) {
            sVal = sVal.replace(/[^a-z0-9]/gi, ' ');
            sVal = sVal.replace(/ +/g, ' ');
        }

        if (this.hasVal(sVal) && sVal.substring(sVal.length, sVal.length - 1) == " ") {
            sVal = sVal.substring(0, sVal.length - 1);
        }

        return this.trimString(sVal).toLowerCase();
    }
    this.replaceAllAlphabetic = function (sVal)
    {
        if (this.hasVal(sVal)) {
            sVal = sVal.replace(/[^0-9]/gi, '');
        }
        return sVal;
    }
    this.redirectTo = function (uri, defaultUri) {
        if (this.isNullOrEmpty(uri)) {
            window.location.href = this.trimString(defaultUri);
        } else {
            window.location.href = uri;
        }
    }
    this.historyBack = function (opts)
    {
        /**** Minh::20240705:: 
        *
        * in Javascript
        * stackoverflow.com/questions/3588315/how-to-check-if-the-user-can-go-back-in-browser-history-or-not
        * case 1: open new empty tab, and run a page          => history.length = 2 => we CANNOT use back event 
        * case 2: right click open new tab from result        => history.length = 1 => we CANNOT use back event
        * case 3: redirect to detail from result              => history.length > 1 => we CAN use back event
        *
        * in C# Request.UrlReferrer
        * case 1: open new empty tab, and run a page          => history.length = 2 => Request.UrlReferrer == null => we CANNOT use back event 
        * case 2: right click open new tab from result        => history.length = 1 => Request.UrlReferrer != null => we CANNOT use back event
        * case 3: redirect to detail from result              => history.length > 1 => Request.UrlReferrer != null => we CAN use back event
        *
        * if history.length == 1  => CANNOT call back() event => redirect to page with default-value
        * if history.length => 2 
        *    if from new temp tab => CANNOT call back() event => redirect to page with default-value (timeout check prevPage)
        *    if from result  page => CAN    call back() event => redirect to page with back() event
        */

        if (this.hasVal(opts.defVal) == false) opts.defVal = window.webApp.util.getUrlHasPrefix("/"); //window.webApp.rootURL;
        else opts.defVal = window.webApp.util.getUrlHasPrefix(opts.defVal);

        if (this.hasVal(opts.urlRef) == false)
        {
            this.redirectTo(opts.defVal);//*** if Url Referrer of server-side is empty, redirect to page with default-value
            return;
        }

        try {
            if (window.history.length < 2) {
                this.redirectTo(opts.defVal);
            }
            else
            {
                //*** server-side: UrlReferrer (urlRef)
                
                     
                var prevPage = window.location.href;                
                window.history.back();
                
                setTimeout(function () {

                    if (window.location.href == prevPage) {
                        this.redirectTo(opts.defVal);
                    }
                }, 500);
            }
            
        } catch (e) {
            this.redirectTo(opts.defVal);
        }
    }
    
    this.roundToPrecision = function (price, precision) {
        //ex: round_to_precision(36.14, 0.05) return 36.15
        var y = +price + (precision === undefined ? 0.5 : precision / 2);
        return y - (y % (precision === undefined ? 1 : +precision));
    }
    this.sumOfVal = function (data, key1, key2) {
        var iVal = null;
        if (data && data.length > 0 && this.hasVal(key1))
        {
            if (this.hasVal(key2)) {
                for (var i = 0; i < data.length; i++) {
                    iVal = this.toNumber(iVal) + (this.toNumber(data[i][key1]) * this.toNumber(data[i][key2]));
                }
            } else {
                for (var i = 0; i < data.length; i++) {
                    iVal = this.toNumber(iVal) + this.toNumber(data[i][key1]);
                }
            }            
        }
        return iVal;
    }

    //#endregion

    //#region AusPost functions
    
    this.ausPostDelivery =
    {
        isValidLength: function (value) {
            return (value > 0 && value <= 105);
        },
        isValidWeight: function (value) {
            return (value > 0 && value <= 22);
        },
        isValidCubicMeter: function (valueL, valueW, valueH) {
            return (((valueL / 100) * (valueW / 100) * (valueH / 100)) < 0.25);
        },
        isValidByObj: function (oItem) {
            if (oItem.L < 0 || oItem.W < 0 || oItem.H < 0 || oItem.WT < 0) return false;
            if (this.isValidLength(oItem.L) == false || this.isValidLength(oItem.W) == false || this.isValidLength(oItem.H) == false) {
                console.log('The length cannot exceed 105cm.');
                return false;
            }
            if (this.isValidWeight(oItem.WT) == false) {
                console.log('The maximum weight of a parcel is 22 kg.');
                return false;
            }
            if (this.isValidCubicMeter(oItem.L, oItem.W, oItem.H) == false) {
                console.log('The Cubic Measurement cannot exceed 0.25 cubic meters.');
                return false;
            }
            return true;
        },
        isValidParcel: function (iLength, iWidth, iHeight, iWeight)
        {
            return this.isValidByObj({ L: iLength, W: iWidth, H: iHeight, WT: iWeight });
        }
    }

    //#endregion

    //#region pagination
    this.generatePagination = function (TotalRecords, TotalPage, CurrentPage) {
        var arrayPages = [];
        var minPage = 1;
        // output nice pagination
        // always have a group of 5
        var minRange = Math.max(minPage, CurrentPage - 2);
        var maxRange = Math.min(TotalPage, CurrentPage + 2);
        if (minRange != minPage) {
            arrayPages.push({
                idx: arrayPages.length,
                val: minPage + ""
            });
            arrayPages.push({
                idx: arrayPages.length,
                val: "..."
            });
        }
        for (var i = minRange; i <= maxRange; i++) {
            arrayPages.push({
                idx: arrayPages.length,
                val: i + ""
            });
        }
        if (maxRange != TotalPage) {
            arrayPages.push({
                idx: arrayPages.length,
                val: "..."
            });
            arrayPages.push({
                idx: arrayPages.length,
                val: TotalPage + ""
            });
        }
        return arrayPages;
    }
    this.pagination = {
        next: function (iPageNum, iTotal, cbReload)
        {
            if (iPageNum < iTotal)
            {
                cbReload(iPageNum + 1);
            }
        },
        page: function (sPageNum, cbReload)
        {
            if (sPageNum.indexOf('...') < 0)
            {
                cbReload(Number(sPageNum));
            }
        },
        prev: function (iPageNum, cbReload) {
            if (iPageNum > 1) {
                cbReload(iPageNum - 1);
            }
        }
    }
    //#endregion
    this.scrollToTop = function (ele, minusTop)
    {
        minusTop = minusTop || 0;

        var jq;
        if (ele) {
            jq = ele;
        } else {
            jq = $('html, body');
        }        
        $('html, body').animate({
            scrollTop: jq.offset().top - minusTop
        }, 1000);
    }
    this.scrollToTopNotAnimate = function (ele, minusTop) {
        minusTop = minusTop || 0;

        var jq;
        if (ele) {
            jq = ele;
        } else {
            jq = $('html, body');
        }
        $('html, body').scrollTop((jq.offset().top - minusTop));
    }

    //#region Cookie
    this.deleteCookie = function (c_name) {
        document.cookie = encodeURIComponent(c_name) + "=deleted; expires=" + new Date(0).toUTCString();
    }
    this.setCookie = function (c_name, value, exdays) {
        var exdate = new Date();
        if (exdays == null) {
            exdate.setDate(exdate.getDate());
        } else {
            exdate.setDate(exdate.getDate() + exdays);
        }
        var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
        document.cookie = c_name + "=" + c_value;
    }
    this.getCookie = function (c_name) {
        var c_value = document.cookie;
        var c_start = c_value.indexOf(" " + c_name + "=");
        if (c_start == -1) {
            c_start = c_value.indexOf(c_name + "=");
        }
        if (c_start == -1) {
            c_value = null;
        }
        else {
            c_start = c_value.indexOf("=", c_start) + 1;
            var c_end = c_value.indexOf(";", c_start);
            if (c_end == -1) {
                c_end = c_value.length;
            }
            c_value = unescape(c_value.substring(c_start, c_end));
        }
        return c_value;
    }
    //#endregion
    
    //this.mappingData = function (dataItem, lMappingFields)
    //{
    //    //lMappingFields = [{ from: '', to: '' }];
    //    for (var i = 0; i < lMappingFields.length; i++) {
    //        if (this.hasVal(dataItem[lMappingFields[i].from])) {
    //            dataItem[lMappingFields[i].to] = dataItem[lMappingFields[i].from];
    //        }
    //    }
    //}
    this.buildQueryString = function (obj) {
        return buildQueryStringExtensionMethod(obj);
    }
    this.mappingContactInfor = function (oMail, fieldName) {
        
        if (this.isDefined(oMail) && oMail != null && this.isDefined(oMail[fieldName])) // && (this.hasVal(oMail[fieldName]) || (typeof oMail[fieldName]) == 'boolean')
        {
            var isBooleanValue = false;
            var mappingField = null;
            switch (fieldName) {
                case 'ContactName':
                case 'inputFullName':
                case 'inputYourName':
                case 'inputFirstName':
                case 'inputName':
                case 'FullName':
                    mappingField = 'FirstName';
                    break;
                case 'inputLastName':
                    mappingField = 'LastName';
                    break;
                //case 'MailFrom':
                case 'inputEmail':
                    mappingField = 'ContactEmail';
                    break;
                case 'inputPhone':
                case 'PhoneNumber':
                    mappingField = 'ContactNumber';
                    break;
                case 'IsReceivePromo':
                case 'inputNewsSpecial':
                    mappingField = 'FlagSubscribeForMarketing';
                    isBooleanValue = true;
                    break;
                case 'IsContactedBySalesperson':
                case 'inputSalesPerson':
                    mappingField = 'FlagSalesContactRequest';
                    isBooleanValue = true;
                    break;
                default:
                    break;
            }
            if (mappingField != null) {
                if (oMail[fieldName] != null)
                {
                    if (isBooleanValue) {
                        oMail[mappingField] = this.toBool(oMail[fieldName]);
                    } else {
                        if(this.hasVal(oMail[fieldName])) oMail[mappingField] = this.trimString(oMail[fieldName]);
                    }
                }
                
            }

        }
    }

    var _base64 = {
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        _utf8_encode: function (e) { e = e.replace(/\r\n/g, "\n"); var t = ""; for (var n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r) } else if (r > 127 && r < 2048) { t += String.fromCharCode(r >> 6 | 192); t += String.fromCharCode(r & 63 | 128) } else { t += String.fromCharCode(r >> 12 | 224); t += String.fromCharCode(r >> 6 & 63 | 128); t += String.fromCharCode(r & 63 | 128) } } return t },
        _utf8_decode: function (e) { var t = ""; var n = 0; var r = c1 = c2 = 0; while (n < e.length) { r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r); n++ } else if (r > 191 && r < 224) { c2 = e.charCodeAt(n + 1); t += String.fromCharCode((r & 31) << 6 | c2 & 63); n += 2 } else { c2 = e.charCodeAt(n + 1); c3 = e.charCodeAt(n + 2); t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); n += 3 } } return t },
        encode: function (e) { var t = ""; var n, r, i, s, o, u, a; var f = 0; e = _base64._utf8_encode(e); while (f < e.length) { n = e.charCodeAt(f++); r = e.charCodeAt(f++); i = e.charCodeAt(f++); s = n >> 2; o = (n & 3) << 4 | r >> 4; u = (r & 15) << 2 | i >> 6; a = i & 63; if (isNaN(r)) { u = a = 64 } else if (isNaN(i)) { a = 64 } t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a) } return t },
        decode: function (e) { var t = ""; var n, r, i; var s, o, u, a; var f = 0; e = e.replace(/[^A-Za-z0-9+/=]/g, ""); while (f < e.length) { s = this._keyStr.indexOf(e.charAt(f++)); o = this._keyStr.indexOf(e.charAt(f++)); u = this._keyStr.indexOf(e.charAt(f++)); a = this._keyStr.indexOf(e.charAt(f++)); n = s << 2 | o >> 4; r = (o & 15) << 4 | u >> 2; i = (u & 3) << 6 | a; t = t + String.fromCharCode(n); if (u != 64) { t = t + String.fromCharCode(r) } if (a != 64) { t = t + String.fromCharCode(i) } } t = _base64._utf8_decode(t); return t },
        generateCode: function (iWebsiteId, capId, sDateStrShort) {
            var sCode = iWebsiteId + capId + sDateStrShort;//code format: webid + form.CapId + dd/MM/yyyy
            return this.encode(sCode);
        }
    }

    this.base64Extend = _base64;


    return this;
}


function historyState() {
    var DOM_EVENT = {
        RESIZE: 'resize',
        SCROLL: 'scroll',
        CLICK: 'click',
        KEYDOWN: 'keydown',
        FOCUS: 'focus',
        INPUT: 'input'
    };

    var _unused = null;//sUnused || 'state-title';
    var _url = null;//sUrl || window.location.pathname;
    var _state = {
        data: null,
        scrollPosition: null
    };
    var _hasHistoryPushed = false;


    var _init = function (opts) {
        _unused = opts.unused || 'state-title';
        _url = opts.url || window.location.pathname;

        if (window.history.state != null) {
            _state.data = window.history.state.data;
            _state.scrollPosition = window.history.state.scrollPosition;
            _hasHistoryPushed = true;
        }
        _wireupEvents();
    }

    var _wireupEvents = function () {

        $(document).on(DOM_EVENT.CLICK, function (event) {
            //var $event = event;            
            //$scope.$evalAsync(function () {
            //    _handleDocumentClick($event);
            //});
            if (_hasHistoryPushed == true) {
                _updatePosition();
            }
        });
    }    
    var _scrollTo = function (delay) {
        delay = delay || 1000;
        if (_hasHistoryPushed == true && _state.scrollPosition != null)
        {
            window.setTimeout(function(){
                $(window).scrollTop(_state.scrollPosition);
            }, delay);
        }
            
    }

    var _pushState = function () {
        window.history.pushState(_state, _unused, _url);
        _hasHistoryPushed = true;
    }
    var _replaceState = function () {
        window.history.replaceState(_state, _unused, _url);
        //window.history.replaceState({}, 'state-title', window.location.pathname);
    }
    var _updatePosition = function () {
        _state.scrollPosition = $(window).scrollTop();
        _replaceState();
    }

    var _hasData = function () {
        return _state.data != null;
    }


    //**** public functions
    this.push = function (stateData) {
        _state.data = stateData;
        _state.scrollPosition = $(window).scrollTop();
        _pushState();
    }

    //this.updateState = function () {
    //    _state.scrollPosition = $(window).scrollTop();
    //    _updatePosition();
    //}

    this.getStateData = function () {
        return _state.data;
    }
    this.hasData = function ()
    {
        return _hasData();
    }
    this.scrollToPrevious = function (delay) {
        _scrollTo(delay);
    }
    this.init = function (sUnused, sUrl) {
        _init({ unused: sUnused, url: sUrl});
    }


    return this;
};
//#region String Extensions
if (typeof String.prototype.trim !== 'function') {
    String.prototype.trim = function () {
        return this.replace(/^\s+|\s+$/g, '');
    }
}
String.prototype.trimString = function () {
    return this.isNullOrEmpty() ? "" : this.trim();
}
String.prototype.isEmpty = function () {
    return (this.length === 0 || !this.trim());
}
String.prototype.isNullOrEmpty = function () {
    return (this === undefined || this === null || this.isEmpty());
}
String.prototype.hasVal = function () {
    return !(this === undefined || this === null || this.isEmpty());
}
String.prototype.toDateNull = function () {
    /// <summary>String to Date or Null</summary>
    /// <returns type="Date"></returns>
    return this.isNullOrEmpty() ? null : new Date(this.trimString());
}
String.prototype.toBool = function () {
    /// <summary>String to Bool or False</summary>
    /// <returns type="Bool"></returns>
    if (this.isNullOrEmpty()) {
        return false
    }
    if (this.toLowerCase() === "true") {
        return true
    }
    if (this.toLowerCase() === "false") {
        return false
    }
    //return this.isNullOrEmpty() ? false : !!this;
}
String.prototype.isBool = function () {
    /// <summary>String to Bool or False</summary>
    /// <returns type="Bool"></returns>
    if (this.isNullOrEmpty()) {
        return false
    }

    if (this.toLowerCase() === "true" || this.toLowerCase() === "false") {
        return true;
    }
    else {
        return false;
    }
}
//#endregion

Number.prototype.toCurrency = function (n, x) {
    var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\.' : '$') + ')';
    return this.toFixed(Math.max(0, ~ ~n)).replace(new RegExp(re, 'g'), '$&,');
};

//#region Date Extensions
Date.prototype.toLocaleDateStringExt = function (options) {
    /// <summary>Uses day-month-year (dd/MM/yyyy)</summary>
    /// <returns type="String"></returns>
    if (IsNullOrEmpty(options)) options = { year: "numeric", month: "numeric", day: "2-digit" };
    return IsNullOrEmpty(this) ? "" : this.toLocaleDateString('en-GB', options);
}
Date.prototype.toLocaleTimeStringExt = function (options) {
    /// <summary>Uses hour:minute:second (HH:mm:ss)</summary>
    /// <returns type="String"></returns>    
    if (IsNullOrEmpty(options)) options = { hour: '2-digit', minute: '2-digit', second: '2-digit' };
    return IsNullOrEmpty(this) ? "" : this.toLocaleTimeString('en-GB', options);
}
Date.prototype.addDays = function (days) {
    var date = new Date(this.valueOf());
    date.setDate(date.getDate() + days);
    return date;
}
Date.prototype.toStartOfDay = function () {
    var year = this.getFullYear();
    var month = this.getMonth();
    var day = this.getDate();
    return new Date(year, month, day, 0, 0, 0);
}
Date.prototype.toEndOfDay = function () {
    var year = this.getFullYear();
    var month = this.getMonth();
    var day = this.getDate();
    return new Date(year, month, day, 23, 59, 59);
}
Date.prototype.toStartOfWeek = function () {
    //*** local time: 0 for Sunday, 1 for Monday, 2 for Tuesday, and so on

    //clone date object, so we don't mutate it
    var dt = this.toStartOfDay();// new Date(this);
    var dayOfWeek = dt.getDay();// get day of week
    var dayOfMonth = dt.getDate();// get day of week
    //day of month - day of week (-6 if Sunday), otherwise +1
    var diff = dayOfMonth - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
    return new Date(dt.setDate(diff));
}
Date.prototype.toEndOfWeek = function () {
    //*** local time: 0 for Sunday, 1 for Monday, 2 for Tuesday, and so on
    var dt = this.toEndOfDay().toStartOfWeek();
    return dt.addDays(+6);// new Date(dt.setDate(dt.getDate() + 6));
}
Date.prototype.toStartOfMonth = function () {
    var year = this.getFullYear();
    var month = this.getMonth();
    return new Date(year, month, 1, 0, 0, 0);
}
Date.prototype.toEndOfMonth = function () {
    var year = this.getFullYear();
    var month = this.getMonth();
    return new Date(year, month + 1, 0, 23, 59, 59);
}
Object.defineProperty(Date.prototype, 'toYYYYMMDDHHMMSS', {
    value: function () {
        function pad2(n) {  // always returns a string
            return (n < 10 ? '0' : '') + n;
        }

        return this.getFullYear() +
               pad2(this.getMonth() + 1) +
               pad2(this.getDate()) +
               pad2(this.getHours()) +
               pad2(this.getMinutes()) +
               pad2(this.getSeconds());
    }
});
//#endregion

//#region Array Extensions
//Array.prototype.getMaxByFieldName = function (field) {
//    //www.reddit.com/r/javascript/comments/37bhug/map_or_reduce_to_calculate_max_value_from_json/?st=jentf5wh&sh=453d55c5
//    //return Math.max.apply(Math,array.map(function(o){return o.y;}));
//    return this.reduce(function (max, x) { return (x[field] > max) ? x[field] : max; }, 0);
//}
//Array.prototype.getSelectedItem = function (value) {
//    var item = null;
//    //return this.trimString(val1).toLowerCase() == this.trimString(val2).toLowerCase();
//    for (var i = 0; i < this.length; i++) {
//        if (TrimString(this[i].Value).toLowerCase() == TrimString(value).toLowerCase())
//        {
//            item = this[i];
//        }
//    }
//    return item;
//}
//Array.prototype.removeItemByFieldName = function (val, field) {
//    for (var i = 0; i < this.length; i++) {
//        if (this[i][field] === val) this.splice(i, 1);
//    }
//}
//Array.prototype.removeItemByIndex = function (idx) {
//    this.splice(idx, 1);
//}
Object.defineProperty(Array.prototype, 'getMaxByFieldName', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (field) {
        return this.reduce(function (max, x) { return (x[field] > max) ? x[field] : max; }, 0);
    }
});
Object.defineProperty(Array.prototype, 'getSelectedItem', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (value) {
        var item = null;
        //return this.trimString(val1).toLowerCase() == this.trimString(val2).toLowerCase();
        for (var i = 0; i < this.length; i++) {
            var val1, val2;
            if (IsInteger(this[i].Value)) {
                val1 = this[i].Value;
                val2 = value;
            }
            else {
                val1 = TrimString(this[i].Value).toLowerCase();
                val2 = TrimString(value).toLowerCase();
            }
            if (val1 == val2) {
                item = this[i];
            }
        }
        return item;
    }
});
Object.defineProperty(Array.prototype, 'moveItemAt', {
    //example: arr.moveItemAt(arr.length, function(x){return x.Value == 'Att';}); //*** move to last item
    enumerable: false,
    value: function (position, comparer) {
        //var arr = this;
        for (var i = 0; i < this.length; i++) {
            if (comparer(this[i]))
            {
                this.splice(position, 0, this.splice(i, 1)[0]);
                break;
            }
        }
    }
});
Object.defineProperty(Array.prototype, 'removeItemByFieldName', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (val, field) {
        for (var i = 0; i < this.length; i++) {
            if (this[i][field] === val)
            {
                this.splice(i, 1);
                break;
            }
        }
        return this;
    }
});
Object.defineProperty(Array.prototype, 'removeItemByIndex', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (idx) {
        this.splice(idx, 1);
        return this;
    }
});
Object.defineProperty(Array.prototype, 'removeItem', {
    //*** lBreadcrumbs.removeItem(function (x) { return x.fieldName == lDeletings[i].fieldName && x.val == lDeletings[i].val; });
    enumerable: false,
    value: function (comparer) {

        for (var i = 0; i < this.length; i++) {
            if (comparer(this[i])) {
                this.splice(i, 1);
            }
        }
    }
});

Object.defineProperty(Array.prototype, 'groupBy', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (key) {
        var map = {};
        this.forEach(function (e) {
            var k = key(e);
            map[k] = map[k] || [];
            map[k].push(e);
        });
        return Object.keys(map).map(function (k) {
            return { key: k, key2: (k === 'null' ? '' : k), data: map[k] };//*** use key2 for orderby
        });
    }
});

Object.defineProperty(Array.prototype, 'groupBy2', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (key, sort) {
        var map = {};
        this.forEach(function (e) {
            var k = key(e);
            map[k] = map[k] || [];
            map[k].push(e);
        });

        return Object.keys(map).map(function (k) {
            var flagSort = '';
            if (sort && map[k].length > 0) {
                flagSort = sort(map[k][0]);
            } else {
                flagSort = k;//***default sort
            }
            return { key: k, sort: flagSort, data: map[k] };//*** use sort for orderby
        });

        //return records.sort(records.sortBy("sort", false, null));
    }
});

Object.defineProperty(Array.prototype, 'sortBy', {
    //example: var aTemps = $scope.lBreadcrumbs.sort($scope.lBreadcrumbs.sortBy("Sort", false, parseInt));
    //sort function has supported by browser
    enumerable: false,
    value: function (field, reverse, primer) {

        var key = primer ?
            function (x) { return primer(x[field]) } :
            function (x) { return x[field] };

        reverse = !reverse ? 1 : -1;

        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    }
});

// check if an element exists in array using a comparer function
// comparer : function(currentElement)
Object.defineProperty(Array.prototype, 'inArray', {
    //example: var aTemps = data.groupBy(function (item) { return item.NewModelMakeID; });
    enumerable: false,
    value: function (comparer) {
        for (var i = 0; i < this.length; i++) {
            if (comparer(this[i])) return true;
        }
        return false;
    }
});
Object.defineProperty(Array.prototype, 'includesAny', {
    //example: 
    //  [1, 2, 3, 4].includesAll([2, 9]); // true
    //  [1, 2, 3, 4].includesAll([8, 9]); // false
    enumerable: false,
    value: function (arr1) {       
        return this.some(a1 => arr1.includes(a1));
    }
});
Object.defineProperty(Array.prototype, 'includesAll', {
    //www.30secondsofcode.org/js/s/array-includes-any-or-all-values/#:~:text=or%20all%20values-,Check%20if%20a%20JavaScript%20array%20includes%20any%20or%20all%20values,array%20includes%20a%20specific%20value.
    //example: 
    //  [1, 2, 3, 4].includesAll([1, 4]); // true
    //  [1, 2, 3, 4].includesAll([1, 5]); // false
    enumerable: false,
    value: function (arr1) {
        return this.every(a1 => arr1.includes(a1));
    }
});

Object.defineProperty(Array.prototype, 'updateIfExists', {
    //example: $scope.oRecord.WSLClients.updateIfExists(data.item, function (x) { return (data.item.CLIUID == x.CLIUID); });
    enumerable: false,
    value: function (element, comparer) {
        for (var i = 0; i < this.length; i++) {
            if (comparer(this[i])) {
                this[i] = element;
                break;
            }
        }
        //return this;
        //return false;
    }
});

// adds an element to the array if it does not already exist using a comparer 
Object.defineProperty(Array.prototype, 'pushIfNotExist', {
    //example: eleLamListTemp.pushIfNotExist(angular.copy(ele), function (x) { return $(x).data("modelid") == $(ele).data("modelid"); });
    enumerable: false,
    value: function (element, comparer) {
        if (!this.inArray(comparer)) {
            this.push(element);
        }
    }
});
Object.defineProperty(Array.prototype, 'unshiftIfNotExist', {
    //example: eleLamListTemp.unshiftIfNotExist(angular.copy(ele), function (x) { return $(x).data("modelid") == $(ele).data("modelid"); });
    enumerable: false,
    value: function (element, comparer) {
        if (!this.inArray(comparer)) {
            this.unshift(element);
        }
    }
});
Object.defineProperty(Array.prototype, 'spliceIfNotExist', {
    //var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
    //myFish.splice(2, 0, 'drum');
    //myFish is ["angel", "clown", "drum", "mandarin", "sturgeon"]
    //example: response.oAusPostServices.service.spliceIfNotExist(moAUSPost, iIndexArray, function (oService) {return oService.code == moAUSPost.code; });
    enumerable: false,
    value: function (element, idx, comparer) {
        if (!this.inArray(comparer)) {
            this.splice(idx, 0, element);
        }
    }
});

Object.defineProperty(Array.prototype, 'indexOfLowerCase', {
    //example: ['Apple', 'nokia'].indexOfLowerCase('apple') => return true
    enumerable: false,
    value: function (val, fromIndex) {
        if (val === undefined || val === null || val.isEmpty()) return false;
        
        var idx = (fromIndex || 0),
        arrLen = this.length;
        var bVal = false;

        while (idx < arrLen) {
            if (this[idx].hasVal() && this[idx].toLowerCase() === val.toLowerCase()) {
                bVal = true;
                break;
            }
            idx++;
        }
        return bVal;
    }
});

Object.defineProperty(Array.prototype, 'outerExcludingJoin', {
    //*** This outerExcludingJoin function will return all of the records in the left table (Table_A) and all of the records in the right table (Table_B) that do not match
    //example: ['Apple', 'nokia'].diffLowerCase(['APPLE', 'samsung']) => return ['nokia', 'samsung']
    enumerable: false,
    value: function (arr2) {        
        //return this.filter(x => !arr2.includes(x));
        //*** includes() is not supported in Edge 13 (or earlier).
        //*** change to use indexOf()
        return this.filter(x => arr2.indexOfLowerCase(x) == false).concat(arr2.filter(x => this.indexOfLowerCase(x) == false));
    }
});
//#endregion

//#region Standard Methods
function isMobile() {
    return (navigator.userAgent.match(/Android/i) ||
        navigator.userAgent.match(/webOS/i) ||
        navigator.userAgent.match(/iPad/i) ||
        navigator.userAgent.match(/iPhone/i) ||
        navigator.userAgent.match(/iPod/i)
        );
}
function isAppleSafari() {
    return (navigator.userAgent.match(/iPad/i) ||
        navigator.userAgent.match(/iPhone/i) ||
        navigator.userAgent.match(/iPod/i)
        );
}

function IsNullOrEmpty(Val) {
    if (typeof Val === "string") return Val.isNullOrEmpty();
    if (Val === undefined || Val === null) return true;
    if (Val === 0 || Val === false) return false;
    if (Val) return false;//*** will evaluate to true if value is not: 0, false ***
    else return true;
}
function IsInteger(Val)
{
    var reg = /^[\+\-]?\d*\.?\d+(?:[Ee][\+\-]?\d+)?$/;
    return reg.test(Val);
}
function isCallBackFuncion(callback) {
    if (callback && typeof (callback) === 'function') {
        return true;
    } else {
        return false;
    }
}
function TrimString(Val)
{
    if (IsNullOrEmpty(Val) || typeof Val !== "string") return "";
    return Val.trimString();
}
function CDateNull(Val) {
    /// <summary>ResponseData to Date or Null</summary>
    /// <returns type="Date"></returns>
    return IsNullOrEmpty(Val) ? null : Date.parse(Val);
}

function CLocaleDateStringExt(val, options) {
    if (IsNullOrEmpty(val)) return "";
    return val.toLocaleDateStringExt(options);
}
function CLocaleTimeStringExt(val, options) {
    if (IsNullOrEmpty(val)) return "";
    return val.toLocaleTimeStringExt(options);
}

function CNumberZero(Val)
{
    if (!IsInteger(Val)) return 0;
    return Number(Val);
}
function CNumberNull(Val) {
    if (!IsInteger(Val)) return null;
    return Number(Val);
}
//#endregion

//#region Common Methods
var GeneratePagination = function (TotalRecords, TotalPage, CurrentPage) {
    var arrayPages = [];
    var minPage = 1;
    // output nice pagination
    // always have a group of 5
    var minRange = Math.max(minPage, CurrentPage - 2);
    var maxRange = Math.min(TotalPage, CurrentPage + 2);
    if (minRange != minPage) {
        arrayPages.push(minPage + "");
        arrayPages.push("...");
    }
    for (var i = minRange; i <= maxRange; i++) {
        arrayPages.push(i + "");
    }
    if (maxRange != TotalPage) {
        arrayPages.push("...");
        arrayPages.push(TotalPage + "");
    }
    console.warn("Minh: ", arrayPages);
    return arrayPages;
}
function generateUID() {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
          .toString(16)
          .substring(1);
    }
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
      s4() + '-' + s4() + s4() + s4();
}
function getFileExtension(fileName) {
    return fileName.split('.').pop();
}
function convertImageToBase64(file, callback) {
    var oResult = {};
    oResult = $.extend({}, oResult, file);
    var reader = new FileReader();
    reader.onload = function (img) {
        oResult.Status = "OK";
        oResult.Data = img.target.result;
        callback(oResult);
    }
    reader.onerror = function () {
        oResult.Status = "ERROR";
        oResult.Data = reader.error;
        callback(oResult);
    };
    reader.readAsDataURL(file);
}
function convertBase64ToImage(dataurl, filename) {
    var arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);

    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }

    return new File([u8arr], filename, { type: mime });
}

function buildQueryStringExtensionMethod(obj, parentKey) {
    parentKey = parentKey || '';
    return Object.keys(obj)
        .filter(key => obj[key] !== null) // Filter out properties with null values
        .map(key => {
            const fullKey = parentKey ? `${parentKey}.${key}` : key;
            const value = obj[key];
            if (typeof value === 'object' && value !== null) {
                return buildQueryStringExtensionMethod(value, fullKey);
            } else {
                return `${fullKey}=${encodeURIComponent(value)}`;
            }
        })
        .filter(Boolean) // Remove any empty strings resulting from null values
        .join('&');
}
//#endregion;
var AWDSApp = new function () {
    this.util = new util();
    //this.historyState = new historyState();
}

var load_lib_ui_bootstrap_tpls = function (cbLoaded) {
    function initScript(d, s, id) {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) {
            cbLoaded();
            //return;
        }
        else {
            js = d.createElement(s); js.id = id;
            if (angular.isDefined(window.webApp) && angular.isDefined(window.webApp.rootURL)) {
                js.src = window.webApp.rootURL + "Areas/Stock/Scripts/Libs/Common/ui-bootstrap-tpls-2.5.0.js";
            } else {
                js.src = (angular.isDefined(window.rootURL) ? window.rootURL : '/') + "Areas/Stock/Scripts/Libs/Common/ui-bootstrap-tpls-2.5.0.js";
            }
            fjs.parentNode.insertBefore(js, fjs);
            js.onload = function () {
                if (cbLoaded && typeof cbLoaded == 'function') {
                    cbLoaded();
                }
            };
        }
    }
    initScript(document, 'script', 'lib-ui-bootstrap-tpls');
}

var ngAWDSApp = angular.module("AWDSApp", ['ngCookies', 'vcRecaptcha', 'ui-notification']); //, 'ngSanitize'



//angular.module('AWDSApp').requires.push('ui.bootstrap');
try {
    angular.module('ui.bootstrap');
    angular.module('AWDSApp').requires.push('ui.bootstrap');
} catch (err) {
    load_lib_ui_bootstrap_tpls(function () {
        angular.module('AWDSApp').requires.push('ui.bootstrap');
    });
}

ngAWDSApp.run(['$rootScope', '$window', 'HttpFactory', '$timeout', function ($rootScope, $window, HttpFactory, $timeout) {
    
    $rootScope.rootURL = window.rootURL;//*** we will remove this option after changed to webApp object
    $rootScope.ConfigFormMail = angular.copy(window.ConfigFormMail);//*** we will remove this option after changed to webApp object
    //window.ConfigFormMail = null;    
    //***using util object in HTML
    var oUtil = {
        getUrlHasPrefix: function (sAction) {
            if (AWDSApp.util.isNullOrEmpty(sAction))
            {
                return "";
            }
            return $rootScope.webApp.rootURL + (sAction.substring(0, 1) == '/' ? sAction.substring(1, sAction.length) : sAction).toLowerCase();
        },
        loadPaypalScript: function (cbLoaded) {
            function initScript(d, s, id) {
                var js, fjs = d.getElementsByTagName(s)[0];
                if (d.getElementById(id)) {
                    cbLoaded();
                    //return;
                }
                else {
                    js = d.createElement(s); js.id = id;
                    js.src = "https://www.paypalobjects.com/api/checkout.js";
                    fjs.parentNode.insertBefore(js, fjs);
                    js.onload = function () {
                        if (cbLoaded && typeof cbLoaded == 'function')
                        {
                            cbLoaded();
                        }
                    };
                }
            }
            initScript(document, 'script', 'paypal-jscheckout');
        },
        loadPaypalScriptByClientId: function (clientId, cbLoaded) {
            function initScript(d, s, id) {
                var js, fjs = d.getElementsByTagName(s)[0];
                if (d.getElementById(id)) {
                    cbLoaded();
                    //return;
                }
                else {
                    js = d.createElement(s); js.id = id;
                    js.src = "https://www.paypal.com/sdk/js?locale=en_AU&enable-funding=card,credit&currency=AUD&client-id=" + clientId;// + '&debug=true';
                    fjs.parentNode.insertBefore(js, fjs);
                    js.onload = function () {
                        if (cbLoaded && typeof cbLoaded == 'function') {
                            cbLoaded();
                        }
                    };
                }
            }
            initScript(document, 'script', 'paypal-jscheckout');
        },
        loadPaymentScript: function (id, src, async, cbOnload) {
            var d = document;
            var s = 'script';
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) {
                return;
            }
            else {
                js = d.createElement(s); js.id = id;
                js.src = src;
                js.async = async;
                fjs.parentNode.insertBefore(js, fjs);
                if (angular.isFunction(cbOnload)) {
                    js.onload = cbOnload;
                }
                //js.onload = function () {                    
                //};
            }
        },
        loadSecurepayV2Script: function (isSandbox, cbLoaded) {
            function initScript(d, s, id) {
                var js, fjs = d.getElementsByTagName(s)[0];
                if (d.getElementById(id)) {
                    return;
                }
                else {
                    js = d.createElement(s); js.id = id;
                    if (isSandbox) {
                        js.src = "https://payments-stest.npe.auspost.zone/v3/ui/client/securepay-ui.min.js";//sandbox
                    } else {
                        js.src = "https://payments.auspost.net.au/v3/ui/client/securepay-ui.min.js";//live
                    }
                
                    fjs.parentNode.insertBefore(js, fjs);
                    js.onload = function () {
                        if (cbLoaded && typeof cbLoaded == 'function') {
                            cbLoaded();
                        }
                    };
                }
            }
            initScript(document, 'script', 'securepay-v2-ui-jscheckout');
        },
        getObjectLength: function (objValue)
        {
            var iObjLength = 0;
            var objKey = Object.keys(objValue);
            for (var i = 0; i < objKey.length; i++)
            {
                var sFieldName = objKey[i];
                if (angular.isArray(objValue[sFieldName])) {
                    for (var j = 0; j < objValue[sFieldName].length; j++) {
                        iObjLength = iObjLength + this.getObjectLength(objValue[sFieldName][j]);
                    }                
                } else if (angular.isObject(objValue[sFieldName])) {
                    //iObjLength = iObjLength + Object.keys(objValue[sFieldName]).length;
                    iObjLength = iObjLength + this.getObjectLength(objValue[sFieldName]);
                } else {
                    iObjLength++;
                }
            }
            return iObjLength;
        }
    }
    angular.merge(oUtil, AWDSApp.util);
    $rootScope.util = oUtil;//*** we will remove this option after changed to webApp object

    //****************************************************************
    if (window.webApp) {
        window.webApp.util = oUtil;
        if ($rootScope.util.isNullOrEmpty(window.rootURL))
        {
            window.rootURL = window.webApp.rootURL;
        }
        if ($rootScope.util.isNullOrEmpty(window.RecaptchaSiteKey)) {
            window.RecaptchaSiteKey = window.webApp.ga.recaptchaSiteKey;
        }        
    }
    else {
        window.webApp = {
            rootURL: window.rootURL,
            fb: {
                appId: window.FBAppId//*** the key for login ***
            },
            ga: {
                appId: window.GoogleAppId,//*** the key for login ***
                recaptchaSiteKey: window.RecaptchaSiteKey
            },
            formMailOptions: {
                redirectToThankYou: {
                    formMailSubscribeDirective: null,
                    formMailEnquiry3Directive: null,
                    formMailBasicDirective: null
                }
            },
            util: oUtil
        };
        if (window.ConfigFormMail)
        {
            if (window.ConfigFormMail.formMailSubscribeDirective) {
                window.webApp.formMailOptions.redirectToThankYou.formMailSubscribeDirective = window.ConfigFormMail.formMailSubscribeDirective.sThankYou;
            }
            if (window.ConfigFormMail.formMailEnquiry3Directive) {
                window.webApp.formMailOptions.redirectToThankYou.formMailEnquiry3Directive = window.ConfigFormMail.formMailEnquiry3Directive.sThankYou;
            }
            if (window.ConfigFormMail.formMailBasicDirective) {
                window.webApp.formMailOptions.redirectToThankYou.formMailBasicDirective = window.ConfigFormMail.formMailBasicDirective.sThankYou;
            }
            if (window.ConfigFormMail.webAppExt) {
                angular.merge(window.webApp, window.ConfigFormMail.webAppExt);
            }
        }
    }
    //namespace AWDS.Data.Common.Config
    window.webApp.WebsiteIdCode = {
        BrisbaneRV201: 201,
        SunseekerRV214: 214,
        SAMotorcycle222: 222,
        RovaRange229: 229,
        kratzmann250: 250,
        JaycoAlburyWodonga261: 261,
        YamahaDubbo276: 276,
        Carlins316: 316,
        BunburyTrucks351: 351,
        Lawrencerv352: 352,
        Freestylervs3025: 3025,
        EliteMotorcycles325: 325,
        NewAgeGoldCoast3001: 3001,//Stock site
        NewAgeAdelaide3002: 3002,
        AdventureDubbo3004: 3004,
        FarmAndGarden3008: 3008,
        WorkRestPlay3009: 3009,
        StreetCaravans3012: 3012,
        AutoGiant3018: 3018,
        DestinyRVGoldCoast3028: 3028,//Stock site
        GippslandRVStock3032: 3032,//Stock site
        BrisbaneYamahaPart3040: 3040,
        BodyShop3041: 3041,
        WangarattaCaravans3036: 3036,
        AlburyCaravans3039: 3039,
        Offtrackrv3042: 3042
    }

    window.webApp.btnSpinner = {
        cl_btn: 'btn-spinner-service',
        cl_clicked: 'clicking',
        cl_loading: 'loading'
    }

    //window.webApp.historyState = new historyState();

    $rootScope.webApp = window.webApp;

    //$http.defaults.headers.common.IANATimeZoneName = $rootScope.webApp.util.getTimezoneName();// + ';tos=' + sTimeOffset;
    if (angular.isDefined(window.sJsonWebInfo) && window.sJsonWebInfo != null)
    {
        //*** the sJsonWebInfo value come from <script src="@Url.Content("~/stock/website/getwebsiteinfoscript")"></script>
        var jsonData = angular.fromJson(window.sJsonWebInfo);
        $rootScope.webApp.oWebsite = jsonData;
        window.webApp.oWebsite = jsonData;
        delete window.sJsonWebInfo;
    }
    else {
        HttpFactory.get({}, "Stock/Website/GetWebsite").then(function (response) {
            var jsonData = angular.fromJson(response.data);
            $rootScope.webApp.oWebsite = jsonData;
            window.webApp.oWebsite = jsonData;
        }, function (responseError) { });
    }

    if (IsNullOrEmpty($rootScope.webApp.rootURL)) {
        console.error("rootURL is empty. please set rootURL value in Layout");
    }

}]);
ngAWDSApp.config(['$provide', '$httpProvider', '$cookiesProvider', 'vcRecaptchaServiceProvider', 'NotificationProvider', '$locationProvider', function ($provide, $httpProvider, $cookiesProvider, vcRecaptchaServiceProvider, NotificationProvider, $locationProvider) {
    //Set default google site key    
    if (window.RecaptchaSiteKey) {
        vcRecaptchaServiceProvider.setSiteKey(window.RecaptchaSiteKey);
        if (window.webApp && window.webApp.ga && window.webApp.ga.recaptchaSize)
        {
            //console.warn('Minh: setSize', window.webApp.ga.recaptchaSize);
            vcRecaptchaServiceProvider.setSize(window.webApp.ga.recaptchaSize);
        }
    }
    else console.warn("system - no google re-captcha key");

    //Set default notification
    NotificationProvider.setOptions({
        delay: 3000,
        positionX: "center",
        //classNoti: 'shopping-noti'
    });

    $httpProvider.interceptors.push(['$q', '$timeout', function ($q, $timeout) {
        return {
            'request': function (config) {
                // Contains the data about the request before it is sent.
                if (config.beforeSend)
                    config.beforeSend(config);
                // Return the config or wrap it in a promise if blank.
                return config || $q.when(config);
            },

            'responseError': function (rejection) {
                if (!(angular.isDefined(rejection.config) && angular.isDefined(rejection.config.suppressGloablErrorHandeler) && rejection.config.suppressGloablErrorHandeler == true)) {
                    console.error(rejection);
                }
                //btnSpinnerService.endLast();
                return $q.reject(rejection);
            },
            'response': function (response)
            {
                //btnSpinnerService.endLast();
                return response;
            }
        };
    }]);

    if (!$httpProvider.defaults.headers.get) {
        $httpProvider.defaults.headers.get = {};
    }

    // Answer edited to include suggestions from comments
    // because previous version of code introduced browser-related errors
    if (AWDSApp.util.isBrowserIE())
    {
        //disable IE ajax request caching
        $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Sun, 01 Jan 2017 05:00:00 GMT';
        //// extra
        $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
        $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
    }

    //console.warn('Minh: CONFIG user-portal', window.webApp.userPortal);
    if (window.webApp && window.webApp.userPortal)
    {
        $httpProvider.defaults.headers.common['User-Portal'] = window.webApp.userPortal;
    }

    $locationProvider.html5Mode({
        enabled: true,
        requireBase: false,
        rewriteLinks: false
    });
}]);



if (navigator.serviceWorker != null) {
    navigator.serviceWorker.getRegistrations().then(function (registrations) {
        for (var i = 0; i < registrations.length; i++) {
            registrations[i].unregister();
        }
    });
}


//------------------- Responsive Left Filter New Model Farm 
$(function () {//document.ready()
    $(".btn-filter-farm").click(function () {
        $(".content-filter-farm").toggleClass("active");
    });
    $(".content-filter-farm").click(function () {
        $(this).removeClass("active");
    })

})
function createStaticMenu(lProduct) {    
    addNewModelMakeToLeftFilter($(".filter-list .list-menu"), lProduct)
}
function addNewModelMakeToLeftFilter(element, lProducts) {
    var strHtml = "";
    for (var i = 0; i < lProducts.length; i++) {
        var item = lProducts[i];
        strHtml += '<li class=""><a href="' + item.link + '" target="' + item.target + '">' + item.name + '<i class="glyphicon glyphicon-menu-right"></i></a></li>';

    }
    $(element).append(strHtml);
};
ngAWDSApp.directive("btnComprareDir", ['$timeout', '$rootScope', 'ModalService', 'localstorageFactory', 'Notification', function ($timeout, $rootScope, ModalService, localstorageFactory, Notification) {
    return {
        restrict: 'EA',
        replace: true, //*** will replace the directives original HTML on view
        scope: {
            fieldNameId: '@',
            dataItem: '=dItem',
            photo: '@'
        },
        template: '<div><a class="btn-link" ng-click="addToCompare()" ng-show="oItem.IsAdded == false">Add to my compare list</a>'
                    + '<a class="btn-link" ng-show="oItem.IsAdded">Added to my compare list</a></div>',
        link: function ($scope, $element, attr, ngModel) {
            $timeout(function () {
                $scope.init();
            }, 1);
        },
        controller: ['$scope', '$element', '$attrs', '$parse', function ($scope, $element, $attrs, $parse) {
            var sLocalStorageKey = 'compare';
            $scope.lCompares = [];
            $scope.oItem = [];
            $scope.init = function () {
                sLocalStorageKey += $scope.fieldNameId;
                $scope.dataItem[$scope.fieldNameId] = $rootScope.webApp.util.toNumber($scope.dataItem[$scope.fieldNameId]);                
                $scope.mappingDataItem();

                $scope.lCompares = localstorageFactory.getObject(sLocalStorageKey, []);
                if ($scope.lCompares.length > 0) {
                    for (var i = 0; i < $scope.lCompares.length; i++) {
                        if ($scope.lCompares[i].id == $scope.dataItem[$scope.fieldNameId]) {
                            $scope.oItem.IsAdded = true;
                            break;
                        }
                    }
                }
            }
            $scope.mappingDataItem = function () {
                var oItem = {
                    id: $scope.dataItem[$scope.fieldNameId],
                    ref: $scope.fieldNameId,
                    photo: $scope.photo,
                    title: null,
                    desc: null,
                    IsAdded: false
                };
                switch ($scope.fieldNameId) {
                    case 'NewModelID':
                        oItem.title = $scope.dataItem.Model + ' ' + $scope.dataItem.Series;
                        oItem.desc = $scope.dataItem.BodyAxles + ' Caravan';                        
                        break;
                    default:
                        break;
                }
                $scope.oItem = oItem;
            }
            //#region HTMLEvent
            $scope.getUrlHasPrefix = function (sAction) {
                return $rootScope.webApp.util.getUrlHasPrefix(sAction);
            }
            //#endregion


            $scope.addToCompare = function () {
                $scope.lCompares = localstorageFactory.getObject(sLocalStorageKey, []);
                if ($scope.lCompares.length > 2) {
                    Notification.warning('Your compare list is full!');
                } else {
                    $scope.oItem.IsAdded = true;
                    $scope.lCompares.pushIfNotExist($scope.oItem, function (x) { return $scope.oItem.id == x.id; });
                    localstorageFactory.setObject(sLocalStorageKey, $scope.lCompares);
                }

            }
        }]
    }
}]);
//angular.module('AWDSApp').requires.push('ui.bootstrap');//rzSliderForceRender
ngAWDSApp.directive("btnLogin", ['$timeout', '$rootScope', 'ModalService', function ($timeout, $rootScope, ModalService) {
    return {
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        scope: {
            bActiveLoginTab: '@',
            templateUrl: '@'
        },
        template: '<a class="btn-link" ng-click="openLoginModal()"><i class="login-icon-cls"></i> <span>Log in / Sign up</span></a>',
        controller: ['$scope', '$element', '$attrs', '$parse', function ($scope, $element, $attrs, $parse) {

            $scope.bActiveLoginTab = $rootScope.webApp.util.toBool($scope.bActiveLoginTab);

            //#region HTMLEvent
            $scope.getUrlHasPrefix = function (sAction) {
                return $rootScope.webApp.util.getUrlHasPrefix(sAction);
            }
            //#endregion


            $scope.openLoginModal = function () {
                ModalService.loginModal({
                    templateUrl: $scope.templateUrl,// 'modalLoginUserTemplate.html',
                    bActiveLoginTab: true
                });
            }
        }]
    }
}])

;
ngAWDSApp.directive("btnLogout", ['$timeout', '$rootScope', function ($timeout, $rootScope) {
    return {
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        scope: {
            returnUrl: '@'
        },
        template: '<a class="btn-link" ng-click="loggout()"><span>Logout</span></a>',
        controller: ['$scope', '$element', '$attrs', 'HttpFactory', function ($scope, $element, $attrs, HttpFactory) {

            var sUrlLoginController = "Stock/ShoppingCart";
            var oUrlLoginAction = {
                logout: sUrlLoginController + '/Logout'
            }

            $scope.loggout = function () {
                HttpFactory.post({}, oUrlLoginAction.logout).then(function (response) {
                    //var jsonData = response.data;
                    if ($rootScope.webApp.util.hasVal($scope.returnUrl)) {
                        $rootScope.webApp.util.redirectTo($rootScope.webApp.util.getUrlHasPrefix($scope.returnUrl));
                    } else {
                        $rootScope.$broadcast('btnLogoutDirSuccess', response); //*** the $rootScope.$broadcast event will fire an event down the $scope.$on('btnLogoutDirSuccess', {}) events
                    }
                }, function () { })
            }
        }]
    }
}]);
ngAWDSApp.directive('convertToLowercase', function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, modelCtrl) {
            //*** Minh: not working with <select>
            var capitalize = function (inputValue) {
                if (inputValue == undefined) inputValue = '';
                var capitalized = inputValue.toLowerCase();
                if (capitalized !== inputValue) {
                    // see where the cursor is before the update so that we can set it back
                    var selection = element[0].selectionStart;
                    modelCtrl.$setViewValue(capitalized);
                    modelCtrl.$render();
                    // set back the cursor after rendering
                    element[0].selectionStart = selection;
                    element[0].selectionEnd = selection;
                }
                return capitalized;
            }
            modelCtrl.$parsers.push(capitalize);
            capitalize(scope[attrs.ngModel]); // capitalize initial value
        }
    };
});;
ngAWDSApp.directive('convertToNumber', function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, ngModel) {
            ngModel.$parsers.push(function (val) {
                return val != null ? parseInt(val, 10) : null;
            });
            ngModel.$formatters.push(function (val) {
                return val != null ? '' + val : null;
            });
        }
    };
});

ngAWDSApp.directive('convertToFloat', function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, ngModel) {
            ngModel.$parsers.push(function (val) {
                return val != null ? parseFloat(val) : null;
            });
            ngModel.$formatters.push(function (val) {
                return val != null ? '' + val : null;
            });
        }
    };
});
;
ngAWDSApp.directive("filterAutocomplete", ['$timeout', '$q', '$compile', '$window', '$document', function ($timeout, $q, $compile, $window, $document) {
    
    var defaultOptions = {
        /**
        * A template for the dropddown list. it no include another templates (itemTemplate, loadMoreTemplate,...)
        * @default null. if null, will itemTemplate logic
        */
        template: null,
        /**
         * A template for the dropddown list item. For example "<p ng-bind-html='renderItem.Make'></p>";
         * Or using interpolation "<p>{{renderItem.Make}}></p>".
         * @default "<p>{{renderItem.Text}}></p>"
         */
        itemTemplate: '<p>{{renderItem.Text}}</p>',        
        /**
         * The template used to display the message when no items match the search text.
         * @default "<span>No results match '{{searchText}}'></span>"
         */
        noMatchTemplate: "<span>No results match '{{searchText}}'</span>",
        loadMoreTemplate: '<span> {{renderItems.length}}/{{totalRecords}} Load more</span>',

        /**
         * CSS class applied to the dropdown container.
         * @default null
         */
        containerCssClass: null,
        /**
         * CSS class applied to the selected list element.
         * @default auto-complete-item-selected
         */
        selectedCssClass: 'auto-complete-item-selected',
        fieldValue: 'Value',
        placeholder: null,
        searchIconEnable: true,
        /**
         * If true displays the dropdown list when the search-icon gets click.
         * @default false
         */
        activateOnClickSearchIcon: false,
        /**
         * Set to true to enable callback when invoked load more
         * @default false
         */
        loadMoreCallbackEnable: false,
        /**
         * CSS class applied to the search-text container.
         * @default null
         */
        searchTextCssClass: null,
        /**
         * Width in "px" of the input. This can also be applied using CSS.
         * @default 'auto'
         */
        searchTextWidth: 'auto',
        /**
         * Width in "px" of the dropddown list. This can also be applied using CSS.
         * @default 'auto'
         */
        dropdownWidth: 'auto',
        /**
         * Maximum height in "px" of the dropddown list. This can also be applied using CSS.
         * @default 'auto'
         */
        dropdownHeight: 'auto',
        /**
         * Minimum number of characters required to display the dropdown.
         * @default 1
         */
        minimumChars: 1,
        /**
         * Maximum number of items to render in the list.
         * @default 20
         */
        maxItemsToRender: 20,
        /**
         * If true displays the dropdown list when the textbox gets focus.
         * @default false
         */
        activateOnFocus: false,
        /**
         * Callback to get the data for the dropdown. The callback receives the search text as the first parameter.
         * If paging is enabled the callback receives an object with "pageIndex" and "pageSize" properties as the second parameter.
         * This function must return a promise.
         * @default angular.noop
         */
        data: angular.noop,
        /**
         * Callback before the "data" callback is invoked.
         * @default angular.noop
         */
        loading: angular.noop,
        /**
         * Callback after the items are rendered in the dropdown
         * @default angular.noop
         */
        loadingComplete: angular.noop,
        /**
         * Callback after the plugin is initialized and ready. The callback receives an object with the following methods:
         * @default angular.noop
         */
        ready: angular.noop,
        /**
         * Callback after press enter. The callback receives an object with a search-text
         * @default angular.noop
         */
        search: angular.noop,
        /**
         * Callback if loadMoreCallbackEnable true. The callback receives an object with a search-text
         * @default angular.noop
         */
        loadMore: angular.noop,
        /**
         * Callback if activateOnClickSearchIcon true. The callback receives an object with a search-text
         * @default angular.noop
         */
        searchIconCallback: angular.noop
    };
    var DOM_EVENT = {
        RESIZE: 'resize',
        SCROLL: 'scroll',
        CLICK: 'click',
        KEYDOWN: 'keydown',
        FOCUS: 'focus',
        INPUT: 'input'
    };

    var KEYCODE = {
        TAB: 9,
        BACKSPACE: 8,
        ENTER: 13,
        CTRL: 17,
        ALT: 18,
        ESCAPE: 27,
        LEFTARROW: 37,
        UPARROW: 38,
        RIGHTARROW: 39,
        DOWNARROW: 40,
        DELETE: 46,
        MAC_COMMAND_LEFT: 91,
        MAC_COMMAND_RIGHT: 93
    };
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {            
            cOptions: '&' //– Behavior binding / Method binding                 
        },
        link: function ($scope, $element, $attrs) {

            $timeout(function () {
                // execute the options expression
                
                $q.when($scope.cOptions()).then(_initialize);
            });
            function _initialize(options) {
                options = options || {};
                $scope.init(angular.extend({}, defaultOptions, options));
                
                _initializeContainer();
                _wireupEvents();
            }
            function _initializeContainer() {
                $scope.container = _getDefaultContainer();
                if ($scope.options.containerCssClass) {
                    $scope.container.addClass($scope.options.containerCssClass);
                }
                if ($scope.options.searchTextCssClass) {
                    $scope.container.find('.search-text-container').addClass($scope.options.searchTextCssClass);
                }
                if ($scope.options.searchTextWidth) {
                    $scope.container.find('.search-text-container input.search-text').width($scope.options.searchTextWidth);
                }
                
                $element.append($scope.container);
                // keep a reference to the <ul> element
                $scope.elementUL = angular.element($scope.container[0].querySelector('ul.auto-complete-results'));
                $scope.targetSearchText = angular.element($scope.container[0].querySelector('input.search-text'));
                if ($scope.options.searchIconEnable) {
                    $scope.targetSearchIcon = angular.element($scope.container[0].querySelector('.icon-search'));
                }

                console.warn('Minh::::' + $element.find('input.search-text').length, $element.find('input.search-text').val());
            }
            function _getDefaultContainer() {
                var linkFn = $compile(_getContainerTemplate());
                return linkFn($scope);
            }
            function _getContainerTemplate() {
                var html = '';
                html += '<div class="auto-complete-container">'
                        + '<div class="search-text-container">'
                            + '<input type="text" class="search-text" ng-model="searchText" placeholder="{{options.placeholder}}" />'
                            + '<i class="glyphicon glyphicon-search icon-search ng-hide" ng-show="options.searchIconEnable"></i>'
                        + '</div>';
                html += _getDropdownListTemplate();
                html += '</div>';
                return html;
            }
            function _getDropdownListTemplate() {
                var html = '';
                var sItem = '';
                if ($scope.options.template != null) {
                    html = $scope.options.template;
                }
                else {
                    if ($scope.options.itemTemplate.indexOf('<li') == 0) {
                        sItem = $scope.options.itemTemplate;
                    } else {
                        sItem = '<li ng-if="renderItems.length" class="item"'
                                    + 'ng-repeat="renderItem in renderItems"'
                                    + 'data-index="{{$index}}"'
                                    + 'ng-class="getSelectedCssClass($index)"'
                                    + 'ng-click="selectItem($index, true)">'
                                    + $scope.options.itemTemplate
                                + '</li>'
                    }
                    html += '<ul class="auto-complete-results" ng-show="containerVisible">'
                                + sItem
                                + '<li ng-if="!renderItems.length"'
                                    + 'class="item no-match">'
                                    + $scope.options.noMatchTemplate
                                + '</li>'
                                + '<li ng-if="renderItems.length"'
                                    + 'class="item load-more"'
                                    + 'ng-click="loadMore()">'
                                    + $scope.options.loadMoreTemplate
                                + '</li>'
                            + '</ul>';
                }
                              
                return html;
            }

            function _wireupEvents() {
                var elementSearchText = $element.find('input.search-text');
                // when the target(textbox) gets focus activate the corresponding container
                elementSearchText.on(DOM_EVENT.FOCUS, function () {
                    $scope.$evalAsync(function () {                        
                        if ($scope.options.activateOnFocus) {
                            _waitAndQuery(elementSearchText.val(), 100);
                        }
                    });
                });

                elementSearchText.on(DOM_EVENT.INPUT, function () {
                    $scope.$evalAsync(function () {
                        _tryQuery(elementSearchText.val());
                    });
                });

                elementSearchText.on(DOM_EVENT.KEYDOWN, function (event) {
                    var $event = event;
                    $scope.$evalAsync(function () {
                        _handleElementKeyDown($event);
                    });
                });
                //$scope.container.find('ul').on(DOM_EVENT.SCROLL, function () {
                //    if (!$scope.options.pagingEnabled) {
                //        return;
                //    }
                //    var list = this;
                //    $scope.$evalAsync(function () {
                //        if (!$scope.containerVisible) {
                //            return;
                //        }
                //        // scrolled to the bottom?
                //        if ((list.offsetHeight + list.scrollTop) >= list.scrollHeight) {
                //            $scope.tryLoadNextPage();
                //        }
                //    });
                //});
                //$document.on(DOM_EVENT.KEYDOWN, function (event) {
                //    var $event = event;
                //    $scope.$evalAsync(function () {
                //        _handleDocumentKeyDown($event);
                //    });
                //});
                $document.on(DOM_EVENT.CLICK, function (event) {
                    var $event = event;
                    $scope.$evalAsync(function () {
                        _handleDocumentClick($event);
                    });
                });
                //// $window is a reference to the browser's window object
                //angular.element($window).on(DOM_EVENT.RESIZE, function () {
                //    if ($scope.options.hideDropdownOnWindowResize) {
                //        $scope.$evalAsync(function () {
                //            $scope.autoHide();
                //        });
                //    }
                //});
            }
           
            function _waitAndQuery(searchText, delay) {
                var elementSearchText = $element.find('input.search-text');
                // wait few millisecs before calling query(); this to check if the user has stopped typing
                var promise = $timeout(function () {
                    // has searchText unchanged?
                    if (searchText === elementSearchText.val()) {
                        $scope.query(searchText);
                    }

                    //cancel the timeout
                    $timeout.cancel(promise);

                }, (delay || 300));
            }
            function _containerContainsTarget(target) {
                // use native Node.contains
                // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains
                var container = $scope.container[0];
                if (angular.isFunction(container.contains) && container.contains(target)) {
                    return true;
                }

                // otherwise use .has() if jQuery is available
                if (window.jQuery && angular.isFunction($scope.container.has) &&
                    $scope.container.has(target).length > 0) {

                    return true;
                }

                // assume target is not in container
                return false;
            }
            function _handleDocumentClick(event) {
                var elementSearchText = $element.find('input.search-text');
                //// ignore inline
                //if ($scope.isInline()) {
                //    return;
                //}

                // no container. probably destroyed in scope $destroy
                if (!$scope.container) {
                    return;
                }

                // ignore target click
                if (event.target === $scope.targetSearchText[0]) {
                    event.stopPropagation();
                    return;
                }

                
                if (event.target === $scope.targetSearchIcon[0]) {                    
                    if ($scope.options.activateOnClickSearchIcon)
                    {
                        if ($scope.options.searchIconCallback) {
                            $scope.searchIconClicked(elementSearchText.val());
                        } else {
                            _waitAndQuery(elementSearchText.val(), 100);
                        }
                        
                    }
                }

                if (_containerContainsTarget(event.target)) {
                    event.stopPropagation();
                    return;
                }
                $scope.autoHide();
            }
            function _ignoreKeyCode(keyCode) {
                return [
                    KEYCODE.TAB,
                    KEYCODE.ALT,
                    KEYCODE.CTRL,
                    KEYCODE.LEFTARROW,
                    KEYCODE.RIGHTARROW,
                    KEYCODE.MAC_COMMAND_LEFT,
                    KEYCODE.MAC_COMMAND_RIGHT
                ].indexOf(keyCode) !== -1;
            }

            function _handleElementKeyDown(event) {
                var keyCode = event.charCode || event.keyCode || 0;

                if (_ignoreKeyCode(keyCode)) {
                    return;
                }

                switch (keyCode) {
                    case KEYCODE.UPARROW:
                        $scope.scrollToPreviousItem();

                        event.stopPropagation();
                        event.preventDefault();

                        break;

                    case KEYCODE.DOWNARROW:
                        $scope.scrollToNextItem();

                        event.stopPropagation();
                        event.preventDefault();

                        break;

                    case KEYCODE.ENTER:

                        if ($scope.selectedIndex == -1) {
                            $scope.search();
                        }
                        else {
                            $scope.selectItem($scope.selectedIndex, true);
                        }

                        //prevent postback upon hitting enter
                        event.preventDefault();
                        event.stopPropagation();

                        break;
                    case KEYCODE.BACKSPACE:
                    case KEYCODE.DELETE:
                        var elementSearchText = $element.find('input.search-text');
                        $scope.removedSearchText(elementSearchText.val());

                        //prevent postback upon hitting enter
                        event.preventDefault();
                        event.stopPropagation();

                        break;

                    case KEYCODE.ESCAPE:
                        $scope.restoreOriginalText();
                        $scope.autoHide();

                        event.preventDefault();
                        event.stopPropagation();

                        break;

                    default:
                        break;
                }
            }
            function _tryQuery(searchText) {
                // query only if minimum number of chars are typed; else hide dropdown
                if (($scope.options.minimumChars === 0)
                    || (searchText && searchText.trim().length !== 0 && searchText.length >= $scope.options.minimumChars)) {
                    _waitAndQuery(searchText);
                    return;
                } 
            }
        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            var originalSearchText = null;
            var queryCounter = 0;
            var currentPageIndex = 0;
            var endOfPagedList = false;
            var dataLoadInProgress = false;

            $scope.selectedIndex = -1;
            $scope.containerVisible = false;
            $scope.totalRecords = 0;
            $scope.renderItems = [];
            
            var dataRenderFieldNames = [];
            var bScrolledToloadMoreItem = false;


            $scope.init = function (options) {
                $scope.options = options;
                _safeCallback($scope.options.ready, publicApi);
            }
            $scope.query = function (searchText) {
                //$scope.selectedIndex = -1;
                $scope.emptyRenderItems();

                currentPageIndex = 0;

                return _query(searchText, 0);
            }
            $scope.selectItem = function (itemIndex, closeDropdownAndRaiseCallback) {
                //var item = $scope.renderItems[itemIndex];
                
                //var item = $scope[_getRenderFieldNameByIndex(itemIndex)][itemIndex];
                if (bScrolledToloadMoreItem == false) {
                    var item = _getItemByIndex(itemIndex);
                    if (!item) {
                        return;
                    }
                    $scope.selectedIndex = itemIndex;
                    //_setSearchText(item[$scope.options.fieldValue]);
                    _setSearchText(item[_getFieldValueByIndex(itemIndex)]);

                    if (closeDropdownAndRaiseCallback) {
                        _hideDropdown();
                        _safeCallback($scope.options.itemSelected, { item: item, renderType: _getRenderFieldNameByIndex(itemIndex), searchText: $scope.searchText });
                    }

                }
                else
                {
                    $scope.selectedIndex = itemIndex;
                    if (closeDropdownAndRaiseCallback)
                    {
                        $scope.loadMore();
                    }                    
                }
            };
            $scope.selectItemByType = function (item, sType) {                
                _hideDropdown();
                _safeCallback($scope.options.itemSelectedByType, { item: item, type: sType, searchText: $scope.searchText });

            };
            $scope.search = function () {
                if (angular.isFunction($scope.options.search))
                {
                    _hideDropdown();
                    _safeCallback($scope.options.search, { searchText: $scope.searchText });
                }                
            }

            $scope.searchIconClicked = function () {
                if ($scope.options.searchIconCallback) {
                    _hideDropdown();
                    _safeCallback($scope.options.searchIconCallback, { searchText: $scope.searchText });
                }
            }

            $scope.loadMore = function () {
                if ($scope.options.loadMoreCallbackEnable) {
                    _hideDropdown();
                    _safeCallback($scope.options.loadMore, { searchText: $scope.searchText });
                } else {
                    _loadNextPage();
                }
            }
            
            $scope.getSelectedCssClass = function (itemIndex) {
                return (itemIndex === $scope.selectedIndex) ? $scope.options.selectedCssClass : '';
            };
            $scope.emptyRenderItems = function () {                
                $scope.selectedIndex = -1;
                $scope.renderItems = [];
                for (var i = 0; i < dataRenderFieldNames.length; i++) {
                    $scope[dataLoadInProgress[i]] = [];
                }
            }
            $scope.restoreOriginalText = function () {
                if (!originalSearchText) {
                    return;
                }

                _setSearchText(originalSearchText);
            };
            $scope.removedSearchText = function (searchText) {

                if (!(searchText && searchText.trim().length !== 0))
                {
                    _safeCallback($scope.options.searchTextRemoved, {});
                }
            }
            $scope.scrollToNextItem = function () {
                var itemIndex = _getItemIndexFromOffset(1);
                if (itemIndex === -1)
                {                    
                    return;
                }

                _scrollToItem(itemIndex);

                //if (_shouldLoadNextPageAtItemIndex(itemIndex)) {
                //    _loadNextPage();
                //}
            };
            $scope.scrollToPreviousItem = function () {
                var itemIndex = _getItemIndexFromOffset(-1);
                if (itemIndex === -1) {
                    return;
                }

                _scrollToItem(itemIndex);
            };
            $scope.show = function () {
                // the show() method is called after the items are ready for display
                // the textbox position can change (ex: window resize) when it has focus
                // so reposition the dropdown before it's shown
                _positionDropdown();

                // callback
                _safeCallback($scope.options.dropdownShown);
            }
            $scope.autoHide = function () {
                if ($scope.options) {
                    _hideDropdown();
                }
            };


            function _loadNextPage() {
                return _query(originalSearchText, (currentPageIndex + 1));
            }
            function _query(searchText, pageNumber) {
                var params = {
                    searchText: searchText,
                    pageNumber: pageNumber,
                    queryId: ++queryCounter
                };

                return _queryInternal(params);
            }
            function _queryInternal(params) {               

                // backup original search term in case we need to restore if user hits ESCAPE
                originalSearchText = params.searchText;

                dataLoadInProgress = true;

                _safeCallback($scope.options.loading);

                return $q.when($scope.options.data(params),
                    function successCallback(responses) {
                        // verify that the queryId did not change since the possibility exists that the
                        // search text changed before the 'data' promise was resolved. Say, due to a lag
                        // in getting data from a remote web service.
                        if (_didQueryIdChange(params)) {
                            _hideDropdown();
                            return;
                        }

                        // verify that the searchText did not change since the possibility exists that the
                        // search text changed before the 'data' promise was resolved. Say, due to a lag
                        // in getting data from a remote web service.
                        if (_didValueChange(params)) {
                            //_hideDropdown();
                            return;
                        }

                        try {
                            /**** Minh::20230925:: change result obj to array
                            * support render multiple data in autocomplete template
                            */
                            var result;
                            var cbDataRender = [];
                            var iTotalRecords = 0;
                            dataRenderFieldNames = [];

                            if (angular.isArray(responses)) {
                                for (var i = 0; i < responses.length; i++) {
                                    if (responses[i].fieldNameInTemplate == 'renderItems') {
                                        result = responses[i];
                                    } else {
                                        dataRenderFieldNames.push(responses[i].fieldNameInTemplate);
                                        $scope[responses[i].fieldNameInTemplate] = responses[i].records;
                                        iTotalRecords += responses[i].totalRecords;
                                    }
                                }

                            } else {
                                result = responses;//*** keep old way
                            }

                            if (result) {
                                // in case of paged list we add to the array instead of replacing it
                                angular.forEach(result.records, function (item) {
                                    $scope.renderItems.push(item);
                                });
                                iTotalRecords += result.totalRecords;
                                //$scope.totalRecords = result.totalRecords;
                            }

                            $scope.totalRecords = iTotalRecords;
                            $scope.searchText = params.searchText;

                            //$scope.searchText = params.searchText;
                            currentPageIndex = params.pageNumber;

                            $scope.show();
                            // callback
                            _safeCallback($scope.options.loadingComplete, {
                                dataSources: responses,
                                renderItems: $scope.renderItems,
                                setOptions: _setOptions
                            });
                        } catch (e) {
                            console.error('unknown error', e);
                        }
                    },
                    function errorCallback(error) {
                        _hideDropdown();

                        _safeCallback($scope.options.loadingComplete, { error: error });
                    }).then(function () {
                        dataLoadInProgress = false;
                    });
            }
            function _didValueChange(params) {
                var elementSearchText = $element.find('input.search-text');
                console.warn('Minh: _didValueChange: ', params.queryId + '; counter: ' + queryCounter, params, elementSearchText.val());
                return (params.searchText !== elementSearchText.val());
            }
            function _didQueryIdChange(params) {
                console.warn('Minh: _didQueryIdChange: ', params.queryId + '; counter: ' + queryCounter, params);
                return (params.queryId !== queryCounter);
            }
            function _hideDropdown() {
                if (!$scope.containerVisible) {
                    return;
                }

                // reset scroll position
                //$scope.elementUL[0].scrollTop = 0;
                $scope.containerVisible = false;
                $scope.emptyRenderItems();

                _resetSearchText();

                // callback
                _safeCallback($scope.options.dropdownHidden);
            }
            function _resetSearchText() {
                //originalSearchText = $scope.searchText = null;
                originalSearchText = null;
                currentPageIndex = 0;
            }
            
            function _scrollToItem(itemIndex) {
                if (!$scope.containerVisible) {
                    return;
                }

                //dataRenderFieldNames
                $scope.selectItem(itemIndex, false);

                var attrSelector = 'li[data-index="' + itemIndex + '"]';
                if (itemIndex == _getTotalItemRendered())
                {
                    attrSelector = 'li.load-more';
                }

                // use jquery.scrollTo plugin if available
                // http://flesler.blogspot.com/2007/10/jqueryscrollto.html
                if (window.jQuery && window.jQuery.scrollTo) {  // requires jquery to be loaded
                    $scope.elementUL.scrollTo($scope.elementUL.find(attrSelector));
                    return;
                }

                var li = $scope.elementUL[0].querySelector(attrSelector);
                if (li) {
                    // this was causing the page to jump/scroll
                    //    li.scrollIntoView(true);
                    $scope.elementUL[0].scrollTop = li.offsetTop;
                }
            }
            function _safeCallback(fn, args) {
                if (!angular.isFunction(fn)) {
                    return;
                }

                try {
                    return fn.call($element, args);
                } catch (ex) {
                    //ignore
                }
            }
            function _setOptions(options) {
                if (_.isEmpty(options)) {
                    return;
                }
                angular.forEach(options, function (value, key) {
                    if (defaultOptions.hasOwnProperty(key)) {
                        $scope.options[key] = value;
                    }
                });
            }
            
            function _setSearchText(sVal) {
                $scope.searchText = sVal;
            }
            function _getTotalItemRendered() {
                var itotalItemRendered = $scope.renderItems.length;
                if (dataRenderFieldNames.length > 0) {
                    for (var i = 0; i < dataRenderFieldNames.length; i++) {
                        itotalItemRendered = itotalItemRendered + $scope[dataRenderFieldNames[i]].length;
                    }
                }
                return itotalItemRendered;
            }
            function _getItemIndexFromOffset(itemOffset) {
                var itemIndex = $scope.selectedIndex + itemOffset;
                bScrolledToloadMoreItem = false;

                //if (itemIndex >= $scope[_getRenderFieldNameByIndex(itemIndex)].length) {
                //    return -1;
                //}
                var itotalItemRendered = $scope.renderItems.length;
                if (dataRenderFieldNames.length > 0)
                {
                    for (var i = 0; i < dataRenderFieldNames.length; i++) {
                        itotalItemRendered = itotalItemRendered + $scope[dataRenderFieldNames[i]].length;
                    }                    
                }

                
                if (itemIndex >= itotalItemRendered) {
                    if ($scope.options.loadMoreCallbackEnable && itemIndex == itotalItemRendered)
                    {
                        bScrolledToloadMoreItem = true;
                        //load more item
                        return itemIndex;
                    } else {
                        return -1;
                    }
                }

                return itemIndex;
            }
            function _getRenderFieldNameByIndex(itemIndex) {
                return _getAttrByIndexFromLI(itemIndex, 'render-type', 'renderItems');
            }
            function _getFieldValueByIndex(itemIndex) {
                return _getAttrByIndexFromLI(itemIndex, 'field-type', $scope.options.fieldValue);//for text search
            }
            function _getFieldIdByIndex(itemIndex) {
                return _getAttrByIndexFromLI(itemIndex, 'field-id', null);
            }
            function _getFieldIdValByIndex(itemIndex) {
                return _getAttrByIndexFromLI(itemIndex, 'fiedl-id-val', null);
            }
            function _getItemByIndex(itemIndex) {
                var item;// = $scope[_getRenderFieldNameByIndex(itemIndex)][itemIndex];
                if (dataRenderFieldNames.length > 0)
                {
                    var propNameOfScope = _getRenderFieldNameByIndex(itemIndex);
                    var propFieldIdOfItem = _getFieldIdByIndex(itemIndex);
                    var sFieldIdValOfItem = _getFieldIdValByIndex(itemIndex);
                    if (propFieldIdOfItem != null && sFieldIdValOfItem != null) {
                        for (var i = 0; i < $scope[propNameOfScope].length; i++) {
                            var sFieldIdOfItem = $scope[propNameOfScope][i][propFieldIdOfItem];
                            if (sFieldIdOfItem == sFieldIdValOfItem) {
                                item = $scope[propNameOfScope][i];
                                break;
                            }
                        }
                    }
                    else {
                        item = $scope['renderItems'][itemIndex];
                    }
                } else {
                    item = $scope['renderItems'][itemIndex];
                }

                return item;
            }
            function _getAttrByIndexFromLI(itemIndex, attrName, defaultVal) {
                var attrSelector = 'li[data-index="' + itemIndex + '"]';
                if ($scope.elementUL.find(attrSelector).length > 0) {
                    var attrVal = $scope.elementUL.find(attrSelector).attr(attrName);
                    if (attrVal) {
                        return attrVal;
                    }
                }
                return defaultVal;
            }

            function _positionDropdown() {
                var dropdownWidth = null;
                // same as textbox width
                dropdownWidth = $scope.targetSearchText[0].getBoundingClientRect().width + 'px';
                //if ($scope.options.searchIconEnable) {
                //    dropdownWidth = $scope.targetSearchText[0].getBoundingClientRect().width + ($scope.targetSearchIcon[0].getBoundingClientRect().width * 2) + 'px';
                //}
                //dropdownWidth = $element[0].getBoundingClientRect().width + 'px';
                $scope.container.css({ 'width': dropdownWidth });
                $scope.elementUL.css({ 'width': dropdownWidth });

                if ($scope.options.dropdownHeight && $scope.options.dropdownHeight !== 'auto') {
                    $scope.elementUL.css({ 'max-height': $scope.options.dropdownHeight });
                }
                _positionUsingDomAPI();
            }
            function _positionUsingDomAPI() {
                var rect = $scope.targetSearchText[0].getBoundingClientRect();
                var DOCUMENT = $document[0];

                var scrollTop = DOCUMENT.body.scrollTop || DOCUMENT.documentElement.scrollTop || $window.pageYOffset;
                var scrollLeft = DOCUMENT.body.scrollLeft || DOCUMENT.documentElement.scrollLeft || $window.pageXOffset;

                $scope.container.css({
                    'left': rect.left + scrollLeft + 'px',
                    'top': rect.top + rect.height + scrollTop + 'px'
                });
                var maxZindex = _maxZindex();
                $scope.elementUL.css({
                    //'left': rect.left + scrollLeft + 'px',
                    //'top': rect.top + rect.height + scrollTop + 'px',
                    'z-index': maxZindex + 1,
                    'position': 'absolute',
                    'background': 'wheat'
                });

                $scope.containerVisible = true;
            }
            function _maxZindex() {
                var maxZ = Math.max.apply(null, $.map($('body > *'), function (e, n) {
                        return parseInt($(e).css('z-index')) || 1;
                    })
                );
                return maxZ;
                //el.closest(".k-dialog").css('z-index', maxZ + 1);
                //$(".k-overlay").css('z-index', maxZ + 1);
            }
            var publicApi = (function () {
                return {
                    setOptions: _setOptions,
                    //positionDropdown: _positionDropdownIfVisible,
                    //hideDropdown: _hideDropdown
                };
            })();

        }]

    }
}]);;
ngAWDSApp.directive("filterCheckbox", ['$timeout', '$filter', function ($timeout, $filter) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cNgModel: '=', //two-way binding
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
            staffDropDown: '=' //two-way binding; will store the kendo drop down object, you can access from the parent controller using the bound varable on the view
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        template: '<div class="ckb-group">'
                    + '<div class="search-box" ng-show="cOptions.hasSearchText">'
                        + '<input type="text" class="form-control" ng-model="cOptions.sSearchText" placeholder="{{cOptions.placeholder}}" />'
                        + '<span><i class="glyphicon glyphicon-search"></i></span>'
                    + '</div>'
                    + '<div ng-repeat="x in (lFiltered = (cOptions.filterData | filter:{\'Text\': cOptions.sSearchText})) | orderBy: [\'!Sort\', \'Sort\', \'Text\'] | limitTo:cOptions.iLimitTo">'
                        + '<input type="checkbox" name="ckbItem{{cOptions.fieldNameInFilter}}[]" id="ckbItem{{cOptions.fieldNameInFilter}}{{$index}}" value="{{x.Value}}" ng-model="x.Selected" ng-click="selectItem(x)">'
                        + '<label for="ckbItem{{cOptions.fieldNameInFilter}}{{$index}}">{{displayText(x)}}</label>'
                    + '</div>'
                    + '<div ng-show="lFiltered.length > cOptions.iLimitTo">'
                        + '<span class="lbl-more-options" ng-click="cOptions.iLimitTo = lFiltered.length">{{(lFiltered.length - 8)}} more options...</span>'
                    + '</div>'
                    + '<div ng-show="lFiltered.length == cOptions.iLimitTo">'
                        + '<span class="lbl-less-options" ng-click="cOptions.iLimitTo = 8">less options...</span>'
                    + '</div>'                    
				+ '</div>'
		,
        link: function ($scope, $element, $attrs) {

        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                fieldNameInFilter: null,
                fieldNameAllInFilter: null,
                isShowDescription: false,
                hasEmptyAtFirst: false,
                hasSearchText: true,
                sSearchText: '',
                placeholder: '',
                iLimitTo: 8,
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in                
            }            
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);
            $scope.displayText = function (item) {
                return $scope.cOptions.isShowDescription ? item.Description : item.Text;
            }
            
            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                angular.element($element).addClass('loading');
                $scope.cOptions.filterData = null;
            }
            $scope.cOptions.loadFilter = function (filter, filterData, isHideOnNoData, isMoveSelectedToTop) {
                angular.element($element).addClass('loading');
                isMoveSelectedToTop = (angular.isUndefined(isMoveSelectedToTop) || isMoveSelectedToTop == null) ? false : isMoveSelectedToTop;
                $scope.cOptions.filterData = filterData;

                //*** set selected item if fieldName has val
                if ($rootScope.webApp.util.hasVal(filter[$scope.cOptions.fieldNameInFilter])) {
                    

                    for (var j = 0; j < $scope.cOptions.filterData.length; j++)
                    {
                        if ($rootScope.webApp.util.includesAnyWithSplit($scope.cOptions.filterData[j].Value, filter[$scope.cOptions.fieldNameInFilter], ',')) {
                        
                            $scope.cOptions.filterData[j].Selected = true;

                            if (isMoveSelectedToTop == true)
                            {
                                $scope.cOptions.filterData[j].Sort = 9999;
                            }

                        } else {
                            $scope.cOptions.filterData[j].Selected = false;
                            //$scope.cOptions.filterData[j].Sort = null;
                        }
                    }

                    //var splitVal = filter[$scope.cOptions.fieldNameInFilter].split(',');
                    //for (var i = 0; i < splitVal.length; i++) {
                    //    for (var j = 0; j < $scope.cOptions.filterData.length; j++) {
                    //        if ($rootScope.webApp.util.indexOfLowerCase($scope.cOptions.filterData[j].Value, splitVal[i]))
                    //        {
                    //            $scope.cOptions.filterData[j].Selected = true;
                    //            break;
                    //        }
                    //    }
                    //}


                    if ($filter('filter')($scope.cOptions.filterData, function (x) { return x.Selected == true; }).length == $scope.cOptions.filterData.length) {
                        filter[$scope.cOptions.fieldNameAllInFilter] = true;
                    }
                }


                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if ($scope.cOptions.filterData.length < 1) {
                        angular.element($element).find('.ckb-group').hide();
                    } else {
                        angular.element($element).find('.ckb-group').show();
                    }
                }

                angular.element($element).removeClass('loading');
            }
            $scope.cOptions.updateBreadcrumb = function (filter, lBreadcrumbs)
            {
                lBreadcrumbs = lBreadcrumbs || [];
                var lSelectedItems = $filter('filter')($scope.cOptions.filterData, function (x) { return x.Selected == true; });
                var lBreadcrumbsTemp = $filter('filter')(lBreadcrumbs, function (x) { return x.fieldName == $scope.cOptions.fieldNameInFilter; });
                //var iTotalBreadcrumbsTemp = lBreadcrumbsTemp.length;

                var lDeletings = $filter('filter')(lBreadcrumbsTemp, function (bcItem) { return !lSelectedItems.inArray(function (item) { return bcItem.val == item.Value; }); });
                var lInsertings = $filter('filter')(lSelectedItems, function (item) { return !lBreadcrumbsTemp.inArray(function (bcItem) { return item.Value == bcItem.val; }); });
                
                if (lDeletings.length > 0) {
                    for (var i = 0; i < lDeletings.length; i++) {
                        lBreadcrumbs.removeItem(function (x) { return x.fieldName == lDeletings[i].fieldName && x.val == lDeletings[i].val; });
                    }
                }
                if (lInsertings.length > 0) {
                    for (var i = 0; i < lInsertings.length; i++) {
                        var oItem = {
                            fieldName: $scope.cOptions.fieldNameInFilter,
                            lbl: $scope.cOptions.lblBreadcrumb,
                            val: lInsertings[i].Value,
                            text: lInsertings[i].Text
                        }
                        lBreadcrumbs.pushIfNotExist(oItem, function (x) { return x.fieldName == oItem.fieldName && x.val == oItem.val; });
                    }
                }                
            }
            $scope.cOptions.removeBreadcrumb = function (oDeleting, lBreadcrumbs)
            {
                lBreadcrumbs.removeItem(function (x) { return x.fieldName == oDeleting.fieldName && x.val == oDeleting.val; });
                for (var i = 0; i < $scope.cOptions.filterData.length; i++)
                {
                    if ($scope.cOptions.filterData[i].Value == oDeleting.val)
                    {
                        $scope.cOptions.filterData[i].Selected = false;
                        $scope.selectItem($scope.cOptions.filterData[i]);
                        break;
                    }
                }
            }

            $scope.updateCheckboxItemAll = function () {
                $scope.checkedValForAll = $scope.cOptions.filterData.every(function (x) { return x.Selected == true; });
            }
            $scope.updateCheckboxItem = function () {
                var sVal = '';
                $scope.cOptions.filterData.forEach(function (x) {
                    if (x.Selected == true) {
                        sVal += (sVal != '' ? "," : "") + x.Value;
                    }
                });
                if (sVal != "") {
                    if ($scope.cOptions.hasEmptyAtFirst == true)
                    {
                        sVal = "," + sVal;//push one more empty (,) item to get all variants have value is null
                    }
                    
                } else {
                    sVal = null;//*** if null, allow show all records. if "-1", show empty
                    $scope.checkedValForAll = null;
                }
                $scope.checkedValForItem = sVal;
            }

            $scope.selectItem = function (item) {
                //console.warn('Minh: checkbox clicked', item);
                //_safeCallback($scope.cOptions.filterClicked, { item: item });

                $scope.updateCheckboxItemAll();
                $scope.updateCheckboxItem();

                $scope.filterChangedIternal();
            }
            $scope.filterChangedIternal = function () {
                //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller                
                $scope.cOptions.filterChanged($scope.checkedValForAll, $scope.cOptions.fieldNameAllInFilter, $scope.checkedValForItem, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
        }]

    }
}]);
;
ngAWDSApp.directive("filterDropdown", ['$timeout', function ($timeout) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cNgModel: '=', //two-way binding
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
            staffDropDown: '=' //two-way binding; will store the kendo drop down object, you can access from the parent controller using the bound varable on the view
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        template: '<div class="ddl-group">'
                    //+ '<select ng-if="cOptions.isShowDescription == false" class="form-control" ng-options="item.Value as item.Text for item in cOptions.filterData" ng-model="selectedVal" ng-change="filterChangedIternal()">'
	                //    //+ '<option value="">All Categories</option>' //*** we will push this option before call the directive
                    //+ '</select>'
                    + '<select class="form-control" ng-options="item.Value as displayText(item) for item in cOptions.filterData" ng-model="selectedVal" ng-change="filterChangedIternal()">'
                    + '</select>'
				+ '</div>'
		,
        link: function ($scope, $element, $attrs) {

        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                //defaultFilter: null,
                //currentFilter: null,
                //isDeSelectedFilterAllowed: true,
                //filter: null,
                //requiredFilterText: 'Missing Required Filter Text',
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                isShowDescription: false,
                isTextSEOToValue: false,
                isConvertStr: false,
                selectedItem: null,
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);
            $scope.displayText = function (item) {
                return $scope.cOptions.isShowDescription ? item.Description : item.Text;
            }
            
            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                angular.element($element).addClass('loading');
                $scope.cOptions.filterData = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (filter, filterData, isHideOnNoData, isShowRequiredFilter) {
                angular.element($element).addClass('loading');

                if ($rootScope.webApp.util.toBool($scope.cOptions.isTextSEOToValue) == true) {
                    for (var i = 0; i < filterData.length; i++) {
                        if ($rootScope.webApp.util.hasVal(filterData[i].TextSEO)) {
                            filterData[i].Value = filterData[i].TextSEO;
                        }
                        else {
                            filterData[i].Value = null;
                        }
                    }
                }

                $scope.cOptions.filterData = filterData;

                if (angular.isObject(filter[$scope.cOptions.fieldNameInFilter])) {
                    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter].Value;
                } else {
                    if (angular.isUndefined(filter[$scope.cOptions.fieldNameInFilter])) filter[$scope.cOptions.fieldNameInFilter] = null;
                    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter];

                    //*** convert number to string
                    if (angular.isDefined($scope.selectedVal) && $scope.selectedVal != null) {
                        if ($scope.cOptions.isConvertStr) {
                            $scope.selectedVal = $scope.selectedVal.toString();
                        } else {
                            switch ($scope.cOptions.fieldNameInFilter) {
                                case "PageSize":
                                    $scope.selectedVal = $scope.selectedVal.toString();
                                    break;
                            }
                        }                        
                    }
                }
                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;

                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if ($scope.cOptions.filterData.length < 1) {
                        angular.element($element).find('select').hide();
                    } else {
                        angular.element($element).find('select').show();
                    }
                }

                angular.element($element).removeClass('loading');
            }
            $scope.filterChangedIternal = function () {                
                //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller                                
                var isSelected = $rootScope.webApp.util.hasVal($scope.selectedVal);
                $scope.cOptions.selectedItem = null;
                if (isSelected) {
                    for (var i = 0; i < $scope.cOptions.filterData.length; i++) {
                        if ($scope.cOptions.filterData[i].Value == $scope.selectedVal) {
                            $scope.cOptions.selectedItem = $scope.cOptions.filterData[i];
                            break;
                        }
                    }
                }
                
                $scope.cOptions.filterChanged(isSelected, $scope.selectedVal, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
        }]

    }
}]);
ngAWDSApp.directive("filterDropdownSearch", ['$timeout', '$q', '$document', '$filter', function ($timeout, $q, $document, $filter) {
    var DOM_EVENT = {
        RESIZE: 'resize',
        SCROLL: 'scroll',
        CLICK: 'click',
        KEYDOWN: 'keydown',
        FOCUS: 'focus',
        INPUT: 'input'
    };

    var KEYCODE = {
        TAB: 9,
        BACKSPACE: 8,
        ENTER: 13,
        CTRL: 17,
        ALT: 18,
        ESCAPE: 27,
        LEFTARROW: 37,
        UPARROW: 38,
        RIGHTARROW: 39,
        DOWNARROW: 40,
        DELETE: 46,
        MAC_COMMAND_LEFT: 91,
        MAC_COMMAND_RIGHT: 93
    };
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cNgModel: '=', //two-way binding
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
            staffDropDown: '=' //two-way binding; will store the kendo drop down object, you can access from the parent controller using the bound varable on the view
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        templateUrl: function ($element, $attrs) {
            return $attrs.template;
        },
        //template: '<div class="relative">'
        //        + '<div class="relative group">'
        //            + '<button ng-click="dropdownButtonClick()" class="w-full px-4 py-3 text-left text-xl font-medium text-white bg-neutral-800 border border-neutral-800 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blue-500 z-10">'
        //                + '<span class="mr-2">{{selectedText}}</span>'
        //                + '<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mt-1 float-right" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">'
        //                    + '<path fill-rule="evenodd" d="M6.293 9.293a1 1 0 011.414 0L10 11.586l2.293-2.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />'
        //                + '</svg>'
        //            + '</button>'
        //            + '<div ng-show="cOptions.isOpen" class="ng-hide absolute right-0 mt-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 p-1 space-y-1 z-20 w-full">'
        //                + '<input ng-model="inputSearch" class="block w-full px-4 py-2 text-gray-800 border rounded-md border-gray-300 focus:outline-none" type="text" placeholder="Search items" autocomplete="off">'
        //                + '<a ng-repeat="item in cOptions.filterData | filter:inputSearch" class="block px-4 py-2 text-gray-700 hover:bg-gray-100 active:bg-blue-100 cursor-pointer rounded-md text-base no-underline hover:no-underline hover:text-neutral-950"'
        //                    + 'ng-class="{\'active\': item.Value == selectedVal}" ng-click="filterChangedIternal(true,item)" >{{item.Text}}</a>'
        //                + '<div ng-if="cOptions.filterData.length == 0">'
		//			        + 'no results found'
		//			    + '</div>'
        //            + '</div>'
		//		+ '</div>'
        //    + '</div>'
		//,
        link: function ($scope, $element, $attrs) {

            $timeout(function () {
                // execute the options expression
                //$q.when($scope.cOptions()).then(_initialize);
                _initialize();
            });
            function _initialize() {
                //options = options || {};
                //$scope.init(angular.extend({}, defaultOptions, options));
                $scope.init();

                _initializeContainer();
                _wireupEvents();
            }

            function _initializeContainer() {
                $scope.container = $element; //_getDefaultContainer();

                //$scope.elementUL = angular.element($scope.container[0].querySelector('ul.auto-complete-results'));
                $scope.targetSearchText = angular.element($scope.container[0].querySelector('input.search-text'));
                $scope.targetButton = angular.element($scope.container[0].querySelector('.dropdown-btn')) || angular.element($scope.container[0].querySelector('button'));
                $scope.targetMenuResults = angular.element($scope.container[0].querySelector('.dropdown-menu-results'));
                
            }

            function _wireupEvents() {
                $document.on(DOM_EVENT.CLICK, function (event) {
                    var $event = event;
                    $scope.$evalAsync(function () {
                        _handleDocumentClick($event);
                    });
                });
            }
            function _containerContainsTarget(target) {
                // use native Node.contains
                // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains
                var container = $scope.container[0];
                if (angular.isFunction(container.contains) && container.contains(target)) {
                    return true;
                }

                // otherwise use .has() if jQuery is available
                if (window.jQuery && angular.isFunction($scope.container.has) &&
                    $scope.container.has(target).length > 0) {

                    return true;
                }

                // assume target is not in container
                return false;
            }

            function _containerDropdownButtonContainsTarget(target) {
                // use native Node.contains
                // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains

                if ($scope.targetButton)
                {
                    var container = $scope.targetButton[0];
                    if (angular.isFunction(container.contains) && container.contains(target)) {
                        return true;
                    }

                    // otherwise use .has() if jQuery is available
                    if (window.jQuery && angular.isFunction($scope.targetButton.has) &&
                        $scope.targetButton.has(target).length > 0) {

                        return true;
                    }
                }
                // assume target is not in container
                return false;
            }
            function _srollToItemActive() {
                var attrSelector = 'a.active';

                if ($scope.targetMenuResults) {
                    // use jquery.scrollTo plugin if available
                    // http://flesler.blogspot.com/2007/10/jqueryscrollto.html
                    if (window.jQuery && window.jQuery.scrollTo) {  // requires jquery to be loaded
                        $scope.targetMenuResults.scrollTo($scope.targetMenuResults.find(attrSelector));
                        return;
                    }
                    
                    var aTag = $scope.targetMenuResults[0].querySelector(attrSelector);
                    if (aTag)
                    {
                        // this was causing the page to jump/scroll
                        //    li.scrollIntoView(true);
                        $scope.targetMenuResults[0].scrollTop = aTag.offsetTop;
                    }
                    
                }
            }
            function _handleDocumentClick(event) {
                var elementSearchText = $scope.container.find('input.search-text');
                var elementButton = $scope.container.find('.dropdown-btn');
                
                //// ignore inline
                //if ($scope.isInline()) {
                //    return;
                //}
                                

                // no container. probably destroyed in scope $destroy
                if (!$scope.container) {
                    return;
                }

                // ignore target click                 
                if (event.target === elementButton[0] || _containerDropdownButtonContainsTarget(event.target)) {
                    $element.find('input.search-text').focus();
                    _srollToItemActive();
                    event.stopPropagation();
                    return;
                }

                // ignore target click
                if (event.target === $scope.targetSearchText[0]) {
                    event.stopPropagation();
                    return;
                }


                //if (event.target === $scope.targetSearchIcon[0]) {
                //    if ($scope.options.activateOnClickSearchIcon) {
                //        if ($scope.options.searchIconCallback) {
                //            $scope.searchIconClicked(elementSearchText.val());
                //        } else {
                //            _waitAndQuery(elementSearchText.val(), 100);
                //        }

                //    }
                //}

                if (_containerContainsTarget(event.target)) {
                    event.stopPropagation();
                    return;
                }
                $scope.autoHide();
            }

            
        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                defaultFilter: null,
                //currentFilter: null,
                isDeSelectedFilterAllowed: true,
                //filter: null,
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                isHideCount: false,
                isOpen: false,
                requiredFilterText: 'Missing Required Filter Text',
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            $scope.inputSearch = null;
            //merge the default options into the passed in options
            $scope.init = function () {
                $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);
            }
            
            $scope.autoHide = function () {
                if ($scope.cOptions) {
                    _hideDropdown();
                }
            };
            $scope.dropdownButtonClick = function () {
                $scope.cOptions.isOpen = !$scope.cOptions.isOpen;                
            }

            function _hideDropdown() {
                if (!$scope.cOptions.isOpen) {
                    return;
                }

                // reset scroll position
                //$scope.elementUL[0].scrollTop = 0;
                $scope.cOptions.isOpen = false;
                //$scope.emptyRenderItems();
                //_resetSearchText();
                //// callback
                //_safeCallback($scope.options.dropdownHidden);
            }


            $scope.ngFilterSearchText = function () {
                return function (dataItem) {
                    var bResult = false;

                    if ($rootScope.webApp.util.hasVal($scope.inputSearch))
                    {
                        bResult = ($rootScope.webApp.util.indexOfLowerCase(dataItem.Text, $scope.inputSearch));
                    }
                    else {
                        //*** if search-text is empty, show all items
                        bResult = true;
                    }
                    
                    return bResult;
                }

            }

            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                $element.parents('.panel.panel-default').find('.panel-heading a').addClass('loading');
                $scope.cOptions.filterData = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (filter, filterData, isHideOnNoData, isShowRequiredFilter) {
                $element.find('button').addClass('loading');
                //$scope.cOptions.filterData = filterData;
                //if (angular.isObject(filter[$scope.cOptions.fieldNameInFilter])) {
                //    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter].Min || filter[$scope.cOptions.fieldNameInFilter].Max;
                //} else {
                //    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter];
                //}

                //for (var i = 0; i < filterData.length; i++) {
                //    if (filterData[i].Value == $scope.selectedVal) {
                //        $scope.selectedText = filterData[i].Text;
                //        break;
                //    }
                //}

                //$scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;

                //if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                //    ////hide filters that have no data
                //    //if ($scope.cOptions.filterData.length < 1) {
                //    //    angular.element($element).closest('.panel.panel-default').hide();
                //    //} else {
                //    //    angular.element($element).closest('.panel.panel-default').show();
                //    //}
                //}



                ////////////////////////
                if ($rootScope.webApp.util.toBool($scope.cOptions.isTextSEOToValue) == true) {
                    for (var i = 0; i < filterData.length; i++) {
                        if ($rootScope.webApp.util.hasVal(filterData[i].TextSEO)) {
                            filterData[i].Value = filterData[i].TextSEO;
                        }
                        else {
                            filterData[i].Value = null;
                        }
                    }
                }

                $scope.cOptions.filterData = filterData;

                if (angular.isObject(filter[$scope.cOptions.fieldNameInFilter])) {
                    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter].Value;
                } else {
                    if (angular.isUndefined(filter[$scope.cOptions.fieldNameInFilter])) filter[$scope.cOptions.fieldNameInFilter] = null;
                    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter];

                    //*** convert number to string
                    if (angular.isDefined($scope.selectedVal) && $scope.selectedVal != null) {
                        if ($scope.cOptions.isConvertStr) {
                            $scope.selectedVal = $scope.selectedVal.toString();
                        } else {
                            switch ($scope.cOptions.fieldNameInFilter) {
                                case "PageSize":
                                    $scope.selectedVal = $scope.selectedVal.toString();
                                    break;
                            }
                        }
                    }
                }
                //*** update selected Text
                for (var i = 0; i < filterData.length; i++) {
                    if (filterData[i].Value == $scope.selectedVal) {
                        $scope.selectedText = filterData[i].Text;
                        break;
                    }
                }

                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;

                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if ($scope.cOptions.filterData.length < 1) {
                        angular.element($element).find('select').hide();
                    } else {
                        angular.element($element).find('select').show();
                    }
                }




                $element.find('button').removeClass('loading');
            }
            $scope.filterChangedIternal = function (isSelected, item) {

                if (isSelected) {
                    //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller
                    //if (!$rootScope.webApp.util.isEmpty($scope.cOptions.currentFilter)) {
                    //	$scope.cOptions.currentFilter[$scope.cOptions.fieldNameInFilter] = item.Value;
                    //}
                    $scope.selectedVal = item.Value;
                    
                } else {
                    if ($rootScope.webApp.util.toBool($scope.cOptions.isDeSelectedFilterAllowed)) {
                        $scope.selectedVal = null; //We only allow somefilter 
                        //update the default value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller
                        //if (!$rootScope.webApp.util.isEmpty($scope.cOptions.currentFilter)) {
                        //	$scope.cOptions.currentFilter[$scope.cOptions.fieldNameInFilter] = null;
                        //}
                        //if (!$rootScope.webApp.util.isEmpty($scope.cOptions.currentFilter) && !$rootScope.webApp.util.isEmpty($scope.cOptions.defaultFilter)) {
                        //	$scope.cOptions.currentFilter[$scope.cOptions.fieldNameInFilter] = defaultFilter[$scope.cOptions.fieldNameInFilter]
                        //}
                    }
                }

                //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller                                
                //var isSelected = $rootScope.webApp.util.hasVal($scope.selectedVal);
                $scope.cOptions.selectedItem = null;
                if (isSelected) {
                    for (var i = 0; i < $scope.cOptions.filterData.length; i++) {
                        if ($scope.cOptions.filterData[i].Value == $scope.selectedVal) {
                            $scope.cOptions.selectedItem = $scope.cOptions.filterData[i];
                            break;
                        }
                    }
                }
                $scope.selectedText = item.Text;
                $scope.dropdownButtonClick();//hide ddl content
                //$scope.cOptions.filterChanged(isSelected, item, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
                $scope.cOptions.filterChanged(isSelected, $scope.selectedVal, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
        }]

    }
}]);;
ngAWDSApp.directive("filterRange", ['$timeout', function ($timeout) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        //template: '<ul class="list-group">'
		//	        + '<li class="list-group-item" ng-repeat="item in cOptions.filterData" ng-click="item.Value != selectedVal ? filterChangedIternal(true,item) : filterChangedIternal(false,item)">'
		//				+ '<div class="checkbox-box"><input type="checkbox" class="checkbox" ng-checked="item.Value == selectedVal">'
		//					+ '<label class="ckb-label"></label><span>{{item.Text}}</span> <span ng-hide="item.Count > 0">({{item.Count}})</span>'
		//				+ '</div>'
		//			+ '</li>'
		//			+ '<li class="list-group-item" ng-if="isShowRequiredFilter" >'
		//				+ '<div style="text-align: center">'
		//					+ '{{cOption.requiredFilterText}}'
		//				+ '</div>'
		//			+ '</li>'
		//			+ '<div ng-if="cOptions.filterData.length == 0" style="margin-left:22px;margin-bottom: 5px">'
		//			+ 'no results found'
		//			+ '</div>'
		//		+ '</ul>'
		//,
        template: '<div class="row">'
                    + '<div class="col-sm-6 select-padleft">'
                        + '<div class="controls">'
                            //+ '<select ng-options="item for item in cOptions.filterDataMin" ng-change="filterChangedIternal("min", item)"></select>'
                            + '<select ng-options="item.Value as item.Text for item in cOptions.filterDataMin" ng-model="selectedMin" class="form-control" ng-change="filterChangedIternal()">'
                            + '<option value=""> Min </option>'
                            + '</select>'
                            //+ '<select ng-model="selectedMin" ng-options="item.Value for x in cOptions.filterDataMin"></select>'item.name as item.shade for item in colors
                        + '</div>'
                    + '</div>'
                    + '<div class="col-sm-6 select-padright">'
                        + '<div class="controls">'
                            + '<select ng-options="item.Value as item.Text for item in cOptions.filterDataMax" ng-model="selectedMax" class="form-control" ng-change="filterChangedIternal()">'
                            + '<option value=""> Max </option>'
                            + '</select>'
                        + '</div>'
                    + '</div>'
                    + '<div class="col-xs-6" ng-if="!cOptions.isAutoRefresh">'
						+ '<div>'
							+ '<button ng-click="filterClearIternal()" class="btn-sm btn-clear">Clear</button>'
						+ '</div>'
					+ '</div>'
                    + '<div class="col-xs-6" ng-if="!cOptions.isAutoRefresh">'
						+ '<div>'
							+ '<button ng-click="filterUpdateIternal()" class="btn btn-primary btn-sm">Update</button>'
						+ '</div>'
					+ '</div>'
                    + '<div class="col-sm-12" ng-if="isShowRequiredFilter" >'
						+ '<div style="text-align: center">'
							+ '{{cOption.requiredFilterText}}'
						+ '</div>'
					+ '</div>'
                + '</div>',
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                defaultFilter: null,
                //currentFilter: null,
                isDeSelectedFilterAllowed: true,
                //filter: null,
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                isAutoRefresh: false,
                requiredFilterText: 'Missing Required Filter Text',
                filterDataMin: [],
                filterDataMax: [],
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);

            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                $element.parents('.panel.panel-default').find('.panel-heading a').addClass('loading');
                $scope.cOptions.filterDataMin = null;
                $scope.cOptions.filterDataMax = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (filter, filterDataMin, filterDataMax, isHideOnNoData, isShowRequiredFilter) {
                $element.parents('.panel.panel-default').find('.panel-heading a').addClass('loading');

                if (filter[$scope.cOptions.fieldNameInFilter] == null) {
                    filter[$scope.cOptions.fieldNameInFilter] = {};
                }

                $scope.cOptions.filterDataMin = filterDataMin;
                $scope.cOptions.filterDataMax = filterDataMax;
                $scope.selectedMin = filter[$scope.cOptions.fieldNameInFilter].Min;
                $scope.selectedMax = filter[$scope.cOptions.fieldNameInFilter].Max;

                //*** convert number to string
                switch ($scope.cOptions.fieldNameInFilter) {
                    case "YearFilter":
                    case "PriceFilter":
                        if (angular.isDefined($scope.selectedMin) && $scope.selectedMin != null) $scope.selectedMin = $scope.selectedMin;
                        if (angular.isDefined($scope.selectedMax) && $scope.selectedMax != null) $scope.selectedMax = $scope.selectedMax;
                        break;
                }
                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;
                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if ($scope.cOptions.filterDataMin.length < 1 && $scope.cOptions.filterDataMax.length < 1) {
                        angular.element($element).closest('.panel.panel-default').hide();
                    } else {
                        angular.element($element).closest('.panel.panel-default').show();
                    }
                }

                $element.parents('.panel.panel-default').find('.panel-heading a').removeClass('loading');
            }
            $scope.filterChangedIternal = function (item) {
                if ($scope.cOptions.isAutoRefresh)
                {
                    $scope.filterUpdateIternal();
                }
            }
            $scope.filterUpdateIternal = function ()
            {
                $scope.cOptions.filterChanged($scope.selectedMin, $scope.selectedMax, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
            $scope.filterClearIternal = function () {
                $scope.selectedMin = null;
                $scope.selectedMax = null;
                $scope.filterUpdateIternal();
            }
        }]

    }
}]);


ngAWDSApp.directive("filterRangeSingle", ['$timeout', function ($timeout) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
        },
        template: '<div class="ddl-group">'
                    + '<select class="form-control" ng-options="item.Value as displayText(item) for item in cOptions.filterData" ng-model="selectedVal" ng-change="filterChangedIternal()">'
                    + '</select>'
				+ '</div>',
        controller: ['$scope', '$element', '$attrs', '$rootScope', '$filter', function ($scope, $element, $attrs, $rootScope, $filter) {
            $scope.cOptionsDefault = {
                //defaultFilter: null,
                //currentFilter: null,
                //isDeSelectedFilterAllowed: true,
                //filter: null,
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                //isAutoRefresh: false,
                //requiredFilterText: 'Missing Required Filter Text',
                defaultOptionText: null,
                //filterData: [],
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);
            $scope.displayText = function (item) {
                return item.Text;
            }


            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                angular.element($element).addClass('loading');
                $scope.cOptions.filterData = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (filter, filterData, isHideOnNoData, isShowRequiredFilter) {
                angular.element($element).addClass('loading');

                if (filter[$scope.cOptions.fieldNameInFilter] == null) {
                    filter[$scope.cOptions.fieldNameInFilter] = {};
                }


                $scope.cOptions.filterData = [];//filterData;
                $scope.selectedVal = null;
                var priceFrom, priceTo;
                for (var i = 0; i < filterData.length; i++) {
                    if (i == 0) {
                        priceFrom = 0;
                        priceTo = filterData[i];
                    } else {
                        priceFrom = filterData[i - 1];
                        priceTo = filterData[i];
                    }
                    $scope.cOptions.filterData.push({ Value: priceFrom + '-' + priceTo, Text: $filter('extCurrencyZero')(priceFrom, '$', 0) + '-' + $filter('extCurrencyZero')(priceTo, '$', 0) });

                    //*** set selected value
                    if (angular.isDefined(filter[$scope.cOptions.fieldNameInFilter])
                        && (filter[$scope.cOptions.fieldNameInFilter].Min != null || filter[$scope.cOptions.fieldNameInFilter].Max != null))
                    {
                        if((filter[$scope.cOptions.fieldNameInFilter].Min == priceFrom || filter[$scope.cOptions.fieldNameInFilter].Min == null)
                            && (filter[$scope.cOptions.fieldNameInFilter].Max == priceTo || filter[$scope.cOptions.fieldNameInFilter].Max == null)
                            )
                        {
                            $scope.selectedVal = priceFrom + '-' + priceTo;
                        }
                    }
                }
                if ($scope.cOptions.defaultOptionText != null) {
                    $scope.cOptions.filterData.unshift({ Value: null, Text: $scope.cOptions.defaultOptionText });
                }

                //$scope.selectedMin = filter[$scope.cOptions.fieldNameInFilter].Min;
                //$scope.selectedMax = filter[$scope.cOptions.fieldNameInFilter].Max;
                ////*** convert number to string
                //switch ($scope.cOptions.fieldNameInFilter) {
                //    case "YearFilter":
                //    case "PriceFilter":
                //        if (angular.isDefined($scope.selectedMin) && $scope.selectedMin != null) $scope.selectedMin = $scope.selectedMin;
                //        if (angular.isDefined($scope.selectedMax) && $scope.selectedMax != null) $scope.selectedMax = $scope.selectedMax;
                //        break;
                //}


                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;
                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if ($scope.cOptions.filterData.length < 1) {
                        angular.element($element).find('select').hide();
                    } else {
                        angular.element($element).find('select').show();
                    }
                }
                angular.element($element).removeClass('loading');
            }
            $scope.filterChangedIternal = function (item) {
                if ($scope.cOptions.isAutoRefresh) {
                    $scope.filterUpdateIternal();
                }
            }
            $scope.filterUpdateIternal = function () {
                var min = null, max = null;
                if ($scope.selectedVal != null)
                {
                    min = $scope.selectedVal.split('-')[0];
                    max = $scope.selectedVal.split('-')[1];
                }
                $scope.cOptions.filterChanged($scope.selectedVal, min, max, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
            $scope.filterClearIternal = function () {
                $scope.selectedVal = null;
                $scope.filterUpdateIternal();
            }
        }]

    }
}]);;
ngAWDSApp.directive("filterSlider", ['$timeout', function ($timeout) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cNgModel: '=', //two-way binding
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
            staffDropDown: '=' //two-way binding; will store the kendo drop down object, you can access from the parent controller using the bound varable on the view
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        template: '<div class="slider-group">'                    
                    + '<rzslider rz-slider-model="iSliderMin"'
                            + 'rz-slider-high="iSliderMax"'
                            + 'rz-slider-options="cOptions.oSliderOptions"></rzslider>'
				+ '</div>'
		,
        link: function ($scope, $element, $attrs) {

        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                defaultFilter: null,
                //currentFilter: null,
                isDeSelectedFilterAllowed: true,
                //filter: null,
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                requiredFilterText: 'Missing Required Filter Text',
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);
            $scope.iSliderMin = 0;
            $scope.iSliderMax = 0;//the field is set selected value of slider            

            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                angular.element($element).addClass('loading');
                $scope.cOptions.oSliderOptions = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (oFilter, oSliderOptions, isHideOnNoData, isShowRequiredFilter) {
                angular.element($element).addClass('loading');

                $scope.cOptions.oSliderOptions = oSliderOptions;
                $scope.iSliderMin = oFilter[$scope.cOptions.fieldNameInFilter].Min;
                $scope.iSliderMax = oFilter[$scope.cOptions.fieldNameInFilter].Max;

                oFilter[$scope.cOptions.fieldNameInFilter].Ceil = oSliderOptions.ceil;

                if ($scope.iSliderMax == null)//!$rootScope.webApp.util.hasVal($scope.iSliderMax)
                {
                    $scope.iSliderMax = oSliderOptions.ceil;
                }
                if ($scope.iSliderMax > oSliderOptions.ceil)
                {
                    $scope.iSliderMax = oSliderOptions.ceil;
                }

                //if (angular.isObject(filter[$scope.cOptions.fieldNameInFilter])) {
                //    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter].Value;
                //} else {
                //    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter];
                //}
                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;

                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if (!$rootScope.webApp.util.hasVal($scope.iSliderMax)) {
                        angular.element($element).find('select').hide();
                    } else {
                        angular.element($element).find('select').show();
                    }
                }

                angular.element($element).removeClass('loading');
            }

            $scope.$on('slideEnded', function () {
                // user finished sliding a handle
                $scope.filterChangedIternal();
            })

            $scope.filterChangedIternal = function () {
                //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller   
                //oFilter[$scope.cOptions.fieldNameInFilter].Ceil = angular.copy($scope.iCeil);
                $scope.cOptions.filterChanged($scope.iSliderMin, $scope.iSliderMax, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
        }]

    }
}]);
ngAWDSApp.directive("filterSliderSingle", ['$timeout', function ($timeout) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cNgModel: '=', //two-way binding
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
            staffDropDown: '=' //two-way binding; will store the kendo drop down object, you can access from the parent controller using the bound varable on the view
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        template: '<div class="slider-group">'
                    + '<rzslider rz-slider-model="iSliderMin"'                            
                            + 'rz-slider-options="cOptions.oSliderOptions"></rzslider>'
				+ '</div>'
        
		,
        link: function ($scope, $element, $attrs) {

        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                defaultFilter: null,
                //currentFilter: null,
                isDeSelectedFilterAllowed: true,
                //filter: null,
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                requiredFilterText: 'Missing Required Filter Text',
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);
            $scope.iSliderMin = 0;
            //$scope.iSliderMax = 0;//the field is set selected value of slider            

            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                angular.element($element).addClass('loading');
                $scope.cOptions.oSliderOptions = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (oFilter, oSliderOptions, isHideOnNoData, isShowRequiredFilter) {
                angular.element($element).addClass('loading');

                $scope.cOptions.oSliderOptions = oSliderOptions;
                $scope.iSliderMin = oFilter[$scope.cOptions.fieldNameInFilter].Min;
                //$scope.iSliderMax = oFilter[$scope.cOptions.fieldNameInFilter].Max;

//                console.warn('Minh: loadFilter: ' + $scope.cOptions.fieldNameInFilter, $scope.iSliderMin);

                oFilter[$scope.cOptions.fieldNameInFilter].Ceil = oSliderOptions.ceil;

                //if ($scope.iSliderMax == null)//!$rootScope.webApp.util.hasVal($scope.iSliderMax)
                //{
                //    $scope.iSliderMax = oSliderOptions.ceil;
                //}
                //if ($scope.iSliderMax > oSliderOptions.ceil) {
                //    $scope.iSliderMax = oSliderOptions.ceil;
                //}

                //if (angular.isObject(filter[$scope.cOptions.fieldNameInFilter])) {
                //    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter].Value;
                //} else {
                //    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter];
                //}
                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;

                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    ////hide filters that have no data
                    //if (!$rootScope.webApp.util.hasVal($scope.iSliderMax)) {
                    //    angular.element($element).find('select').hide();
                    //} else {
                    //    angular.element($element).find('select').show();
                    //}
                }

                angular.element($element).removeClass('loading');
            }

            $scope.$on('slideEnded', function () {
                // user finished sliding a handle
                $scope.filterChangedIternal();
            })

            $scope.filterChangedIternal = function () {
                //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller   
                //oFilter[$scope.cOptions.fieldNameInFilter].Ceil = angular.copy($scope.iCeil);
                $scope.cOptions.filterChanged($scope.iSliderMin, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
        }]

    }
}]);;
ngAWDSApp.directive("filterStandard", ['$timeout', function ($timeout) {
    return {
        //require: 'ngModel',
        restrict: 'E',
        replace: true, //*** will replace the directives original HTML on view
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            cNgModel: '=', //two-way binding
            cOptions: '=', //two-way binding; configuration for this drop down & callback function
            staffDropDown: '=' //two-way binding; will store the kendo drop down object, you can access from the parent controller using the bound varable on the view
        },
        //{ code: "All", count: 51, description: "Used"} cOptions.filterData format
        template: '<ul class="list-group">'
			//+ '<li class="list-group-item" ng-repeat="item in cOptions.filterData" ng-class="{\'active\': item.Value == selectedVal}" ng-click="item.Value != selectedVal ? filterChangedIternal(true,item) : filterChangedIternal(false,item)">'
            + '<li class="list-group-item" ng-repeat="item in cOptions.filterData" ng-class="{\'active\': item.Value == selectedVal}" ng-click="filterChangedIternal(true,item)">'
						+ '<div class="checkbox-box item-{{item.TextSEO}}"><input type="checkbox" class="checkbox" ng-checked="item.Value == selectedVal">'
							+ '<label class="ckb-label"></label><span>{{item.Text}}</span> <span ng-hide="cOptions.isHideCount">({{item.Count}})</span>'
						+ '</div>'
					+ '</li>'
					+ '<li class="list-group-item" ng-if="isShowRequiredFilter" >'
						+ '<div style="text-align: center">'
							+ '{{cOption.requiredFilterText}}'
						+ '</div>'
					+ '</li>'
					+ '<div ng-if="cOptions.filterData.length == 0" style="margin-left:22px;margin-bottom: 5px">'
					+ 'no results found'
					+ '</div>'
				+ '</ul>'
		,
        link: function ($scope, $element, $attrs) {

        },
        controller: ['$scope', '$element', '$attrs', '$rootScope', function ($scope, $element, $attrs, $rootScope) {
            $scope.cOptionsDefault = {
                defaultFilter: null,
                //currentFilter: null,
                isDeSelectedFilterAllowed: true,
                //filter: null,
                fieldNameInFilter: null,
                isShowRequiredFilter: false,
                isHideCount: false,
                requiredFilterText: 'Missing Required Filter Text',                
                filterChanged: function () { }, //empty function so nothing is fired if function not passed in
            }
            //merge the default options into the passed in options
            $scope.cOptions = angular.merge({}, $scope.cOptionsDefault, $scope.cOptions);

            //add directive function we can call from controller
            $scope.cOptions.resetFilter = function () {
                $element.parents('.panel.panel-default').find('.panel-heading a').addClass('loading');
                $scope.cOptions.filterData = null;
                $scope.cOptions.isShowRequiredFilter = false;
            }
            $scope.cOptions.loadFilter = function (filter, filterData, isHideOnNoData, isShowRequiredFilter) {
                $element.parents('.panel.panel-default').find('.panel-heading a').addClass('loading');
                $scope.cOptions.filterData = filterData;
                if (angular.isObject(filter[$scope.cOptions.fieldNameInFilter])) {
                    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter].Min || filter[$scope.cOptions.fieldNameInFilter].Max;
                } else {
                    $scope.selectedVal = filter[$scope.cOptions.fieldNameInFilter];                    
                }

                
                $scope.cOptions.isShowRequiredFilter = isShowRequiredFilter;

                if ($rootScope.webApp.util.toBool(isHideOnNoData)) {
                    //hide filters that have no data
                    if ($scope.cOptions.filterData.length < 1) {
                        angular.element($element).closest('.panel.panel-default').hide();
                    } else {
                        angular.element($element).closest('.panel.panel-default').show();
                    }
                }

                $element.parents('.panel.panel-default').find('.panel-heading a').removeClass('loading');
            }
            $scope.filterChangedIternal = function (isSelected, item) {

                if (isSelected) {
                    //update the new value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller
                    //if (!$rootScope.webApp.util.isEmpty($scope.cOptions.currentFilter)) {
                    //	$scope.cOptions.currentFilter[$scope.cOptions.fieldNameInFilter] = item.Value;
                    //}
                    $scope.selectedVal = item.Value;
                } else {
                    if ($rootScope.webApp.util.toBool($scope.cOptions.isDeSelectedFilterAllowed)) {
                        $scope.selectedVal = null; //We only allow somefilter 
                        //update the default value to the currentFilter if we have one, its optional so we can skip this if we want to handle in the controller
                        //if (!$rootScope.webApp.util.isEmpty($scope.cOptions.currentFilter)) {
                        //	$scope.cOptions.currentFilter[$scope.cOptions.fieldNameInFilter] = null;
                        //}
                        //if (!$rootScope.webApp.util.isEmpty($scope.cOptions.currentFilter) && !$rootScope.webApp.util.isEmpty($scope.cOptions.defaultFilter)) {
                        //	$scope.cOptions.currentFilter[$scope.cOptions.fieldNameInFilter] = defaultFilter[$scope.cOptions.fieldNameInFilter]
                        //}
                    }                    
                }                
                $scope.cOptions.filterChanged(isSelected, item, $scope.cOptions.fieldNameInFilter); //this will call the function set on the view directive
            }
        }]

    }
}]);;
ngAWDSApp.directive("gplaceApiDirective", function () {
    return {
        restrict: "EA",
        scope: {
            apiParam: "@apiParam",
            apiKey: "@apiKey"
        },
        template: '<span>{{sConent}}</span>',
        controller: ['$scope', 'HttpFactory', function ($scope, HttpFactory) {
            //restrict: 'EA', //E = element, A = attribute, C = class, M = comment         
            //scope: {
            //    //@ reads the attribute value, = provides two-way binding, & works with functions
            //    title: '@'
            //},
            if ($scope.apiParam != null && $scope.apiParam != null) {
                //*** Minh::20190724:: the function has many requests to google API (same time), so the API stoped. we will use another application to get data and then uncomment code bellow
                //HttpFactory.get({ ApiParam: $scope.apiParam, ApiKey: $scope.apiKey }, "Stock/ThirdPartyApi/GetGPlace").then(function (response) {
                //    $scope.sConent = response.data;
                //}, function (responseError) { });
            } else {
                console.error("Minh: GPlaceApi required Param and Key");
            }
        }]
    }
});;
ngAWDSApp.directive('ngErrorSrc', function () {
    return {
        link: function (scope, element, attrs) {
            element.bind('error', function () {
                if (attrs.src != attrs.onErrorSrc && attrs.onErrorSrc != null) {
                    attrs.$set('src', attrs.onErrorSrc);
                }
            });
        }
    }
});;

ngAWDSApp.directive('ngRepeatFinish', ['$timeout', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit('ngRepeatFinished');
                });
            }
        }
    }
}]);;
ngAWDSApp.directive('ngSrc', ['$timeout', '$rootScope', function ($timeout, $rootScope) {
    //**** lazy loading
    return {
        restrict: 'A',
        terminal: true,
        priority: 100,//*** override ng-src directive
        link: function ($scope, $element, $attrs) {
            var observer;

            function loadImg(changes) {
                $element.addClass('ng-lz-loading');
                changes.forEach(function (change) {
                    if (change.intersectionRatio > 0) {
                        observer.unobserve(change.target);

                        change.target.src = $attrs.ngSrc;
                        //console.warn('Minh: ', change);
                        change.target.classList.add('ng-lz-visible');
                        //change.target.classList.remove('ng-lz-loading');
                        //change.target.classList.remove('lazy-load');
                        //change.target.parentElement.classList.add('lazy-image--handled');
                        //change.target.parentElement.classList.remove('lazy-load');
                    } else {
                        change.target.classList.add('ng-lz-non-visible');
                    }
                });
            }

            $attrs.$observe('ngSrc', function (value) {
                if (!value) return;

                try {
                    /**** 
                    * dev.to/iamafro/lazy-loading-images-in-your-angularjs-project-using-the-intersectionobserver-api-ae9
                    * The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
                    */
                    var isLazyLoading = $rootScope.webApp.util.toBool(($attrs.isLazy || true));
                    if (isLazyLoading == true) {
                        
                        observer = new IntersectionObserver(loadImg);
                        var img = angular.element($element)[0];
                        img.onload = function () {
                            $element.addClass('ng-lz-loaded');
                            $element.removeClass('ng-lz-loading');
                        }
                        observer.observe(img);
                    } else {
                        $attrs.$set('src', value);
                    }
                    
                }
                catch (e) {
                    //$attrs.$observe('ngSrc', function (value) {
                        $attrs.$set('src', value);
                    //});
                }

            });
        }
    };
}]);

//ngAWDSApp.directive('ngSrc1', ['$timeout', '$rootScope', '$window', '$document', 'Notification', function ($timeout, $rootScope, $window, $document, Notification) {
//    //**** lazy loading
//    return {
//        restrict: 'A',
//        terminal: true,
//        priority: 100,//*** override ng-src directive
//        link: function ($scope, $element, $attrs) {
//            /**** check-if-an-element-is-visible-in-the-viewport 
//                www.javascripttutorial.net/dom/css/check-if-an-element-is-visible-in-the-viewport/#:~:text=Use%20the%20getBoundingClientRect()%20method%20to%20get%20the%20size%20of,in%20the%20viewport%20or%20not.
//            */
//            //$(elem).attr('src', attr.ngSrc);
//            $scope.sNgSrc = null;
//            $attrs.$observe('ngSrc', function (value) {
//                if (!value)
//                    return;
//                //// Make your changes here!!
//                ////value = 'prefix-' + value;
//                //$timeout(function () {
//                //    //attr.$set('src', value);                            
//                //    updateSrc();
//                //}, 100);
//                updateSrc();
//            });
//            function updateSrc() {
//                if (angular.isUndefined($attrs.src)) {
//                    $timeout(function () {
//                        var bIsVisibleInScreen = isVisibleInScreen();
//                        if (angular.isDefined(bIsVisibleInScreen) && isValidFile()) {
//                            if ($element.hasClass('ng-lz-loaded') == false) {
//                                if (isVisibleInScreen()) {
//                                    $attrs.$set('src', $attrs.ngSrc);
//                                    $element.addClass('ng-lz-loaded');
//                                    $element.removeClass('ng-lz-loading');
//                                } else {
//                                    $element.addClass('ng-lz-loading');
//                                }
//                            }
//                        } else {
//                            //*** browser is not support the function getBoundingClientRect()
//                            if ($($element).attr('src')) {
//                            } else {
//                                $attrs.$set('src', $attrs.ngSrc);
//                            }
//                        }
//                    }, 1);
//                }
//            }
//            function isValidFile() {
//                var bVal = true;
//                if ($attrs.ngSrc) {
//                    var arrEntens = ['svg'];//*** no need to use lazy loading if existing the arrEntens
//                    var sExten = webApp.util.getFileExtension($attrs.ngSrc);
//                    if (arrEntens.indexOfLowerCase(sExten)) {
//                        bVal = false;
//                    }
//                }
//                return bVal;
//            }
//            function isVisibleInScreen() {
//                var bVal = undefined;
//                if ($element.length > 0) {
//                    try {
//                        if ($element.parents().is(':hidden')) {
//                            bVal = false;
//                        }
//                        else {
//                            var elementSelector = $element[0]; //document.querySelector('#main-container');
//                            var rect = elementSelector.getBoundingClientRect();
//                            bVal = false;
//                            /*
//                                To get the width the height of the viewport, you use the window.innerWidth and window.innerHeight in modern browsers. Some browsers, however, use the document.documentElement.clientWidth and document.documentElement.clientHeight.
//                            */
//                            //bVal = (
//                            //    rect.top >= 0 &&
//                            //    rect.left >= 0 &&
//                            //    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
//                            //    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
//                            //);
//                            // checking whether fully visible
//                            if (rect.top >= 0 && rect.bottom <= ($window.innerHeight || $document.documentElement.clientHeight)) {
//                                //console.log('Element is fully visible in screen');
//                                bVal = true;
//                            }
//                            // checking for partial visibility
//                            if (bVal == false && (rect.top < ($window.innerHeight || $document.documentElement.clientHeight) && rect.bottom >= 0)) {
//                                //console.log('Element is partially visible in screen');
//                                bVal = true;
//                            }
//                        }
//                    } catch (e) {
//                        bVal = undefined;
//                    }
//                }
//                return bVal;
//            }
//            /* Add one or more listeners to an element
//            ** @param {DOMElement} element - DOM element to add listeners to
//            ** @param {string} eventNames - space separated list of event names, e.g. 'click change'
//            ** @param {Function} listener - function to attach for each event as a listener
//            */
//            function addListenerMulti(element, eventNames, listener) {
//                if (element && element.length > 0) {
//                    var events = eventNames.split(' ');
//                    for (var i = 0, iLen = events.length; i < iLen; i++) {
//                        element.addEventListener(events[i], listener, false);
//                    }
//                }
//            }
//            angular.element($document).ready(function () {
//                //var _events = 'scroll mouseover click';
//                var _events = 'scroll';
//                var lElementModalBody = document.querySelectorAll('.modal-body');
//                if ($rootScope.webApp.util.isMobile()) {
//                    //window.addEventListener('touchstart', function () {
//                    //    Notification.warning('touchstart');
//                    //    updateSrc();
//                    //}, false);
//                    //window.addEventListener('touchmove', function () {
//                    //    //Notification.warning('touchmove');
//                    //    updateSrc();
//                    //}, false);
//                    //window.addEventListener('touchend', function () {
//                    //    Notification.warning('touchend');
//                    //    updateSrc();
//                    //}, false);
//                    angular.forEach(lElementModalBody, function (ele, idx) {
//                        //ele.addEventListener('touchstart', function () {
//                        //    updateSrc();
//                        //}, false);
//                        //ele.addEventListener('touchmove', function () {
//                        //    updateSrc();
//                        //}, false);
//                        //ele.addEventListener('touchend', function () {
//                        //    updateSrc();
//                        //}, false);
//                    });
//                }
//                addListenerMulti(window, _events, function () {
//                    Notification.warning('on event ' + _events);
//                    updateSrc();
//                })
//                //*** angularjs $document does not support function .querySelector('.modal-body')                 
//                angular.forEach(lElementModalBody, function (ele, idx) {
//                    addListenerMulti(ele, _events, function () {
//                        updateSrc();
//                    });
//                });
//                //window.addEventListener('scroll', function () {
//                //    updateSrc();
//                //});
//                //window.addEventListener('mouseover', function () {
//                //    updateSrc();
//                //});
//                //window.addEventListener('click', function () {
//                //    updateSrc();
//                //});
//                //document.querySelector('.modal-body').addEventListener('scroll', function () {
//                //    updateSrc();
//                //});
//                ////document.querySelector('.modal-body').addEventListener('mouseover', function () {
//                ////    updateSrc();
//                ////});
//                $(document).on('shown.bs.modal', '.modal', function () {
//                    updateSrc();
//                });
//            })
//        }
//    };
//}]);;
ngAWDSApp.directive('numbersQty', function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attr, ngModelCtrl) {
            function fromUser(text) {
                if (text) {
                    var transformedInput = text.replace(/[^0-9]/g, '');

                    if (transformedInput !== text) {
                        ngModelCtrl.$setViewValue(transformedInput);
                        ngModelCtrl.$render();
                    }
                    return transformedInput;
                }
                return undefined;
            }
            ngModelCtrl.$parsers.push(fromUser);
        }
    };
});;
ngAWDSApp.directive('ngClick', ['btnSpinnerService', '$rootScope', function (btnSpinnerService, $rootScope) {

    //we can override ng-click directive by same name, or use $provide.decorator.
    // $provide.decorator for example: stackoverflow.com/questions/69370369/overriding-angularjs-directive-at-runtime
    return {
        restrict: 'A',
        //terminal: true,
        priority: 0,//*** override ng-click directive
        link: function ($scope, $element, $attr) {

            if (!$attr.dataLoadingText)
            {
                $element.attr('data-loading-text', '<span class="button__text">loading...</span>');
            }
            $element.bind('click', function () {
                if ($element.hasClass($rootScope.webApp.btnSpinner.cl_btn)) {
                    if (!$attr.disabled) {
                        btnSpinnerService.clicked($element);
                    }
                }
            });
        }
    }
}]);

ngAWDSApp.directive('btnSpinner', ['btnSpinnerService', '$rootScope', function (btnSpinnerService, $rootScope) {

    //we can override ng-click directive by same name, or use $provide.decorator.
    // $provide.decorator for example: stackoverflow.com/questions/69370369/overriding-angularjs-directive-at-runtime
    return {
        restrict: 'A',
        //terminal: true,
        priority: 0,//*** override ng-click directive
        link: function ($scope, $element, $attr) {

            if (!$attr.dataLoadingText) {
                $element.attr('data-loading-text', '<span class="button__text">loading...</span>');
            }

            $element.bind('click', function () {
                if ($element.hasClass($rootScope.webApp.btnSpinner.cl_btn)) {
                    if (!$attr.disabled)
                    {
                        //*** if form $valid, enable class clicking
                        btnSpinnerService.clicked($element);
                    }
                }
            });
        }
    }
}]);


ngAWDSApp.service('btnSpinnerService', ['$rootScope', function ($rootScope) {
    //var arrButtons = [];
    /**
    * 1) clicked on button: add class clicking. showloadingicon function will find the button and change state
    * 2) if showloadingicon function is called, change button state to loading
    * 3) if hideloadingicon function is called, change button state to reset and remove class clicking
    */
    var _setState = function (ele, state) {
        switch (state) {
            case 'clicked':
                if (ele.hasClass($rootScope.webApp.btnSpinner.cl_clicked) == false) ele.addClass($rootScope.webApp.btnSpinner.cl_clicked);
                break;
            case 'loading':
                ele.button("loading");
                if (ele.hasClass($rootScope.webApp.btnSpinner.cl_loading) == false) ele.addClass($rootScope.webApp.btnSpinner.cl_loading);//add class loading for custom css
                break;
            case 'reset':
                ele.button("reset");
                ele.removeClass($rootScope.webApp.btnSpinner.cl_clicked).removeClass($rootScope.webApp.btnSpinner.cl_loading);
                break;
            default:
                break;
        }
    }

    this.clicked = function (ele) {
        _setState(ele, 'clicked');
    }
    this.loading = function (ele) {
        _setState(ele, 'loading');
    }
    this.reset = function (ele) {
        _setState(ele, 'reset');
    }
}]);;
ngAWDSApp.directive("paginationDirective", ['$rootScope', function ($rootScope) {
    return {
        restrict: "EA",
        //*** if scope = undefined or scope = false or scope attibute missing, it will use parent controller $scope
        //*** if scope = {} or scope = true, it will create new $scope
        //scope: {
        //    //There are 3 types of prefixes in AngularJS:
        //    //‘@’ – Text binding / one-way binding
        //    //‘=’ – Direct model binding / two-way binding
        //    //‘&’ – Behavior binding / Method binding
        //},
        scope: {
            dataSource: "=source",
            pageNumber: "=pageNumber",
            totalPages: "=totalPages",
            totalRecords: "=totalRecords",
            showPageTitle: "@showPageTitle",
            onPrevPage: "&",
            onNextPage: "&",
            onPage: "&",
            onResetFilter: "&",
        },
        template: '<div class="row justify-content-md-center" ng-if="dataSource.length > 0">'
                        + '<p class="page-title ng-hide" ng-show="bShowPageTitle">Page {{pageNumber}} of {{totalPages}}</p>'
                        + '<ul class="pagination">'
                            + '<li class="page-item" ng-class="{\'disabled\': pageNumber == 1}">'
                                + '<a class="btn page-link page-prev" ng-click="onPrevPage()" tabindex="-1">Prev</a>'
                            + '</li>'
                            + '<li ng-repeat="x in lPageNumber" class="page-item" ng-class="{\'active\': x.val == pageNumber, \'disabled\': x.val == \'...\'}">'
                                + '<a class="btn page-link" ng-if="x.val == pageNumber">{{x.val}}<span class="sr-only">(current)</span></a>'
                                + '<a class="btn page-link" ng-if="x.val != pageNumber" ng-click="onPage({page:x.val})" ng-class="{\'disabled\': x.val == \'...\'}">{{x.val}}</a>'
                            + '</li>'
                            + '<li class="page-item" ng-class="{\'disabled\': pageNumber == totalPages}">'
                                + '<a class="btn page-link page-next" ng-click="onNextPage()">Next</a>'
                            + '</li>'
                        + '</ul>'
                    + '</div>'
                    + '<div class="row justify-content-md-center" ng-if="totalRecords != null && dataSource.length < 1">'
                        + '<p>Your search filters have returned 0 results. Please broaden your search.</p>'
                        + '<p><a class="btn btn-moreDetails btn-reset-filter" ng-click="onResetFilter()">Reset Filters</a></p>'
                    + '</div>',
        link: function ($scope) {
            $scope.showPageTitle = $scope.showPageTitle || 'true';
            $scope.bShowPageTitle = $rootScope.webApp.util.toBool($scope.showPageTitle);
            $scope.resetPageNumber = function () {
                $scope.lPageNumber = $rootScope.webApp.util.generatePagination($scope.totalRecords, $scope.totalPages, $scope.pageNumber);
            }
            /*
            * $watch() will be triggered by:
            * $scope.myArray = [];
            * $scope.myArray = null;
            * $scope.myArray = someOtherArray;
            *
            * $watch(..., true) will be triggered by EVERYTHING above AND:
            * $scope.myArray[0].someProperty = "someValue";
            *
            *
            * $watchCollection() will be triggered by everything above AND:
            * $scope.myArray.push({}); // add element
            * $scope.myArray.splice(0, 1); // remove element
            * $scope.myArray[0] = {}; // assign index to different value
            * 
            * jsfiddle.net/luisperezphd/2zj9k872/
            */
            $scope.$watchCollection('dataSource', function (newVal, oldVal) {
                if (newVal != oldVal) {
                    $scope.resetPageNumber();
                }
            });

            //$scope.$watch('totalRecords', function (newVal, oldVal) {
            //    if (newVal != oldVal) {
            //        $scope.resetPageNumber();
            //    }
            //});
        }
    };
}]);;
/* check strong password
* one or more lowercase letter
* one or more captial (uppercase) letter
* one or more number (Digit)
* one or more special characters letter
* minimum 8 characters letter or number => use min-length

var LOWER_REGEXP = new RegExp("(?=.*[a-z])");
var UPPER_REGEXP = new RegExp("(?=.*[A-Z])");
var DIGIT_REGEXP = new RegExp("(?=.*[0-9])");
//var SPECIAL_REGEXP = new RegExp("(?=.*[$@^!%*?&])");
var SPECIAL_REGEXP = new RegExp("(?=.*[!\";#$%&'()*+,-./:;<=>?@[]^_`{|}~])");
*/


ngAWDSApp.directive('requiresDigit', ['$rootScope', function ($rootScope) {
    /* check strong password
    * one or more lowercase letter
    * one or more captial (uppercase) letter
    * one or more number (Digit)
    * one or more special characters letter
    * minimum 8 characters letter or number => use min-length
    */    
    var DIGIT_REGEXP = new RegExp("(?=.*[0-9])");

    return {
        //restrict: 'A', // only activate on element attribute
        require: 'ngModel', // get a hold of NgModelController
        link: function (scope, elem, attrs, ngModel) {
            if (!ngModel) return; // do nothing if no ng-model

            ngModel.$validators.requiresDigit = function (modelValue) {
                if (ngModel.$isEmpty(modelValue)) {
                    // consider empty models to be valid
                    return true;
                }
                if (attrs.requiresDigit == "false") {
                    return true;
                }
                if (DIGIT_REGEXP.test(modelValue)) {
                    return true;
                }
                else {
                    return false;
                }
            };
        }
    }
}]);
ngAWDSApp.directive('requiresLower', ['$rootScope', function ($rootScope) {
    var LOWER_REGEXP = new RegExp("(?=.*[a-z])");
    return {
        //restrict: 'A', // only activate on element attribute
        require: 'ngModel', // get a hold of NgModelController
        link: function (scope, elem, attrs, ngModel) {
            if (!ngModel) return; // do nothing if no ng-model

            ngModel.$validators.requiresLower = function (modelValue) {
                if (ngModel.$isEmpty(modelValue)) {
                    // consider empty models to be valid
                    return true;
                }
                if (attrs.requiresLower == "false") {
                    return true;
                }
                if (LOWER_REGEXP.test(modelValue)) {
                    return true;
                }
                else {
                    return false;
                }
            };
        }
    }
}]);
ngAWDSApp.directive('requiresUpper', ['$rootScope', function ($rootScope) {
    var UPPER_REGEXP = new RegExp("(?=.*[A-Z])");

    return {
        //restrict: 'A', // only activate on element attribute
        require: 'ngModel', // get a hold of NgModelController
        link: function (scope, elem, attrs, ngModel) {
            if (!ngModel) return; // do nothing if no ng-model

            ngModel.$validators.requiresUpper = function (modelValue) {
                if (ngModel.$isEmpty(modelValue)) {
                    // consider empty models to be valid
                    return true;
                }
                if (attrs.requiresUpper == "false") {
                    return true;
                }

                if (UPPER_REGEXP.test(modelValue)) {
                    return true;
                }
                else {
                    return false;
                }
            };
        }
    }
}]);
ngAWDSApp.directive('requiresSpecialchar', ['$rootScope', function ($rootScope) {
    var SPECIAL_REGEXP = new RegExp("(?=.*[$@^!%*?&#])");
    //var SPECIAL_REGEXP = new RegExp("(?=.*[!\";#$%&'()*+,-./:;<=>?@[]^_`{|}~])");
    //var SPECIAL_REGEXP = new RegExp("(?=.*[^A-z0-9])");//missing these letters: -_`^[]\
    //SPECIAL_REGEXP = new RegExp("!@#$%&*+")
    //var SPECIAL_REGEXP = new RegExp("(?=.*[$@^!%*?&])");

    
    

    return {
        //restrict: 'A', // only activate on element attribute
        require: 'ngModel', // get a hold of NgModelController
        link: function (scope, elem, attrs, ngModel) {
            if (!ngModel) return; // do nothing if no ng-model

            ngModel.$validators.requiresSpecialchar = function (modelValue) {
                if (ngModel.$isEmpty(modelValue)) {
                    // consider empty models to be valid
                    return true;
                }
                if (attrs.requiresSpecialchar == "false") {
                    return true;
                }
                if (SPECIAL_REGEXP.test(modelValue)) {
                    return true;
                }
                else {
                    return false;
                }
            };
        }
    }
}]);
ngAWDSApp.directive('requiresMatch', ['$rootScope', function ($rootScope) {

    return {
        //restrict: 'A', // only activate on element attribute
        require: 'ngModel', // get a hold of NgModelController
        link: function (scope, elem, attrs, ngModel) {
            if (!ngModel) return; // do nothing if no ng-model

            var isMatched = function (modelValue, attrVal) {
                if (ngModel.$isEmpty(modelValue))
                {
                    return true;
                }
                if ($rootScope.webApp.util.toBool(attrs.required) == false)
                {
                    return true;
                }
                if ($rootScope.webApp.util.equalsLowerCase(modelValue, attrVal)) {
                    return true;
                } else {
                    return false;
                }
            }

            //ngModel.$validators.requiresMatch = function (modelValue) {
            //    if (ngModel.$isEmpty(modelValue)) {
            //        // consider empty models to be valid
            //        return true;
            //    }
            //    var parentVal = attrs.requiresMatch;
            //    if ($rootScope.webApp.util.equalsLowerCase(parentVal, modelValue)) {
            //        return true;
            //    }
            //    else {
            //        return false;
            //    }
            //};

            scope.$watch(attrs.ngModel, function () {
                ngModel.$setValidity('requiresMatch', isMatched(ngModel.$viewValue, attrs.requiresMatch));
            });

            // observe the other value and re-validate on change
            attrs.$observe('requiresMatch', function (parentVal) {
                //console.warn('Minh: val: ', val, 'viewmodel:' + ngModel.$viewValue, 'attrs:' + attrs.requiresMatch)
                ngModel.$setValidity('requiresMatch', isMatched(ngModel.$viewValue, parentVal));
                
            });
        }
    }
}]);



//*** passwordVerify is old function
ngAWDSApp.directive('passwordVerify', ['$rootScope', function ($rootScope) {
    return {
        restrict: 'A', // only activate on element attribute
        require: '?ngModel', // get a hold of NgModelController
        link: function (scope, elem, attrs, ngModel) {
            if (!ngModel) return; // do nothing if no ng-model

            // watch own value and re-validate on change
            scope.$watch(attrs.ngModel, function () {
                validate();
            });

            // observe the other value and re-validate on change
            attrs.$observe('passwordVerify', function (val) {
                validate();
            });

            var validate = function () {
                // values
                var val1 = $rootScope.webApp.util.trimString(ngModel.$viewValue); 
                var val2 = attrs.passwordVerify;

                // set validity
                if ($rootScope.webApp.util.toBool(attrs.required)) {
                    var isEquals = $rootScope.webApp.util.equalsLowerCase(val1, val2);
                    ngModel.$setValidity('passwordVerify', isEquals);
                } else {
                    ngModel.$setValidity('passwordVerify', true);
                }

                
            };
        }
    }
}]);;
ngAWDSApp.directive("multipleEmails", function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {
            function validateMultipleEmails(viewValue) {
                var emails = viewValue.split(';');
                // define single email validator here
                //var email_regexp = /^[_a-z0-9-]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,20})$/;
                var email_regexp = /^[a-z0-9!'#$%&*+\/=?^_`{|}~-]+(?:\.[a-z0-9!'#$%&*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-zA-Z]{2,}$/i;
                var validityArr = viewValue.trim() == "" || $.map(emails, function (str) {
                    if (str.trim() == "") {
                        return true;
                    } else {
                        return email_regexp.test(str.trim());
                    }
                }); // sample return is [true, true, true, false, false, false]

                var atLeastOneInvalid = false;
                angular.forEach(validityArr, function (value) {
                    if (value === false)
                        atLeastOneInvalid = true;
                });
                if (!atLeastOneInvalid) {
                    ctrl.$setValidity('multipleEmails', true);
                    return viewValue;
                } else {
                    ctrl.$setValidity('multipleEmails', false);
                    return undefined;
                }
            }
            element.ready(function () {
                scope.$apply(function () {
                    return validateMultipleEmails(element.val());
                })
            });
            ctrl.$parsers.unshift(function (viewValue) {
                return validateMultipleEmails(viewValue);
            });
        }
    };
});
ngAWDSApp.directive("validEmail", function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {

            function isValidEmails(viewValue)
            {
                /**** input type="email"
                * default validation of angularjs is allow minh@acs -> missed .com or .vn or .net or v.v...
                * so use this function  to check them if want
                */

                //var emails = viewValue.split(';');                
                // define single email validator here
                //var email_regexp = /^[_a-z0-9-]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,20})$/;
                var email_regexp = /^[a-z0-9!'#$%&*+\/=?^_`{|}~-]+(?:\.[a-z0-9!'#$%&*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-zA-Z]{2,}$/i;
                var validityArr = viewValue.trim() == "" || email_regexp.test(viewValue.trim());

                var atLeastOneInvalid = (validityArr == false);//false;
                //angular.forEach(validityArr, function (value) {
                //    if (value === false)
                //        atLeastOneInvalid = true;
                //});
                if (!atLeastOneInvalid) {
                    //ctrl.$setValidity('validEmail', true);
                    ctrl.$setValidity('email', true);
                    return viewValue;
                } else {
                    //ctrl.$setValidity('validEmail', false);
                    ctrl.$setValidity('email', false);
                    return undefined;
                }
            }
            element.ready(function () {
                scope.$apply(function () {
                    return isValidEmails(element.val());
                })
            });
            ctrl.$parsers.unshift(function (viewValue) {
                return isValidEmails(viewValue);
            });
        }
    };
});

ngAWDSApp.directive("validEmailSrv", ['$q', '$timeout', 'HttpFactory', function ($q, $timeout, HttpFactory)
{
    //*** Minh::20240315:: created this function
    //var EMAIL_REGEXP = /^[_a-z0-9-]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,20})$/;
    var EMAIL_REGEXP = /^[a-z0-9!'#$%&*+\/=?^_`{|}~-]+(?:\.[a-z0-9!'#$%&*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-zA-Z]{2,}$/i;
    return {
        require: '?ngModel',
        link: function (scope, element, attrs, ctrl) {

            // only apply the validator if ngModel is present and AngularJS has added the email validator
            if (ctrl && ctrl.$validators.email)
            {
                var sServerErrorMsgCallback = attrs['validEmailSrv']

                // this will overwrite the default AngularJS email validator
                ctrl.$validators.email = function (modelValue) {
                    /**** input type="email"
                    * default validation of angularjs is allow minh@acs -> missed .com or .vn or .net or v.v...
                    * so use this function  to check them if want
                    * if valid, send to server-side to validate more
                    */
                    return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);
                };

                //ctrl.$validators.validEmailSrv = function (modelValue) {
                //    if (ctrl.$validators.email(modelValue)) {
                //        return ctrl.$isEmpty(modelValue) || (EMAIL_REGEXP.test(modelValue) && 'minh@acs.com' == (modelValue));
                //    }
                //    else {
                //        return true;//*** not need to show error message if not pass the email validator
                //    }
                //};
                ctrl.$serverError = null;
                ctrl.$asyncValidators.validEmailSrv = function (modelValue, viewValue) {

                    if (ctrl.$validators.email(modelValue) == false) {
                        //*** not need to show error message if not pass the email validator
                        //*** consider "not pass the email validator" model valid
                        return $q.resolve();
                    }

                    var def = $q.defer();

                    HttpFactory.post({ sEmailAddress: modelValue }, "Stock/EmailProcesser/EmailValidation").then(function (response) {
                        //return $q.resolve();   // to mark as valid or
                        // return $q.reject();   // to mark as invalid
                        var jsonData = angular.fromJson(response.data);
                        if (jsonData.IsVaid) {
                            def.resolve();
                        }
                        else {
                            ctrl.$serverError = jsonData.MessageResponse;
                            def.reject();
                        }
                    }, function (response) {
                        def.reject(response);
                    });

                    //$timeout(function () {
                    //    // Mock a delayed response
                    //    if ('minh@acs.com' == (modelValue)) {
                    //        // The username is available
                    //        def.resolve('success');
                    //    }
                    //    else {
                    //        console.warn('Minh: ', scope, element, attrs, ctrl)
                    //        ctrl.$serverError = 'error msg';
                    //        //scope.$eval(sServerErrorMsgCallback);
                    //        def.reject('error msg');
                    //    }
                    //}, 2000);

                    return def.promise;

                };
            }

            //if (ctrl && ctrl.$validators.email) {
            //    // this will overwrite the default AngularJS email validator
            //    ctrl.$validators.email = function (modelValue) {
            //        return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);
            //    };
            //}

        }
    };
}]);;
ngAWDSApp.directive('validUbsCaptcha', ['$http', '$q', '$rootScope', 'HttpFactory', function ($http, $q, $rootScope, HttpFactory) {
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: {
            recaptcha: "="
        },
        link: function (scope, element, attrs, ctrl) {

            //*** we changed to validate it in server
            if (attrs.ngModel) {
                scope.recaptcha.CaptCode = scope[attrs.ngModel];
            }

            //ctrl.$asyncValidators.validUbsCaptcha = function (modelValue, viewValue) {
            //    if (ctrl.$isEmpty(modelValue)) {
            //        return $q.when();
            //    }
            //    scope.recaptcha.CaptCode = modelValue;
            //    //HttpFactory.post(scope.recaptcha, "Stock/EmailProcesser/ValidateUbsCaptcha").then(function (response) {
            //    //    //return $q.resolve();   // to mark as valid or
            //    //    // return $q.reject();   // to mark as invalid
            //    //    if (response.data) {
            //    //        result.resolve();
            //    //    } else {
            //    //        result.reject();
            //    //    }
            //    //}, function (response) {
            //    //    result.reject(response);
            //    //});
            //    //return result.promise;
            //};
        }
    };
}]);;

ngAWDSApp.factory("breadcrumbsService", ['$rootScope', function ($rootScope) {
    var saveBreadcrumbs = function (breadcrumbs, item) {
        breadcrumbs = breadcrumbs || [];
        
        var isInsertNew = true;
        var isRemoveItem = false;
        var isDefinedValue2 = item.Value2 !== undefined;


        for (var i = 0; i < breadcrumbs.length; i++) {
            if (breadcrumbs[i].fieldNameInFilter == item.fieldNameInFilter) {
                isInsertNew = false;
                if (isDefinedValue2) {
                    if ($rootScope.webApp.util.isNullOrEmpty(item.Value) && $rootScope.webApp.util.isNullOrEmpty(item.Value2)) {
                        isRemoveItem = true;
                    }
                } else {
                    if ($rootScope.webApp.util.isNullOrEmpty(item.Value)) {
                        isRemoveItem = true;
                    }
                }
                if (isRemoveItem) {
                    //delete
                    breadcrumbs.removeItemByIndex(i);
                } else {
                    //edit
                    breadcrumbs[i].Text = item.Text;
                    breadcrumbs[i].Value = item.Value;
                    breadcrumbs[i].Value2 = item.Value2;//*** if value2 != undefined, it's mean the breadcrumbs has 2value (min - max)
                    //breadcrumbs[i].fieldNameInFilter = item.fieldNameInFilter;
                    //breadcrumbs[i].Sort = item.Sort;
                }

                break;
            }
        }
        if (isInsertNew == true) {
            //insert
            switch (item.fieldNameInFilter)
            {
                case 'SearchText':
                    item.Sort = 1;
                    break;
                case 'Make':
                    item.Sort = 2;
                    break;
                case 'Model':
                    item.Sort = 3;
                    break;
                case 'Series':
                    item.Sort = 4;
                    break;
                case 'Variant':
                    item.Sort = 5;
                    break;
                default:                    
                    item.Sort = breadcrumbs.getMaxByFieldName('Sort') + 1;
                    break;
            }
            

            if (isDefinedValue2) {
                if ($rootScope.webApp.util.hasVal(item.Value) && $rootScope.webApp.util.hasVal(item.Value2)) {
                    breadcrumbs.push(item);
                }
            } else {
                if ($rootScope.webApp.util.hasVal(item.Value)) {
                    breadcrumbs.push(item);
                }
            }
            
            
        }
        return breadcrumbs;
    }
    var removeBreadcrumbs = function (breadcrumbs, item) {
        breadcrumbs = breadcrumbs || [];
        for (var i = 0; i < breadcrumbs.length; i++) {
            if (breadcrumbs[i].fieldNameInFilter == item.fieldNameInFilter) {
                breadcrumbs.removeItemByIndex(i);
                break;
            }
        }
        //breadcrumbs = breadcrumbs.sort(breadcrumbs.sortBy("Sort", false, parseInt))
        return breadcrumbs.sort(breadcrumbs.sortBy("Sort", false, parseInt));            
    }
    return {
        saveBreadcrumbs: saveBreadcrumbs,
        removeBreadcrumbs: removeBreadcrumbs
    };
}]);;
ngAWDSApp.factory("commonFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {
    
    return {
        getPCodeState: function () {
            return HttpFactory.get({}, "Stock/Website/GetPCodeState");
        },
        getPCode: function (sSuburb) {
            return HttpFactory.get({ Suburb: sSuburb }, "Stock/Website/GetPCode");
        },
        getSystemMakes: function () {
            return HttpFactory.get({}, "Stock/VehicleRequest/GetSystemMakes");
        },
        getSystemModels: function (sMake) {
            return HttpFactory.get({Make: sMake}, "Stock/VehicleRequest/GetSystemModels");
        },
        getSystemMakesV2: function (oParams) {
            return HttpFactory.post(oParams, "Stock/VehicleRequest/GetSystemMakesV2");
        },
        getSystemModelsV2: function (oParams) {
            return HttpFactory.post(oParams, "Stock/VehicleRequest/GetSystemModelsV2");
        }
    };
}]);;
ngAWDSApp.factory("CommonServices", [function () {
    var _GeneratePagination = function (TotalRecords, TotalPage, CurrentPage) {
        var arrayPages = [];
        var minPage = 1;
        // output nice pagination
        // always have a group of 5
        var minRange = Math.max(minPage, CurrentPage - 2);
        var maxRange = Math.min(TotalPage, CurrentPage + 2);
        if (minRange != minPage) {
            arrayPages.push({
                idx: arrayPages.length,
                val: minPage + ""
            });
            arrayPages.push({
                idx: arrayPages.length,
                val: "..."
            });
        }
        for (var i = minRange; i <= maxRange; i++) {
            arrayPages.push({
                idx: arrayPages.length,
                val: i + ""
            });
        }
        if (maxRange != TotalPage) {
            arrayPages.push({
                idx: arrayPages.length,
                val: "..."
            });
            arrayPages.push({
                idx: arrayPages.length,
                val: TotalPage + ""
            });
        }
        return arrayPages;
    }
    return {
        generatePagination: _GeneratePagination
    };
}]);;
ngAWDSApp.factory("ERNewModelFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {

    var getUri = function (sAction) {
        return "Stock/NewModel/" + sAction;
    }
    var getUriAngular = function (sAction) {
        return "Stock/NewModelAngular/" + sAction;
    }
    var getFilterData = function (sAction, oParams) {
        return HttpFactory.post(oParams, getUriAngular(sAction));
    }
    //#region get
    var getDefaultData = function (oParams) {
        return HttpFactory.get(oParams, getUriAngular("GetDefaultData"));
    }
    //#endregion

    //#region post

    var getRecords = function (oParams) {
        return HttpFactory.post(oParams, getUriAngular("GetWebsiteNewModels"));
    }
    var getRecordsChild = function (oParams) {
        return HttpFactory.post(oParams, getUriAngular("GetWebsiteNewModelsChild"));
    }

    //#endregion

    //#region submit
    
    //#endregion

    return {
        getDefaultData: getDefaultData,
        getRecords: getRecords,
        getRecordsChild: getRecordsChild,
        getFilterData: getFilterData
    };
}]);;
ngAWDSApp.factory("ERProductFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {

    var getUri = function (sAction) {
        return "Stock/ERProduct/" + sAction;
    }
    var getUriAngular = function (sAction) {
        return "Stock/ERProductAngular/" + sAction;
    }
    var getFilterData = function (sAction, oParams) {
        return HttpFactory.post(oParams, getUriAngular(sAction));
    }
    //#region get
    var getDefaultData = function (oParams, sUrl) {
        if (angular.isUndefined(sUrl) || sUrl == null) sUrl = getUriAngular("GetDefaultData");
        return HttpFactory.get(oParams, sUrl);
    }
    //#endregion

    //#region post

    var getRecords = function (oParams) {
        return HttpFactory.post(oParams, getUriAngular("GetProducts"));
    }

    //#endregion

    //#region submit

    //#endregion

    return {
        getDefaultData: getDefaultData,
        getRecords: getRecords,
        getFilterData: getFilterData
    };
}]);;
ngAWDSApp.factory("FormMailFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {
    var sUrlEmailProcesserPrefix = "Stock/EmailProcesser/";
    var _GetDataFormSortBy = function (oParams, FormSortByNumber) {
        return HttpFactory.get(oParams, "Stock/WSStock/GetDataFormSortBy" + FormSortByNumber);
    }
    var _getUbsCaptcha = function (oParams) {
        return HttpFactory.get(oParams, sUrlEmailProcesserPrefix + "GetUbsCaptcha");
    }

    var _postToServer = function (oData, sAction)
    {
        var mappingData = function (item)
        {
            //*** Minh::20230804:: monday task #4914424617
            //*** When user submits a form, we need to collect more information as separate fields in ER.
            //'MailFrom', 
            var lFieldNames = ['ContactName', 'inputFullName', 'inputYourName', 'inputFirstName', 'inputName', 'FullName',
                'inputLastName', 'inputEmail', 'PhoneNumber', 'inputPhone',
                'IsReceivePromo', 'inputNewsSpecial', 'IsContactedBySalesperson', 'inputSalesPerson'];
            for (var i = 0; i < lFieldNames.length; i++) {
                $window.webApp.util.mappingContactInfor(item, lFieldNames[i]);
            }            
        }        
        mappingData(oData);
        if ($window.webApp.util.isDefined(oData.SecondMail)) mappingData(oData.SecondMail);

        return HttpFactory.post(oData, sUrlEmailProcesserPrefix + sAction);
    }

    //#region post
    var _verifyGoogleRecaptcha = function (oData) {
        return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "VerifyGoogleRecaptcha");
    }
    
    var _sendMailBasic = function (oData) {
        return _postToServer(oData, "SendMailBasic");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailBasic");
    }
    var _sendMailWithoutCap = function (oData) {
        return _postToServer(oData, "SendMailWithoutCap");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailWithoutCap");
    }
    var _sendMailBasicV2 = function (oData) {
        return _postToServer(oData, "SendMailBasicV2");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailBasicV2");
    }
    var _sendMailEnquiry = function (oData) {
        return _postToServer(oData, "SendMailEnquiry");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailEnquiry");
    }
    var _sendMailEnquiryV2 = function (oData) {
        return _postToServer(oData, "SendMailEnquiryV2");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailEnquiryV2");
    }
    var _sendMailSubscribe = function (oData) {
        return _postToServer(oData, "SendMailSubscribe");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailSubscribe");
    }
    var _sendMailWithNewModelMedia = function (oData) {
        return _postToServer(oData, "SendMailWithNewModelMedia");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailWithNewModelMedia");
    }
    var _sendMailWithNewModelMediaV2 = function (oData) {
        return _postToServer(oData, "SendMailWithNewModelMediaV2");
        //return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "SendMailWithNewModelMediaV2");
    }
    var getMainDealership = function (oData) {
        return HttpFactory.post(oData, "Stock/Website/GetMainDealership");
    }
    //#endregion

    //#region general
    var _validateUbsCaptcha = function (oData) {
        return HttpFactory.post(oData, sUrlEmailProcesserPrefix + "ValidateUbsCaptcha");
    }
    //#endregion
    return {
        verifyGoogleRecaptcha: _verifyGoogleRecaptcha,
        SendMailBasic: _sendMailBasic,
        sendMailWithoutCap: _sendMailWithoutCap,
        SendMailBasicV2: _sendMailBasicV2,
        SendMailEnquiry: _sendMailEnquiry,
        sendMailEnquiryV2: _sendMailEnquiryV2,
        SendMailSubscribe: _sendMailSubscribe,
        SendMailWithNewModelMedia: _sendMailWithNewModelMedia,
        sendMailWithNewModelMediaV2: _sendMailWithNewModelMediaV2,
        getUbsCaptcha: _getUbsCaptcha,
        validateUbsCaptcha: _validateUbsCaptcha,
        getMainDealership: getMainDealership
    };
}]);;
ngAWDSApp.factory("FormMailServices", ['$rootScope', 'FormMailFactory', 'HttpFactory', '$filter', function ($rootScope, FormMailFactory, HttpFactory, $filter) {

    // Create Base64 Object // https://scotch.io/tutorials/how-to-encode-and-decode-strings-with-base64-in-javascript
    var Base64 = {
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        encode: function (e) { var t = ""; var n, r, i, s, o, u, a; var f = 0; e = Base64._utf8_encode(e); while (f < e.length) { n = e.charCodeAt(f++); r = e.charCodeAt(f++); i = e.charCodeAt(f++); s = n >> 2; o = (n & 3) << 4 | r >> 4; u = (r & 15) << 2 | i >> 6; a = i & 63; if (isNaN(r)) { u = a = 64 } else if (isNaN(i)) { a = 64 } t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a) } return t },
        decode: function (e) { var t = ""; var n, r, i; var s, o, u, a; var f = 0; e = e.replace(/[^A-Za-z0-9+/=]/g, ""); while (f < e.length) { s = this._keyStr.indexOf(e.charAt(f++)); o = this._keyStr.indexOf(e.charAt(f++)); u = this._keyStr.indexOf(e.charAt(f++)); a = this._keyStr.indexOf(e.charAt(f++)); n = s << 2 | o >> 4; r = (o & 15) << 4 | u >> 2; i = (u & 3) << 6 | a; t = t + String.fromCharCode(n); if (u != 64) { t = t + String.fromCharCode(r) } if (a != 64) { t = t + String.fromCharCode(i) } } t = Base64._utf8_decode(t); return t },
        _utf8_encode: function (e) { e = e.replace(/\r\n/g, "\n"); var t = ""; for (var n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r) } else if (r > 127 && r < 2048) { t += String.fromCharCode(r >> 6 | 192); t += String.fromCharCode(r & 63 | 128) } else { t += String.fromCharCode(r >> 12 | 224); t += String.fromCharCode(r >> 6 & 63 | 128); t += String.fromCharCode(r & 63 | 128) } } return t },
        _utf8_decode: function (e) { var t = ""; var n = 0; var r = c1 = c2 = 0; while (n < e.length) { r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r); n++ } else if (r > 191 && r < 224) { c2 = e.charCodeAt(n + 1); t += String.fromCharCode((r & 31) << 6 | c2 & 63); n += 2 } else { c2 = e.charCodeAt(n + 1); c3 = e.charCodeAt(n + 2); t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); n += 3 } } return t },
        generateCode: function (capId) {
            var sCode = $rootScope.webApp.oWebsite.WebsiteID + capId + $filter('toDateStrShort')(new Date());//code format: webid + form.CapId + dd/MM/yyyy
            return this.encode(sCode);
        }
    }

    var isLoadedUbsCaptcha = function () {
        return $rootScope.webApp.util.isNullOrEmpty($rootScope.webApp.ga.recaptchaSiteKey);
    }
    var reloadUbsCaptcha = function (cbSuccess) {
        if ($rootScope.webApp.util.isNullOrEmpty($rootScope.webApp.ga.recaptchaSiteKey)) {
            FormMailFactory.getUbsCaptcha({}).then(function (response) {
                //$scope.Recaptcha = response.data;
                cbSuccess(response);
            }, function (responseError) {
            })
        } else {
            console.error("Minh: ", "cannot reload ubs captcha because RecaptchaSiteKey has value");
        }
    }
    var getWSStock = function (VehicleId, cbSuccess)
    {
        //HttpFactory.get({ VehicleId: VehicleId }, "Stock/WSStockAngular/GetStockDetail").then(function (response) {
        //    cbSuccess(response);
        //}, function (responseError) {
        //});
        console.error("web-error", "not found function WSStockAngular/GetStockDetail by vehicleid: " + VehicleId);
    }
    var getWSStockAndOptions = function (VehicleId, FilterId, cbSuccess) {
        HttpFactory.get({ VehicleId: VehicleId, FilterId: FilterId }, "Stock/WSStockAngular/GetDefaultDataStockDetail").then(function (response) {
            cbSuccess(response);
        }, function (responseError) {
        });
    }
    var getThankYouUri = function (sDirectiveName) {
        //*** Minh ::20190802:: created new function
        var sUri = "";
        if (angular.isDefined($rootScope.webApp) && angular.isDefined($rootScope.webApp.formMailOptions) && angular.isDefined($rootScope.webApp.formMailOptions) && angular.isDefined($rootScope.webApp.formMailOptions.redirectToThankYou)) {
            if (!$rootScope.webApp.util.isNullOrEmpty($rootScope.webApp.formMailOptions.redirectToThankYou[sDirectiveName]))
            {
                sUri = $rootScope.webApp.util.getUrlHasPrefix($rootScope.webApp.formMailOptions.redirectToThankYou[sDirectiveName]);//new setup
            }
        } else {
            if (angular.isDefined($rootScope.ConfigFormMail) && angular.isDefined($rootScope.ConfigFormMail[sDirectiveName]))
            {
                sUri = $rootScope.util.getUrlHasPrefix($rootScope.ConfigFormMail[sDirectiveName].sThankYou);//old setup
            }
        }
        return sUri;
    }
    var redirectToThankYou = function (sDirectiveName, element, sMsgResponse) {
        //*** Minh ::20190802:: created new function
        var sThankYouUri = getThankYouUri(sDirectiveName);
        if (sThankYouUri != "") {
            window.location = sThankYouUri;
        } else {
            if (!(angular.isDefined(sMsgResponse) && $rootScope.webApp.util.hasVal(sMsgResponse))) {
                sMsgResponse = "<strong>We have received your enquiry and will be in touch with you shortly.</strong><br /><br />Regards";
            }
            element.html(sMsgResponse);
        }
    }
    
    //var mappingContactInfor = function (oMail, fieldName)
    //{
    //    if ($rootScope.webApp.util.hasVal(oMail[fieldName]))
    //    {
    //        var mappingField = null;
    //        switch (fieldName) {
    //            case 'ContactName':
    //                mappingField = 'FirstName';
    //                break;
    //            //case 'ContactName':
    //            //    mappingField = 'LastName';
    //            //    break;
    //            case 'MailFrom':
    //                mappingField = 'ContactEmail';
    //                break;
    //            case 'ContactNumber':
    //            case 'PhoneNumber':
    //                mappingField = 'ContactNumber';
    //                break;
    //            //case 'ContactName':
    //            //    mappingField = 'FlagSubscribeForMarketing';
    //            //    break;
    //            //case 'ContactName':
    //            //    mappingField = 'FlagSalesContactRequest';
    //                break;
    //            default:
    //                break;
    //        }
    //        if (mappingField != null)
    //        {
    //            oMail[mappingField] = oMail[fieldName];
    //        }
            
    //    }
    //}

    return {
        reloadUbsCaptcha: reloadUbsCaptcha,
        isLoadedUbsCaptcha: isLoadedUbsCaptcha,
        getWSStock: getWSStock,
        getWSStockAndOptions: getWSStockAndOptions,
        getThankYouUri: getThankYouUri,
        redirectToThankYou: redirectToThankYou,
        Base64: Base64,

    };
}]);;
ngAWDSApp.factory("HttpFactory", ['$q', '$http', '$window', '$rootScope', '$sce', 'sessionstorageFactory', function ($q, $http, $window, $rootScope, $sce, sessionstorageFactory) {
    var _HTTPGet = function (oParams, sUrl) {
        return _HTTPGetApi(oParams, $rootScope.util.getUrlHasPrefix(sUrl));
    }
    
    var _HTTPPost = function (oData, sUrl) {
        return _HTTPPostApi(oData, $rootScope.util.getUrlHasPrefix(sUrl));
    }

    var _HTTPPostSerialization = function (data, sUrl) {
        return _HTTPPostApiSerialization(data, $rootScope.util.getUrlHasPrefix(sUrl));
    }
    var _HTTPUpload = function (oData, sUrl) {
        return _HTTPUploadApi(oData, $rootScope.util.getUrlHasPrefix(sUrl));
    }
    var _HTTPGetApi = function (oParams, sUrl) {
        var result = $q.defer();
        sUrl = angular.lowercase(sUrl);
        $http({
            method: 'GET',
            url: sUrl,
            params: oParams,
            beforeSend: function (req) {
            }
        }).then(function (response) {
            result.resolve(response);
        }, function (response) {
            result.reject(response);
        });
        return result.promise;
    }

    var _HTTPPostApi = function (oData, sUrl) {
        var deferred = $q.defer();        
        var oConfig = {
            method: 'POST',
            url: sUrl,
            data: oData,
            beforeSend: function (req) {
                //console.warn("Minh: before send " + sUrl, oData);
            }
        };
        //if ($window.webApp.antiForgeryAjaxToken) {
        //    oConfig.headers = {
        //        'RequestVerificationToken': oData.antiForgeryAjaxToken
        //    }
        //} else {
        //    if (oData.antiForgeryAjaxToken) {
        //        oConfig.headers = {
        //            'RequestVerificationToken': oData.antiForgeryAjaxToken
        //        }
        //        //delete oData.antiForgeryAjaxToken;
        //    }
        //}
        
        $http(oConfig).then(function (response) {
            deferred.resolve(response);
            
        }, function (responseError) {
            deferred.reject(responseError);
            
        });
        //.catch(function (responseError) {
        //    console.warn('Minh: $http-finally', responseError);
        //    deferred.reject(responseError);
        //})
        //.finally(function (response) {
        //    console.warn('Minh: $http-finally', response);
        //    deferred.resolve(response);
        //});
        return deferred.promise;
    }
    var _HTTPPostApiSerialization = function (data, sUrl) {
        var result = $q.defer();
        $http({
            method: 'POST',
            url: sUrl,
            data: $.param(data),
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
            },
            beforeSend: function (req) {
            }
        }).then(function (response) {
            result.resolve(response);
        }, function (response) {
            result.reject(response);
        });
        return result.promise;
    }
    var _HTTPUploadApi = function (oData, sUrl) {
        var result = $q.defer();
        $http({
            method: 'POST',
            url: sUrl,
            data: oData,
            headers: {
                'Content-Type': undefined
            },
            beforeSend: function (req) {
            }
        }).then(function (response) {
            result.resolve(response);
        }, function (response) {
            result.reject(response);
        });
        return result.promise;
    }

    var _HTTPJsonp = function (sUrl, oParams) {
        var result = $q.defer();
        sUrl = angular.lowercase(sUrl);
        var trustedUrl = $sce.trustAsResourceUrl(sUrl); //via $sce as a trusted resource URL

        $http.jsonp(trustedUrl, { jsonpCallbackParam: 'callback' })
            .then(function (response) {
                result.resolve(response);
            }, function (response) {
                result.reject(response);
            });
        //stackoverflow.com/questions/12066002/parsing-jsonp-http-jsonp-response-in-angular-js
        return result.promise;
        //var result = $q.defer();
        //sUrl = angular.lowercase(sUrl);
        //var trustedUrl = $sce.trustAsResourceUrl(sUrl);
        //$http({
        //    method: 'JSONP',
        //    url: trustedUrl,
        //    jsonpCallbackParam: 'callback',
        //    beforeSend: function (req) {
        //    }
        //}).then(function (response) {
        //    result.resolve(response);
        //}, function (response) {
        //    result.reject(response);
        //});
        //return result.promise;
    }


    var fnPromise = function (fn, data)
    {
        var deferred = $q.defer();
        setTimeout(function () {
            if (angular.isFunction(fn)) {
                try {
                    var data = fn.call(this, data);
                    deferred.resolve({
                        config: {
                            url: fn.name,
                            method: 'function'
                        },
                        data: data//fn.call(this, data)
                    });
                } catch (e) {
                    deferred.reject('function call fail.');
                }
            } else {
                deferred.reject('is not function type');
            }
        }, 100);
        return deferred.promise;

        

        //return $q(function (resolve, reject) {
        //    setTimeout(function () {
        //        if (angular.isFunction(fn)) {
        //            resolve({
        //                config: {
        //                    url: fn.name,
        //                    method: 'function'
        //                },
        //                data: fn.call(this, data)
        //            });
        //        } else {
        //            reject('is not function type');
        //        }                
        //    }, 100);
        //});
    }
    

    var _HTTPPostWithSessionStorage = function (oData, sUrl, secondDuration)
    {
        //var userPortal = window.webApp.userPortal;
        var iWebsiteID = ($window.webApp && $window.webApp.oWebsite) ? $window.webApp.oWebsite.WebsiteID : 0;
        var sReleaseVersionNumberAreas = ($window.webApp && $window.webApp.vAreas) ? $window.webApp.vAreas : '2526.0607.9';
        var sKey = angular.toJson({ action: sUrl, param: oData, websiteId: iWebsiteID, vAreas: sReleaseVersionNumberAreas, userPortal: window.webApp.userPortal });
        var sKeyBase64 = $window.webApp.util.base64Extend.encode(sKey);

        if (sessionstorageFactory.get(sKeyBase64) != false) {            
            return fnPromise(function () {
                var decodeResponse = $window.webApp.util.base64Extend.decode(sessionstorageFactory.get(sKeyBase64));
                var response = angular.fromJson(decodeResponse);
                return response.data;
            }, {});
        }
        else {
            return $q.all([_HTTPPostApi(oData, $rootScope.util.getUrlHasPrefix(sUrl))])
                .then(function (responses) {

                    var encodeResponse = $window.webApp.util.base64Extend.encode(angular.toJson(responses[0]));
                    sessionstorageFactory.set(sKeyBase64, encodeResponse, secondDuration);

                    return responses[0];
                },
                    function (responsesError) {
                        return responsesError[0];
                    }
                );
        }
        //return _HTTPPostApi(oData, $rootScope.util.getUrlHasPrefix(sUrl));
    }
    var _HTTPGetWithSessionStorage = function (oData, sUrl, secondDuration) {
        //var userPortal = window.webApp.userPortal;
        var iWebsiteID = ($window.webApp && $window.webApp.oWebsite) ? $window.webApp.oWebsite.WebsiteID : 0;
        var sKey = angular.toJson({ action: sUrl, param: oData, websiteId: iWebsiteID, userPortal: window.webApp.userPortal });
        var sKeyBase64 = $window.webApp.util.base64Extend.encode(sKey);

        if (sessionstorageFactory.get(sKeyBase64) != false) {
            return fnPromise(function () {
                var decodeResponse = $window.webApp.util.base64Extend.decode(sessionstorageFactory.get(sKeyBase64));
                var response = angular.fromJson(decodeResponse);
                return response.data;
            }, {});
        }
        else {
            return $q.all([_HTTPGetApi(oData, $rootScope.util.getUrlHasPrefix(sUrl))])
                .then(function (responses) {

                    var encodeResponse = $window.webApp.util.base64Extend.encode(angular.toJson(responses[0]));
                    sessionstorageFactory.set(sKeyBase64, encodeResponse, secondDuration);

                    return responses[0];
                },
                    function (responsesError) {
                        return responsesError[0];
                    }
                );
        }
        //return _HTTPPostApi(oData, $rootScope.util.getUrlHasPrefix(sUrl));
    }

    return {
        get: _HTTPGet,
        getApi: _HTTPGetApi,
        post: _HTTPPost,
        postApi: _HTTPPostApi,
        postSerialization: _HTTPPostSerialization,
        upload: _HTTPUpload,
        fnPromise: fnPromise,
        jsonpApi: _HTTPJsonp,
        postWithSession: _HTTPPostWithSessionStorage,
        getWithSession: _HTTPGetWithSessionStorage
    };
}]);

;
ngAWDSApp.factory('localstorageFactory', ['$window', function ($window) {
    return {
        set: function (key, value) {
            $window.localStorage[key] = value;
        },
        get: function (key, defaultValue) {
            return $window.localStorage[key] || defaultValue || false;
        },
        setObject: function (key, value) {
            $window.localStorage[key] = JSON.stringify(value);
        },
        getObject: function (key, defaultValue) {
            if ($window.localStorage[key] != undefined) {
                return JSON.parse($window.localStorage[key]);
            } else {
                return defaultValue || false;
            }
        },
        remove: function (key) {
            $window.localStorage.removeItem(key);
        },
        clear: function () {
            $window.localStorage.clear();
        }
    }
}]);

ngAWDSApp.factory('sessionstorageFactory', ['$window', '$filter', function ($window, $filter) {
    var _formatDateExpiry = "YYYYMMDDHHMMSS"; //.toYYYYMMDDHHMMSS()

    var _generateDateExpiry = function (secondDuration)
    {
        var val = '';
        if (secondDuration > 0)
        {
            var dtNow = new Date();
            dtNow.setSeconds(secondDuration);
            val = dtNow.toYYYYMMDDHHMMSS();// $filter('date')(dtNow, _formatDateExpiry, null);
        }
        return val;
    }
    var _getValueWithoutDateExpiry = function (value) {
        
        if (value != null) {
            var data = value.substring(0, value.length - _formatDateExpiry.length);
            var sDateFormatted = value.substring(value.length - _formatDateExpiry.length, value.length);
            if (isFinite(sDateFormatted) == false) return value;//*** if end string not datetime format, return value back

            var y = sDateFormatted.substring(0, 4);
            var m = sDateFormatted.substring(4, 6); m = parseInt(m) - 1;
            var d = sDateFormatted.substring(6, 8);
            var hh = sDateFormatted.substring(8, 10);
            var mm = sDateFormatted.substring(10, 12);
            var ss = sDateFormatted.substring(12, 14);
            var dtExpired = new Date(y, m, d, hh, mm, ss);

            var dtNow = new Date();
            if (dtExpired <= dtNow) {
                return null;
            } else {
                return data;
            }
            
        }
        return null;
    }

    return {
        set: function (key, value, secondDuration) {
            secondDuration = secondDuration || ((1 * 60) * 60);//default: session will expire about 1 hrs
            $window.sessionStorage[key] = value + _generateDateExpiry(secondDuration);
        },
        get: function (key, defaultValue) {
            if ($window.sessionStorage[key] != undefined) {
                var val = _getValueWithoutDateExpiry($window.sessionStorage[key]);
                if (val == null) {
                    return (defaultValue || false);
                } else {
                    return val;
                }
            } else {
                return defaultValue || false;
            }
            //return $window.sessionStorage[key] || defaultValue || false;
        },
        setObject: function (key, value) {
            $window.sessionStorage[key] = JSON.stringify(value);
        },
        getObject: function (key, defaultValue) {
            if ($window.sessionStorage[key] != undefined) {
                return JSON.parse($window.sessionStorage[key]);
            } else {
                return defaultValue || false;
            }
        },
        remove: function (key) {
            $window.sessionStorage.removeItem(key);
        },
        clear: function () {
            $window.sessionStorage.clear();
        }
    }
}]);;
ngAWDSApp.factory('ModalService', ['$window', '$rootScope', '$uibModal', function ($window, $rootScope, $uibModal) {
    var _defaultTitle = null;

    var _custom = function (options) {
        //if (oData == null) oData = {};
        //if (oData.sTitle == null || oData.sTitle == undefined) oData.sTitle = _defaultTitle;

        var _defaultOption = {
            //title: 'EngineRoom',
            cbSuccess: function () { },
            modal: {
                animation: true,
                backdrop: 'static',//*** default true. Includes a modal-backdrop element. Alternatively, specify static for a backdrop which doesn't close the modal on click.
                //template: '<div class="modal-header">'
                //        + '<h3 class="modal-title">' + _defaultTitle + '</h3>'
                //    + '</div>'
                //    + '<div class="modal-body">'                        
                //    + '</div>'
                //    + '<div class="modal-footer">'
                //        + '<button class="btn btn-primary" ng-click="saveModal()">OK</button>'
                //        + '<button class="btn btn-warning" ng-click="closeModal()">Cancel</button>'
                //    + '</div>',
                //controller: 'ConfirmModalCtrl',
                resolve: {
                    param: function () {
                        return {};
                    },
                    modalOpts: function () {
                        return {};
                    }
                }
            }
        }
        var oOptions = angular.merge(_defaultOption, options);

        var modalInstance = $uibModal.open(oOptions.modal);
        modalInstance.result.then(function (response) {
            var dataItem = response.data;
            oOptions.cbSuccess(dataItem);
        }, function () {
            //console.warn('modal-component dismissed at: ' + new Date());
        });
    }
    var _confirm = function (oData, cbSuccess) {
        if (oData == null) oData = {};
        if (oData.sTitle == null || oData.sTitle == undefined) oData.sTitle = _defaultTitle;

        var modalInstance = $uibModal.open({
            animation: true,
            backdrop: 'static',
            windowClass: "",
            //templateUrl: "modalLogContentTemplateScript",
            template: '<div class="modal-header">'
                        + '<h3 class="modal-title" ng-show="oParam.sTitle != null">' + oData.sTitle + '</h3>'
                    + '</div>'
                    + '<div class="modal-body">'
                        //+ 'Are you sure you want to delete the <b>' + name + '</b>?'
                        + oData.sBody
                    + '</div>'
                    + '<div class="modal-footer">'
                        + '<button class="btn btn-primary" ng-click="saveModal()">OK</button>'
                        + '<button class="btn btn-warning" ng-click="closeModal()">Cancel</button>'
                    + '</div>',
            controller: 'ConfirmModalCtrl',
            size: 'sm',
            resolve: {
                param: function () {
                    return oData;
                },
                modalOpts: function () {
                    return {};
                }
            }
        });

        modalInstance.result.then(function (response) {
            var dataItem = response.data;
            cbSuccess(dataItem);
        }, function (responseDismissed) {
            console.warn('modal-component dismissed at: ' + new Date(), responseDismissed);
        });
    }
    var _confirm2 = function (oData, cbSuccess) {
        if (oData == null) oData = {};
        if (oData.sTitle == null || oData.sTitle == undefined) oData.sTitle = _defaultTitle;
        if (oData.sType == null || oData.sType == undefined) oData.sType = 'default';//"primary", "info", "success", "warning", "error"

        var modalInstance = $uibModal.open({
            animation: true,
            backdrop: 'static',
            windowClass: 'modal-confirm2 ' + ' color-' + oData.sType,
            //templateUrl: "modalLogContentTemplateScript",
            template: '<div class="modal-body">'
                        //+ 'Are you sure you want to delete the <b>' + name + '</b>?'
                        + '<div class="modal-confirm2-img">' + '</div>'
                        + '<div class="modal-confirm2-body">'
                            + '<h3 class="modal-title" ng-show="oParam.sTitle != null">' + oData.sTitle + '</h3>'
                            + '<p>' + oData.sBody + '</p>'
                        + '</div>'                        
                    + '</div>'
                    + '<div class="modal-footer">'
                        + '<button class="btn btn-primary btn-OK" ng-click="saveModal()">' + oData.commandOk.text + '</button>'
                        + '<button class="btn btn-warning btn-cancel" ng-click="closeModal()">' + oData.commandCancel.text + '</button>'
                    + '</div>',
            controller: 'Confirm2ModalCtrl',
            size: 'md',
            resolve: {
                param: function () {
                    return oData;
                },
                modalOpts: function () {
                    return {};
                }
            }
        });

        modalInstance.result.then(function (response) {
            //var dataItem = response.data;
            cbSuccess(response);
        }, function (responseDismissed) {
            //console.warn('modal-component dismissed at: ' + new Date(), responseDismissed);
            cbSuccess(responseDismissed);
        });
    }
    var _alert = function (oData, cbSuccess) {
        if (oData == null) oData = {};
        if (oData.sTitle == null || oData.sTitle == undefined) oData.sTitle = _defaultTitle;

        var modalInstance = $uibModal.open({
            animation: true,
            backdrop: 'static',
            windowClass: "",
            //templateUrl: "modalLogContentTemplateScript",
            template: '<div class="modal-header">'
                        + '<h3 class="modal-title" ng-show="oParam.sTitle != null">' + oData.sTitle + '</h3>'
                    + '</div>'
                    + '<div class="modal-body" ng-bind-html="oParam.sBody | trustAsHtml">'
                    + '</div>'
                    + '<div class="modal-footer">'
                        + '<button class="btn btn-primary" ng-click="saveModal()">OK</button>'
                        //+ '<button class="btn btn-warning" ng-click="closeModal()">Cancel</button>'
                    + '</div>',
            controller: 'ConfirmModalCtrl',
            size: 'md',
            resolve: {
                param: function () {
                    return oData;
                },
                modalOpts: function () {
                    return {};
                }
            }
        });

        modalInstance.result.then(function (response) {
            var dataItem = response.data;
            cbSuccess(dataItem);
        }, function () {
            //console.warn('modal-component dismissed at: ' + new Date());
        });
    }

    var _loginModal = function (options)
    {
        var _defaultOption = {
            templateUrl: 'modalLoginUserTemplate.html',
            bActiveLoginTab: true
        };
        var oOptions = angular.merge(_defaultOption, options);

        _custom({
            modal: {
                size: 'md',
                windowClass: "modal-window-login-register",
                templateUrl: oOptions.templateUrl,//'modalLoginUserTemplate.html',
                resolve: {
                    param: function () {
                        return { bActiveLoginTab: oOptions.bActiveLoginTab };
                    }
                },
                controller: ['$scope', '$uibModalInstance', 'param', 'Notification', '$rootScope', 'HttpFactory', '$parse', 'vcRecaptchaService', '$window', 'FormMailFactory', function (scopeModal, $uibModalInstance, param, Notification, $rootScope, HttpFactory, $parse, vcRecaptchaService, $window, FormMailFactory) {

                    var sUrlLoginController = "Stock/ShoppingCart";
                    var oUrlLoginAction = {
                        login: sUrlLoginController + '/Login',
                        loginSocial: sUrlLoginController + '/LoginSocial',
                        saveMyAccRegister: 'Stock/WebsiteClient/SaveMyAccRegister'
                    }

                    scopeModal.oParam = param || {};

                    scopeModal.sCurrentSelectedTab = '#modalTabLogin';
                    scopeModal.oLogin = {};
                    scopeModal.oRegisterAcc = {};
                    scopeModal.Recaptcha = {};

                    //#region ScreenInitilise
                    scopeModal.initModalLoginUserTemplate = function () {
                        scopeModal.bActiveLoginTab = $rootScope.webApp.util.toBool(scopeModal.oParam.bActiveLoginTab);
                        if (scopeModal.bActiveLoginTab) {
                            scopeModal.sCurrentSelectedTab = '#modalTabLogin';
                        } else {
                            scopeModal.sCurrentSelectedTab = '#modalTabSignUp';
                        }
                        scopeModal.loadCaptcha();
                    }
                    //#endregion

                    //#region GeneralFunction
                    var _loginMyAcc = function (params, cbSuccess) {
                        var sUrl = oUrlLoginAction.login;
                        if (params.ClientSource != 'System') {
                            sUrl = oUrlLoginAction.loginSocial;
                        }
                        HttpFactory.post(params, sUrl).then(function (response) {
                            var jsonData = response.data;
                            var serverMessage = $parse('frmLoginMyAccForModal.LoginEmail.$error.serverMessage');
                            if (jsonData.ErrorMsg != "") {
                                //*** show error message ***
                                scopeModal.frmLoginMyAccForModal.LoginEmail.$setValidity("serverMessage", false);
                                serverMessage.assign(scopeModal, jsonData.ErrorMsg);
                            } else {
                                cbSuccess(response);
                            }
                        }, function () { })
                    }
                    scopeModal.sendRegisterMyAccToServer = function (cbSuccess) {
                        HttpFactory.post(scopeModal.oRegisterAcc, oUrlLoginAction.saveMyAccRegister).then(function (response) {
                            if (response.data.ResponseMessage != "") {
                                //Notification.error(response.data.ResponseMessage);
                                var serverMessage = $parse('frmRegisterMyAccForModal.RegisterPass.$error.serverMessage');
                                scopeModal.frmRegisterMyAccForModal.RegisterPass.$setValidity("serverMessage", false);
                                serverMessage.assign(scopeModal, response.data.ResponseMessage);
                                scopeModal.reloadUbsCaptcha();
                            } else {
                                cbSuccess();

                            }
                            $rootScope.webApp.util.showLoadingIcon($element, false);
                        }, function (responseError) {
                            Notification.errorStd('Unknown Error.');
                            $rootScope.webApp.util.showLoadingIcon($element, false);
                            console.error("Send Mail", responseError);
                            scopeModal.reloadUbsCaptcha();
                        });
                    }
                    scopeModal._registerMyAcc = function (cbSuccess) {
                        scopeModal.frmRegisterMyAccForModal.submitted = true;

                        if (scopeModal.frmRegisterMyAccForModal.$valid) {
                            scopeModal.oRegisterAcc.Source = 'System';

                            if (scopeModal.isUbsCaptcha == false) {
                                if ($rootScope.webApp.util.trimString($rootScope.webApp.ga.recaptchaSize) == "invisible") {
                                    scopeModal.Recaptcha.cbSuccess = cbSuccess;
                                    //*** grecaptcha.execute only works with invisible reCAPTCHA.
                                    //*** google api will call callback function "scopeModal.Recaptcha.SetWidgetId()"
                                    vcRecaptchaService.execute(scopeModal.Recaptcha.widgetId);
                                } else {
                                    scopeModal.verifyGoogleRecaptchaAndSend({ sRecaptchaResponse: scopeModal.Recaptcha.response, cbSuccess: cbSuccess });
                                }
                            } else {
                                scopeModal.sendRegisterMyAccToServer(cbSuccess);
                            }
                        }
                    }
                    //#endregion

                    //#region DOMEvent
                    scopeModal.selectTab = function (sTarget) {
                        scopeModal.sCurrentSelectedTab = sTarget;
                    }
                    scopeModal.changeLoginEmail = function () {
                        var serverMessage = $parse('frmLoginMyAccForModal.LoginEmail.$error.serverMessage');
                        if (scopeModal.frmLoginMyAccForModal.LoginEmail.$error.serverMessage) {
                            scopeModal.frmLoginMyAccForModal.LoginEmail.$setValidity("serverMessage", true);
                            serverMessage.assign(scopeModal, undefined);
                        }
                    }
                    scopeModal.changeLoginPass = function () {
                        var serverMessage = $parse('frmLoginMyAccForModal.LoginEmail.$error.serverMessage');
                        if (scopeModal.frmLoginMyAccForModal.LoginEmail.$error.serverMessage) {
                            scopeModal.frmLoginMyAccForModal.LoginEmail.$setValidity("serverMessage", true);
                            serverMessage.assign(scopeModal, undefined);
                        }
                    }
                    scopeModal.changeRegisterEmail = function () {
                        var serverMessage = $parse('frmRegisterMyAccForModal.RegisterPass.$error.serverMessage');
                        if (scopeModal.frmRegisterMyAccForModal && scopeModal.frmRegisterMyAccForModal.RegisterPass.$error.serverMessage) {
                            scopeModal.frmRegisterMyAccForModal.RegisterPass.$setValidity("serverMessage", true);
                            serverMessage.assign(scopeModal, undefined);
                        }
                    }
                    scopeModal.changeRegisterPass = function () {
                        var serverMessage = $parse('frmRegisterMyAccForModal.RegisterPass.$error.serverMessage');
                        if (scopeModal.frmRegisterMyAccForModal && scopeModal.frmRegisterMyAccForModal.RegisterPass.$error.serverMessage) {
                            scopeModal.frmRegisterMyAccForModal.RegisterPass.$setValidity("serverMessage", true);
                            serverMessage.assign(scopeModal, undefined);
                        }
                    }
                    scopeModal.dismissModal = function () {
                        $uibModalInstance.dismiss({ data: {} });
                    }
                    scopeModal.closeModal = function () {
                        $uibModalInstance.close({ data: {} });
                    }

                    scopeModal.keypressInputLogin = function (e) {
                        if (e.which === 13) {
                            scopeModal.loginMyAccForModal();
                            e.preventDefault();
                        }
                    }
                    //#endregion

                    //#region save
                    scopeModal.loginMyAccForModal = function () {
                        scopeModal.frmLoginMyAccForModal.submitted = true;
                        if (scopeModal.frmLoginMyAccForModal.$valid) {
                            var params = angular.copy(scopeModal.oLogin);
                            params.ClientSource = "System";
                            params.Password = $rootScope.webApp.util.base64Extend.encode(params.Password);
                            params.IsCreateAccount = false;
                            _loginMyAcc(params, function (response) {
                                scopeModal.dismissModal();
                                $rootScope.$broadcast('btnLoginModalLogged', response); //*** the $rootScope.$broadcast event will fire an event down the $scope.$on('btnLoginModalLogged', {}) events
                            });
                        }
                    }
                    scopeModal.loginGoogleForModal = function () {

                        var GoogleAuthFetchUserDetails = function () {
                            var currentUser = scopeModal.gauth.currentUser.get();
                            var profile = currentUser.getBasicProfile();
                            var sFullName = profile.getName();
                            var sGivenName = profile.getGivenName();
                            var sFamilyName = profile.getFamilyName();
                            if (typeof (sFullName) == "undefined") {//*** gglogin
                                sFullName = profile.getEmail().split('@')[0];
                            }
                            if (typeof (sGivenName) == "undefined") {
                                sGivenName = sFullName;
                            }
                            var params = {
                                ClientSource: "Google",
                                //IsCreateAccount: false,
                                //Password: "",
                                Email: profile.getEmail(),
                                FullName: sFullName,
                                FirstName: sGivenName,
                                LastName: sFamilyName
                            };
                            //console.warn('google login user profile', params);
                            _loginMyAcc(params, function (response) {
                                scopeModal.dismissModal();
                                $rootScope.$broadcast('btnLoginModalLogged', response);
                            });
                        }
                        if (typeof ($window.webApp.ga.appId) == "undefined") {
                            console.error("sys-ex: google app ID is undefined");
                        } else {
                            if (typeof (scopeModal.gauth) == "undefined") {
                                scopeModal.gauth = gapi.auth2.getAuthInstance();
                                if (!scopeModal.gauth.isSignedIn.get()) {
                                    scopeModal.gauth.signIn({ scope: 'profile email' }).then(function (googleUser) {
                                        //console.warn('scopeModal.gauth.signIn', googleUser);
                                        GoogleAuthFetchUserDetails();
                                    }, function (err) {
                                        console.log(err);
                                    });
                                } else {
                                    GoogleAuthFetchUserDetails();
                                }
                            }
                        }
                    }

                    scopeModal.registerMyAccForModal = function () {
                        scopeModal.frmRegisterMyAccForModal.submitted = true;
                        if (scopeModal.frmRegisterMyAccForModal.$valid) {
                            scopeModal._registerMyAcc(function () {
                                var params = {};
                                params.ClientSource = "System";
                                params.Password = $rootScope.webApp.util.base64Extend.encode(scopeModal.oRegisterAcc.Password);
                                params.Email = scopeModal.oRegisterAcc.Email;
                                params.Phone = scopeModal.oRegisterAcc.Phone;
                                params.FirstName = scopeModal.oRegisterAcc.FirstName;
                                params.LastName = scopeModal.oRegisterAcc.LastName;
                                params.Postcode = scopeModal.oRegisterAcc.Postcode;
                                params.IsCreateAccount = false;
                                //$rootScope.$broadcast('btnLoginModalRegistered', { data: params });
                                //*** auto login after registered
                                _loginMyAcc(params, function (response) {
                                    scopeModal.dismissModal();
                                    $rootScope.$broadcast('btnLoginModalLogged', response); //*** the $rootScope.$broadcast event will fire an event down the $scope.$on('btnLoginModalLogged', {}) events
                                    $rootScope.webApp.util.showLoadingIcon($element, false);
                                });
                            });
                        }
                    }
                    //scopeModal.registerMyAccOfModal = function () {
                    //    $rootScope.$broadcast('btnLoginModalRegistered', 'saved');
                    //}
                    //#endregion

                    //#region google recaptcha
                    scopeModal.loadCaptcha = function () {
                        if ($rootScope.webApp.util.isNullOrEmpty($rootScope.webApp.ga.recaptchaSiteKey)) {
                            //ubs captcha
                            scopeModal.isUbsCaptcha = true;
                            scopeModal.reloadUbsCaptcha();

                        }
                        else {
                            function __onCreateAndSuccess(response, fn) {
                                if (angular.isDefined(fn) && fn == 'create') {
                                    console.log('recap-' + fn + ' widget ' + response);
                                    //__onCreate($scope[sObjName], response);
                                    scopeModal.cbCreateRecaptcha(response)
                                } else {
                                    console.log('recap-' + fn + ' response ');
                                    //__onSuccess($scope[sObjName], response);
                                    scopeModal.cbSuccessRecaptcha(response)
                                }
                            }
                            scopeModal.isUbsCaptcha = false;
                            //google captcha
                            scopeModal.Recaptcha = {
                                response: null,//https://github.com/VividCortex/angular-recaptcha
                                widgetId: null, SetResponse: __onCreateAndSuccess, SetWidgetId: __onCreateAndSuccess,
                                //SetResponse: scopeModal.cbCreateRecaptcha,//*** I have passed the function in wrong attr name "on-create". this function should be pass in attr name "on-success"
                                //SetWidgetId: scopeModal.cbSuccessRecaptcha,//*** I have passed the function in wrong attr name "on-success". this function should be pass in attr name "on-create" 
                                cbSendSuccess: function () { },
                                //SetResponse: function (widgetId) {
                                //    //scopeModal.Recaptcha.response = response;
                                //},
                                //SetWidgetId: function (response) {
                                //    //scopeModal.Recaptcha.widgetId = widgetId;
                                //},
                                Reset: function () {
                                    vcRecaptchaService.reload(scopeModal.Recaptcha.widgetId);
                                    scopeModal.Recaptcha.response = null;
                                }
                            }
                        }
                    }

                    scopeModal.reloadUbsCaptcha = function () {
                        if ($rootScope.webApp.util.isNullOrEmpty($rootScope.webApp.ga.recaptchaSiteKey)) {
                            FormMailFactory.getUbsCaptcha({}).then(function (response) {
                                scopeModal.Recaptcha = response.data;
                            }, function (responseError) {

                            })
                        } else {
                            //console.error("Minh: ", "cannot reload ubs captcha because RecaptchaSiteKey has value");
                            scopeModal.Recaptcha.Reset();
                        }
                    }
                    scopeModal.cbSuccessRecaptcha = function (response) {
                        scopeModal.Recaptcha.response = response;
                        if ($rootScope.webApp.util.trimString($rootScope.webApp.ga.recaptchaSize) == "invisible") {
                            //*** we have checked valid form when called vcRecaptchaService.execute                    
                            scopeModal.verifyGoogleRecaptchaAndSend({ sRecaptchaResponse: response });

                        } else {
                            //*** recaptcha with checkbox: no need to call verifyGoogleRecaptcha in here. we will call verify when user clicked on submit
                        }
                    }
                    scopeModal.cbCreateRecaptcha = function (widgetId) {
                        scopeModal.Recaptcha.widgetId = widgetId;
                    }
                    scopeModal.verifyGoogleRecaptchaAndSend = function (oParam) {

                        scopeModal.oRegisterAcc.CapCode = oParam.sRecaptchaResponse;
                        scopeModal.sendRegisterMyAccToServer(oParam.cbSuccess);//validate re-captcha in server

                    }
                    //#endregion

                    scopeModal.initModalLoginUserTemplate();
                }]
            },
            cbSuccess: function () {

            }
        });
    }

    return {
        confirm: _confirm,
        confirm2: _confirm2,
        alert: _alert,
        custom: _custom,
        loginModal: _loginModal
    };
}]);


ngAWDSApp.controller('ConfirmModalCtrl', ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) {
    //*** Modal controller doesn't have: $element, init(),...***
    //var params = $scope.$resolve.params

    $scope.oParam = {};

    $scope.initModal = function () {
        $scope.oParam = angular.copy($scope.$resolve.param);
    }();//*** auto call then initModal function when the function created by the end of function has ();



    //#region Save / callback function  
    $scope.saveModal = function () {
        $uibModalInstance.close({ data: $scope.oParam });
    }
    $scope.closeModal = function () {
        $uibModalInstance.dismiss({ data: 'cancel' })
    }
    //#endregion

}]);
ngAWDSApp.controller('Confirm2ModalCtrl', ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) {
    //*** Modal controller doesn't have: $element, init(),...***
    //var params = $scope.$resolve.params

    $scope.oParam = {};

    $scope.initModal = function () {
        $scope.oParam = angular.copy($scope.$resolve.param);
    }();//*** auto call then initModal function when the function created by the end of function has ();



    //#region Save / callback function  
    $scope.saveModal = function () {
        $uibModalInstance.close({ data: { command: $scope.oParam.commandOk, param: $scope.oParam } });
    }
    $scope.closeModal = function () {
        $uibModalInstance.dismiss({ data: { command: $scope.oParam.commandCancel, param: $scope.oParam } });
    }
    //#endregion

}]);;
ngAWDSApp.factory('NotificationExtend', ['$window', '$rootScope', 'Notification', '$templateCache', '$filter', '$timeout', function ($window, $rootScope, Notification, $templateCache, $filter, $timeout) {

    var defaultOptions = {
        layoutNum: 1,
        message: null,
        title: null,
        type: 'error',//"primary", "info", "success", "warning", "error"
        delay: 3000
    };
    var getPriceQty = function (price, qty) {
        price = CNumberNull(price);
        if (price != null) {
            price = price.toFixed(2);
        }
        return price * qty;
    }

    var _getLayoutNumber = function () {
        var iTemplateNum = 1;

        if ($rootScope.webApp.ssConfig) {
            if ($rootScope.webApp.ssConfig.noti_template_num) {
                iTemplateNum = $rootScope.webApp.ssConfig.noti_template_num || 1;
            }
        }
        return iTemplateNum;
    }
    var _getLayoutNumber4Qty = function () {
        var iTemplateNum = 1;

        if ($rootScope.webApp.ssConfig) {
            if ($rootScope.webApp.ssConfig.noti_qty_template_num) {
                iTemplateNum = $rootScope.webApp.ssConfig.noti_qty_template_num || 1;
            }
        }
        return iTemplateNum;
    }

    var _notiStdWithLayoutNumber = function (msg, title, type)
    {
        var iTemplateNum = _getLayoutNumber();

        switch (iTemplateNum) {
            case 2:
                Notification.standard({ layoutNum: 2, message: msg, title: title, type: type, delay: 6000 });
                break;
            default:
                Notification.standard({ layoutNum: 1, message: msg });
                break;
        }
    }

    var _getBasketNotificationTemplate = function (oNotiItem, opt) {
        opt = opt || {};
        opt.isWishlist = opt.isWishlist || false;
        opt.isEnquiryCart = opt.isEnquiryCart || false;

        var iTemplateNum = 1;
        var sHtml = '';
        var sHtmlButtons = '';

        if ($rootScope.webApp.ssConfig) {
            if ($rootScope.webApp.ssConfig.noti_template_num) {
                iTemplateNum = $rootScope.webApp.ssConfig.noti_template_num || 1;
            }
        }
        if (opt.isWishlist) {
            var priceCal = getPriceQty(oNotiItem.Price, oNotiItem.Quantity);
            switch (iTemplateNum) {
                case 2:
                    if (opt.action == 'A') {
                        sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                + '<div class="myaccount-popup color-success">'
                                    + '<div class="myaccount-popup-content">'
                                       + '<h3>Item Added to Your Wishlist</h3>'
                                       + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'
                                       + (priceCal > 0 ? '<p class="price ng-binding">PRICE: ' + $filter('currency')(priceCal, "$", 2) + '</p>' : '')
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    }
                    else {
                        //remove wishlist item
                        sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                + '<div class="myaccount-popup color-success">'
                                    + '<div class="myaccount-popup-content">'
                                       + '<h3>Item Removed to Your Wishlist</h3>'
                                       + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    }

                    break;
                default:
                    sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                //+ '<a class="close-icon" ng-click="nClick()">&times;</a>'
                                + '<div class="popup-shopping">'
                                    + '<div class="img-shop"><img src="' + oNotiItem.PicPath + '" class="img-responsive"></div>'
                                    + '<div class="item-class">'
                                        + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'
                                        + (priceCal > 0 ? '<p class="price ng-binding">PRICE: ' + $filter('currency')(priceCal, "$", 2) + '</p>' : '')
                                        + '<p><b>ITEM ADDED TO WISHLIST</b></p>'
                                        //+ '<p><a href="' + $rootScope.util.getUrlHasPrefix('/shopping-basket/checkout') + '">PROCEED TO CHECKOUT</a></p>'
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    break;
            }
        }
        else if (opt.isEnquiryCart) {            
            switch (iTemplateNum) {
                case 2:
                    if (opt.action == 'A') {
                        sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                + '<div class="myaccount-popup color-success">'
                                    + '<div class="myaccount-popup-content">'
                                       + '<h3>Item Added to Your Enquiry</h3>'
                                       + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    }
                    else {
                        //remove ENQUIRY item
                        sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                + '<div class="myaccount-popup color-success">'
                                    + '<div class="myaccount-popup-content">'
                                       + '<h3>Item Removed to Your Enquiry</h3>'
                                       + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    }

                    break;
                default:
                    sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                //+ '<a class="close-icon" ng-click="nClick()">&times;</a>'
                                + '<div class="popup-shopping">'
                                    + '<div class="img-shop"><img src="' + oNotiItem.PicPath + '" class="img-responsive"></div>'
                                    + '<div class="item-class">'
                                        + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'                                        
                                        + '<p><b>ITEM ADDED TO ENQUIRY</b></p>'                                        
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    break;
            }
        }
        else {
            //*** default: add to cart notification template
            switch (iTemplateNum) {
                case 2:
                    if ($rootScope.webApp.util.equalsLowerCase(oNotiItem.SalePortal, 'trade') == false) {
                        sHtmlButtons = '<a href="' + $rootScope.webApp.util.getUrlHasPrefix('/shopping-basket') + '" class="btn-1 btn-cart">Go to Cart <i></i></a>'
                                        + '<a href="' + $rootScope.webApp.util.getUrlHasPrefix('/shopping-basket/checkout') + '" class="btn-2 btn-checkout">Check Out <i></i></a>';
                    }
                    sHtmlButtons = '<div class="myaccount-btn-list">'
                                        + sHtmlButtons
                                    + '</div>';
                    sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                + '<div class="myaccount-popup color-success">'
                                    + '<div class="myaccount-popup-content">'
                                       + '<h3>Item Added to Your Cart</h3>'
                                       + '<p class="title ng-binding">' + oNotiItem.ItemTitle + ' x ' + oNotiItem.Quantity + '</p>'
                                       + '<p class="price ng-binding"><b>Total</b> ' + $filter('currency')(getPriceQty(oNotiItem.Price, oNotiItem.Quantity), "$", 2) + '</p>'
                                       + sHtmlButtons
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    break;
                default:
                    sHtml = '<div class="ui-notification custom-template notification-shoppingcart shopping-noti noti-v' + iTemplateNum + '">'
                                //+ '<a class="close-icon" ng-click="nClick()">&times;</a>'
                                + '<div class="popup-shopping">'
                                    + '<div class="img-shop"><img src="' + oNotiItem.PicPath + '" class="img-responsive"></div>'
                                    + '<div class="item-class">'
                                        + '<p class="title ng-binding">' + oNotiItem.ItemTitle + '</p>'
                                        + '<p class="price ng-binding">PRICE: ' + $filter('currency')(getPriceQty(oNotiItem.Price, oNotiItem.Quantity), "$", 2) + '</p>'
                                        + '<p><b>ITEM ADDED TO CART</b></p>'
                                        + '<p><a href="' + $rootScope.webApp.util.getUrlHasPrefix('/shopping-basket/checkout') + '">PROCEED TO CHECKOUT</a></p>'
                                    + '</div>'
                                + '</div>'
                            + '</div>';
                    break;
            }

        }
        return sHtml;
    }

    Notification.standard = function (data) {
        var cOptions = angular.merge({}, defaultOptions, data);
        switch (data.layoutNum) {
            case 2:
                Notification.layout1({ message: cOptions.message, title: cOptions.title, type: cOptions.type, delay: cOptions.delay });
                break;
            case 1:
            default:
                Notification.error(cOptions.message);
                break;
        }
    }
    
    Notification.errorStd = function (msg, title) {
        _notiStdWithLayoutNumber(msg, title, 'error');
        //var iTemplateNum = _getLayoutNumber();
        //switch (iTemplateNum) {
        //    case 2:
        //        Notification.standard({ layoutNum: 2, message: msg, title: title, type: 'error', delay: 6000 });
        //        break;
        //    default:
        //        Notification.standard({ layoutNum: 1, message: msg });
        //        break;
        //}
    }
    Notification.successStd = function (msg, title) {
        _notiStdWithLayoutNumber(msg, title, 'success');
        //var iTemplateNum = _getLayoutNumber();
        //switch (iTemplateNum) {
        //    case 2:
        //        Notification.standard({ layoutNum: 2, message: msg, title: title, type: 'success', delay: 6000 });
        //        break;
        //    default:
        //        Notification.standard({ layoutNum: 1, message: msg });
        //        break;
        //}
    }
    Notification.primaryStd = function (msg, title) {
        _notiStdWithLayoutNumber(msg, title, 'primary');
        //var iTemplateNum = _getLayoutNumber();
        //switch (iTemplateNum) {
        //    case 2:
        //        Notification.standard({ layoutNum: 2, message: msg, title: title, type: 'primary', delay: 6000 });
        //        break;
        //    default:
        //        Notification.standard({ layoutNum: 1, message: msg });
        //        break;
        //}
    }
    Notification.warningStd = function (msg, title) {
        _notiStdWithLayoutNumber(msg, title, 'warning');
    }
    Notification.infoStd = function (msg, title) {
        _notiStdWithLayoutNumber(msg, title, 'info');
    }

    Notification.errorQty = function (msg)
    {
        var iTemplateNum = _getLayoutNumber4Qty();
        
        switch (iTemplateNum) {
            case 3:
                msg = "Sorry we don't have any more in stock right now.";
                sDesc = "If you would like to order more of this item, please contact us to discuss availability and restocking lead-time.";                
                Notification.standard({ layoutNum: 2, message: sDesc, title: msg, type: 'error', delay: 6000 });
                break;
            case 2:
                sDesc = 'If you would like to order more, please call us on ' + $rootScope.webApp.oWebsite.DLRPhone1 + ' to discuss availability and restocking lead-time.';
                //Notification.layout1({ message: sDesc, title: msg, type: 'error', delay: 6000 });
                Notification.standard({ layoutNum: 2, message: sDesc, title: msg, type: 'error', delay: 6000 });
                break;
            default:
                //Notification.error(msg);
                Notification.standard({ layoutNum: 1, message: msg });
                break;
        }        
    }

    Notification.errorShopping = function (msg, title) {
        var iTemplateNum = _getLayoutNumber();

        switch (iTemplateNum) {
            case 2:
                title = title || 'Unknown Error';
                
                Notification.standard({ layoutNum: 2, message: msg, title: title, type: 'error', delay: 6000 });
                break;
            default:                
                Notification.standard({ layoutNum: 1, message: msg });
                break;
        }
    }

    Notification.layout1 = function (opt)
    {
        var oOptions = angular.merge({
            title: null,
            message: null,
            delay: null,
            type: 'primary',
            closeOnClick: true
        }, opt);
        oOptions.title = oOptions.title || null;
        /*
            docs: github.com/alexcrack/angular-ui-notification/blob/master/src/angular-ui-notification.js
            template-default: <div class="ui-notification"><h3 ng-if="title" ng-bind-html="title"></h3><div class="message" ng-bind-html="message"></div></div>
        */
        var htmlStr = '<div class="ui-notification notification-extend-layout1" >'
            + '<div class="myaccount-popup-content">'
                    + '<h3 ng-if="title" ng-bind-html="title"></h3>'
                    + '<p ng-bind-html="message"></p>'
                + '</div>'
            + '</div>';

        var noti_layout1_cache = 'angular-ui-notification-layout1.html';
        if ($templateCache.get(noti_layout1_cache)) {

        } else {
            $templateCache.put(noti_layout1_cache, htmlStr);
        }
        
        Notification({
            templateUrl: noti_layout1_cache,
            title: oOptions.title,
            message: oOptions.message,
            type: oOptions.type,
            closeOnClick: oOptions.closeOnClick,
            delay: oOptions.delay, //*** defined null value to reset default notification in main
            positionX: null, //*** defined null value to reset default notification in main
            classNoti: 'color-' + oOptions.type
        });
    }

    Notification.basketNoti = function (item, opts)
    {
        //opts = {
        //    action: 'A', // add(A) | remove(R)
        //    isWishlist: false,
        //    isEnquiryCart: false
        //}

        var bBindFlowbiteToBasketNotification = false;
        if ($rootScope.webApp.ssConfig) {
            if (angular.isDefined($rootScope.webApp.ssConfig.bindFlowbiteToBasketNoti))
            {
                bBindFlowbiteToBasketNotification = $rootScope.webApp.ssConfig.bindFlowbiteToBasketNoti;
            }
        }


        if (bBindFlowbiteToBasketNotification == true) {
            
            var sFlowb_dropdown_targetEL = '';
            var sFlowb_dropdown_triggerEL = '';
            if (opts.isEnquiryCart == true) {
                if (angular.isDefined($rootScope.webApp.ssConfig.flowb_dropdown_targetEL_enquire)) {
                    sFlowb_dropdown_targetEL = $rootScope.webApp.ssConfig.flowb_dropdown_targetEL_enquire;
                    sFlowb_dropdown_triggerEL = $rootScope.webApp.ssConfig.flowb_dropdown_triggerEL_enquire;
                } else {
                    sFlowb_dropdown_targetEL = $rootScope.webApp.ssConfig.flowb_dropdown_targetEL;
                    sFlowb_dropdown_triggerEL = $rootScope.webApp.ssConfig.flowb_dropdown_triggerEL;
                }
            } else {
                sFlowb_dropdown_targetEL = $rootScope.webApp.ssConfig.flowb_dropdown_targetEL;
                sFlowb_dropdown_triggerEL = $rootScope.webApp.ssConfig.flowb_dropdown_triggerEL;
            }
            // set the dropdown menu element
            const $targetEl = document.getElementById(sFlowb_dropdown_targetEL);
            //$targetEl = document.querySelector('.' + $rootScope.webApp.ssConfig.flowb_dropdown_targetEL);

            // set the element that trigger the dropdown menu on click
            const $triggerEl = document.getElementById(sFlowb_dropdown_triggerEL);

            //// options with default values
            //const options = {
            //    placement: 'bottom',
            //    triggerType: 'click',
            //    //offsetSkidding: 0,
            //    //offsetDistance: 10,
            //    //delay: 300,
            //    ignoreClickOutsideClass: false,
            //    onHide: () => {
            //        console.log('dropdown has been hidden');
            //    },
            //    onShow: () => {
            //        console.log('dropdown has been shown');
            //    },
            //    onToggle: () => {
            //        console.log('dropdown has been toggled');
            //    },
            //};

            //// instance options object
            //const instanceOptions = {
            //    id: $rootScope.webApp.ssConfig.flowb_dropdown_targetEL,//'dropdownMenu',
            //    override: false
            //};


            /*
             * $targetEl: required
             * $triggerEl: required
             * options: optional
             * instanceOptions: optional
             */
            //const dropdown = new Dropdown($targetEl, $triggerEl, options, instanceOptions);
            //const dropdown = new Dropdown($targetEl, $triggerEl);

            const dropdown = FlowbiteInstances.getInstance('Dropdown', sFlowb_dropdown_targetEL);

            dropdown.show();

            $timeout(function () {
                dropdown.hide();
            }, $rootScope.webApp.ssConfig.flowb_dropdown_delay);

        } else {
            var sMsgNotification = _getBasketNotificationTemplate(item, opts);
            $timeout(function () {
                Notification.primary(sMsgNotification);//*** add to cart ***
            }, 1);
        }
    }

    return Notification;
}]);;
ngAWDSApp.factory("SCReviewFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {

    var getUriSCReview = function (sAction) {
        return "Stock/ShoppingReview/" + sAction;
    }

    //#region get
    var getDefaultData = function (oParams) {
        return HttpFactory.post(oParams, getUriSCReview("GetDefaultSCRData"));
    }

    //#endregion

    //#region post

    var getShoppingReviews = function (oParams) {
        return HttpFactory.post(oParams, getUriSCReview("GetShoppingReviews"));
    }

    
    //#endregion

    //#region submit
    var saveReview = function (oData) {
        return HttpFactory.post(oData, getUriSCReview("SaveWriteAReview"));
    }
    //#endregion

    return {
        getDefaultData: getDefaultData,
        getShoppingReviews: getShoppingReviews,
        saveReview: saveReview
    };
}]);;
ngAWDSApp.factory("WSLUserFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {

    var getUriCtrl = function (sAction) {
        return "trade/account-detail/" + sAction;
    }
    //#region get
    var getDefaultDataList = function (oParams) {
        return HttpFactory.get(oParams, getUriCtrl("GetDefaultDataList"));
    }
    var getDefaultData = function (oParams) {
        return HttpFactory.get(oParams, getUriCtrl("GetDefaultData"));
    }
    //#endregion

    //#region post
    var getRecord = function (oParams) {
        return HttpFactory.post(oParams, getUriCtrl("GetWSLCompany"));
    }
    var getFilterData = function (sAction, oParams) {
        return HttpFactory.post(oParams, getUriCtrl(sAction));
    }
    //#endregion

    //#region submit

    var saveCompany = function (oParams) {
        return HttpFactory.post(oParams, getUriCtrl("SaveWSLCompany"));
    }
    var saveUser = function (oParams) {
        return HttpFactory.post(oParams, getUriCtrl("SaveWSLClient"));
    }
    var deleteUser = function (oParams) {
        return HttpFactory.post(oParams, getUriCtrl("DeleteWSLClient"));
    }
    var reinviteUser = function (oParams) {
        return HttpFactory.post(oParams, getUriCtrl("ReInviteWSLClient"));
    }

    //#endregion

    var logout = function () {
        return HttpFactory.get({}, "Stock/ShoppingCart/Logout");
    }

    return {
        getDefaultData: getDefaultData,
        getDefaultDataList: getDefaultDataList,
        getRecord: getRecord,
        //getRecords: getRecords,
        getFilterData: getFilterData,
        saveUser: saveUser,
        deleteUser: deleteUser,
        reinviteUser: reinviteUser,
        saveCompany: saveCompany,
        logout: logout
    };
}]);;
ngAWDSApp.factory("WSPartFactory", ['HttpFactory', '$window', '$q', function (HttpFactory, $window, $q) {

    var getUriWSPart = function (sAction) {
        return "Stock/WSPart/" + sAction;
    }
    var getUriWSPartAngular = function (sAction) {
        return "Stock/WSPartAngular/" + sAction;
    }
    var getFilterData = function (sAction, oParams) {
        //return HttpFactory.post(oParams, getUriWSPartAngular(sAction));
        return HttpFactory.postWithSession(oParams, getUriWSPartAngular(sAction), 3600);//***cache to session - duration: 1 hrs
    }

    var getFilterDataWithSession = function (sAction, oParams) {
        return HttpFactory.postWithSession(oParams, getUriWSPartAngular(sAction), 3600);//***cache to session - duration: 1 hrs
    }

    //#region get
    var getDefaultData = function (oParams) {
        return HttpFactory.get(oParams, getUriWSPartAngular("GetDefaultDataV2"));
    }
    var getDefaultDataByOpts = function (oParams) {
        return HttpFactory.post(oParams, getUriWSPartAngular("GetDefaultDataByOpts"));
    }
    //#endregion

    //#region post
    
    var getPartsList = function (oParams) {
        return HttpFactory.post(oParams, getUriWSPartAngular("GetParts"));
        //return HttpFactory.postWithSession(oParams, getUriWSPartAngular("GetParts"), (15 * 60));//***cache to session - duration: 15 mins


        //var promises = [HttpFactory.post(oParams, getUriWSPartAngular("GetParts"))];
        //return $q.all(promises)
        //        .then(function (responses) {

        //            //var encodeResponse = $window.webApp.util.base64Extend.encode(angular.toJson(responses[0]));
        //            //sessionstorageFactory.set(sKeyBase64, encodeResponse);

        //            return angular.fromJson(responses[0]);
        //        },
        //            function (responsesError) {
        //                return responsesError[0];
        //            }
        //        );
    }
    var getPartFranchises = function (oParams) {
        return getFilterData("GetPartFranchises", oParams);
    }
    var getMaxPrice = function (oParams) {
        return getFilterData("GetMaxPrice", oParams);
    }
    var getPartGroups = function (oParams) {
        return getFilterData("GetPartGroups", oParams);
    }
    var getPartGroupsStartsWith = function (oParams) {
        return getFilterData("GetPartGroupsStartsWith", oParams);
    }
    var getPartMainCategoriesWithPrefix = function (oParams) {
        return getFilterData("GetMainCategoriesWithPrefix", oParams);
    }
    var getPartCategories = function (oParams) {
        return getFilterDataWithSession("GetPartCategoriesV2", oParams);
        //return getFilterData("GetPartCategoriesV2", oParams);

        ////'sessionstorageFactory', '$q', 
        //var sKey = angular.toJson({ action: 'GetPartCategoriesV2', param: oParams });
        //var sKeyBase64 = $window.webApp.util.base64Extend.encode(sKey);
        //if (sessionstorageFactory.get(sKeyBase64) != false)
        //{
        //    console.warn('Minh: ss-', angular.fromJson(sessionstorageFactory.get(sKeyBase64)));
        //    return HttpFactory.fnPromise(function () {
        //        return angular.fromJson(sessionstorageFactory.get(sKeyBase64)).data;
        //    }, {});
        //}
        //else {
        //    return $q.all([getFilterData("GetPartCategoriesV2", oParams)])
        //        .then(function (responses) {
        //                sessionstorageFactory.set(sKeyBase64, angular.toJson(responses[0]));
        //                return responses[0];
        //            },
        //            function (responsesError) {
        //                return responsesError[0];
        //            }
        //        );
        //}
    }
    var getPartCategoriesNotExtra = function (oParams)
    {
        return getFilterData("GetPartCategoriesNotExtra", oParams);
    }
    var getPartCategoriesExtra = function (oParams) {
        return getFilterData("GetPartCategoriesExtra", oParams);
    }
    var getPartCategoriesDDL = function (oParams) {
        return getFilterData("GetPartCategoriesDDL", oParams);
    }
    var getVariants = function (oParams) {
        return getFilterData("GetVariants", oParams);
    }

    var getClientDeal = function (oParams) {
        return getFilterData("GetClientDeal", oParams);
        
        //*** HTTPget has error: A potentially dangerous Request.QueryString value was detected from the client (PartNumber="...AIDO-212 &#8713;").
        //return HttpFactory.get(oParams, getUriWSPartAngular('GetClientDeal'));
    }
    //#endregion

    //#region submit
    var submitFormSearch = function (oData) {
        return HttpFactory.post(oData, getUriWSPart("FormSearch"));
    }
    var submitFormSearchByStockNumber = function (oData) {
        return HttpFactory.post(oData, getUriWSPart("FormSearchByStockNumber"));
    }
    var saveGlobalSearchOption = function (oData) {
        return HttpFactory.post(oData, getUriWSPartAngular("SaveGlobalSearchOption"));
        
    }
    //#endregion

    return {
        getDefaultData: getDefaultData,
        getDefaultDataByOpts: getDefaultDataByOpts,
        getPartsList: getPartsList,
        getPartFranchises: getPartFranchises,
        getMaxPrice: getMaxPrice,
        getPartGroups: getPartGroups,
        getFilterData: getFilterData,
        getFilterDataWithSession: getFilterDataWithSession,
        getPartGroupsStartsWith: getPartGroupsStartsWith,
        getPartCategories: getPartCategories,
        getPartMainCategoriesWithPrefix: getPartMainCategoriesWithPrefix,
        getPartCategoriesExtra: getPartCategoriesExtra,
        getPartCategoriesNotExtra: getPartCategoriesNotExtra,
        getPartCategoriesDDL: getPartCategoriesDDL,
        getClientDeal: getClientDeal,
        saveGlobalSearchOption: saveGlobalSearchOption
    };
}]);

ngAWDSApp.factory("WSPartService", ['$rootScope', '$window', 'WSPartFactory', function ($rootScope, $window, WSPartFactory) {

    var getQtyStickerValue = function (oOption)
    {
        var sVal = "";
        if (angular.isDefined(oOption.minQtyLSE) && $rootScope.webApp.util.hasVal(oOption.minQtyLSE))
        {
            //MinQtyLSE is MinQtyLowStockEnquiry
            if (oOption.qav < oOption.minQtyLSE) {
                if ((angular.isDefined(oOption.isLSEWithQAV) && oOption.isLSEWithQAV == true) && oOption.totalQty == 0) {
                    sVal = "check-availability";
                }
                else {
                    sVal = "low-stock";
                }
            } else {
                sVal = "in-stock";
            }
        }
        else
        {
            if ($rootScope.webApp.util.hasVal(oOption.totalQty) == false) return "";

            if (oOption.totalQty == 0) {
                sVal = "check-availability";
            } else if (oOption.totalQty > 0 && oOption.totalQty < 3) {
                sVal = "low-stock";
            } else if (oOption.totalQty > 2) {
                sVal = "in-stock";
            }
        }
        
        return sVal;
    }

    var getClientDeal = function (item)
    {
        return WSPartFactory.getClientDeal({ Id: item.Id, PartNumber: item.PartNumber, FranchiseCode: item.FranchiseCode }).then(function (response) {
            if (response.data != null)
            {
                var jsonData = angular.fromJson(response.data);
                //if (jsonData.HasClientDeal == true) {
                //    return jsonData;
                //}
                return jsonData;
            }            
            return {};
        }, function (responseError) {
            return responseError;
        });
    }

    return {
        getQtyStickerValue: getQtyStickerValue,
        getClientDeal: getClientDeal
    };
}]);;
ngAWDSApp.factory("WSStockFactory", ['HttpFactory', '$window', function (HttpFactory, $window) {
    
    var getUriWSStock = function (sAction) {
        return "Stock/WSStock/" + sAction;
    }
    var getUriWSStockAngular = function (sAction) {
        return "Stock/WSStockAngular/" + sAction;
    }
    //#region get
    var getDefaultDataCaravan = function (oParams) {
        return HttpFactory.get(oParams, getUriWSStockAngular("GetDefaultDataCaravan"));
    }
    var getDefaultDataCaravanByOpts = function (oParams) {
        return HttpFactory.post(oParams, getUriWSStockAngular("GetDefaultDataCaravanByOpts"));
    }
    var getDefaultDataVehicle = function (oParams) {
        return HttpFactory.get(oParams, getUriWSStockAngular("GetDefaultDataVehicle"));
    }
    var getDefaultDataMotor = function (oParams) {
        return HttpFactory.get(oParams, getUriWSStockAngular("GetDefaultDataMotor"));
    }

    var getDefaultDataCar = function (oParams) {
        return HttpFactory.get(oParams, getUriWSStockAngular("GetDefaultDataCar"));
    }
    var getDefaultDataCarByOpts = function (oParams) {
        //*** if use GET method, we need to setting the requestLimits's maxQueryString (FlyAutohaus site using this)
        return HttpFactory.post(oParams, getUriWSStockAngular("GetDefaultDataCarByOpts"));
    }
    var getDefaultDataAuctionCatalogue = function (oParams) {
        return HttpFactory.get(oParams, getUriWSStockAngular("GetDefaultDataAuctionCatalogue"));
    }
    
    var getDataFormSearch = function (oParams, FormSearchName) {
        return HttpFactory.get(oParams, getUriWSStock("GetDataFormSearch" + FormSearchName));
    }

    var getStockOptionsByFilterId = function (oParams) {
        return HttpFactory.get(oParams, getUriWSStock("GetStockOptionsByFilterId"));
    }

    var getWSStockDetail = function (VehicleId) {
        //return HttpFactory.get({ VehicleId: VehicleId }, getUriWSStockAngular("GetStockDetail"));
    }
    var getWSStockDetailV2 = function (oParams) {
        //return HttpFactory.get({ VehicleId: VehicleId, FilterId: FilterId }, getUriWSStockAngular("GetStockDetailV2"));
        return HttpFactory.get(oParams, getUriWSStockAngular("GetStockDetailV2"));
    }
    var getWSStockAndOptions = function (oParams, cbSuccess) {
        HttpFactory.get(oParams, getUriWSStockAngular("GetDefaultDataStockDetail")).then(function (response) {
            cbSuccess(response);
        }, function (responseError) {
        });
    }
    var getAuctionCatalogues = function (oParams)
    {
        return HttpFactory.get(oParams, getUriWSStockAngular("GetAuctionCatalogues"));
    }
    //#endregion

    //#region post
    var getStrattonFinance = function (oParam, cbSuccess) {
        HttpFactory.post(oParam, getUriWSStock("GetMarineAndLeisureQuoteFromStrattonFinance")).then(function (response) {
            cbSuccess(response);
        }, function (responseError) {
        });
    }
    var getStockList = function (oParams) {
        return HttpFactory.post(oParams, getUriWSStockAngular("GetStockList"));
    }
    var getStockListByAuctionId = function (oParams) {
        return HttpFactory.post(oParams, getUriWSStockAngular("GetStockListByAuctionId"));
    }
    
    var getFilterData = function (sAction, oParams) {
        //if (sAction == 'GetLengthMetricSlider' || sAction == 'GetSleepFilterSlider')
        //{
        //    return HttpFactory.post(oParams, 'current-stock/' + (sAction));// GippslandRVStock site using this, will remove this line when back ThaiLan trip
        //}
        //*** Minh::20240618::  (sAction == 'GetLengthMetricSlider' || sAction == 'GetSleepFilterSlider') moved to Areas
        return HttpFactory.post(oParams, getUriWSStockAngular(sAction));
    }
    var getMake = function (oParams) {
        return getFilterData("GetMakes", oParams);
    }
    var getModel = function (oParams) {
        return getFilterData("GetModels", oParams);
    }
    var getSeries = function (oParams) {
        return getFilterData("GetSeries", oParams);
    }
    var getVariants = function (oParams) {
        return getFilterData("GetVariants", oParams);
    }    
    //#endregion
    
    //#region submit
    var submitFormSearch = function (oData) {
        return HttpFactory.post(oData, getUriWSStock("FormSearch"));
    }
    var submitFormSearchByStockNumber = function (oData) {
        return HttpFactory.post(oData, getUriWSStock("FormSearchByStockNumber"));
    }
    //#endregion

    return {
        submitFormSearch: submitFormSearch,
        submitFormSearchByStockNumber: submitFormSearchByStockNumber,
        getDefaultDataCaravan: getDefaultDataCaravan,
        getDefaultDataCaravanByOpts: getDefaultDataCaravanByOpts,
        getDefaultDataCar: getDefaultDataCar,
        getDefaultDataCarByOpts: getDefaultDataCarByOpts,
        getDefaultDataVehicle: getDefaultDataVehicle,
        getDefaultDataMotor: getDefaultDataMotor,
        getDefaultDataAuctionCatalogue: getDefaultDataAuctionCatalogue,
        getDataFormSearch: getDataFormSearch,
        getStockOptionsByFilterId: getStockOptionsByFilterId,
        getWSStockDetail: getWSStockDetail,
        getWSStockDetailV2: getWSStockDetailV2,
        getWSStockAndOptions: getWSStockAndOptions,
        getStrattonFinance: getStrattonFinance,
        getAuctionCatalogues: getAuctionCatalogues,
        getStockList: getStockList,
        getStockListByAuctionId: getStockListByAuctionId,
        getFilterData: getFilterData,
        getMake: getMake,
        getModel: getModel,
        getSeries: getSeries,
        getVariants: getVariants
    };
}]);;
ngAWDSApp.factory("WSStockServices", ['$rootScope', 'WSStockFactory', function ($rootScope, WSStockFactory) {

    var loadCreditOneFinace = function (oStockDetail, cbSuccess) {
        var sAssertType = null;
        if (oStockDetail.Variant != null) {
            switch (oStockDetail.Variant) {
                case "caravan": case "pop top": case "poptop": case "campervan":
                    sAssertType = "caravan";
                    break;
                case "camper trailer":
                    sAssertType = "campertrailer";
                    break;
                default:
                    //sAssertType = oStockDetail.Variant;
                    sAssertType = oStockDetail.Class;
                    break;
            }
        }
        var oParam = {
            assetType: sAssertType,
            amountToFinance: oStockDetail.PriceForProcessing,
            isNew: oStockDetail.Type.toLowerCase() == "new",
            state: oStockDetail.Registration.State,
            year: oStockDetail.YearForProcessing,
            purchaseSource: "dealer"
        }
        WSStockFactory.getStrattonFinance(oParam, function (response) {
            cbSuccess(response);
        })
    }
    
    
    return {
        loadCreditOneFinace: loadCreditOneFinace
    };
}]);;
ngAWDSApp.filter('convertMeterToFeet', ['$filter', function ($filter) {
    return function (val) {
        return $filter('convertMeterToFeet2')(val, null, true, null);
    };
}]);

ngAWDSApp.filter('convertMeterToFeet2', ['$filter', function ($filter) {
    return function (val, feetUnit, hasInch, inchUnit) {
        //console.warn("Minh: convertMeterToFeet2", val, feetUnit, hasInch, inchUnit);

        if (AWDSApp.util.isNullOrEmpty(val)) return "";
        if (Number(val) < 1) return "";        
        if (AWDSApp.util.isNullOrEmpty(feetUnit)) feetUnit = '\'';//feat
        if (AWDSApp.util.isNullOrEmpty(inchUnit))
        {
            if (feetUnit == "'")
            {
                inchUnit = '"';//inch
            }            
        }

        hasInch = AWDSApp.util.toBool(hasInch);

        var lInches, sFeet, sInch;

        ////######################################### monday task 7167471900.

        //c#: lInches = (long)Math.Round((text * (double)100 / 2.54), 0);
        lInches = Math.round((val * 100 / 2.54));

        //c#: sFeet = Math.Round((double)(lInches / 12), 0) + feetUnit;
        sFeet = Math.floor((lInches / 12)) + feetUnit;
                        
        sInch = (lInches % 12) + inchUnit;
        return sFeet + " " + sInch;

        //######################################### monday task #7084066051
        ////https://stackoverflow.com/questions/33474726/how-to-convert-meters-into-feet-and-inches
        //var feet = (val / 0.3048);        
        //var tempInch = (feet - Math.trunc(feet)) / 0.08333;//**** 1(inch) / 12 = 0.08333(ft)
        //var feetInt = parseInt(feet);
        //var inchInt = Math.round(tempInch);// parseInt(temp.toFixed());
        //if (inchInt >= 12) {
        //    //*** 1 feet = 12 inch. so if inchInt > 12, feet = feet + 1
        //    //*** custom logic (monday task #7084066051):  When a length is 12” it should add 1 foot and go back to 0” Example 15’ 12” -> 16’ 0” 
        //    feetInt = feetInt + Math.round(inchInt / 12);
        //    inchInt = 0;
        //}
        //var feetStr = $filter('extNumber')(feetInt, feetUnit, 0);
        //var inchStr = "";
        //if (hasInch == true)
        //{
        //    if (inchInt == 0) inchStr = "0" + inchUnit;
        //    else inchStr = $filter('extNumber')(inchInt, inchUnit, 0);
        //}
        //var sResult = feetStr;
        //if (!AWDSApp.util.isNullOrEmpty(inchStr)) {
        //    sResult = feetStr + " " + inchStr;
        //}
        //return sResult;

        //######################################### old 
        //var inch = (feet - feetInt) * 12;
        //var sResult = $filter('extNumber')(feet, feetUnit, 0);
        //var sInch = hasInch ? $filter('extNumber')(inch, inchUnit, 0) : "";
        //if (!AWDSApp.util.isNullOrEmpty(sInch)) {
        //    sResult = sResult  + " " + sInch;
        //}
        //return sResult;
    };
}]);


ngAWDSApp.filter('convertMillimeterToFeetInch', ['$filter', function ($filter) {
    return function (val, feetUnit, hasInch, inchUnit) {
        //console.warn("Minh: convertMeterToFeet2", val, feetUnit, hasInch, inchUnit);



        if (AWDSApp.util.isNullOrEmpty(val)) return "";
        if (Number(val) < 1) return "";

        if (AWDSApp.util.isNullOrEmpty(feetUnit)) feetUnit = '\'';//feat
        if (AWDSApp.util.isNullOrEmpty(inchUnit)) inchUnit = '"';//inch
        if (AWDSApp.util.isNullOrEmpty(hasInch)) hasInch = true;//default true, show inch


        //var metter = val / 1000; //to metter
        //return $filter('convertMeterToFeet2')(metter, "'", true, '"');

        //*** www.rapidtables.com/convert/length/mm-to-feet-inch.html?x=6230

        var metre = val * 0.001;
        return $filter('convertMeterToFeet2')(metre, feetUnit, hasInch, inchUnit);

        //var length = val / 25.4;
        //var feet = parseFloat(length / 12);
        //var feetInt = parseInt(feet);
        //var inch = length - (12 * feetInt);
        ////var inchInt = parseInt(inch);
        //return $filter('extNumber')(feetInt, feetUnit, 0) + " " + $filter('extNumber')(inch, inchUnit, 0);
    };
}]);;
ngAWDSApp.filter('extCurrency', ['$filter', function ($filter) {
    return function (val, symbol, fractionSize) {
        if (AWDSApp.util.isNullOrEmpty(val)) return "";
        if (Number(val) < 1) return "";
        return $filter('currency')(val, symbol, fractionSize);
    };
}]);

ngAWDSApp.filter('extCurrencyOriginal', ['$filter', function ($filter) {
    return function (val, symbol, fractionSize) {
        if (AWDSApp.util.isNullOrEmpty(val)) val = "";
        if (AWDSApp.util.isNullOrEmpty(symbol)) symbol = "$";
        if (AWDSApp.util.isNullOrEmpty(fractionSize)) fractionSize = 2;
        
        return $filter('currency')(val, symbol, fractionSize);
    };
}]);

ngAWDSApp.filter('extCurrencyZero', ['$filter', function ($filter) {
    return function (val, symbol, fractionSize) {
        var sVal = $filter('extCurrency')(val, symbol, fractionSize);
        if (sVal == "") {
            return symbol + "0";
        }
        else {
            return sVal;
        }
    };
}]);

ngAWDSApp.directive('compile5CentRounding', ['$compile', '$rootScope', '$window', '$filter', function ($compile, $rootScope, $window, $filter) {
    return {
        restrict: 'A',
        replace: true,
        scope: {
            compile5CentRounding: '=',
            isZero: '=?' // if true, will show zero
        },
        link: function (scope, element, attrs) {
            function formatPrice(val) {
                var sResult = $rootScope.webApp.util.to5CentRounding(val);
                if (angular.isDefined($rootScope.webApp.isUnuse5CentRoundingFormat) && $rootScope.webApp.util.toBool($window.webApp.isUnuse5CentRoundingFormat) == true)
                {
                    sResult = $filter('extCurrencyOriginal')(val, null, null);
                }
                
                if ($rootScope.webApp.util.toBool(scope.isZero)) {
                    if ($rootScope.webApp.util.hasVal(sResult) == false)
                    {
                        sResult = "$0.00";
                    }
                }
                if ($window.webApp && $rootScope.webApp.util.toBool($window.webApp.isPriceSpanFormat) == true) {
                    //sResult = sResult.replace(/(\D*)(\d*\.)(\d*)/, '<span style="font-size:16px;">$1</span><span style="font-size:22px;">$2</span><span style="font-size:14px;">$3</span>');
                    var splitPrice = sResult.split('.');
                    if (splitPrice.length > 1) {
                        sResult = '<span class="sPrice-1">' + splitPrice[0] + '</span><span class="sPrice-2">.' + splitPrice[1] + '</span>';
                    } else {
                        
                    }
                    
                }
                return sResult;
            }
            scope.$watch('compile5CentRounding', function (newVal, oldVal) {

                if (angular.isDefined(newVal)) {
                    element.html(formatPrice(newVal));
                    $compile(element.contents())(scope);
                }
               
            })
        }
    }
}]);
ngAWDSApp.filter('extDate', ['$rootScope', '$filter', function ($rootScope, $filter) {
    return function (val, format, timezone) {
        //date, format, timezone
        if (AWDSApp.util.isNullOrEmpty(val)) return "";
        
        if (AWDSApp.util.isNullOrEmpty(format)) format = "dd/MM/yyyy";

        return $filter('date')(val, format, timezone);
    };
}]);
ngAWDSApp.filter('toDateStrShort', ['$rootScope', '$filter', function ($rootScope, $filter) {
    return function (sVal) {
        //date, format, timezone
        if ($rootScope.webApp.util.isNullOrEmpty(sVal)) return "";

        format = "dd/MM/yyyy";
        sVal = $rootScope.webApp.util.toDateTime(sVal);

        return $filter('date')(sVal, format, null);
    };
}]);
ngAWDSApp.filter('toDateStrLong', ['$rootScope', '$filter', function ($rootScope, $filter) {
    return function (sVal) {
        //date, format, timezone
        if ($rootScope.webApp.util.isNullOrEmpty(sVal)) return "";

        format = "dd/MM/yyyy HH:mm:ss";
        sVal = $rootScope.webApp.util.toDateTime(sVal);

        return $filter('date')(sVal, format, null);
    };
}]);

ngAWDSApp.filter('extDateNum', ['$rootScope', '$filter', function ($rootScope, $filter) {
    return function (val, format, timezone) {
        //date-number, format, timezone
        
        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";

        if ($rootScope.webApp.util.isNullOrEmpty(format)) format = "dd/MM/yyyy";

        return $filter('date')($rootScope.webApp.util.formatDateNumberToDate(val), format, timezone);
    };
}]);

ngAWDSApp.filter('extDateNumLong', ['$rootScope', '$filter', function ($rootScope, $filter) {
    return function (val, format, timezone) {
        //date-number, format, timezone
        var format = "dd/MM/yyyy HH:mm:ss";

        return $filter('extDateNum')(val, format, null);
    };
}]);

;
ngAWDSApp.filter('extNumber', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val, symbol, fractionSize) {
        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";
        if (Number(val) < 1) return "";
        return $filter('number')(val, fractionSize) +((symbol) ? symbol : "");
    };
}]);
ngAWDSApp.filter('extNumberZero', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val, symbol, fractionSize) {
        var sVal = $filter('extNumber')(val, symbol, fractionSize);
        if (sVal == "") {
            return "0" + ((symbol) ? symbol : "");
        }
        else {
            return sVal;
        }
    };
}]);

ngAWDSApp.filter('extNumberZeroWithoutComma', ['$filter', function ($filter) {
    return function (val, symbol, fractionSize) {
        var sVal = $filter('extNumberZero')(val, symbol, fractionSize);        
        return sVal.replace(/,/g, "");
    };
}]);

ngAWDSApp.filter('extNumberOrdinal', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val, symbol, fractionSize) {
        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";
        if (Number(val) < 1) return "";

        switch (Number(val)) {
            case 1:
            case 21:
            case 31:
                return val + "st";
            case 2:
            case 22:
                return val + "nd";
            case 3:
            case 23:
                return val + "rd";
            default:
                return val + "th";
        }
        return "";
    };
}]);

ngAWDSApp.filter('toPerson', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {
        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";
        if (Number(val) < 1) return "";
        return $filter('extNumber')(val, (Number(val) > 1 ? ' Persons' : ' Person'));
    };
}]);

ngAWDSApp.filter('toPreOwnedForVehType', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {
        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";
        if ($rootScope.webApp.util.equalsLowerCase(val, 'used')) return "Pre-Owned";        
        return val;
    };
}]);
;
ngAWDSApp.filter('sumOfVal', ['$rootScope', function ($rootScope) {
    return function (data, key1, key2) {
        return $rootScope.webApp.util.sumOfVal(data, key1, key2);
    };
}]);;
ngAWDSApp.filter('toCylinder', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumberZeroWithoutComma')(val, ' Cyl', 0);
    };
}]);
ngAWDSApp.filter('toLongCylinder', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumberZeroWithoutComma')(val, ' Cylinder', 0);
    };
}]);


;
ngAWDSApp.filter('toEngineSize', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumberZeroWithoutComma')(val, 'cc', 0);
    };
}]);
ngAWDSApp.filter('toEngineSizeZero', ['$filter', function ($filter) {
    return function (val) {
        var sVal = $filter('toEngineSize')(val);

        if (sVal == "") {
            return "0cc";
        }
        else {
            return sVal;
        }
    };
}]);
;
ngAWDSApp.filter('toEngineSizeLiter', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {
        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";

        //var ltr = val / 1000;
        return $filter('extNumber')(val, ' l', 1);
    };
}]);;
ngAWDSApp.filter('toFuelTypeDescVehicle', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {

        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";
        else
        {
            var sDesc = "";
            switch (val)
            {
                case "P":
                    sDesc = "Petrol";
                    break;
                case "D":
                    sDesc = "Diesel";
                    break;
                case "U":
                    sDesc = "Unleaded";
                    break;
                case "E":
                    sDesc = "Electric";
                    break;
                case "H":
                    sDesc = "Hybrid";
                    break;
                case "G":
                    sDesc = "LPG";
                    break;
                default:
                    sDesc = val;
                    break;
            }
            return sDesc;
        }
    };
}]);;
ngAWDSApp.filter('toHorsePower', ['$filter', function ($filter) {
    return function (val) {
        var sVal = $filter('extNumberZeroWithoutComma')(val, ' hp', 0);
        if (sVal == '0 hp') return null;
        else return sVal;
    };
}]);
ngAWDSApp.filter('toHorsePowerZero', ['$filter', function ($filter) {
    return function (val) {
        var sVal = $filter('toHorsePower')(val);

        if (sVal == "") {
            return "0 hp";
        }
        else {
            return sVal;
        }
    };
}]);
;
ngAWDSApp.filter('toKilogram', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumber')(val, ' kg', 0);
    };
}]);
ngAWDSApp.filter('toKilogramZero', ['$filter', function ($filter) {
    return function (val) {
        var sVal = $filter('toKilogram')(val);
        if (sVal == "") {
            return "0 kg";
        } else {
            return sVal;
        }
    };
}]);;
ngAWDSApp.filter('toKilometer', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumber')(val, ' km', 0);
    };
}]);
ngAWDSApp.filter('toKilometerZero', ['$filter', function ($filter) {
    return function (val) {
        var sVal = $filter('toKilometer')(val);
        if (sVal == "") {
            return "0 km";
        }
        else {
            return sVal;
        }
    };
}]);

ngAWDSApp.filter('toHoursOdo', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumber')(val, ' hrs', 0);
    };
}]);
ngAWDSApp.filter('toHoursOdoZero', ['$filter', function ($filter) {
    return function (val) {
        var sVal = $filter('toHoursOdo')(val);
        if (sVal == "") {
            return "0 hrs";
        }
        else {
            return sVal;
        }
    };
}]);;
ngAWDSApp.filter('toLowerCaseExt', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {
        return $rootScope.webApp.util.toLowerCaseExt(val);
    };
}]);;
ngAWDSApp.filter('toMeter', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extNumber')(val, ' m', 2);
    };
}]);

ngAWDSApp.filter('toMeter2', ['$filter', function ($filter) {
    return function (val, meterUnit) {
        meterUnit = AWDSApp.util.isNullOrEmpty(meterUnit) ? "m" : meterUnit;
        return $filter('extNumber')(val, meterUnit, 2);
    };
}]);

ngAWDSApp.filter('toMeterZero', ['$filter', function ($filter) {
    return function (val, meterUnit) {
        meterUnit = AWDSApp.util.isNullOrEmpty(meterUnit) ? "m" : meterUnit;

        var sVal = $filter('toMeter2')(val, meterUnit);
        if (sVal == "")
        {
            sVal = "0" + meterUnit;
        }
        return sVal;
    };
}]);

ngAWDSApp.filter('toMeterZeroN0', ['$filter', function ($filter) {
    return function (val, meterUnit) {
        meterUnit = AWDSApp.util.isNullOrEmpty(meterUnit) ? "m" : meterUnit;

        var sVal = $filter('extNumber')(val, meterUnit, 0);
        if (sVal == "") {
            sVal = "0" + meterUnit;
        }
        return sVal;
    };
}]);;
ngAWDSApp.filter('toPrice', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extCurrency')(val, '$', 0);
    };
}]);;
ngAWDSApp.filter('toPrice2', ['$filter', function ($filter) {
    return function (val) {
        return $filter('extCurrency')(val, '$', 2);
    };
}]);;
ngAWDSApp.filter('toTransmissionDescVehicle', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {

        if ($rootScope.webApp.util.isNullOrEmpty(val)) return "";
        else {
            var sDesc = "";            
            var valSplit = val.split(' ');
            var code = valSplit[valSplit.length - 1].toLocaleLowerCase();
            switch (code)
            {
                case 'man':
                    code = 'Manual';
                    break;
                case 'c':
                case 'cvt':
                    //code = "CVT";
                    code = "Auto";
                    break;
                //case 's':
                //case 'sport':
                //    code = "Semi-Auto";
                    //    break;
                case 's':
                case 'sport':
                case 'automatic':
                    code = 'Auto';
                    break;
            }
            for (var i = 0; i < valSplit.length - 1; i++) {
                sDesc += valSplit[i] + ' ';
            }
            sDesc = sDesc + code;

            //if (!val.indexOf("Manual") >= 0)
            //{
            //    if (val.indexOf("Man") >= 0) {
            //        sDesc = sDesc.replace("Man", "Manual");
            //    } else if (val.indexOf("SPEED C") >= 0) {                    
            //        sDesc = sDesc.replace("SPEED C", "SPEED AUTO");//sDesc = sDesc.replace("C", "CVT");
            //    }
            //    else if (val.indexOf("SPORT") >= 0){
            //        sDesc = sDesc.replace("SPORT", "AUTO");
            //    }
            //    else if (val.indexOf("AUTOMATIC") >= 0) {
            //        sDesc = sDesc.replace("AUTOMATIC", "AUTO");
            //    }
            //}

            
            return sDesc;
        }
    };
}]);;
ngAWDSApp.filter('toUpperFirstChar', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {
        return $rootScope.webApp.util.toUpperFirstChar(val);
    };
}]);

ngAWDSApp.filter('toDashLower', ['$filter', '$rootScope', function ($filter, $rootScope) {
    return function (val) {
        return $rootScope.webApp.util.replaceAllSpecialCharsToDashLower(val);
    };
}]);
;
ngAWDSApp.filter('trustAsResourceUrl', ['$sce', function ($sce) {
    return function (val) {
        return $sce.trustAsResourceUrl(val);
    };
}])

ngAWDSApp.filter('trustAsHtml', ['$sce', function ($sce) {
    return function (val) {
        var sDecoded = "";
        var eleDiv = angular.element('<div />');
        eleDiv.html(val);
        if (eleDiv.children().length > 0) {
            sDecoded = val;
        } else {
            sDecoded = eleDiv.text();
        }
        //var sDecoded = angular.element('<div />').html(val).text();
        return $sce.trustAsHtml(sDecoded);
    };
}])

ngAWDSApp.directive('compileDirect', ['$compile', function ($compile) {
    return {
        //restrict: 'E',
        replace: true,
        link: function (scope, element, attrs) {
            scope.$watch(
                function (scope) {
                    // watch the 'compile' expression for changes
                    return scope.$eval(attrs.compileDirect);
                },
                function (value) {
                    // when the 'compile' expression changes
                    // assign it into the current DOM
                    element.html(value);

                    // compile the new DOM and link it to the current
                    // scope.
                    // NOTE: we only compile .childNodes so that
                    // we don't get into infinite loop compiling ourselves
                    $compile(element.contents())(scope);
                }
            );
        }
    }
}]);
