Putty로 우분투 접속하기

 

Putty를 이용해 우분투에 접속해 봅니다.

 

 

우분투 설치하기

 

우분투 홈페이지에서 iso파일을 받아 우분투를 설치합니다.

본 글에선 Ubuntu Server 18.04.2 버전을 사용했습니다.

 

 

SSH 설치하기

 

> sudo apt-get install ssh

위의 명령어를 통해 ssh를 설치합니다.

>sudo apt-get install openssh-server

일반적으로 ssh를 설치하면 자동으로 설치됩니다. 혹시모르니 확인해줍니다.

 

SSH 재시작

 

>sudo /etc/init.d/ssh restart

위의 명령어를 통해 ssh를 재시작합니다.

 

Putty 접속

 

>ifconfig

우분투에서 위의 명령어를 통해 ip주소를 확인해 둔다.

 

Putty를 켜고 확인한 IP주소를 입력합니다.

 

ID와 Password를 입력하고 접속을 확인합니다.

반응형

'Programming > Linux' 카테고리의 다른 글

[Putty] Network error: Connection refised  (0) 2020.01.10
Synology Nas를 이용한 Private Registry 구축  (4) 2019.05.27
[Ubuntu] Docker Private Registry  (0) 2019.05.24
[Ubuntu] Docker 설치하기  (0) 2019.05.23
[Ubunto] IP주소 확인  (0) 2017.09.28

하이퍼V 브릿지 네트워크 생성

 

그동안 오라클 VM Virtualbox를 써오다 이번에 Hyper-V로 갈아탔습니다.

 

네트워크 설정을 보니 VirtualBox엔 기본으로 있던 브릿지 네트워크가 없네요...

 

가상 스위치에서 만들어 줍시다.

 

 

가상 스위치 생성

 

다음과 같이 가승스위치 관리자로 이동해 신규 가상 스위치를 만들어 줍니다.

 

위와 같이 외부 네트워크를 선택한 후 가상스위치를 생성합니다.

 

가상 스위치 변경

이후 생성된 가상 컴퓨터 설정의 네트워크 어댑터 메뉴의 가상스위치를 변경해줍니다.

 

 

 

반응형

References: Top Web Design Trends for 2019

 

Top Web Design Trends for 2019

Staying current on the top web design trends is crucial to the success of your website. Here are the latest website design trends for 2019.

www.theedigital.com

 

다음의 내용은 위의 글을 참조한 내용입니다.

 

 

 

앞서는 말

 

최신 웹 디자인 트렌드는 유지하는 것은 웹사이트가 성공하는 데 있어서 중요합니다. 비주얼, 알고리즘, 업데이트와 모범사례의 인기도 측면에서 12개월마다 많은 변화가 있습니다.


매끄럽고 모던해 보이는 웹사이트는 몇 개월 만에 뒤떨어지고 싸구려처럼 보일 수 있습니다. 그렇기 때문에 웹사이트를 디자인할 때 업계의 트렌드와 지나가는 일시적인 유행의 차이를 아는 것은 중요합니다 - 당신은 당신의 디자인이 몇 년간 괜찮아 보이길 원하지 1996년의 지오시티 게시판을 연상시키는 걸 원하진 않을 것입니다.


2019년 사용자 경험에 관한 모든 것입니다: 최고의 웹 디자인 트렌드의 우선순위는 속도, 모바일 디자인, 눈을 사로잡는 비대칭 레이아웃의 심플한 디자인, 사용자를 감싸는듯한 백그라운드 비디오 등입니다.

 

Speed

 

잠재적 고객에게 좋은 인상을 주기 위해서 얼마만큼의 시간을 생각하고 있습니까? 만약 온라인에서의 소통에 대해 얘기하고 있는 것이라면 3초 미만입니다.

 

 

인간은 변덕이 심하고 참을성이 없는 경향이 있습니다. 만약 인터넷이 진짜로 인간성을 관찰하려는 외계인의 음모라면 그들의 연구는 우리가 기다리는 것을 좋아하지 않는다는 것을 알 수 있을 것입니다. Akamai와 Gomez.com 이 진행한 연구에 의하면 50%의 유저가 사이트에서 클릭했을 때, 2초 안에 로드되어야 하며 3초 이상이 걸리면 사이트를 포기할 것이라고 예측합니다.

 

보다시피 아름다운 사이트는 아름다운 사이트지만 많은 데이터의 디자인은 다운로드하는데 너무 오랜 시간이 걸려 아무도 사이트에 붙어 둘러보지 않을 것이기 때문에 많은 수익을 잃을 것입니다. 더 심각한 건 2018년 7월에 적용되기 시작한 구글 스피드 업데이트에서 구글은 다른 사이트들보다 로딩이 빠른 사이트들을 랭킹에 우선순위를 매기기 시작했고 다른 검색 엔진들 또한 곧 따를 가능성이 있습니다.

 

이것의 의미는 웹사이트는 빨라야 한다는 것입니다. 그러므로 웹 디자인은 디자인 프로세스 동안에 속도를 우선순위로 둘 필요가 있습니다. 웹 디자이너는 개발자가 단순히 작업을 좋게 보이고 잘 평가할 것이라고 믿을 수는 없습니다. - 속도는 디자인 매개 변수가 되어야 합니다.

 

거대한 사진, 압축되지 않은 비디오, 비대해진 자바스크립트의 시대는 끝났습니다. 큰 사진과 비디오가 사라지지는 않았지만 2017년에는 로딩 시긴을 느리게 하지 않는 방식으로 통합될 것입니다.

 

 

Flat Design

 

몇 년간 웹 디지이너들과 개발자들은 더 나은 모바일 성능을 위해 깔끔하고 간단한 웹사이트를 만들기 시작했습니다. 그 이후로 모바일 검색이 증가하고 있으며 모바일을 우선한 웹사이트 디자인은 이제 옵션이 아닌 필수가 되었습니다. 앞서 말했던 대로 많은 이미지의 웹사이트는 느리게 로딩되고 모바일 유저를 방해합니다.

 

깔끔함, 최소한의 디자인, 플랫디자인은 빠르게 로딩되는 것이 특징이며 현재의 트렌드이고 두 가지 중요한 이유에서 요구됩니다. 첫째로 모바일과 데스크톰 유저 모두 빠른 로딩 속도의 웹사이트를 경험할 수 있다는 점입니다. 둘째는 높은 SEO(Search Engine Optimization, 검색엔진 최적화) 값을 유치할 수 있다는 점입니다. 플랫디자인은 검색 엔진이 요구하는 많은 속도 요구사항을 충족시키게 합니다. 이것이 플랫 디자인이 인기 있는 이유이며 2019년의 트렌드에도 계속될 이유입니다.

 

플랫 디자인은 모든 것을 2차원으로 낮추라는 것을 의미하진 않습니다. - 모든 것은 미니멀리즘과 유용성에 관련된 것입니다. 디자인 미학은 웹사이트에서 어지럽게 흩어진 것을 정리하고 중요한 부분에 포커스를 맞추는 것입니다. 밝은 색상, 깨끗하고 선명한 가장자리, 많은 빈 공간을 활용하는 플랫디자인은 산만하고 느리게 로딩되는 고해상도 이미지 기반의 디자인에서의 상쾌한 변화입니다.

 

미니멀리스트임에도 불구하고 플랫디자인이 지루하다는 것을 의미하진 않습니다. 밝은 색상과 간단한 형상의 삽화 그리고 산-세리프 폰트의 대조로 플랫 디자인의 파트가 합쳐져 눈길을 끌고 매력적인 훌륭한 사용자 경험을 제공합니다.

 

플랫 디자인은 매력적인 사진들에 의존하지 않기 때문에 유저가 페이지를 둘러볼 때 많은 추가 데이터를 로드할 필요가 없습니다. 이것은 웹사이트 소유주에게 정말 중요한 두 가지를 의미합니다. 첫째는 고객들이 모바일인지 다른 것들 인지 상관없이 브라우징 할 때 웹사이트를 빠른 로딩으로 즐길 수 있다는 점입니다. 울째는 이러한 적은 데이터와 빠른 로딩의 디자인은 페이지 속도와 최적화를 훨씬 더 빠르게 만들어 주는 것입니다 - 구글, 빙 그리고 대부분의 다른 검색엔진에 실제로 바람직합니다.

 

 

Mobile First

 

모바일을 우선한 웹 디자인속도는 구글이 웹사이트를 측정하는데 사용한 유일한 측정 수치는 아닙니다. 2015년에 모바일 검색이 데스크톱 검색량을 초과한 후 모바일 검색은 세계에서 가장 높은 검색 형식이 되었습니다. 이에 따라 구글은 인덱스를 하는 사이트를 먼저 변경하였습니다. - 구글은 이제 모바일 사이트를 모바일 친화적이지 않은 사이트보다 우선합니다.

 

모바일 사이트가 데스트톱 사이트보다 우선순위가 높으므로 모바일을 우선한 디자인이 트렌트가 되는것은 이상한 일이 아닙니다. 모바일을 우선한 웹 디자인은 웹 사이트가 근본적으로 디자인 된 방식을 바꾸는것 입니다. 기본의 사이트는 데스크톱 또는 랩톱을 위해서만 디자인 되었고 모바일 친화적이거나 모바일을 대응하는 디자인이 추가될 수 있습니다. 모바일 우선 디자인은 반대입니다: 모바일 사용자를 위해 먼저 디자인 하고 데스크톱 사용자를 위해 버전을 생성합니다.

 

다시말하지만, 모바일 우선 디자인을 위한 이러한 추진은 단지 랭킹 요인이나 SEO에 기반한 것이 아닙니다. 시각적인 결과는 무엇보다 가장먼저 가장 많이 검색할 것으로 예상되는 기기의 웹사이트 사용자 경험을 강화시킵니다. 이 디자인 트렌드는 모바일 유저들이 오랜시간 요구해온 트렌드를 충족시킵니다.

 

 

 

 

 

 

Broken Grid/Asymmetrical Layouts

 

깨진 그리드 또는 비대칭 레이아웃에 대해 그리드 시스템은 웹페이지, 신문, 광고까지 수십년간 사용된 레이아웃을 얘기 합니다. 그리드 시스템은 디자이너가 이미지, 헤드라인, 복사본, 클릭유도등의 컨텐츠를 추가할 때 그리드의 구조를 유지하면서 정렬과 일관성을 쉽게 유지하도록 도와주며 대칭도 마찬가지 입니다.

 

역사적으로 그리드를 사용하지 않거나 "날개를 다는것"을 하지 않는것은 유저가 페이지의 가장 중요한 부분에 집중하지 못하게 하는 엉성하거나 매력적이지 않은 디자인을 만드는것입니다. 그러나 비대칭과 깨진 그리드는 다른 웹사이트의 디자인과 달라보이게 하는 방법을 찾고 동시에 매력적이지 않거나 엉성하지 않기 때문에 점차 인기를 얻고 있습니다.

 

디자이너들은 조심스럽고 깊은 생각을 통해 그리드 패턴을 깨고 계획된 비대칭의 계층을 확립함으로써 이를 달성할 수 있습니다. 일반적이지 않은 배치, 다른 색과 재질의 레이어, 불규칙적인 패턴의 반복, 여백의 사용, 창의적인 타이포그래피의 사용으로 그리드 기반 레이아웃에서 일반적으로 찾을 수 없는 깊이감을 만들 수 있습니다.

 

이러한 디자인들은 눈에 띄고 유저들의 관심을 끄는데 도움이 되며 유저를 웹사이트의 가장 중요한 부분으로 이끕니다만 정교한 디자인 패턴때문은 아닙니다. 대신에 창의적인 시작적 계층의 사용은 볼 곳으로 눈을 이끕니다. 색상, 모양, 질갑, 형식의 표현적인 사용과 동적인 이미지를 사용해 디자이너는 새롭고 흥미로운 방식으로 사용자의 관심을 유저가 찾길 원하는 컨텐츠 또는 CTA(Call To Action)들을 향해 이끌 수 있습니다.

 

 

Shapes

 

플랫 디자인의 미니멀리즘과 깨진 그리드의 제어된 혼돈 사이의 트렌드에 기하학적인 도형 트렌드가 있습니다. 단순하게 중학교에서 배운 삼각형, 육각형, 원과 같은 유클리드의 기하학적인 도형을 생각한다면 바로 옳습니다.

 

도형은 디자인에 매우 쉽게 통합 될 수 있기 때문에 플랫디자인과 깨진 그리드의 간격을 연결할 수 있습니다. 밝은 색상의 간단한 도형은 흥미로운 가장자리를 만들어낼 수 있으면서 매력적인 상태로 빠르게 로드할 수 있습니다. 사진과 유형과 함께 도형을 혼합하거나 도형을 사용하여 반복되는 패턴은 깨진 그리드 또는 비대칭 레이아웃에서 활력과 깊이를 보여줄 수 있습니다.

 

이러한 다양성은 2019년에 도형이 트렌드가 된 이유중 큰 부분을 차지합니다. 기하학의 수학은 웹사이트에 비대칭이 포함되어 있는 경우에도 균형을 느낄 수 있도록 합니다. 도형은 주로 쉽게 들어맞거나 다른 도형의 옆에서 쉽게 계층 또는 구조를 확립합니다.

 

도형은 색상과 같이 사람의 생각과 감정에 자연적으로 연관되어 있습니다. 사각형은 안정성, 원은 단일 그리고 삼각형과 마름모는 동적을 나타냅니다. 특정 도형이나 도형의 조합을 창의적으로 사용하는 것은 웹사이트의 방문자가 느끼기를 원하는 느낌이나 감정을 형성할 수 있습니다.

 

도형은 극적으로 또는 절제하여 사용할 수 있습니다. - 그건 단순이 당신의 브랜드 미학에 달려있습니다. 도형은 시각적 구조를 쉽게 확립하고 기존의 그리드 디자인에서 벗어났을 때 방문자가 알아야할 부분에 주의를 끌 수 있도록 도와줍니다. 이것이 2019년 웹 디자인에서 도형을 계속 볼 수 있는 이유입니다.

 

 

 

Single Page Design

 

속도와 미니멀리즘은 2019년 뤱 디자인에 대해 얘기할 때 반복적으로 등장하는 트렌드로 pageless 디자인으로 알려진 단일 페이지 디자인이 2019년 웹 디자인 트렌드가 된 주된 이유입니다. 단일 페이지 디자인은 매우 서술적인 제목입니다. - 여러 서비스 페이지나 블로그 기사 대신 단 한 페이지만 갖고 있는 웹사이트를 말하며 모두 사일로 구조에 깔끔하게 정렬되어 있습니다.

전총적인 SEO 디지털 마케팅 관점에서, 이것은 실수인것처럼 보일것 입니다. - 일반적으로 갖고있을 페이지와 컨텐츠 없이 특정 키워드에 대한 평가는 더 어려울 것이며 다른 진보된 SEO 기술도 달성하기 쉽지 않습니다. 그렇습니다, 특정 SEO 전략을 pageless 디자인에 사용하는 것은 어렵겠지만 거기서 단점이 사라지기 시작합니다.

속도에서 논의한바와 같이 플랫디자인, 어수선하지 않고 깔끔하며 단순한 웹사이트들이 모바일 기기에서 빠르고 쉽게 다운로드 될 수 있기에 검색엔진에 의해 선호되고 있습니다. pageless 디자인은 웹사이트의 속도를 늦출 수 있는 모든것들을 적게 함으로써 달성하였습니다. HTML, CSS, Javascript와 사진들은 다운로드 될 사이트를 틀어막지 않아 유저에게 더 나은 경험을 제공하고 검색엔진에 우선순위를 부여합니다.

이 단순함은 모든 검색 기기에서 좋아 보이고 자동으로 모바일 우선 스타일 사이트로 나뉘기 때문에 단일 페이지 웹사이트에 두배로 좋습니다. 또한 단순해진다는 것은 관리하기 더 쉽다는것을 의미합니다. 변경하거나 업데이트 할 것이 적어서 사이트 업데이트는 빨리 이루어 지며 이는 당신의 비즈니스가 당신의 웹사이트를 최신으로 유지하기 더 쉬워진다는것을 의미합니다.

 

기업들은 pageless 웹사이트를 단순히 사용하기 쉬운것 이상으로 좋아합니다: 그것들은 또한 높은 전환률을 갖는 경향이 있습니다. 기존의 계층적 사이트는 키워드 검색을 통해 유저를 붙잡고, 그들을 사이트에 도착하게 하고 그들의 연락처 양식, 페이지 또는 핸드폰 번호를 모으는것이 전부입니다. 단일 페이지 사이트에서는 유저가 혼란스러워할 데가 없습니다. - 페이지의 모든 부분이 그들에게 본보기와 비즈니스를 제공하며 전환점으로 이끕니다.

 

단일 페이지 디자인이 웹 디자인의 미래를 넘겨받진 못할것이지만, 분명히 2019년까지는 흔적을 남길것 입니다.

 

 

Video Backgrounds

 

미니멀리스트, 빠른로딩, 플랫디자인 트렌드에더 불구하고 비디오 배경은 믿을수 없게도 여전히 2019년의 트렌드로 인기가 있습니다. 여러분은 아마 속도가 너무 큰 요인이라 비디오는 많은 사이트의 발목을 붙잡을것으로 생각할 수 있으나 흥미롭게도 비디오 배경은 전환률을 높이는 것으로 보여집니다.

 

비디오는 텍스트나 이미지보다 더 매력적입니다. 여러분은 지난 몇년간 페이스북과 같은 소셜미디어 플랫폼에서 이러한 트렌드가 나타나는것을 보았을것 입니다. 비디오 게시물은 다른 종류의 게시물보다 높은 우선순위가 매겨집니다. 심지어 그들은 여러분의 피드를 스크롤 하는 동안 음소거로 비디오를 자동 재생함으로써 비디오를 보는것을 더 쉽게 만들었습니다.

 

사용자가 여러분의 웹사이트에 방문하고 나서 비디오가 배경에서 재생될 때, 방문자들은 비디오가 관심을 끌기 때문에 그것을 보기위해 붙어있을 것 입니다. 유저들이 여러분의 사이트에 머물수록 유저들은 더 많이 바뀔것 입니다. 그것은 결국 사이트 통계에 시간을 증가시키며 사이트의 평균시간이 높을 수록 SEO가 더 좋아집니다.

 

비디오의 힘은 문자그대로 말로는 표현할 수 없습니다. - 비디오는 여러분의 메시지를 빠르고 효과적으로 전달하며 무언가를 이루기 위해 텍스트 단락을 포함할 수 있습니다. 비디오는 단 몇초안에 그것들을 해냅니다. 이건 특히 짧은시간 안에 유저들에게 주의를 돌리는 복잡한 메시지를 설명할 때 유용합니다.

 

마지막으로 웹사이트의 비디오 배경에는 깔끔하고 모던한 무언가가 있습니다. 비디오가 짧고, 음소거가 되고, 멋지고, 보기 좋은 고품질이라면 비디오 배경은 여러분의 브랜딩에 많은 도움이 됩니다.

 

 

Micro-Animations

 

웹 디자인에서 미묘하지만 눈에띄는 2019년 트렌드는 마이크로 애니메이션 입니다. 마이크로 애니메이션은 유저들이 여러분의 웹사이트를 탐색할 때 직관적이고 만족스러운 경험을 제공하는 강력한 방법입니다. 이건 유저가 커서를 올리면 버튼의 색이 바뀌거나 햄버거를 클릭하면 메뉴가 펼쳐지는것과 같이 요소를 클릭하거나 마우스를 올릴 때 사이트를 이해하고 검증하는 것을 돕는 작은 애니메이션을 통해 이뤄집니다.

 

 

대부분의 사용자는 데스크톱과 모바일 검색 플랫폼 모두에서 이런 마이크로 애니메이션을 경험하였습니다. 마이크로 애니메이션은 너무 퍼져있어서 경험하지 못한것이 더 놀라울 정도입니다. 이런 애니메이션들은 유저가 여러분의 사이트를 통해 올바른 액션을 진행하고 있음을 알수 있도록 도와줍니다. 양식의 제출 버튼을 눌렀나요? 누르면 색이 변경됩니다. 어떻게 페이지를 빠르게 새로고침할 수 있을까요? 아래로 당기면 풀링 모션이 생성되어 새로고침이 동작하였음을 알수 있습니다.

 

비디오와 같은 움직이는것이 어떻게 유저가 빠져나가지 않게 하고 주의를 끄는것에 도움이 되는지 기억하나요? 이러한 움직임은 비록 작은 애니메이션 일지라도 여러분의 사이트를 둘러볼 때 유저의 눈길을 붙잡고 풍부한 경험을 만들도록 도와줍니다. 마이크로 애니메이션을 적절히 사용하면 웹사이트에 시작적 계층을 생성해 방문자들을 여러분의 변환점으로 이끌게 되고 방문자들은 작성한것들에 대한 보상을 받게 됩니다.

 

마이크로 애니메이션이 유저가 양식에서 제출버튼을 클릭하는것에 대해 "보상"할 수 있다고 생각하는 것은 너무 단순하거나 어리석은 것 처럼 보일 수 있습니다만 많은 양식들에서 사용되는 짜증나는 Captcha를 생각해 보세요. 여러분이 운이 좋다면 체크박스를 클릭ㅎ기만 하면 되지만 그렇지 않다면 사진을 선택하거나 숫자와 문자열을 입력해야 하며 원 모양의 소용돌이를 확인표시로 보는것 만큼 만족스러운 것은 없습니다. 2019년이 다가옴에따라 마이크로 애니메이션은 웹 디자인 업계에 머물것 입니다.

 

 

Chatbots/Machine Learning

 

지난 몇년간 봇과 상호작용하고 의사소통하는것은 점점 더 평범해졌습니다. 디지털 미디어 전반에 걸쳐 웹사이트와 마이크로 인터랙션에서 봇이나 챗복이 점점 흔해지고 있습니다.

 

봇들이 약 20년전 처음 출시되었을 때, 봇들은 문제를 해결하는것을 더욱 어렵게 만드는것 처럼 보였지만 수년간 개선된 인공지능(AI)과 기계학습 덕에 더 똑똑해졌습니다. 검색창에 입력하기 시작하면 구글이 자동검색어 제안을 만드는데 돕고 있습니다.

 

페이스북이 우리들을 잘 아는것도 이때문 입니다. AI는 페이스북이 우리의 모습을 배우고 사진에 태그를 추가할지 묻는것을 돕습니다. AI는 또한 위치 데이터를 사용하고 우리들의 독서 습관을 학습했기 때문에 우리들이 "좋아요" 버튼을 누르게 하기 위하여 보여줄 광고, 이벤트, 정보들을 정확하게 알고있습니다.

 

챗봇과 기계학습은 특히 대부분의 채팅에 포함된 자동응답 기능이 유저들과 원활히 상호작용 하고 우수한 고객 서비스를 제공할 수 있으므로 웹사이트와 사용자의 상호작용을 향상시킬 것 입니다. 이렇게 하면 잠재 고객과의 관계가 좋게 시작되며 동시에 영업팀이 잠재고객과 실제로 상호작용을 시작하기 전에 정보를 수집할 수 있습니다.

 

2019년에는 이 기술이 완성되고 회사 웹 사이트에 통합될 것입니다. 곧, 웹 상호작용은 매끄러워질 것입니다. 여러분의 회사와 고객들의 과거 상호작용을 분석함으로써 고객들이 무엇을 찾고 있는지 이미 정확히 알고 있는 웹사이트를 상상해 보세요. 이런 이런 새로운 기술덕에 고객 서비스는 매일 더 빠르고 효율적으로 변하고 있습니다. 웹 디자인에서 그것들을 간과하지 마세요.

 

 

마지막으로: Web Design 2019 — Engaging Minimalism or Attention-Grabbing Visuals?

 

2019년은 웹 디자인에 있어서 야누스처럼 보일수 있습니다: 한쪽은 깨진 그리드의 시각적 즐거움과 비디오 배경의 유혹입니다. 다른 한쪽은 플랫 디자인과 단일 페이지 레이아웃의 아름다움과 실용주의 입니다. 트렌드가 바뀌면 산업도 변합니다. 이러한 트렌드들을 계속 유지하면 여러분의 웹사이트를 신선하고 아릅다우며 전환을 만드는데 도움이 될 것입니다.

반응형

References: 13 Noteworthy Points from Google’s JavaScript Style Guide

 

13 Noteworthy Points from Google’s JavaScript Style Guide

For anyone who isn’t already familiar with it, Google puts out a style guide for writing JavaScript that lays out (what Google believes to…

medium.freecodecamp.org

 

다음의 내용은 위의 글을 참조한 내용입니다.

 

 

1.Use spaces, not tabs - 탭 대신 스페이스를 사용하세요.

 

The ASCII horizontal space character (0x20) is the only whitespace character that appears anywhere in a source file. 
- ASCII 수평 공백 문자 (0x20)는 소스 파일의 아무 곳에 나 나타나는 유일한 공백 문자입니다.

This implies that… Tab characters are not used for indentation.
- 탭 문자는 들여쓰기에 사용하지 않습니다.

 

// bad
function foo() {
∙∙∙∙let name;
}

// bad
function bar() {
∙let name;
}

// good
function baz() {
∙∙let name;
}

 

 

2. Semicolons ARE required - 세미콜론은 필요합니다.

 

Every statement must be terminated with a semicolon.
-모든 문장은 세미콜론으로 끝나야 합니다.

Relying on automatic semicolon insertion is forbidden.
- 자동으로 세미콜론이 삽입되는것에 의존하는것은 금지되어있습니다.

 

왜 이 아이디어에 반대하는 사람이 있는지는 상상할 수 없습니다만, JS에서 세미콜론을 일관되게 사용하는것이 새로운 'Space 대 Tab' 논쟁이 되고 있습니다. 구글은 세미콜론을 지키기 위해 단호히 나왔습니다.

 

// bad
let luke = {}
let leia = {}
[luke, leia].forEach(jedi => jedi.father = 'vader')

// good
let luke = {};
let leia = {};
[luke, leia].forEach((jedi) => {
  jedi.father = 'vader';
});

 

 

3. Don’t use ES6 modules (yet) - (아직은) ES6 모듈을 사용하지 마세요.

 

Do not use ES6 modules yet (i.e. the export and import keywords), as their semantics are not yet finalized.
- 의미론이 아직 확정되지 않았으므로 ES6 모듈 (예를 들면 export와 import 키워드들)을 사용하지 마세요.

Note that this policy will be revisited once the semantics are fully-standard.
- 이 정책은 의미론이 완전히 표준화되면 다시 검토될 것입니다.

 

//이러한 것들을 아직 하지 마세요:

//------ lib.js ------
export function square(x) {
  return x * x;
}
export function diag(x, y) {
  return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';

 

 

4. Horizontal alignment is discouraged (but not forbidden) - 수평정렬은 권장되지 않습니다. (그러나 금지되지는 않습니다.)

 

This practice is permitted, but it is generally discouraged by Google Style.
- 이 방법은 허용이 됩니다만 일반적으로 구글 스타일에서는 권장되지 않습니다.

It is not even required to maintain horizontal alignment in places where it was already used.
- 이미 사용된 장소에서도 수평정렬을 유지할 필요가 없습니다.

 

// bad
{
  tiny:     42,
  longer: 435,
};

// good
{
  tiny: 42,
  longer: 435,
};

 

 

5. Don’t use var anymore. - 더이상 var을 사용하지 마세요.

 

Declare all local variables with either const or let.
- 모든 로컬 변수들을 const나 let으로 선언하세요.

Use const by default, unless a variable needs to be reassigned.
- 변수에 재할당이 필요한 경우가 아니면 const를 기본으로 사용하세요.

The var keyword must not be used.
- var 키워드는 사용되지 말아야 합니다.

 

아직도 StackOverflow 및 다른곳에 있는 코드 샘플에서 var를 사용하는 사람이 보입니다. 누군가가 그로 인해 사건을 만들지, 단지 죽어가는 오래된 습관인지는 알 수 없습니다.

 

// bad
var example = 42;

// good
let example = 42;

 

 

6. Arrow functions are preferred - 화살표 함수가 선호됩니다.

 

Arrow functions provide a concise syntax and fix a number of difficulties with this.
- 화살표 함수는 간결한 구문을 제공하고 그로인해 많은 어려움을 해결합니다.

Prefer arrow functions over the function keyword, particularly for nested functions.
- 특히 중첩된 함수의 경우 화살표 함수가 function 키워드보다 선호됩니다.

 

솔직히 화살표 함수가 더 간결하고 보기 좋기 때문에 더 좋다고 생각했습니다. 화살표 함수는 꽤나 중요한 목적 또한 수행하는 것으로 밝혀졌습니다.

 

// bad
[1, 2, 3].map(function (x) {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

 

 

7. Use template strings instead of concatenation - 연결하는것 대신에 탬플릿 문자열을 사용하세요.

 

Use template strings (delimited with `) over complex string concatenation, particularly if multiple string literals re involved.
- 여러 string 리터럴이 관련되어있는 경우 특히 복잡한 문자열 연결에 대해서 템플릿 문자열 (`로 구분됨)을 사용하세요.

Template strings may span multiple lines.
- 템플릿 문자열은 여러 줄에 걸쳐있을 수도 있습니다.

 

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
  return ['How are you, ', name, '?'].join();
}

// bad
function sayHi(name) {
  return `How are you, ${ name }?`;
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}

 

 

8. Don’t use line continuations for long strings - 긴 문자열에서 줄 연속을 쓰지 마세요.

 

Do not use line continuations (that is, ending a line inside a string literal with a backslash) in either ordinary or emplate string literals.
- 일반적이거나 템플릿 문자열의 리터럴에서 줄 연속 (문자열 리터럴 내에서 백 슬래시로 줄을 끝내는것)을 사용하지 마세요.

Even though ES5 allows this, it can lead to tricky errors if any trailing whitespace comes after the slash, and is less obvious to readers.
- ES5에서는 이것을 허용하지만 슬래시 뒤에 오는 공백이 있으면 보는이가 쉽게 알 수 없으므로 까다로운 오류가 발생할 수 있습니다.

 

흥미롭게도 이것은 Google과 Airbnb가 동의하지 않는 규칙입니다 (Airbnb의 스펙 참조).
Google은 긴 문자열 (아래 그림 참조)을 연결하는 것을 권장하지만 Airbnb의 스타일 가이드는 본질적으로 아무 것도하지 말고 긴 문자열을 필요하면 길게 사용할 수 있도록 권장합니다.

 

// bad (미안하지만 모바일에선 잘 표시되지 않습니다.)
const longString = 'This is a very long string that \
  far exceeds the 80 column limit. It unfortunately \
  contains long stretches of spaces due to how the \
  continued lines are indented.';

// good
const longString = 'This is a very long string that ' +
  'far exceeds the 80 column limit. It does not contain ' +
  'long stretches of spaces since the concatenated ' +
  'strings are cleaner.';

 

 

9. “for… of” is the preferred type of ‘for loop’ - "for ... of"는 'for loop'의 바람직한 유형입니다.

 

With ES6, the language now has three different kinds of for loops.
- ES6에서는 이제 언어에 3 가지 다른 종류의 for loop를 갖습니다.

All may be used, though 
for-of loops should be preferred when possible.

 

내게 묻는다면 이상한 것이지만, 난 구글이 for루프의 바람직한 유형을 선헌한것이 꽤나 흥미롭기 때문에 이것을 포함시킬 것이라고 생각했습니다.

저는 항상 "... in" 루프는 객체에 더 좋았고, "for ... of"는 배열에 더 적합하다는 생각 이었습니다. -  "올바른 작업을 위한 올바른 도구"유형의 상황.

구글의 사향이 반드시 그 생각과 상반되는것은 아니지만, 특히 이 루프에 대한 선호가 있단것을 하는것은 여전히 흥미롭습니다.

 

 

10. Don’t use eval() - eval()을 사용하지 마세요

 

Do not use eval or the Function(...string) constructor (except for code loaders).
eval 또는 Function (... string) 생성자를 사용하지 마세요 (코드 로더는 제외됩니다). 

These features are potentially dangerous and simply do not work in CSP environments.

 

eval()의 MDN 페이지에는 "Do not ever use eval!"라는 섹션이 있습니다.

 

// bad
let obj = { a: 20, b: 30 };
let propName = getPropName(); // returns "a" or "b"
eval( 'var result = obj.' + propName );

// good
let obj = { a: 20, b: 30 };
let propName = getPropName(); // returns "a" or "b"
let result = obj[ propName ]; // obj[ "a" ] is the same as obj.a

 

 

11. Constants should be named in ALL_UPPERCASE separated by underscores - 상수는 밑줄로 구분된 대문자로 명명되어야 합니다.

 

Constant names use CONSTANT_CASE: all uppercase letters, with words separated by underscores.

 

변수가 변경되지 않아야한다고 절대적으로 확신하는 경우 상수의 이름을 대문자로 표시하여 이를 나타낼 수 있습니다. 이렇게하면 코드 전체에서 사용됨에 따라 상수의 불변성이 명확해집니다.
이 규칙의 주목할만한 예외는 상수가 function-scope인 경우입니다. 이 경우 camelCase로 작성해야합니다.

 

// bad
const number = 5;

// good
const NUMBER = 5;

 

 

12. One variable per declaration - 선언 하나에 변수 하나

 

Every local variable declaration declares only one variable: declarations such as let a = 1, b = 2; are not used.
- 모든 지역 변수의 선언은 하나의 변수만 선언합니다: let a = 1, b = 2;와 같은 선언은 사용하지 않습니다.

 

// bad
let a = 1, b = 2, c = 3;

// good
let a = 1;
let b = 2;
let c = 3;

 

 

13. Use single quotes, not double quotes - 큰따옴표(") 대신 작은따옴표(')를 사용하세요.

 

Ordinary string literals are delimited with single quotes ('), rather than double quotes (").
- 일반적인 문자열 리터럴은 큰 따옴표(") 대신 작은 따옴표(')로 구분됩니다.

Tip: if a string contains a single quote character, consider using a template string to avoid having to escape the quote.

 

// bad
let directive = "No identification of self or mission."

// bad
let saying = 'Say it ain\u0027t so.';

// good
let directive = 'No identification of self or mission.';

// good
let saying = `Say it ain't so`;

 

마지막으로

 

제가 처음에 말했듯이, 이것들은 명령이 아닙니다. 

Google은 많은 기술 대기업 중 하나일 뿐이며 이러한 것들은 단지 권고사항일 뿐 입니다.
즉 훌륭한 코드를 작성하는 데 많은 시간을 할애하는 훌륭한 사람들을 고용하고있는 Google과 같은 회사에서 제안한 스타일 권장 사항을 살펴 보는 것은 흥미로운것 입니다.
'Google compliant source code’에 대한 가이드 라인을 따르고 싶다면 이러한 규칙들을 따라야 합니다. 

물론 많은 사람들이 동의하지 않으며 이 중 일부 또는 전부를 마음대로 쓸 수 있습니다.
저는 개인적으로 Airbnb의 사양이 Google보다 더 매력적이라고 생각합니다.

이러한 특정 규칙을 취하는 태도에 상관없이 모든 종류의 코드를 작성할 때 문체의 일관성을 염두에 두는 것이 중요합니다.

 

 

 

반응형

1. 생성된 유저 확인

>USE mysql;

>SELECT host, user FROM user;

 

2. 유저 생성

>CREATE USER 'userNameHere'@'accessRange' IDENTIFIED BY 'userPasswordHere';

** Access Range

> '%' 모든 ip주소에 대하여 접근 허용

> 'localhost' 로컬에서의 접근만 허용

> '192.168.0.%' 특정 ip주소에 대하여 접근 허용

 

3. 권한 부여

>GRANT ALL PRIVILEGES ON databaseNameHere.* TO 'userNameHere'@'accessRange';

 

4. 권한 새로고침

>FLUSH PRIVILEGES;

 

반응형

 

웹팩

 

* 웹팩은 프로젝트의 구조를 분석하고 자바스크립트 모듈을 비롯한 관련 리소스들을 찾은 다음 이를 브라우저에서 이용할 수 있는 번들로 묶고 패킹하는 모듈 번들러(Module bundler)다.

 

* 빌드툴이 아닌 모듈 번들러다. Grunt나 Gulp와는 다르다.

 

Webpack

반응형

References: Creating your first app


마지막으로 테스팅을 진행해 보도록 합니다.



1. 테스트 진행하기



자바스크립트 테스트 드라이버와 어설션 라이브러리를 설치합니다.


$ meteor add meteortesting:mocha

$ meteor npm install --save-dev chai









반응형

References: Creating your first app



프로젝트 폴더에서 콘솔로 meteor list를 입력해 봅시다.


몇 가지 패키지가 눈에 띄시지 않나요 ?



지난 포스팅에서도 언급했듯이 저 패키지를 그대로 내보내는 건 보안 이슈가 있습니다.


이번에는 이 패키지를 제거해 보겠습니다.




1. 메서드를 이용한 보안



일단 insecure 패키지를 제거합시다. 콘솔에서 meteor remove insecure를 입력해 패키지를 제거해 주세요



이로써 클라이언트 측에 기본으로 주어지던 DB 사용 권한이 다 사라졌습니다.  


이제 새로 디비에 접근하기 위한 메서드를 정의합니다.


/imports/api/tasks.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor'
import { check } from 'meteor/check';
 
export const Tasks = new Mongo.Collection('tasks');
 
Meteor.methods({
    'tasks.insert'(text) {
      check(text, String);
      // Make sure the user is logged in before inserting a task
      if (! this.userId) {
        throw new Meteor.Error('not-authorized');
      }
      Tasks.insert({
        text,
        createdAt: new Date(),
        owner: this.userId,
        username: Meteor.users.findOne(this.userId).username,
      });
    },
    'tasks.remove'(taskId) {
      check(taskId, String);
      Tasks.remove(taskId);
    },
    'tasks.setChecked'(taskId, setChecked) {
      check(taskId, String);
      check(setChecked, Boolean);
      Tasks.update(taskId, { $set: { checked: setChecked } });
    },
  });
cs


기존 task 컬렉션에 직접 붙어서 수행하던 것을 새로 정의한 메서드로 대체 합니다.


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Meteor.call('tasks.insert', text);
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
cs


/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';
 
// Task component - represents a single todo item
export default class Task extends Component {
    toggleChecked() {
        // Set the checked property to the opposite of its current value
        Meteor.call('tasks.setChecked', this.props.task._id, !this.props.task.checked);
    }
 
    deleteThisTask() {
        Meteor.call('tasks.remove', this.props.task._id);
    }
 
    render() {
        // Give tasks a different className when they are checked off,
        // so that we can style them nicely in CSS
        const taskClassName = this.props.task.checked ? 'checked' : '';
        return (
            <li className={taskClassName}>
                <button className="delete" onClick={this.deleteThisTask.bind(this)}> &times; </button>
                <input type="checkbox" readOnly checked={!!this.props.task.checked} onClick={this.toggleChecked.bind(this)} />
                <span className="text">
                    <strong>{this.props.task.username}</strong>: {this.props.task.text}
                </span>
            </li>
        );
    }
}
cs


그리고 화면을 확인합니다.


모든 인풋과 버튼이 동작함을 확인할 수 있습니다.


위와 같이 수정함으로써 클라이언트 코드와 DB코드가 분리되는 장점을 추가로 얻게 됩니다.




2. 발행과 구독



자동으로 DB에서 모든 데이터를 가져오는걸 막기 위해 autopublish 패키지를 제거합니다. 


$ meteor remove autopublisk



이제 자동으로 서버측 몽고디비에서 데이터를 가져오지 않습니다.


Publish/Subscribe 메서드를 구현합니다.


/imports/api/tasks.js 


1
2
3
4
5
6
7
8
9
10
11
12
...
export const Tasks = new Mongo.Collection('tasks');
 
if (Meteor.isServer) {
    // This code only runs on the server
    Meteor.publish('tasks'function tasksPublication() {
      return Tasks.find();
    });
  }
 
Meteor.methods({
...
cs


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
...
export default withTracker(() => {
  Meteor.subscribe('tasks');
  return {
    tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
})(App);
cs


이제 다시 작업 리스트가 보입니다.


서버에서 Meteor.publish를 호출하면 tasks라는 이름으로 발행을 진행합니다.


이후 클라이언트에서 Meteor.subscribe를 통해 발행된 정보를 가져옵니다.




3. 비공개 작업 추가하기.



css 클래스의 이름별로 다른 설정을 하기 위해 classnames 모듈을 설치합니다.


$ meteor npm install --save classnames



공개/비공개 버튼을 추가하고 비공개일 경우 작업을 추가한 유저가 아니면 보이지 않도록 하는 코드를 추가합니다.


/imports/api/tasks.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
 
export const Tasks = new Mongo.Collection('tasks');
 
if (Meteor.isServer) {
    // This code only runs on the server
    // Only publish tasks that are public or belong to the current user
    Meteor.publish('tasks'function tasksPublication() {
        return Tasks.find({
            $or: [
                { private: { $ne: true } },
                { owner: this.userId },
            ],
        });
    });
}
 
Meteor.methods({
    'tasks.insert'(text) {
        check(text, String);
        // Make sure the user is logged in before inserting a task
        if (!this.userId) {
            throw new Meteor.Error('not-authorized');
        }
        Tasks.insert({
            text,
            createdAt: new Date(),
            owner: this.userId,
            username: Meteor.users.findOne(this.userId).username,
        });
    },
    'tasks.remove'(taskId) {
        check(taskId, String);
        const task = Tasks.findOne(taskId);
        if (task.private && task.owner !== this.userId) {
          // If the task is private, make sure only the owner can delete it
          throw new Meteor.Error('not-authorized');
        }
        Tasks.remove(taskId);
    },
    'tasks.setChecked'(taskId, setChecked) {
        check(taskId, String);
        check(setChecked, Boolean);
        const task = Tasks.findOne(taskId);
        if (task.private && task.owner !== this.userId) {
          // If the task is private, make sure only the owner can check it off
          throw new Meteor.Error('not-authorized');
        }
        Tasks.update(taskId, { $set: { checked: setChecked } });
    },
    'tasks.setPrivate'(taskId, setToPrivate) {
        check(taskId, String);
        check(setToPrivate, Boolean);
        const task = Tasks.findOne(taskId);
        // Make sure only the task owner can make a task private
        if (task.owner !== this.userId) {
            throw new Meteor.Error('not-authorized');
        }
        Tasks.update(taskId, { $set: { private: setToPrivate } });
    },
});
cs


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
import { Meteor } from 'meteor/meteor';
 
// App component - represents the whole app
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hideCompleted: false,
    };
  }
 
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Meteor.call('tasks.insert', text);
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
 
  renderTasks() {
    let filteredTasks = this.props.tasks;
    if (this.state.hideCompleted) {
      filteredTasks = filteredTasks.filter(task => !task.checked);
    }
    return filteredTasks.map((task) => {
      const currentUserId = this.props.currentUser && this.props.currentUser._id;
      const showPrivateButton = task.owner === currentUserId;
      return (
        <Task
          key={task._id}
          task={task}
          showPrivateButton={showPrivateButton}
        />
      );
    });
  }
 
  toggleHideCompleted() {
    this.setState({
      hideCompleted: !this.state.hideCompleted,
    });
  }
 
  render() {
    return (
      <div className="container">
        <header>
        <h1>Todo List ({this.props.incompleteCount})</h1>
          <label className="hide-completed">
            <input
              type="checkbox"
              readOnly
              checked={this.state.hideCompleted}
              onClick={this.toggleHideCompleted.bind(this)}
            />
            Hide Completed Tasks
          </label>
          <AccountsUIWrapper />
          { this.props.currentUser ?
            <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
              <input
                type="text"
                ref="textInput"
                placeholder="Type to add new tasks"
              />
            </form> : ''
          }
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  Meteor.subscribe('tasks');
  return {
    tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
})(App);
cs

 

/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';
import classnames from 'classnames';
 
// Task component - represents a single todo item
export default class Task extends Component {
    toggleChecked() {
        // Set the checked property to the opposite of its current value
        Meteor.call('tasks.setChecked', this.props.task._id, !this.props.task.checked);
    }
 
    deleteThisTask() {
        Meteor.call('tasks.remove', this.props.task._id);
    }
 
    togglePrivate() {
        Meteor.call('tasks.setPrivate', this.props.task._id, !this.props.task.private);
    }
 
    render() {
        // Give tasks a different className when they are checked off,
        // so that we can style them nicely in CSS
        const taskClassName = classnames({
            checked: this.props.task.checked,
            private: this.props.task.private,
        });
        return (
            <li className={taskClassName}>
                <button className="delete" onClick={this.deleteThisTask.bind(this)}> &times; </button>
                <input type="checkbox" readOnly checked={!!this.props.task.checked} onClick={this.toggleChecked.bind(this)} />
                {this.props.showPrivateButton ? (
                    <button className="toggle-private" onClick={this.togglePrivate.bind(this)}>
                        {this.props.task.private ? 'Private' : 'Public'}
                    </button>
                ) : ''}
                <span className="text">
                    <strong>{this.props.task.username}</strong>: {this.props.task.text}
                </span>
            </li>
        );
    }
}
cs


이제 화면을 보면 Private 로 작업을 변경할 수 있습니다.



Private작업은 로그아웃 하면 보이지 않습니다.






반응형

References: Creating your first app



이번엔 User Account를 추가해 봅니다.



1. 유저 기능 추가하기



미티어 프레임 워크에서 제공해주는 accounts-ui와 accounts-password 패키지를 사용합니다.


콘솔에서 다음 명령을 실행합니다: meteor add accounts-ui accoutns-password



accounts-ui는 블레이즈 UI 컴포넌트이므로 리액트에서 사용하기 위해 래핑해줍니다.


/imports/ui/AccountsUIWrapper.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
 
export default class AccountsUIWrapper extends Component {
  componentDidMount() {
    // Use Meteor Blaze to render login buttons
    this.view = Blaze.render(Template.loginButtons,
      ReactDOM.findDOMNode(this.refs.container));
  }
  componentWillUnmount() {
    // Clean up Blaze view
    Blaze.remove(this.view);
  }
  render() {
    // Just render a placeholder container that will be filled in
    return <span ref="container" />;
  }
}
cs


래퍼를 사용하도록 /imports/ui/app.jsx파일을 수정해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
...
            ...
            Hide Completed Tasks
          </label>
          <AccountsUIWrapper />
          <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
            <input
                ...
 
cs


accounts-ui의 기본 id포맷은 email입니다. 이걸 변경할 수 있도록 설정을 추가합니다.


/imports/startup/accounts-config.js


1
2
3
4
5
import { Accounts } from 'meteor/accounts-base';
 
Accounts.ui.config({
  passwordSignupFields: 'USERNAME_ONLY'
});
cs


추가한 설정 파일을 적용합니다.


/clinet/main.jsx


1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
 
import App from '../imports/ui/App';
import '../imports/startup/accounts-config.js';
 
Meteor.startup(() => {
  render(<App />document.getElementById('render-target'));
});
 
cs


이후 화면을 확인하면 Sing in이 추가된 것을 확인할 수 있으며 이를 클릭하면 다음과 같은 화면이 출력됩니다.





2. 작업 목록에 유저 기능 적용하기.



이제 작업 목록에 유저에 대한 정보를 적용 시켜 보겠습니다.

** owner: 작업을 작성한 사용자의 _id

** username: 작업을 작성한 사용자의 이름.


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
import { Meteor } from 'meteor/meteor';
 
// App component - represents the whole app
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hideCompleted: false,
    };
  }
 
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
      owner: Meteor.userId(),           // _id of logged in user
      username: Meteor.user().username,  // username of logged in user
    });
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
 
...
 
export default withTracker(() => {
  return {
    tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
})(App);
cs


그리고 로그인 한 경우에만 새 작업을 생성할 수 있도록 합니다.


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
  render() {
    return (
      <div className="container">
        <header>
        <h1>Todo List ({this.props.incompleteCount})</h1>
          <label className="hide-completed">
            <input
              type="checkbox"
              readOnly
              checked={this.state.hideCompleted}
              onClick={this.toggleHideCompleted.bind(this)}
            />
            Hide Completed Tasks
          </label>
          <AccountsUIWrapper />
          { this.props.currentUser ?
            <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
              <input
                type="text"
                ref="textInput"
                placeholder="Type to add new tasks"
              />
            </form> : ''
          }
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
...
cs


리스트에 유저 이름과 작업이 함께 나오도록 수정합니다.


/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from 'react';
import { Tasks } from '../api/tasks.js';
 
// Task component - represents a single todo item
export default class Task extends Component {
    toggleChecked() {
        // Set the checked property to the opposite of its current value
        Tasks.update(this.props.task._id, {
            $set: { checked: !this.props.task.checked },
        });
    }
 
    deleteThisTask() {
        Tasks.remove(this.props.task._id);
    }
 
    render() {
        // Give tasks a different className when they are checked off,
        // so that we can style them nicely in CSS
        const taskClassName = this.props.task.checked ? 'checked' : '';
        return (
            <li className={taskClassName}>
                <button className="delete" onClick={this.deleteThisTask.bind(this)}> &times; </button>
                <input type="checkbox" readOnly checked={!!this.props.task.checked} onClick={this.toggleChecked.bind(this)} />
                <span className="text">
                    <strong>{this.props.task.username}</strong>: {this.props.task.text}
                </span>
            </li>
        );
    }
}
cs


기존에 있던 작업을 지우고 회원가입을 합니다.



이후 새로운 작업을 추가하면 다음과 같이 보입니다.






반응형

References: Creating your first app




1. Input Form 만들기



계속 콘솔로 데이터를 넣어줄 순 없으니 인풋 폼을 만들어 줍니다.


/imports/ui/app.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
 
// App component - represents the whole app
class App extends Component {
  handleSubmit(event) {
    event.preventDefault();
 
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
 
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
    });
 
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
  renderTasks() {
    return this.props.tasks.map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
  render() {
    return (
      <div className="container">
        <header>
          <h1>Todo List</h1>
          <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
            <input
              type="text"
              ref="textInput"
              placeholder="Type to add new tasks"
            />
          </form>
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  return {
    tasks: Tasks.find({}).fetch(),
  };
})(App);
cs


다음과 같이 인풋 창이 생깁니다. (New task!를 입력한 상태)



이 상태에서 엔터키를 입력하면 데이터가 저장됩니다.


만약 가장 최근에 등록한 글을 맨 위에 보고 싶으면 다음과 같이 수정 해 주시면 됩니다.


/imports/ui/app.jsx Line:49


export default withTracker(() => {
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
};
})(App);





2. 완료 체크박스와 삭제 버튼 만들기



체크 박스와 삭제 버튼을 추가해 봅시다.


/import/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component } from 'react';
 
import { Tasks } from '../api/tasks.js';
 
// Task component - represents a single todo item
export default class Task extends Component {
  toggleChecked() {
    // Set the checked property to the opposite of its current value
    Tasks.update(this.props.task._id, {
      $set: { checked: !this.props.task.checked },
    });
  }
 
  deleteThisTask() {
    Tasks.remove(this.props.task._id);
  }
 
  render() {
    // Give tasks a different className when they are checked off,
    // so that we can style them nicely in CSS
    const taskClassName = this.props.task.checked ? 'checked' : '';
 
    return (
      <li className={taskClassName}>
        <button className="delete" onClick={this.deleteThisTask.bind(this)}>
          &times;
        </button>
 
        <input
          type="checkbox"
          readOnly
          checked={!!this.props.task.checked}
          onClick={this.toggleChecked.bind(this)}
        />
 
        <span className="text">{this.props.task.text}</span>
      </li>
    );
  }
}
cs


다음과 같은 결과를 확인할 수 있습니다.



이번엔 완료 체크된 작업은 숨길 수 있는 기능과 미완료 작업의 갯수를 표시해 보겠습니다.


/imports/ui/app.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
 
// App component - represents the whole app
class App extends Component {
  constructor(props) {
    super(props);
 
    this.state = {
      hideCompleted: false,
    };
  }
 
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
    });
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
 
  renderTasks() {
    let filteredTasks = this.props.tasks;
    if (this.state.hideCompleted) {
      filteredTasks = filteredTasks.filter(task => !task.checked);
    }
    return filteredTasks.map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
 
  toggleHideCompleted() {
    this.setState({
      hideCompleted: !this.state.hideCompleted,
    });
  }
 
  render() {
    return (
      <div className="container">
        <header>
        <h1>Todo List ({this.props.incompleteCount})</h1>
          <label className="hide-completed">
            <input
              type="checkbox"
              readOnly
              checked={this.state.hideCompleted}
              onClick={this.toggleHideCompleted.bind(this)}
            />
            Hide Completed Tasks
          </label>
          <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
            <input
              type="text"
              ref="textInput"
              placeholder="Type to add new tasks"
            />
          </form>
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  return {
      tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
      incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    };
})(App);
cs


완료되지 않은 작업의 갯수를 확인 할 수 있고 Hide Completed Tasks 옵션을 통해 완료한 작업을 숨길 수 있습니다.







반응형

+ Recent posts