본문 바로가기
실습/Nexacro + Spring Boot

[Nexacro + Spring Boot] 넥사크로(2/3) - 데이터 주고받기

by 이민우 2025. 4. 15.
728x90
반응형

이전 포스팅에서 프로젝트 생성이 완료되었으니, 이제 직접 코딩을 해보자.

 

 

Spring Boot Config 파일 작성

 

Nexacro라는 3rd Party 모듈을 추가했으므로, 당연하게도 Spring Boot에 설정을 추가해야 한다.

 

코드는 아래 블로그를 참고했다.

https://devhan.tistory.com/116?category=1002345

 

[Nexacro] 넥사크로 N + SpringBoot 연동하기 2 - 프로젝트 설정 및 연동

Uiadapter 넥사크로의 Uiadapter를 사용하려고 gradle을 통해 라이브러리를 다운하려 했지만 아직 자사의 Nexus 서버가 불안정해서 Gradle을 통한 자동 다운은 못한다고 한다.. - 20220509 기준 그래서 플레이

devhan.tistory.com

 

설정은 두 개 설정 클래스에서 진행되며, 코드는 아래와 같다. 설명은 주석으로 갈음한다.

 

WebAppConfig.java

package com.example.nexacrodemo;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class WebAppConfig implements WebMvcConfigurer {

    /**
     * CORS(Cross-Origin Resource Sharing) 설정
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*") // 또는 "http://localhost:8080"
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*");
    }

    /**
     * JSP ViewResolver 설정
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/jsp", ".jsp");
    }

    /**
     * 루트 URL("/") 접속 시 index.html로 포워딩 처리
     * 여기서 말하는 index.html은 Nexacro의 FrameBase/Frame_Work
     */
    @Override
    @Order(Ordered.HIGHEST_PRECEDENCE + 1)
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index.html");
    }

    /**
     * 정적 리소스(Resource) 핸들러 설정
     * Nexacro의 리소스 매핑
     */
    @Override
    @Order(Ordered.HIGHEST_PRECEDENCE + 2)
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/_resource_/**").addResourceLocations("classpath:/static/_resource_/");
        registry.addResourceHandler("/FrameBase/**").addResourceLocations("classpath:/static/FrameBase/");
        registry.addResourceHandler("/nexacrolib/**").addResourceLocations("classpath:/static/nexacrolib/");
        registry.addResourceHandler("/*.json").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/*.html").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/*.js").addResourceLocations("classpath:/static/");
    }

    /*
    
    // 아래부터는 메세지 소스인데, 사용하지 않을 예정이므로 배제함.
    
    // 메시지 소스 생성
    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource();

        //메세지 프로퍼티 파일의 위치와 이름을 지정
        source.setBasename("classpath:/messages/message");
        source.setDefaultEncoding("UTF-8");
        //프로퍼티 파일의 변경을 감지할 시간 간격을 지정한다.
        source.setCacheSeconds(60);
        //없는 메세지일 경우 예외를 발생시키는 대신 코드를 기본 메세지로 한다.
        source.setUseCodeAsDefaultMessage(true);
        return source;
    }

    // 변경된 언어 정보를 기억할 로케일 리졸버 생성
    // 여기서는 세션에 저장하는 방식 사용
    @Bean
    public SessionLocaleResolver localeResolver() {
        return new SessionLocaleResolver();
    }
    
    // 언어 변경을 위한 인터셉터를 생성
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang");
        return interceptor;
    }

    // 인터셉터 등록
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
    */
}

 

 

NexacroConfig.java

package com.example.nexacrodemo;

import com.nexacro.java.xapi.tx.PlatformType;
import com.nexacro.uiadapter.jakarta.core.context.ApplicationContextProvider;
import com.nexacro.uiadapter.jakarta.core.resolve.NexacroHandlerMethodReturnValueHandler;
import com.nexacro.uiadapter.jakarta.core.resolve.NexacroMappingExceptionResolver;
import com.nexacro.uiadapter.jakarta.core.resolve.NexacroMethodArgumentResolver;
import com.nexacro.uiadapter.jakarta.core.resolve.NexacroRequestMappingHandlerAdapter;
import com.nexacro.uiadapter.jakarta.core.view.NexacroFileView;
import com.nexacro.uiadapter.jakarta.core.view.NexacroView;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.util.List;

@Configuration
public class NexacroConfig extends WebAppConfig implements WebMvcRegistrations {

    /**
     * ApplicationContextProvider 빈 등록
     * - Spring의 ApplicationContext를 정적으로 접근하기 위해 사용
     * - @Lazy(false)로 설정하여 Spring Boot 실행 시 즉시 초기화되도록 함
     */
    @Bean
    @Lazy(false)
    public ApplicationContextProvider applicationContextProvider() {
        return new ApplicationContextProvider();
    }

    /**
     * RequestMappingHandlerAdapter 커스터마이징
     * - Nexacro에서 정의한 Request 처리 어댑터로 교체
     * - HTTP 요청 → 컨트롤러 매핑 처리에 사용됨
     */
    @Override
    public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
        return new NexacroRequestMappingHandlerAdapter();
    }

    /**
     * 컨트롤러의 메서드 파라미터 처리용 커스텀 ArgumentResolver 등록
     * - Nexacro에서 전달되는 데이터를 파라미터로 자동 변환
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        NexacroMethodArgumentResolver nexacroResolver = new NexacroMethodArgumentResolver();
        resolvers.add(nexacroResolver);
        super.addArgumentResolvers(resolvers);
    }

    /**
     * 컨트롤러의 리턴 값을 처리할 커스텀 ReturnValueHandler 등록
     * - NexacroView와 NexacroFileView를 사용해 응답 데이터 생성
     */
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {

        NexacroHandlerMethodReturnValueHandler returnValueHandler = new NexacroHandlerMethodReturnValueHandler();

        // 일반 View
        NexacroView nexacroView = new NexacroView();
        nexacroView.setDefaultContentType(PlatformType.CONTENT_TYPE_XML);
        nexacroView.setDefaultCharset("UTF-8");
        returnValueHandler.setView(nexacroView);

        // 파일 다운로드 처리용 View
        NexacroFileView nexacroFileView = new NexacroFileView();
        returnValueHandler.setFileView(nexacroFileView);

        handlers.add(returnValueHandler);
        super.addReturnValueHandlers(handlers);
    }

    /**
     * 예외 처리 핸들러 등록
     * - Nexacro 요청에 대한 예외를 XML 기반으로 응답하도록 설정
     * - 예외 메시지, 스택 트레이스 반환 여부 설정 가능
     */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        // 기본 View 설정
        NexacroView nexacroView = new NexacroView();
        nexacroView.setDefaultContentType(PlatformType.CONTENT_TYPE_XML);
        nexacroView.setDefaultCharset("UTF-8");

        // Nexacro 전용 예외 처리기
        NexacroMappingExceptionResolver nexacroException = new NexacroMappingExceptionResolver();
        nexacroException.setView(nexacroView);
        nexacroException.setShouldLogStackTrace(true);
        nexacroException.setShouldSendStackTrace(true);
        nexacroException.setOrder(1);
        
        // 아래는 메시지라서 WebAppConfig에서 뺐으므로 같이 제외
        //nexacroException.setDefaultErrorMsg("fail.common.msg");
        //nexacroException.setMessageSource(messageSource());

        // 예외 처리기에 등록
        resolvers.add(nexacroException);

        super.configureHandlerExceptionResolvers(resolvers);
    }
}

 

이제 Sping Boot를 Run해서 에러는 없는지 한 번 확인해보자.

 

 

 

 

 

화면 생성

 

테스트를 위해 아래와 같은 웹 페이지를 개발할 것이다.

좌 : 1번화면 / 우: 2번화면

 

 

그리고 위의 1번화면/2번화면 이동하는 메뉴와 아래의 페이징은 별도의 화면으로 분리해 재사용 가능하도록 할 예정이다.

 

고로 필요한 프레임은 총 네 개이다.

  1. 1번 화면 프레임
  2. 2번 화면 프레임
  3. 화면 이동용 프레임
  4. 페이징 프레임

Nexacro 프로젝트 내 FrameBase를 들어가보면 기본적으로 Form_Work가 생성되어 있을 것이다.

 

Form_Work는 1번 화면으로 사용하도록 하고, 나머지 세 개의 프레임을 만들어준다.

참고로 FrameBase에 우클릭하고 파일 추가는 안되고, 위의 파일 추가를 통해 만들어줘야 한다.

(이것 때문에 처음에 한 Form_Work 안에서 모든 화면을 다 만들어야 하는 줄 알고 있었다.)

 

 

어쩄든 위 버튼을 눌러 이렇게 세 개의 프레임을 더 만들어준다.

 

우선 화면간 이동을 위해 Header_Form을 만들어보자.

 

Header_Form 의 Design으로 들어가 상단에서 메뉴를 클릭한 후 화면을 한 번 클릭해 메뉴를 만든다.

 

메뉴 크기를 조정한 후 Dataset을 누르고 다시 화면을 한 번 눌러 데이터셋을 생성한다.


그러면 아래와 같이 하단의 Invisible Object에 Dataset00이 생성될 것이다.

 

우클릭 후 Rename으로 이름을 menuDs로 바꾸고, 더블 클릭하면 우측에 Dataset Editor가 뜰 것이다. 그 안에서 Column과 Row를 아래와 같이 입력한다.

  • captioncolumn : 화면에 보일 이름
  • idcolumn : pk
  • levelcolumn : 화면 레벨 (0일 경우 대분류, 1일 경우 대분류 안의 소분류)
  • userdatacolumn : 사용자 데이터로, 여기서는 이동할 프레임의 이름 입력

참고로 Menu에서는 사용되는 Dataset 양식이 정해져 있다.

위 menuDs에서 생성한 데이터셋은 그 중 당장 필요한 일부분만 선언해서 사용한 것임을 명심하자.

 

완료되었다면 menuDs를 드래그해서 끌어와 Menu에 놓자.

 

그리고 뜨는 창의 두 번째인 Bind Innerdataset을 선택하자.

말했다시피 원래 사용되는 데이터셋이 아니라 임의로 일부분만을 선언해서 사용했기에 바인딩을 해줘야만 하기 떄문이다.

 

Bind Innerdataset을 누르면 뜨는 창에 각 컬럼을 바인딩해준다.

 

그리고 OK를 누르면 다음과 같이 메뉴창이 바뀌었음을 확인할 수 있다.

 

이제 해당 프레임은 div로 다른 프레임 안에 들어갈 예정이므로, 전체 창의 크기를 조정한다.

나는 310*60으로 맞췄다.

 

이제 만들어진 메뉴를 더블클릭하면 Source 화면으로 넘어가며 onclick이 자동으로 생성되어 있다.

그 안에 다음 코드를 입력하자.

this.Menu00_onmenuclick = function(obj:nexacro.Menu,e:nexacro.MenuClickEventInfo)
{
	var target = 'FrameBase::' + e.userdata + '.xfdl';
	
    // div내에 들어가는 프레임 내 Scipt이기 때문에 아래와 같이 사용
    // 만약 div내 들어가는 프레임이 아닌 곳의 Script라면 this.go 사용
	this.getOwnerFrame().form.go(target);
};

 

위에서 this.getOwnerFrame().form.go 로 선언을 했는데, 이는 현재 메인 프레임은 Form_Work, Second_Form이고 Header_Form은 해당 프레임 내에 div로 들어가는 프레임이기 때문이다.

 

만약 Form_Work 내 Script에서 페이지 이동을 할 경우 this.go 함수를 사용하면 된다. 그런데 div안의 프레임에서 이 함수를 사용하면 전체 페이지가 아니라 div만 그 페이지로 변경이 된다.

 

Header_Form이 완료되었으니 이제 메인 프레임 안에서 사용해보자.

Form_Work 안에 div를 생성한다.

 

Div의 크기를 Header_Form의 크기인 310*60으로 맞춘 후, Properties에서 url 속성을 찾는다.

 

url을 눌러보면 사전에 생성한 Frame들이 있을 것이다. 그 중 Header_Form을 선택한다.

 

그러면 이와 같이 div 내 내용에 Header_Form이 들어갈 것이다.

 

동일한 작업을 Second_Form에도 반복한다. 그리고 Second_Form임을 확인하기 위해 text를 하나 추가한다.

static을 추가한 후 F2를 눌러 내용을 수정한다.

 

모든 프레임을 저장한 후 이제 Spring Boot로 돌아가 프로젝트를 실행시키고 localhost:8080로 들어가보자.

 

그리고 헤더의 버튼들을 눌러보며 화면이 잘 전환되는지 확인해보자.

 

잘 실행된다면 2번화면 및 header는 끝난 것이다

 

그러면 이제 1번화면을 마저 제작해보자.

 

우선 백엔드 코드를 짜줄 것이다.

모든 로직은 Controller안에서 끝나게 짤 것이다.

 

ApiController.java

package com.example.nexacrodemo;

import jakarta.annotation.PostConstruct;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

@RequestMapping("/api")
@RestController
public class ApiController {

    private static AtomicInteger cnt = new AtomicInteger(0);
    List<Map<String, Object>> userList = new ArrayList<>();

    /**
     * 초기 데이터 세팅
     */
    @PostConstruct
    public void init() {

        Map<String, Object> user1 = new HashMap<>();
        Map<String, Object> user2 = new HashMap<>();

        user1.put("idx", cnt.incrementAndGet());
        user2.put("idx", cnt.incrementAndGet());

        user1.put("name", "kim");
        user2.put("name", "lee");

        user1.put("age", 20);
        user2.put("age", 30);

        userList.add(user1);
        userList.add(user2);
    }

    /**
     * 검색
     * 페이지에 따라 OFFSET, LIMIT 설정
     * NAME이 존재할 경우 해당 NAME이 포함된 사용자만 검색
     *
     * @param page
     * @param name
     * @return
     */
    @GetMapping
    public /*NexacroResult Map<String, Object>*/Map<String, Object> infos(@RequestParam(name="page", required = true) int page, @RequestParam(name="name", required = false) String name) {
        /*
        NexacroResult rslt = new NexacroResult();
        rslt.addDataSet("userDs", userList);

        return rslt;
        */

        Map<String, Object> rslt = new HashMap<>();
        rslt.put("page", page);

        if(name == null || "".equals(name) || "null".equals(name)) {
            rslt.put("userDs", userList.stream().skip((page-1) * 10).limit(10).toList());
            int maxPage = userList.size()/10;
            if(userList.size()%10 != 0) {
                // 11개 > 2페이지
                // 21개 > 3페이지
                maxPage++;
            }
            rslt.put("maxPage", maxPage);
        }
        else {
            List<Map<String, Object>> subList = userList.stream().filter(map -> map.get("name").toString().contains(name)).toList();
            rslt.put("userDs", subList.stream().skip((page-1) * 10).limit(10).toList());
            int maxPage = subList.size()/10;
            if(subList.size()%10 != 0) {
                // 11개 > 2페이지
                // 21개 > 3페이지
                maxPage++;
            }
            rslt.put("maxPage", maxPage);
        }

        return rslt;
    }

    /**
     * 신규 유저 추가
     * @param data
     */
    @PostMapping
    public void insert(@RequestBody Map<String, Object> data) {

        Map<String, Object> newUser = new HashMap<>();
        newUser.put("idx", cnt.incrementAndGet());
        newUser.put("name", data.get("name"));
        newUser.put("age", Integer.parseInt(data.get("age").toString()));

        userList.add(newUser);
    }

    /**
     * 해당 idx의 유저 수정
     * @param idx
     * @param data
     */
    @PutMapping("/{idx}")
    public void update(@PathVariable int idx, @RequestBody Map<String, Object> data) {

        for(int i=0; i<userList.size(); i++) {
            Map<String, Object> user = userList.get(i);
            if((Integer) user.get("idx") == idx) {
                user.put("name", data.get("name").toString());
                user.put("age", Integer.parseInt(data.get("age").toString()));
                break;
            }
        }
        

    }

    /**
     * 해당 idx의 유저 삭제
     * @param idx
     */
    @DeleteMapping("/{idx}")
    public void delete(@PathVariable int idx) {

        for(int i=0; i<userList.size(); i++) {
            Map<String, Object> user = userList.get(i);
            if((Integer) user.get("idx") == idx) {
                userList.remove(i);
                break;
            }
        }

    }



}

 

 

위 코드에는 사실 하자가 하나 있다.

 

Nexacro에서 데이터를 주고 받을 때 Front 단에서는 transaction 함수를 통해서 Request를 요청해야 한다.

그리고 백엔드 단에서는 NexacroResult 데이터를 return해줘야 한다.

*파일의 경우에는 NexacroFileResult

 

그런데 현재 이 API는 Map을 return하고 있다.

 

이유는 NexacroResult를 return할 수가 없기 때문이다.

  • @RestController 혹은 @ResponseBody사용 시 Json 에러가 남
  • @Controller만 사용 시 API를 찾을 수가 없음.

이유나 해결법을 도저히 알 수가 없다.

 

그래서 원래 넥사크로의 방식인 NexacroResult <-> transactional이 아니라 Map<String, Object> <-> XMLHttpRequest 통신을 사용하고자 한다.

 

위 현상에 대해서는 나중에 자세하게 알게될 경우 다시 공유하도록 하겠다.

*혹은 아시는 분 댓글좀 부탁드립니다.. ㅠ

 

어쨌든 API는 완성되었으니, 이제 화면을 개발해보자.

 

화면은 퍼블리싱된 것과 마찬가지로 다음과 같이 구성했다.

그리고 우측 Properties의 속성을 통해 다음 작업을 수행하자.

  1. 이름 입력 란 (좌측 상단 input)
    1. id를 inputName으로 변경
    2. displaynulltext에 "이름 입력"
  2. 나이 입력 란 (우측 상단 input)
    1. id를 inputAge로 변경
    2. dsplaynulltext에 "나이 입력"
    3. inputtype을 number로 변경
  3. 이름 입력란 (하단 input)
    1. id를 searchInputName으로 변경
    2. displaynulltext에 "검색할 이름 입력"
  4. 등록 버튼 (상단 버튼)
    1. id를 regBtn으로 변경
    2. text에 "등록" 입력
  5. 검색 버튼 (하단 버튼)
    1. id를 srchBtn으로 변경
    2. text에 "검색" 입력

 

여기까지 했다면 화면이 아래와 같이 변경되어 있을 것이다.

 

이제 다음으로 데이터셋을 추가한다.

방법은 위 Header_Form에서와 동일하다.

 

1. 상단에서 데이터셋을 찾아 클릭 후 화면 한 번 클릭

 

2. 생성된 데이터 셋 우클릭으로 이름 변경 후 더블클릭 (userDs)

 

3. 백엔드에서 프론트엔드로 넘겨줄 데이터에 대한 양식 지정 (로우는 백엔드에서 받아올 것이므로 컬럼만)

 

4. 수정된 데이터셋을 드래그를 통해 그리드에 할당

 

 

그런데 퍼블리싱과 지금 화면은 다른 점이 있다.

퍼블리싱에는 grid에 삭제와 수정이 존재한다.

 

다만 데이터셋대로 grid 내 컬럼이 배정되기에 현재 grid 안에는 삭제와 수정 부분이 존재하지 않는다.

 

데이터셋에 삭제, 수정 컬럼이 있을 수는 없으므로, 이 부분은 grid 속성으로 수동 추가해야한다. grid를 더블클릭해서 상세 설정 창으로 이동하자자.

 

들어온 김에 idx, name, age를 한 번씩 누르고 text 속성을 한글로 바꿔준다.

 

그리고 상단의 addColumn을 통해 삭제와 수정 컬럼을 추가해준다.

 

마찬가지로 text 속성을 조작해 각각 "삭제" 와 "수정"을 입력한다.

 

그리고 해당하는 두 개의 컬럼은 항상 버튼으로 차있어야 할 것이다.

그러므로 아래 세 개 속성을 다음과 같이 바꿔준다.

  • displaytype > buttoncontrol
  • edittype > button
  • text > 삭제 혹은 수정

 

마지막으로 각 col을 눌러서 size를 조정해 저 빨간 점선에 맞게 조정한다.

 

그리고 OK를 누르면 아래와 같이 grid가 잘 적용되었음을 확인할 수 있다.

 

 

이제 데이터를 주고받을 수 있도록 Script를 작성한다.

 

참고로 나눠서 작성을 했으나, 전부 Form_Work 내 Script에 들어가는 코드들이다.

 

우선 조회를 먼저 만들어보자.

var pageNum = 1;

/*
	null 체크 함수 (공통)
*/
this.isNull = function(val) {
	return (val === undefined || val === null || val.toString().trim() === "");
}

/*
	데이터 로딩 함수
*/
this.loadUserGrid = function(page)
{
	if(page == null || typeof page === 'undefined' || typeof page === 'object' || page === 0) {
		page = 1;
	}
	// 현재 페이지 저장
	pageNum = page;
	
	var reqUrl = "/api";
	reqUrl = reqUrl + "?page=" + page;
	
	// search하는 항목이 있으면 추가
	var srchName = this.searchInputName.value;
	if(srchName != null && typeof srchName !== 'undefined' && srchName !== "") {
		reqUrl = reqUrl + "&name=" + srchName;
	}
	
	// GET 요청
	var req = new XMLHttpRequest();
	req.open("GET", reqUrl);
	req.setRequestHeader("Content-Type", "application/json");

	var that = this;
	req.onreadystatechange = function () {
		if (req.readyState === 4 && req.status === 200) {
			// 200 OK일 경우에만
			var response = JSON.parse(req.responseText);
			
			that.userDs.clearData();

			for (var i = 0; i < response.userDs.length; i++) {
				var row = that.userDs.addRow();
				that.userDs.setColumn(row, "idx", response.userDs[i].idx);
				that.userDs.setColumn(row, "name", response.userDs[i].name);
				that.userDs.setColumn(row, "age", response.userDs[i].age);
			}
		}
	};
	req.send();
	
/*

	this.transaction(
		"getUsers"				 // id
		, "/api/first/get"			 // URL
		, ""					   // input dataset 없음
		, "userDs=userDs"		  // output dataset
		, ""					   // argument
		, "fn_callback"			// callback 함수
	);
*/
};

 

백엔드에서 설명했듯, 원래 넥사크로에서 지원하는 NexacroResult <-> transaction이 나는 계속 에러가나서 기본 자바스크립트 request를 사용했다.

 

작성된 함수를 웹이 로딩되면 바로 실행될 수 있도록 Form_Work의 onload에 넣어놓자.

 

이제 한 번 실행을 해보자.

데이터를 정상적으로 불러왔음을 확인할 수 있다.

 

생각해보니 까먹은게 하나가 있다. 등록/수정을 같은 input에서 진행할 것이라서, "등록"버튼 클릭 시 두 기능 중 무엇인지 판별하기 위한 데이터가 하나 필요하므로, input 하나를 hidden으로 추가할 것이다.

 

빼먹은 것까지 다시 해놓았으므로, 이제 등록, 검색, 삭제, 수정 기능을 각각 개발해보자.

 

먼저 등록기능이다. 등록버튼을 더블클릭해서 onclick 함수를 만든 후 만들어진 빈 함수 안을 채워넣는다.

this.regBtn_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo)
{
	var name = this.inputName.value;
	var age = this.inputAge.value;

	// 유효성 검사
	if (this.isNull(name) || this.isNull(age)) {
		alert("이름과 나이를 모두 입력해주세요.");
		return;
	}
	
	var reqUrl = "/api";
	
	param = {
		"name": name,
		"age": age
	}
	
	var req = new XMLHttpRequest();

	// hidden으로 숨겨진 idx 입력란.
	// 만약 수정이면 데이터가 있고, 등록이면 없음.
	var idx = this.inputIdx.value;
	
	if(idx == null || typeof idx === 'undefined' || idx === "" || idx === "0" || idx === 0) {
		// idx가 없다는 말은 등록이라는 의미임.
		req.open("POST", reqUrl);
	}
	else {
		// idx가 있으므로 수정임.
		reqUrl = reqUrl + "/" + idx;
		param["idx"] = idx;
		req.open("PUT", reqUrl);
	}
	req.setRequestHeader("Content-Type", "application/json");

	var that = this;
	req.onloadend = function () {
		// input란 빈칸으로 변경
		that.inputIdx.set_value("");
		that.inputName.set_value("");
		that.inputAge.set_value("");
		// 데이터 갱신
		that.loadUserGrid(pageNum);
	};
	
	req.send(JSON.stringify(param));
};

 

이제 Spring Boot를 재실행해보면 데이터가 잘 등록되고, 등록 후 정상적으로 데이터도 재로딩됨을 확인할 수 있다.

 

 

다음으로 검색이다. 검색은 간단하게, onclick에 유저 로딩 함수만 넣어주면 된다.

 

다음은 수정/삭제이다.

 

수정, 삭제는 버튼에 대한 클릭이 아니라, grid에 대한 클릭을 이벤트로 삼아 동작하도록 설정한다.

그리고 단순히 grid의 onclick이 아니라, oncellclick을 통해 함수를 생성한다.

 

grid를 한 번 클릭한 후 우측 Properties에서 oncellclick을 더블클릭해서 자동으로 메서드를 생성한다.

 

그렇게 자동으로 만들어진 함수에 코드를 채워넣는다.

this.Grid00_oncellclick = function(obj:nexacro.Grid,e:nexacro.GridClickEventInfo)
{
	// 몇 번째 컬럼인지 확인
	var colIndex = e.col;

	// {idx, name, age, del, upd} 순이므로 
	// 0부터 시작하므로 3번째 컬럼이 삭제임.
    if (colIndex == 3) {
        var row = e.row;
        var idx = this.userDs.getColumn(row, "idx");

        if (confirm("정말 삭제하시겠습니까?")) {
            var reqUrl = "/api/" + idx;

            var req = new XMLHttpRequest();
            req.open("DELETE", reqUrl);
			
            req.onloadend = function () {
                this.loadUserGrid(pageNum);  // 삭제 후 다시 조회
            }.bind(this);  // this 유지

            req.send();
        }
    }
	// 4번째 열이 수정임.
	else if(colIndex == 4) {
        var row = e.row;
		
		// 등록으로 사용되는 칸들에 수정할 데이터 입력
		// hidden으로 숨겨진 inputIdx에 값이 있으므로, 등록 로직에서 수정으로 빠져서 수정될 것임.
        this.inputIdx.set_value(this.userDs.getColumn(row, "idx"));
        this.inputName.set_value(this.userDs.getColumn(row, "name"));
        this.inputAge.set_value(this.userDs.getColumn(row, "age"));
	}
};

 

 

이제 마지막으로 테스트를 해보자.

 

 

 

잘 되는 것을 확인할 수 있다.

 

최종적으로 Form_Work 내 Script 전부를 공개하며, 다음 포스팅에서는 페이징을 포스팅하겠다.

var pageNum = 1;

/*
	null 체크 함수 (공통)
*/
this.isNull = function(val) {
	return (val === undefined || val === null || val.toString().trim() === "");
}

/*
	데이터 로딩 함수
*/
this.loadUserGrid = function(page)
{
	if(page == null || typeof page === 'undefined' || typeof page === 'object' || page === 0) {
		page = 1;
	}
	// 현재 페이지 저장
	pageNum = page;
	
	var reqUrl = "/api";
	reqUrl = reqUrl + "?page=" + page;
	
	// search하는 항목이 있으면 추가
	var srchName = this.searchInputName.value;
	if(srchName != null && typeof srchName !== 'undefined' && srchName !== "") {
		reqUrl = reqUrl + "&name=" + srchName;
	}
	
	// GET 요청
	var req = new XMLHttpRequest();
	req.open("GET", reqUrl);
	req.setRequestHeader("Content-Type", "application/json");

	var that = this;
	req.onreadystatechange = function () {
		if (req.readyState === 4 && req.status === 200) {
			// 200 OK일 경우에만
			var response = JSON.parse(req.responseText);
			
			that.userDs.clearData();

			for (var i = 0; i < response.userDs.length; i++) {
				var row = that.userDs.addRow();
				that.userDs.setColumn(row, "idx", response.userDs[i].idx);
				that.userDs.setColumn(row, "name", response.userDs[i].name);
				that.userDs.setColumn(row, "age", response.userDs[i].age);
			}
		}
	};
	req.send();
	
/*

	this.transaction(
		"getUsers"				 // id
		, "/api/first/get"			 // URL
		, ""					   // input dataset 없음
		, "userDs=userDs"		  // output dataset
		, ""					   // argument
		, "fn_callback"			// callback 함수
	);
*/
};


this.regBtn_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo)
{
	var name = this.inputName.value;
	var age = this.inputAge.value;

	// 유효성 검사
	if (this.isNull(name) || this.isNull(age)) {
		alert("이름과 나이를 모두 입력해주세요.");
		return;
	}
	
	var reqUrl = "/api";
	
	param = {
		"name": name,
		"age": age
	}
	
	var req = new XMLHttpRequest();

	// hidden으로 숨겨진 idx 입력란.
	// 만약 수정이면 데이터가 있고, 등록이면 없음.
	var idx = this.inputIdx.value;
	
	if(idx == null || typeof idx === 'undefined' || idx === "" || idx === "0" || idx === 0) {
		// idx가 없다는 말은 등록이라는 의미임.
		req.open("POST", reqUrl);
	}
	else {
		// idx가 있으므로 수정임.
		reqUrl = reqUrl + "/" + idx;
		param["idx"] = idx;
		req.open("PUT", reqUrl);
	}
	req.setRequestHeader("Content-Type", "application/json");

	var that = this;
	req.onloadend = function () {
		// input란 빈칸으로 변경
		that.inputIdx.set_value("");
		that.inputName.set_value("");
		that.inputAge.set_value("");
		// 데이터 갱신
		that.loadUserGrid(pageNum);
	};
	
	req.send(JSON.stringify(param));
};

this.Grid00_oncellclick = function(obj:nexacro.Grid,e:nexacro.GridClickEventInfo)
{
	// 몇 번째 컬럼인지 확인
	var colIndex = e.col;

	// {idx, name, age, del, upd} 순이므로 
	// 0부터 시작하므로 3번째 컬럼이 삭제임.
    if (colIndex == 3) {
        var row = e.row;
        var idx = this.userDs.getColumn(row, "idx");

        if (confirm("정말 삭제하시겠습니까?")) {
            var reqUrl = "/api/" + idx;

            var req = new XMLHttpRequest();
            req.open("DELETE", reqUrl);
			
            req.onloadend = function () {
                this.loadUserGrid(pageNum);  // 삭제 후 다시 조회
            }.bind(this);  // this 유지

            req.send();
        }
    }
	// 4번째 열이 수정임.
	else if(colIndex == 4) {
        var row = e.row;
		
		// 등록으로 사용되는 칸들에 수정할 데이터 입력
		// hidden으로 숨겨진 inputIdx에 값이 있으므로, 등록 로직에서 수정으로 빠져서 수정될 것임.
        this.inputIdx.set_value(this.userDs.getColumn(row, "idx"));
        this.inputName.set_value(this.userDs.getColumn(row, "name"));
        this.inputAge.set_value(this.userDs.getColumn(row, "age"));
	}
};
728x90
반응형