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

개요

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

한국투자증권 OpenAPI를 사용하려고 코딩을 했는데, 정작 실행을 하자 [javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]라는 길고 긴 예외가 발생했습니다.

검색을 해보니 원인이 여러가지일 수 있다고 하는군요. 일단 순서대로 첫번째 것부터 진행해봤는데, 첫번째 걸로 해결이 되버렸습니다.

*해결책 링크 : [Java 에서 ValidatorException 등 인증서 관련 에러 해결 - keystore에 SSL/TLS 인증서를 import 하기]

링크의 내용을 그냥 따라하면 되는데, 다만 [6]번에서 keytool이 어디에 있는지 설명이 없어서 약간 해맸습니다. 혹시나 해서 자바 설치 폴더의 bin 폴더를 살펴보니 거기에 있더군요.(설정에 패스가 잡혀 있으면 문제 없겠지만, 저처럼 안 잡혀 있으면 해당 경로에 맞춰서 입력해주면 됩니다.)

덕분에 잘 실행되어서 계속해서 개발해봐야겠습니다.

반응형

+ Recent posts