728x90

개요

  String 변수가 null이나 undefined인지 확인하는 기능은 쓰임새가 많다. Java라면 그런 목적으로 Apache commons의 StringUtils를 많이 사용하고, String 관련 메서드를 사용할 때 null 체크를 생략시켜줘서 편리하다. JavaScript에서도 클래스 및 메서드 명칭이 동일하고 동작구조도 최대한 동일하면 협업 시 팀원들이 빠르게 기능을 이해하고 사용할 수 있을 거란 생각이 들었다.

샘플코드

  Apache commons의 StringUtils의 메서드를 모두 구현하려면 시간이 많이 들어서 당장 자주 쓸 기능만 작성했다. 나머지는 필요해질 때마다 추가 작성하면 된다.

  [2023-03-28] isEmpty, isNotEmpty, isNotBlank 추가.

<StringUtils.js>

/**
 * Apache commons의 StringUtils 클래스의 편리한 기능을 자바스크립트에서 그대로 사용하기 위한 객체. 
 */
const StringUtils = {};
/**
 * 
 * @param str {String} 확인할 문자열
 * @return 문자열 길이. 문자열이 없으면 0.
 */
StringUtils.length = function(str) {
	if (str) {
		return str.length;
	}
	
	return 0;
};

/**
 * <p>Checks if a String is empty ("") or null or undefined.</p>
 *
 * <pre>
 * StringUtils.isEmpty(undefined) = true
 * StringUtils.isEmpty(null)      = true
 * StringUtils.isEmpty("")        = true
 * StringUtils.isEmpty(" ")       = false
 * StringUtils.isEmpty("bob")     = false
 * StringUtils.isEmpty("  bob  ") = false
 * </pre>
 * 
 * @param cs {String} 확인할 문자열
 * @return {boolean} <code>true</code> if the cs is empty or null or undefined.
 */
StringUtils.isEmpty = function(cs) {
	if (cs == undefined || cs == null) {
		return true;
	}
	
	if (!("string" === typeof(cs) || cs instanceof String)) {
		throw new TypeError("Argument cs must be type of String.");
	}
	
	return cs.length == 0;
};

/**
 * <p>Checks if a String is not empty ("") and not null and not undefined.</p>
 *
 * <pre>
 * StringUtils.isNotEmpty(undefined) = false
 * StringUtils.isNotEmpty(null)      = false
 * StringUtils.isNotEmpty("")        = false
 * StringUtils.isNotEmpty(" ")       = true
 * StringUtils.isNotEmpty("bob")     = true
 * StringUtils.isNotEmpty("  bob  ") = true
 * </pre>
 * 
 * @param cs {String} 확인할 문자열
 * @return {boolean} <code>true</code> if the cs is not empty and not null and not undefined.
 */
StringUtils.isNotEmpty = function(cs) {
	return !StringUtils.isEmpty(cs);
};

/**
 * <p>Checks if a CharSequence is empty (""), null or whitespace only.</p>
 *
 * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
 *
 * <pre>
 * StringUtils.isBlank(undefined) = true
 * StringUtils.isBlank(null)      = true
 * StringUtils.isBlank("")        = true
 * StringUtils.isBlank(" ")       = true
 * StringUtils.isBlank("bob")     = false
 * StringUtils.isBlank("  bob  ") = false
 * </pre>
 * 
 * @param cs {String} 확인할 문자열
 * @return {boolean} <code>true</code> if the cs is undefined, null, empty or whitespace only.
 */
StringUtils.isBlank = function(cs) {
	if (cs == undefined || cs == null) {
		return true;
	}

	if (!("string" === typeof(cs) || cs instanceof String)) {
		throw new TypeError("Argument cs must be type of String.");
	}

    let strLen;
    if ((strLen = cs.length) == 0) {
        return true;
    }
//    for (var i = 0; i < strLen; i++) {
//        if (!StringUtils.isWhitespace(cs.charAt(i))) {
//            return false;
//        }
//    }
        if (!StringUtils.isWhitespace(cs)) {
            return false;
        }
    return true;
};

/**
 * <pre>
 * StringUtils.isNotBlank(undefined) = false
 * StringUtils.isNotBlank(null)      = false
 * StringUtils.isNotBlank("")        = false
 * StringUtils.isNotBlank(" ")       = false
 * StringUtils.isNotBlank("bob")     = true
 * StringUtils.isNotBlank("  bob  ") = true
 * </pre>
 *
 * @param cs  the CharSequence to check, may be null
 * @return <code>true</code> if the CharSequence is
 *  not empty and not null and not whitespace only
 */
StringUtils.isNotBlank = function(cs) {
    return !StringUtils.isBlank(cs);
}

/**
 * Apache commons의 StringUtils 클래스에는 없지만, 유용할 듯하여 별도 메서드를 추가함.
 * @param cs {String} 확인할 문자열
 * @return {boolean} <code>true</code> if the str is undefined, null, empty or whitespace only.
 */
StringUtils.isWhitespace = function(cs) {
	if (!("string" === typeof(cs) || cs instanceof String)) {
		throw new TypeError("Argument cs must be type of String.");
	}
	
    if (/^\s+$/g.test(cs)) {
        return true;
    }
    return false;
};

/**
*
* <pre>
* StringUtils.trim(undefined)     = undefined
* StringUtils.trim(null)          = null
* StringUtils.trim("")            = ""
* StringUtils.trim("     ")       = ""
* StringUtils.trim("abc")         = "abc"
* StringUtils.trim("    abc    ") = "abc"
* </pre>
*
* @param str {String} the String to be trimmed, may be null
* @return {String} the trimmed string, <code>null</code> if null String input
*/
StringUtils.trim = function(str) {
	return (str == null || str == undefined) ? str : str.trim();
};
/**
 *
 * <pre>
 * StringUtils.trimToNull(undefined)     = null
 * StringUtils.trimToNull(null)          = null
 * StringUtils.trimToNull("")            = null
 * StringUtils.trimToNull("     ")       = null
 * StringUtils.trimToNull("abc")         = "abc"
 * StringUtils.trimToNull("    abc    ") = "abc"
 * </pre>
 *
 * @param str {String} the String to be trimmed, may be null
 * @return {String} the trimmed String,
 *  <code>null</code> if only chars &lt;= 32, empty or null String input
 */
StringUtils.trimToNull = function(str) {
	const ts = StringUtils.trim(str);
	return StringUtils.isEmpty(ts) ? null : ts;
};
/**
 *
 * <pre>
 * StringUtils.trimToEmpty(undefined)     = ""
 * StringUtils.trimToEmpty(null)          = ""
 * StringUtils.trimToEmpty("")            = ""
 * StringUtils.trimToEmpty("     ")       = ""
 * StringUtils.trimToEmpty("abc")         = "abc"
 * StringUtils.trimToEmpty("    abc    ") = "abc"
 * </pre>
 *
 * @param str {String} the String to be trimmed, may be null
 * @return {String} the trimmed String, or an empty String if <code>null</code> input
 */
StringUtils.trimToEmpty = function(str) {
	return (str == null || str == undefined) ? "" : str.trim();
};

 

단위테스트코드

<StringUtilsTest.html>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>StringUtilsTest</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.19.3.css">
</head>
<body>
	<div id="qunit"></div>
	<div id="qunit-fixture"></div>
	<script src="https://code.jquery.com/qunit/qunit-2.19.3.js"></script>
	<script src="./StringUtils.js"></script>
	<script>
		QUnit.module('isEmpty', function() {
			let undfnd;
			const nll = null;
			const empty = "";
			const blank = new String(" ");
			const longBlank = new String("      ");
			const notBlank = "asdf";
			QUnit.test('isEmpty', function(assert) {
				assert.true(StringUtils.isEmpty(undfnd), 'undefined');
				assert.true(StringUtils.isEmpty(nll), 'null');
				assert.true(StringUtils.isEmpty(empty), 'empty');
				assert.false(StringUtils.isEmpty(blank), 'blank');
				assert.false(StringUtils.isEmpty(longBlank), 'long blank');
				assert.false(StringUtils.isEmpty(notBlank));
			});
		});

		QUnit.module('isNotEmpty', function() {
			let undfnd;
			const nll = null;
			const empty = "";
			const blank = new String(" ");
			const longBlank = new String("      ");
			const notBlank = "asdf";
			QUnit.test('isNotEmpty', function(assert) {
				assert.false(StringUtils.isNotEmpty(undfnd), 'undefined');
				assert.false(StringUtils.isNotEmpty(nll), 'null');
				assert.false(StringUtils.isNotEmpty(empty), 'empty');
				assert.true(StringUtils.isNotEmpty(blank), 'blank');
				assert.true(StringUtils.isNotEmpty(longBlank), 'long blank');
				assert.true(StringUtils.isNotEmpty(notBlank));
			});
		});

		QUnit.module('isBlank', function() {
			let undfnd;
			const nll = null;
			const empty = "";
			const blank = new String(" ");
			const longBlank = new String("      ");
			const notBlank = "asdf";
			QUnit.test('isBlank', function(assert) {
				assert.true(StringUtils.isBlank(undfnd), 'undefined');
				assert.true(StringUtils.isBlank(nll), 'null');
				assert.true(StringUtils.isBlank(empty), 'empty');
				assert.true(StringUtils.isBlank(blank), 'blank');
				assert.true(StringUtils.isBlank(longBlank), 'long blank');
				assert.false(StringUtils.isBlank(notBlank));
			});
		});

		QUnit.module('isNotBlank', function() {
			let undfnd;
			const nll = null;
			const empty = "";
			const blank = new String(" ");
			const longBlank = new String("      ");
			const notBlank = "asdf";
			QUnit.test('isBlank', function(assert) {
				assert.false(StringUtils.isNotBlank(undfnd), 'undefined');
				assert.false(StringUtils.isNotBlank(nll), 'null');
				assert.false(StringUtils.isNotBlank(empty), 'empty');
				assert.false(StringUtils.isNotBlank(blank), 'blank');
				assert.false(StringUtils.isNotBlank(longBlank), 'long blank');
				assert.true(StringUtils.isNotBlank(notBlank));
			});
		});

		QUnit.module('trim', function() {
			QUnit.test('trim', function(assert) {
				assert.equal(StringUtils.trim(undefined), undefined);
				assert.equal(StringUtils.trim(null), null);
				assert.equal(StringUtils.trim(""), "");
				assert.equal(StringUtils.trim(" "), "");
				assert.equal(StringUtils.trim("    "), "");
				assert.equal(StringUtils.trim("asdf"), "asdf");
				assert.equal(StringUtils.trim("  asdf  "), "asdf");
			});
		});

		QUnit.module('trimToEmpty', function() {
			const blank = new String(" ");
			const longBlank = new String("      ");
			const notBlank = "asdf";
			QUnit.test('trimToEmpty', function(assert) {
				assert.equal(StringUtils.trimToEmpty(undefined), "");
				assert.equal(StringUtils.trimToEmpty(null), "");
				assert.equal(StringUtils.trimToEmpty(""), "");
				assert.equal(StringUtils.trimToEmpty("     "), "");
				assert.equal(StringUtils.trimToEmpty("abc"), "abc");
				assert.equal(StringUtils.trimToEmpty("    abc    "), "abc");
			});
		});
	</script>
</body>
</html>

단위테스트결과

 

반응형
728x90

  datagrid 내의 컬럼에 툴팁을 사용하고 싶을 경우 formatter를 이용해 정의하는 것을 생각해볼 수 있다. 그런데, formatter 내에서 tooltip 생성 코드를 바로 사용해도 생성이 되지 않는다. 아마도 datagrid 내의 formatter 내에서 구성한 엘리먼트가 formatter 코드가 모두 실행된 후에야 생성되기 때문일 것이다.( formatter 코드가 실행 완료되기 전에는 아직 존재하지 않는 엘리먼트여서 툴팁을 붙일 엘리먼트가 존재하지 않기 때문일 것으로 생각된다.)

  때문에 formatter 내에서는 툴팁을 띄울 이벤트에 대신 툴팁을 생성하는 함수를 연결해 놓는다. 이벤트가 발생할 때는 이미 요소가 있으므로 툴팁 생성이 가능하다.

  1. 툴팁에 노출할 데이터는 글로벌 변수에 저장한다.(datagrid를 그리면서 툴팁 생성까지 되지 않기 때문에 분리해서 실행이 되도록 데이터를 별도영역에 저장해 놓는다.)
  2. formatter를 이용해 툴팁이 필요한 컬럼의 엘리먼트에 고유한 아이디를 생성하고, 툴팁 생성 이벤트를 설정한다.
  3. 툴팁이 필요한 곳에 마우스이벤트가 발생하면, 툴팁을 생성한다.(현재로서는 엘리먼트에 EasyUI의 tooltip이 생성되었는지 따로 판단할 방법이 없다. 때문에 오류가 발생하면 생성되지 않은 것으로 판단하고 생성하도록 한다.)

<html>

	<table id="dg" title="Example" style="width:600px"  data-options="
            rownumbers:true,
            singleSelect:true,
            autoRowHeight:false,
            pagination:true,
            pageSize:10">
    <thead>
        <tr>
            <th data-options="field:'tupleId',width:100,align:'right'">Tuple ID</th>
            <th data-options="field:'name',width:100">Name</th>
            <th data-options="field:'files',width:200,formatter:uiService.fileFormatter">Files</th>
        </tr>
    </thead>

<JavaScript>

/**
 * 데이터.
 */
var gData = {};
gData.files = [];

/**
 * UI서비스.
 */
var uiService = {};
/**
 * 파일 정보 엘리먼트에 고유한 아이디를 생성하고, 툴팁 생성 이벤트를 설정한다.
 */
uiService.fileFormatter = function(value, row) {
	let result = null;

	gData.files[row.tupleId] = row.files;
	if (row.files) {
		const len = row.files.length;
		if(len > 0){
			const $tempElem = $('<div id="temp'+ row.tupleId + '"></div>');
			const fileListDiv = '<a href="javascript:void(0)" id="tipFile' + row.tupleId 
					+ '"onmouseover="uiService.buildFilesTooltip(\''+ row.tupleId + '\')"><font color="blue"><u>첨부파일 : ' + len + ' 건</u></font></a>';
			$tempElem.html(fileListDiv);

			result = $tempElem.html();
		}
	} else {
		result = '';
	}
	return result;
};

/**
 * 툴팁을 생성한다.
 */
uiService.buildFilesTooltip = function(tupleId) {
	try {
		const opts = $('#tipFile'+tupleId).tooltip('options');
		// EasyUI에 tooltip이 생성되어 있는지 여부를 판단할 방법이 지원되지 않기에, try~catch를 이용한다.
	} catch (e) {
		// tooltip이 없으면 오류가 발생하므로 생성한다.
		const files = gData.files[tupleId];
		var fileIds = [];
		var html = "";
		$.each(files, function(index, value) {
			html += '<div style="margin:5 5 10 5px;">';
			html += '<a href="javascript:void(0)" style="color:#c60;" onClick="fileService.downloadFile(\''
					+ value.fileId
					+ '\')"><u><b>'
					+ value['fileName'] + '</b></u></a>';
			html += '</div>';
			
			fileIds.push(value.fileId);

			if (index > 0 && (files.length - 1) == index) {
				html += '<div style="margin:5px;">'
					  + '<a href="javascript:void(0)" style="color:#c60;" '
					  + 'onClick="fileService.downloadAllFile([' + fileIds + '])"><u><b>일괄 다운로드</b></u></a></div>';
			}
		});
		const $tipFile = $('#tipFile'+ tupleId);
		$tipFile.tooltip({
			position : 'right',
			content : '<span style="color:#fff;">' + html + '</span>',
			onShow : function() {
				var t = $(this);
				t.tooltip('tip').unbind().bind('mouseenter', function() {
					t.tooltip('show');
				}).bind('mouseleave', function() {
					t.tooltip('hide');
				}).css({
					backgroundColor : '#d9d9d9',
					borderColor : '#d9d9d9',
					opacity : 1
				});
			}
		});
		$tipFile.tooltip('show');
	}
}

/**
 * 파일서비스.
 */
var fileService = {};
fileService.downloadFile = function(fileId) {
	// 예시이므로 실제로 파일을 다운로드하지는 않음.
	alert("fileId:" + fileId);
};
fileService.downloadAllFile = function(fileIds) {
	// 예시이므로 실제로 파일을 다운로드하지는 않음.
	alert("fileIds:" + fileIds);
};

/**
 * 초기화.
 */
$(document).ready(function() {
	const list = [
		{"tupleId":"2", "name": "홍길동", "files":[{"fileId":1, "fileName": "증명사진1"}]},
		{"tupleId":"4", "name": "홍길일", "files":[{"fileId":2, "fileName": "증명사진2"},{"fileId":3, "fileName": "여권사진"}]},
		{"tupleId":"8", "name": "홍길삼"},
		{"tupleId":"16", "name": "홍길사"}
	];
	$('#dg').datagrid().datagrid('loadData', list);
});
반응형
728x90

*getSelected : 현재 선택된 탭 가져오기.

var tab = $('#tt').tabs('getSelected');


*getTabIndex : 탭 인덱스 가져오기.

var tab = $('#tt').tabs('getSelected');
var index = $('#tt').tabs('getTabIndex',tab);
alert("index:" + index);

*id 확인하기.

const tab = $('#infoTabs').tabs('getSelected');
const tabOptions = tab.panel('options');
const infoTabId = tabOptions.id;
반응형
728x90

개요

Ajax 통신중에 프로그레스바 마스크를 호출하여 사용자의 조작을 막고 처리중임을 알릴 필요가 있다.

빠른 길잡이

jQuery UI 이용

  html로 프로그레스바를 준비한다.

  스타일

<style>
#progressbar {
	margin-top: 20px;
}

.progress-label {
	font-weight: bold;
	text-shadow: 1px 1px 0 #fff;
}

/* Dialog의 우상단 닫기버튼[x]를 숨기는 설정. */
.ui-dialog-titlebar-close {
	display: none;
}
</style>

  html

<!-- Progressbar Dialog -->
<div id="dialog" title="Progressbar Example" style="display: none;">
  <div class="progress-label">Starting...</div>
  <div id="progressbar"></div>
</div>

beforeSend에서 jQuery UI 프로그레스바를 노출시킨한다.

(*[2022-11-14] modal 속성이 누락되었어서 추가함.)

beforeSend : function() {
	var progressbar = $("#progressbar");
	progressbar.progressbar({
		value: false,
	});
	//progressbar.progressbar("value", 0);
	$('#dialog').dialog({
		modal: true
	});
}

complete에서 jQuery 프로그레스바를 비노출시킨다.

complete/*always*/ : function() {
	$('#dialog').dialog('close');
}

EasyUI 이용

beforeSend에서 EasyUI 프로그레스바를 노출시킨한다.

beforeSend : function(jqXHR, settings) {
	$.messager.progress({
		title:'요청을 처리하는 중입니다.',
		msg:'처리가 끝날 때까지 잠시만 기다려주세요...'
	});
}

complete에서 EasyUI 프로그레스바를 비노출시킨다.

complete/*always*/ : function() {
	$.messager.progress('close');
}

해설

jQuery Ajax에서 beforeSend는 요청 전 항상 실행된다.

또한 complete는 응답을 받고 success와 error 어느 쪽이더라도 그 후에 실행된다.

반응형
728x90

  EasyUI의 [Documentation]을 보면 Properties 항목을 볼 수 있다.
  하지만 Properties 조회 명령어는 properties가 아닌 options이기 때문에 주의가 필요하다.

*예시)

const tab = $('#infoTabs').tabs('getSelected');
const tabOptions = tab.panel('options');
const infoTabId = tabOptions.id;
반응형
728x90

이번 주 업무 중에 자바스크립트에서 정규식(정규표현식)을 조금 복잡하게 짜야 하는 것이 있어, 그 동안 써먹은 표현식으로는 한계에 부딪혔습니다.

그래서 기존에 안 쓰던 표현식을 찾아서 여기저기 돌아다니고 있었는데…

정규표현식을 시각화해서 보여주는 사이트가 있더군요.

"regular expressions 101"

간편하게 테스트를 할 수 있는 것은 물론이고, 어떻게 동작하여 결과가 나오는 것인지까지 설명을 해줍니다. 사용한 표현식에 대한 해설도 첨부되어 있구요.(영어라는 게 문제지만…)

화면 왼쪽의 ‘FLAVOR’ 영역에서 ‘ECMAScript (JavaScript)’를 선택하고 사용하는 것만 잊지 않으면 사용법이 어려운 사이트는 아닙니다. 그리고 그 선택락을 보면 알 수 있지만, 자바스크립트 외에도 다른 언어의 정규표현식도 테스트해 볼 수 있습니다.

사용법이 어려운 사이트는 아니지만, 감이 잘 안 오시면 다음 사이트의 사용례를 보시면 쉽게 이해가 가실 겁니다.(스크롤을 내리다보면 중간중간 나오는 스샷으로 감을 잡을 수 있습니다.)

반응형

+ Recent posts