본문 바로가기

개발일지/JavaScript + jquery

[JavaScript] 인쇄 기능 재설계

인쇄기능 참고 사이트 (이벤트 핸들러 사용법)

 

JS#1. 웹페이지의 특정 부분만 인쇄하고 싶을 때

웹페이지에 인쇄 기능을 넣고 싶은데, 인쇄 결과물에는 전체가 아니라, 특정 부분만 나오게 하고 싶을 때가 있습니다. 웹서핑을 하다보면 이런 기능이 구현된 곳이 종종 보이지요. 두 가지 방법

openuri.net

 

전에 열심히 만들어뒀던 인쇄 기능이 있긴 했지만, 어느 순간부터 인쇄 기능이 실행이 된 이후 취소를 누르면 원본 복사 해둔 상태로 돌아가지 않는 문제가 생기고 말았다. 전에도 같은 부분에서 작은 문제가 있었는데 인쇄 기능 실행 이후에 원본의 창(모달창)의 취소 버튼이 눌리지 않는 문제가 있어서 아래 코드에 보면 그 부분을 수정하는 코드도 추가를 해뒀었다. 구글링을 해보니 새 창을 열어 원본 복제를 하고, 인쇄 또는 취소 후에 그 새 창을 닫는 방법이 가장 좋다고 하니 그냥 처음부터 다시 만드는 게 나을 것 같아서 다시 만듦

 

기존 코드

var printDiv;
var OriginalBody;

var printPage = {
		
		printDiv : printArea => {
			let printId = $(printArea).prop('id');			// 프린터할 영역 세팅		
		 	let size = '';
			printDiv = document.getElementById(printId);
			
			// 큰 컨텐츠는 그대로.. 작은 컨텐츠(영수증)는 확대해서 출력하기 위함
		 	if( Number(printDiv.clientWidth) >= 1000 )	size = 'small';
		 	
			window.onbeforeprint = printPage.beforePrint(size);	// 프린터 전 이벤트 지정
			window.onafterprint = printPage.afterPrint;		// 프린터 후 이벤트 지정
			window.print();									// 프린트
		},
		
		beforePrint : size => {
			OriginalBody = document.body.innerHTML;			// 원본 복사
			
			$(printDiv).find('.modal-dialog').removeClass('modal-dialog'); 			// 모달의 경우 가운데 정렬을 없애고, 오른쪽 치우침 방지
			
			if(size != '' && size == 'small')
				$('body').css('zoom', '90%').css('padding', '15mm 0 0 10mm');
			else
				$('body').css('zoom', '130%').css('padding', '15mm 0 0 15mm');		// 영수증 아닐 땐 생각해 봐야 됨 
			
			document.body.innerHTML = printDiv.innerHTML;
		},
		
		afterPrint : () => {
			document.body.innerHTML = OriginalBody;			// 원본 복구
			$('body').css('zoom', '100%').css('padding', '0');
		},
		
		// 인쇄 또는 취소 이후 => 닫기 버튼 안 눌리는 문제 해결
		// 인쇄 옆 취소 버튼에 적용
		modalCancel : divId => {
			$(divId).css('display', 'none');
			$('.modal-backdrop').remove();
		}
};

 

인쇄 영역이 크게 보였으면 좋겠다는 의견이 있어서 zoom을 사용했었는데, 해당 사이트의 경우 환경설정에서 화면 크기를 zoom으로 확대 축소하는 기능이 있기 때문에 어차피 수정을 해야 하는 상황이었음

 

 

 

수정된 코드

var printPage = {
		printNow() {
			// 이벤트가 일어난(인쇄 버튼을 누름) 최상위 Div 갖고 옴
			// => 모든 인쇄 버튼은 모달창 안에 위치한다는 가정을 두고 있음
			const printDiv  = event.target.closest('.modal');
			let	  newWindow = '';
			
			// 새창에 프린트 할 영역 복붙
			newWindow = window.open('', 'print', 'width=1000, height=1620, toolbars=no, scrollbars=no, status=no, resizable=no');
			newWindow.document.writeln(printDiv.innerHTML);		
			
			newWindow.onbeforeprint = this.beforePrint(newWindow, printDiv);	// 프린터 전 이벤트
			newWindow.onafterprint  = this.afterPrint(newWindow);			// 프린터 후 이벤트
			
			// 인쇄 : 시간차 없으면 css 적용 안 됨
			setTimeout(() => newWindow.print(), 600);
		},
		
		// 주의 : 자식창의 요소들을 조정해야 돼서 모든 기준은 window.document로 시작 
		beforePrint(window, printDiv) {
			// 새창 호출시 css 먹히지 않기 때문에 부모창 <head> 복붙
			const parentHeadCopy = document.querySelector("head").cloneNode(true);	// 여기서 document는 부모창 의미
			let childNode = window.document.querySelector("head");			// 여기서 window.document는 자식창 의미
			childNode.after(parentHeadCopy);
			
			this.printStyle(window, printDiv);		// 스타일 지정
			window.focus();					// 포커스
		},
		
		// 인쇄창 호출 후 : 인쇄 또는 취소 누르기 전에 창꺼짐 현상 대비
		afterPrint(window) {setTimeout(()=> window.close(), 1000)},
		
		printStyle(window, printDiv) {
			// 인쇄할 영역마다 사이즈가 다르기 때문에 아직까진 지정하는 방법만 ...
			let divName = $(printDiv).attr('id');
			if( String(divName) == 'modalReceipt') {
				window.document.body.firstChild.classList.remove('modal-dialog', 'modal-dialog-centered');		// 모달의 가운데 정렬 삭제 오른쪽 치우침 방지
				$(window.document.body).css({'padding':'25% 0 0 13%', 'zoom':'90%'});							// 인쇄 페이지에 맞춤
				window.document.body.querySelector('.modal-content').style.width = '800px';						// width 지정
				window.document.body.querySelector('.modal-content').classList.add('modalReceipt');				// css 적용하기 위함
			}
		},
		
};

모든 부분이 야매적으로 만들었기 때문에 진지하게 보지 않았음 함

 

 

제작하고 있는 사이트의 경우 프린트 할 영역이 모두 모달창 안쪽이다. 그리고 모달창 안에 인쇄 버튼이 있는 구조 라서 만들 때 한 페이지 안에 여러 개의 모달창과 여러 개의 인쇄 버튼이 있다면? 이라는 가정하에 제작했음. (대략 상단의 버튼이 인쇄 버튼 저런 구조임)

 

 

 

 

 

 


// 이벤트가 일어난(인쇄 버튼을 누름) 최상위 Div 갖고 옴
// => 모든 인쇄 버튼은 모달창 안에 위치한다는 가정을 두고 있음
const printDiv  = event.target.closest('.modal');

 

 

버튼을 누르면 이벤트가 일어난 버튼을 감싸고 있는 최상위 Div를 호출한다. 처음엔 .modal 대신에 .printDiv를 넣으면 어떨까? 생각했는데 새로운 클래스를 넣는 방법보다는 부트스트랩의 모달을 사용하고 있으니 이벤트가 일어난 최상단 Div에서 modal 클래스를 갖고 오면 되겠다 싶어서 수정하게 됨

 

// 새창에 프린트 할 영역 복붙
newWindow = window.open('', 'print', 'width=1000, height=1620, toolbars=no, scrollbars=no, status=no, resizable=no');
newWindow.document.writeln(printDiv.innerHTML);		

newWindow.onbeforeprint = this.beforePrint(newWindow, printDiv);	// 프린터 전 이벤트
newWindow.onafterprint  = this.afterPrint(newWindow);				// 프린터 후 이벤트

// 인쇄 : 시간차 없으면 css 적용 안 됨
setTimeout(() => newWindow.print(), 600);

 

1. window.open메서드로 새창을 열고, 그 이름은 newWindow로 정함

2. 위에서 정의한 인쇄 영역을 writeln으로 추가

 

이렇게 새 창 인쇄를 할 경우 가장 큰 단점이 있었는데 부모창 head 영역에 지정된 파일들도 모두 초기화 되기 때문에 프린터 전 이벤트에서 부모창의 head 영역을 복붙해주었다.

 

마지막으로 인쇄 부분에 setTimeout을 설정한 이유는 아래에 설명할 예정

 

 

인쇄 전 이벤트 메서드

// 주의 : 자식창의 요소들을 조정해야 돼서 모든 기준은 window.document로 시작 
beforePrint(window, printDiv) {
    // 새창 호출시 css 먹히지 않기 때문에 부모창 <head> 복붙
    const parentHeadCopy = document.querySelector("head").cloneNode(true);
    let childNode = window.document.querySelector("head");
    childNode.after(parentHeadCopy);

    this.printStyle(window, printDiv);			// 스타일 지정
    window.focus();								// 포커스
},

 

- 새창을 열어 DOM 영역을 잡으려고 할 때 가장 많이 실수했던 부분은 습관적으로 document.로 DOM 영역을 잡으려고 한 부분인데, 이렇게 되면 부모창을 잡게 되니 새로 연 창을 기준으로 DOM 영역을 잡으려면 앞에 window.document.로 시작 하는 걸 신경써야 한다. 하지만 부모의 <head> 영역을 복사해야하기 때문에 처음은 document.로 시작을 해야 함 또는 window.opener.document... 이런식으로 사용을 해도 된다. opener의 경우 새창을 열 수 있도록 도와준 부모를 가르키는 메서드라서 알고만 있어도 좋다.

- 노드 복사를 할 때 cloneNode 메서드에 true를 매개변수로 던져주는 이유는 자식노드까지 복사하기 위함

- this.printStyle 메서드로 한 번 더 스타일을 지정하는 이유는 아래에서... 

- window.focus() 포커스를 해줘야만 인쇄 영역이 해당 부분을 가르킨다. window.print() 바로 전에 사용해도 됨!!

 

 

this.printStyle()

printStyle(window, printDiv) {
    // 인쇄할 영역마다 사이즈가 다르기 때문에 아직까진 지정하는 방법만 ...
    let divName = $(printDiv).attr('id');
    if( String(divName) == 'modalReceipt') {
        window.document.body.firstChild.classList.remove('modal-dialog', 'modal-dialog-centered');		// 모달의 가운데 정렬 삭제 오른쪽 치우침 방지
        $(window.document.body).css({'padding':'25% 0 0 13%', 'zoom':'90%'});							// 인쇄 페이지에 맞춤
        window.document.body.querySelector('.modal-content').style.width = '800px';						// width 지정
        window.document.body.querySelector('.modal-content').classList.add('modalReceipt');				// css 적용하기 위함
    }
},

 

가장 위에서 지정했던 printDiv 요소 안에 있는 아이디를 갖고 온다. 그 아이디가 modalReceipt인 경우 if문 실행을 하게 되는데 .. 사실 이 부분이 가장 야매적인 부분이라고 생각하는데 어찌할 방도가 없음

- 클래스 삭제 : 부트스트랩 모달창의 경우 이 클래스로 가운데 정렬을 하고 있음. 인쇄시 이 부분 때문에 오른쪽으로 치우침 발생해 삭제한다.

- zoom 90% : 해당 사이트의 경우 환경설정에서 비율 조정을 할 때 zoom으로 조정을 하고 있기 때문에 인쇄 페이지에 크기를 맞추기 위해서 해당 설정을 둠

- 인쇄창에서 width 커지는 문제 해결을 위해 width를 따로 지정했다.

- 마지막 클래스 추가 : 사실 이 부분이 가장 예상 못 했던 부분임 처음 이 모달창을 제작할 때 인쇄 부분을 생각 못 했기 때문에 css에 id를 기준으로 효과를 적용시켰던 것. 같은 css 부분에 modalReceipt 이걸 추가할까 생각도 했는데 다음에 또 쓸지도 모르겠단 생각이 들어서 클래스명으로 넣었다.

 

// 인쇄창 호출 후 : 인쇄 또는 취소 누르기 전에 창꺼짐 현상 대비
afterPrint(window) {setTimeout(()=> window.close(), 1000)},

인쇄창 호출 후에 인쇄 또는 취소 버튼 누르지도 않았는데 바로 창이 꺼지는 것을 대비하기 위해서 setTimeout을 사용했다. 적어도 바로 꺼지는 현상은 없었고, 버튼을 눌러야만 사라지도록 설정이 됨

 

setTimeout(() => newWindow.print(), 600);

인쇄창이 뜨기 전 css가 제대로 적용이 안 돼서 0.5초 정도 시간을 주었다. 

 

가능한 setTimeout 대신에 콜백 함수를 사용하고 싶었는데 객체 안에 있는 메소드 방식이라서 그런건지.. 아니면 내가 사용법을 잘 못 익혀서 그런건지 제대로 적용이 안 됐고, 조금이라도 더 나은 방법이 있을까 오늘 집 와서 약 3시간 동안 쑈를 했는데 아무것도 안 됨. 콜백 함수/promise/async-await 등.. 저번에도 비동기 처리방식 도전했다가 처참하게 실패 했는데 이 부분은 공부가 많이 필요할 듯하다.

 


이 기능을 제작하면서 이번에 새로 배운 것!

객체 안에 함수가 있는 메소드 방식의 경우 arrow 함수를 쓰는 것보다 일반적인 함수를 사용하는 것이 좋다. 그 이유는 객체 자신을 지칭하는 this 키워드를 사용할 수 있기 때문이고, 대신에 더 깔끔하게 표현하기 위해서는 아래 처럼 사용하는 방법도 있다!

 

var ObjectName1 = {
	methodName1 : function() {
    	// 기존에 사용하던 방법
    },
};

var ObjectName2 = {
	methodName2() {
    	// 이렇게 단축해도 된단 말씀!
    },
};

아쉬운 부분

1. 가능한 setTimeout 대신에 콜백 함수를 사용하고 싶었는데 객체 안에 있는 메소드 방식이라서 그런건지.. 내가 사용법을 잘 못 익혀서 그런건지 제대로 적용이 안 됐던 점
2. 처음 모달창을 만들 때 인쇄 기능을 생각 못 하고 설계를 잘 못해서 마지막 스타일 지정하는 부분이 길어졌다는 점 

 

프론트단은 거의 혼자 맡아서 하고 있기 때문에 어디 물어 볼 곳도 없고, 구글링으로도 한계가 있어서 애를 많이 먹고 있다. 아쉬운 부분이 많긴 하지만 오히려 더 잘하고 싶고, 자바스크립트 구조나 기능에 대한 이해도를 높이고 싶은 욕심이 크게 들어서 어떻게 공부를 해야하나 고민이 너무너무 많은 요즘이다. 배워도 배워도 부족함이 크게 다가오는데 일단은 만든 거라도 내 것으로 만들기 위해서 블로그를 열심히 해야겠단 생각을 하구 있음... 만들어두고 이게 왜 이렇게 돌아갔더라? 까먹는 일이 다반사이기 때문에 기록하고 찾아 보는 게 가장 큰 도움이 될 듯 하다.