Author Archives: nekonitrate

어쩌면 도움이 될지도 모르는 CSS 팁들

1. 가운데 정열하기

Inline 요소는 가운데로 정열하기가 참 쉽습니다.

.inline-element {
  text-align: center;
}

Block 요소 역시 가운데로 정열하는데 어려움이 없습니다.

.block-element {
  margin: auto;
}

하지만 position이 absolute인 요소를 가운데 정열하는데서 난관에 봉착하시는 분들이 많습니다. 그리고 Javascript를 사용하여 굉장히 어려운 길을 돌아가시는 분들도 계십니다.

  // 가운데 정열을 위한 험난한 여정

  var absElement = document.getElementById('abs-element');

  window.addEventListener('resize', function () {
    var windowWidth = window.innerWidth;
    var newWidth = (windowWidth / 2) - (absElement.offsetWidth / 2);

    absElement.style.width = newWidth + 'px';
  });

이런 어려운 길을 선택하지 않아도 absolute 요소도 css만으로 가운데 정열이 가능합니다.

#abs-element {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

참 쉽죠? left 혹은 right 속성은 부모의 width를 기준으로 길이를 잡습다만, transform은 자기 자신의 width를 기준으로 길이를 정합니다. 그 결과 요소는 왼쪽을 기준으로 부모 요소 width의 50% 만큼 이동한뒤, 자기 자신의 width의 50% 만큼을 왼쪽으로 땡깁니다.

같은 방법으로 absolute 요소를 세로 가운데 정열을 할 수 있습니다.

#abs-element {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

그리고 가로, 세로 정중앙에 배치할 수도 있지요.

#abs-element {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

2. 가로 길이에 따라 세로 길이도 같이 변하는 요소 만들기

반응형으로 사이트를 만들면 width 값은 보통 %로 주어 window 가로 길이에 맞게 요소의 가로 길이도 변하게 합니다. 그런데 height의 경우에는 좀 난감한 경우가 발생합니다. 예를 들어 width 대비 1/5길이의 height를 가진 요소를 만들고 싶다면 어떻게 할까요. 보통 Javascript에 의존하는 경우가 많습니다. 저도 꽤 오랜기간 Javascript를 통해서 사이즈를 조정해왔는데요, 이 역시 css만으로 작성이 가능합니다.

  <div id="container">

    <!-- 가로 세로가 5:1인 박스 -->
    <div class="ratio-box">
      <div class="box-contents"> 
        <p>5:1 Box</p>
      </div>
    </div>

  </div>
  .ratio-box {
    position: relative;
    width: 100%;

    // 아래 padding-top을 가로 대비 세로 비율로 지정합니다.
    padding-top: 20%;
  }

  .ratio-box::before {
    content: '';
    display: block;
  }

  .box-contents {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  }

소스가 좀 길어졌군요. #ratio-box와 .box-contents가 한 세트 입니다. padding-top으로 가로 대비 세로의 비율을 지정하고 내부에 들어갈 내용은 .box-contents에 작성합니다. .box-contents의 스타일을 보시게되면 position이 absolute에 상하좌우가 0인 특이한 스타일이 지정되어 있는데, 이렇게 함으로써 .box-contents가 padding 값에 눌리지 않고 .ratio-box 안에 꽉차는 요소가 됩니다.

Codepen에 링크를 첨부하니 참조하셔서 직접 이것저것 만져보면서 확인하시는 것이 이해가 빠르실 겁니다.

3. inline-block으로 정열한 요소의 간격 없애기

inline-block로 요소들을 정열하게 되면 각 요소간에 간격이 발생하게 됩니다. 가끔 이걸 margin으로 없애려고 하시는 분들이 계신데 의외로 굉장히 간단하게 해결됩니다.

#container {
  // inline-block 요소들을 감싸고 있는 부모 요소

  font-size: 0;
}

.items {
  display: inline-block;
}

부모 요소에 font-size: 0을 주는 것으로 간단하게 해결됩니다.

[번역] Dear Web Designer

원문링크

본 포스팅은 저작자의 허락을 받아 번역한 글입니다. 링크를 제외하고, 일부 혹은 전부를 전재할 시에는 출처를 표기해 주시면 감사하겠습니다.

Dear Web Designer

꽤 오래전부터 웹 디자인이란 무엇인가, 웹 디자이너란 어떠해야 하는가에 대해 생각해 왔습니다.

우선 제가 어떤 이력을 갖게 있는지 말씀드린 후 이것에 대해 이야기해보도록 하겠습니다. 저는 DTP 디자이너로서 1년 반 정도를 지냈고, 그 후 웹 디자이너, 마크업 엔지니어를 거쳐 프론트엔드 엔지니어가 되었습니다. 코드 작성에 흥미를 갖기 시작한 후 포지션을 바꿔가며 일 해온지 벌써 10년이는 세월이 흘렀군요. 최근에는 프리랜서로 모 스타트업 웹 서비스의 디자인과 HTML, CSS, Javascript 영역에서 분투중입니다.

여기서 말씀드리는 내용은 ‘웹 서비스에서의 디자인’에 편중된 이야기입니다만, 코퍼레이트 사이트나 랜딩 페이지 등에도 적용될 수 있는 내용일 것이라 생각합니다.

1. 타이포그라피는 웹 디자인에 필요한가?

곧잘 웹 디자인에서는 타이포그라피를 자유롭게 적용하기 힘들다는 의견을 듣곤 합니다. 이런 불편함은 장래에 브라우저의 진화에 따라 충분히 개선될 여지가 있습니다. 하지만 DTP 디자인에서의 타이포그라피와 웹 디자인에서의 타이포그라피는 탄생한 시기도, 기대되는 역할도 완전히 다르기 때문에 DTP 디자인의 타이포그라피와 같은 방향성을 갖진 않을 것입니다.

활판인쇄의 역사에서 태어난 타이포그라피가 완전히 다른 세상인 웹에서 그대로 적용될 수 있을리 없습니다. 디자인의 기초는 오랜 역사를 갖는 DTP 디자인에 있다는 것은 충분히 알고 있습니다만, 어디까지나 기초를 두고 있을 뿐이기 때문에 웹에서의 타이포그라피의 의의에 대해서는 별도로 논의할 여지가 많다고 봅니다.

DTP 디자인

  • 유저에게 어떤 시각적 도구를 통해 전달될 지 정해져 있다.
  • 폰트의 종류와 크기가 유동적이지 않다
  • 어떤 인쇄물이냐에 따라 다르지만 유저는 다양한 환경에서 이를 확인한다.

웹 디자인

  • 유저에게 어떤 시각적 도구를 통해 전달될 지 정해져 있지 않다.
  • 폰트의 종류나 크기가 환경에 따라 유동적으로 변한다.
  • 유저는 다양한 환경에서 이를 확인한다.

좀더 예를 들수도 있지만 가장 큰 차이점으로 알아두시면 될 것은 이런 부분입니다. 문자의 간격이나 개행위치가 디바이스나 유저 환경에 의해 변화하기 때문에 고정된 디자인을 보증하기 어려운데, 문자들의 위치와 크기를 통제하기 위해 주력해야 할까요? 그것을 구현하기 위해 엔지니어의 공수를 투입한 만큼의 효과가 있을까요? 유저에게는 어떤 메리트가 있을까요? 무엇을 가지고 아름다운 타이포그라피라고 해야하는 걸까요?

저는 웹에의 아름다운 타이포그라피란 다양한 환경에서 일정한 틀의 외양을 보증해 주는 것이라고 생각합니다. 다만 개행위치를 어떤 디바이스에서 보아도 같은 곳에서 하는 것이 아니라, 어디서 개행을 해야 그 디바이스에서 가장 읽기 쉬운가를 고려하는 것이 가장 중요합니다. 이런 것이 웹이 필요로하는 타이포그라피의 의의라고 생각합니다.

‘인쇄 디자인에서는 타이포그라피에 신경쓸 수 있으니까 좋아요’. 두번다시 이런 의견이나, 그럴때마다 보이는 자신감있는 표정은 보기 싫습니다. 애초에 그런식으로 비교하는 것이 잘못되었고, 부끄러운 일이라는 것을 하루빨리 깨닳았으면 합니다.

The New Typography

웹의 타이포그라피에 대해 재미있는 고찰이 있어서 소개해 봅니다. 웹 디자이너 중에서 타이포그라피가 좋아서 공부하시는 분들은 많습니다만, 그 차이와 한계에 대해서 깊이 생각하고 계시는 분들은 많지 않은것 같습니다.

2. 범용성있는(재이용 가능한) 디자인이 왜 필요한가?

범용성 있는 디자인을 어려워하는 웹 디자이너가 많습니다. 실재로 프론트엔드 엔지니어를 하면서 범용성 있는 디자인 데이터를 받아본 적은 손에 꼽을 정도록 적습니다. 개성을 표현하기 힘들다느니, 심심한 디자인이 된다느니 듣고 있는 이쪽이 부끄러워 질것 같은 의견을 표명 하시는 분들이 계십니다만, 자신의 능력 없음을 그렇게 자랑스럽게 말씀하지 않으셔도 됩니다. 그 개성이라는 것은 유저에게 도움이 되는 것인가요? 무엇을 위해서 디자인을 하는 걸까요? 세상이 자신의 예술 세계를 이해하지 못한다며 열폭하고 있는 아티스트같은 발언은 안하느니만 못합니다.

범용성 있는 디자인을 하지 못하는 것은 아무것도 생각하고 있지 않기 때문입니다. 왜 필요한지에 대해 생각해 봅시다.

3. 범용성이란?

Atomic Design by Brad Frost

상기한 링크는 우선 범용성에 대해서 좋은 자침이 될 것이라고 생각합니다. 컴포넌트의 원자화에 대한 가이드라인을 제시하는 내용입니다.

공통화된 부분을 조합해서 페이지를 구성하는 웹에서 필수라고 할 수 있는 디자인의 기초 지식입니다. 이러한 사고를 바탕으로 디자인이 된 후에야 비로서 코딩 가이드라인이라는 것을 만들 수 있습니다. 종종 휘황찬란한 디자인을 가지고와서 코딩가이드 라인을 만들어 달라고 하는 경우가 있는데 무리에요, 못합니다.

사이트를 제작하는데 있어서의 지침의 필요성

Atomic Design 에서도 언급하고 있습니다만 정보설계, 정보정리, 각 컨텐츠의 의미부여나 규칙제정 등 지침이라는 것이 필요합니다. 이것을 선정하는 것은 디자이너만의 일은 아닙니다만 디자이너가 중요한 역할을 담당하고 있는 것이 사실입니다. 애초에 디자인이라는 것은 이러한 것이 가능해야 탄생합니다. 이런저것 너무 어렵나요? 일 안하세요?

웹은 디자인하고 끝이 아니라 그 뒤에 기다리고 있는 사람이 있습니다

물론 DTP 디자인에도 인쇄회사에서 디자인 이후의 작업을 담당하는 사람들이 있습니다만, 웹과 비교하면 디자인이 그 뒤의 작업에 영향을 미치는 정도는 적습니다. 웹의 경우 코드를 작성하는 사람에 대해 고려하지 않거나, 의식조차 하지 않는 분들이 많은 것 같습니다. 그런 의식이 있다면 범용성을 갖춘 디자인이 올라올테니까요.

  • 디자인에 범용성이 없다
  • 코드에도 범용성이 없어 코드의 양이 증가한다
  • 페이지에 표시속도가 느려지고 버그가 많아진다
  • 범용성이 없이 때문에 이후 유지보수에 들어가는 코스트가 증가한다

디자인에 범용성이 없다면 기술적 부채가 시작부터 발생하며 이후 개발에도 악순환을 일으킵니다. 자신의 디자인이 이후 공수나 팀의 모티베이션에 큰 영향을 준다는 것을 명확하게 인식해 주셨으면 합니다. 버튼이나 리스트 디자인에 정합성이 있어야 기술적으로도 최적화가 가능하고, 유저도 사이트를 조작하는데 있어 학습 코스트가 낮아집니다. 모든 것이 좋아집니다. 그래도 어렵다고 어물쩡 넘어갈 수 있다고 생각하시나요?

4. DTP 디자인을 할 수 있다면 웹 디자인을 할 수 있다?

이것이야 말로 어리석음의 정점입니다. 디자인 툴을 잘 다룬다는 점에서는 긍정할 수 있을지 모르겠습다. 또 종종 웹 디자인이라는 분야를 낮게 보는 분들도 있습니다. 그런 분들을 디자이너라고 부를 수 있을까를 따지기 이전에 인간으로서 됨됨이부터 챙겨야 할 것입니다. 그런분들과는 친구는 둘째치고 같은 공간에 있고 싶지 않습니다. 공기 아까우니 숨쉬는 것도 자제바랍니다.

몇번이고 말씀드리지만 완전히 별개의 물건입니다. 각각 디자인의 의의가 다르고 필요로하는 능력도 대부분 다릅니다. 이게 같은 것이라고 생각하는 사람은 DTP와 WEB 모두 제대로 알지 못하고 있다는 사실을 자각하세요. 부탁합니다.

@jacob_usability | Twitter

웹 디자인이라고 하면 야곱 박사입니다만, 이분의 Twitter Bot을 개인적으로 좋아하기 때문에 소개해봅니다.

5. 그외

이야기가 많이 샜습니다만 어려운 디자인을 마크업하는 것이 즐겁다고 하시는 분들도 있습니다. 이것도 이해하기 힘들군요. 노력해야할 대상을 잘못 찾고 계십니다. 프로로서 어려워도 마크업할 수 있는 능력을 갖주는 것은 당연한 일입니다. 이런분들에게는 어떤 말씀을 드려야 할지 아직도 결론을 내리지 못하고 있습니다.

6. 정리

새로운 웹 디자인의 가능성을 모색할 수 없는 디자니어를 WWW는 필요로 하지 않는다.

이 한문장이 결론입니다.

저 자신의 이력을 적고 시작한 시점에서 이미 훌륭한 꼰대질을 한 것 같기도 합니다. 저 자신의 생각을 정리한 것이므로 다른 분들의 의견도 대환영합니다.

Windows10 으로 업데이트 했더니 Apache 서버가 죽었음다

XAMPP로 로컬에서 Apache 서버를 돌리고 있습니다만, Windows 10(기존 7)으로 업데이트를 하자 다음과 같은 에러메세지와 함께 서버가 동작하지 않습니다.

Error: Apache shutdown unexpectedly.
This may be due to a blocked port, missing dependencies, 
improper privileges, a crash, or a shutdown by another method.
Press the Logs button to view error logs and check
the Windows Event Viewer for more clues
If you need more help, copy and post this
entire log window on the forums

어떤 프로세스가 겁도 없이 80포트를 점령하고 있는 듯 했습니다. 작업관리자에서 PID를 확인해 보니 ‘시스템 및 압축된 메모리’ 라는 프로세스가 돌아가고 있는데, 이걸 정지 시키는 건 뭔가 해결책이 아닌것 같아보였습니다. 일단 이름부터 뭔가 중요한 프로세스 같아보이기도 했구요.

좀더 찾아보니 의외로 간단한 해결법이 있었습니다.

제어판 > 시스템 및 보안 > 관리 도구 > 서비스 로 들어가셔서 World Wide Web Publishing Service 이라는 서비스를 찾아서 “중지” 시킵니다. 이녀석이 80 포트를 점령하고 있던 진범이라는 군요.

[HTML5] input 테그에 autocomplete 속성 제대로 활용하기

최근 웹브라우저들은 폼(Form)을 자동으로 입력해주는 기능을 탑재하고 있습니다. 사용자 입장에서는 매번 같은 내용을 반복해서 입력하지 않아도되기 때문에 상당히 편리한 기능이지요. 하지만 이 기능이 제대로 동작하게 하기 위해서는 HTML 문서를 작성할 때 신경을 써주어야 하는 부분이 있습니다. 바로 autocomplete 이라는 속성(Attribute)입니다.

1. 사용법


autocomplete 은 input 테그에 사용되는 속성으로, 이를 설정해두면 자동 입력기능이 활성화 됩니다. 이 속성을 적절하게 설정해주면 브라우저가 등록되어 있는 내용을 자동으로 입력해 줍니다. 대표적인 항목은 아래와 같습니다.

  • name: 성명
  • family-name: 성
  • given-name: 이름
  • email: 이메일주소
  • postal-code: 우편번호
  • address-level1: 주소(도, 시)
  • address-level2: 주소(시, 군, 구)
  • address-line1: 상세주소 1
  • address-line2: 상세주소 2
  • organization: 직장명
  • off: 자동입력 기능을 사용하지 않음

그 외의 항목들에 대해서는 WHATW Standard에서 AutoFill(링크)에 관련한 사양을 확인해 보시기 바랍니다. autocomplete 속성을 off로 설정할 수 있지만 현재 대부분의 브라우저에서는 이 설정을 무시하고 있기때문에 사실상 의미가 없습니다. 따라서 자동입력 기능의 사용여부는 유저에게 맡기고, 사용하는 유저가 편리하게 이용할 수 있도록 하는 것이 좋습니다.

2. 주의점

autocomplete 기능은 name 속성을 기준으로 동작하기 때문에, name 속성의 값을 위의 명명규칙에 따라 지정하지 않으면 autocomplete 기능이 동작하지 않으며 의도치 않게 엉뚱한 곳에 값을 입력하여 사용자를 쓸데없이 불편하게 만들 수 있으니 주의하시기 바랍니다.

성명을 성과 이름으로 분리하는 것은 브라우저마다 다르게 동작하는 경우가 있으므로 성명(name)으로 입력받는 것이 좋습니다. 또 주소의 경우에도 select 를 사용하면 자동 입력이 되지 않기 때문에 input을 사용합니다. 생년월일 자동입력은 크롬에서 동작하지 않습니다. 전화번호나 우편번호를 하이픈 단위로 끊어서 입력받는 경우가 많은데 autocomplete을 이용한다면 하나의 입력란에서 받는것이 좋습니다.

3. 기타 알아두면 좋은 기능들

iOS에서는 영문 입력시 첫글자를 대문자로 변환하거나 스펠링 체크를 하는 기능이 있습니다. 하지만 이 기능은 경우에 따라 사용자를 불편하게 만들 수 있는데 autocorrect와 autocapitalize라는 속성의 값을 off로 설정하여 이를 사용하지 않도록 할 수 있습니다.


폼 입력 도중에 사용자가 실수로 뒤로 가기나 새로고침등을 누르는 경우가 있습니다. 기존에 입력한 내용이 죄다 날아가기 때문에 여간 짜증가는 일이 아닙니다. 하지만 폼을 이탈할 때 사용자에게 confirm을 띄워서 사용자에게 확인을 요청할 수 있습니다.

window.addEventListener('beforeunload' function (event) {
  var confirmationMessage = '입력 내용이 모두 사라집니다.';
  event.returnValue = confirmationMessage;

  return confirmationMessage;
});

beforeunload 이벤트는 Mobile Safari에서는 지원하지 않습니다.

참조

[ES6] var, let 그리고 const

지금까지 Javscript 에서 변수를 선언하는 방법은 var 를 이용하는 것 뿐이었습니다. 물론 Javascript 특성상 var 없이 변수를 선언하는 것도 가능하지만 부작용이 심각하기 때문에 실무에서는 적용하지 않는 것이 보통입니다. 일이 아니더라도 var 선언 없이 변수를 사용하는 것은 정신건강에 좋지 않기 때문에 절대로 사용하지 말 것을 강력하게 권장합니다.

본론으로 넘어가서, ES6로 넘어오면서 let과 const라는 새로운 선언방법이 생겼습니다. 새롭게 탄생한 let와 const는 도대체 어떤 녀석이며, 어떤 특징이 있는지 한번 짚어보고자 합니다.

1. 이미 선언 하셨는데요?

var 는 상당히 너그러운 친구입니다.

var foo = 'bar1';
var foo = 'bar2';

같은 이름의 변수를 두번 선언하고 있습니다. foo라는 변수에는 ‘bar1′ 이라는 문자열이 할당되었다가 다음 선언에서 ‘bar2’라는 변수가 할당됩니다. 별다른 에러도 발생시키지 않습니다.

하지만 let과 const는 엄격합니다.

let foo = 'bar1';
let foo = 'bar2';

// ERROR: Uncaught SyntaxError: Identifier 'foo' has already been declared

앞서 선언한 변수를 다시 선언하게 되면 칼 같은 오류를 발생시킵니다. 규모가 큰 코드에서 버그를 방지 할 수 있는 매우 바람직한 특징입니다.

2. 난 그때 거기 없었어요

뜬금 없이 변수 하나를 호출해 보겠습니다.

console.log(foo);
// Error: Uncaught ReferenceError: foo is not defined

어디에도 존재하지 않는 변수인 foo를 호출하면 당연히 에러가 발생합니다. 하지만 var를 이용해 아래와 같이 변수를 선언하면 조금 재미있는 현상이 발생합니다.

console.log(foo); // undefined

var foo;

더 이상 에러가 발생하지 않습니다. 변수 foo는 값이 정의되지 않는 형(type)인 undefined가 되어 있을 뿐입니다. 선언보다 호출이 먼저 있었음에도 불구하고 이 코드는 정상적으로 작동합니다. 왜 이런 현상이 발생하는지는 Hoisting(호이스팅) 이라는 키워드로 검색해 보시길 바랍니다.

그럼 let과 const는 어떨까요?

console.log(foo);
// Error: Uncaught ReferenceError: foo is not defined

let foo;

호출한 시점에서 변수가 선언되어 있지 않음을 알리는 에러가 발생합니다. 일시적 사각지대(Temporal Dead Zone; TDZ) 라는 개념인데, 개인적인으로는 특정 개념을 정의해서 설명하지 않더라도 let과 const의 동작방식이 직관적이고 자연스럽다고 생각합니다.

3. 이 블록의 변수는 나야!

변수의 유효 범위에 대한 부분입니다. var의 경우에는 Function-scope 라고 합니다. 유효 범위가 함수 단위라는 이야기죠.

var foo = 'bar1';
cosole.log(foo); // bar1

if (true) {
  var foo = 'bar2';
  console.log(foo); // bar2
}

console.log(foo); // bar2

위 코드가 하나의 함수 구문 안에 존재한다고 가정했을 때 if문 밖의 변수 foo와 if문 안의 변수 foo는 동일한 변수가 됩니다. 중복 선언을 했지만 앞서 말한바와 같이 별다른 에러를 발생시키지 않고, 값마저 ‘bar2’로 변경해 버렸습니다.

하지만 let과 const Block-scope 라고 합니다. 유효 범위가 블록, 즉 {}로 감싸지 범위라는 이야기 입니다.

let foo = 'bar1';
console.log(foo); // bar1

if (true) {
  let foo = 'bar2';
  console.log(foo) // bar2
}

console.log(foo); // bar1

위 코드에서는 var를 사용한 경우와는 달리 if문 밖의 foo와 if문 안의 foo는 서로 다른 변수 입니다. 따라서 중복 선언으로 인한 에러도 발생하지 않으며, if문 안쪽에서 선언한 foo의 경우는 if문이 닫히는 시점에서 유효범위가 끝납니다.

그런데 여기서 의문이 생깁니다. if문 안에서 foo를 먼저 호출한 다음 let으로 foo를 다시 선언하게 되면 어떤일이 발생할까요? 일단 if 문에서 단순히 foo를 호출만 해보면

let foo = 'bar1';
console.log(foo); // bar1

if (true) {
  console.log(foo) // bar1
  foo = 'bar2';
  console.log(foo) // bar2
}

console.log(foo); // bar2

정상적으로 호출도되고 값의 변경에도 아무 문제가 없습니다. 그런 foo 호출 이후에 let으로 foo를 선언해 보겠습니다.

let foo = 'bar1';
console.log(foo); // bar1

if (true) {
  console.log(foo);
  // Uncaught ReferenceError: foo is not defined

  let foo = 'bar2';
}

console.log(foo);

foo는 정의되지 않았다는 에러가 발생합니다. 앞에서 말한 임시적 사각지대(TDZ)의 정체가 이것입니다. 어떤 변수가 호출되었을 때 블록 안에 같은 이름의 변수가 없으면 상위 블록에서 선언된 같은 이름의 변수를 호출합니다. 하지만 블록 안에서 let이나 const로 변수 선언이 있었다면 그 이름의 변수는 변수가 선언되기 이전까지 그 블록안에서는 정의되지 않은 변수로 간주되는 것이죠.

4. let과 const는 적절한 관계

그럼 let과 const는 무슨 차이가 있는 걸까요. 일단 이름으로만 봐서는 const는 상수 선언으로 보이는데 말입니다. 실제로 원시형(Primitives type: string, number, boolean, null, undefined)에서 const는 상수로 동작합니다. 따라서 const 로 선언되면 값을 재할당 할 경우 에러가 발생합니다(당연하지만 초기값을 설정하지 않아도 에러가 발생합니다)

const foo = 0;
foo = 1;
// Error: Uncaught TypeError: Assignment to constant variable.

따라서 단순형의 경우 값의 변경이 있는 경우에는 let으로, 상수로 사용하는 경우에는 const로 선언하는 것이 바람직하겠습니다.

하지만, 참조형(Complex type: array, object, function)의 경우 결론부터 말씀드리면 const 로 선언하는 것이 바람직합니다. 참조형은 const로 선언하더라도 멤버값을 조작하는 것이 가능합니다.

const foo = [0, 1];
const bar = foo;

foo.push(2);
bar[0] = 10;

console.log(foo, bar)
// [10, 1, 2] [10, 1, 2]

위의 결과를 보시면 아시겠지만 const bar = foo; 의 선언으로 bar는 foo를 참조합니다. 참조가 아니라 값을 복사(copy)하는 경우에는 array는 … 연산자를 사용하고, object는 assign()함수를 사용합니다.

const arg = [0, 1];
const obj = {foo: 'bar'};

const newArg = [...arg];
const newObj = Ojbect.assign({}, obj);

newArg[0] = 10;
newObj.foo = 'rab';

console.log(arg, obj);
// [0, 1], {foo: 'bar'}

console.log(newArg, newObj);
// [10, 1], {foo: 'rab'}

5. 결론
– ES6 에서는 var는 지양하고 가급적 let과 const를 사용하자
– 원시형에서 변수는 let, 상수는 const로 선언한다
– 참조형은 const로 선언한다

Lavral Elixir로 Bower 모듈 가져오기

레퍼런스
NPM Package: Laravel elixir wiredep
NPM Package: Laravel elixir useref

bower로 설치한 모듈들을 elixir로 관리하는 방법은 여러가지가 있겠지만 여기서는 제가 쓰는 방법을 소개 합니다. 현재 Laravel은 최초 설정된 npm 모듈만 설치되어 있고 아직 bower 모듈은 설치되지 않은 상태로 가정합니다. bower_components 에서 가져오는 모듈 이외에 사용자가 작성한 파일들에 대한 처리는 편의상 생략하도록 하겠습니다.

1. 준비
먼저 루트에 .bowerrc라는 파일을 생성하고 아래의 내용을 입력해 줍니다. bower 모듈은 public/bower_components 디렉토리에 설치하라는 의미죠.

{
  "directory": "public/bower_components"
}

그리고 필요한 elixir 모듈을 설치합니다. 가장 기본적으로 사용되는 모듈은 laravel-elixir-wiredep과 laravel-elixir-useref죠. npm으로 이를 설치합니다.

  npm install laravel-elixir-wirdep laravel-elixir-useref --save-dev

그리고 자신이 사용하고자하는 bower 모듈들을 인스톨합니다.

2. bower_componet 모듈 가져오기
views 디렉토리의 blade 템플릿 파일 중에서 script와 stylesheet를 로드하는 역할을 하는 파일을 불러옵니다. 저는 layout/master.blade.php 라는 파일이네요. 파일을 열고 <head>태그 안쪽에 다음과 같은 내용을 입력합니다.

(주: js를 로드하는 script 태그의 위치는 body 태그가 끝나기 직전에 위치하는 것을 개인적으로 선호하고 그렇게 하는 것이 바람직하다고 생각하지만 여기서는 편의상 head 태그 안쪽에 배치하겠습니다. 또한 프로젝트에 AngularJS를 적용하고 블레이드 템플릿을 사용하는 경우 Angular에서 사용하는 {{ }}는 @를 붙여서 @{{ }} 이런 형태로 사용하여야 합니다. 그런데 Angular보다 html이 먼저 로딩되는 경우 짧은 순간이지만 Angular에서 렌더링하지 않은 상태의 @{{ }} 가 그대로 나타나는 경우가 있습니다. 이 경우에는 부득이하게 Angular 관련 스크립트는 head 쪽에서 로드하는 것이 바람직합니다.)

<!-- bower:css -->
<!-- endbower -->

<!-- bower:js -->
<!-- endbower -->

이제 잠시 gulpfile.js로 넘어와서 스크립트를 작성해보겠습니다.

var elixir = require('laravel-elixir');

// 아까 설치한 두 모듈을 로드합니다.
require('laravel-elixir-wiredep');
require('laravel-elixir-useref');

elixir(function (mix) {
  mix.sass('app.scss')
    .wiredep();
});

mix.sass(‘app.scss’)는 처음부터 작성되어 있는 내용입니다. 여기에 wiredep()을 추가하고 gulp를 돌려봅니다. 그리고 아까 bower로 시작하는 주석문을 달아준 master.blade.php를 열어보면 …

<!-- bower:css -->
  <link rel="stylesheet" href="/bower_components/sweetalert/dist/sweetalert.css" />
  // 이하생략
<!-- endbower -->

<!-- bower:js -->
  <script src="/bower_components/sweetalert/dist/sweetalert.min.js"></script>
  // 이하생략
<!-- endbower -->

신박하게도 주석문과 주석문 사이에 bower로 설치한 js 파일과 css 파일을 로드하는 스크립트가 자동으로 생성됐습니다.

3. 가져온 모듈 빌드하기

여기서 한발 더 낳아가 이 모듈들을 js의 경우에는 vendor.js, css의 경우에는 vendor.css라는 이름으로 병합해보겠습니다. 아까 열어놓은 master.blade.php 를 다시 편집합니다.

<!-- build:css(public) css/vendor.css -->
<!-- bower:css -->
  <link rel="stylesheet" href="/bower_components/sweetalert/dist/sweetalert.css" />
  // 이하생략
<!-- endbower -->
<!-- endbuild -->

<!-- build:js(public) js/vendor.js -->
<!-- bower:js -->
  <script src="/bower_components/sweetalert/dist/sweetalert.min.js"></script>
  // 이하생략
<!-- endbower -->
<!-- endbuild -->

그리고 gulpfile.js를 열어서 편집을 합니다.

var elixir = require('laravel-elixir');

// 아까 설치한 두 모듈을 로드합니다.
require('laravel-elixir-wiredep');
require('laravel-elixir-useref');

elixir(function (mix) {
  mix.sass('app.scss')
    .wiredep()
    .useref({
      src: 'layouts/master.blade.php'
    }, {
      searchPath: 'public'
    });
});

이제 gulp를 실행해보겠습니다. public/js 와 public/css 에 각각 vendor.js, vendor.css가 생성된 것을 알 수 있습니다. 하지만 정작 브라우저에서 개발자 도구를 열어 확인해 보면 vendor 파일을 가져오는 것이 아니라 합쳐지기 전의 bower_components 에 있는 파일들을 로드하고 있음을 확인할 수 있습니다.

4. 마무리

다시 master.blade.php로 돌아가서 약간의 수정을 가해봅니다.

@if(config('app.debug'))
  <!-- build:css(public) css/vendor.css -->
  <!-- bower:css -->
    <link rel="stylesheet" href="/bower_components/sweetalert/dist/sweetalert.css" />
    // 이하생략
  <!-- endbower -->
  <!-- endbuild -->
@else
  <script src="{{elixir('css/vendor.css'}}">
@endif

@if(config('app.debug'))
  <!-- build:css(public) js/vendor.js -->
  <!-- bower:js -->
  <script src="/bower_components/sweetalert/dist/sweetalert.min.js"></script>
  // 이하생략
  <!-- endbower -->
  <!-- endbuild -->
@else
  <script src="{{elixir('js/vendor.js'}}"></script>
@endif

Laravel이 Debug 모드일 때는 bower_components에서 파일을 불러오지만 Debug 모드가 아닐때는 elixir() 핼퍼 함수에서 생성한 경로에서 소스 파일을 가져오게 됩니다. 하지만 elixir() 핼퍼 함수로부터 해싱된 경로를 반환받기 위해서는 gulpfile.js 에서 먼저 해줘야 할 것이 있습니다.

var elixir = require('laravel-elixir');

// 아까 설치한 두 모듈을 로드합니다.
require('laravel-elixir-wiredep');
require('laravel-elixir-useref');

elixir(function (mix) {
  mix.sass('app.scss')
    .wiredep()
    .useref({
      src: 'layouts/master.blade.php'
    }, {
      searchPath: 'public'
    })
    .version(['js/vendor.js', 'css/vendor.css']);
});

이제 gulp를 돌려보겠습니다. Laravel의 Debug 모드는 루트의 .env 파일에서 변경할 수 있는데(config/app.php 가 아니라), 디버그 모드의 설정 여부에 따라 빌드된 vendor 파일을 불러오거나, bower_components 에서 파일을 불러오는 것을 확인 할 수 있습니다. 각 elixir 모듈의 세부적인 옵션들은 상단 링크에서 확인 하실 수 있습니다.

Windows에서 npm 버전업 하기…

npm 으로 laravel-elixir-wiredep을 설치하려고 하니 npm 버전이 낮다며 설치가 안된다. npm 3.0.0 이상이 설치되어 있어야 한다고 하는데 지금 설치되었 있는데 2.x 버전이니 정말 어지간히도 버전업 안했구나라는 자기반성(?)과 함께

npm install -g npm

커맨드를 실행시켰다. 업데이트가 끝나고 다시 wiredep을 설치하려고 보니 같은 에러가 나면서 설치가 되지 않았다. npm -v 로 확인해 보니 여전히 예전 버전이 깔려있다. 중간에 한 삽질은 생략하고 결론만 말하면 nodejs가 설치된 폴더에 가서 npm을 설치해주면 된다. 나의 경우에는 c:\Program Files\nodejs 에 설치가 되어 있으므로 해당 폴더로 이동후 다음 커맨드를 입력한다.

c:\Program Files\nodejs> npm install npm@latest

-g 옵션이 붙지 않은 것에 주의하자. npm -v 로 확인해보니 이번에는 제대로 버전업 되어 있다.

angular-masonry-directive 모듈 imagesLoaded 에러

angular-masonry-directive 라는 모듈이 있다. Masonry 배치를 jQuery 없이 구현할 수 있고 무엇보다도 사용이 무척 간단하다. 그런데 이 directive 모듈을 로드하고 앱을 실행시키면 function undefined 에러가 발생하는데 소스파일을 수정해 주면 말끔히 해결된다. 글을 쓰는 시점을 기준으로 60행을 보면 directive(‘masonryTile’) 로 시작하는 코드가 보인다. 코드를 아래와 같이 수정하자.

}).directive('masonryTile', function () {
  return {
    restrict: 'AC',
    link: function(scope, elem) {
    // 생략
    if (update) {
      // 원래 소스 
      // imagesLoaded( elem.get(0), update);

      // 수정된 소스
      imagesLoaded( elem.eq(0), update);
    }
    if (appendBricks) {
      // 원래 소스
      // imagesLoaded( elem.get(0), appendBricks(elem));

      // 수정된 소스
      imagesLoaded( elem.eq(0), appendBricks(elem));
    }

    // 이하 생략

제대로 작동할 것이다. 하지만, bower를 이용해서 설치한 경우라면 또 다른 에러가 발생한다. Dependency 모듈인 masonry 문제인데 기본 파일이 masonry.js 로 되어있다. 같은 디렉토리에 있는 masonry.pkgd.js 로 바꿔서 로드하면 해결된다. gulp나 grunt를 이용해서 injection 을 자동으로 수행하고 있는 경우라면, bower.json 을 수정하면 된다. bower.json 에 아래 내용을 추가하자.

"overrides" : {
  "masonry": {
    "main": "./dist/masonry.pkgd.js",
    "dependencies": {}
  }
}

gulp나 grunt에 설치된 모듈(보통 main-bower-files 일 것이다)이 메인 파일을 masonry.pkgd.js 로 변경해서 injection 해 줄것이다. 아 근데 정말 사람을 왜 이렇게 괴롭혀요…

AngularJS $http 함수 CORS 설정

[2016년 1월 15일: 추기]
AngularJS에서 $http로 POST 요청(그리고 아마도 DELETE와 PUT도 마찬가지로)을 PHP 서버로 보내게 되면, $_POST에 아무것도 잡히지 않습니다. 이 경우에도 아래처럼 헤더를 변경(Content-Type만 변경해 주면 됩니다)해 주고, 데이터를 쿼리스트링으로 파싱하여 보내면 정상적으로 송신됩니다. 대체 왜…
[마침]

AngularJS 에서 기본설정으로 Cross-Domain 에 대해 AJAX 요청을 보내면 애석하게도 CORS에러가 발생합니다. 만일 프로젝트에 jQuery를 사용하고 있다면 $.ajax 함수를 이용해 비교적 간단하게 해결할 수 있습니다만, 프로젝트에 jQuery를 사용하지 않은 상황이라면 AJAX 통신만을 위해 중간에 jQuery를 도입하는 것도 좀 애매합니다. AngularJS 에서 $http 만으로 Cross-Domain에 대한 AJAX 통신을 위해서는 크게 두 가지 작업을 해야 합니다. 첫번째는 헤더의 변경, 두번째는 JSON 데이터를 URL 쿼리 문장으로 파싱하는 것입니다.

우선 헤더를 변경합니다. angular.module.config()가 위치한 파일(통상 app.js)에 $httpProvider를 인젝션하고 다음과 같이 헤더를 변경합니다.

angular
  .module('...', [...])
  .config(function ($httpProvider) {

    // $httpProvider에서 header 를 수정합니다.

    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
    $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

  });

다음으로 실제 $http로 데이터를 포함한 요청을 보낼때 데이터를 파싱합니다.

// JSON 형식을 쿼리스트링으로 파싱합니다.
var jsonToUrlString = function (json) {
  var string = '';

  string = Object.keys(json).map(function (key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
  }).join('&');

  return string;
};

// cross-domain에 POST 요청을 날립니다.
var postRequest = function (jsonData) {
  jsonData = jsonToUrlString(jsonData);

  $http({
    url: apiUrl,
    method: 'POST',
    data: jsonData
  })
  .success(function (data) {
    // do something
  });
};

이 설정으로 Cross-Domain 에 성공적으로 통신을 할 수 있습니다. JSON 형식을 그대로 사용할 수 없을까해서 header의 Content-Type을 application/json으로 변경해서 시도를 해보기도 했지만 좌절되었습니다. JSON을 쿼리 스트링으로 파싱하지 않고 보내면 CORS 에러와 함께 500 error가 함께 딸려오는 상황을 볼 수 있습니다.