728x90

개요

  EasyUI의 textbox에는 원래의 <input>에 적용할 수 있는 이벤트 중에 onChange만 설정할 수 있습니다. 그 외에는 지원을 하지 않기 때문에 직접 textbox 객체를 받아서 설정하는 방법을 사용합니다.

예제코드


$('#box').textbox('textbox').on('keyup', function(event) {
	const $box = $('#box');
	const text = $box.textbox('getText');
	
	// 숫자, 영문, 한글 가능. 특수문자 ;:,.?!-_()*%#만 가능.
	const regExp = /[^\s\wㄱ-힣;:,.?!\-()*%#]/g;
	let safeText = text.replace(regExp, '');
	
	// 최대 60자.
	const max = 60;
	if (max < safeText.length) {
		safeText = safeText.substring(0, max);
	}
	$box.textbox('setValue', safeText);
});

해설

  textbox라는 메서드 호출을 통해 얻은 객체에 이벤트를 붙일 수 있습니다. 이를 통해 EasyUI가 지원하지 않는 이벤트도 사용 가능합니다. numberbox에도 동일한 방식으로 사용 가능합니다.

  참고로 위 방식을 몰랐을 때는 아래처럼 복잡한 방식으로 구현하기도 했습니다.

$('#box1').numberbox({
	inputEvents:$.extend({},$.fn.numberbox.defaults.inputEvents,{
		keyup : function(event) {
			const text = $('#box1').numberbox('getText');
			if (3 <= text.length) {
				$('#box2').textbox('textbox').focus();
			}
		},
	})
});
반응형
728x90

개요

Apache Http Client를 통해 웹페이지에서 파일업로드를 하듯이 파일전송도 할 수 있습니다. 간단한 전송 예제코드를 보이고, 주의점을 설명합니다.

(방법이 어렵진 않으나 모르면 발생하는 오류를 해결하기 어려워 이 문서를 작성합니다.)

예제코드

	public void upload(
			final Map<String,Object> paramMap,
			final Properties prop) {
		HttpEntity multipartEntity = null;
		try {
			// <패스|쿼리|바디 필수값 검증>
			final String filePath = (String) paramMap.get("filePath");
			if (StringUtils.isBlank(filePath)) {
				throw new IllegalArgumentException("필수값 누락 : filePath");
			}
			final File file = new File(filePath);
			if (!file.exists()) {
				throw new IllegalArgumentException("필수파일 누락. file.getAbsolutePath():" + file.getAbsolutePath());
			}
			// </패스|쿼리|바디 필수값 검증>
			if (log.isDebugEnabled()) {
				log.debug("File.separator:" + File.separator);
			}
			String fileName = (String) paramMap.get("fileName");
			if (StringUtils.isBlank(fileName)) {
				fileName = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
			}

			final String url = prop.getProperty("URL");

			multipartEntity = MultipartEntityBuilder.create()
					.addBinaryBody("file", file, ContentType.MULTIPART_FORM_DATA, fileName).build();

			// <http-method="POST">
			final HttpUriRequest httpReq = new HttpPost(url);
			// </http-method="POST">
//			httpReq.setHeader("Content-Type","multipart/form-data");	// !주의! 'Content-Type'을 직접 지정하지 말고, MultipartEntityBuilder에서 자동으로 생성되는 대로 사용할 것.
			httpReq.setHeader("Accept","*/*");
			httpReq.setHeader("Accept-Encoding","gzip, deflate, br");

			// <http-method="POST" "multipart/form-data">
			((HttpEntityEnclosingRequestBase) httpReq).setEntity(multipartEntity);
			// </http-method="POST" "multipart/form-data">

			int timeoutSecond = 8;
			RequestConfig config = RequestConfig.custom()
					.setConnectTimeout(timeoutSecond * 1000)
					.setConnectionRequestTimeout(timeoutSecond * 1000)
					.setSocketTimeout(timeoutSecond * 1000).build();
			CloseableHttpClient httpclient = HttpClientBuilder.create()
					.setDefaultRequestConfig(config).build();
			httpclient.execute(httpReq);
		} catch (final Throwable t) {
			final String message = "API 파일업로드 실패.";
			throw new RuntimeException(message + " iParam:" + paramMap + ", prop:" + prop, t);
		}
	}

중요한 코드 위주로 간략화시키려하다보니 프로젝트 코드와 차이가 있는 편입니다. 핵심 내용만 참고하고, 응용할 필요가 있습니다.

해설

전송 방식은 POST, 파일데이터는 ‘MultipartEntityBuilder’를 이용해 추가합니다. 헤더에는 “Content-Type”을 직접 입력하지 않도록 주의합니다.(이유는 아래 [주의점]에서 후술.)

그 외에는 일반적인 POST와 동일합니다.

주의점

MultipartEntityBuilder를 사용할 때, 아래와 같이 직접 헤더에 "multipart/form-data"로 입력하면 안 됩니다.

httpReq.setHeader("Content-Type","multipart/form-data");

위와 같이 코딩했다면, 응답으로 '400 Bad Request'를 받게 될 것입니다.

왜냐하면 파일 데이터 전송 시 boundary도 필요하기 때문입니다. boundary는 파일데이터와 다른 데이터를 구분하는 경계로 사용하며, 이 boundary가 아래처럼 Content-Type에 함께 있어야 합니다.

Content-Type : multipart/form-data; boundary=(자동생성)

다만, MultipartEntityBuilder를 사용하여 Entity를 입력하면 이러한 헤더를 자동으로 입력해주므로 boundary를 어떻게 입력할 것인지 고민하지 않아도 됩니다.

참고자료

 

<끝> [DEV240827a]

반응형
728x90

개요

웹페이지에서 업로드 가능한 파일 유형을 제한해야 하는 경우가 있습니다.(이미지 파일만 업로드 가능하다던가, pdf 파일만 업로드 가능한 경우 등등.)

javascript를 통해 선택된 파일을 직접 검증하는 것도 가능하지만, accept 속성을 이용하여 애초에 선택 가능한 유형을 허용하는 파일로만 제한할 수도 있습니다.

예제코드

<input type="file" />에서 허용 파일 제한 accept 속성을 추가하기만 하면 됩니다. 아래에서는 pdf 파일만 허용하도록 설정했습니다.

<form id="fileupload" method="POST" enctype="multipart/form-data">
	<table>
		<tr>
			<th>요청서 업로드</th>
			<td colspan="3"><input type="file" accept=".pdf,application/pdf" /></td>
		</tr>
	</table>
</form>

위에서 pdf 파일만 허용하도록 설정했기 때문에, pdf 파일만 목록에 나타납니다.

accept가 없으면 모든 파일이 나타납니다.

해설

accept에는 파일확장자와 MIME 유형 문자열을 입력하여 허용할 파일을 지정할 수 있습니다. 각각은 콤마(,)로 구분합니다.

(하단 [참고자료] 항목 참고.)

주의점

불행히도 파일 선택창의 드롭다운리스트에서 ‘모든 파일 (*.*)’을 선택하면 다른 파일도 선택 가능합니다. 때문에 자바스크립트로 추가 검증이 필요합니다.

(하단 [참고자료] 항목 참고.)

참고자료

 

-끝-

반응형
728x90

개요

  장례식을 치르는 동안, 장례지도사가 장례식 후 해야 할 일에 대해 조언을 해주었습니다.

  사실 처음이라 잘 모르는 부분이라 우왕좌왕했는데, 일단 알게 된 부분들에 대해 정리해봅니다.

  속시 틀리거나, 미리 알아두면 좋은 팁 같은 게 있으면 공유해주시면 좋겠습니다.

 

<순서>

  1. 사망신고
  2. 안심상속원스톱서비스 신청
  3. 법무소 고용

  다만 개인은 법 관련 처리가 어려울 테니 법무소를 고용하는 것이 좋을 것이라고 장례지도사에게 조언을 받았습니다.

사망신고

  • 사망진단서 필수.
  • 기본적으로 동거하는 친족이 해야 한다.
  • 신고기한: 사망사실을 안 날로부터 1개월 이내.

안심상속원스톱서비스

  • 사망신고 필수: 사망신고가 되어야만 신청이 가능.
  • 사망신고 시 ‘안심상속원스톱서비스’도 함게 신청이 가능.
  • 신청기한: 사망일이 속한 달의 말일로부터 1년 이내

조회해주는 재산

  1. 국세(체납, 고지세액)
  2. 금융거래(은행잔고, 대출, 보험, 주식 등)
  3. 국민연금(가입여부)
  4. 지방세(체납, 고지세액)
  5. 자동차(소유정보)
  6. 토지(소유내역) 등 총 19종.

법무소(법무사 사무소)

  장례지도사의 말을 들었을 때는 상속과 관련해서 전반적으로 업무를 맡아주는 것이라 추측했는데, 몇 군데 방문 및 문의해본 결과 내 생각과 좀 달랐습니다.

  • 일단 원스톱서비스 결과가 있어야 법무소도 무언가 해 줄 수가 있다고 한다.
  • 대부분의 법무소는 부동산 관련 일만 처리한다고 하였다.
  • 그 외의 일은 개인이 각 기관에 문의하여 처리 가능하다는 답만 들었다.

 

  또한, 상속 관련 다음 3개의 사무소를 나눠서 생각해야 한다고 설명 받았습니다.

  • 법무소(법무사 사무소): 부동산 관련 서류 및 처리를 한다.
  • 세무소(세무사무소): 상속세 세금 계산 및 처리.
  • 변호사: 상속 관련 분쟁이 발생했을 때.

 

*참고: 법률사무소를 보고 찾아가봤었는데, 전혀 관련 없는 곳이었다. ‘법무소’는 ‘법률사무소’의 줄임말’이 아니었다. 법률사무소는 분쟁이 있을 때 찾아가는 곳이라고 한다.

추가 정보 링크

조언 부탁드립니다

  내용이 틀리거나 미리 알아두면 좋은 팁 등이 있으면 조언 부탁드립니다.

 

반응형

'' 카테고리의 다른 글

운전면허증 갱신 끝났다~~~!  (1) 2022.10.11
7천억 해먹고, 8천억 더.. 서민 등치는 '빌라왕국'  (0) 2022.09.04
시스템1의 지배  (0) 2022.05.01
728x90


왜 몸에 좋은 자세는 불편하고, 몸에 안 좋은 자세는 편할까?

 

몸에 좋은 자세가 불편한 이유 요약  : 몸에 좋은 자세가 불편한 이유는 근육을 사용하기 때문이다. 근육이 몸을 지탱하므로 근육이 피로를 느끼고, 이 때문에 불편하다고 느낀다.

편한 자세가 몸에 안 좋은 이유 요약 : 편한 자세는 근육 대신 관절을 사용한다. 하지만 관절은 근육과 달리 재생이 거의 되지 않는 부위이다. 편한 자세를 취하는 동안 관절이 갈려나가는 것과 마찬가지이기 때문에, 이것이 누적되면 꼬부랑 노인을 벗어날 수 없게 된다.

반응형
728x90

//XXX chan1. 아직 작성중인 문서이나, 바로바로 참고하기 위해 일단 초안 상태로라도 공개함.

개요

  인간의 몸은 발암물질 등으로 인해 세포분열 시 오류가 발생하면 암세포가 된다. 우리 몸은 그에 대항하여 NK세포 등의 면역세포가 암세포(비정상세포)를 인식하면 바로 죽이는 활동을 해서 암의 확대를 억제한다. 보통은 이 균형이 지켜지면서 암이 커지지 못한다.

  하지만, 무언가의 이유로 암세포의 발생이 늘어나고, 면역세포의 활동이 저해되면, 이 균형이 깨지게 된다. 이 균형이 깨지면 암이 발생하고, 설사 치료했다고 해도 재발하게 된다. 때문에, 이 균형을 유지할 수 있는 생활습관을 계속해서 유지해야 한다.

 

  이 균형을 지키기 위해 생활에서 다음 2가지를 생각하고 실천할 수 있을 것이다.

  • 암세포 발생 줄이기
  • 면역세포 돕기

 

//TODO chan1. 개요는 간결하고, 흥미가 생기게 잘 작성했다고 생각함. 이 이후는 {1. 실천방법, 2. 그 이유, 3. 매일매일 할 수 있는 체크리스트에 추가} 순서로 작성하는 게 좋다고 생각됨.

암세포 발생 줄이기

  암세포 발생을 줄이려면, 암의 발생 원인을 이해해야 할 것이다.

암 발생 원인

  발암물질은 음식물 외에도 미세먼지, 야근으로 인한 스트레스 등도 포함되기 때문에 생각보다 범위가 넓다.

면역세포 돕기

  암세포를 죽이는 면역세포로는 아래 2가지가 알려져 있다.

  • 세포독성T세포(killer T cell): 비정상 세포를 죽이지만, 비정상세포임을 판별할 수 있는 분자가 변형된 암세포는 죽이지 못한다.
  • 자연살해세포 = NK세포(Natural killer cell): 다른 면역세포와 달리, 정상세포가 아니라고 의심되기만 해도 죽인다.(NK세포는 HMC분자가 비정상인 세포도 죽인다.)

  이러한 면역세포가 제대로 활동할 수 있는 환경을 만들어준다.

NK세포

  백혈구의 일종으로 비정상 세포를 발견하면 죽인다. 비정상 세포란 암세포, 바이러스에 감염된 세포, 노화가 심해져 망가진 세포 등을 말한다.

면역력

음식

기타

  • 겨울에 춥게 지내지 맙시다: 유튜브[[무엇이든 물어보세요] 체온이 1도만 떨어져도 암 세포가 많아진다?! 체온 1도 떨어지면 나타나는 내 몸속의 변화! | KBS 211125 방송]”https://youtu.be/B97L5PXEH3k?si=_YeyeZUz3PJm2v0q




정리 전

  아직 내용을 확실히 파악하지 못했음. 내용 파악하여 정리해볼 예정.

[[737회] 생로병사의 비밀]”https://vod.kbs.co.kr/index.html?source=episode&sname=vod&stype=vod&program_code=T2002-0429&program_id=PS-2020078911-01-000&broadcast_complete_yn=N&local_station_code=00§ion_code=05§ion_sub_code=03”: “암보다 앎”

반응형
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

  일정 시간 간격 또는 정해진 시간에 실행되도록 하는 기능이다.

기본예제코드

스케줄러 클래스.

클래스에 @Component를 달고 메서드에는 @Scheduled를 단다.

비동기로 실행되어야 하는 업무는 @Async를 단다.

@Component
public class ExampleScheduler {
	/**
	 * 생성자.
	 */
	public ExampleScheduler() {
		super();
	}

	/**
	 * 스케줄 테스트하기.
	 */
	@Scheduled(cron = "0/60 * * * * ?")
	public void testCron() {
		System.out.println("(cron = \"0/60 * * * * ?\") 시스템 시간을 기준으로 1분 마다 주기적으로 실행한다.");
	}

	/**
	 * 스케줄 테스트하기.
	 */
	@Scheduled(fixedRate = 8000)
	public void testFixedRate() {
		System.out.println("(fixedRate = 8000) 다른 스케줄 작업 완료 여부와 상관 없이 8000ms마다 실행한다.(단, 스케줄러풀 필요.)");
	}

	@Scheduled(fixedDelay = 2000)
	public void testFixedDelay() {
		final String prefix = DateFormatUtils.format(new Date(), "ddHHmmss");
		for (int i = 0; i < 10; i++) {
			System.out.println(prefix + "-" + i + ". 비동기가 제대로 작동하는지 확인하기 위해, 의도적으로 장시간 실행되도록 함.");
			try {
				Thread.sleep(1000L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("(fixedDelay = 2000) 이 작업이 끝나면, 2000ms 뒤에 다시 실행한다.");
	}

	/**
	 * 비동기 스케줄 테스트하기.
	 */
	@Scheduled(fixedRate = 4000)
	@Async
	public void testFixedRateAysnc() {
		System.out.println("(fixedRate = 4000 @Async) 다른 스케줄 작업 완료 여부와 상관 없이 4000ms마다 실행한다.(단, 스케줄러풀 필요.)");
	}
}

“dispatcher-servlet.xml”에 설정들 추가.

  루트 beans에 task와 그 스키마 정보를 추가한다.

  그리고 task가 인식되도록 어노태이션에서 읽어들이도록 ‘<task:annotation-driven/>’를 추가한다.

<beans xmlns="http://www.springframework.org/schema/beans" …(생략)...
    xmlns:task="http://www.springframework.org/schema/task"
    …생략…
    xsi:schemaLocation="...
        http://www.springframework.org/schema/task     http://www.springframework.org/schema/task/spring-task.xsd
">

    <!-- 스케줄러 -@Scheduled가 있는 @Component 찾기 -->
    <task:annotation-driven scheduler="jobScheduler"/>
    <!-- 스케줄러 -@Async를 위한 스케줄러풀 설정 -->
    <task:scheduler id="jobScheduler" pool-size="10"/>

    …생략…
</beans>

  ‘<task:annotation-driven/>’의 스케줄러 컴포넌트 스캔 범위도 ‘<context:component-scan>’의 ‘base-package’에 설정한 스캔범위에 들어있어야 함은 동일하니 패키지 경로에 주의하자.

해설

  스케줄들은 실행시간이 되어도 다른 스케줄이 실행되는 중이면 끝날 때까지 대기하게 된다. 만약 반드시 정해진 시간에 실행되어야 하는 스케줄이라면 문제 될 것이다. 이를 위해 비동기로 실행시킬 수 있다.

  비동기는 스케줄 메서드에 @Async를 붙이면 된다. 단, 그냥 붙이기만 하면 실제로는 비동기로 실행이 되지 않는다. 비동기로 실행시키려면 [스케줄러 풀]이 필요하다.

참고 링크

반응형
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;
반응형

+ Recent posts