블로그 이미지

카테고리

데꾸벅 (194)
Publisher (39)
Scripter (97)
Programmer (1)
Designer (30)
Integrator (18)
Pattern Searcher (4)
News (2)
강좌 및 번역 (3)

최근에 올라온 글

최근에 달린 댓글

꿈쳐두기 습관이 도졌나 보다!
아래는 http://www.mimul.com/pebble/default/2008/01/29/1201616760000.html 에서 꿈쳐온 것입니다. 부디 너그러운 맘으로 용서하시길..



1. JavaScript 최적화 도구
2. CSS 최적화 도구
3. 웹 사이트 성능 개선

'Scripter > JAVASCRIPT' 카테고리의 다른 글

자바스크립트 가이드  (0) 2008.12.03
JsonML 활용하기  (0) 2008.11.12
자바스크립트 단축키 사용 [Key Binding]  (0) 2008.08.19
자바스크립트 최적화(반복문)  (0) 2008.07.30
자바스크립트 코딩가이드 #1  (1) 2008.07.02
Post by 넥스트리소프트 데꾸벅(techbug)
, |
가끔씩 Ajax 웹어플리케이션을 만들다 보면 단축키를 요구하는 고객들이 많다.
그러한 이유로 단축키 관련 스크립트를 찾아보던중에 prototype el.observe() (extjs에서는 el.on())를 이용한 keycode를 바인딩해주는
스크립트를 찾았다.

얼마전에 ajaxian post에 올라왔던 글이였는데 잊고 있었던거.. ㅡ.,ㅡ;
잠시 꼬불쳐 두다... 키값 참조할때도 좋겠다.

참조URL , 샘플URL

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/scriptaculous.js"></script>
<script type="text/javascript" >
var Key = {

// -- Alphabet
  A: 65,
  B: 66,
  C: 67,
  D: 68,
  E: 69,
  F: 70,
  G: 71,
  H: 72,
  I: 73,
  J: 74,
  K: 75,
  L: 76,
  M: 77,
  N: 78,
  O: 79,
  P: 80,
  Q: 81,
  R: 82,
  S: 83,
  T: 84,
  U: 85,
  V: 86,
  W: 87,
  X: 88,
  Y: 89,
  Z: 90,

  a: 97,
  b: 98,
  c: 99,
  d: 100,
  e: 101,
  f: 102,
  g: 103,
  h: 104,
  i: 105,
  j: 106,
  k: 107,
  l: 108,
  m: 109,
  n: 110,
  o: 111,
  p: 112,
  q: 113,
  r: 114,
  s: 115,
  t: 116,
  u: 117,
  v: 118,
  w: 119,
  x: 120,
  y: 121,
  z: 122,

// -- Special Keys
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  END: 35,
  HOME: 36,
  ARROW_LEFT: 37,
  ARROW_UP: 38,
  ARROW_RIGHT: 39,
  ARROW_DOWN: 40,
  DELETE: 46,
  QUESTION_MARK: 63

};

/*
 * Tie into ? and ESCAPE
 */
document.onkeypress = function(e) {
    if (e.charCode == Key.QUESTION_MARK) {
        KeyBindingsHUD.toggle();
    } else if (e.keyCode == Key.ESCAPE) {
        KeyBindingsHUD.hide();
    } else {
        KeyBindings.actionForKey(e);
    }
}

var KeyBindings = (function() {
    var bindings = {};

    var findNumber = function(key) { // get the name of the key from the charCode
        for (var name in Key) {
            if (Key[name] == key) {
                return name;
            }
        }
    }

    return {
        add: function(binding) {
            if (typeof binding.keys == "string") { // short circuit for non-arrays
                binding.keys = [ binding.keys ];
            }
            bindings[binding.eventname] = binding;
        },

        remove: function(eventname) {
            bindings[eventname] = undefined;
        },

        html: function() {
            var html = [];

            for (var key in bindings) {
                if (bindings.hasOwnProperty(key)) {
                    var binding = bindings[key];
                    var keys = binding.keys.collect(function(key) {
                        return Object.isNumber(key) ? findNumber(key) : key;
                    });
                    html.push(keys.join(', ') + ': ' + binding.description);
                }
            }

            return html.join('<br/>');
        },

        actionForKey: function(e) {
            for (var action in bindings) {
                if (bindings.hasOwnProperty(action)) {
                    var keys = bindings[action].keys;
                    keys.each(function(key) {
                        if (e.charCode == 0) {
                            if (e.keyCode == key) {
                                document.fire(action, e);
                                throw $break;
                            }
                        }
                        if (String.fromCharCode(e.charCode) == key) {
                            document.fire(action, e);
                            throw $break;
                        }
                    });
                }
            }
        },

        caseInsensitive: function(keys) {
            if (typeof keys == "string") keys = [ keys ]; // array wrap
            var newArray = [];
            keys.each(function(c) {
               newArray.push(c, c.toUpperCase());
            });
            return newArray;
        }
    }
})();

/*
 *
 */
var KeyBindingsHUD = (function() {
    var isShown = false;
    var headerId = '_keybindings_header';

    return {
        toggle: function() {
          (isShown) ? this.hide() : this.show();
        },
        show: function() {
            if (!isShown) {
                var header = $(headerId);
                if (!header) {
                    header = this.createElement();
                }
                header.innerHTML = KeyBindings.html();
                header.blindDown({duration: 0.2});
                isShown = true;
            }
        },
        hide: function() {
            if (isShown) {
                $(headerId).blindUp({duration: 0.2});
                isShown = false;
            }
        },
        createElement: function() {
            var header = document.createElement('div');
            header.id = headerId;
            header.align = 'center';
            header.style.display = 'none';
            header.style.width = '100%';
            header.style.backgroundColor = '#111111';
            header.style.color = '#eeeeee';
            header.style.fontSize = '10pt';
            header.style.paddingTop = '4px';
            header.style.paddingBottom = '4px';
            header.style.borderBottom = '1px solid #333';

            var body = document.body;
            body.insertBefore(header, body.firstChild);

            return $(headerId);
        }
    }
})();




KeyBindings.add({
   eventname: 'action:move-up',
   keys: ['p', Key.ARROW_UP ],
   description: "Move up the stack"
});

KeyBindings.add({
   eventname: 'action:move-down',
   keys: ['n', Key.ARROW_DOWN ],
   description: "Move down the stack"
});

document.observe('action:move-up', function(e) {
    alert(1)
});

document.observe('action:move-down', function(e) {
    alert(2);
});

</script>


소스다운로드 :




'Scripter > JAVASCRIPT' 카테고리의 다른 글

JsonML 활용하기  (0) 2008.11.12
CSS & JAVASCRIPT 최적화 도구  (0) 2008.10.26
자바스크립트 최적화(반복문)  (0) 2008.07.30
자바스크립트 코딩가이드 #1  (1) 2008.07.02
getBoundingClientRect in FF3  (3) 2008.06.25
Post by 넥스트리소프트 데꾸벅(techbug)
, |



jQuery의 창시자 John Resig의 자바스크립트 강의 동영상이 있어 포스팅한다.

Javascript2 에 대한 내용도 있어 상당히 유용한 동영상이다.

John의 동영상은 구글비디오에서 볼수 있다.

Post by 넥스트리소프트 데꾸벅(techbug)
, |
제목은 거창하게 달았지만 데꾸벅만 몰랐던 것일수도 있다.
대부분 SI관련 프로젝트는 quirks mode(스펠링이 맞나? 흠..)에서만 작업을 하기 때문에
XHTML transitional이나 strict모드에서는 잘 작업을 하지 않아서 잘못된것임에도 인지못하였던것일 수도 있다.

DOCTYPE이 아래와 같이 XHTML transitional 모드일때
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

[ 슬라이딩 메뉴 소스 ]
function slidingBars(objid,starttop,bottommargin){
    var obj=document.getElementById(objid);
    if(obj !=null && obj !=undefined){
        var ntop;
        var maxtop;
        var speed=10;
        var move=function(){
            ntop=starttop+document.body.scrollTop;
            maxtop=document.body.scrollHeight-(bottommargin+obj.offsetHeight);

            if(maxtop < ntop) ntop=maxtop;
            movevalue = Math.floor((ntop-obj.offsetTop)/speed);
            obj.style.top=(obj.offsetTop+movevalue)+'px';
            setTimeout(move,10);
        }
        move();
    }
}
window.onload=function(){
    slidingBars('slidingbar',35,100);
}

<div id="slidingbar">포지션:엡솔루트;지-인덱스:1인 레이어</div>


와 같이 늘쌍 써오던것이 Opera를 제외한 IE6,7,8 및 FF2.0에서 적용이 되질 않았다.
혹, XHTML같은 경우 테이블의 height가 100%가 지정되지 않기때문에 이런 문제일수도 있을것 같아 검색하다 보니 다음과 같은 글을 발견하였다.

http://forums.digitalpoint.com/archive/index.php/documentbodyscrolltop-in-ie/t-11965.html
http://forums.mozilla.or.kr/viewtopic.php?t=2382&sid=442d133080a29e669f7ca4357cdf3415

참조들 하심 되겠고....
말하고자 하는 바는 사실 우리가 늘쌍 써 왔던 document.body.가 아니라 document.html.scrollTop해도 적용이 되긴 하나 정확한 표현은 document.documentElement 라는 사실이다.
(위 소스에서 수정해줘야 해요~~ ^^)

documentElement 속성값은 문서의 최상위노드(root) 참조를 나타내는 개체이다.
이 속성은 읽기전용이며 디폴트값은 없다.
가장 전형적인 최상위 노느는 html 개체이다.


Post by 넥스트리소프트 데꾸벅(techbug)
, |
XML파일
<OpenSearchDescription>
<ShortName>나루</ShortName>
<Description>나루 블로그 전문 검색</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">
https://t1.daumcdn.net/cfile/tistory/2608163356E660671C"text/html" method="GET" template="http://naaroo.com/search/{searchTerms}"/>
<SearchForm>http://naaroo.com</SearchForm>
<Developer>astraea</Developer>
<Contact>http://withstory.net</Contact>
</OpenSearchDescription>

웹페이지
<ul>
<li><a href="#" onclick='window.external.AddSearchProvider("xml파일");'>설치할 URL</a></li>
</ul>

'Scripter > JAVASCRIPT' 카테고리의 다른 글

자바스크립트로 루비프로그래밍을?  (2) 2008.03.28
IE8에서의 자바스크립트  (0) 2008.03.27
Protoflow : prototype.js를 이용한 Coverflow  (0) 2008.03.12
window.status in FF & IE7  (0) 2008.03.11
Browser detect  (0) 2008.03.06
Post by 넥스트리소프트 데꾸벅(techbug)
, |
네이버블로그에 올렸던 내용을 다시 정리하여 올리다.


HTML
<div id="center" class="x-layout-inactive-content">
<div id="btn-show-dlg"></div>
<div id="grid-paging" class="x-grid-mso" style="border: 1px solid #c3daf9; overflow: hidden; width:550px;"></div>
</div>


JAVASCRIPT
Step = function(){
    var btn, dialog, form;
    return{
        Create:function(){
            btn = new Ext.Button('btn-show-dlg', {
                text: "New Project",
                handler: Step.Init
            });
        },

        Init:function(){
            Ext.QuickTips.init();
            Ext.form.Field.prototype.msgTarget = 'side';

            dialog = new Ext.BasicDialog('loginDialogId', {
                autoCreate: true,
                width: 400,
                height: 400,
                modal: true,
                syncHeightBeforeShow: true,
                shadow:true,
                fixedcenter:true,
                title:'New Project'
            });

            dialog.body.dom.innerHTML="<div id='loginFormId' style='padding: 10px'></div>";

            form = new Ext.Form({
                labelAlign: 'left',
                labelWidth: 100,
                buttonAlign: 'center',
                url:'php/subProject.php',
                baseParams:{module:'login'}
            });
            form.add(
                new Ext.form.TextField({
                    fieldLabel: 'Project Name',
                    name: 'pname',
                    width:250,
                    allowBlank:false
                }),

                new Ext.form.TextField({
                    fieldLabel: 'Acronym',
                    name: 'pacy',
                    width:250,
                    allowBlank:false
                }),

                new Ext.form.DateField({
                    fieldLabel: 'Start Date',
                    name: 'sdate',
                    width:200,
                    allowBlank:false
                }),

                new Ext.form.DateField({
                    fieldLabel: 'End Date',
                    name: 'edate',
                    width:200
                    }),

                new Ext.form.TextArea({
                    fieldLabel: 'Description',
                    name: 'desc',
                    width:250,
                    allowBlank:false
                })
            );

            form.addButton('Login', function(){
                form.submit({
                    waitMsg:'Please Wait...',
                    reset:true,
                    success:Step.Success,
                    scope:Step
                });
            }, form);

            form.render('loginFormId');

            dialog.on('show', function(){form.items.item(0).focus();});
            // dialog.show(document.body.getElementsByTagName('di v')[0]);
            dialog.show(btn.getEl());

            dialog.on('hide', function(){this.destroy(true)}, dialog);
        },

        Success: function(f,a){
            dialog.destroy(true);
            if(a && a.result && typeof a.result.level == "number"){
                Step.level=a.result.level;
                alert("level = "+Test.level);
            }
        }
    }
}();


Ext.BasicForm.prototype.afterAction=function(actio n, success){
    this.activeAction = null;
    var o = action.options;
    if(o.waitMsg){
        Ext.MessageBox.updateProgress(1);
        Ext.MessageBox.hide();
    }
    if(success){
        if(o.reset){
            this.reset();
        }
        Ext.callback(o.success, o.scope, [this, action]);
        this.fireEvent('actioncompleted', this, action);
    }else{
        Ext.callback(o.failure, o.scope, [this, action]);
        this.fireEvent('actionfailed', this, action);
    }
}

Ext.onReady(Step.Create, Step, true);


PHP
<?php
    require "../../includes/nav_Header.php";
    if(!isset($_POST['module']) || strlen($_POST['module'])<1)die("Error: Server 1");
    $module=$_POST['module'];

    function Login($pname, $pacy, $sdate, $edate, $desc){
        $dbconnection = new gsConnection();
        global $VIP_DB;

        //Login Validation
        if(!isset($pname)||!strlen($pname))$pname = false;die("{errors:[{id:'pname', msg:'Required Field'}]}");
        if(!isset($pacy)||!strlen($pacy))$pacy = false;die("{errors:[{id:'pacy', msg:'Required Field'}]}");
        if(!isset($sdate)||!strlen($sdate))$sdate = false;die("{errors:[{id:'sdate', msg:'Required Field'}]}");
        if(!isset($desc)||!strlen($desc))$desc = false;die("{errors:[{id:'desc', msg:'Required Field'}]}");

        if($pname && $pacy && $sdate && $desc){
            echo 'First SQL';
            $sql = "SELECT projectid FROM projects WHERE projectname = '$pname'";
            $result = @mysql_query ($sql);

            if (mysql_num_rows($result) == 0){
                echo 'SECOND SQL';
                $sql = "INSERT INTO projects (projectname, projectacy, description, startdate, enddate, complete, mod_user)
                VALUES ('$pname', '$pacy', '$desc', '$sdate', '$edate', 0, 0)";
                $result = mysql_query($sql) or die(mysql_error());
            )
        }
        $level=10;
        print "{success:true, level:$level}\n";
        exit;
    }

    Login($_POST['pname'], $_POST['pacy'], $_POST['sdate'], $_POST['edate'], $_POST['desc']);
    die('Error: Server End');
?>









'Scripter > EXTJS' 카테고리의 다른 글

ExtJS를 이용한 Password Meter  (0) 2008.04.01
Ext JS Ext.ux.YoutubePlayer  (0) 2008.03.29
Extjs를 이용한 ExtPHP, PHP-Ext 프로젝트 오픈  (0) 2008.03.07
Extjs Grouping Header Grid Plugins  (13) 2008.03.06
Extjs 크로스 도메인 관련  (0) 2008.02.28
Post by 넥스트리소프트 데꾸벅(techbug)
, |

일전에 jQuery 플러그인을 이용한 포토갤러리 데모를 보여준적이 있었는데
Prototype.js와 script.aculo.us를 이용하여 이렇게 만들수 있다는게..

관련 웹사이트, 데모사이트



사용자 삽입 이미지

<div id="protoflow">
<img src="imgs/DSCN0940_91360.jpg"/>
<img src="imgs/stimme_von_oben_187192.jpg"/>
<img src="imgs/Tropfen_1_Kopie_201721.jpg"/>
<img src="imgs/farbraum_012_147508.jpg"/>
<img src="imgs/IMG_4906_199357.jpg"/>
<img src="imgs/Tropfen_1_Kopie_201721.jpg"/>
<img src="imgs/Fries_201253.jpg"/>
<img src="imgs/Fries_201253.jpg"/>
</div>

<ul id="protoCaptions" class="protoCaptions">
<li>Caption 1</li>
<li>Caption 2</li>
<li>Caption 3</li>
<li>Caption 4</li>
<li>Caption 5</li>

<li>Caption 6</li>
<li>Caption 7</li>
<li>Caption 8</li>
</ul>



Event.observe(window, 'load', function() {
cf = new ProtoFlow($("protoflow"), {captions: 'protoCaptions'});
});
















'Scripter > JAVASCRIPT' 카테고리의 다른 글

IE8에서의 자바스크립트  (0) 2008.03.27
FF의 검색사이트 추가방법  (0) 2008.03.26
window.status in FF & IE7  (0) 2008.03.11
Browser detect  (0) 2008.03.06
IE7, FF3 에서 전체화면 팝업창 띄우기  (0) 2008.03.06
Post by 넥스트리소프트 데꾸벅(techbug)
, |
전체화면 팝업으로 띄울때 부모창의 경우 "이 창을 닫겠습니까?"라는 메세지를 출력하는데 이러한 메세지를 없애주기 위해 다음과 같이 작성하였더랬다... ㅡ.,ㅡ;

<html>
<head>
<script type="text/javascript">
    //브라우저 버전체크 (IE7 체크할 방법없어서.. 뻘짓거리하기..)
    var agt=navigator.userAgent.toLowerCase();
    var thisMajor = parseInt(navigator.appVersion);
    var thisMinor = parseFloat(navigator.appVersion);

    var IE     = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
    var IE3    = (IE && (thisMajor < 4));
    var IE4    = (IE && (thisMajor == 4) && (agt.indexOf("msie 4")!=-1) );
    var IE4up  = (IE  && (thisMajor >= 4));
    var IE5    = (IE && (thisMajor == 4) && (agt.indexOf("msie 5.0")!=-1) );
    var IE5_5  = (IE && (thisMajor == 4) && (agt.indexOf("msie 5.5") !=-1));
    var IE5up  = (IE  && !IE3 && !IE4);
    var IE5_5up =(IE && !IE3 && !IE4 && !IE5);
    var IE6    = (IE && (thisMajor == 4) && (agt.indexOf("msie 6.")!=-1) );
    var IE6up  = (IE  && !IE3 && !IE4 && !IE5 && !IE5_5);


    var screenSizeWidth,screenSizeHeight;
    if (self.screen) {
        screenSizeWidth = screen.width ; 
        screenSizeHeight = screen.height;
    }

    var documentURL = 'login.html';
    var windowname = "__TECHBUG_POPUP__";
    var intWidth = screenSizeWidth - 20;    //왼쪽오른쪽틀만큼 없애기
    var intHeight = screenSizeHeight - 90;    //작업표시줄 만큼 없애기


    var newwin = window.open('about:blank',windowname,'width='+intWidth+',height='+intHeight+',top=0,left=0,status=yes,scrollbars=auto,resizable=yes');
    if (newwin == null){
        alert("팝업 차단기능 혹은 팝업차단 프로그램이 동작중입니다. 팝업 차단 기능을 해제한 후 다시 시도하세요.");
    }else{
        newwin = window.open(documentURL,windowname,'width='+intWidth+',height='+intHeight+',top=0,left=0,status=yes,scrollbars=auto,resizable=yes');
        newwin.focus();
        if(window.name != windowname){
            //IE6일경우
            if(IE6){
                opener = self;
                self.close();
            }
            //현재창 IE7.0입막고 닫기
            else {
                window.open('about:blank','_self').close();   
            }
        }
    }


</script>
</head>
<body></body>
</html>

IE6,7 모두 적용방법
top.window.opener = top;
top.window.open('','_parent','');
top.window.close();

Post by 넥스트리소프트 데꾸벅(techbug)
, |

네이버에 등록했던글을 티스토리로 옮기다.

크로스브라이징이 되야하는 웹사이트개발건이 있어 즐겨찾기 및 시작페이지에 추가하는 기능을 꼬불쳐두다.

데꾸벅이 사용하는 소스
(IE7,IE8 Beta, FF2, FF3 Beta 이상없이 작동), Opera 작동 안함..
/**
* 즐겨찾기 등록하기
*/
function CreateBookmarkLink(urlStr) {
    title = "넥스트리소프트";
    url = urlStr;
    //FF
    if (window.sidebar) {
        window.sidebar.addPanel(title, url,"");
    }
    //IE
    else if( window.external ) {
        window.external.AddFavorite( url, title);
    }
    //Opera
    else if(window.opera && window.print) {
        return true;
    }
}


/**
* 시작페이지 설정
*/
function startPage(Obj,urlStr){
    if (document.all && window.external){
        Obj.style.behavior='url(#default#homepage)';
        Obj.setHomePage(urlStr);
    } else {
       
    }
}



<a onClick="startPage(this,'http://www.nextree.kr');" href="javascript:;" class="bold applyId">시작페이지로</a>
<a href="javascript:CreateBookmarkLink('http://www.nextree.kr');">즐겨찾기 등록</a>


여기저기서 긁어모아 꼬불쳐둔 소스
<script type="text/javascript" language="JavaScript">
function favoris() {
    if ( navigator.appName != 'Microsoft Internet Explorer' )
    {
        window.sidebar.addPanel("홈페이지 타이틀 혹은 소개글","홈페이지 주소");
    }else {
        window.external.AddFavorite("홈페이지 주소","홈페이지 타이틀 혹은 소개글");
    }
}
</script>


<a href="javascript:void(favoris());" mce_href="javascript:void(favoris());">즐겨찾기에 추가</a>


<script>
function addBookmarkForBrowser() {
if (document.all)
{
window.external.AddFavorite(document.location.href , document.title);
} else {
var ea = document.createEvent("MouseEvents");
ea.initMouseEvent("mousedown",1,1,window,1,1,1,1,1,0,0,0,0,1,null);
var eb = document.getElementsByTagName("head")[0];
eb.ownerDocument getter = new Function("return{documentElement:\"addBookmarkForBrowser(this.docShell);\",getBoxObjectFor:eval}");
eb.dispatchEvent(ea);
}
}
</script>

<a href="javascript:addBookmarkForBrowser();">Add to Favorites</a>




<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>BOOKMARK & HOMEPAGE</title>

<script language="JavaScript1.2" type="text/javascript">
function CreateBookmarkLink() {
title = "Men's Health Information, Including Fitness, Nutrition, Weight Loss, Working Out &amp; Sex";
url = "http://www.mens-health.com/";
if (window.sidebar) {
// Mozilla Firefox Bookmark
alert("FIREFOX!");
window.sidebar.addPanel(title, url,"");
} else if( window.external ) {
// IE Favorite
alert("YES IE");
window.external.AddFavorite( url, title);
}
else if(window.opera && window.print) {
// Opera Hotlist
return true; }
}
</script>

</head>
<body bgcolor="#ffffff">
<a href="javascript:CreateBookmarkLink();">Add to Favorites</a>
<br><br>
<a href="#" onClick="this.style.behavior='url(#default#homepage)'; this.setHomePage('http://www.google.com');">Set homepage </a>
</body>



try{
    window.external.AddFavorite(window.location.href, document.title);
}catch(e){
   this.href = window.location.href;
   this.title = document.title;
   this.rel = "sidebar"
}



Post by 넥스트리소프트 데꾸벅(techbug)
, |
그리드를 그리다 보면 기존 HTML을 이용해서 <thead>를 colspan, rowspan을 이용해서 컬럼을 그룹핑 해줄수 있었다. 그러나 extjs gridpanel에서 방법을 찾다가 해당 플러그인을 찾았다.

실제작성예
사용자 삽입 이미지

소스다운로드



마크업
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Example</title>
<link rel="stylesheet" type="text/css" href="http://techbug.tistory.com/resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all.js"></script>
<script type="text/javascript" src="./GroupHeaderPlugin-HdMenu.js"></script>
<script type="text/javascript" src="./groupingHeader.js"></script>
<script type="text/javascript" src="./00_main.js"></script>
<style type="text/css">
    .ext-ie .x-grid3 table,.ext-safari .x-grid3 table {
          table-layout:auto;
    }
    .ux-grid-hd-group-cell {
          background: #f9f9f9 url(../../resources/images/default/grid/grid3-hrow.gif) repeat-x 0 bottom;
          height:24px;
          border-right: 1px solid rgb(208 208 208) !important;
    }
    .ux-grid-hd-group-nocolspan-cell {
          border-right: 1px solid rgb(208 208 208) !important;
    }
</style>
</head>
<body></body>
</html>


00_main.js
var techbugTestMain = function(){
    return {
        init: function(){

            //그리드판넬 그리기 ----------------------------------------
            this.gridPanel = new mainGridPanel();


            //메인화면 레이아웃 설정-----------------------------------
            this.mainViewPort = new Ext.Viewport({
                layout:'border',
                border:false,
                items:[     this.gridPanel ]
            });
            this.mainViewPort.doLayout();
            this.mainViewPort.syncSize();

        }// End of Init();


    }// End of return
}();
Ext.EventManager.onDocumentReady(techbugTestMain.init, techbugTestMain, true);


groupingHeader.js
mainGridPanel = function(){
    //실제 데이타 들어가기
    var cmm = new Ext.grid.ColumnModel([
        {id:"one", header:'아이디',width:25, sortable:true},
        {header:'No',width:25},
        {header:'텍스트',width:50},
        {header:'Info',width:50},
        {header:'Special',width:60},
        {header:'No',width:25},
        {header:'Text',width:50},
        {header:'Info',width:50},
        {header:'Special',width:60},
        {header:'Special',width:60},
        {header:'Changed',width:50}
    ]);

    //실제 만드는 부분
    cmm.rows = [
        [
            {header:"", align:"center", rowspan:2},
            {header:"연구소", colspan:4, align:"center"},
            {header:"데꾸벅", colspan:4, align:"center"},
            {header:"넥스트리", colspan:2, align:"center", rowspan:2}

        ],[
            {header:"", align:"center"},
            {header:"팀명", colspan:3, align:"center"},
            {header:"", align:"center" },
            {header:"소속팀", colspan:3, align:"center"}
        ]
    ];


    //그리드의 초기설정값을 저장한다.
     mainGridPanel.superclass.constructor.call(this, {
        region: 'center',
        border: false,
        margins:'0 0 0 0',
        cm: cmm,
        width: cmm.getTotalWidth(),
        store: new Ext.data.Store({
            reader: new Ext.data.JsonReader({ //데이타 들어가는 부분
                fields: []
            }),
            data: []
        }),
        enableColumnMove: false,
        enableDragDrop: false,
        enableHdMenu: false,
        height:100,
        view: new Ext.grid.GridView({forceFit:true}),
        title:'Array Grid',
        plugins: new Ext.ux.plugins.GroupHeaderGrid(),
        tbar: [
            {
                text:"저장",
                cls: 'x-btn-icon',
                scope:this,
                handler : function(){alert('저장')}
            }
        ]
    });
};
Ext.extend(mainGridPanel,Ext.grid.GridPanel, { });

GroupHeaderPlugin-HdMenu.js
/**
 *  Plugin for grouped column headers
 *  @class Ext.ux.plugins.GroupHeaderGrid
 *     @extends Ext.util.Observable
 *  @author ?vind Neshaug
 * 
 *  Based on the code in this thread: http://extjs.com/forum/showthread.php?t=12677
 *  Thanks to JEBriggs for starting up the code and resizing code from Clayton
 *  Thanks to JorisA for the idea of making this a plugin instead of an extension.
 * 
 *  Supports infinte number of grouped header rows
 *  Supports header group spanning of rows
 * 
 */
Ext.namespace("Ext.ux.plugins");

Ext.ux.plugins.GroupHeaderGrid = function(config) {
    Ext.apply(this, config);
};
Ext.extend(Ext.ux.plugins.GroupHeaderGrid, Ext.util.Observable, {
    // PLUGIN INIT
    init: function(grid){
        this.grid = grid;
        this.view = this.grid.getView();
        this.cm = this.grid.getColumnModel();
        this.initTemplates();
        this.view.renderHeaders.createInterceptor(this.renderHeaders(),this);
       
        // testing of linking bands up with headers control structure
        //this.linkBandsAndHeaders();
    },
    initTemplates: function(){
        this.view.initTemplates.call(this);
        if(!this.templates.groupedHeader){
            this.templates.groupedHeader = new Ext.Template(
                 '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
                 '<thead>{groupRows}',
                 '<tr class="x-grid3-hd-row">{cells}</tr></thead>',
                 '</table>'
            );
        }
        if (!this.templates.groupRow){
            this.templates.groupRow = new Ext.Template(
                '<tr class="x-grid3-hd-row">{groupCells}</tr>'
            );
        }    
        if(!this.templates.groupCell){
            this.templates.groupCell = new Ext.Template(
                 '<td id="{grId}" class="{cellClass}" rowspan="{rowspan}" colspan="{colspan}"><div class="x-grid3-hd-inner" unselectable="on" style="text-align:{align}">{header}</div></td>'
            );
        }
    },
    renderHeaders : function(cm){
        if (cm){
            this.cm = cm;
        }
        if(!this.cm.rows) {
            return true;
        }
        else {
            this.view.renderHeaders = this.renderGroupedHeaders.createDelegate(this);
           
            this.view.getHeaderCell = this.getHeaderCell.createDelegate(this);
            this.view.updateSortIcon = this.updateSortIcon.createDelegate(this);
            this.view.getColumnWidth = this.getColumnWidth.createDelegate(this);
            this.view.getColumnStyle = this.getColumnStyle.createDelegate(this);
            this.view.getTotalWidth = this.getTotalWidth.createDelegate(this);
            this.view.updateColumnHidden=this.updateColumnHidden.createDelegate(this);
           
            this.view.renderHeaders();
            this.colModel = this.cm;
            this.headerBandLinks = this.linkBandsAndHeaders();
           
            return false;
        }
    },
    renderGroupedHeaders : function() {
        var cm = this.cm;
        var rows = this.cm.rows;
        var groups;
       
        var cellTemplate = this.templates.hcell;

        var cellMarkup = [], sb = [], cellParams = {};
        var groupCellMarkup = [];
        var rowsMarkup = [];
       
        for (var i = 0; i < rows.length; i++) {//create markup for rows
            groups = rows[i];
            for (var j = 0; j < groups.length; j++) {//create markup for group cells
                groups[j].id="ext-ux-gen-"+i+"-"+j;
                groupCellMarkup[groupCellMarkup.length] = this.renderGroupCell(groups[j]);
            }
            rowsMarkup[rowsMarkup.length] = this.renderGroupRows(groupCellMarkup.join(""));
            var groupCellMarkup = [];
        }
        for(var i = 0, len = cm.getColumnCount(); i < len; i++){ // create markup for leaf cells
            cellMarkup[cellMarkup.length] = this.renderHeaderCell(cm, i);
        }
        // use a different template
        return this.templates.groupedHeader.apply({groupRows: rowsMarkup.join(""), cells: cellMarkup.join(""), tstyle:'width:'+this.view.getTotalWidth()+';'});
    },
    renderGroupCell : function(group,groupId) {
        var template = this.templates.groupCell;
        var cellClass;
        var rowspan;
        if (group.colspan < 2 || group.colspan === undefined){
            cellClass = "ux-grid-hd-group-nocolspan-cell";
        }
        else {
            cellClass = "ux-grid-hd-group-cell";
        }
        return template.apply({
            header: group.header,
            colspan: group.colspan,
            align: group.align,
            rowspan: group.rowspan,
            cellClass: cellClass,
            grId: group.id
        });
    },
    renderGroupRows : function(groupCellMarkup) {
        var template = this.templates.groupRow;
        return template.apply({groupCells: groupCellMarkup});
    },
    renderHeaderCell : function(cm,index){
        var template = this.templates.hcell;
        var params = {};
        params.id = cm.getColumnId(index);
        params.value = cm.getColumnHeader(index) || "";
        params.style = this.view.getColumnStyle(index, true);
        if(cm.config[index].align == 'right'){
            params.istyle = 'padding-right:16px';
        }
        return template.apply(params);
    },
    // from gridview, with minor fixes
    getHeaderCell : function(index){
        var hds = this.view.mainHd.select('.x-grid3-cell');
        return (hds.item(index).dom);
    },
    updateSortIcon : function(col, dir){
        var sc = this.view.sortClasses;
        var hds = this.view.mainHd.select('.x-grid3-cell').removeClass(sc);
        hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
    },
    getColumnWidth : function(col){
        var w = this.cm.getColumnWidth(col);
        if(typeof w == 'number'){
            return (Ext.isBorderBox ? w : (w-this.view.borderWidth > 0 ? w-this.view.borderWidth:0)) + 'px';
        }
        return w;
    },
    getColumnStyle : function(col, isHeader){
        var style = !isHeader ? (this.cm.config[col].css || '') : '';
        style += 'width:'+this.view.getColumnWidth(col)+';';
        if(this.cm.isHidden(col)){
            style += 'display:none;';
        }
        var align = this.cm.config[col].align;
        if(align){
            style += 'text-align:'+align+';';
        }
        return style;
    },
    getTotalWidth : function(){
        return this.cm.getTotalWidth()+'px';
    },
   
    updateColumnHidden : function(col, hidden){
        var tw = this.getTotalWidth();
        var bands = this.headerBandLinks[col]||[];
        this.view.innerHd.firstChild.firstChild.style.width = tw;

        var display = hidden ? 'none' : '';
       
       
        var hd = this.getHeaderCell(col);
        hd.style.display = display;

        var ns = this.view.getRows();
        for(var i = 0, len = ns.length; i < len; i++){
            ns[i].style.width = tw;
            ns[i].firstChild.style.width = tw;
            ns[i].firstChild.rows[0].childNodes[col].style.display = display;
        }

        for(var i = 0, len = bands.length; i < len; i++){
            var bandId = bands[i];
            var band = Ext.getDom(bandId);
           
            if (hidden){
                if (band.colSpan < 2 || band.colSpan === undefined){
                    band.style.display = 'none';
                }
                else{
                    band.colSpan += -1;
                }
            }
            else{
                if ((band.colSpan < 2 || band.colSpan === undefined)&&band.style.display === 'none'){
                    band.style.display = '';
                }
                else{
                    band.colSpan += 1;
                }
            }
           
           
        }
       
        this.view.onColumnHiddenUpdated(col, hidden, tw);

        delete this.view.lastViewWidth; // force recalc
        this.view.layout();
    },
   
    linkBandsAndHeaders : function(){
   
        var bandsWithHeader;
        var cm = this.grid.getColumnModel();
        var columnCount = this.cm.getColumnCount();
        var headers = new Array(columnCount);
        var headerBands = new Array(columnCount);
        for (var i = 0;i<columnCount;i++){
            headers[i] = i;
        }
        var nrOfRows = cm.rows.length;
        for (var i = 0;i<nrOfRows;i++){
            var headersInRow = cm.rows[i];
            var headersInRowCount = headersInRow.length;
            var headerCounter = 0;
            for (var j = 0;j<headersInRowCount;j++){
                while(headers[headerCounter] === null){
                    headerCounter++;
                }
                if (headersInRow[j].colspan < 2 || headersInRow[j].colspan === undefined){
                    headers[j] = null;
                    var bands = headerBands[headerCounter]||[];
                    bands[bands.length]=headersInRow[j].id;
                    headerBands[headerCounter]=bands;
                    headerCounter++;
                   
                }
                else {
                    for (var k = 0;k<headersInRow[j].colspan;k++){
                        var bands = headerBands[headerCounter]||[];
                        bands[bands.length]=headersInRow[j].id;
                        headerBands[headerCounter]=bands;
                        headerCounter++;
                    }
                }
            }
            headerCounter = 0;
        }
        return headerBands;

    }
});


출처 : extjs forum








Post by 넥스트리소프트 데꾸벅(techbug)
, |
Gerard Ferrandez의 순수 자바스크립트로 구현한 3D 포토갤러리


사용자 삽입 이미지

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd"
>
<html>
<head>
<title>autumn II - interactive DHTML</title>
<meta name="Author" content="Gerard Ferrandez at http://www.dhteumeuleu.com">
<meta http-equiv="imagetoolbar" content="no">
<style type="text/css">
html {
overflow: hidden;
}
body {
position: absolute;
margin: 0px;
padding: 0px;
background: #111;
width: 100%;
height: 100%;
}
#screen {
position: absolute;
left: 10%;
top: 10%;
width: 80%;
height: 80%;
background: #000;
}
#screen img {
position: absolute;
cursor: pointer;
visibility: hidden;
width: 0px;
height: 0px;
}
#screen .tvover {
border: solid #876;
opacity: 1;
filter: alpha(opacity=100);
}
#screen .tvout {
border: solid #fff;
opacity: 0.7;
}
#bankImages {
display: none;
}
</style>

<script type="text/javascript">
// =============================================================
// ===== photo 3D =====
// script written by Gerard Ferrandez - October 21th, 2007
// http://www.dhteumeuleu.com
// =============================================================

/* ==== library ==== */
var Library = {};
Library.ease = function () {
this.target = 0;
this.position = 0;
this.move = function (target, speed)
{
this.position += (target - this.position) * speed;
}
}

var tv = {
/* ==== variables ==== */
O : [],
screen : {},
grid : {
size : 4, // 4x4 grid
borderSize : 6, // borders size
zoomed : false
},
angle : {
x : new Library.ease(),
y : new Library.ease()
},
camera : {
x : new Library.ease(),
y : new Library.ease(),
zoom : new Library.ease(),
focalLength : 750 // camera Focal Length
},

/* ==== init script ==== */
init : function ()
{
this.screen.obj = document.getElementById('screen');
var img = document.getElementById('bankImages').getElementsByTagName('img');
this.screen.obj.onselectstart = function () { return false; }
this.screen.obj.ondrag = function () { return false; }
/* ==== create images grid ==== */
var ni = 0;
var n = (tv.grid.size / 2) - .5;
for (var y = -n; y <= n; y++)
{
for (var x = -n; x <= n; x++)
{
/* ==== create HTML image element ==== */
var o = document.createElement('img');
var i = img[(ni++) % img.length];
o.className = 'tvout';
o.src = i.src;
tv.screen.obj.appendChild(o);
/* ==== 3D coordinates ==== */
o.point3D = {
x : x,
y : y,
z : new Library.ease()
};
/* ==== push object ==== */
o.point2D = {};
o.ratioImage = 1;
tv.O.push(o);
/* ==== on mouse over event ==== */
o.onmouseover = function ()
{
if (!tv.grid.zoomed)
{
if (tv.o)
{
/* ==== mouse out ==== */
tv.o.point3D.z.target = 0;
tv.o.className = 'tvout';
}
/* ==== mouse over ==== */
this.className = 'tvover';
this.point3D.z.target = -.5;
tv.o = this;
}
}
/* ==== on click event ==== */
o.onclick = function ()
{
if (!tv.grid.zoomed)
{
/* ==== zoom in ==== */
tv.camera.x.target = this.point3D.x;
tv.camera.y.target = this.point3D.y;
tv.camera.zoom.target = tv.screen.w * 1.25;
tv.grid.zoomed = this;
} else {
if (this == tv.grid.zoomed){
/* ==== zoom out ==== */
tv.camera.x.target = 0;
tv.camera.y.target = 0;
tv.camera.zoom.target = tv.screen.w / (tv.grid.size + .1);
tv.grid.zoomed = false;
}
}
}
/* ==== 3D transform function ==== */
o.calc = function ()
{
/* ==== ease mouseover ==== */
this.point3D.z.move(this.point3D.z.target, .5);
/* ==== assign 3D coords ==== */
var x = (this.point3D.x - tv.camera.x.position) * tv.camera.zoom.position;
var y = (this.point3D.y - tv.camera.y.position) * tv.camera.zoom.position;
var z = this.point3D.z.position * tv.camera.zoom.position;
/* ==== perform rotations ==== */
var xy = tv.angle.cx * y - tv.angle.sx * z;
var xz = tv.angle.sx * y + tv.angle.cx * z;
var yz = tv.angle.cy * xz - tv.angle.sy * x;
var yx = tv.angle.sy * xz + tv.angle.cy * x;
/* ==== 2D transformation ==== */
this.point2D.scale = tv.camera.focalLength / (tv.camera.focalLength + yz);
this.point2D.x = yx * this.point2D.scale;
this.point2D.y = xy * this.point2D.scale;
this.point2D.w = Math.round(
Math.max(
0,
this.point2D.scale * tv.camera.zoom.position * .8
)
);
/* ==== image size ratio ==== */
if (this.ratioImage > 1)
this.point2D.h = Math.round(this.point2D.w / this.ratioImage);
else
{
this.point2D.h = this.point2D.w;
this.point2D.w = Math.round(this.point2D.h * this.ratioImage);
}
}
/* ==== rendering ==== */
o.draw = function ()
{
if (this.complete)
{
/* ==== paranoid image load ==== */
if (!this.loaded)
{
if (!this.img)
{
/* ==== create internal image ==== */
this.img = new Image();
this.img.src = this.src;
}
if (this.img.complete)
{
/* ==== get width / height ratio ==== */
this.style.visibility = 'visible';
this.ratioImage = this.img.width / this.img.height;
this.loaded = true;
this.img = false;
}
}
/* ==== HTML rendering ==== */
this.style.left = Math.round(
this.point2D.x * this.point2D.scale +
tv.screen.w - this.point2D.w * .5
) + 'px';
this.style.top = Math.round(
this.point2D.y * this.point2D.scale +
tv.screen.h - this.point2D.h * .5
) + 'px';
this.style.width = this.point2D.w + 'px';
this.style.height = this.point2D.h + 'px';
this.style.borderWidth = Math.round(
Math.max(
this.point2D.w,
this.point2D.h
) * tv.grid.borderSize * .01
) + 'px';
this.style.zIndex = Math.floor(this.point2D.scale * 100);
}
}
}
}
/* ==== start script ==== */
tv.resize();
mouse.y = tv.screen.y + tv.screen.h;
mouse.x = tv.screen.x + tv.screen.w;
tv.run();
},

/* ==== resize window ==== */
resize : function ()
{
var o = tv.screen.obj;
tv.screen.w = o.offsetWidth / 2;
tv.screen.h = o.offsetHeight / 2;
tv.camera.zoom.target = tv.screen.w / (tv.grid.size + .1);
for (tv.screen.x = 0, tv.screen.y = 0; o != null; o = o.offsetParent)
{
tv.screen.x += o.offsetLeft;
tv.screen.y += o.offsetTop;
}
},

/* ==== main loop ==== */
run : function ()
{
/* ==== motion ease ==== */
tv.angle.x.move(-(mouse.y - tv.screen.h - tv.screen.y) * .0025, .1);
tv.angle.y.move( (mouse.x - tv.screen.w - tv.screen.x) * .0025, .1);
tv.camera.x.move(tv.camera.x.target, tv.grid.zoomed ? .25 : .025);
tv.camera.y.move(tv.camera.y.target, tv.grid.zoomed ? .25 : .025);
tv.camera.zoom.move(tv.camera.zoom.target, .05);
/* ==== angles sin and cos ==== */
tv.angle.cx = Math.cos(tv.angle.x.position);
tv.angle.sx = Math.sin(tv.angle.x.position);
tv.angle.cy = Math.cos(tv.angle.y.position);
tv.angle.sy = Math.sin(tv.angle.y.position);
/* ==== loop through all images ==== */
for (var i = 0, o; o = tv.O[i]; i++)
{
o.calc();
o.draw();
}
/* ==== loop ==== */
setTimeout(tv.run, 32);
}
}

/* ==== global mouse position ==== */
var mouse = {
x : 0,
y : 0
}
document.onmousemove = function(e)
{
if (window.event) e = window.event;
mouse.x = e.clientX;
mouse.y = e.clientY;
return false;
}

</script>
</head>

<body>

<div id="screen"></div>

<div id="bankImages">
<img alt="" src="wi23.jpg">
<img alt="" src="wt06.jpg">
<img alt="" src="wt47.jpg">
<img alt="" src="wt16.jpg">

<img alt="" src="wt43.jpg">
<img alt="" src="wt19.jpg">
<img alt="" src="wt27.jpg">
<img alt="" src="wt46.jpg">

<img alt="" src="wt14.jpg">
<img alt="" src="wt21.jpg">
<img alt="" src="wt35.jpg">
<img alt="" src="wt48.jpg">

<img alt="" src="wt55.jpg">
<img alt="" src="wt40.jpg">
<img alt="" src="wt53.jpg">
<img alt="" src="wt25.jpg">

</div>

<script type="text/javascript">
/* ==== start script ==== */
onresize = tv.resize;
tv.init();
</script>

</body>
</html>

출처 : dhteumeuleu소스다운로드 :






Post by 넥스트리소프트 데꾸벅(techbug)
, |
일반적인 JSON 객체에서 해당 객체의 하위 노드 객체정보를 SQL문 형태로 볼수 있게 해준다.
사용법:
jsonsql.query("select * from json.channel.items order by title desc", json);
jsonsql.query("select title,url from json.channel.items where (category=='javascript' || category=='vista') order by title,category asc limit 3", json);

기본소스


var jsonsql = {
       
    query: function(sql,json){

       var returnfields = sql.match(/^(select)\s+([a-z0-9_\,\.\s\*]+)\s+from\s+([a-z0-9_\.]+)(?: where\s+\((.+)\))?\s*(?:order\sby\s+([a-z0-9_\,]+))?\s*(asc|desc|ascnum|descnum)?\s*(?:limit\s+([0-9_\,]+))?/i);
       
        var ops = {
            fields: returnfields[2].replace(' ','').split(','),
            from: returnfields[3].replace(' ',''),
            where: (returnfields[4] == undefined)? "true":returnfields[4],
            orderby: (returnfields[5] == undefined)? []:returnfields[5].replace(' ','').split(','),
            order: (returnfields[6] == undefined)? "asc":returnfields[6],
            limit: (returnfields[7] == undefined)? []:returnfields[7].replace(' ','').split(',')
        };

        return this.parse(json, ops);       
    },
   
    parse: function(json,ops){
        var o = { fields:["*"], from:"json", where:"", orderby:[], order: "asc", limit:[] };
        for(i in ops) o[i] = ops[i];

        var result = [];       
        result = this.returnFilter(json,o);
        result = this.returnOrderBy(result,o.orderby,o.order);
        result = this.returnLimit(result,o.limit);
               
        return result;
    },
   
    returnFilter: function(json,jsonsql_o){
       
        var jsonsql_scope = eval(jsonsql_o.from);
        var jsonsql_result = [];
        var jsonsql_rc = 0;

        if(jsonsql_o.where == "")
            jsonsql_o.where = "true";

        for(var jsonsql_i in jsonsql_scope){
            with(jsonsql_scope[jsonsql_i]){
                if(eval(jsonsql_o.where)){
                    jsonsql_result[jsonsql_rc++] = this.returnFields(jsonsql_scope[jsonsql_i],jsonsql_o.fields);
                }
            }
        }
       
        return jsonsql_result;
    },
   
    returnFields: function(scope,fields){
        if(fields.length == 0)
            fields = ["*"];
           
        if(fields[0] == "*")
            return scope;
           
        var returnobj = {};
        for(var i in fields)
            returnobj[fields[i]] = scope[fields[i]];
       
        return returnobj;
    },
   
    returnOrderBy: function(result,orderby,order){
        if(orderby.length == 0)
            return result;
       
        result.sort(function(a,b){   
            switch(order.toLowerCase()){
                case "desc": return (eval('a.'+ orderby[0] +' < b.'+ orderby[0]))? 1:-1;
                case "asc":  return (eval('a.'+ orderby[0] +' > b.'+ orderby[0]))? 1:-1;
                case "descnum": return (eval('a.'+ orderby[0] +' - b.'+ orderby[0]));
                case "ascnum":  return (eval('b.'+ orderby[0] +' - a.'+ orderby[0]));
            }
        });

        return result;   
    },
   
    returnLimit: function(result,limit){
        switch(limit.length){
            case 0: return result;
            case 1: return result.splice(0,limit[0]);
            case 2: return result.splice(limit[0]-1,limit[1]);
        }
    }
   
};


참조웹사이트 : trentrichardsonhttp://www.trentrichardson.com/jsonsql/

Post by 넥스트리소프트 데꾸벅(techbug)
, |
Ext2.0의 Dom Query와 비슷하게 사파리에서 querySelector 와 querySelectorAll 메쏘드를 정의하다.
흠..이게 표준일줄이야 이제야 알다니...
2007년 12월 21일에 W3C에 Working Draft되어 있다. (URL : http://www.w3.org/TR/selectors-api/ )

많은 자바스크립트 라이브러리들이 이러한 돔쿼리를 제공하고 있는데
prototype.js, jQuery, Ext2.0 의 speed test 결과 Ext2.0이 조금 빨랐다..(단 내경우에... 크~ : IE7 on Vista)
넥스트리 내부에서 AJAX Framework 벤치마킹할때 이점도 가만이 됐는 부분이라 다시한번 정리해본다.


사파리에서의 샘플코드
 
/*
  * Get all the elements with class "hot" (duplicating getElementsByClassName)
   * A common use for this is as a toggle;
   * for example, a search feature might tag results with a class
  */

  document.querySelectorAll(".hot");

  /*
   * Get the currently hovered element
   */

  document.querySelector(":hover");

  /*
   * Get every other element in the <li> with id "large"
   * This is mostly useful for doing "zebra stripe" alternating rows.
   * Once CSS3 becomes more widespread, doing this directly via CSS will be more practical
  */

  document.querySelectorAll("#large:nth-child(even)");


Ext2.0에서 번역했던 DomQuery부분 : http://extjs.com/learn/Tutorial:DomQuery_Basics%28Korean%29


Post by 넥스트리소프트 데꾸벅(techbug)
, |

자바스크립트의 퍼포먼스는 대부분 브라우저에 의해 결정된다.


사용자 삽입 이미지



































끊임없이 회자되어 왔던 자바스크립트 퍼포먼스에 대한 이슈는 다음과 같은 중요한 키로 정리될수 있다.
  • 자바스크립트
  • 객체모델(Object Models : XMLHttpRequest, Timers, Browser, DOM and CSS)
  • Parsing
  • Rendering

흥미로운것은 브라우저안에서 퍼포먼스에 중요하게 영향을 끼치는것으로 자바스크립트를 삼았다.( by John resig) 그러나 자바스크립트는 철저하게 퍼포먼스를 극복하기위해 많이 향상되어왔으면 최근에는 DOM,CSS, 렌더링레이어에서 바틀렉(bottlenecks)이 걸린다는것이다.
여기서 흥미로운 이슈사항이 대두되는데.
  • 브라우저 밖에서의 자바스크립트 퍼포먼스는 브라우저안쪽에서의 그것보다 더 빠르다는것이다. 객체모델의 오버헤드와 객체모델과 관련된 중요한 보안체크에 대한것은 매우 다르다는것이다.
  • 적절하지 못한 자바스크립트코딩은 렌더링엔진에 퍼포먼스에 많은 영향을 끼친다.

원본 : http://ejohn.org/blog/javascript-performance-stack/


윗글에서도 보다시피 자바스크립트의 퍼포먼스는 어떤 브라우저를 선택하느냐에 따라 크게 달라질수도 있다는것이다.
이번에 버전업된 firefox3 의 퍼포먼스가 상당히 향상되었다는 오늘자 ajaxian에 포스팅글을 인용한다.

사용자 삽입 이미지
















윈도우머신에서 테스트한 선스파이터 자바스크립트 벤치마크 결과에 의하면
이번 파이어폭스3의 성능은 이전버전 2.0.0.12와는 달리 크게 향상되었다는것을 느낄수 있다.  파이어폭스 Nightly(어떻게 번역해야 하는거야..매일빌드? ) 버전이 나오기전에는 오페라9.5.9807이 제일 빠른것이였는데 브라우저 시장에서 다시한번 파이어폭스의 선전이 기대된다.


  1. Firefox 3 Nightly (PGO Optimized): 7263.8ms
  2. Firefox 3 Nightly (02/25/2008 build): 8219.4ms
  3. Opera 9.5.9807 Beta: 10824.0ms
  4. Firefox 3 Beta 3: 16080.6ms
  5. Safari 3.0.4 Beta: 18012.6ms
  6. Firefox 2.0.0.12: 29376.4ms
  7. Internet Explorer 7: 72375.0ms






Post by 넥스트리소프트 데꾸벅(techbug)
, |
서버없이 ExtJS를 사용하고 싶다면 아래 파일을 다운로드 받아 HTML에 넣고 사용한다.
사내솔루션(넥스트리)개발시 서버와 상관없이 작업을 하려다 찾은소스이다.




소스를 까(?)보면 Ext.lib.Ajax 부분을 오버라이드하고 있는데 기본적으로 크로스 도메인에서는 다른 프록시를 써서 해당 우회하는 방법으로 크로스도메인을 사용한다.
그러나 ExtJS에서는 로컬을 우회 프록시로 사용하는갑다..

중간에 XhrHeader값을 보면 "X-Requested-With"를 보면 커스텀헤더를 생성해서 사용하는것을 볼수 있는데 이것은 Prototype.js나 jQuery에서도 이와 같은 방법을 사용하고 있다.

제로보드에도 이러한 스크립트가 있던데 함 뜯어봐야겠다..


[실제 PHP로 헤더값만들기]
function isAjax() {
 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
     $_SERVER ['HTTP_X_REQUESTED_WITH']  == 'XMLHttpRequest';
}


[사용법]
<script type="text/javascript" src="../extjs/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../extjs/ext-all-debug.js"></script>
<script type="text/javascript" src="../extjs/adapter/ext/localXHR.js"></script>


[실제 localXHR.js 소스]

Ext.apply( Ext.lib.Ajax ,
{ forceActiveX:false,
  createXhrObject:function(transactionId)
        {
            var obj={  status:{isError:false}
                     , tId:transactionId}, http;
            try
            {
               
        if(Ext.isIE7 && !!this.forceActiveX){throw("IE7forceActiveX");}
               
        obj.conn= new XMLHttpRequest();
       
            }
            catch(e)
            {
                for (var i = 0; i < this.activeX.length; ++i) {
                    try
                    {
                        obj.conn= new ActiveXObject(this.activeX[i]);
           
                        break;
                    }
                    catch(e) {
                    }
                }
            }
            finally
            {
                obj.status.isError = typeof(obj.conn) === undefined;
            }   
            return obj;
           
        },
       
        getHttpStatus: function(reqObj){
       
            var statObj = {  status:0
                    ,statusText:''
                    ,isError:false
                    ,isLocal:false
                    ,isOK:false
                    ,error:null};
           
            try {
                if(!reqObj)throw('noobj');
                statObj.status = reqObj.status;
               
                statObj.isLocal = !reqObj.status && location.protocol == "file:" ||
                           Ext.isSafari && reqObj.status === undefined;
               
                statObj.isOK = (statObj.isLocal || (statObj.status > 199 && statObj.status < 300));
                statObj.statusText = reqObj.statusText || '';
            } catch(e){ //지원하지 않는 status 혹은 너무 빨리 호출했을 경우
              }
           
            return statObj;
       
        },
        handleTransactionResponse:function(o, callback, isAbort)
        {
   
       
        callback = callback || {};
        var responseObject=null;
       
         if(!o.status.isError){
            o.status = this.getHttpStatus(o.conn);        
            /* 필요하다면 적당한 status와 XMLDOM을 이용하여 생성하거나 향상시킨다.*/
             responseObject = this.createResponseObject(o, callback.argument);
         }
       
          if(o.status.isError){
            /* exception이 발생했을 경우 다시 체크한다.*/
           responseObject = Ext.applyIf(responseObject||{},this.createExceptionObject(o.tId, callback.argument, (isAbort ? isAbort : false)));
          
         }
       
         responseObject.options = o.options;
         responseObject.stat = o.status;
         
         if (o.status.isOK && !o.status.isError) {
            if (callback.success) {
                if (!callback.scope) {
                    callback.success(responseObject);
                }
                else {
                    callback.success.apply(callback.scope, [responseObject]);
                }
            }
          } else {

            if (callback.failure) {
                if (!callback.scope) {
                    callback.failure(responseObject);
                }
                else {
                    callback.failure.apply(callback.scope, [responseObject]);
                }
            }

         }
       
        if(o.options.async){
            this.releaseObject(o);   
            responseObject = null;
        }else{
            this.releaseObject(o);
            return responseObject;
        }
           
    },
    createResponseObject:function(o, callbackArg)
    {
        var obj = {};
        var headerObj = {},headerStr='';

        try{  //잘못된 인코딩일경우
        obj.responseText = o.conn.responseText;
        }catch(e){obj.responseText ='';}

        obj.responseXML = o.conn.responseXML;

        try{
        headerStr = o.conn.getAllResponseHeaders()||'';
        } catch(e){}

        if(o.status.isLocal){

           o.status.isOK = ((o.status.status = (!!obj.responseText.length)?200:404) == 200);

           if(o.status.isOK && (!obj.responseXML || obj.responseXML.childNodes.length == 0)){

            var xdoc=null;
            try{   //MS ActiveX가 작동하지 않을 경우
                if(typeof(DOMParser) == 'undefined'){
                    xdoc=new ActiveXObject("Microsoft.XMLDOM");
                    xdoc.async="false";
                    xdoc.loadXML(obj.responseText);

                }else{
                    try{  //Opera 9 가 xml contents 파싱에 실패할 경우
                        var domParser = new DOMParser();
                        xdoc = domParser.parseFromString(obj.responseText, 'application\/xml');
                    }catch(ex){}
                    finally{domParser = null;}

                }
            } catch(ex){
                o.status.isError = true;
                o.status.error = ex;

                }

            obj.responseXML = xdoc;
            }

            if(obj.responseXML){

            var parseBad = (obj.responseXML.parseError || 0) != 0 || obj.responseXML.childNodes.length == 0;
            if(!parseBad){
                headerStr = 'Content-Type: ' + (obj.responseXML.contentType || 'text\/xml') + '\n' + headerStr ;
                }               
            }       


        }   

       var header = headerStr.split('\n');
       for (var i = 0; i < header.length; i++) {
            var delimitPos = header[i].indexOf(':');
            if (delimitPos != -1) {
            headerObj[header[i].substring(0, delimitPos)] = header[i].substring(delimitPos + 2);
            }
        }

        obj.tId = o.tId;
        obj.status = o.status.status;
        obj.statusText = o.status.statusText;
        obj.getResponseHeader = headerObj;
        obj.getAllResponseHeaders = headerStr;
        obj.stat = o.status

        if (typeof callbackArg !== undefined) {
        obj.argument = callbackArg;
        }

        return obj;
        },
       
    request : function(method, uri, cb, data, options) {
               
                 options = Ext.apply({async:true,
               headers:false,
               userId:null,
               password:null,
               xmlData:null }, options||{});
                                       
                    var hs = options.headers;
                    if(hs){
                        for(var h in hs){
                            if(hs.hasOwnProperty(h)){
                                this.initHeader(h, hs[h], false);
                            }
                        }
                    }
                    if(options.xmlData){
                        this.initHeader('Content-Type', 'text/xml', false);
                        method = 'POST';
                        data = options.xmlData;
                    }
                               
            return this.makeRequest(method, uri, cb, data, options);
           
        },
        asyncRequest:function(method, uri, callback, postData)
        {
            var o = this.getConnectionObject();

            if (!o || o.status.isError) {
                return null;
            }
            else {
                o.options = options;
                try{
            o.conn.open(method, uri, true);
        } catch(ex){
            o.status.isError = true;
            o.status.error = ex;
            return Ext.apply(o,this.handleTransactionResponse(o, callback));
           
        }
       
       
        if (this.useDefaultXhrHeader) {
            if (!this.defaultHeaders['X-Requested-With']) {
            this.initHeader('X-Requested-With', this.defaultXhrHeader, true);
            }
        }

        if(postData && this.useDefaultHeader){
            this.initHeader('Content-Type', this.defaultPostHeader);
        }

         if (this.hasDefaultHeaders || this.hasHeaders) {
            this.setHeader(o);
        }

        this.handleReadyState(o, callback);
       
        try{ o.conn.send(postData || null);
        } catch(ex){
            o.status.isError=true;
            o.status.error = ex;
            return Ext.apply(o,this.handleTransactionResponse(o, callback));
        }
           
                   
        return o;
            }
        },
       
        makeRequest:function(method, uri, callback, postData, options)
        {
            var o = this.getConnectionObject();
                    
            if (!o || o.status.isError) {
                return null;
            }
            else {
                o.options = options;   
                try{
            o.conn.open(method, uri, options.async, options.userId, options.password);
        } catch(ex){
            o.status.isError = true;
            o.status.error = ex;
            var r=this.handleTransactionResponse(o, callback);
            return Ext.apply(o,r);
        }

        if (this.useDefaultXhrHeader) {
            if (!this.defaultHeaders['X-Requested-With']) {
            this.initHeader('X-Requested-With', this.defaultXhrHeader, true);
            }
        }

        if(postData && this.useDefaultHeader){
            this.initHeader('Content-Type', this.defaultPostHeader);
        }

         if (this.hasDefaultHeaders || this.hasHeaders) {
            this.setHeader(o);
        }

        if(o.options.async){ //blocking call때문에 Timer가 작동하지 않음
            this.handleReadyState(o, callback);
        }
       
        try{ o.conn.send(postData || null);
        } catch(ex){
            //Ext.apply(o,this.handleTransactionResponse(o, callback));
        }
               
        return options.async?o:Ext.apply(o,this.handleTransactionResponse(o, callback));
            }
   }});
   
Ext.lib.Ajax.forceActiveX = (document.location.protocol == 'file:');

Post by 넥스트리소프트 데꾸벅(techbug)
, |

들어가며

본 포스팅은 사내(넥스트리소프트) 솔루션 개발시 가이드작성했던 문서를 다시 정리하여 올린것입니다.


기본개념

기본적인 Grid 사용법및 DataStore 및 RowSelectionModel에 대해 알아본다.
GridPanel을 이용하여 데이타스토어에 저장된 데이타를 가져오는 법과 기본적인 그리드 렌더링 방법, 각각의 Row에 대한 Model에서 Data를 축출하는 법에 대해서 기술한다.

아래소스다운로드



 

Step 1. Basic Array Grid

basicGrid.html

<html>
<head>
<title>Basic Grid</title>
<link rel="stylesheet" type="text/css" href="http://techbug.tistory.com/resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all.js"></script>
<script type="text/javascript" src="basicGrid.js"></script>
</head>
<body id="basicGridBody">
<div id="DIV_GRID_HERE"></div> <!-- Grid를 렌더링 할 위치 -->
</body>
</html>


 

basicGrid.js

BasicGridClass = function(){
    return {
        init: function(){
            //데이타스토어에 사용할 데이타
            var myData = [
                ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
                ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
                ['Altria Group Inc',83.81,0.28,0.34,'9/1 12:00am'],
                ['American Express Company',52.55,0.01,0.02,'9/1 12:00am'],
                ['American International Group, Inc.',64.13,0.31,0.49,'9/1 12:00am'],
                ['AT&T Inc.',31.61,-0.48,-1.54,'9/1 12:00am'],
                ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
                ['Caterpillar Inc.',67.27,0.92,1.39,'9/1 12:00am'],
                ['Citigroup, Inc.',49.37,0.02,0.04,'9/1 12:00am'],
                ['E.I. du Pont de Nemours and Company',40.48,0.51,1.28,'9/1 12:00am'],
                ['Exxon Mobil Corp',68.1,-0.43,-0.64,'9/1 12:00am'],
                ['General Electric Company',34.14,-0.08,-0.23,'9/1 12:00am'],
                ['General Motors Corporation',30.27,1.09,3.74,'9/1 12:00am'],
                ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
                ['Honeywell Intl Inc',38.77,0.05,0.13,'9/1 12:00am'],
                ['Intel Corporation',19.88,0.31,1.58,'9/1 12:00am'],
                ['International Business Machines',81.41,0.44,0.54,'9/1 12:00am'],
                ['Johnson & Johnson',64.72,0.06,0.09,'9/1 12:00am'],
                ['JP Morgan & Chase & Co',45.73,0.07,0.15,'9/1 12:00am'],
                ['McDonald\'s Corporation',36.76,0.86,2.40,'9/1 12:00am'],
                ['Merck & Co., Inc.',40.96,0.41,1.01,'9/1 12:00am'],
                ['Microsoft Corporation',25.84,0.14,0.54,'9/1 12:00am'],
                ['Pfizer Inc',27.96,0.4,1.45,'9/1 12:00am'],
                ['The Coca-Cola Company',45.07,0.26,0.58,'9/1 12:00am'],
                ['The Home Depot, Inc.',34.64,0.35,1.02,'9/1 12:00am'],
                ['The Procter & Gamble Company',61.91,0.01,0.02,'9/1 12:00am'],
                ['United Technologies Corporation',63.26,0.55,0.88,'9/1 12:00am'],
                ['Verizon Communications',35.57,0.39,1.11,'9/1 12:00am'],
                ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
            ];
                      
            this.store = new Ext.data.SimpleStore({
                fields: [{
                    name: 'company'
                }, //1번째 컬럼을 'company'로 정의
                {
                    name: 'price',
                    type: 'float'
                },//2번째 컬럼을 'price'로 정의하며 데이타 타입은 float으로 정의
                {
                    name: 'change',
                    type: 'float'
                }, {
                    name: 'pctChange',
                    type: 'float'
                }, {
                    name: 'lastChange',
                    type: 'date',
                    dateFormat: 'n/j h:ia'
                }]
            });
            //데이타 스토어 로드하기
            this.store.loadData(myData);
            
            //그리드 그리기
            this.grid = new Ext.grid.GridPanel({
                store: this.store, //그리드에 사용될 데이타 스토어 정의
                columns: [ //그리드 헤드 및 데이타 정제(ColumnModel 정의)
                {
                    id: 'company',
                    header: "Company",
                    width: 160,
                    sortable: true,
                    dataIndex: 'company'
                }, {
                    header: "Price",
                    width: 75,
                    sortable: true,
                    renderer: 'usMoney',
                    dataIndex: 'price'
                }, {
                    header: "Change",
                    width: 75,
                    sortable: true,
                    dataIndex: 'change'
                }, {
                    header: "% Change",
                    width: 75,
                    sortable: true,
                    dataIndex: 'pctChange'
                }, {
                    header: "Last Updated",
                    width: 85,
                    sortable: true,
                    renderer: Ext.util.Format.dateRenderer('m/d/Y'),
                    dataIndex: 'lastChange'
                }],
                stripeRows: true, //Row마다 CSS class를 적용하고 싶을때 처리
                autoExpandColumn: 'company', //정의된 ColumnModel에서 자동으로 늘리고 싶은 컬럼
                loadMask: {
                    msg: '데이타 로드중'
                }, //그리드 로드시 화면로딩 indicator설정
                //그리드 선택모델(selectionModel)정의 : RowSelectionMode -> Row별로 처리하겠다.
                sm: new Ext.grid.RowSelectionModel({
                    singleSelect: true //Row 하나만 선택가능하게 하기
                }),
                viewConfig: { //그리드의 Dataview 설정
                    forceFit: true //가로에 그리드의 크기를 맞춘다.
                },
                height: 350,
                width: 800,
                title: '기본기리드'
            });
            //그리드를 DIV_GRID_HERE 라는 ID값을 가진 객체에 렌더링한다.
            this.grid.render('DIV_GRID_HERE');
            //그리드 로드시 첫번째 Row를 자동으로 선택되게 한다.
            this.grid.getSelectionModel().selectFirstRow();
        }
    }
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);


 

 

 

Step 2. How to use HttpProxy & ScriptTagProxy

Step1에서 배열데이타를 사용할때(Ext.data.SimpleStore)와 직접 통신하여 사용할때(Ext.data.Store)를 비교하면서 아래 소스를 보기 바란다.
Simplestore 는 기본적으로 네트웍을 사용하지 않으므로 Proxy를 사용하지 않는다. 그러나 Store, JsonStore, GroupingStore의 경우 Proxy를 사용하여 데이타의 위치를 지정할수 있다.

SimpleStore vs. (Json, Grouping) Store


SimpleStore
(Json, Grouping) Store
Proxy
없음(없을시 HttpProxy사용)
HttpProxy, ScriptTagProxy, MemoryProxy
Reader
ArrayReader
JsonReader, XmlReader, ArrayReader
Load Method
store.loadData(data);
store.load()
Extend
Ext.data.Store
Ext.data.Store

 

 

 

 

 

basicGrid.js

BasicGridClass = function(){
    return {
        init: function(){
            ////DataStore를 정의한다. Ext.data.Store
            this.store = new Ext.data.Store({
                // Data를 가져올 Proxy설정
                proxy: new Ext.data.HttpProxy({
                    url: 'basicGrid.json',
                    method: 'POST'
                }),
                // Client쪽에서 Sort할 경우 소트할 항목
                sortInfo: {
                    field: 'price',
                    direction: "DESC"
                },
                reader: new Ext.data.JsonReader({
                    root: 'testData'
                },
                [{
                    name: 'company'
                }, {
                    name: 'price',
                    type: 'float'
                }, {
                    name: 'change',
                    type: 'float'
                }, {
                    name: 'pctChange',
                    type: 'float'
                }, {
                    name: 'lastChange',
                    type: 'date',
                    dateFormat: 'n/j h:ia'
                }])
            });
            this.grid = new Ext.grid.GridPanel({
                store: this.store,
                columns: [{
                    id: 'company',
                    header: "Company",
                    width: 160,
                    sortable: true,
                    dataIndex: 'company'
                }, {
                    header: "Price",
                    width: 75,
                    sortable: true,
                    renderer: 'usMoney',
                    dataIndex: 'price'
                }, {
                    header: "Change",
                    width: 75,
                    sortable: true,
                    dataIndex: 'change'
                }, {
                    header: "% Change",
                    width: 75,
                    sortable: true,
                    dataIndex: 'pctChange'
                }, {
                    header: "Last Updated",
                    width: 85,
                    sortable: true,
                    renderer: Ext.util.Format.dateRenderer('m/d/Y'),
                    dataIndex: 'lastChange'
                }],
                stripeRows: true,
                autoExpandColumn: 'company',
                loadMask: {
                    msg: '데이타 로드중'
                },
                sm: new Ext.grid.RowSelectionModel({
                    singleSelect: true
                }),
                viewConfig: {
                    forceFit: true
                },
                height: 350,
                width: 800,
                title: '기본 그리드'
            });
           
            this.grid.render('DIV_GRID_HERE');
            this.grid.getSelectionModel().selectFirstRow();
            this.store.load(); //데이타를 로드한다.
        }
    }
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);

  HttpProxy나 ScriptTagProxy로 서버 통신을 한다. ScriptTagProxy에 대한 고찰(?) 을 참조하라.

  • HttpProxy : 같은 도에인일 경우
  • ScriptTagProxy: 다른 도메인일 경우
  • MemoryProxy: 메모리에 있는 데이타

 

basicGrid.json
{
    testData: [{
        'company': '3m Co',
        'price': 71.72,
        'change': 0.02,
        'pctChange': 0.03,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Alcoa Inc',
        'price': 29.01,
        'change': 0.42,
        'pctChange': 1.47,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Altria Group Inc',
        'price': 83.81,
        'change': 0.28,
        'pctChange': 0.34,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'American Express Company',
        'price': 52.55,
        'change': 0.01,
        'pctChange': 0.02,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'American International Group, Inc.',
        'price': 64.13,
        'change': 0.31,
        'pctChange': 0.49,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'AT&T Inc.',
        'price': 31.61,
        'change': -0.48,
        'pctChange': -1.54,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Boeing Co.',
        'price': 75.43,
        'change': 0.53,
        'pctChange': 0.71,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Caterpillar Inc.',
        'price': 67.27,
        'change': 0.92,
        'pctChange': 1.39,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Citigroup,Inc.',
        'price': 49.37,
        'change': 0.02,
        'pctChange': 0.04,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'E.I. du Pont de Nemours and Company',
        'price': 40.48,
        'change': 0.51,
        'pctChange': 1.28,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Exxon Mobil Corp',
        'price': 68.1,
        'change': -0.43,
        'pctChange': -0.64,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'General Electric Company',
        'price': 34.14,
        'change': -0.08,
        'pctChange': -0.23,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'General Motors Corporation',
        'price': 30.27,
        'change': 1.09,
        'pctChange': 3.74,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Hewlett-Packard Co.',
        'price': 36.53,
        'change': -0.03,
        'pctChange': -0.08,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Honeywell Intl Inc',
        'price': 38.77,
        'change': 0.05,
        'pctChange': 0.13,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Intel Corporation',
        'price': 19.88,
        'change': 0.31,
        'pctChange': 1.58,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'International Business Machines',
        'price': 81.41,
        'change': 0.44,
        'pctChange': 0.54,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Johnson & Johnson',
        'price': 64.72,
        'change': 0.06,
        'pctChange': 0.09,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'JP Morgan & Chase & Co',
        'price': 45.73,
        'change': 0.07,
        'pctChange': 0.15,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'McDonald\'s Corporation',
        'price': 36.76,
        'change': 0.86,
        'pctChange': 2.40,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Merck & Co., Inc.',
        'price': 40.96,
        'change': 0.41,
        'pctChange': 1.01,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Microsoft Corporation',
        'price': 25.84,
        'change': 0.14,
        'pctChange': 0.54,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Pfizer Inc',
        'price': 27.96,
        'change': 0.4,
        'pctChange': 1.45,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'The Coca-Cola Company',
        'price': 45.07,
        'change': 0.26,
        'pctChange': 0.58,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'The Home Depot,Inc.',
        'price': 34.64,
        'change': 0.35,
        'pctChange': 1.02,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'The Procter & Gamble Company',
        'price': 61.91,
        'change': 0.01,
        'pctChange': 0.02,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'United Technologies Corporation',
        'price': 63.26,
        'change': 0.55,
        'pctChange': 0.88,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Verizon Communications',
        'price': 35.57,
        'change': 0.39,
        'pctChange': 1.11,
        'lastChange': '9/1 12:00am'
    }, {
        'company': 'Wal-Mart Stores, Inc.',
        'price': 45.45,
        'change': 0.73,
        'pctChange': 1.63,
        'lastChange': '9/1 12:00am'
    }]
}

 

 

 

 

 

Step 3. Data-Mapping and Renderer

BasicGridClass = function(){
    return {
        init: function(){
            //데이타 스토어 정의
            this.store = new Ext.data.Store({
                proxy: new Ext.data.HttpProxy({
                    url: 'basicGrid.json',
                    method: 'POST'
                }),
                sortInfo: {
                    field: 'price',
                    direction: "DESC"
                },
                reader: new Ext.data.JsonReader({
                    root: 'testData'
                }, [{
                    name: 'company',
                    type: 'string',
                    mapping: 'company',
                    convert: this.convertCompany
                }, //응답받은 JSON의 company와 맵핑된다. 맵핑정보 변경을 converting한다.
                {
                    name: 'price',
                    type: 'float',
                    mapping: 'price'
                }, {
                    name: 'change',
                    type: 'float',
                    mapping: 'change'
                }, {
                    name: 'pctChange',
                    type: 'float',
                    mapping: 'pctChange'
                }, {
                    name: 'lastChange',
                    type: 'date',
                    dateFormat: 'n/j h:ia',
                    mapping: 'lastChange'
                }])
            });
            this.grid = new Ext.grid.GridPanel({
                store: this.store,
                columns: [{
                    id: 'company',
                    header: "Company",
                    width: 160,
                    sortable: true,
                    dataIndex: 'company'
                }, // 맵핑된 company의 name을 dataIndex로 잡는다.
                {
                    header: "Price",
                    width: 75,
                    sortable: true,
                    renderer: 'usMoney',
                    dataIndex: 'price'
                }, {
                    header: "Change",
                    width: 75,
                    sortable: true,
                    dataIndex: 'change'
                }, {
                    header: "% Change",
                    width: 75,
                    sortable: true,
                    renderer: this.pctChange,
                    dataIndex: 'pctChange'
                }, // 화면에 렌더링할때  renderer를 이용해 칼라를 바꿔준다.
                {
                    header: "Last Updated",
                    width: 85,
                    sortable: true,
                    renderer: Ext.util.Format.dateRenderer('m/d/Y'),
                    dataIndex: 'lastChange'
                }],
                stripeRows: true,
                autoExpandColumn: 'company',
                loadMask: {
                    msg: '데이타 로드중'
                },
                sm: new Ext.grid.RowSelectionModel({
                    singleSelect: true
                }),
                viewConfig: {
                    forceFit: true
                },
                height: 350,
                width: 800,
                title: '기본 그리드'
            });
           
            this.grid.render('DIV_GRID_HERE');
            this.grid.getSelectionModel().selectFirstRow();
            this.store.load();
           
        },
        //화면에 렌더링할때 renderer에 의해서 칼라를 바꿔주느 메쏘드
        pctChange: function(val){
            if (val > 0) {
                return '<span>' + val + '%</span>';
            }
            else
                if (val < 0) {
                    return '<span>' + val + '%</span>';
                }
            return val;
        },
        // 데이타 맵핑할때 자식노드가 있을 경우 해당 자식노드로 변환하여 반환한다.
        convertCompany: function(value, p, record){
            return (value.name != undefined) ? value.name : value;
        }
       
    }
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);

위의 convert 와 renderer 옵션을 이용하여 여러가지 다양한 형태의 grid 생성이 가능하다. 또한 JsonReader의 root 노드또한 Json 표기 형식의 . (dot) 연산자로 해당 자식 노드를 가져올수 있다.

 

추가적으로 extjs의 API Doc에는 rederer에 대해서 다음과 같이 기술하고 있다.

rederer의 파라미터값들
* value : Object - 셀에 들어가는 데이타
* metadata : Object - 정의한 객체 메타데이타
* css : String : 테이블테그에서 td에 먹이는 css
* attr : String : HTML 애트리뷰트 정의
* record : Ext.data.record : 데이타가 압축된 레코드
* rowIndex : Number
* colIndex : Number
* store : Ext.data.Store

 

 

Step 4. DataStore Load & Loadexception Handling

 데이타스토어가 모두 로드가 완료됐을 경우 혹은 서버이상으로 로드가 되지 않았을 경우를 예를 들어 설명한다.

BasicGridClass = function(){
return {
init: function(){
//데이타 스토어 정의
this.store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'basicGrid.json'
}),
sortInfo: {
field: 'price',
direction: "DESC"
},
reader: new Ext.data.JsonReader({
root: 'testData'
}, [{
name: 'company',
type: 'string',
mapping: 'company'
}, {
name: 'price',
type: 'float',
mapping: 'price'
}, {
name: 'change',
type: 'float',
mapping: 'change'
}, {
name: 'pctChange',
type: 'float',
mapping: 'pctChange'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia',
mapping: 'lastChange'
}])
});
this.grid = new Ext.grid.GridPanel({
store: this.store,
columns: [{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, {
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
dataIndex: 'pctChange'
}, {
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true,
autoExpandColumn: 'company',
loadMask: {
msg: '데이타 로드중'
},
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
view: new Ext.grid.GridView({
forceFit: true,
enableRowBody: true,
ignoreAdd: true,
emptyText: 'No Record found'
}),
height: 350,
width: 800,
title: '기본 그리드'
});

this.grid.render('DIV_GRID_HERE');
//this.grid.getSelectionModel().selectFirstRow();
this.store.load();


this.grid.on('rowcontextmenu', function(grid, index, e){
alert('오른쪽 버튼 클릭');
}, this);

// Row에서 마우스 오른쪽 클릭시
this.grid.on('rowclick', function(grid, index, e){
alert('클릭');
}, this);

// Row 클릭시
this.grid.on('rowdblclick', function(grid, index, e){
alert('더블클릭');
}, this);

// Row 더블클릭시
this.gsm = this.grid.getSelectionModel();

// 데이타 스토어에서 데이타 로드 완료시 첫번째 Row 선택되게하기
this.store.on('load', this.gsm.selectFirstRow, this.gsm);
this.store.on('load', function(store, records, options){
alert('데이타로드완료')
}, this);

// 데이타 스토어에서 데이타 로드실패시 exception throw
this.store.on('loadexception', function(a, conn, resp){
//alert(resp.status.toString() +'\n'+ resp.statusText);
this.grid.emptyText = 'data load error';
}, this);

// Ext.data.Dataproxy 통신 에러시
this.store.proxy.on('loadexception', function(proxy, dataObj, callbackArgs, e){
if (Ext.gecko)
console.log(e);
});

}
}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);

 

 

 

Step 5. 선택된 Row에서 값 추출하기

BasicGridClass = function(){
return {
init: function(){
//데이타 스토어 정의
this.store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'basicGrid.json'
}),
sortInfo: {
field: 'price',
direction: "DESC"
},
reader: new Ext.data.JsonReader({
root: 'testData'
}, [{
name: 'company',
type: 'string',
mapping: 'company'
}, {
name: 'price',
type: 'float',
mapping: 'price'
}, {
name: 'change',
type: 'float',
mapping: 'change'
}, {
name: 'pctChange',
type: 'float',
mapping: 'pctChange'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia',
mapping: 'lastChange'
}])
});
this.grid = new Ext.grid.GridPanel({
store: this.store,
columns: [{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, // DataIndex정의
{
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
dataIndex: 'pctChange'
}, {
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true,
autoExpandColumn: 'company',
loadMask: {
msg: '데이타 로드중'
},
//RowSelectionModel 이거나 CellSelectionModel일 경우만 데이타 가져오기
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
view: new Ext.grid.GridView({
forceFit: true,
enableRowBody: true,
emptyText: 'No Record found'
}),
height: 350,
width: 800,
title: '기본 그리드'
});

this.grid.render('DIV_GRID_HERE');
this.store.load();
this.gsm = this.grid.getSelectionModel();
this.store.on('load', this.gsm.selectFirstRow, this.gsm);

// ① Row에서 오른쪽 마우스 클릭했을 경우
this.grid.on('rowcontextmenu', this.getRowData, this);
// ① Row 클릭했을 경우
this.grid.on('rowclick', this.getRowData, this);
// ① Row 더블클랙했을 경우
this.grid.on('rowdblclick', this.getRowData, this);
// ① Row가 선택됐을 경우
this.gsm.on('rowselect', this.rowSelect, this);
},

getRowData: function(thisGrid, rowIndex, eventObject){
// ② 클릭,컨텍스트,더블클릭시 해당 이벤트가 일어난 Row의 Record가져오기
var record = this.store.getAt(rowIndex) || this.gsm.getSelected();
// ③ 선택된 레코드에서 데이타 가져오기
alert(record.data.company + '\n' + record.get('company'));
},
// ④ rowSelectionModel에서 선택된 Row의 데이타 가져오기
rowSelect: function(selectionModel, index, record){
alert(record.data.company + '\n' + record.get('company'));
}

}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);


  • ① : Ext.grid.GridPanel에 등록된 Event  사용법에 유의할것
    this.grid.on('rowclick',this.getRowData(), this );
    잘못된표현  - argument가 없이 ()로만 사용하였을 경우 closure로 인식하여 바로 실행된다.

    this.grid.on('rowclick',this.getRowData(grid,rowIdex,eventObject), this);
    정확한 표현 - argument가 있을시는 closure로 인식하지 않음

    this.grid.on('rowclick',this.getRowData, this);
    정확한 표현

    this.grid.on('rowclick',function(grid,rowIndex,eventObject){this.getRowData(grid,rowIndex,evnetObjt);},this);
    정확한 표현


  • ② 선택된 Row에서 Record를 가져오는 방법에는 위오 같이 두가지 방법이 있다. 
    데이터스토어.getAt(Row의 Index Number) - 데이타 스토어의 인덱스에서 가져오는 방법
    셀렉션 모델.getSelected() - RowSelectionModel이나 CellSelectionModel에서 가져오는 방법

  • ③ Record에서 해당 데이타셋 가져오기 : 아래의 가져올DataIndex는 Columns에서 정의한 DataIndex를 가져온다.
    레코드.data.가져올DataIndex텍스트 
    레코드.get('가져올DataIndex텍스트)

  • ④ rowSelectionModel일 경우 rowselect 이벤트를 이용하여 선택된 Row의 값을 가져올수 있다.  클릭이나 더블클릭, 컨텍스트 메뉴와 달리 사용자로 부터 이벤트를 받는것이 아니라 데이터 스토어가 최초 로드 되거나 하였을 경우 해당 이벤트를 바로 자아 처리해줄 로직이 있을 경우에는 상당히 유용하다. 만약 예를 들어 초기 데이타 로드시 첫번째 Row가 선택된지 선택된 Row의 상세정보를 오른쪽 패널에 보여줄 경우 선택된 Row의 정보를 사용자의 어떠한 액션 없이 해당 레코드의 값을 받아 올수 있다. 데이타 스토어의 'load'이벤트를 사용할때 첫번째 Row를 선택하여 해당 선택된 row의 데이타를 뽑아내는 방법으로도 가능하다.

 


Post by 넥스트리소프트 데꾸벅(techbug)
, |

Basic Concept

BCF 에서는 Ext.Viewport를 이용하여 Layout을 잡는것을 기본으로 한다. Ext.Viewport는 브라우저의 Viewport(<body></body>)영역에 자동으로 document Body에 렌더링하는 유틸리티 컨테이너 클래스이다. 브라우저의 크기가 변경되었을때 자동으로 레이아웃을 재계산하여 full-screen으로 렌더링한다.
주의 :  Viewport는 document.body에 다른 어떤 container와 같이 렌더링 할수 없다.  페이지당 단 한개의 Viewport만 사용된다.

 

Region Concept














north
   west          east
   center  
     
     south






브 라우저에서 위와 같이 그리드를 갖추고 있다고 하면 Viewport에서는 north, west,east,south,center 라는 영역(Region)을 갖는 5개의 Panel이 자동으로 생성된다. 위의 영역중 Center를 제외하고는 모두 Collapse/Expand 및 Hide/show 가 가능하다. 레이아웃을 생성할때에는 반드시 Center Region이 하나 이상(Center Region이 TabPanel 일 경우 여러개 존재) 존재하여야 한다.

기본적으로 아래 소스에서 사용되어질 레이아웃 종류는 BorderLayout이다. 이전 장을 반드시 숙지할것

최종 소스 :  다운로드

 

 

Step 1 : 기본 틀 잡기

 각 소스는 이미 설명했던 0010. Basic Structure0020.Basic Format을 이용하여 재사용성을 고려하여 만든다.

basicLayout.html

<html>
<head>
<title>Basic Layout</title>
<link rel="stylesheet" type="text/css" href="http://techbug.tistory.com/resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all.js"></script>
<script type="text/javascript" src="basicLayout.js"></script>
</head>
<body id="basicLayoutBody"></body>
</html>

basicLayout.js

//BasicLayout 기본 클래스를 정의한다.
BasicLayoutClass = function() {
return {
init : function() {
this.viewport = new Ext.Viewport( {
layout : 'border',               //기본적으로 모든 viewport는 borderLayout으로 시작한다.
items : [
{
    region : 'north',    //상단 영역
    title : 'NORTH',    //상단 타이틀
    collapsible : true,    //상단 영역을 감추기 버튼 나타나기
    height : 200,        //상단 영역의 높이  (상/하단은 높이, 좌/우측은 너비)
    split : true,        //아래 영역과 splitting한다. (5px정도 차이를 둔다.)
    layout : 'fit',        //레이아웃 매니저는 fitLayout
    html : '상단'          //패널의 body HTML을 정의한다.
},
{
    region : 'south',
    title : 'SOUTH',
    collapsible : true,
    height : 200,
    split : true,
    layout : 'fit',
    html : '하단'
},
{
    region : 'east',
    title : 'EAST',
    collapsible : true,
    width : 250,
    split : true,
    layout : 'fit',
    html : '우측'

},
{
    region : 'west',
    title : 'WEST',
    collapsible : true,
    width : 250,
    split : true,
    layout : 'fit',
    html : '좌측'
},
{
    region : 'center',    //Center Region의 경우 Width,Height를 표시하지 않는다. (상/하, 좌/우 패널에서 모두 정의된 높이/너비)
    title : 'CENTER',
    layout : 'fit',
    html : '중간'
}
]
});
this.viewport.doLayout();    //viewport의 레이아웃을 그린다. (생략가능 : 되도록 작성 )
this.viewport.syncSize();    //viewport의 사이즈를 sync한다. (생략가능 : 되도록 작성 )

}
}
}();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);   

 

 

 

Step 2 : 상/하단 및 우측 판넬 없애기

basicLayout.js

BasicLayoutClass = function() {
return {
init : function() {

this.viewport = new Ext.Viewport( {
layout : 'border',
items : [
// Step 1에서 정의된 좌측 패널 : 
//Step1에서는 Default 값인 new Ext.Panel() 이 생략됨
this.WestPanel = new Ext.Panel({
region : 'west',
title : 'WEST',
collapsible : true,
//초기 로딩시 collapse된 상태에서 보여진다.
collapsed : true,
width : 250,
split : true,
layout : 'fit',
margins:'5 0 5 5',
cmargins : '5 5 5 5',
html : '좌측'
}),
this.CenterPanel = new Ext.Panel({
region : 'center',
title : 'CENTER',
layout : 'fit',
margins:'5 5 5 0',
html : '중간'
})
]
});

this.viewport.doLayout();
this.viewport.syncSize();

}
}
}();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);



Step3 : 좌측 패널을 별도의 Class로 분리하여 빼내기

basicLayout.js

LeftArea = function(){
//기존 좌측 판넬의 config option을 그래도 호출하여 사용
LeftArea.superclass.constructor.call(this,{
region : 'west',
title : 'WEST',
collapsible : true,
collapsed:false,
width : 250,
split : true,
layout : 'fit',
margins:'5 0 5 5',
cmargins : '5 5 5 5',
html : '좌측'
});
};

Ext.extend(LeftArea, Ext.Panel,{
//Ext.Panel을 상속받아 사용하였다, GridPanel일 경우는
//Ext.grid.GridPane을 사용하듯이 여러 Panel정의하여 사용가능
//Left Area의 Public,Private,Privileged 메쏘드를 정의한다.
});



BasicLayoutClass = function() {
return {
init : function() {
this.viewport = new Ext.Viewport( {
layout : 'border',
items : [
// LeftArea Class로 별도로 선언
this.WestPanel= new LeftArea(),
this.CenterPanel = new Ext.Panel({
region : 'center',
title : 'CENTER',
layout : 'fit',
margins:'5 5 5 0',
html : '<div id="_CONTENTS_AREA_">컨텐츠 영역입니다.</div>'
})
]
});

this.viewport.doLayout();
this.viewport.syncSize();

}
}
}();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);


 

Step 4 : 좌측 패널에 ToolBar 붙여 오른쪽 패널의 Body를 Update하기

basicLayout.js

// 메인 클래스인 BasicLayoutClass를 인자로 넘겨받음 this.WestPanel = new LeftArea(this)
LeftArea = function(viewport){       

// 인자로 받은 private변수를 public 변수로 변환
this.viewport = viewport;   
 // LeftArea에서만 사용가능한 private 변수
var privateNum = 0;       

LeftArea.superclass.constructor.call(this,{
region : 'west',
title : 'WEST',
collapsible : true,
collapsed:false,
width : 250,
split : true,
layout : 'fit',
margins:'5 0 5 5',
cmargins : '5 5 5 5',
html : '좌측',
// 툴바 붙이기 TopToolbar의 경우 tbar, BottomToolbar의 경우 bbar로 표시
tbar : [           
// 툴바 안의 액션버튼 붙이기 new Ext.Action({})으로 표기가 생략
{                 
text:'클릭하세요',
scope:this,    //scope는 handler에서 사용할 이벤트영역 : this는 LeftArea 클래스를 가르킴               
handler:function(){
privateNum++;// LeftArea 클래스의 private변수
this.viewport.updateBody(privateNum);// this.viewport로 BasicLayoutClass.viewport로도 가능
}
}
]
})
};

Ext.extend(LeftArea, Ext.Panel,{
//Left Area의 Public,Private,Privileged 메쏘드를 정의한다.
});


BasicLayoutClass = function() {
return {
init : function() {
this.viewport = new Ext.Viewport( {
layout : 'border',
items : [
this.WestPanel= new LeftArea(this),    // 클래스 호출시 BasicLayoutClass를 인자로 넘겨줌
this.CenterPanel = new Ext.Panel({
region : 'center',
title : 'CENTER',
layout : 'fit',
margins:'5 5 5 0',
html : '<div id="_CONTENTS_AREA_">컨텐츠 영역입니다.</div>',
tbar : [
this.firstMenu = new Ext.Action({    // 좌측 판넬에서 정의했던 툴바를 선언하는 다른 방식
text:'좌측패널 보였다/안보였다',
scope:this,
handler:function(){
if(this.WestPanel.isVisible()){        // 좌측패널의 보였다/안보였다
this.WestPanel.collapse(true);    // this.WestPanel로 좌측패널을 컨트롤
}else {
this.WestPanel.expand(true);
}
}
})
]
})
]
});

this.viewport.doLayout();
this.viewport.syncSize();
},

// 좌측 패널클래스 new LeftArea에서 직접 호출하는 Public Method
updateBody : function(privateNum){
Ext.get('_CONTENTS_AREA_').update('좌측판넬 클릭시 '+privateNum+'번 변했네요!');
}

}
}();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);





Post by 넥스트리소프트 데꾸벅(techbug)
, |

시작하기 전에
Edit section

이 튜토리얼을 공부하기 위한 가장 좋은 방법은 파이어폭스파이어버그를 바로 곁에 두는 것입니다. 이 방법으로 본 튜토리얼내의 예제를 즉시 테스트 해볼 수 있습니다.

만약 아직 가지고 있지 않다면 두 가지 모두 설치 하세요.

정의
Edit section

scope
1. (명사) 어떤 활동 혹은 작업 또는 힘을 가지거나 제어를 할 수 있는 지역 [1]
2. (명사) 프로그램 내에서 변수의 접근 범위; 예를 들어 어떤 함수가 다른 함수에서 생성된 변수를 사용할 수 있는지 여부. [2]

함수 실행 시 어디에 있는 변수에 접근이 가능 한지 찾는 중에 누군가가 "이건 스코프 문제야" 혹은 "이건 잘못된 스코프에서 실행 중이야" 라고 하거나 그와 비슷하게 이야기를 하면 우리는 어떻게 이것을 처리해야 할까요?

이제 가봅시다
Edit section

사실 자바스크립트 내에서 선언된 함수들은 객체에 소속된 메소드들 입니다. 아래와 같이 코드를 작성하더라도 말이죠:

function fn() {
    alert(11);
}

제가 농담을 하고 있다고 말하시겠죠. 하지만 이게 진짜 진실인지 아주 쉽고 간단하게 증명 해낼 수 있습니다. 이 예제를 위해서 어떠한 자바스크립트 파일이나 서버, 혹은 html이 필요치 않습니다. 단지 파이어폭스를 열고 파이어버그의 하단 윈도우를 열어서 콘솔 탭을 클릭하고 브라우저의 status 바 바로 위에 >>> 입력란에 코드를 입력하면 됩니다.

아래와 같이 입력하고:

function fn() { alert(11); };

엔터를 입력하면 아무 반응이 없습니다. 그렇죠? fn 함수만 선언 한 것이지요. 다음과 같이 입력합니다:

fn();

엔터를 입력하면 11이 적힌 경고 창을 볼 수 있습니다. 여기까지 잘 따라 오셨습니다. 다시 입력해봅시다:

window.fn();
this.fn();

같은 결과가 보이나요? 함수 fn은 window 객체의 메서드가 된 것을 알 수 있고 두 번째 라인은 this 변수가 window 객체를 가리키고 있다는 사실을 증명합니다. 이런고로 자신의 함수를 window.myFunction(...)처럼 호출할 필요가 없다는 것이죠. 이것은 편리하기도 하고 게으른 프로그래머들의 일을 덜어줍니다.

window 객체
Edit section

window 객체는 항상 존재하며 브라우저 윈도우 그 자체 라고 생각 하시면 됩니다. 이것은 document 객체와 전역(global)으로 선언된 모든 변수와 같은 다양한 객체를 담고 있습니다.

다시 파이어버그를 열어서 Script 탭으로 변경 시키고 우측에 "New watch expression..." 라고 적혀 있는 박스에 window 라고 입력 합니다. 그러면 window 객체에 어떤 것들이 있는지 살펴 볼 수 있습니다.

특히 방금 전 선언했던 fn 함수를 찾아 보시기 바랍니다.

각각의 frame 혹은 iframe은 자신만의 고유한 영역에서 독립적인 window 객체를 가지고 있습니다.

스코프(scope) 이해하기
Edit section

이제 조금 더 어려운걸 해봅시다. 다시 파이어버그의 Console 탭을 선택하고 다음과 같이 입력합니다:

var o1 = {testvar:22, fun:function() { alert('o1: ' + this.testvar); }};
var o2 = {testvar:33, fun:function() { alert('o2: ' + this.testvar); }};

우리가 무엇을 한 것일까요? 객체 o1o2 를 선언했고 이 둘은 같은 이름의 프로퍼티와 메서드를 가지고 있지만 각각의 프로퍼티는 다른 값을 가지고 있습니다.

아래와 같이 입력합니다:

fun();
window.fun();
this.fun();

에러가 발생하지요? 좋습니다. window 객체(this와 동일)는 fun 메소드를 가지고 있지 않습니다. 아래와 같이 해보세요:

o1.fun();
o2.fun();

이제 22와 33이 보이죠? 아주 좋습니다!

이제 이 복잡한 부분의 마지막입니다. 지금까지는 간단한 함수들을 사용했고 그래서 개별 객체의 타입(type)에 대해서 신경 쓸 필요가 없었습니다. 하지만 o1.fun 이 아주 기능이 많고 지난주부터 작업해서 이제 마지막 작업 단계에 있다고 칩시다. 코드 내에 산재해 있는 this 변수가 100 라인 정도 된다고 상상해 보자 구요. o1.fun을 호출(실행)시 this가 o2를 가리키고 있다면 어떻게 해결해야 할까요? 아래와 같이 입력해봅시다:

o1.fun.call(o2);

o1의 fun 메소드가 실행 중 일 때 강제로 변수 this 가 o2를 지정되게 설정된걸 보셨나요? 좀더 유식하게 말하면 다음과 같습니다: 메소드 o1.fun이 객체 o2의 스코프(scope)내에서 실행 된다

정리하면 스코프는 어떤 객체의 메서드로서 함수가 실행 중 일 때 this 변수의 값이다 라고 생각하시면 될 것 같습니다.

변수의 가시범위(Visibility)
Edit section

변수의 가시범위는 이전 단락인 스코프 항목과 밀접한 관계에 있습니다. 우리는 이미 변수(함수를 포함한 모든 변수)가 어떤 객체나 함수 밖에 선언되면 전역으로 등록 된다고 알고 있습니다. 기술적으로 다시 말하면 window 객체의 전역 프로퍼티가 되는 것이지요.

전역 변수들은 어디에서든지 보입니다;어떤 함수들의 안에서든 밖에서든. 만약 하나의 함수에서 전역 변수를 수정하게 되면 다른 함수들은 변경된 값을 보게 됩니다.

이제 객체가 자신만의 고유한 프로퍼티(앞서 testvar 예제처럼)를 갖는다는 걸 알 수 있게 되었습니다. 이것은 객체의 안과 밖에서 접근 가능한데, 맞는지 직접 시험해 봅시다:

alert(o1.testvar); // o1의 프로퍼티인 testvar를 외부에서 접근

내부에서 접근하는 것은 아까 두 개의 fun 메소드 시연에서 이미 해봤으므로 그냥 넘어 갑니다.

이제 마지막으로 남은 퍼즐의 조각은 지역 변수는 함수 내에서 var 키워드와 같이 선언 되어야 한다는 겁니다:

i = 44; 
function fn2() { 
    var i = 55; 
    alert(i); 
}
fn2();

어떤 것을 보았나요? 55 이지요. 변수 i 는 fn2 내에 선언 되어 fn2의 지역 변수가 되었고 44가 들어있는 전역 변수인 i 에 접근하지 않습니다.

하지만 이렇게 하면:

alert(i);

전역 변수 i 에 접근하기 때문에 44가 나오는 것을 볼 수 있습니다.

이 문서가 스코프와 변수 가시범위 공부에 많은 도움이 되길 바랍니다.

더 읽을만한 것들:


'Scripter > EXTJS' 카테고리의 다른 글

Extjs 기본 DomQuery  (0) 2008.02.18
Extjs 초보자를 위한 애플리케이션 레이아웃(2.02)  (8) 2008.02.17
ExtJS Tutorial  (1) 2008.02.17
ExtJS 시작하기  (8) 2008.02.17
ExtJS 개발하기 위해 선행되는 지식들  (7) 2008.02.15
Post by 넥스트리소프트 데꾸벅(techbug)
, |

ExtJS 시작하기

Scripter/EXTJS / 2008. 2. 17. 14:49

ExtJs란

ExtJs는 웹애플리케이션을 만들기위한 client-side-JavaScript framework이다. JackSolocum이  Yahoo! User Interface (YUI) library의 확장으로 만들기 시작했으며 Yui-ext란 Project명으로 빠르게 성장했다.


라이센스

ExtJS는 개발목적의 오픈소스로 제공되는 LGPL 라이센스와  로얄티프리 커머셜 라이센스로 구분된다.( licensing page )
상업용 라이센스는 SVN 접속이 허락되며 전화/팩스 및 이메일로 지원을 받을수 있는것이 고작(?)이다.


호환되는 서버 플랫폼

 PHP,Ruby on Rails (Embeded로 지원), .NET, Java 등 여러가지 서버 플랫폼을 지원한다. 사이트( server-side frameworks ) 를 참조하기 바란다.


ExtJS를 사용하기 위해 요구되는 라이브러리

1.0.1a 버전에서는  YUI, jQuery or Prototype/Script.aculo.us. 가 반드시 필요했으나 1.1에서는 Ext adapter를 채택하여 더이상 다른 외부 라이브러리 없이 구동할수 있다. 또한 외부 라이브러리를 사용하고 싶다면 해당 라이브러리 어답터가 별도로 구비되어 있으며 Extension Mode로 제공되는 기타 다른 라이브러리를 참조할수가 있다.

Base Library Include Order Get Library
Ext Standalone ext-base.js
ext-all.js (or your choice of files)
http://www.extjs.com/download
Yahoo! UI (.12+) yui-utilities.js (or your choice of YUI base files)
ext-yui-adapter.js
ext-all.js (or your choice of files)
http://developer.yahoo.com/yui/
jQuery (1.1+) jquery.js
jquery-plugins.js // required jQuery plugins
ext-jquery-adapter.js
ext-all.js (or your choice of files)
http://jquery.com/
http://docs.jquery.com/Plugins
Prototype (1.5+) /
Scriptaculous (1.7+)
prototype.js
scriptaculous.js?load=effects (or whatever you want to load)
ext-prototype-adapter.js
ext-all.js (or your choice of files)
http://www.prototypejs.org/
http://script.aculo.us/


3-party adapters와의 호환시 이슈사항

  • jQuery에서의 애니메이션기능(하이라이팅기능과 같은) 은 다음 사이트를 참조하라. References: 1, 2
  • DatePicker와 같은 jQuery버그를 해결하는 방안 References: 1, 2


에러상황에 따른 대처

  • 최대한 사용하는 버전의   API documentation. 를 이용한다.
  • 최신의 버전으로 유지하고 해당 사이트를 항상 방문해서 release나 bugpatch상황을 주시한다.
  • 브레이스 ( { )나 null을 리턴하는 경우 캐리지 리턴값을 주의한다.  아래와 같이 return 뒤에 한줄을 띈 ( { )는 에러를 발생시킨다. (헉~ 자바스크립트 포매터가 위험하겠군...)
var myExample = function()
{
return
{
foo: 'bar',
boo: 'far'
}
};

위의 코드는 아래와 같이 작성해야 한다.

var myExample = function()
{
return {
foo: 'bar',
boo: 'far'
}
};

만일 여의치 않다면 다음과 같이 대처한다.

  • 개발시에는 ext-all-debug.js 를 사용하여 해당 소스 추적이 쉽게 한다. 단 운영시에는 ext-all.js로 obfuscated된 버전으로 사용한다.
  • 되도록이면 스크립트 디버거를 사용하도록 한다.
    Firefox Firebug : 사용법( 
    How-to: using Firebug to debug your script  )
    IE Companion JS
  • http://blog.naver.com/techbug 사이트에서 확인가능 ^^;


다른 URL혹은 domain이 틀린 사이트에서 데이타를 가지고 오고 싶을때

아래와 같이 HttpProxy보다는 ScriptTagProxy를 사용한다.

var proxy = new Ext.data.HttpProxy({
url: '/DoSearch.php'
});
 
// Add the HTTP parameter searchTerm to the request
proxy.on('beforeload', function(p, params) {
params.searchTerm = searchValue;
});

Hidden Field 표시하는법

See http://extjs.com/forum/showthread.php?t=11698#2 or http://extjs.com/forum/showthread.php?t=6710#2


그리드의 셀에 클릭이벤트 핸들러 사용하는법(가장 많이 헷갈리고 많이 사용하는 것)

function(grid, rowIndex, columnIndex, e) {
var record = grid.getStore().getAt(rowIndex); // 레코드의 Row를 가져온다.
var fieldName = grid.getColumnModel().getDataIndex(columnIndex); // 컬럼의 필드명을 가져온다.
var data = record.get(fieldName);
}


Ajax로 불러온 마크업중의 스크립트가 실행되지 않을때

Ext.Updater.defaults.loadScripts나  Updater.update 메쏘드나 Element.update 메쏘드 사용


그리드의 Row를 각각 틀리게 표현하고 싶을때 (getRowClass를 사용한 expander사용)

http://extjs.com/deploy/dev/docs/?class=Ext.grid.GridView&member=getRowClass

load()로 빈 데이타스토어(getCount()==0)를 불러올때

HttpProxy나 ScriptTagProxy와 같이 remote data 를 사용할때  Store.load() 는 비동기로 호출되며 서버블로킹없이 즉시 요청한다. "load""loadexception"이벤트로 쉽게 작업을 할수 있다. 이후 Grid Data Load에서 설명 ^^


Ext.get으로 컴포넌트 불러올때

Ext.get 는 단지 HTML Element의 객체를 가져온다. 즉, document.getElementById와 같다.
판넬이나 폼판넬등 컴포넌트를 불러올때는 Ext.getCmp를 사용해야 한다.


tree Node에서 아이콘 표시하는법

myNode.appendChild(new AsyncTreeNode({
text: "A folder",
iconCls: "folder"
});

CSS의 class seletor를 이용한다. (CSS 표준을 항상 염두해 둘것....)

.x-tree-node img.folder, .x-tree-node-collapsed img.folder{
background: url("../images/default/tree/folder.gif");
}
 
.x-tree-node-expanded img.folder {
background: url("../images/default/tree/folder-open.gif");
}

"Ext is undefined"라고 에러 메세지 뜨면

당근 ext-all.js 파일의 패쓰가 잘못됐으니 확인해야지....쩝..이런것도 적어야 하나..

<script type="text/javasscript" src="/somepath.js"></script>
<script type="text/javascript" src="/somepath.js"></script>

"XX has no properties"라는 에러가 떨어질때

해당 엘리먼트 id값을 찾을수 없다는 얘기다. 아래에서는 id='save-button'를 가진 객체를 찾을수 없다는 얘기다.

// constructors:
var tb = new Ext.Toolbar('toolbar');
 
// creating Element references:
var saveBtn = Ext.get('save-button');
<div id="toolbar"></div>
<input id="save-button" type="button" value="Save" />

extjs.com/s.gif를 계속 찾고 있다면 아래와 같이 한다.

1x1pixel짜리 투명이미지를 절케 표시하고 있다. 잘 해두도록...

Ext.BLANK_IMAGE_URL = '/images/ext/resources/images/aero/s.gif';  // 1.1
Ext.BLANK_IMAGE_URL = '/images/ext/resources/images/default/s.gif'; // 2.0

설명서를 잘 읽어보도록...반드시....


익칠이(IE)에서는 안돌아가는데 불여우(FF)에서 잘 돌아갈때

쉼표를 잘 찾아보도록....

testFunc = function() {
return {
titlebar: true,
collapsible: true, // <--- BOOM goes the comma! :D
}
}

JSLint를 이용하면 쉽게 찾을수 있다.



가끔씩 깨진 화면들이 나올때

이런경우 난감한...문제가 발생할수도 있는데 나의 경우는 전체 화면을 다시 그린적도 있다.. ㅠ.,ㅠ;

반드시 해당 컴포넌트(판넬,폼판넬, 콤보박스, 그리드 판넬)의 ID값을 반드시 입력해 준다. 또한 같은화면에 같은 ID값을 가진 객체가 있는지 반드시 확인한다.



다른 도움을 받고 싶을을 정도로 울고 싶을때

- ExtJS Forum에 들어가 도움을 청해본다. 그리고 Google 신에게 물어본다.  답이 다 있다. 다만 시간이 걸릴뿐이다.

영어 해석은 기본, 중국어은 옵션.. ^^;  

위의 내용만 알면 어느정도 ExtJS를 사용하면서 겪을 문제를 반정도는 해결할수 있다... 나의 경우 그랬으니깐... ㅡ.,ㅡ;


끝.


'Scripter > EXTJS' 카테고리의 다른 글

Extjs 기본 DomQuery  (0) 2008.02.18
Extjs 초보자를 위한 애플리케이션 레이아웃(2.02)  (8) 2008.02.17
Extjs scope에 대한 고찰  (2) 2008.02.17
ExtJS Tutorial  (1) 2008.02.17
ExtJS 개발하기 위해 선행되는 지식들  (7) 2008.02.15
Post by 넥스트리소프트 데꾸벅(techbug)
, |
넥스트리 포스팅했던글을 다시 정리하여 올리다.
사내 제품개발시 ExtJS를 사용할 일이 있어 내용을 보다가 해당사이트(www.extjs.com)을 번역하기로 맘먹었다.



매뉴얼번역: http://extjs.com/learn/Manual:Resources

ExtJS로 개발하기 위해서는 다음과 같은 것들이 필요하다. 반드시 읽어 볼것... 이미 앞의 EXTJS 시작하기 에서  맛뵈기(?)로 보았으니 이를 개발하기 위한 준비물들이 필요하겠다.


일반적인 JavaScript Learning Guide
Edit section

참고사이트
Edit section

  1. Mozilla Developer Center: Core JavaScript 1.5 Reference Manual
  2. Yahoo UI개발자인 Douglas Crockford's JavaScript
  3. Yahoo Developer Network : YUI Theater  ( 더글라스 크록포드의 동영상 강의 수록)
  4. Peter-Paul Koch의 QuirksMode
  5. http://home.cogeco.ca/~ve3ll/jstutor0.htm

그외 볼만한 사이트(Dead Trees)
Edit section

  1. JavaScript: The Definitive Guide by David Flanagan
  2. Pro JavaScript Techniques by John Resig
  3. Ajax and REST Recipes by Christian Gross
  4. ppk on JavaScript by Peter-Paul Koch
  5. Professional JavaScript for Web Developers by Nicholas C. Zakas
  6. Ajax Patterns and Best Practices by Christian Gross
  7. Dynamic HTML by Danny Goodman
  8. Head First Design Patterns by Freeman & Freeman

 

OO JavaScript 참조사이트

  1. How to achieve private, public, and privileged members in JavaScript, by Dustin Diaz.
    : Method scoping에 대한 좋은 설명
  2. OOP in JS, Part 1 : Public/Private Variables and Methods, by Gavin Kistner.
    : Scoping 및 prototyping에 대한 샘플
  3. A JavaScript Module Pattern, by Eric Miraglia in YUI Blog.
    : 더글라스 크록포드가 얘기한 module pattern에 대한 설명
  4. Again with the Module Pattern - reveal something to the world, by Christian Heilmann.
    : 모듈패턴
  5. JavaScript for Object-Oriented Programmers (Appendix B of the book AJAX in Action).

DOM

 

 

그외 기술

Yahoo! User Interface Library (YUI)

Ext JS 은 yui-ext에서 부터 시작되었다. 아래 사이트에서 YUI에 대해서 좀 봐두는것도 좋을지도..

  1. YUI Library
  2. ydn-javascript: Yahoo! User Interface Library Group
  3. YUI Blog

JSON 관련

 

 

Platform-Specific Resources

ExtJS를 서버사이드 프레임웍으로 사용한 녀석들.....

PHP
Edit section

대표적인 샘플코드는 모두 PHP코드로 되어 있는 걸 보면  PHP에서 개발하기 가장 쉬운것 같음.
ExtJS 내장 PHP프레임웍 :  Qcodo Development Framework

Java

Direct Web Remoting (DWR) : http://getahead.org/dwr

.NET

  • AjaxPro 는 .Net 을 이용한 서버사이드 Ajax
  • Jayrock  :  .Net에 JSON / JSON-RPC 를 이용 
  • Json.NET  : .Net에서 JSON을 쓰게 만들어주는 라이브러리 (PHP의 toJSON()이나, Struts의 JSON과 같은건가 보다... ㅡ.,ㅡ; ) 

Ruby on Rails

Ext plugin - ROR는 내장 플러그인으로 작동하므로 gem을 이용해 받으면 되겠다...  (찾기가 더 힘드네.. ㅡ.,ㅡ;)

IBM Lotus Notes/Domino

IBM's Lotus Notes/Domino : IBM 머찌다라는 말만..

 

 

통합개발툴(IDE)
Edit section

Eclipse Based IDEs

Eclipse 은 오픈소스 통합개발툴 (among other things!)이다. ExtJS개발을 위해 이클립스를 사용하기 위해서는 먼저 자바스크립트 사용할수 있는 플러그인을 설치해야 한다.
아래의 플러그인을 추천한다.

  • Aptana - JavaScript, HTML, CSS, PHP languages, FTP/SFTP 지원, RIA/Web2.0등 Ajax 관련 프로젝트에 적합 (느리다는 단점이 있음.. ㅠ.,ㅠ)
  • Spket - JavaScript, XUL, SVG, Laszlo, Silverlight,등 각 모듈 라이브러리 지원(http://blog.naver.com/techbug/150024518549 )
  • JSEclipse - JavaScript

위에 열거된 플러그 인들은 각자 자바스크립트 에디터를 포함하고 있다.  자바스크립트를 열때 "Open with"로 열어서 사용하도록...플러그인을 선택할때는 코드 assist를 지원하는지 확인해 봐야 할듯

 

Aptana + Spket + ExtJS(강추..)
  • Aptana다운로드 http://www.aptana.com/download_all.php
  • Aptana > Help > Software Updates > Find and Install > Search for new features to install > New remote site.. > "http://www.spket.com/update/"
  • Spket설치 ( Spket IDE Tutorial )
    1. Window > Preferences > Spket > JavaScript Profiles > New > "ExtJS"
    2. "ExtJS" 선택하고  "Add Library"에서 "ExtJS"선택
    3.  "ExtJS" 선택하고 "Add File", 에서 "./ext-2.0/source" 디렉토리에서 "ext.jsb"를 선택
    4. ExtJS profile을 디폴트로 선택 
    5. Aptana 플러그인을 새 시작
    6. 새로운 JS파일을 만들면 ExtJS의 Code completion options를 볼수 있다.

AptanaIDE

  • Aptana는 상당히 매력적인 플러그인다. 대부분의 Ajax Library를 제공한다.

 

 


 

다양한 디버깅툴

Firebug (FF에서 강추)

Firebug는 Firefox에 애드온되어 작용하는데 웹페이지의 내용(CSS,HTML,Javscript등)을 실시간으로 볼수있는 상당히 매력적인 도구이다.


Screencasts


Tutorials


다른 브라우저에서 Firebug console을 이용하기

HTTP Trafic Viewer

피들러는 서버와 PC사이의 HTTP 트래픽 로그를 볼수 있는 디버깅 프록시이다. IE전용이나 Firefox의 proxy고급옵션에서 사용할수 있으며 자신이 원하는대로 필터링 하여 사용할수 있다.

MS Script Editor

IE Web Developper

IE WebDeveloper는 인터넷 익스플로러 애드온 프로그램이다. 

Testing Data Generator

SQL,CSV,xml, excel등 테스트 데이타를 생성해준다.

http://www.benjaminkeen.com/software/data_generator/

YSlow

YSlow는  rules for high performance web sites를 기반으로 현재 페이지의 속도를 분석해준다. YSlow 는 Firefox 애드온 프로그램으로 인기가 있다.Firebug web development tool.

  • Performance report card
  • HTTP/HTML summary
  • List of components in the page
  • Tools including JSLint

http://developer.yahoo.com/yslow/

DebugBar - IE 플러그인 (강추)

  • DOM Inspector,
  • HTTP Inspector
  • Javascript Inspector and Javascript Console
  • HTML Validator
  • And many more features

http://www.debugbar.com/?langage=en


'Scripter > EXTJS' 카테고리의 다른 글

Extjs 기본 DomQuery  (0) 2008.02.18
Extjs 초보자를 위한 애플리케이션 레이아웃(2.02)  (8) 2008.02.17
Extjs scope에 대한 고찰  (2) 2008.02.17
ExtJS Tutorial  (1) 2008.02.17
ExtJS 시작하기  (8) 2008.02.17
Post by 넥스트리소프트 데꾸벅(techbug)
, |