2013. 10. 31.

자바스크립트 javascript form onsubmit 양식 제출 되는 문제.

HTML form의 submit 문제는 여러가지 이유에서 발생한다.
form.submit(); return true; 라고 했을 때 두번 submit 되는 문제도 있고, 이것 저것 많다.
이번 포스팅에서는 폼의 onsubmit 함수 안에서 문제가 발생했을 때를 보고자 한다.

아래는 일반적인 onsubmit 속성을 가지고 있는 form과 그 함수이다.

<form onsubmit="return MGY_submit(this);">
    <input type="text" id="value1">
    <input type="image" value="submit">
</form>
function MGY_submit(f){
    var _str = $("#value2").val().substr(0, 4);
    if( _str && _str != "" ){
        return true;
    }
    alert( "빈칸은 안되요" );
    return false;
}

예제 적다 보니 생각난 건데, image input이 form 안에 있을 경우 submit 버튼과 같은 역할을 해버려서 가끔 문제가 생긴다.

아무튼, 버튼을 누르면 form이 submit 하게 되면서 MGY_submit() 함수를 호출한다.
이 함수에서 true 반환하면 정상적으로 submit 하게 되고, false 를 반환하면 submit 되지 않는다. 제자리에 머문다.

MGY_submit() 함수를 보면 문제가 하나 있다. jquery를 이용해 input을 id값으로 참조해 value를 가져오는데, id가 value2 인 input은 form안에 존재하지 않는다. 이런 경우 어떻게 될까?

존재하지 않는 객체를 참조하여 value를 반환하는 함수 val()을 호출 했다. 그리고 그 값에서 일정 부분 추출하는 substr() 함수를 사용했다. 허공에 문자열을 추출하려 했으므로 에러가 나는 시점이다. _str 변수 안에는 값이 들어오지 않았다. 선언과 동시에 초기화가 되는 순간에 에러가 발생했다. 에러 발생 후의 자바스크립트는 더 이상 다음 코드를 실행하지 않는다. if 문은 실행되지 않는다.

그럼 리턴값을 기다리고 있는 form에게 가는 값은 무엇일까? true가 간다.
_str 이 초기화도 안되고, 값도 들어가지 않았다. if문을 통과할 수가 없는데, 심지어 alert()도 반응하지 않고 form은 submit 된다. 왜 그러냐고? 나도 모른다. form의 onsubmit 함수 실행 중에 에러나면 form이 submit 돼버리더라. 라고 기억해 놓으면 언젠가 도움이 된다.

그럼 어떻게 하면 좋을까? try - catch 구문을 사용하자.

function MGY_submit(f){
   try{
      var _str = $("#value2").val().substr(0, 4);
      if( _str && _str != "" ){
          return true;
      }
      else {
          alert( "빈칸은 안돼요" );
          return false;
      }
   } 
   catch ( error ){
      alert( error );
      return false;
   }
}

try 구문 안에서 문법적 에러가 발생하면, catch 부분의 코드가 실행된다. try - catch는 예상치 못한 에러로 프로그램이 비정상적으로 종료되거나 하는 상황을 예방 할 수 있는 아주 좋은 안전 장치다.

javascript try catch error message

캡쳐할때 당시엔 match() 함수를 사용했었다. 에러를 출력해주고 제자리에서 머물게 된다. form은 submit 되지 않는다. form이 잘못 submit 되었을 경우에, DB 등 상황이 곤란해질 우려가 있으므로 신중하게 처리해 주는 편이 좋겠다.

가끔은 아니 자주, 컴퓨터가 말을 좀 했으면 좋겠다.

고양이가 할퀴어서 상처가 나면? 고양이 발톱에 세균이 많을까?


고양이 발톱 할퀸 자국
이정도 상처는 아무것도 아니라는...

손으로 놀아주지 말자 라고 매번 생각하면서도 그게 잘 생각대로 안됩니다. 이번엔 좀 거칠게 당했네요.
고양이에게 긁히면 유독 긁힌 부위가 다른 곳에 긁혔을때 보다 더 붓습니다. 아마 고양이 발톱에 있는 뭔지 모를 세균 때문인 것 같은데... 소독약 발라놓고 이것 저것 좀 찾아 봤습니다.

반려동물에게서 옮을 수 있는 병들로 3가지 정도 글들이 많더군요.
고양이 발톱병( 묘소[조]병 ), 샤가스병, 선페스트!

고양이 발톱병은 공격당하고 며칠이 지나서 두부, 경부, 겨드랑이(!?) 가 붓고... 심하면 곪아 터지기도... 헌데 대부분 건강한 사람이라면 별 문제 없이, 후유증 없이 치유 된답니다. 고양이들은 벼룩한테서 옮는 병이라네요.

샤가스병남미쪽 열대질병이고, 키스벌레라는 '크리아민 노린재'가 옮기는 병이랍니다. 사람, 개, 고양이, 여우, 다람쥐, 쥐 등이 병원균을 보유하고 있을 수 있다네요. 저 무시무시한 벌레는 우리가 잘때 피를 잡수시고 액체 용변을 싸버리는데, 이 안에 기생충 들이 있답니다. 키스벌레라는 별명이 붙은 이유는 이 벌레가 연약한 살 주위를 좋아해서 사람의 경우 주로 입술 근처에서 피를 빨아 먹고 입 주변에 용변을 싸버린다네요... 사람들이 자다가 슥 문지른다던가 하면 입으로 들어가게 되고 그렇게 감염 되버립니다. 남미에선 이 병으로 많은 사람들이 죽어가고 있다는 군요. 허허.

선페스트에서 선은 왜 붙었나 했는데, 선페스트는 페스트의 한 종류였군요. 가장 흔하게 발생하는 페스트의 한 종류랍니다. 주로 페스트 걸린 쥐나 페스트 걸린 사람을 문 벼룩에게서 옮을 수 있답니다.

여기저기 이리저리 사전도 검색해보고 뉴스 기사도 봤습니다. 집 고양이에게서 걸리기 쉽지 않은 병처럼 보이는데 뉴스에서는 마치 당장 반려동물들 내다 버려야 된다는 것 처럼 써놓은 기사들이 많네요. 허허.

한달에 한번은 목 뒤에 진드기 약 바르러 가고, 거의 이틀에 한번은 청소기 돌리고, 하루에 한번은 바닥을 닦는지라... 그다지 와닿지 않는 것들이군요.

열정적인 뜀박질, 장난감 쥐에 날리는 불꽃 싸다구의 세기와, 마구 생산되는 맛동산과 감자들을 지켜보니 아주 건강해 보입니다.

잘 먹고 잘 싸는게 최고라고 배웠습니다... :-D

러시안블루 고양이 라면봉지
긁히고 비명지르니 저리 도망가버렸다는...

2013. 10. 29.

세탁소 옷걸이와 티셔츠로 고양이 텐트를 만들어 보자 :- )



집 구석구석 먼지 많은 곳이나 청소하기 힘든 곳들 여기저기를 막았습니다만 녀석... 뭔가 흥이 떨어져 보입니다. 따로 고양이 집 같은걸 사지 않았던 터라 고민을 하던 차에 괜찮은 것을 발견 했습니다. :-D

안입는 티셔츠와 세탁소 옷걸이 4개로 고양이 텐트를 만들어 봤습니다.
뒤적 뒤적 검색해보니 다른분들께서도 많이 만들어 놓으셨더군요. 흐흐.

만드는 방법은 아주 간단 합니다.
손이 조금 아프긴 했지만... 펜치가 있어야 편하실 겁니다... ㅜ_ㅜ

준비물 입니다.

① 세탁소 옷걸이 4개. 
② 안입는 티셔츠.
③ 무릎 담요 정도. 텐트 안에 넣을꺼에요.
④ 고정용 테이프.
⑤ 그리고 가장 핵심인 미끄러 지지 않는 다부진 손 또는 펜치.


일단 저 옷걸이를 일자로 펴야 합니다...
저처럼 손에 땀이 많으신분들은 꼭 장갑 끼셔야 합니다.
힘주다 미끄러져서 옷걸이 끝 부분에 찔립니다!! 아파요... ㅜ
힘 주면서 이리저리 옷걸이가 움직일텐데 달려드는 고양이가 찔리지 않게도 조심 조심...

펜치가 있으시다면 요리조리 잘 피시면 됩니다.
펜치로 하는게 더 깔끔하고 곧은 일자로 펴실 수 있겠네요.

땀 닦고 힘주고 땀 닦고 힘주고 하다보면 금방 됩니다.
문제는 옆에서 괴롭히는 고양이 녀석이 가장 큰 문제라는...


손으로 피다보니 옷걸이 꼬여있는 부분은 정말 도저히 도무지 저게 한계 더군요.
옷걸이 다 폈으면 끝난 겁니다. 이제 마무리 하시면 됩니다.


두개는 ∩ 이런 모양에 끝만 살짝 꺽어 주시고, 나머지 두개는 반으로 접어주세요.
∩자 하나 반 접은거 하나 해서 한쪽 면을 만듭니다.


이음새 부분은 테이프로 칭칭 감아 주시면 되요. 튼튼하기만 하면 문제 없습니다. 저 테이프 감긴 부분은 텐트 바닥에 깔아줄 담요에 다 덮여서 녀석들이 물어 뜯을 수 가 없어요. :-D


정말 대충 감아 놓은거 같지만 아주 견고 합니다. 테이프를 쭉쭉 땡겨가면서 발랐더니 손도 아프고 테이프도 중간에 끊어지고를 몇 번 반복했습니다. 허허.


드... 드디어 뼈대가 완성 됐습니다. 사진에 '여기' 라고 적어 놓은 부분이 있는데요. 두 면을 이어 붙일려고 하면 붙일 곳이... 마땅치 않더라구요. 지붕 부분 옷걸이 끝부분을 푸셔도 되고 대각선으로 뻗는 옷걸이 끝부분을 접으셔도 상관 없습니다. 해보시면 아실 거에요.

지붕과 바닥에 옷걸이 겹치는 부분은 특히 튼튼하게 테이프로 감아 줍니다. 특히 지붕.
가끔 고양이가 극 흥분 상태에 돌입해서 온 방바닥을 뛰어다닐 때면, 텐트 위로 점프 합니다. 그걸 버터야 해요.

이제 만들어진 뼈대에 티셔츠만 입혀주면 완성!
뒷부분은 이리저리 모아서 집개 등으로 집어 주시면 됩니다. 벽에 확 붙이셔도 될것 같... 그러면 고양이가 빠져 나오겠군요... 집개를 사용 합시다.



포스팅은 지금 하지만 만든건 한 두달 전인거 같네요. 요즘 날씨가 쌀쌀해져서 그런가... 녀석이 텐트를 애용 합니다. 안입는 옷들 서너개 더 집어 넣어서 푹신푹신 따뜻하게 만들어 줬더니 만족 스러운가 봅니다.


저처럼 만들어져 파는것들은 왠지 사기싫고, 사실 돈도 없고 이러시다면, 한번 만들어 보세요.
재.. 재밌답니다. 하하하하하.


2013. 10. 28.

PHP mysql mysqli 데이터베이스 mysqli_fetch_row() mysqli_fetch_assoc() mysqli_fetch_array() 함수 차이.

PHP에서 데이터베이스 쿼리 함수들의 성능에 대한 글을 어쩌다가 봤다.
데이터베이스에서 결과 가져올때 흔히들 쓰는 함수들이다.
정확히 알지도 못하고, 소스에 사용된 부분이 있으면 긁어서 쓰고 또 썼었다.
맨날 긁어서 쓰기만 하니까 기억이 안나서 검색을 좀 해봤다.

array mysql_fetch_row ( resource $result )
array mysql_fetch_assoc ( resource $result )
array mysql_fetch_array ( resource $result [,int $result_type = MYSQL_BOTH ] )

mixed mysqli_fetch_row ( mysqli_result $result )
array mysqli_fetch_assoc ( mysqli_result $result )
mixed mysqli_fetch_array ( mysqli_result $result [,int $resulttype = MYSQLI_BOTH ] )

일단 mysqli_fetch_row().
값을 꺼내오는데 [index] 숫자값을 사용한다.

$connection = new mysqli("localhost", "id", "pw", "database");
$query = " SQL QUERY ";
$result = mysqli_query($connection, $query);
$row = mysqli_fetch_row($result);
$row[0], $row[1] ...

그리고 mysqli_fetch_assoc().
필드명이나 쿼리문에 사용된 alias로 배열을 참조 할 수 있다.

...
$row = mysqli_fetch_assoc($result);
$row["Name"], $row["CountryCode"] ...

그럼 mysqli_fetch_array() 이녀석을 보자.

/* numeric array */
$row = $result->fetch_array(MYSQLI_NUM);
printf ("%s (%s)\n", $row[0], $row[1]);

/* associative array */
$row = $result->fetch_array(MYSQLI_ASSOC);
printf ("%s (%s)\n", $row["Name"], $row["CountryCode"]);

/* associative and numeric array */
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
printf ("%s (%s)\n", $row[0], $row["CountryCode"]);

이녀석은 위에서 본 두가지 배열을 다 만들어서 내놓는다. 두번째 파라미터 기본값으로 MYSQLI_BOTH가 들어간다. 달리 명시해 주지 않으면 두 가지 배열 다 만들어서 돌려 준다.
결국 mysqli_fetch_row() 함수와 mysqli_fetch_assoc() 함수를 동시에 쓰는 거다.
가져오는 데이터베이스 양이 적으면 3가지 중에 뭘 쓰든 별 상관 없다. 편한거 쓰면 된다.

천만개 정도 들어있는 데이터베이스에 쿼리를 날리면 어떻게 될까.
http://www.spearheadsoftwares.com/tutorials/php-performance-benchmarking/50-mysql-fetch-assoc-vs-mysql-fetch-array-vs-mysql-fetch-object

링크를 타고가서 확인해보면 백만개 정도부터 살짝 차이가 나기 시작한다.
mysqli_fetch_object()는 쿼리 결과를 객체로 만들어서 뱉어주는거라 생각 하면 된다.

$obj = mysqli_fetch_object($result);
$obj->Name, $obj->CountryCode) ...

객체로 만들어서 뱉어 주는거라, 그냥 왠지 느릴 것만 같은데 정말 늦다.
물론 이것도 데이터 양이 몇개 안되면 차이 없다.

천만개 정도 데이터 양이 있을때, object()와 assoc()의 성능을 비교해 보면 15초 이상 차이난다.
array()와 assoc()에서는 큰 차이를 보이진 않는다. 4초 정도 되려나. object()와 assoc()에서 15초 이상 차이가 나서 1초가 아주 작아 보인다. 0.1초라도 줄이기 위해 노력 하는데 ... 4초도 정말 큰 시간이다.

스택오버플로우 같은 곳들을 뒤지다 보니 mysqli_fetch_array() 함수를 잊으라고 추천한다.
row()와 assoc()은 따로 비교되지 않았다. row()가 만들어놓은 배열 index와 필드명을 이어 주는게 필요해 보이는데... 성능 차이가 거의 없는걸까. 아무래도 row()가 가장 빠를 것 같다.
아무튼,  mysqli_fetch_row() 또는 mysqli_fetch_assoc()을 사용하자.

* mysql_*() 함수들은 PHP 5.5.0 버전에서 부터 DEPRECATED 되었고, 미래에 함수 자체가 지워질 것이라 한다. 확장격인 mysqliPDO_MySQL을 사용하자.

관련 출처.
http://us2.php.net/mysql_fetch_assoc
http://www.php.net/manual/en/mysqli-result.fetch-assoc.php
http://www.php.net/manual/en/mysqli-result.fetch-array.php
http://stackoverflow.com/questions/11480129/mysql-fetch-row-vs-mysql-fetch-assoc-vs-mysql-fetch-array

2013. 10. 25.

android phonegap(폰갭) 에서 뒤로가기 버튼 제어.

폰갭이 결국 웹뷰를 포장해 놓은거라 그런지, 모바일 기기의 백키를 누르면 뒤로가기가 되버린다.
기기의 back key를 눌렀을때 이벤트를 어떻게 처리 할 수 있을까?

어플 단에서 처리할 수도 있고, 웹 페이지에서 자바 스크립트로 처리할 수도 있다.
간단한 정도로 치면 아무래도 웹 페이지쪽에 한 표 준다.

웹 페이지쪽 부터 알아보자.

<head>
...
function onLoad() {
    document.addEventListener("deviceready", onDeviceReady, false);
}
function onDeviceReady() {
    document.addEventListener("backbutton", onBackKey, false);
}
function onBackKey() {
    navigator.notification.confirm('msg', onBackKeyResult, 'title', 'N, Y');
    //무반응으로 만들려면 위를 주석처리.
}
function onBackKeyResult(index) {
    if(index == 2) {
      navigator.app.exitApp(); 
    }
}
</script>
</head>

<body onload="onLoad()">
...

헤더 header에 집어 넣어야지 모든 페이지에서 작동 하겠지.
onBackKey() 함수의 confirm에서 원하는 텍스트로 갈아준다.
이렇게 하면 기기에서 백키를 눌렀을때 종료 확인 창을 띄울 수 있다.

navigator.notification.confirm() 함수는 리턴 값이 없다. 콜백 함수로 처리 해야 한다.
위에서 콜백 함수는 onBackKeyResult().
콜백 함수는 버튼의 index값을 받는다. 위에서 보면 N이 1, Y가 2다.

이번엔 안드로이드 앱 단에서 처리하는 방법을 찾아보자.
이리저리 검색해봤는데 버전 차인지 뭔지 안되는 소스들도 있더라.

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        confirmAppExit();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}


private void confirmAppExit() {
    ...
}

confirmAppExit() 함수 내용은 Dialog 만들어서 버튼 값에 따라 어플 종료 finish() 시키는 정도의 함수다.
다만, 위 소스는 원하는대로 움직여 주지 않는다.

백키를 한번 더 누르면 홈으로 빠져 나가는 상황, 어플이 종료 되는 상황, 에서만 작동한다. 어떤 놈인지는 모르겠는데 우선순위가 높은 놈이 있는게 분명하다.

이리저리 뒤져보다가 폰갭의 웹뷰 인스턴스 이벤트 리스너를 건드리는 것을 봤다.

appView.setOnKeyListener(new OnKeyListener() {
      @Override
      public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK &&
                 event.getAction() ==  KeyEvent.ACTION_UP)
            {
                 //원한다면 여기에.
                 return true;
            }
            return onKeyDown(keyCode, event);
      }
});

이 코드는 onCreate() 함수 안에 들어간다. 지금 이대로라면 백키를 눌러도 아무 반응이 없다. 종료 확인을 위해 다이얼로그를 띄우려면 주석 자리에 코드를 추가하자.

아무리 봐도, 앱 단에서 처리해서 다시 폰에 집어 넣느니 웹페이지 수정하는 편이 훨씬 나은거 같다. 다만 웹페이지의 <body> 시작 태그가 독립된 헤더에 포함되어 있지 않다면, <body> 태그를 찾아 페이지 마다 추가해야 되는 그런 불상사가 생길 수도 있겠다. 그런 경우라면 어플 단에서 처리 하는게 담배를 한대 덜 필 수 있는 길이다.


스택오버플로우 출처
http://stackoverflow.com/questions/15834629/android-droidgap-disabling-back-button

안드로이드 하이브리드 앱 폰갭(phonegap)에서 쿠키 사용이 안된다.

저번 포스팅에서 폰갭 플랫폼에서 하이브리드 앱을 개발할때 쿠키를 사용하기 위해, 어플 단에서 자바 코드를 추가 하는 방법을 적었다. 헌데, 그렇게 해도 달라지는게 없더라고 글을 마무리 했다. 찾아보고 찾아보고 찾아보니 이유가 있더라.

저번 포스팅의 방식은 원래 작동 해야 하는게 맞다.
안드로이드 2.+ 버전에서 작동 한다고 한다.
허나, 안드로이드 4.+ 버전에선 같은 방식으로 처리해도 쿠키 사용이 되지 않는다.

폰갭이 안드로이드 버전에 맞춰서 따라가질 못하는 건지, 아니면 폰갭의 버그인지, 아니면 안드로이드 자체에 변화가 생긴건지는 자세하게 모르겠다만, 해외 블로그들을 읽을 때마다 모두 다 같은 소리를 한다.

쿠키 말고 localStorage를 써보란다.
http://community.phonegap.com/nitobi/topics/using_cookies_does_not_seem_to_work
http://stackoverflow.com/questions/3709315/phonegap-cookie-based-authentication-php-not-working-webview
http://stackoverflow.com/questions/16463188/cookie-based-authentication-on-phonegap
https://groups.google.com/forum/#!topic/phonegap/brNhSI7oTU4
https://groups.google.com/forum/#!topic/phonegap/wnSPTDRWglU
http://stackoverflow.com/questions/11392364/how-to-store-cookies-values-in-an-android-device-using-phonegap-and-jquery-mobil

쿠키랑 성격이 비슷한 녀석이다. 크로스 브라우징 걱정도 없고, 아주 쓸만한 녀석이다.
http://www.w3schools.com/html/html5_webstorage.asp

사용법 또한 아주 간단한게 마음에 쏙 든다.
위 링크에서 나와있는 사용 예제는 아래와 같다.

<script language="javascript">

localStorage.lastname="Smith";
document.getElementById("result").innerHTML="Last name: " + localStorage.lastname;

</script>

폰갭 API 문서에도 설명이 나와 있다.
http://docs.phonegap.com/en/2.2.0/cordova_storage_storage.md.html#Storage

<script language="javascript">

//값 설정
window.localStorage.setItem("key", "value");
//가져오고
var value = window.localStorage.getItem("key");
//지워주고
window.localStorage.removeItem("key");
window.localStorage.clear();

</script>

데스크탑 환경에서( 폰갭 라이브러리 링크 안했는데 ) 문제 없이 돌아 간다.
그냥 모양새만 다른거고, HTML5 기본으로 제공하는 건가보다.
폰갭에서 따로 W3C Storage를 사용 가능하게 함수를 제공해 주는게 아니고 설명만 적어 놓은 거구나.

아주 쓸만한 녀석이지만, 클라이언트 쪽 데이터라 살짝 귀찮아 진다.
PHP 환경을 생각 해보자.쿠키나 세션이라면 $_COOKIE['...'] 등으로 서버단에서 처리해서 결과를 낼 수 있다만 이 경우라면 ajax등을 이용하여 필요한 처리를 해야한다.

http://stackoverflow.com/questions/3855337/php-localstorage

조만간에 머리가 크게 한번 지끈 거릴 것 같은 슬픈 예감이 든다.


2013. 10. 24.

폰갭(phonegap)에서 쿠키 cookie 사용하기.

웹뷰에서 돌아가는 녀석인데 당연히 쿠키가 사용될 것이라 생각 했다.
이렇게 해도 내가 원하는대로 쿠키는 움직여 주지 않았지만...
해매면서 이것저것 집어 넣어 보고 주무르다 보니, 이 코드 추가 전과 후의 결과가 기억이 안난다.

//변경전
import android.os.Bundle;
import org.apache.cordova.*;

public class App extends DroidGap {
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       super.loadUrl("file:///android_asset/www/index.html");
   }
}

//변경후
import android.os.Bundle;
import android.webkit.CookieManager;
import org.apache.cordova.*;

public class App extends DroidGap {
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
       CookieManager.setAcceptFileSchemeCookies(true);
       super.onCreate(savedInstanceState);
       super.loadUrl("file:///android_asset/www/index.html");
   }
}
http://stackoverflow.com/questions/11083160/how-to-enable-cookies-for-android-phonegap-1-8-0-app

대충.
file:// (안드로이드) 쪽 로컬 쿠키를 사용하려면, 폰갭 프로잭트(어플)에서 로컬 쿠키를 받아들이게 해 줘야 한다는데... 내 경우는 어떻게 되는건지 잘 모르겠다. 난 PHP에서 쿠키 사용할때 이렇게 했다.

set_cookie('param', 'data', time() + 3600), "/";

이게 폰갭을 통해서 안드로이드로 가면 어떤 경로가 될지는 정확히 모르겠는데,
일단 내가 사용하는 경우에는, 저렇게 어플단에서 코드를 추가 하던 안하던 차이가 없다.

솔직히 뭔지 잘 모르겠다만,
폰갭으로 하이브리드 앱 만드는데 쿠키말고 localStorage()를 사용하라는 의견이 지배적이다.

이번 포스팅은 일기가 됐네 :-(

PHP setcookie()로 생성한 쿠키가 cookie 삭제가 안된다.

md5 함수로 막 어지럽히고, base64_encode 함수로 막 괴롭힌 값들로 쿠키를 생성했다.
사실 이건 문제가 안된다.

쿠키를 생성하는 방법 등 기본적인 것들은 링크를 참조 하도록 하자.
http://www.w3schools.com/php/php_cookies.asp
친절한 곳이다.

쿠키 생성하고, 지우는데 사용한 코드다.
헌데 도대체가 지워지지 않는다. 꼬박 이틀을 날렸다.

setcookie('param', 'data', time() + 3600 );
setcookie('param', '',     time() - 3600 );

http://stackoverflow.com/questions/6843822/android-jquery-mobile-cookies-not-stored
http://stackoverflow.com/questions/5681117/cookies-not-being-deleted/7103043#7103043

setcookie()에 네번째 파라미터가 있다.
경로를 지정해 주는 파라미터인데, 이놈을 지정해 주지 않으면 쿠키 삭제가 안될 수 도 있다.
브라우저가 디폴트로 현재 페이지를 어쩌고 저쩌고 ...
그래서 찾아보니 대부분 이렇게 가더라.

setcookie('param', 'data', time() + 3600, '/' );
setcookie('param', '', time() - 3600, '/' );

아참, 그리고 쿠키는 setcookie()로 만들어 주자 마자 바로 사용할 수 없다.

setcookie('param', 'data', time() + 3600, '/' );
echo $_COOKIE[param];

이렇게 해봤자 값은 나오지 않는다. 새로고침 처럼 페이지가 다시 불러와야지 써먹을 수 있다.

setcookie('param', 'data', time() + 3600, '/' );
if( $_COOKIE[param] == "" ){
  echo "<script type='text/javascript'> window.location.reload(); </script>";
} else{
  echo $_COOKIE[param];
}

이렇게 하라는건 아니고, 이렇게 해서 페이지를 다시 불러주면 한방에 찍어 볼 수 있다.

폰갭(PhoneGap) 이란 무엇인가?

하이브리드 앱을 만들때 폰갭과 같은 플랫폼을 사용한다.
치밀하게 알고싶은 사람은 위키디피아로! http://en.wikipedia.org/wiki/PhoneGap

그럼 하이브리드 어플은 무엇인가.

앱 하나를 생각해보자.
얼굴을 찍어서 전생이나 닮은 사람 찾아주는 그런 간단한 것으로. 입력은 사진 한장이다.
사진 한장으로 어떤 알고리즘을 거치든, 결과를 뱉기까지 플랫폼이나 언어가 걸림돌이 되진 않는다.

아이폰이나 안드로이드라면, 카메라로 촬영하고, 촬영한 사진 데이터로 이리저리 구워 삶고 비교하고 또 비교해서 결과 내면 된다.

그럼, 똑같은 기능을 하는 웹사이트가 있다고 해보자. 그리고 아이폰이나 안드로이드의 웹 브라우저로 그 사이트에 접속한 상황이다. 결과 만들어 내는 로직은 웹언어라 해도 문제될 것은 없다. 문제는 입력을 어떻게 주는지다.  카메라를 띄워야 되는데, 모바일 OS들의 카메라를 제어할 수가 없다.

이때 필요한 것이 폰갭과 같은 플랫폼이다. 포장지라고 생각하자.

폰갭을 사용하여 어플을 만드는 과정이나, 그냥 어플을 만드는 과정이나 똑같다.
다만, 폰갭 라이브러리를 어플에 포함시켜서 사용하는 것 뿐이다.

public class MainActivity extends DroidGap {
    @Override
    public void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);       
        super.loadUrl(" url ");
    }
}

안드로이드 어플을 개발해 본 사람이 있다면 많이 봐왔을 만한 부분이다.
메인엑티비티 인데, Activity를 상속하는게 아니라 저 DroidGap을 상속한다.

그리고 loadUrl()함수로 해당 웹페이지를 가져 오면 된다.
물론 어플 내로 웹페이지 코드 자체를 옮길 수 도 있다. 허나 번거롭다.

그럼 이제 어떻게 되는건가.
카메라를 제어 하고자 하는 웹페이지에서 써주면 된다.

<script type='text/javascript' charset='utf-8' src='cordova.js'></script>
<input type="button" onclick="capturePhoto();" value="사진촬영" />

스크립트 불러와주고, 홈페이지에서 API 봐가면서 맞게 써주면 된다.
밑에 함수들은 웹 페이지에 포함 해준다.

function capturePhoto(){
     navigator.camera.getPicture(
         onPhotoURISuccess,
         onFail,
         { quality:50,
           correctOrientation: true,
           destinationType : Camera.DestinationType.FILE_URI 
         }
     );
}

function onPhotoURISuccess(imageURI){
     //파워하게 처리.
   alert(" 사람의 얼굴로 다시 촬영해 주세요. ");
}

function onFail(message){
     //사진을 촬영하거나, 가져오는데서 실패하면.
     alert(" 카메라가 촬영을 거부 했습니다." );
}

저 navigator.camera 저 녀석들이 폰갭 놈들 인가 보다.
촬영하면 콜백 함수(onPhotoURISuccess, OnFail)가 호출되고 이미지를 리턴해 준다. 이제 위에서 말했던 나이를 맞추든, 닮은 사람을 찾아주든 처리해서 결과를 뱉어 주면 된다.

아이폰에서도 폰갭 라이브러리 불러오고 아이폰 어플 만드는 방식 따라서 웹사이트에 연결해주면 된다. 안드로이드용, 아이폰용 어플을 따로 만들 필요 없이 웹페이지 소스 하나로 안드로이드, 아이폰 어플을 각각 만들어 낼 수 있다는 것이다.


2013. 10. 23.

JQuery ajax 에러 error 코드가 code 0.

form에서 submit할때 호출되는 함수에서 ajax로 뭔가를 처리 하는데 에러가 뜬다.
alert으로 에러 코드를 찍어보니 code 값으로 0을 뱉어 낸다.

아래는 submit 버튼을 누르면 form의 submit_check()함수가 호출되고,
form이 submit되는 간단한 구조의 form이다.

<form name="Frm" method="post" onsubmit="return submit_check(this);">
<input type="submit" value="submit">
function submit_check(){
   jQuery.ajax({
      type:"GET",
      url: target_url,
      success:function(msg){
          //성공!
      },
      error: function(xhr,status,error){
          //에러!
          alert("code:"+xhr.status);
      }
   });

   f.action = submit_url;
   return true;
}

에러 0의 의미가 더 있을진 모르지만 2가지를 알아 봤다.

In my experience, you'll see a status of 0 when either doing cross-site scripting (where access is denied) or requesting a URL that is unreachable (typo, DNS issues, etc).

1. 대충 뭐, 접근 거부 된거나 DNS 문제 등으로 접속 안되는 주소로 연결 했을때.

It's could be possible to get a status code of 0 if you have sent an ajax call and a refresh of the browser was trigger before getting the ajax response. The ajax call will be cancelled and you will getting this status.

2. ajax 호출하고 반환값을 받아와야 되는데, 이 반환값이 도착 하기전에 submit 또는 새로고침 등으로 페이지 이동이 발생해 버리는 경우.

내 문제는 2번이다. 저 위의 form에서 onsubmit 부분은 지워 버리자. ajax가 끝 날때 submit 되야 하는데, 저건 곤란하다. 어찌 됐건 함수가 끝나면 submit 되버리니까.

<input type="button" onclick="submit_check()" value="submit">

버튼 type은 button으로, 이벤트도 달아서 변경해 준다. 그리고 아래는 바뀐 함수.

function submit_check(){
   jQuery.ajax({
      type:"GET",
      url: target_url,
      success:function(msg){
          //성공!
         document.logFrm.action = submit_url;
         document.logFrm.submit();
      },
      error: function(xhr,status,error){
          //에러!
          alert("code:"+xhr.status);
      }
   });
}

이렇게 되면, 버튼을 눌렀을때 함수가 실행되며 ajax 발사!
submit_check() 함수는 실행을 마쳐도 form은 submit 되지 않는다.
ajax가 무사히 반환값을 가지고 돌아오면 그때 form을 submit 시킨다.

꿀같은 출처는 역시 스택오버플로우.
http://stackoverflow.com/questions/2000609/jquery-ajax-status-code-0

HTML textarea value 출력 시 개행 문제.

textarea의 value 값을 데이터베이스에 저장해 놓고, 필요한 곳에 출력해야 한다.
허나, 어떻게 입력을 해도 한줄로 출력된다.

textarea에 입력할때 엔터를 치면 당장 눈에 보이는 개행은 된다.
\n 이 녀석이 들어가는 것. 헌데 그럼 왜 뿌려줄때는 한줄로 나온단 말인가!

브라우저에서 HTML에서 개행은 \n로 되지 않는다. 소스보기를 하면 아마 개행 잘 되있을 것이다.
<br/>이나 다른 개행 태그가 사용 되야 된다.

<form name="cf" onsubmit="return replaceBR();" >
   <textarea id="c"></textarea>
</form>

submit 될때 함수를 호출해서 \n값을 <br/>로 바꿔 주는 방법을 써보자.

function replaceBR(){   
   document.cf.c.value = document.cf.c.value.replace( /\n/gi, '<br//>'); 
   return true; 
}

replace() 함수는 일치하는 문자열을 바꿔 주긴 주는데, 첫 번째 일치하는 녀석만 바꿔준다.
바꾸고 싶은 문자열을 / / 로 감싸고 뒤에 gi를 붙여 주는 것으로 전체를 바꿀 수 있다.

나처럼 "/\n/gi"따옴표로 묶어 놓고, 안된다고 빡치는 시간 낭비 하지 말자.
묶어버리면 그냥 문자열 덩어리일 뿐이다.

아, 함수내에 br태그 슬래시 2개다. 코드 하일라이터에서 개행이 되버려서리...

JQUERY 모바일에서 로딩 애니메이션을 제거 해보자.

특정 엘리먼트가 최상단에 오게 페이지를 스크롤 해야 하는데, JQuery mobile에서 페이지 이동할때 뜨는 'Loading...' 애니메이션 때문인지 스크롤이 안된다.

아래와 같이 jquery mobile 기본 애니메이션을 해제 시킬 수 있다.

<script type="text/javascript" src="/jquery-1.7.1.js"></script>

<script>
$(document).on("mobileinit", function(){
      $.mobile.ajaxEnabled=false;
      $.mobile.loadingMessage = false;
});           
</script>

<script type="text/javascript" src="/jquery.mobile-1.0a3.min.js"></script>

javascript JQuery offset() 반환값이 0이 나와요.

offset()값이 top이나 left나 모두 0으로 나온다.
아래는 특정 엘리먼트를 최상단에 위치하게 페이지 스크롤 해주는 코드다.

var position = $('#time_'+idx).offset();
$('html, body').animate( {scrollTop: position.top-5 }, 500);

phonegap(cordova) + jquery mobile에서 필요해서 갖다 붙였는데 반응하지 않는다.
alert으로 값들을 찍어 봤는데, position.top 값이 0으로 찍힌다.

document.getElementById("target").offsetParent.tagName
$("#target").get(0).offsetParent.tagName

아래 것은 JQuery 방식.
이렇게 특정 엘리먼트를 포함하는 기준 개체가 뭔지 확인 해 볼 수 있다. 부모 개념인가?

내가 이동하고자 하는 특정 엘리멘트를 포함하고 있는 기준 개체란 것의 potision 속성값에 따라 자식 엘리먼트의 좌표값이 바뀔 수 도 있단다.

CSS쪽으론 아는게 없어서 일단  모든 CSS파일을 링크 해제 했다. 형편없게 뭉게져버린 페이지지만 offset()값은 제대로 나온다! 무엇인진 모르겠으나 position에 속성값을 주나보다. 찾아서 좀 수정 해보려 했으나 jquery mobile 기본 CSS라 건드릴 엄두가 안난다. 화면 가득 빽뺵한 소스를 보고있으니 멀미가 난다.

다시 CSS링크 하고 저 기준개체란 녀석을 찍어봤는데 null이 나온다... 여기까지다 못하겠다.
결국 고정값으로 스크롤 되게끔 처리 했지만,  다른 컴포넌트들의 height 값 바뀌면 사정없이 엉뚱한테 스크롤 되버린다.

GET 방식으로 url 주소값을 넘길때 "&" 문제.


url 주소를 GET 방식으로 넘길일이 있겠냐만은... 넘길일이 있더라.

page.php?url=page2.php?param1=1&param2=2

GET 방식으로 url 파라미터에 page2.php?param1=1&param2=2를 보내고 싶었다.
허나 page.php에서 $_GET[url]을 하면 page2.php?param1=1 여기까지만 나온다.
url 파라미터 값 안에 &가 있어서, 한 덩어리가 아닌 다음 파라미터라고 생각하는 것.

즉, page.php에서 받은 $_GET 배열이

$_GET[url] = page2.pgp?param1=1
$_GET[param2] = 2

이렇게 쪼개져 있다는 것이다.

url 주소를 GET 방식으로 넘겨줄려면,
urlencode()함수로 인코딩하여 GET값을 보내주면 된다.

$temp = urlencode(page2.php?param1=1&param2=2);
page.php?url=$temp

HTML form의 input으로 파일(file) 넘겨 받을때 오류 처리 조건문.

PHP 버전이 현재 쓰고 있는 것보다 더 높은 곳으로 서버를 옮겼습니다. type=file인 input을 POST 방식으로 받아 처리하는 페이지가 있는데 서버를 옮긴 이후로 결과가 달라졌더라고요. 일반적인 form과 input 입니다.
 
<form action="upload_file.php" method="post" enctype="multipart/form-data">
      <input type="file" name="file" id="file">
      <input type="submit" name="submit" value="Submit">
</form>

upload_file.php에서 file이 잘 넘어왔는지 확인하는 조건 절이 있는데 이상합니다. extract() 함수를 이용해서 $_POST 변수를 풀어 놓은 상태라 바로 $file 이런 식으로 사용했고요. 위에 보면 input name 속성을 file로 해놨습니다. 가만있자... file 이라는 변수명은 HTML이나 PHP의 예약어라서 사용을 할 수 없는 건가요? form의 name 속성 값을 form 이라고 주면 안됐었던 걸로 갑자기 기억이 나는 듯 마는 듯 합니다.
http://php.net/manual/kr/function.extract.php

if( $file ){...}

같은 소슨데 버전이 달라졌다고 내게 이런 귀찮음을 선물하다니 ㅜ _ㅜ 귀찮습니다.
제대로 쓰려면 어떻게 해야 하나 싶어서 좀 찾아 봤습니다. 아래 링크 참조.
http://www.w3schools.com/php/php_file_upload.asp

권장 사항은 이렇네요.

if ($_FILES["file"]["error"] > 0){...}  

음! 그럼 우리는 이렇게 사용해주면 되겠습니다.

<?php
if ( $_FILES['file']['error'] == 0 ) {
      //에러없을때, 잘 넘어왔을때.
} else {
      //에러.
}
?>

JQuery 엘리먼트 element 스타일 style 변경.

jquery를 써서 html 엘리먼트의 스타일을 변경하고 싶었다.

 $('# ID ').attr("style", "background-color: #f0ffff");

이렇게 하면 배경이 적용 되긴 한다. 하지만 background-color 속성 외에 width, height가 있었다면 사라지고 없다. style 속성에 덮어쓰는 것인가 보다.

이래선 안된다. 기존의 style 속성은 유지하면서 배경만 바꿔야 하는데...

 $('# ID ').css("background", "#f0ffff");

attr 함수가 아닌 css함수로 backround를 줘봤다.
이렇게 하면 기존 설정들은 남아있고 배경색만 변경 된다.

HTML DIV 안의 iframe 높이 height 를 구해보자. 다음에디터 높이를 동적으로 변경!

다음 에디터를 사용 하는데, 작성한 글의 높이에 맞춰서 에디터의 높이도 변경하고 싶었다. 에디터를 로드 하는 부분에서 높이를 지정할 수 있는데, 이 높이 값보다 본문 높이 값이 크면 스크롤바가 생겨서 마음에 영 들지 않는다.

PHP로 계산해서 높이값을 던져 주고 싶었지만, PHP단에서 높이를 측정할 방법을 못 찾았다.

<div id="tx_canvas_wysiwyg_holder" ...>
<iframe id="tx_canvas_wysiwyg" ...></iframe>
</div>

크롬엔 개발자 툴이 있다. F12를 누르면 현재 페이지의 구석 구석을 해집어 볼 수 있다.
tx_canvas_wysiwyg이란 iframe 안에 본문 내용이 몽땅 들어 간다.

Editor.getCanvas().setCanvasSize({ height: '700px' });

위와 같이 높이를 설정해 줄 수 있다. 에디터 안의 본문 높이가 설정 값보다 클 경우 스크롤바가 생겨 버린다. 본문을 모두 불러온 다음 에디터 높이를 변경하려면 스크립트로 처리 하는게 좋을 것 같다.

alert( $("#tx_canvas_wysiwyg').height() );

스크롤바가 생겨 있는데도 본문을 담고 있는 iframe의 높이값을 찍어보면 '700'을 돌려준다.
그럼 어떻게 해야 스크롤바 길이 까지 포함해서 가져올 수 있을까.
iframe이 담고 있는 html body 본문의 height 값을 가져오면 된다.

alert( document.getElementById("tx_canvas_wysiwyg").
          contentWindow.document.body.offsetHeight );

뭔가 길어졌지만, 이렇게 하면 iframe안의 본문 높이를 확인 할 수 있다.
이제 아래와 같이 에디터의 높이 height 값을 변경 해주면 된다.

var editor_height = document.getElementById("tx_canvas_wysiwyg").
                                   contentWindow.document.body.offsetHeight;
$("#tx_canvas_wysiwyg").height( editor_height );

IE8에서는 offsetHeight대신 scrollHeight로 바꾸자. 두개 차이는 뭔지 모르겠다.
그 이상 버전에서는 확인하지 못했다.

2013. 10. 18.

HTML div에 style 속성으로 모서리 둥글게 만들어보자.

div에 border로 테두리 넣을 일이 잘 없었다. 테두리를 어쩌다가 넣었는데 너무 네모난게 마음에 들지 않는다. 이클립스 ctrl + space bar 기능으로 사용 가능한 것들 이리저리 찾아 봤는데 이 style 속성값은 나오지 않더라. 귀찮게 검색을 시키다니 말이야.

<div style="border: 2px solid #ccc;">

뭐 이정도로 div가 하나 있다고 하자. 테두리도 style 속성.
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
border-top-left-radius: 10px;&nbsp;
border-top-right-radius: 10px;

이 속성들만 넣어주면 된다.

<div style="border-bottom-left-radius: 10px; 
               border-bottom-right-radius: 10px; 
               border-top-left-radius: 10px; 
               border-top-right-radius: 10px; 
               border: 2px solid #ccc;">
</div>

10px에서 값이 커지면 둥근 정도도 커진다.

2013. 10. 17.

HTML 새로고침 버튼을 만들어보자.

새로고침은 F5 눌러버리면 그만이라 생각했는데, 만들어 놓아야 할 일이 생기긴 하네. 대충 두 가지 방법으로 새로고침 버튼을 만들어 봤다. 역할은 같고, 둘다 새로고침 버튼이다. 자바스크립트를 썼냐 안썼냐 그 정도 차이 밖에 없다.

echo "<script> var url = '$_SERVER[REQUEST_URI]'; </script>";
<a href="http://www.blogger.com/url">Refresh</a> 
<input onclick="location.replace( url );" type="button" value="Refresh" /> 

두번째에 location.replace를 한번 써봤다.
첫번째의 경우 그냥 페이지 이동인 셈이니까 history가 쌓이겠지.
아마 페이지를 뒤로 이동 하게 된다면 같은 페이지로 제자리 걸음 할 것이야.
라고 생각했는데 아무 문제 없더라. :-)

GET 방식으로 한글값 전달. 근데 이상한 문자로 나오면?

GET 방식으로 url 뒤에 한글 보냈는데, 받는 쪽 PHP에서 확인하니 깨짐 현상이 생긴다.
현재 주므르고 있는 상황은 아래와 같다.

* A.php는 서버단에서 연산하고 자바스크립트 구문을 만들어 출력한다.
* 본문에서는 어떤 이벤트가 발생했을때 동적으로 A.php를 스크립트로 추가 한다.

s = document.createElement('script');
s.src = './A.php?param=뭐임마';

대충 위처럼 본문에서 A.php를 동적으로 삽입한다. 이것도 나름 AJAX 인건가... 잘 모르겠다.  근데 A.php의 PHP 문장에서 저 param값이 '뭐임마'이 아니다. 흔히 인터넷 주소창나 에서 볼 수 있는 %EC&A7%80%EB.... 이렇게 변신해 있다.

A.php에서 값을 echo로 출력해보려고 하면 자꾸 스크립트 오류가 나서 열채게 한다. 왜그런진 모르겠고 화만 치밀었다. 뭔가 인코딩이 된 것 같아서 이것 저것 인코딩/디코딩 PHP 함수를 다 갖다 써 봤다.

A.php 에서 변수 사용할 때 디코팅 함수에 담궈 준다.
$param = URLDecode( $_GET[param] );
이렇게 디코딩 함수를 한번 통과 시켜 주니, 한글이 제대로 나온다.

PHP 문자열 찾기 함수. strpos() 그리고 eregi().

어느샌가 점점 돌대가리가 되어 가는것 같다.
자주 사용 하는 함수 인데, 매번 생각이 안나서 검색을 해야 한다. 여기에 정리 하다 보면 기억에 남으리라 믿는다. 일단, PHP에서 문자열에서 특정 문자가 포함 되었는지 찾아 주는 함수다.
int eregi ( string $pattern , string $string [, array &$regs ] );
예를 들어보자.
echo eregi( 'A', "ABCDEFG");
간단 하다. 파라미터 2개 중에 앞에 찾을려는 문자, 뒤에는 문자열이다. 세번째 파라미터는 패턴 뭐 어쩌고... 모르겠다. 궁금한 사람은 http://php.net/manual/en/function.eregi.php 여기서 보면 된다.

반환값은 몇 번째에 있는가 그런거 상관 없이 있으면 1, 없으면 0을 반환 한다. 세번째 파라미터가 들어가면 반환값이 바뀌는것 같다만 거기까진 알지 말자. PHP닷넷에서 보다보니, 이 함수는 PHP 5.3.0. 버전 이후로는 사용하지 말란다.

mixed strpos ( string $haystack , mixed $needle [, int $offset = 0 ] );
위에 녀석과 파라미터가 반대다. 앞에 문자열이 들어가고 뒤에 찾을 문자가 들어간다. eregi()와 다른점은 이녀석은 찾고자 하는 문자의 위치를 반환 해준다.
예를 들어보자.
echo strpos( "ABCDEFG" , "A" );
이러면 0이 나온다. 첫번째니까. "B"를 찾으면 1이 나오고, G를 찾으면 6이 나오겠지.
그럼 이러면 어떨까?
echo strpos( "ABCDEFG" , "H" );
찾고자 하는 문자가 문자열에 없다. 그럼 이녀석은 int 숫자값이 아닌 boolean 값을 뱉는다. 
찾았을때는 위치 숫자값을, 못찾으면 boolean(false)를 뱉는다. http://php.net/manual/en/function.strpos.php 
참고해보면 이 함수를 사용해서 조건문을 돌릴때면 비교 연산자를 === 또는 !==를 쓰란다.

'=' 이게 3개가 붙어있는거 솔직히 처음 봤다.
 $a === $b  $a와 $b가 같고, 같은 자료형이면 TRUE. (PHP 4에서 추가)
 $a !== $b  $a가 $b와 같지 않거나, 같은 자료형이 아니면 TRUE. (PHP 4에서 추가) http://php.net/manual/kr/language.operators.comparison.php 읽었는데 무슨 말인지 모르겠다.
이해 안되는게 분해서 저 권장사항이 쓸모없음을 증명하고 싶었다.
if( strpos( "ABCDEFG" , "H" ) >=0 )
   echo "포함되네";
어짜피 찾으면 0부터 시작되는 숫자값 줄꺼고, 못찾으면 false니까 뭐 이러면 되겠네!
라고 생각했는데 멍청한 생각이다.
if( -1    ) echo "헐퀴-1";
if(  0    ) echo "헐퀴0";
if(  1    ) echo "헐퀴1";
if( true  ) echo "헐퀴true";
if( false ) echo "헐퀴false";
틈이 나면 이렇게 한번 찍어보라. 0과 false는 헐퀴를 볼 수 없다. 0을 false처럼 써지니까.
while(1) 이런거 많이 해봤잖아.

strpos()는 찾지 못하면 false 을 뱉는다. 찾고자 하는게 가장 첫번째에 있어도 0을 뱉는다.
위의 예제를 보면 결국  false >= 0 이 되는거고 참이다. 항상 참이다.
false는 boolean형이고 0은 int형이지만, if는 신경 안쓴다. 그래서 !== 이나 ===을 쓴다.

권장사항은 이렇다.
if( strpos( "ABCDEFG" , "H" ) !== false )
    echo "헤헿 포함!";
이러면 false !== false 가 되고, 자료형도 달라야되고, 값도 달라야되는데 둘다 같아서 거짓!.
찾을경우 0~n !== false 가 되고, 자로형도 다르고 값도 다르므로 이 되는 것. 

어? 쓰고있는데 햇갈린다.