`

js tools(2)

阅读更多

 

/**

 * 

 * @param divId

 *            one container

 * @param info

 *            operation result message to show in the up DIV, such as: "Add

 *            Successfully!"

 * @param isSuccess

 *            one sign whether operate successfully.Just two values:true or

 *            false.true means success.

 * @returns undefined

 */

function showOperationResultInfo(divId,info,isSuccess){

$("#"+divId).removeAttr("style");

if(isSuccess){

$("#"+divId).attr("style","color:green");

}else{

$("#"+divId).attr("style","color:red");

}

$("#"+divId).empty();

$("#"+divId).html(info);

}

 

/**

 * @param url

 *            format must be xxx.action?eventName=

 * @param params

 *            format must consist of "key=value",such as deleteId=10

 * @param doDeleteCallback

 *            do works after servers response

 * @param validateFun

 *            do validation before submitting deletion request

 * @param deleteNoteMess

 *            popup warning information. if not passing,to use default value

 * @returns {Boolean} whether this function operates successfully or not

 

 * 

 * How to use: commonDeleteEntireObjectFun(URL.deleteInstance,"id=3",deleteCallback,deleteValidationFun);

 * function deleteCallback(data){

var flag = isSuccessRetun(data);

if(flag){

location.href=URL.loadInstanceListPage;

}else{

toShowResponseResMessWithoutParsingData("operationTipMess","operationTipMess_error",data);

}

}

 * 

 * function deleteValidationFun(params)

{

return commonValidateFun(URL.validateInstanceUsed,params);

}

 * 

 *

 */

function commonDeleteEntireObjectFun(url,params,doDeleteCallback,validateFun,deleteNoteMess){

var flag = false;

if(validateFun&&$.isFunction(validateFun)){

flag = validateFun(params);

if(flag=="true"){

alert("The record is still referenced and cannot be deleted.");

return false;

}

}

if(!validateFun||flag){

var confirmObj="";

if(deleteNoteMess){

confirmObj = confirm(deleteNoteMess);

}else{

confirmObj = confirm(Messages.deleteConfirmMess);

}

if(confirmObj){ 

$.post(url+"&"+params,function(data){

doDeleteCallback(data);

});

return false;

}else {

return false;

}

}

return false;

}

 

// to show servers' response messages in the current page

function toShowResponseResMessWithoutParsingData(divId,className,data){

$("#"+divId).empty();

$("#"+divId).html(data);

$("#"+divId).attr("class",className);

}

 

// whether servers respone successfully or not

function isSuccessRetun(data){

if(!data.startWith("error")){

return true;

} else{

return false;

}

}

 

/**

 * Description: simple function to set data given to hidden area

 * 

 * @param hiddenId

 *            html id of hidden area

 * @param data

 *            value to be set,format:json,such as,{"objectId":"100"}

 * @param jsonKey

 *            json key

 */

function setSingleHiddenAreaValue(hiddenId,data,jsonKey){

if(data!=undefined&&data!=null&&""!=$.trim(data)){

var value = $.parseJSON(data);

$("#"+hiddenId).empty();

$("#"+hiddenId).val(value[jsonKey]);

}

}

 

/**

 * * Description: simple function to send ajax reqeuest

 * 

 * @param url

 *            reqeust address,format:xxxx.action,such as ContactGroupForm.action

 * @param params

 *            request params,format:json.such as,{name:"Keifer",dept:"dev"}

 * @param callback

 *            do work after response successfully

 * @returns json data if callback exists,data handled by it. Or return orignal

 *          data responed

 */

function commonPreAction(url,params,callback){

var returnValue="";

$.ajaxSetup({

cache:false,

async:false

});

$.post(url,params,function(data){

if(callback&&$.isFunction(callback)){

if(callback&&$.isFunction(callback)){

callback(data);

}

}

returnValue=data;

});

return returnValue;

}

 

 

/**

 * Description: simple common function to send ajax reqeuest

 * 

 * @param url

 *            reqeust address,format:xxxx.action?eventName=,such as

 *            ContactGroupForm.action?validateRecordUsed=

 * @param params

 *            request params,format:string.such as,'name="Keifer"&dept="dev"'

 * @param callback

 *            do work after response successfully

 * @returns json data if callback exists,data handled by it. Or return orignal

 *          data responed

 */

function simpleCommonSubmitAction(url,params,callback){

var returnValue="";

$.ajaxSetup({

cache:false,

async:false

});

$.post(url+"&"+params,function(data){

if(callback&&$.isFunction(callback)){

if(callback&&$.isFunction(callback)){

callback(data);

}

}

returnValue=data;

});

return returnValue;

}

 

/**

 * Description: simple common function to send ajax reqeuest for validation

 * 

 * @param url

 *            reqeust address,format:xxxx.action?eventName=,such as

 *            ContactGroupForm.action?validateRecordUsed=

 * @param params

 *            request params,format:string.such as,'name="Keifer"&dept:"dev"'

 * @returns json data return orignal data responed

 */

function commonValidateFun(url,params){

var returnValue="";

$.ajaxSetup({

cache:false,

async:false

});

$.post(url+"&"+params,function(data){

returnValue=data;

});

return returnValue;

}

 

/**

 * Description: simple function to show single hidden element

 * 

 * @param elementId

 *            id of element to show

 * @param specifiedStyle

 *            specified style to show

 * @returns undefined

 */

function showHtmlElement(elementId,specifiedStyle){

$("#"+elementId).removeAttr("style");

$("#"+elementId).attr("style",specifiedStyle);

}

 

/**

 * Description: simple function to hide single element

 * 

 * @param elementId

 *            id of element to show

 * @returns undefined

 */

function hideHtmlElement(elementId){

$("#"+elementId).attr("style","display:none");

}

 

 

/**

 * Description: simple function to set value from select element to hidden area

 * 

 * @param inputId

 *            id of hidden input

 * @param selectId

 *            id of select id

 * @param sperator

 *            sperator between one value and another,such as 11-12

 * @returns undefined

 */

function simpleSetHiddenValueFromSelectElement(inputId,selectId,sperator)

{

var hiddenValueStr = "";

$.each($("#"+selectId+" option"),function(){

var value = $(this).val();

if(value){

hiddenValueStr += value+sperator;

}

});

$("#"+inputId).val(hiddenValueStr);

}

 

/**

 * Description: simple function to move one option of select element to another

 * select element

 * 

 * @param selectOneId

 *            id of select one

 * @param selectTwoId

 *            id of select two

 * @param actionNames

 *            action name,such as move,delete,append

 * @param selectOneButtonIds

 *            one array to store up element ids as the same direction

 * @param selectTwoButtonIds

 *            one array to store up element ids as the same direction

 * @returns {Boolean} if occurring error,to return false

 *

 * How to use:simpleCommonMouseClickFun("selectionLeftId", "selectionRightId", ['move'],['selection_RightMoveleft', 'selection_AllRightMoveleft' ], ['selection_LeftMoveRight', 'selection_AllLeftMoveRight' ],true,"----","skillLevelOnPIC");

 * or simpleCommonMouseClickFun('selectionLeftId', 'selectionRightId', ['move'],['selection_RightMoveleft', 'selection_AllRightMoveleft' ], ['selection_LeftMoveRight', 'selection_AllLeftMoveRight' ]);

 *

 */

function simpleCommonMouseClickFun(selectOneId, selectTwoId, actionNames,

selectOneButtonIds, selectTwoButtonIds,isWithSkillLevel,separator,inputId) {

if(actionNames.length==1){

actionName = actionNames[0];

}else{

moveActionName = actionNames[0];

deleteActionName = actionNames[1];

}

$("#" + selectOneId).dblclick(function() {

if(isWithSkillLevel){

var isNumber_=OptionWithSkillLevel.removeSkilLevel(selectOneId, false, separator);

if(!isNumber_) return false;

}

$.listTolist(selectOneId, selectTwoId, actionName, false);

commonSortSelect(selectTwoId,true,"ASC");

return true;

});

$("#" + selectTwoId).dblclick(function() {

if(isWithSkillLevel){

var isNumber_=OptionWithSkillLevel.addSkilLevel(selectTwoId, false, separator, inputId);

if(!isNumber_) return false;

}

$.listTolist(selectTwoId, selectOneId, actionName, false);

commonSortSelect(selectOneId,true,"ASC");

return true;

});

if (selectOneButtonIds instanceof Array || selectOneButtonIds

&& typeof selectOneButtonIds == 'object'

&& 'length' in selectOneButtonIds) {

$.each(selectOneButtonIds, function(index, item) {

$("#" + item).bind("click",function() {

if (index < 1) {

if(isWithSkillLevel){

var isNumber_=OptionWithSkillLevel.addSkilLevel(selectTwoId, false, separator, inputId);

if(!isNumber_) return false;

}

$.listTolist(selectTwoId, selectOneId,actionName, false);

commonSortSelect(selectOneId,true,"ASC");

return true;

} else {

if(isWithSkillLevel){

var isNumber_= OptionWithSkillLevel.addSkilLevel(selectTwoId, true, separator, inputId);

if(!isNumber_) return false;

}

$.listTolist(selectTwoId, selectOneId,actionName, true);

commonSortSelect(selectOneId,true,"ASC");

return true;

}

});

});

$.each(selectTwoButtonIds, function(index, item) {

$("#" + item).bind("click",function() {

if (index < 1) {

if(isWithSkillLevel){

var isNumber_= OptionWithSkillLevel.removeSkilLevel(selectOneId, false, separator);

if(!isNumber_) return false;

}

$.listTolist(selectOneId, selectTwoId,actionName, false);

commonSortSelect(selectTwoId,true,"ASC");

return true;

} else {

if(isWithSkillLevel){

var isNumber_=OptionWithSkillLevel.removeSkilLevel(selectOneId, true,separator);

if(!isNumber_) return false;

}

$.listTolist(selectOneId, selectTwoId,actionName, true);

commonSortSelect(selectTwoId,true,"ASC");

return true;

}

});

});

} else {

alert('Parameter error!');

return false;

}

}

 

 

var OptionWithSkillLevel={

validateSkillLevelNumber:function(inputId){

var skillLevel = $("#"+inputId).val();

if(skillLevel){

var inputValue =  checkIsPositiveInteger(skillLevel);

if(!inputValue){

alert(Messages.numberrequired);

return false;

}

}

return true;

},

validateSkillLevelNull:function(inputId){

var skillLevel = $("#"+inputId).val();

if($.trim(skillLevel)==""){

return false;

}

return true;

},

addSkilLevel:function(selectId,isAll,separator,inputId){

var options ;

if(isAll){

options = $("#"+selectId+" option");

}else{

options = $("#"+selectId+" option:selected");

}

var skillLevelNull = OptionWithSkillLevel.validateSkillLevelNull(inputId);

if(!skillLevelNull){

alert("Skill Level"+Messages.unnull);

return false;

}else if(OptionWithSkillLevel.validateSkillLevelNumber(inputId)){

var skillLevel = $("#"+inputId).val();

$.each(options,function(){

var textField = $(this).text();

$(this).text(textField+separator+skillLevel);

});

return true;

}else{

return false;

}

},

removeSkilLevel:function(selectId,isAll,separator){

var options ;

if(isAll){

options = $("#"+selectId+" option");

}else{

options = $("#"+selectId+" option:selected");

}

var flag = true;

$.each(options,function(){

var textField = $(this).text();

var skillLevel = textField.split(separator)[1];

var subsystem =  textField.split(separator)[0];

if(skillLevel){

var inputValue =  checkIsPositiveInteger(skillLevel);

if(!inputValue){

alert(Messages.numberrequired);

flag=false;

return;

}else{

if(subsystem){

$(this).text(subsystem);

}

}

}

});

return flag;

}

};

 

function simpleCommonCreateSelect(data,textfieldKey,valuefiledKey,selectId){

$("#"+selectId).empty();

// var result = eval(data);

var result = data;

$.each(result,function(index,item){

$("#"+selectId).AddOption(item[textfieldKey],item[valuefiledKey],false,index); 

});

}

 

/**

 * DESC:Append option element to the selection 

 * @param text   text on show in the selection 

 * @param value   value on show in the attribute "value" of option

 * @param selected  whether selected current option,two values:true,false

 * @param index  add option to special index

 * Such as <option value='value'>text</option>

 */

jQuery.fn.AddOption = function(text, value, selected, index) {

option = new Option(text, value);

this[0].options.add(option, index);

this[0].options[index].selected = selected;

};

 

/**

 * @param url

 * @param selectOneId

 * @param selectTwoId

 *            {selectId:"selGroupId",inputId:"groupId"}

 * @param params

 *            {datatype:"json",textfield:"group",valuefiled:"id",parameter:"contactParams.detailId"}

 * @param callback

 *            one function without params that is usesd to do final something

 */

  function simpleCommonCascade(url,selectOneId,selectTwoId,params,callback){

 $("#"+selectOneId).bind("change",function(){

 try{

 var text  =$("#"+selectOneId+" option:selected")[0].value;

 if(text==""||"Create"==text){

 $("#"+selectTwoId.selectId).empty();

 $("#"+selectTwoId.inputId).val("");

 return false;

 }

 }catch(e){}

var data = simpleCommonSubmitAction(url,params.parameter+"="+$("#"+selectOneId+" option:selected").val());

$("#"+selectTwoId.selectId).empty();

var result = eval(data);

if(result.length>1){

var groupId = $("#"+selectOneId+" option:selected")[0].alt;

$.each(result,function(index,item){

$("#"+selectTwoId.selectId).AddOption(item[params.textfield],item[params.valuefiled],false,index); 

if(item[params.valuefiled]==groupId){

$("#"+selectTwoId.selectId)[0].selectedIndex=index;

}

});

}else if(result.length==1){

$("#"+selectTwoId.selectId).AddOption(result[0][params.textfield],result[0][params.valuefiled],true,0);

}

$("#"+selectTwoId.inputId).val($("#"+selectTwoId.selectId+" option:selected").text());

callback();

 });

}

 

  /**

* @param selectId

*            select element id that needs to sort.

* @param sOrder

*            if to sort. true means by sort

* @param sortType

*            format:String. Just two values:"ASC","DESC"

 *

 * How to use : commonSortSelect("selectionId",true,"ASC");

*/

   function commonSortSelect(selectId,sOrder,sortType){

 function addOption(object, object2) { 

           each(object2, function(o, index) { 

               object.options[index] = o; 

           }); 

       } 

       function sortlist(sortName,isDesc,sortType) { 

           var what = document.getElementById(sortName); 

           this._options = map(what.options, function(o) { 

               return o; 

           }); 

           this._options.sort(

            function(a, b) { 

            if(sortType=="DESC"){

                   if (a.text > b.text) { 

                       return isDesc == true ? 1 : -1; 

                   } else { 

                       return isDesc == true ? -1 : 1; 

                   } 

            }else if(sortType=="ASC"){

                   if (a.text > b.text) { 

                       return isDesc == true ? -1 : 1; 

                   } else { 

                       return isDesc == true ? 1 : -1; 

                   } 

               }

           }

           ); 

           what.options.length = 0;// clear current options

           addOption(what, this._options); 

       } 

       function map(object, callback, thisp) { 

           var ret = []; 

           each.call(thisp, object, function() { 

               ret.push(callback.apply(thisp, arguments)); 

           }); 

           return ret; 

       } 

       function each(object, callback) { 

           if (undefined === object.length) { 

               for ( var name in object) { 

                   if (false === callback(object[name], name, object)) 

                       break; 

               } 

           } else { 

               for ( var i = 0, len = object.length; i < len; i++) { 

                   if (i in object) { 

                       if (false === callback(object[i], i, object)) 

                           break; 

                   } 

               } 

           } 

       } 

       function sort(){    

           if(sOrder){ 

               sOrder = false; 

           }else{ 

               sOrder = true; 

           } 

           sortlist(selectId,sOrder,sortType); 

       } 

       sort();

  }

 

   function simpleManuallyAssembleSelect(data,selectId,valueKey,textKey,isAlt,altKey){

   var optionHtml ="";

   if(isAlt){

    if(!altKey){

    alert("Parameter error!");

    return false;

    }

    if(data&&data.length>1){

    optionHtml = "<option value='' alt=''></option>";

    }

    if(data){

    $.each(data,function(index,item){

    optionHtml +="<option value="+item[valueKey]+" alt="+item[altKey]+">"+item[textKey]+"</option>";

    });

    }

   }else{

    if(data&&data.length>1){

    optionHtml = "<option value=''></option>";

    }

    if(data){

    $.each(data,function(index,item){

    optionHtml +="<option value="+item[valueKey]+">"+item[textKey]+"</option>";

    });

    }

   }

$("#"+selectId).empty();

$(optionHtml).appendTo($("#"+selectId));

   }

 

   function simpleManuallyAssembleSelect3(data,selectId,valueKey,textKey){

   var optionHtml ="";

   if(data){

   $.each(data,function(index,item){

    optionHtml +="<option value="+item[valueKey]+">"+item[textKey]+"</option>";

   });

   }

$("#"+selectId).empty();

$(optionHtml).appendTo($("#"+selectId));

  }

 

   function simpleManuallyAssembleSelect2(data,selectId,valueKey,textKey,objectKey,isAlt,altKey){

   var optionHtml ="";

   if(isAlt){

    if(!altKey){

    alert("Parameter error!");

    return false;

    }

    if(data&&data.length>1){

    optionHtml = "<option value='' alt=''></option>";

    }

    if(data){

    $.each(data,function(index,item){

    optionHtml +="<option value="+item[objectKey][valueKey]+" alt="+item[altKey]+">"+item[objectKey][textKey]+"</option>";

    });

    }

   }else{

    if(data&&data.length>1){

    optionHtml = "<option value=''></option>";

    }

    if(data){

    $.each(data,function(index,item){

    optionHtml +="<option value="+item[objectKey][valueKey]+">"+item[objectKey][textKey]+"</option>";

    });

    }

   }

$("#"+selectId).empty();

$(optionHtml).appendTo($("#"+selectId));

  }

 

   /**

    *  DESC:add tip for selection

    *  how to use : selectionShowTipFun.addSelectionTip("demoId");

    */

   var selectionShowTipFun = {

  showSelectionTip:function(e,selectId){

  var Tip = document.getElementById("Tip");

if (Tip == null || Tip == 'undefined')

Tip = document.createElement("div");

 

var target = e.srcElement ? e.srcElement : e.target;

var scrollT = document.documentElement ? document.documentElement.scrollTop : document.body.scrollTop;

Tip.innerHTML = $("#"+selectId+" option:selected").text();

Tip.setAttribute("id", "Tip");

Tip.style.display = "block";

Tip.style.backgroundColor = "#FFFFCC";

Tip.style.width = 250;

Tip.style.height = 15;

Tip.style.left = (parseInt(e.clientX)  + 10 ) +  "px";

Tip.style.top = (parseInt(e.clientY) + parseInt(scrollT) + 3) + "px";

Tip.style.position = "absolute";

document.body.appendChild(Tip);

  },

  closeSelectionTip:function(){

  var Tip = document.getElementById("Tip");

  Tip.style.display = "none";

  },

  addSelectionTip:function(selectId){

  var selectItem = $("#"+selectId);

  var showText = $.trim($("#"+selectId+" option:selected").val());

  // add event handlers in order that selection light up

if(showText){

selectItem.hover(function() {

selectionShowTipFun.showSelectionTip(event,selectId);

},function() {

selectionShowTipFun.closeSelectionTip();

});

}

  }

   };

 

分享到:
评论

相关推荐

    JavaScript Tools Guide CC.pdf

    2. **插件开发**:开发者可以通过JavaScript编写插件,扩展Photoshop的功能,满足个性化需求或商业项目的需求。 3. **脚本事件**:Photoshop支持通过JavaScript设置脚本事件,当特定事件触发时(如打开文件、保存...

    JavaScript Tools Guide CC

    1. 文档标题与描述:文档标题为“JavaScript Tools Guide CC”,描述中指出这是“Adobe®CreativeCloud®JavaScript Tools Guide”的开发说明。文档是为Windows和Macintosh操作系统编写的,由Adobe Systems ...

    JavaScript Tools Guide CS6.pdf

    ### JavaScript Tools Guide for Adobe Creative Suite 6 #### 概述 《JavaScript Tools Guide CS6.pdf》是一份由Adobe Systems Incorporated发布的官方文档,旨在为使用Adobe Creative Suite 6(简称CS6)的用户...

    json解析js Tools

    json解析js Tools json解析js Tools json解析js Tools json解析js Tools

    html to js tools

    2. **HTML转JS的动机**: - **代码复用**:将HTML模板转换为JavaScript字符串,可以方便地在多个页面之间共享这些模板,减少网络请求。 - **性能优化**:预加载HTML字符串到JavaScript中,可以延迟到需要时再进行...

    jquery.tools.min.js 最新的1.2.7版本

    jquery.tools.min.js 最新的1.2.7版本 jquery.min.js是压缩版的jquery库,是由完整版的jQuery库经过压缩得来,压缩后功能与未压缩的完全一样,只是将其中的空白字符、注释、空行等与逻辑无关的内容删除,并进行一些...

    jquery.tools.js下载

    jquery.tools.js jquery.tools.js

    geotools汉语版资料

    2. **MyGeoTools文档** "MyGeoTools.doc"可能是用户自定义的关于GeoTools使用经验或特定应用场景的文档。这可能包含了一些示例代码、常见问题解答或者特定功能的详细解释,对于初学者来说是一份非常有价值的参考...

    EditorTools 2 个人版

    **EditorTools 2 个人版**是一款专为提高网页排版效率而设计的编辑工具,其强大功能和易用性使其在同类软件中脱颖而出。这款工具不仅能够帮助用户轻松完成网页内容的编辑,还能实现高效的排版工作,是网页设计师和...

    用于 or-tools 车辆路线问题的 Node.js 绑定_C++_代码_下载

    在使用这个 Node.js 绑定时,开发者需要注意的是,由于是跨语言交互,性能可能会受到一定影响,但相比于纯 JavaScript 的解决方案,C++ 的高效执行仍然能带来显著的优化效果。此外,理解 `or-tools` 的基本概念和 ...

    React-Developer Tools-3.6.0.zip 适合react v17以下

    2. **组件状态和属性检查**:开发者可以使用 React Developer Tools 查看每个组件的状态和属性,从而更好地理解组件的行为和状态变化。这对于调试和排查问题非常有帮助。 3. **组件性能分析**:React Developer ...

    geotools依赖包

    它在JavaScript和Web服务中广泛使用,因为其易于解析和生成。GeoTools库中的GeoJSON模块允许开发者将Java对象转换为GeoJSON字符串,或者将GeoJSON字符串解析为Java对象,以便在Java应用中处理。 在使用GeoTools时,...

    基于or-tools的人员排班问题建模求解(JavaAPI)

    `OR-Tools`是谷歌推出的一个开源的、强大的优化工具箱,它支持多种编程语言,包括Java。本篇文章将深入探讨如何利用OR-Tools的Java API解决一个具体的实例——“人员排班问题”。 人员排班问题是一个典型的线性规划...

    解决maven项目找不到tools-1.8.0.jar的问题,缺失tools-1.8.0.jar包

    2. **使用Maven的`&lt;dependencies&gt;`管理**:如果`tools-1.8.0.jar`是第三方库,你应该在Maven的中央仓库或其他合法的仓库中找到它的坐标,并在`pom.xml`中声明。 3. **设置本地仓库**:如果你有`tools-1.8.0.jar`的...

    JavaScript Tools Guide CS3

    ### JavaScript Tools Guide CS3 关键知识点解析 #### 一、引言 《JavaScript Tools Guide CS3》是一份针对Adobe Creative Suite 3 (CS3) 的专业文档,详细介绍了如何使用JavaScript进行扩展编程。Adobe CS3是一款...

    jQuery.Tools.min.js 下载

    jQuery Tools 是一套非常优秀的 Web UI 库,包括 Tab 容器,可折叠容器,工具提示,浮动层以及可滚动容器等等,可以为你的站点带来非同寻常的桌面般体验,这套工具的主要作用是显示内容,这是绝多多数站点最需要的...

    vs_build_tools.zip

    Visual Studio Build Tools,简称VS Build Tools,是微软公司为开发者提供的一款重要工具集,主要用于构建C++、C#、JavaScript等语言的项目,而无需完整安装Visual Studio IDE。在给定的压缩包"vs_build_tools.zip...

    js-tools:js常用工具

    在JavaScript工具集"js-tools"中,我们通常会看到以下几类知识点: 1. **代码压缩与优化**:为了减少网页加载时间,JS代码通常需要进行压缩,如使用UglifyJS或Terser将源代码转换为最小化的生产版本。同时,Webpack...

    webpack-babel-react-development-tools, JavaScript 2015 开发/生产环境.zip

    5. **配置文件**:在“webpack-babel-react-development-tools-master”中,很可能包含了Webpack的配置文件(webpack.config.js),Babel的配置文件(.babelrc或babel.config.js),以及React应用的基础代码结构。...

Global site tag (gtag.js) - Google Analytics