Scenario


윈도우에서 BitNami에서 제공하는 패키지로 RedMine을 설치했는데, DB를 MySQL 에서 MariaDB로 변경하고 싶었다.



Solution


1. 레드마인 메인 서버

 -bitnami설치(설치는 설명없음)

 -DB Migtion (처음 설치 시 필요없는 작업)


2. 레드마인 DB 서버

 -mysql 설치 (설치는 설명없음)

 -DB 복원(처음 설치 시 필요없는 작업)


3.mainserver 에서 DB연동

DB를 연결시켜주는 부분

- C:\Bitnami\redmine\apps\redmine\htdocs\config - (본인이 설치한 경로)



4.database.yml  수정

  

  adapter: 해당어댑터(bitnami는 mysql2)

  database: Redmine_DBname 예)bitnami_redmine

  host: DB_Server_IP 예)127.0.0.1 예는 이렇지만 서버 분리할 경우 mysql설치한 서버의 IP겠죠

  username: mysql_ID 예)bitnami *root로 해도 상관 없음

  password: mysql_PW

  encoding: utf8

  port: mysql_port(기본은 3306)


이후 Maria DB에도 레드마인과 관련된 기본 데이터를 넣어주어야한다.


5.    세션 생성

http://dev.mysql.com/downloads/connector/c/6.0.html#downloads 사이트에서

mysql-connector-c-noinstall-6.0.2.zip을 받아서 lib 밑에 있는 libmysql.dll을 넣어 준다.

입력 : 경로\redmine\redmine-2.3.3 -> rake generate_secret_token



6.   DB migrate 실행 (아마 여기서부터 시작하면 된다)

입력 : 경로\redmine\redmine-2.3.3 -> rake db:migrate RAILS_ENV=production




7.  기본 데이터 입력

입력 : 경로\redmine\redmine-2.3.3 -> rake redmine:load_default_data RAILS_ENV=production

(명령어 실행 중 언어선택 메시지가 나오면 ko라고 입력한다.)


8.  서버 기동

입력 : 경로\redmine\redmine-2.3.3 -> ruby script/rails server webrick -e production

 

9.  확인

http://localhost:3000/


BitNami로 설치하면 기본적으로 MySQL이 설치되니, Maria DB로 연동후 MySQL은 삭제할것


참고: http://pinkwony.tistory.com/1

Scenario


ReactNative로 타이머를 만들고 있는데,

Props에 따라 state를 변화시켜야 할때, componentWillReceiveProps(newtProps)를 써야할때가 있다.

*(이 메소드 안에서 this.setState()를 해도 추가적으로 렌더링하지 않는다.)


JavaScript 0.44 KB
  1.     componentWillReceiveProps (nextProps) {
  2.         const currentProps = this.props;
  3.         if(!currentProps.isPlaying && nextProps.isPlaying) {
  4.             const timerInterval = setInterval(() => {currentProps.addSecond();}, 1000);
  5.             this.setState({
  6.                 timerInterval
  7.             })
  8.         } else if(currentProps.isPlaying && !nextProps.isPlaying) {
  9.             clearInterval(this.state.timerInterval);
  10.         }
  11.     }


위 코드가 타이머에서 elapsedTime(지금까지 카운팅한 시간) state를 증가시키는 로직인데,


if(!currentProps.isPlaying && nextProps.isPlaying)

이런식으로 조건을 주는게 직관적이지도 않고 왠지 비효율적으로 보였다.


이 소스를 단지 

if(nextProps.isPlaying

이런식의 조건식으로 바꿀수 없는것일까? 생각해보았다.


Solution


결론부터 말하자면 componentWillReceiveProps(newtProps) 에서 setState를 해야할 경우에는 무조건 이전값과 다음값을 비교한뒤에 해야한다.


현재 props와 next props를 비교하지 않고 setState를 하면 어떻게 되는지 알아보기위해


  1.         if(currentProps.isPlaying) {
  2.             const timerInterval = setInterval(() => {currentProps.addSecond();}, 1000);
  3.             this.setState({
  4.                 timerInterval
  5.             })
  6.         }

이런식으로 소스코드를 바꿨더니, setInterval 함수가 여러번 실행되서 카운팅 속도가 1초주기가 아닌, 점점 빨라졌다.



(카운트가 빨라진 앱의 모습)



왜 그럴까?



그림 출처: https://velopert.com/1130 - velopert님


일단 Reactjs Life Cycle 부터 살펴보자면 위와 같다.


만약 Prop의 변화를 감지해서 State를 변환시켜 줄때는 componentWillReceiveProps에서 해주는게 맞다.


왜냐하면 shouldComponentUpdate 이후에는 setState를 할 수 없다.


--------------------------------------------------------

본론으로 들어가서 왜 그럴까?


Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.

출처:https://reactjs.org/docs/react-component.html


리액트 공식문서를 살펴보면 componentWillReceiveProps에 대해 위와같이 서술하고 있다.

"이 메소드는 props가 변하지 않더라도 Call 할 수 있으므로, 현재 current value와 next value를 꼭 비교해서 써야하며, 이는 상위 component가 너의 컴포넌트를 re-render 시킨다" 고 설명한다


더 이해하기 쉽게 예를들어보자,


예를들어, 타이머가 현재 카운팅을 하고있어서 현재 isPlaying 값이 true라고 가정해보자.

현재 isPlaying이 true 면서 props가 바뀌지 않았을때 componentWillReceiveProps 메소드를 콜 한다면, if(nextProps.isPlaying) 의 조건식은 항상 참 일수 밖에없다.


그러나 if(!currentProps.isPlaying && nextProps.isPlaying) 형식으로 조건문을 걸어준다면, false && true 이므로, props가 변동이 있을때만, 이 조건문이 실행되게 되는 것 이다.


 

 current.isPlaying

nextProps.isPlaying

when props are not Changed

 true

 true

when props are Changed

 true

 false 



Scenario


개인적으로 리액트 네이티브를 공부하다가, Expo 서버 연결 없이 안드로이드 앱을 공기계에서 실행해보고싶었다.

예전 앱개발을 할땐 apk파일이 자동적으로 떨궈졌는데

ReactNative로 apk파일을 얻어보려니, 공식 DOC에도 제공을 하지 않아 이것저것 삽질(?) 을 시작해봤다.



Solution


One of the points of Expo on top of React Native is that you don't go down to android or ios code. Expo deals with those folders for you, you don't need to interact with them. Is there a reason you need those folders? if so, you will have to detach. Here's the documentation to do so


설명: Expo를 React-Native에서 공식적으로 패키징 시키면서, android나 ios 코드로 직접 접근하지 않게 해놨다.

(근데 듀토리얼은 왜 예전 기준일까?)

출처:https://stackoverflow.com/questions/44270504/react-native-ios-and-android-folders-not-present


삽질 좀 하다가 프로젝트 구조를 예전으로 되돌릴 방법을 찾아 냈다.


다음 세가지 순서로 진행된다.

1.expo 에서 expo 프레임워크를 제거 한다.

npm run eject

2.Bundle debug build 를한다 

react-native bundle --dev false --platform android --entry-file index.android.js --bundle-output ./android/app/build/intermediates/assets/debug/index.android.bundle --assets-dest ./android/app/build/intermediates/res/merged/debug

3.Create debug build를 한다

cd android
./gradlew assembleDebug

For Windowsgradlew assembleRelease

Generated apk will be located at android/app/build/outputs/apk


출처:https://stackoverflow.com/questions/35283959/build-and-install-unsigned-apk-on-device-without-the-development-server


Scenario


원래는 Expo를 이용해서 React Native 앱을 핸드폰에서 실행해서 디버깅을 했다.

그런데 방화벽 문제가 생기면서 Expo로 핸드폰에서 앱을 실행 할 수 없었다.

Expo로 App을 Create 하지않고 React Native로 공식 문서를 보며 안드로이드 AVD로 개발환경을 구축하고자 했다.

그렇게 나의 삽질은 시작되었다..



Solution


1. npm install -g create-react-native-app

 일단 RN 앱을 쉽게 만들기 위해 create-react-native-app을 설치한다


2.create-react-native-app AwesomeProject && cd AwesomeProject && npm start

여기까지면 Rn 프로젝트를 만든 것이다.


3. Android Studio 와 JDK를 설치해야한다. (JDK는 환경변수도 설정, 이건 패스)


4.안드로이드 스튜디오를 키고 Tools -> android -> AVD manager 메뉴에 들어가서 nexus 5를 설치한다

 참고

 -Tools -> Android 메뉴조차 활성화가 안돼있을때가 있다. 그럴때는

 4.1.우측 하단에 Event Log 를 누른다

 4.2.클릭하면 에러메세지가 뜨는데 그것을 클릭하면 알아서 인스톨을 해준다 (이거때문에 거의 3시간 삽질함)

 출처: https://stackoverflow.com/questions/46948322/how-to-open-avd-manager-in-android-studio-3-0-version/47143861

 


5.이제 AVD를 켠뒤에 아까 만든 폴더에 들어가서 npm run android 라고 치면 실행이 된다.

만약 안될경우에는 재부팅을 한번 해보자(여기서 1시간 정도 삽질함ㄴㅇ라ㅓㄴ)


6.Ctrl + M 으로 핫로딩 설정도 완료 할 수있다.

(Hot loading도 아주 잘되는걸 볼 수 있다.)

Array(배열)

sort()

배열 안의 원소를 정렬해 반환(알파벳,한글,숫자 모두가능)

[2,3,1].sort() => [1,2,3]

reverse()

배열의 원소 순서를 반대로 정렬해 반환

[1,2,3].reverse() => [3,2,1]

concat(인자,인자,…)

기존 배열에 인자의 내용을 추가해 반환

[1,2].concat(3) => [1,2,3]

[1,2].concat([3,[4]]) => [1,2,3[4]]

slice(시작,끝)

시작 인자부터 끝 인자 전까지 배열을 반환, 문자열의 substring과 비슷함

인자를 1개 입력시 입력값의 인덱스부터 끝까지 반환

음수 입력시 뒤에서부터 반환

[1,2,3,4].slice(1,2) => [2]

[1,2,3,4].slice(2) => [3,4]

[1,2,3,4].slice(-3,-2) => [2]

splice(시작,끝,삽입,삽입,…)

배열의 원소를 삽입, 제거할 때 사용

인자는 순서대로 삭제시작인덱스, 삭제할개수, 삽입할원소, 삽입할원소,…

아래 예제는 실행시마다 변수 a 다시 선언 후 실행한 결과

let a = [1,2,3,4,5]

a.splice(2) => [3,4,5] , a => [1,2]

a.splice(2,1) => [3] , a => [1,2,4,5]

a.splice(2,1,’a’,’b’) => [3] , a => [1,2,”a”,”b”,4,5]

push(인자,인자,…)

인자의 내용을 배열의 뒤에 추가하고 배열의 길이를 반환

let a = [1,2,3]

a.push(4,5) => 5, a => [4,5,1,2,3]

pop()

배열의 마지막 원소를 삭제 후 반환

let a = [1,2,3]

a.pop() => 3 , a => [1,2]

unshift()

배열의 앞에 추가하고 배열의 길이를 반환

let a = [1,2,3]

a.unshift(4,5) => 5, a => [1,2,3,4,5]

shift()

배열의 앞에서 제거하고 배열의 길이를 반환

let a = [1,2,3]

a.shift() => 1, a => [2,3]

toString()

배열의 모든 원소를 쉼표로 구분해 문자열로 반환

[1,2,”a”,”b”].toString() => “1,2,a,b”


ECMA2015(ES6) 배열 메서드


forEach(함수(인자))

배열을 순회하는 메서드

let sum = 0;

[1,2,3,4].forEach((x) => (sum += x)) => 10

map(함수(인자))

배열의 각 원소를 함수의 인자로 전달하고 해당 함수로 연산 후 새로운 배열로 반환

[1,2,3,4].map((x) => { return x*x }) => [1,4,9,16]

filter(함수(인자))

배열의 각 원소를 함수의 인자로 전달하고 해당 함수의 조건식을 만족하는 원소만 새로운 배열로 반환

[1,2,3,4].filter((x) => { return x<4 }) => [1,2,3]

every(함수(조건문)))

배열의 각 원소에 대해 조건식을 모두 만족하면 true를 반환, 빈 배열일 경우 true 반환

[1,2,3].every((x) => { return x<4 }) => true

some(함수(조건문)))

배열의 일부 원소에 대해 조건식을 만족하면 true를 변환, 반환값이 true로 결정되면 중단, 빈 배열일 경우 항상 false 반환

[1,2,3].some((x) => { return x<2 }) => true

reduce(함수(인자,인자)), reduceRight()

배열의 각 원소를 순차적으로 두개씩 비교해 함수에 의해 연산, reduce()는 오름차순(왼쪽>오른쪽), reduceRight()는 내림차순(오른쪽>왼쪽)으로 진행

[1,2,3,4,5].reduce((x,y) => { return x-y }) => 1-2-3-4-5 = -13

[1,2,3,4,5].reduceRight((x,y) => { return x-y }) => 5-4-3-2-1 = -5

indexOf(인자), lastIndexOf(인자)

배열의 원소중 특정 값을 찾음, indexOf는 오름차순(왼쪽>오른쪽), lastIndexOf는 내림차순(오른쪽>왼쪽) 순으로 검색하며 가장 먼저 찾은 값의 인덱스를 반환, 값이 없으면 -1 반환

[2,4,6,4,8].indexOf(4) => 1

[2,4,6,4,8].lastIndexOf(4) => 3

isArray(인자)

해당 인자가 배열인지 확인

let a = [1,2,3]

Array.isArray(a) => true


String(문자열)


charAt(인자)

지정한 인덱스의 문자값을 반환

“hello”.charAt(str.length ### 1) => “o”

indexOf(인자)

인자의 내용을 탐색해 index 반환

“hello”.indexOf(“h”) => 4

match(인자)

인자의 내용을 탐색해 반환, 여러개 있으면 배열로 반환

보통 인자는 정규식으로 작성 /정규식/플래그 형태로 쓴다

“hello”.match(/l/g) => [“l”,”l”]

replace(인자,인자)

앞의 인자에 문자열에서 찾을 내용을 적고, 뒤의 인자에 변경할 내용을 적는다

“hello”.replace(“ello”,”i”) => “hi”

split(인자) <=> join(인자)

split: 인자의 내용을 기준으로 문자열을 잘라 배열로 반환

join: 인자의 내용을 기준으로 배열을 문자열로 합침(미입력시 ,로 연결)

“hello world”.split(“ “) => [“hello”,”world”]

[“hello”,”world”].join(“ “) => “hello world”

substring(시작,끝)

문자열에서 시작과 끝 인덱스로 문자열 반환

“hello”.substring(1,2) => “e”

substr(시작,길이)

문자열에서 시작 인덱스와 길이로 문자열 반환

“hello”.substr(1,2) => “el”

출처



출처: http://takeuu.tistory.com/102 [워너비스페셜]

Atom 에디터 단축키 (Windows 기준)


Editor


Ctrl + Shift + P: 커맨드 팔레트 열기

Ctrl + P : 트리뷰 내 파일 찾기.  [eclipse: Ctrl + Shfit + F]

Ctrl + ,: 설정 뷰

Ctrl + .: 키 바인딩 보기 (단축키 작동 현황 보기)

Ctrl + K > 방향키: 패널 나누기

Ctrl + K > Ctrl + 방향키: 패널 전환

Ctrl + W: 창(패널) 닫기

Alt + \ or Ctrl + 0: Treeview 포커스 전환

Ctrl + \: Treeview 열기/닫기

Ctrl + K > Ctrl + B: Treeview 열기/닫기

Ctrl + Shift + L: 언어 선택

Ctrl + Shift + M: Markdown 프리뷰

Ctrl + Shift + U: 인코딩 설정

Ctrl + Alt + I: 개발자 콘솔 열기

Esc: 열린 기능 패널 닫기 및 취소

F11: 전체화면

Tree View (활성화 상태일 때)


A: 새로운 파일 생성

Shift + A: 새로운 폴더 생성

M or F2: 파일(폴더) 경로 및 이름 변경

Delete or Backspace: 파일(폴더) 삭제

D: 파일(폴더) 복사

J, K 커서 이동

H, L 선택 열기/닫기



Tab


Ctrl + T: 파일 파인더 열기 (탭 전환)

Ctrl + B: 열린 파일 보기 (탭 전환)

Ctrl + Tab: 다음 탭 전환

Ctrl + Shift + Tab: 이전 탭 전환

Ctrl + Pagedown, Pageup: 다음/이전 탭 전환

Crtl + Shift + T: 최근에 닫은 탭 다시 열기

Alt + 숫자: 열린 탭 전환



General Editing


Ctrl + /: 주석 토글

Ctrl + F: 찾기/바꾸기

Ctrl + Shift + F: 프로젝트 전체에서 찾기

Ctrl + E: 선택 영역을 찾기/바꾸기

Ctrl + G: 라인 번호로 커서 이동

Ctrl + R: 키워드로 이동

Ctrl + M: 블럭 매칭

Ctrl + J: 라인 조인

Ctrl + L: 라인 선택

Ctrl + D: 현재 단어 선택 (이후 전체 범위에서 같은 단어 선택)

Ctrl + Backspace, Delete: 단어 별 삭제

Ctrl + Shift + K: 현재 라인 삭제 [Eclipse Ctrl + d]

Ctrl + Shift + D: 현재 라인 다음 라인으로 복사 [Eclipse ctrl + alt + ↓]

Ctrl + [, ]: 들여쓰기

Ctrl + Alt + [, ]: 코드 폴딩 토글

Ctrl + Shift + Alt + [, ]: 전체 코드 폴딩 토글

Ctrl + ←, →: 단어 별 이동

Ctrl + ↑, ↓: 현재 라인 이동

Ctrl + Alt + ↑, ↓: 다중 커서 삽입

Ctrl + Enter: 현재 라인 밑으로 개행

Ctrl + Shift + Enter: 현재 라인 위로 개행

Ctrl + Space: 코드 힌트 보기

Shift + 방향키: 텍스트 선택

Shift + Ctrl + ←, →: 단어별 텍스트 선택



서론


 스프링에서는 총 3개의 컨테이너가 구동된다고 할 수있다. 컨테이너란 서버(정적리소스 관리)내에서 Client의 요청을 동적으로 처리하기 위한 웹 서버의 한 부분 이다.


(사진)Servlet Container 


본론




톰캣 서버를 처음 구동하면, 

1. Web.xml 파일을 로딩하여 서블릿 컨테이너가 구동된다.

2. 서블릿컨테이너는 web.xml 파일에 등록된 ContextLoaderListener객체를 생성 한다.

3. 이때 ContextLoaderListener는 applicationContext.xml 파일을 로딩하여 스프링을 구동하는데 이를 'Root 컨테이너'라고 한다.

4. 동시에 Service 구현 클래스나 DAO 객체들이 메모리에 생성된다.

5. Clinet가 서버에 요청을 하게 되면, 서블릿 컨테이너는 DispathcerServlet 객체를 생성하고,

6. Presnetation-layer.xml 파일을 로딩하여 두 번쨰 스프링 컨테이너를 구동한다. 이때 Controller 객체들이 메모리에 올라가게 된다.

 

(+ 추가사항) 

web.xml에서 <servlet><init-param> 과 

<context-param>

<param-name>contextConfigLocation</param-name> 정확히 이해해서 추가내용 쓰기

개요


일반적으로 프레임워크 기반의 웹프로젝트를 보면 아래 그림과 같이 2개의 레이어로 시스템을 나누어 개발한다.



위는 스프링의 프레임워크의 대략적인 구조를 나타낸 것이다. 특히 2-Layered 아키텍쳐에따라 컴포넌트를 분류 해 놓았다.


컴포넌트 설명


(클라이언트의 요청이 들어온 뒤 흐름에 따라 기술)

1.Dispathcer Servlet : 프론트 컨트롤러로써 클라이언트의 요청은 무조건 여기를 거친다.

2.Handler Mapping : 사용자의 요청에 따라 어느컨트롤러로 분기할지 맵핑을 시켜준다.

3.Controller : 기능마다 어느 비즈니스 로직을 태울지 관장하는 컨트롤러이다. 만약 스프링 프레임워크를 쓴다면 실질적으로 여기서부터 프로그래밍을 시작한다.

4.ServiceImpl : 서비스 인터페이스를 상속받아 작성한 비즈니스 로직을 구체적으로 기술 한 곳이다.

5. DAO는 직접 DB에 접근한뒤 DTO 객체에 담아온다.

6.View Resolver : 뷰 리졸버는 이제 다음 *.do URL로 분기할지 아니면, 어떤 jsp로 분기할지 관장하는 컴포넌트이다. 

 *책에서 실제로 구현을 해봣는데 prefix와 suffix를 붙여줘서 실질적인 파일 jsp 이름으로 맵핑시켜줬었다.

7. View : 클라이언트가 마주하는 화면으로써, jsp파일이나 html 페이지에 해당하는 부분이다.


상세


# src/main/resources 폴더에는 비즈니스 레이어에 해당하는 설정파일인 applicationContext.xml (AOP,JDBC등 설정) 파일이 있으며, /WEB-INF/config 폴더에는 프레젠테이션 레이어에 해당하는 설정파일인 presentation-layer.xml 이 있다. 

# DispatcherServlet이 생성되면 presentation-layer.xml 파일을 읽고, 스프링 컨테이너를 구동하면 Controller 객체들이 메모리에 생성된다. 하지만 Controller 들이 생성되기전에, src/main/resources 소스 폴더에 있는 applicationContext.xml 파일을 읽어 비즈니스 컴포넌트 들을 먼저 메모리에 생성해야 한다. 이때 사용하는 클래스가 스픵에서 제공하는 ContextLoaderListener 


->ContextLoaderListener는 다음 글에서 자세히설명 할것 



ps. 본 내용은 스프링 퀵스타트 책을보며 공부한내용을 요약 한 것 입니다.(루비페이퍼, 채규태 저)



Scenario


NodeJS의 npm 모듈중 passport를 쓰면서, 분명히 가이드라인대로 했는데 안되는 게 좀 있엇다. 다들 어이없는 이유로 안됬는데, 이런거땜에 1~2시간은 족히 소요되는 것 같다.

 

1.Passport 사용시 Client에서 서버로 요청까지는 보내지는데 authenticate 에서 passReqToCallback로 진입이 안되는 상황.


2.bcrypt.compareSync(password, this.local.password) 함수로 비밀번호 비교 하련는데, cannot read property 'password' of not undefined 라고 뜨는 상황


3.(가장 어이없음 주의**) req.flash를 사용헤 flash 메세지를 얻어오려는데 못얻어오는 상황.


Solution


1.Passport 사용시 Client에서 서버로 요청까지는 보내지는데 authenticate 에서 passReqToCallback로 진입이 안되는 상황.

->View 에서 보낼때 name과 passport의 usernameField,passwordField 가 일치해야한다.

ex)내가 오류났던 상황

JavaScript 0.45 KB

  1. -----------------view---------------------  
  2.       <div>
  3.         <label>Id:</label>
  4.         <input type="text" name="id"/>
  5.       </div>
  6.       <div>
  7.         <label>Password:</label>
  8.         <input type="password" name="password"/>
  9.       </div>
  10.  
  11.  
  12.  
  13. -----------------------server------------------------------
  14.   passport.use('login',new LocalStrategy({
  15.     usernameField:'email',
  16.     passwordField:'password',
  17.     passReqToCallback : true
  18.   },



2.bcrypt.compareSync(password, this.local.password) 함수로 비밀번호 비교 하련는데, cannot read property 'password' of not undefined 라고 뜨는 상황

->아주 단순했다 this.local.password를 this.password 로 바꿔주니 잘 동작했다.



3.(가장 어이없음 주의**) req.flash를 사용헤 flash 메세지를 얻어오려는데 못얻어오는 상황.

https://stackoverflow.com/questions/38136792/typeerror-req-flash-is-not-a-function

이유를 알고나서 정말 어이가 없었는데.

passReqToCallback:true 를 쓸때 나는 띄어쓰기가 상관 없는줄 알았다. 왜냐하면 다른필드도 띄어쓰기를 안해도 상관 없었으니까.

그러나 passReqToCallback : true 무조건 이런식으로 띄어쓰기를 해주어야한다.

명심하자

passReqToCallback^:^true

Scenario


몽고 DB에서 파일 형식 JSON Array를 꺼내와서, EJS단에서 꺼내서 쓸려고 했다.

서버에서 EJS로 보낸 JSON을 자바스크립트 파일에 담으니 자꾸 

ex)

<Script>

var fileArrys = <%= board.files%>

<script>


Invalid or unexpected token 이라는 에러가 뜨는 것이었다





Solution


다행히도 나와 비슷한 오류를 겪고있는 사람들이 스택오버플로우에 많았다.


JavaScript 0.10 KB

  1. var initData = JSON.parse('<%-JSON.stringify(list)%>');
  2. //or
  3. var initData = <%-JSON.stringify(list)%>;


이렇게 써서 간단히 해결!!

+ Recent posts