굳이굳이 새로고침 없는 글목록 만들기... (유사SPA) > 자유게시판

본문 바로가기

자유게시판

굳이굳이 새로고침 없는 글목록 만들기... (유사SPA)

본문

안녕하세요 이윰빌더 잘 쓰고 있는 한 마개조 전문가(?)입니다ㅋㅋ 저는 리액트나 vue.js 등등 모던한 프론트엔드 프레임워크를 배워본 적이 없습니다. 왜냐하면 자바스크립트랑 php도 그냥 그누보드 개조하면서 배웠거든요.. 그런 제가 기본 이윰빌더 테마 뜯어 고치고 노는 게 생각보다 재미있어서 이번에는 그누보드도 좀 더 모던해질 수 있다! 는 이상한 집념을 가지고 이번에는 글목록 페이지 넘길 때 새로고침 없이 넘길 수 있도록 작업을 좀 해봤습니다. 아니... 따지고 보면 대단하게 복잡하게 뭘 한 것도 아닌데 체계적으로 웹이나 프로그래밍을 공부한 적이 단 한번도 없어서 그런건지 굉장히 시간이 오래걸리더군요... 그래도 챗지피티한테 모르는거 열심히 물어보며... 끝끝내 완성에 가깝게 마무리..를 했습니다. ![!\[image\]](https://eyoom.net/data/editor/2407/3716934337_1720719387.24004.gif) ## 1\. 버튼을 누르면 POST 요청을 날리게끔 일단 새로고침 없이 페이지를 넘기기 위해서는 글 목록을 HTML이 아닌 JSON으로 받아와야 했습니다. 그러면서도, 처음 글 목록 페이지에 접속했을 때, 특히 첫번째 페이지가 아닌 다른 페이지로 바로 접속했을 때에도 잘 보여져야 하므로 페이지 주소는 그대로 유지하는 것이 좋겠다고 생각했습니다. 우선 기존의 pagination은 유지하고 새로고침 없이 운용할 게시판에서 별도로 사용하기 위해 pagination 스킨을 복사했습니다.. `/theme/eb4_basic/skin/paging/basic` 이 폴더를 `basic_with_json`으로 복사해서요. 이제 페이지 버튼(a태그)의 링크를 눌렀을 때 페이지 전환이 되지 않도록 ```javascript $('.eb-pagination a:not(.active)').on('click', (event)=>{ event.preventDefault(); }) ``` 코드를 넣었습니다. 자 이제 버튼을 눌러도 아무런 반응을 하지 않게 되었죠. 그 대신 이것을 POST 요청을 보내서 JSON으로 받아오게 하기 위하여 Fetch API를 활용했습니다. ```javascript fetch(event.target.href, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Action-Type': 'pagination' }, body: JSON.stringify({bbs: 'list', type: 'json'}) }) .then(response => { return response.json(); }) .then(data => { console.log(data); }) .catch(error => { console.log('Error: ', error); alert('목록을 받아오지 못했습니다.'); }) ``` 중요한 것은 같은 주소를 요청했을 때, json으로 받아와야 하는 순간을 구분해야 했기 때문에, 여러 방법을 고민하고 시도해보다가, 헤더에 저만의 선언을 만들어서 하는 쪽으로 가닥을 잡았습니다. 왜 이렇게 했는지는.. 뒤이어 나올 내용에서 설명이 되겠죠ㅋㅋ 일단, POST로 보내는 부분의 뼈대는 이렇게 작성하고, 이제 실제로 해당 주소에 POST 요청을 날렸을 때 제가 원하는 응답이 올 수 있도록 php 쪽을 봐야 했습니다. 예를 들어 https://example.com/bbs/board.php?bo\_table=my\_board&page=4 이런 주소를 생으로 접속하면 기존과 같이 익히 아는 글 목록 화면이 나오고, 이것을 'Action-Type'이라는 헤더를 가진 POST 요청으로 날리면 json이 튀어나오게끔 해야 했습니다. 우선은 제가 날리는 요청이 잘 가는지부터 봐야했습니다. 처음에는, 그냥 `list.php` 관련된 부분만 건드리면 될 줄 알고서 `/eyoom/user/board/list.skin.php` 파일 하나 만들고 ```php if (!defined('_EYOOM_')) exit; if ($_SERVER['REQUEST_METHOD'] == "POST") { $list = $_POST; echo json_encode($list); exit(); } ``` 이런식으로 코드를 짜봤습니다. 그리고서 링크를 클릭했는데, HTML 문서가 로드되는 것처럼 보이고... 순수한 json이 나오지 않더군요. 그래서 주소를 잘 보고 생각했죠. 아, list.php에 바로 접속하는게 아니구나! board.php를 조져야겠네! 하고서 우선 뼈대가 되는 그누보드의 `/bbs/board.php`를 살펴봅니다... ```php //... include_once(G5_BBS_PATH.'/board_head.php'); // 게시물 아이디가 있다면 게시물 보기를 INCLUDE if (isset($wr_id) && $wr_id) { include_once(G5_BBS_PATH.'/view.php'); } // 전체목록보이기 사용이 "예" 또는 wr_id 값이 없다면 목록을 보임 //if ($board['bo_use_list_view'] || empty($wr_id)) if ($member['mb_level'] >= $board['bo_list_level'] && $board['bo_use_list_view'] || empty($wr_id)) include_once (G5_BBS_PATH.'/list.php'); //... ``` 코드 끝자락에 이런 부분이 보이는데 옳거니, list.php를 불러오기 전에 board\_head.php를 불러오는군! (사실 당연한게... 글목록 페이지라고 헤더 부분 안나오는거 아니니까,,) 그 후 타고타고... list.php가 불러와지기 전에는 head.php가 불러와진다는 것을 발견하고, 이젠 `/eyoom/user/head.skin.php`를 만들고 조져봅니다. 코드는 동일하게 넣구요. 그랬더니, 응답에 아무런 내용이 나오지 않는겁니다. ??? 왜일까 한참 생각했죠... 열심히... 우선은 페이지에 담긴 모든 변수를 파악해보자 싶어서, ```php echo json_encode(get_defined_vars()); ``` 이것을 작성하고 다시 시도. 그런데도 아무것도 내용이 안나오는 겁니다... 여기서 일단 멘붕. 왜? print\_r로 저거 치면 잘만 나오드만 왜? 열심히 구글링해보니 간단하게도,,, get\_defined\_vars()를 array\_keys()로 한번 감싸주면 되더군요. 근데도 잘 안됐던 것 같습니다. 제 기억에는... 그래서 그냥 json으로 받는건 포기하고, fetch 부분에서 json 대신 text로 받게끔 수정하고 print\_r로 출력하게끔 했습니다. 어쨌든 데이터가 나왔습니다. 그런데? $\_POST 변수에는 아무 것도 없는 겁니다... 허허 그런데 여기서 또 다른 문제가 생깁니다. ## 2\. POST 요청은 니 생각만큼 안쓰이지 않는단다? 너무너무 당연한 이야기지만... 생각보다 많은 동작에서 POST 요청이 이루어지는데... 댓글 쓰는거나 글 쓰는거나 뭐 이런 기본적인 것에서부터ㅠ 이걸 어떻게 알게 됐냐, 사실 실제 운용하는 서버에서 이걸 테스트 하고 있었거든요, 사이트는 저희 회사 발주게시판인데, 발주 처리 작업하시던 직원 분께서 이상한 화면을 보신겁니다. 정상적인 페이지가 아니라. 알고보니 아까 get\_defined\_vars() 이거 출력하게끔 했던 거... 이게 떠버린거죠. 그래서 허,,, 이걸 어떻게 해결할까 한참 고민했습니다. 일단 다행히, 테스트 전용 게시판을 만들어 거기서 작업을 하고 있는 것이었기 때문에, 여러 방법을 시도하다가, ```php if ($board['bo_table'] == 'test_board') ``` 이렇게 테스트 보드에서만 작동하도록 수정을 해서 일단락합니다. 사실 페이지 넘기는 데 굳이 POST 양식이 필요하진 않잖아요? 그래서 POST 보낼 때, '이것은 페이지를 넘기기 위핸 POST 요청이다!'라는 것을 알리기 위해서 양식을 받게끔 하려고 한거거든요. 근데 아무리 해도 답이 나오지 않는겁니다. 계속해서 고민하고 또 고민해봐도 답이 나오질 않아서 결국 챗지피티에게 질문했습니다. \> php에서 들어온 post 요청 값을 초기화하는 함수 같은게 있나? 뭐 그런건 없지만 대충 직접 만들어서 수행할 수는 있다고 답을 해주더군요. ```javascript fetch("/page/posttest.php", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({data: {bbs: 'list', type: 'json'}}) }) .then(response => { return response.text(); }) .then(data => { console.log(data); console.log('데이터를 성공적으로 받았습니다.'); }) .catch((error) => { console.log('Error: ', error); }); }); ``` \> 이렇게 작성하면 /page/posttest\.php에 요청을 보내면 posttest\.php에서 echo json\_encode\($\_POST\['data'\]\); 하면 bbs와 type가 담긴 배열이 응답으로 돌아와야 하지 않은가? 이렇게 물어보니 ```php $input = file_get_contents('php://input'); ``` 이런 것을 활용하는 방법을 알려주더군요. 그래서 여튼 알고보니 $\_POST로 받으려면 json 타입이 아니라 다른 타입으로 요청을 날려야 하고... 그게 여러모로 좀 더 번잡하길래 그냥 순순히 알려주는 대로 하니 일단 json 응답이 잘 오긴 오더군요.. 하지만 이런 방식으로 페이징과 다른 POST 요청을 구분하는 것은 좀 아니란 생각이 들더라고요. ## 3\. POST 양식 말고 좀 더 뭔가 사전에 파악하는 것이 좋을 것 같다\. 그래서 생각한 것이 헤더에 저만의 선언을 넣어서 요청을 날리는 방법이었고 이것은 유효했습니다. ```javascript fetch(event.target.href, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Action-Type': 'pagination' }, body: JSON.stringify({bbs: 'list', type: 'json'}) }) ``` 이렇게 요청 날릴 때 헤더에 Action-Type을 선언하고 php에서는 ```php if ($_SERVER['REQUEST_METHOD'] == "POST" && isset($_SERVER['HTTP_ACTION_TYPE'])) {... ``` 이렇게 하니, 딱 제가 원하는, 즉 헤더에 Action-Type이 선언되어 있을 때에만 원하는 동작이 실행되게끔 되었습니다. 이렇게 되니 뭐 더이상 POST 양식을 받아올 필요가 없어졌습니다만, 코드를 보니 따로 지우진 않았네요ㅎ ## 4\. head 스킨 파일을 불러오지 마\! 당연하지만, 순수한 json을 받아오려면 head 스킨, 즉 html은 받아와선 안됐습니다. 여러 방법을 고민했습니다건드리지 않고자 했기 때문에, 스킨 파일에서 html 부분은 불러오지 않도록, 조건문을 달아주었습니다. `/theme/eb4_basic/head.html.php`와 `/theme/eb4_basic/head.sub.html.php` 두 곳 모두 작업해주었습니다. ```php <?php /** * theme file : /theme/THEME_NAME/head.html.php */ if (!defined('_EYOOM_')) exit; if (!isset($_SERVER['HTTP_ACTION_TYPE'])) { ...(기존 스킨 파일 내용) } ``` 이렇게 하니, 헤더 없이 글목록 부분이 먼저 나오더라고요! 굳 ## 5\. list도 html로 불러오지 마\! 이제, list의 html 부분도 배제하기 위해서, 스킨 파일을 불러오기 전에 사용자 코드를 불러오는 점을 착안해 다시 `/eyoom/user/board/list.skin.php` 파일을 제대로 수정합니다. ```php // 페이지 새로고침 없는 목록 구현을 위한 코드 if ($_SERVER['REQUEST_METHOD'] == "POST" && isset($_SERVER['HTTP_ACTION_TYPE'])) { $postdata = json_decode(file_get_contents('php://input'), true); echo json_encode($list); exit(); } ``` 이게 끝이었습니다. 뭐.. 리스트 관련된 함수는 이미 코어 파일에서 다 선언이 되어 있기 때문에... 해당 배열만 그냥 json으로 뿌려주기만 하면 되니... 프론트쪽에 비해 해줄 것이 별로 없더군요ㅋㅋ 암튼 이렇게 하니 드디어, 리스트를 json으로 받아올 수 있게 되었습니다..ㅠㅠ ![!\[image\]](https://eyoom.net/data/editor/2407/3716934337_1720718354.02866.png) ### 6\. 프론트\.\.\. 글목록 부분 그러나 진짜 지옥같은 부분은 여기였습니다. 이 부분부터는 전부 테마의 `list.skin.html.php` 스킨 파일 안에서 이루어진 작업입니다. ### 6-1. 일단 뼈대를 잡자. 기존에 php로 글 목록 테이블에서 렌더링하던 부분을 뺄 거 다 빼고 뼈대만 남겨봤습니다. 이것도 시간 꽤 잡아먹었죠... SPA 하면 가장 중요한 개념이 무엇입니까! 가상DOM이잖아요? (아니면 한 수 가르쳐주십숑) 근데 사실 이전부터 이런 생각을 했습니다. '그냥 변수에다가 백틱 감싸서 html 코드를 안에다 작성해놓고 꺼내다 쓰면 그게 가상DOM 아닌가?ㅋㅋ' 네. 사실 이런 생각때문에 굳이 리액트..! 뭘 더 배워..! 이런 생각을 한 것 같습니다. 더 편리하게 접근할 수 있는 툴인건 맞겠죠. 사용자 수가 그걸 증명하는 것이겠고. 근데 그누보드라는 잘 만들어진 플랫폼?에 리액트를 도입하는 방법도 모르겠고,, 배워서 해야하는데 아는 건 하나도 없고. 그래서 그냥 제가 아는 선에서 하려고 한 것입니다. 저는 자바스크립트, 제이쿼리, php밖에 모르그등요. 암튼ㅋㅋ 그래서 우선은 글 목록 부분의 열을 그대로(?) 보이게 할 수 있도록 변수를 하나 만들어줬습니다. ```javascript var head = { 'num': ['글 번호', 'bl-item bl-num <?php echo $is_admin?'bl-num-checkbox':'';?>'], 'wr_subject': ['제목', 'bl-subj'], 'wr_name': ['작성자', 'bl-item bl-author'], 'ex_2': ['발주제품군', 'bl-item bl-prod'], 'ex_1': ['배송형태', 'bl-item bl-dil'], 'wr_datetime': ['날짜', 'bl-item text-gray'], 'wr_hit': ['조회수', 'bl-item bl-num text-gray'] }; ``` 처음 작성했던 형태는 이게 아닌데, 생각해보니 $list 안에 들어있는 변수명이 스킨파일 안에서 쓰인 클래스 이름하고는 다르길래, 이런식으로 작성했습니다. 그 다음에는, Fetch API를 통해 성공적으로 json을 받아오면 실행해줄 함수를 하나 만들었습니다. ```javascript function draw_list(list_json) { ... } ``` ### 6-2. 작은 단위 먼저 잡는 게 좋을 것 같다. 어떤 식으로 코드를 짜면 좋을까 생각하다가, 일단은 뼈대가 될 html 요소를 변수로 담기 위해 아래와 같이 코드를 짰습니다. 제목처럼 처음에는 작은 단위 먼저 잡는 게 좋을 것 같다 생각하고 작업하다보니... 그냥 퉁쳐서 작업하고 있더군요; ```javascript const list_item = (head, className, content, wr_id=false, author=false, cate=false, lock=false, comment=false, is_notice=false) => { const item_type = { 'num': ` ${author} ${is_notice?'공지':""}  `, 'wr_subject': (lock?``:'') + (cate?`[${cate}]`:'') + ` ${content} ` + (comment != "0" ? ` 댓글 ${comment} `:''), 'wr_name': ` ${content}`, 'etc': `${content?content:''}` } return `
${item_type[head]?item_type[head]:item_type['etc']}
`; }; const list_item_mobile = (author, datetime, params) => { return ` ${author}
${params[0]??''} | ${params[1]??''}
${datetime}
`; }; ``` 물론 처음부터 이렇게 짠 것은 아닙니다. 완성된 형태를 걍 보여드리려고요... 과정을 다 적기엔 기억도 안나고 너무 길어질 것 같아서요ㅋㅋ 굉장히 코드가 더럽습니다. 뭐 보시면 아시겠지만, item\_type['num']과 같이 변수를 가져오면 안에 있는 템플릿 리터럴이 채워져 html을 렌더링 할 수 있도록 짜놓은 것입니다. 어 굳이 이렇게 짜야 하나 싶은 생각이 지금도 듭니다만, 뭐 잘 작동은 합더군요ㅋㅋ 이런게 바로 스파게티 코드인가요,,ㅋㅋㅋㅋㅋㅋ 아 괜히 부끄럽네 우선, 기존의 글목록은 다 지워주기 위해 아래와 같이 목록 html을 날려버립니다. ```javascript // 먼저 설정한 head의 수 만큼 열을 그려야 한다. 하지만 페이지를 전환하는 과정에 있어서 head를 다시 그릴 필요가 있을까? // for (let head_item of heads) { // $('.bl-wrap').append(list_head(head_item, head[head_item])); // } // 기존의 리스트는 PHP로 for 문 돌리면서 받아 오는 형태이다 보니 효율성을 위해 일반 목록과 모바일 목록을 같이 렌더링 하도록 구성된 것 같다. // 이러한 방식을 계속 유지하는 쪽으로 가려면... 딱히 문제는 없을 것 같다. // 기존의 리스트를 삭제하자. (기본과 모바일) $('.bl-list').remove(); $('.bl-mobile').remove(); ``` 생각을 정리하면서 해야 하는데 그 부분도 보시라고 쓸 데 없지만 주석까지 남겨서 올려봅니다ㅋㅋㅋ 저게 왜 저렇게 된거냐면,,, 기존 이윰빌더 베이직 테마의 글목록은 바깥쪽에 있는 .bl-list 클래스를 가진 `
` 안에 .bl-list라는 `
`와 .bl-mobile을 가진 `
`이 항목 당 번갈아가면서 나오는 구조더라고요. 예를 들어 ```html
첫번째 글
모바일에서만 보일 어떤 요소
두번째 글
모바일에서만 보일 어떤 요소
세번째 글
모바일에서만 보일 어떤 요소
``` 왜 이렇게? 하나 싶어서 바꿀까 했는데 그러면 너무 큰 공사가 될 것 같아서 그냥 이 틀을 유지하여 작업하기로 했습니다. ### 6-3. 받아온 json의 키 개수만큼 돌아가면서 한줄 한줄 렌더링! 우선 한 페이지의 글 목록만큼 for문을 돌려서 차곡차곡 렌더링하기 위해 아래와 같이 코드를 짰습니다. ```javascript // 다음으로 돌아가면서 내용을 그려야 한다. for (i = 0; i < list_json.length; i++) { //list_json의 개수 만큼 반복 (목록의 개수) // 각 행의 뼈대를 먼저 그려야 한다. // 그리고 그 안에 각 열을 넣어야 한다. // 아니면 우선 각 열을 만들고 그것을 행에 조립해야 할까? $('.bl-wrap').append(`
`); ``` 먼저 이렇게 bl-wrap이라는 div 안에 비어있는 div를 각각 만들구요 ```javascript for (let head_item of heads) { $('.bl-list').eq(i).append( list_item(head_item, head[head_item][1], list_json[i][head_item], list_json[i]['wr_id'], list_json[i]['wr_name'], list_json[i]['ca_name'], list_json[i]['icon_secret'], list_json[i]['wr_comment'], list_json[i]['is_notice'] ); ); } $('.bl-list').eq(i).append( `` ); $('.bl-mobile').eq(i).append( list_item_mobile( list_json[i]['wr_name'], list_json[i]['wr_datetime'], [list_json[i]['ex_1'], list_json[i]['ex_2']] ) ); } ``` 실제로는 이렇게 다 엔터 치지는 않았습니다. 보기 좋을 것 같아서 이렇게,, 그리고 나서, 이제 위에 보았던 head의 변수에 담긴 내용을 참조해서 html에 뿌려줄 수 있도록 작업했습니다. ...왜 굳이 이렇게 했냐고 물어보신다면,,, 무지함에서 나온 결과라고밖에는 못하겠지요ㅋㅋ 암튼 이렇게 하여 새로고침 없이 글 목록을 전환하는 데에는 성공하게 되었습니다!! 아래는 글 목록 갱신을 위한 전체 자바스크립트 코드 되겠습니다. ```javascript var head = { 'num': ['글 번호', 'bl-item bl-num <?php echo $is_admin?'bl-num-checkbox':'';?>'], 'wr_subject': ['제목', 'bl-subj'], 'wr_name': ['작성자', 'bl-item bl-author'], 'ex_2': ['발주제품군', 'bl-item bl-prod'], 'ex_1': ['배송형태', 'bl-item bl-dil'], 'wr_datetime': ['날짜', 'bl-item text-gray'], 'wr_hit': ['조회수', 'bl-item bl-num text-gray'] }; function draw_list (list_json) { const heads = Object.keys(head); const list_head = (head, content) => { return `
${content}
`; }; const list_item = (head, className, content, wr_id=false, author=false, cate=false, lock=false, comment=false, is_notice=false) => { const item_type = { 'num': ` ${author} ${is_notice?'공지':""}  `, 'wr_subject': (lock?``:'') + (cate?`[${cate}]`:'') + ` ${content} ` + (comment != "0" ? ` 댓글 ${comment} `:''), 'wr_name': ` ${content}`, 'etc': `${content?content:''}` } return `
${item_type[head]?item_type[head]:item_type['etc']}
`; }; const list_item_mobile = (author, datetime, params) => { return ` ${author}
${params[0]??''} | ${params[1]??''}
${datetime}
`; }; // 먼저 설정한 head의 수 만큼 열을 그려야 한다. 하지만 페이지를 전환하는 과정에 있어서 head를 다시 그릴 필요가 있을까? // for (let head_item of heads) { // $('.bl-wrap').append(list_head(head_item, head[head_item])); // } // 기존의 리스트는 PHP로 for 문 돌리면서 받아 오는 형태이다 보니 효율성을 위해 일반 목록과 모바일 목록을 같이 렌더링 하도록 구성된 것 같다. // 이러한 방식을 계속 유지하는 쪽으로 가려면... 딱히 문제는 없을 것 같다. // 기존의 리스트를 삭제하자. (기본과 모바일) $('.bl-list').remove(); $('.bl-mobile').remove(); // 다음으로 돌아가면서 내용을 그려야 한다. for (i = 0; i < list_json.length; i++) { //list_json의 개수 만큼 반복 (목록의 개수) // 각 행의 뼈대를 먼저 그려야 한다. // 그리고 그 안에 각 열을 넣어야 한다. // 아니면 우선 각 열을 만들고 그것을 행에 조립해야 할까? $('.bl-wrap').append(`
`); for (let head_item of heads) { $('.bl-list').eq(i).append(list_item(head_item, head[head_item][1], list_json[i][head_item], list_json[i]['wr_id'], list_json[i]['wr_name'], list_json[i]['ca_name'], list_json[i]['icon_secret'], list_json[i]['wr_comment'], list_json[i]['is_notice'])); } $('.bl-list').eq(i).append(``); $('.bl-mobile').eq(i).append(list_item_mobile(list_json[i]['wr_name'], list_json[i]['wr_datetime'], [list_json[i]['ex_1'], list_json[i]['ex_2']])); } //애니메이션은 못참음 $('.bl-list.introfade.disabled').each((index,item)=>{ var that = item; setTimeout(()=>{ $(that).removeClass('disabled'); setTimeout(()=>{ $(that).removeClass('introfade'); },50*index); },50*index) }); document.querySelector('.board-list').scrollIntoView(true); } ``` ## 7\. 프론트\.\.\. 글 목록 바뀐 것까진 좋다\. 근데 현재 페이지가 어딘지 갱신이 안되네? 예. 지금까지는 글 목록만 뿌려주는 부분이었구요, 페이지가 바뀌었으면 응당 현재 페이지 번호가 달라진 것을 표시해야 하는데,,, 와 이것때문에 거진 5시간 쓴 것 같습니다. 일단 기본적으로 아래와 같이 짜서 페이지 번호(링크)를 누르면 글 목록이 바뀌게끔 하는 데 성공했습니다. ```javascript fetch(target.href, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Action-Type': 'pagination' }, body: JSON.stringify({data: {bbs: 'list', type: 'json'}}) }) .then(response => { return response.json(); }) .then(data => { draw_list((data)) ;}) .catch((error) => { console.log('Error: ', error); alert('목록을 받아오지 못했습니다.'); }); ``` 여기서... `.then(data => {})` 부분에서 `draw_list(data)`를 실행하고 난 뒤, 페이지 번호가 바뀌도록 해야 할 필요가 있었습니다. 일단 어... 어디부터 설명해야 할지 모르겠어서 걍 전체 코드 올릴게요ㅋㅋㅋㅋ ```javascript function generate_pagination () { $('.eb-pagination a.active').off('click'); $('.eb-pagination a:not(.active)').off('click'); $('.eb-pagination a.active').on('click', (e)=>{e.preventDefault();}); $('.eb-pagination a:not(.active)').on('click', (event)=>{ const target = event.target.href?event.target:$(event.target).parent()[0]; // console.log(target); const currURL = new URL(window.location.href); const currPage = currURL.searchParams.get('page')??'1'; const hrefURL = new URL(target.href); const hrefPageVal = Number(hrefURL.searchParams.get('page')); // console.log(hrefPageVal); event.preventDefault(); if (Number(currPage) == hrefPageVal) { alert('목록의 처음이거나 끝입니다.'); return false; } if (target.href) fetch(target.href, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Action-Type': 'pagination' }, body: JSON.stringify({data: {bbs: 'list', type: 'json'}}) }) .then(response => { return response.json(); }) .then(data => { // console.log(data); draw_list((data)); if ($(target).hasClass('next') && $('.eb-pagination a').eq($('.eb-pagination a').length - 3).hasClass('active')) { $('.eb-pagination a.active').removeClass('active'); $('.eb-pagination a:not(.start,.end)').each((index, item)=>{ const newPages = new URL(item.href); const newPage = hrefPageVal + index - 1; newPages.searchParams.set('page', newPage); item.href = newPages.href; if (!($(item).hasClass('prev') || $(item).hasClass('next'))) item.innerText = newPage; }); $('.eb-pagination a').eq(2).addClass('active'); } else if ($(target).hasClass('prev') && $('.eb-pagination a').eq(2).hasClass('active')) { $('.eb-pagination a.active').removeClass('active'); $('.eb-pagination a:not(.start,.end)').each((index, item)=>{ const newPages = new URL(item.href); const newPage = hrefPageVal - $('.eb-pagination a:not(.start,.end)').length + index + 2; newPages.searchParams.set('page', newPage); item.href = newPages.href; if (!($(item).hasClass('prev') || $(item).hasClass('next'))) item.innerText = newPage; }); $('.eb-pagination a').eq($('.eb-pagination a:not(.start,.end)').length - 1).addClass('active'); } else { if (hrefPageVal < Number(currPage)) { if ($(target).hasClass('prev')) { $('.eb-pagination li:has(.active)').prev().find('a').addClass('active'); $('.eb-pagination li:has(.active)').eq(1).find('a').removeClass('active'); } else { $(target).addClass('active'); $('.eb-pagination li:has(.active)').eq(1).find('.active').removeClass('active'); } } else { if ($(target).hasClass('next')) { $('.eb-pagination li:has(.active)').next().find('a').addClass('active'); $('.eb-pagination li:has(.active)').eq(0).find('a').removeClass('active'); } else { $(target).addClass('active'); $('.eb-pagination li:has(.active)').eq(0).find('.active').removeClass('active'); } } } currURL.searchParams.set('page', hrefPageVal); window.history.pushState({path: currURL.href}, '', currURL.href); const nextURL = new URL(currURL.href); const prevURL = new URL(currURL.href); const nextNum = Number(currURL.searchParams.get('page')) + 1; const nextPage = nextURL.searchParams.set('page', nextNum); const prevNum = currURL.searchParams.get('page')=="1"?"1":Number(currURL.searchParams.get('page')) - 1; const prevPage = prevURL.searchParams.set('page', prevNum); $('.eb-pagination a.next').attr('href', nextURL.href); $('.eb-pagination a.prev').attr('href', prevURL.href); generate_pagination(); }) .catch((error) => { console.log('Error: ', error); alert('목록을 받아오지 못했습니다.'); }); }); } generate_pagination(); ``` 진짜,,, 별짓 다해놨습니다. 처음에는 그냥 버튼 누르면 해당 링크로 POST 요청하는 것까지는 좋았습니다. 근데 그 버튼을 눌렀으면 해당 페이지 버튼이 active 되어야 하기 때문에, ```javascript $('.eb-pagination a.active').removeClass('active'); $(event.target).addClass('active'); ``` 이렇게 모든 a 태그의 active 클래스를 제거하고, 클릭한 버튼에 active 클래스를 넣어주는 것으로 일단은... 1,2,3,4,5,6,7,8,9,10 버튼 눌렀을 때 active 전환되는 것은 해결했습니다. 아니 해결한 줄 알았습니다. 그런데 페이지 이동을 이걸로만 하나요ㅋㅋ 다음으로, 이전으로 버튼도 있지 않습니까? 그 버튼들은 active 되면 안되기 때문에, 다음으로 버튼의 경우 ```javascript if ($(target).hasClass('next') && $('.eb-pagination a').eq($('.eb-pagination a').length - 3).hasClass('active')) { ... } ``` 이런식으로, 우선 클릭한 버튼이 next 클래스를 가졌는지 조건을 달았습니다. 그리고, 단순하게 생각해서, 다음으로 버튼을 눌렀으니 내 뒤에 있는 a태그에 active 클래스를 달아주고 그러면 active가 두개가 되니, 두개중 첫번째 a태그(다음으로를 누르기 전에 active였던)의 active 클래스를 삭제해주었습니다. ```javascript $('.eb-pagination li:has(.active)').next().find('a').addClass('active'); $('.eb-pagination li:has(.active)').eq(0).find('a').removeClass('active'); ``` 이전으로 버튼도 마찬가지로... 아래처럼 작동되게 했습니다. ```javascript $('.eb-pagination li:has(.active)').prev().find('a').addClass('active'); $('.eb-pagination li:has(.active)').eq(1).find('a').removeClass('active'); ``` 자 이제, 1\~10까지는 대충 잘 작동하는 것 같습니다. 다음으로, 이전으로 버튼을 누를 때에는요... 근데, 페이지가 11페이지까지 있다면? 10페이지에서 다음으로를 눌러 11페이지를 불러와야했습니다. 그 후 1\~10까지의 버튼은 새로운 페이지 번호를 달고 11\~20을 위한 버튼이 되어야 했습니다. 제가 해결한 방법은, 우선 처음으로, 이전으로, 다음으로, 마지막으로 버튼은 항상 고정된 순서로 있는다는 점을 고려해 (1,2번째, 뒤에서 2,1번째) 아래와 같이 조건을 달았습니다. ```javascript if ($(target).hasClass('next') && $('.eb-pagination a').eq($('.eb-pagination a').length - 3).hasClass('active')) { // 마지막 숫자 버튼이 active이고, 다음으로 버튼을 눌렀을 때 (1번 케이스) } else if if ($(target).hasClass('prev') && $('.eb-pagination a').eq(2).hasClass('active')) { /// 처음 숫자 버튼이 active이고, 이전으로 버튼을 눌렀을 때 (2번 케이스) } else { // 숫자 버튼 누를 때 (3번 케이스) } ``` 1번 케이스의 경우, 마지막 숫자 버튼(예: 10번)이 눌려 있는 상태에서 다음 페이지(11페이지)를 불러와야 하므로, 페이지 번호를 11\~20까지 바뀌게끔 해야했습니다. 그리고 11번은 처음 숫자 버튼이 되므로 active 클래스는 첫번재 버튼에 달아야 했죠. 아래와 같이 해결했습니다. ```javascript if ($(target).hasClass('next') && $('.eb-pagination a').eq($('.eb-pagination a').length - 3).hasClass('active')) { $('.eb-pagination a.active').removeClass('active'); $('.eb-pagination a:not(.start,.end)').each((index, item)=>{ const newPages = new URL(item.href); const newPage = hrefPageVal + index - 1; newPages.searchParams.set('page', newPage); item.href = newPages.href; if (!($(item).hasClass('prev') || $(item).hasClass('next'))) item.innerText = newPage; }); $('.eb-pagination a').eq(2).addClass('active'); } ``` 많이 더럽죠? ㅋㅋㅋ 어쩔 수 없었습니다 ㅠ 저기 newPages와 newPage 변수의 경우, 버튼을 클릭하면 처음 변수가 아래와 같이 선언되는 ```javascript const target = event.target.href?event.target:$(event.target).parent()[0]; // console.log(target); const currURL = new URL(window.location.href); const currPage = currURL.searchParams.get('page')??'1'; const hrefURL = new URL(target.href); const hrefPageVal = Number(hrefURL.searchParams.get('page')); ``` 이 부분과 관련이 있습니다. 현재 페이지의 정보(페이지 파라미터)를 파싱하고, 몇번째 페이지인지 판단하기 위함입니다. 그리고 버튼 누른 것의 페이지 번호가 몇번인지도 파악할 필요가 있어서 처음에 이렇게 선언했습니다. 암튼 여차저차. 페이지 번호 다시 다 갱신하고.. 뭐 이런 과정을 이전으로 버튼도 동일하게 거쳐 기능을 완성했습니다. ## 8\. 아직 처음으로 끝으로 버튼은 구현 못했다\.\.\. 일단 여기까진 퇴근 후 여가시간을 5시간 녹여가며 (시간 가는 줄 모르고 하긴 했어요ㅋㅋㅋㅋ) 했는데, 아직도 처음으로 끝으로 버튼은 구현을 못했습니다... 이건 생각 좀 해봐야겠어요. 처음으로 버튼은 그냥 1페이지로 이동하게끔 하면 될 것 같은데, 끝으로 버튼이 좀 애매하네요. 1. 페이지 번호를 보여주는 단위는 PC는 10개, 모바일은 5개로 동일한가? 2. 동일하면 내가 했던 페이지 갱신을 위한 노력들은 뻘짓이 되는 것이다. (그냥 숫자 값으로 하면 될 것이니.. 현재 페이지+10 또는 -10 이런 식으로) 3. 다르다면 그것을 설정하는 부분이 있는가? 관리자 페이지 가보니 없다. 근데 코어 부분 찾기는 귀찮고 너무 시간이 늦었다 4. 다르다면, 마지막 페이지 번호가 몇번째 버튼에 해당하는지는 어떻게 판단해야 하는가? 이런 문제들이 있는데... 이젠 모르겠습니다ㅋㅋㅋ ## 9\. 결론 고작 글 목록 페이지 새로고침 없이 하겠다고, 프론트쪽 코드만 오지게 늘어났습니다. 그도 그럴게... 일단 처음 페이지를 열 때에는 기존처럼 정적인 데이터를 보여줬다가 페이지 번호가 바뀌면 다 날리고 json을 활용해 다시 렌더링 하는 것인데... 뭐 어떻게 보면, 처음 읽어들이는 프론트쪽 코드가 양이 다소 많아졌을지는 몰라도, 페이지 변경 시, 전체 HTML이 아닌 데이터에 해당하는 json만 불러오므로 전송하는 데이터 양 자체는 좀 이득을 보긴 할 것 같네요. 어떻게 보면 상당한 뻘짓을 한 셈인 것이고, 어떻게 보면 공부를 한 셈인 것이고, 어떻게 보면... 쓸 데 없는 공부를 한 셈인 것,,이네요.. 이제 이것을 경험삼아, 글목록 뿐 아니라, 글 읽기, 글 쓰기까지 다 새로고침 없이 불러오게 할 수 있도록 해봐야겠습니다!!! P.S) 지금 다시 생각해보니, 그냥 list.php 부분을 헤더, 푸터 부분 다 똑 떼어놓고 여기에 해당하는 html 부분만 받아오게 한 다음에 html을 통째로 갈아끼워도 비슷한 효과를 보는 것이 아닌가.. 하는 생각이 드네요ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 물론 다소 데이터 전송량이 json에 비해서는 늘어나겠지만...? 상당한 뻘짓을 한 것이 맞는 것 같습니다ㅠ
2
로그인 후 추천 또는 비추천하실 수 있습니다.
추천한 회원 보기
포인트 267
경험치 107
[레벨 1] - 진행률 54%
가입일
2022-06-16 09:51:35
서명
미입력
자기소개
미입력

댓글목록1

NPIO님의 댓글

profile_image
우와!~ 정말 멋진 시도 같습니다.
일단 가볍게 쭈욱~ 살펴 보았는데요.
나중에 시간을 두고 한땀 한땀 노력의 자취를 감상해 보도록 하겠습니다.
무엇이든 실행하지 않고 생각에 머물면 어떤 결과도 없을텐데요.
이렇게 직접 시도해 보시고 결과도 만드셨으니 뿌듯 하시겠습니다.
힘껏 응원합니다. 화이팅!~~

축하합니다. 첫댓글 포인트 4포인트를 획득하였습니다.

자유게시판 이용 안내

자유게시판에 광고/홍보 등의 글은 바로 삭제 처리가 되며, 특정 불법 광고성글(도박, 음란물 등)의 경우 고지없이 바로 회원 강제 탈퇴 처리가 되오니 참고 해 주시기 바랍니다.
질문글은 꼭 질문과 답변 게시판 또는 1:1문의 게시판을 이용하여 주시기 바랍니다.

전체 1 건 - 1 페이지
사이트 내 전체검색