Ajax 비동기 통신을 통해서 목록 데이터를 테이블 태그와 페이징 형식으로 표현하기 위해 플러그인 방식 구현

 

//사용법 예시
url = '/getPagingList.do';
option = {
		widthArray : [20,70,10],
		headerArray : ['순서','제목','날짜'],
		tdTemplte : [
						'<td>@{no}</td>',
						'<td>@{title}</td>',
						'<td>@{date}</td>',
					]
};
urlParams = {
	"pageSize":'10', 
	"pageNo":'1',    
	//나머지 조건값
};

$('#tableArea').tableMaker(option,url,urlParams, function ($this){
	//tr이벤트
	$this.find('tr').off().on('click',function(){
	});
});

1. URL : 비동기 통신을 위한 URL 정보

2. Option 

 - widthArray : 테이블의 넓이값을 %로 설정

 - headerArray : 테이블의 헤더명을 입력

 - tdTemplte

    + td의 클래스 및 화면에 표시될 내용을 설정

    + @{}는 형식 유지 필요

    + @{}안에 설정하는 값은 DB 컬럼값과 같은 명을 사용해야 함.

3. urlParams 

 - pageSize : 목록에 보여질 페이지 사이즈

 - pageNo : 페이지 번호

 

비동기 통신 결과값 예시

data.paging : {
    totalCnt : 3,
    pageNo : 1,
    pageSize : 10
}
data.result : [
    {no : 1, title : 'title1', date : '2020-01-01'},
    {no : 2, title : 'title2', date : '2020-01-01'},
    {no : 3, title : 'title3', date : '2020-01-01'},
]

*만약 결과를 하나의 Object로 작업을 해야 한다면, tableMaker에서 data.paging으로 되어있는 코드를 변경하여 사용

 

 

(function( $ ) {
	$.fn.tableMaker = function(option, url, urlParams, callBack) {
		
		var $this = $(this);
		
		var _this = {
				
			//비동기 통신
			ajaxCall : function(url, urlParams, callBackFunc){
				$.ajax({
					url: url,
					cache: false,
					data: urlParams,
					method: 'POST',
					dataType: 'json',
					contentType: 'application/json;charset=UTF-8',
					success: function(data, textStatus, jqXHR) {
						if(typeof callBackFunc == 'function') {
							callBackFunc(data);
						}
					},
					error: function(jqXHR, textStatus, errorThrown) {
						console.log(textStatus);
					}
				});
				
			},
		
			
			init : function(urlParams){
				//테이블 초기화
				$this.html('');
				
				_this.ajaxCall(url, urlParams, function(data) {
					
					if ( data.paging.totalCnt == 0 ){
						//검색 결과 없음.
						var html = '데이터가 존재하지 않습니다.';
                        $this.html(html);
					}else{
						//테이블 표기
						$this.html(_this.drawTable(data));
					}
					
					//페이징 이벤트 처리
					$this.find('div.paging ul li a').off().on('click',function(){
						var pagingData = $(this).attr('paging-data');
						var pageNo = 1;
						if ( pagingData ){
							pageNo = pagingData;
						}else{
							pageNo = $(this).text();
						}
						urlParams.pageNo = pageNo;
						_this.init(urlParams);
					});
					callBack($this,data);
				});
			},
		
			//페이징 HTML 생성
			drawPaging : function(data){
				var pagingHtml ='';
				
				var currentStartPage = Math.ceil(data.paging.pageNo/10)*10-9;
				var currentEndPage = currentStartPage + 9;
				var totalPageCnt   = Math.ceil(data.paging.totalCnt/data.paging.pageSize);
				
				if(currentEndPage > totalPageCnt ){
					currentEndPage = totalPageCnt;
				}
				
				pagingHtml += '<div class="paging">';
				pagingHtml += '    <ul>';
				
				if ( data.paging.pageNo > 1 ){
					pagingHtml += '        <li class="go_first"><a paging-data="1" href="javascript:;"><i class="fa fa-angle-double-left"></i></a></li>';
				}
				if ( data.paging.pageNo > 1 ){
					pagingHtml += '        <li class="go_prev"><a paging-data="'+(parseInt(data.paging.pageNo)-1)+'" href="javascript:;"><i class="fa fa-angle-left"></i></a></li>';
				}
				
				for ( var i = currentStartPage ; i <= currentEndPage ; i++){
					pagingHtml += '        <li ' 
					pagingHtml +=	( i == data.paging.pageNo ) ? 'class="on">' : '>'
					pagingHtml +=	'<a href="javascript:;">'+i+'</a></li>';
				}
				
				if( data.paging.pageNo < totalPageCnt){
					pagingHtml += '        <li class="go_next"><a paging-data="'+(parseInt(data.paging.pageNo)+1)+'" href="javascript:;"><i class="fa fa-angle-right"></i></a></li>';
				}
				
				
				if( currentEndPage < totalPageCnt){
					pagingHtml += '        <li class="go_last"><a paging-data="'+totalPageCnt+'" href="javascript:;"><i class="fa fa-angle-double-right"></i></a></li>';
				}
				pagingHtml += '    </ul>';
				pagingHtml += '</div>';
				return pagingHtml;
			},
		
			//table HTML 생성
			drawTable : function(data){
				var templteHtml ='';

				templteHtml += '        <table>' 
				
//				templteHtml += '<caption>';
//				templteHtml += data.paging.caption; 
//				templteHtml += '</caption>';
				
				templteHtml += '            <colgroup>';
				option.widthArray.forEach(function(value,index){
					templteHtml += '                <col width="'+value+'%;">';
				});
				templteHtml += '            </colgroup>';
				templteHtml += '            <thead>';
				option.headerArray.forEach(function(value,index){
					templteHtml += '                <th>'+value+'</th>';
				});
				templteHtml += '            </thead>';
				templteHtml += '            <tbody>';
				
				data.result.forEach(function(rsValue,rsIndex){
					templteHtml += '                <tr>';
					option.tdTemplte.forEach(function(value,index){
						var check = false;
						do{
							var match = value.match(/\@\{(.+?)\}/);
							if ( match && match.length >= 2 ){
								var dat = '';
								if ( rsValue[match[1]] ){
									dat = rsValue[match[1]];
								}
								value = value.replace(/\@\{(.+?)\}/, dat);
								check = true;
							}else{
								check = false;
							}
						}while(check);
						
						templteHtml += value;
					});
					templteHtml += '                </tr>';
				});

				templteHtml += '            </tbody>';
				templteHtml += '        </table>';
				templteHtml += _this.drawPaging(data);
				return templteHtml;
			}
		};
		
		_this.init(urlParams);
		
	}
}( jQuery ));

 

MyBatis 사용 시, 페이징이나 그리드를 위한 쿼리문 템플릿 예시

 

-- selectData에는 실제 사용될 쿼리문을 사용

<sql id=”selectData”>

 실제 사용될 쿼리문

</sql>

 

-- selectDataList에는 한 페이지에 표시될 내용을 출력

-- ( ※ startNum과 endNum은 VO에 값이 있어야 오류 없이 사용 가능 )

-- 예시 : startNum : 1, endNum : 10이면 총 10개의 row로 구성된 내용을 출력 

<select id=”selectDataList” parameterType=”” resultType=””>

select 

       A.*

    from (

        select rownum rn

          ,A.*

        from (

          <include refid="selectData"/>

          )A

     where 1=1

    ) A

where 1=1

and   rn >=#{startNum} and rn &lt;=#{endNum}

</select>

 

-- selectDataListCount에는 쿼리문에 대한 전체 개수 출력

<select id="selectDataListCount" parameterType="" resultType="int">

    select 

           count(1) cnt 

    from   (

        <include refid="selectData"/>

    )

 </select>

 

 

 

Background.js 파일은 백그라운드에서 동작하는 기능을 설정할 있다.

 

현재는 TODO 정보를 백그라운드에서 알림창을 사용하여 화면에 표현해주는 내용을 작성한 것이다.

 

  • background.js

chrome.runtime.onMessage.addListener(message =>{

createNotification(message['notificationId']);

return true;

});

/**

* 알림창 옵션 생성

*/

function getNotificationOption(notificationId){

return new Promise(function(resolve, reject){

try{

chrome.storage.sync.get(notificationId, function(items) {

let notificationsOption = {

type: "basic",

title: "TODO Notification!",

iconUrl : 'icon.png',

message: items[notificationId],

requireInteraction : true

}

resolve(notificationsOption);

});

}catch(e){

reject();

}

});

}

/**

* 알림 생성

*/

function createNotification( notificationId ){

getNotificationOption(notificationId).then(function(value) {

chrome.notifications.create(notificationId, value, function(){

});

});

}

/**

* 알림 업데이트

*/

function clickedNotification( notificationId ){

if ( confirm('완료했나요?') ){

chrome.storage.sync.remove(notificationId, function(){});

}else{

getNotificationOption(notificationId).then(function(value) {

chrome.notifications.update(notificationId, value, function(){});

});

return;

}

}

 

chrome.notifications.onClicked.addListener(clickedNotification);

 

 

백그라운드에서는 리스너를 사용하여 popup.js에서 전송되는 메시지 정보를 전달 받을 있도록 해둔다.

 

전달받은 메시지 내용을 기반으로 알림창을 생성하게 된다.

 

알림창 옵션에서 iconUrl 필수 옵션이므로, 알림창 icon영역에 표현될 이미지 파일을 추가해야 한다.

 

이미지 파일은 크롬 확장프로그램 폴더에 동일하게 위치 시키면 된다.

 

알림창 옵션 , requireInteraction true 지정하지 않으면, 알림창은 수초 이내로 자동으로 사라지게 되어있다.

 

따라서 알림창을 클릭하기 전까지 항상 유지하도록 설정하였다.

 

알림창을 클릭하면 알림창을 닫을지 여부를 확인하여, 나머지 작업을 처리하도록 하였다.

 

지금까지 간단하게 입력창을 통하여 데이터를 저장하고, 알림창까지 띄우는 내용을 진행했다.

(다만, 메시지를 전달 받은 , 비동기 동작(storage에서 데이터를 꺼내는 작업)으로 인하여, 콘솔창에 오류가 발생한다. )

 

크롬 확장프로그램은 기본적으로 스크립트와 html 이뤄져있기 때문에, 크롬 확장프로그램 규격에 맞는 구성과 API 사용하면,

 

본인이 원하는 내용은 간단하게 만들어서 사용이 가능하다. ( : 달력연동, 모니터링 기능 등등 )




Popup.html 파일은  Todo 내용을 저장 있도록 간단하게 구성하였다.

( CSS 파일을 사용하지 않았지만, 화면에 맞게 CSS 구성하여 import 하여 사용도 가능하다. )

 

  • popup.html

<!DOCTYPE html>

<head>

<meta charset="UTF-8">

<script src="jquery-3.3.1.min.js"></script>

<script src='popup.js'></script>

</head>

<body>

<input id="todo">

<button id="save">저장</button>

</body>

 

코드에서는 selector 편의성을 위해서 jQuery 추가하였다.

 

, jQuery 사용하려고 할때, CDN 이용해서 사용할 수가 없다.

따라서 jQuery 파일을 다운 받고, 크롬 확장프로그램 폴더에 추가한 다음에 사용하면 된다.

 

실제 화면에서 사용되는 사용되는 스크립트 파일은 popup.js 되며, 소스는 내용은 아래와 같다.

 

  • popup.js

$(document).ready(function(){

$('#save').on('click',function(){

if ( $('#todo').val() ){

chrome.storage.sync.get(null, function(items) {

var allKeys = Object.keys(items);

if ( allKeys.length >= 3){

alert('더이상 저장 할 수 없습니다.');

return;

}

let message = $('#todo').val();

let timeStamp = getTimeStamp();

chrome.storage.sync.set({[timeStamp] : message}, function() {

$('#todo').val('');

chrome.runtime.sendMessage({'notificationId' : timeStamp}, function(){});

});

});

}else{

alert('내용을 입력해주세요.');

}

});

});

/**

* 현재 시간 정보 가져오기

*/

function getTimeStamp(){

let d = new Date();

let result = '';

result += zeroPadding(d.getHours()) + ':';

result += zeroPadding(d.getMinutes()) + ':';

result += zeroPadding(d.getSeconds());

return result;

}

/**

* 10의 자릿수로 설정

* @param {*} num 숫자

*/

function zeroPadding(num){

if ( num < 10 ){

return '0' + num;

}

return num;

}

 

Chrome storage 입력한 시간을 Key, 입력한 값을 value 사용하여 데이터를 저장하고,

 

sendMessage 통하여 데이터를 background.js 전송하도록 하였다.

 

모든 처리를 popup.js 하지 않고, background.js 데이터를 전송하는 이유는

 

Popup.js에서 사용할 있는 chrome.API 제한이 있다. 따라서 본인이 사용하고자 하는 API content영역에서 사용할 있는지 확인 필요하다.

 

(설정에서 추가한 notification api content영역에서 사용할 없어서 데이터를 background.js 전송 , notification 사용하였다.)


Notification의 경우, content에서도 사용 가능하다. tutorial에서는 content.js와 background.js간의 통신부분만 이해하면 된다.


'프로그래밍 > Chrome Extension' 카테고리의 다른 글

크롬 확장프로그램 tutorial 1  (0) 2019.03.23



크롬 확장프로그램 개발은 js, html, 그리고 Manifest File 구성되어 개발이 된다.

 

크롬 확장 프로그램에 필요한 js, html 그리고 Manifest file 하나의 폴더에 두고,

 

크롬을 실행하여 주소입력창에 chrome://extensions 입력하면, 확장프로그램 페이지로 이동한다.

 

개발자 모드(Developer mode) 켜주면, 숨겨진 바가 나온다. 


거기서 "압축해제된 확장 프로그램을 로드합니다.(Load unpacked)" 클릭하면,

 

폴더를 선택할 있는 창이 나온다


여기서 크롬 확장프로그램에 필요한 파일들이 들어있는 폴더를 선택하면 된다.

 

그러면 크롬에 확장 프로그램이 추가된다.

 

크롬 확장 프로그램에서 manifest.json 파일 설정에 따라 필요한 js,html 있으므로


간단하게 작성해 보면 아래와 같다.

 

{

"name": "Tutorial",

"version": "1.0",

"description": "run to Simple",

"permissions":      ["storage","notifications"],

"background": {

"scripts": ["background.js"],

"persistent": false

},

"browser_action": {

"default_popup":"popup.html"

},

"manifest_version": 2

}

 

manifest.json에서 필수로 선언되어야 하는 내용은 name, version, manifest_version 3가지 이고,


manifest_version 2 설정해야된다.

 

상세 내용은 공식 홈페이지 참조 (https://developer.chrome.com/extensions/manifest)

 

위와 같이 설정을 하게 되면 필요한 파일은

 

- Background.js

- popup.html

- popup.js ( popup.html 내에서 사용될 자바스크립 )

 

추가적으로 ajax selector 사용하기 위하여 옵션적으로 jQuery.js 파일을 추가해서 사용해도 된다.





'프로그래밍 > Chrome Extension' 카테고리의 다른 글

크롬 확장프로그램 tutorial 2  (0) 2019.03.24



jQuery HTML 태그에 값이나 html 설정해야 하는 경우, Html 태그 별로 각각 사용하는 함수가 다르다.

 

그러므로 개발을 진행하다고, 함수를 찾아보거나, html 값이 적재되지 않는 이유를 찾지 않아도 된다.

( 예를 들어 span 경우, val() 함수를 사용하고, 값이 들어갔지라는 의문을 가질 필요가 없다. )

 

따라서, 매번 Html 태그를 확인해서 함수를 사용하기 보다는 미리 html 값을 설정 있는

 

getter, setter 함수를 생성하고 사용하는 편이 나중을 생각해서도 편하다.



소스코드 예시

jQuery.fn.extend({

    setValue: function (value) {

      if ( $(this).is("input") || $(this).is('textarea') || $(this).is('select') ){

              $(this).val(value);

      }else{

              $(this).text(value);

      }

    },

    getValue : function(){

      if ( $(this).is("input") || $(this).is('textarea') || $(this).is('select') ){

              return $(this).val();

      }else{

              return $(this).text();

      }

    }

});


//사용법

$('#id').setValue('test');

console.log($('#id').getValue());


'프로그래밍 > jQuery' 카테고리의 다른 글

반복적 비동기 호출  (0) 2019.02.27
[jQuery] scrollTop 을 이용한 textarea focus  (0) 2012.08.22
[jQuery] each()  (0) 2012.05.10
[jQuery] attr()  (0) 2012.04.25
[jQuery] jQuery.each() vs .each()  (1) 2012.04.24





언더 스코어를 사용하여, 데이터를 오름차순, 내림차순으로 정렬하는 기능


데이터는 JSON으로 구성되어있으며, Array로 구성된 상태이다. ( 아래 예시 참조 )

var data = [

  {

    name : 'AA'

    ,age : 20

  }

  ,{

    name : 'BB'

    ,age : 20

  }

];



위 데이터를 기준으로, name과 age는 Key값이 되며, age를 기준으로 오름차순 또는 내림차순 정렬을 진행



var ascData = _.chain(data)

.sortBy("age")

.value();


var descData = _.chain(data)

.sortBy("age")

.reverse()

.value();



언더스코어에서 sortBy() 함수는 오름차순 정렬만을 제공한다.


따라서, 내림차순 정렬을 하기 위해서는 sortBy()을 사용하여 오름차순 정렬을 진행하고,


reverse()함수를 통해서 데이터 내용을 반전시켜서, 


내림차순처럼 데이터를 만들어서 사용하면 된다.





jQuery로 배열이나, Collection 정보를 순차적으로 Ajax 비동기를 호출해서 처리해야 하는 상황에서 

(예시 : 첫번째 Ajax 비동기 호출이 완료 될 때까지, 기다렸다가 두번째 Ajax 비동기 호출을 해야하는 경우 )


Ajax 비동기 호출을 기다리기 위해서 async, await, promise를 사용하여 문제 해결을 진행했다.



단, 소스 코드를 실행 전에 사용하고 있는 브라우저가 async, await, promise 를 지원하는지 확인이 필요하다.


크롬도 마찬가지로 버전이 낮을 경우, 해당 소스코드를 문법 오류로 인식한다.



- 소스는 참조용입니다.

eachAjaxCall();


async function eachAjaxCall(){

  var array = [1,2,3];

  for ( const element of array ){

  try{

    await promiseAjaxCall(element);

      console.log('element done');

    }catch(e){

    console.log('called by reject');

    }

  }

   console.log('process done'); 

}



async function promiseAjaxCall(element){

return new Promise(function(resolve,reject){

$.ajax({

                 url: 'URL 주소 작성',

                 type: 'POST',

data : element,

    }).done(function(data) {

    /**

              * 

      */

      console.log('ajax done' + data);

      console.log(element);

      resolve();

    }).fail(function() {

    reject();

  });

  });

}





'프로그래밍 > jQuery' 카테고리의 다른 글

jQuery로 Value getter(),setter() 만들기  (0) 2019.03.16
[jQuery] scrollTop 을 이용한 textarea focus  (0) 2012.08.22
[jQuery] each()  (0) 2012.05.10
[jQuery] attr()  (0) 2012.04.25
[jQuery] jQuery.each() vs .each()  (1) 2012.04.24


자바 스크립트 에서는 3가지 변수 선언 방식을 사용하고 있다.


1. var : 어디든 접근 가능한 변수


var x = 1;         //x = 1

{

var x = 2; //x=2

}

x = 3;              //x=3



2. let : 블럭영역(scope)에서 사용되는 변수 


var x = 1;      //x=1

{

   let y = 2;    //y=2

}



3. const : 상수로 사용


const PI = 3.14;



3가지 변수 선언 방식 중, var의 경우 한번의 선언으로 전역에서 접근이 가능하다.


따라서, 다양한 Javascript 파일이 import 되는 경우, 변수명이 충돌나는 상황이 발생할 수도 있다.


따라서, let의 사용성을 증가시켜, 무분별한 var 선언을 줄이도록 해야한다.




먼저, AOP나 CGLIB 내용은 고려하지 않고 기본 @Transactional을 사용 할 경우를 다룬 내용입니다.


우선, Spring boot에서 @Transactional 위한 기본 설정 사항


@Configuration

@EnableTransactionManagement

public class DataSourceConfig{

..

    @Bean

    public DataSourceTransactionManager txManager() {

        return new DataSourceTransactionManager(getDataSource());

    }

...

}


 위와 같이 설정을 하고 사용하게 되면 @Transactional 이용하여 트랜잭션을 사용 할 있다.

 


유의점

1. Dynamics proxy 사용하기 때문에 인터페이스 구현이 필요하다.


따라서, 아래와 같은 구조를 가져야 한다.

public interface AService {

}


public class AServiceImp implements AService {

@Transactional

public void biz() {

}

}



2. 내부 호출, 재귀 호출 로서 사용하면 안된다.


예를 들면

public class AServiceImp implements AService {


@Transactional

public void biz() {

//insert 호출

mapper.insertBiz();

//내부호출

bizUpdate();

}


@Transactional

public void bizUpdate() {

mapper.updateBiz();

mapper.deleteBiz();

}

}


위와 같이 @Transactional을 사용한 함수여도 내부적으로 호출을 사용하게 되면

트랙잭션이 동작하지 않게 된다.


따라서, 2가지 유의점을 고려하여 @Transactional을 사용하여야 한다.