블로그 이미지

카테고리

데꾸벅 (194)
Publisher (39)
Scripter (97)
AJAX (6)
COMET (1)
JAVASCRIPT (34)
EXTJS (50)
jQuery (5)
Prototype.js (1)
Programmer (1)
Designer (30)
Integrator (18)
Pattern Searcher (4)
News (2)
강좌 및 번역 (3)

최근에 올라온 글

최근에 달린 댓글




국내환경에서 Ajax Library/Framework에서만 제공하는 기본 컴포넌트/위젯으로는 어려운 경우가 많은데 오늘 우연찮게 블로그 유입경로를 보다가 유난히 틀고정 그리드를 찾는 분들이 많아 포스팅한다.
Ext js 의 포럼글에서 우연찮게 찾아 북마킹해 놓은 것이 있어 공개한다. 아래 소스를 조금 수정하여 열고정, 행고정이 되는 그리드 컴포넌트를 만들어 보는것도 좋을듯....






Markup
<html xmlns="http: //www.w3.org/1999/xhtml" xml: lang="en" lang="en">
<head>
<link rel="stylesheet" type="text/css" href="../lib/ext-3.0.3/resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="columnLock.css" />
<script type="text/javascript" src="../lib/ext-3.0.3/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../lib/ext-3.0.3/ext-all.js"></script>
<script type="text/javascript" src="columnLock.js"></script>


컬럼모델 config 파일은 다음과 같이 작성한다.
columns: [
    {id:'company',header: "Company", width: 120, sortable: true, dataIndex: 'company', locked: true},
    {header: "Price", width: 150, sortable: true, renderer: 'usMoney', dataIndex: 'price'},
    {header: "Change", width: 150, sortable: true, renderer: change, dataIndex: 'change'},
    {header: "% Change", width: 150, sortable: true, renderer: pctChange, dataIndex: 'pctChange'},
    {header: "Last Updated", width: 150, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
],



Ext js 3.0x 이상

Ext js 2.1~ 2.2 버전

























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

Ext.Updater 깜빡거림 방지  (0) 2009.05.06
Extjs Core 3.0 Beta 릴리즈  (4) 2009.04.07
Examples Of ExtJS in Action  (2) 2009.03.31
extjs RowAction 붙이기  (1) 2009.03.31
extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Post by 넥스트리소프트 데꾸벅(techbug)
, |


ext-js 2.x 이상 버전에서 Updater를 이용시 깜빡거릴 경우가 있다.
이는 로딩인디케이터 때문인데 이럴 경우에는 다음과 같이 처리해주면 깜빡거리지 않는다.
아래 소스는 ext2.2 버전에서 테스트한 경우이다.

참조 URL : http://extjs.com/deploy/dev/docs/output/Ext.Updater.html

<html>
<head>
 <title>Ext JS 2.2 Samples</title>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<script type="text/javascript" src="../lib/ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../lib/ext-2.2/ext-all.js"></script>
<body>

<div id="specialDiv" style="border:solid 1px black;width:300px;height:300px"></div>
<button onclick="testFunc('start')">오토 시작</button>
<button onclick="testFunc('stop')">오토 끝</button><span id="techbugNo"></span><br />

<button onclick="testFunc1()">클릭할때마다 시작</button>


<script type="text/javascript">

 function testFunc(a){
  var mgr = new Ext.Updater("specialDiv");
  mgr.showLoadIndicator = false;  <-- 깜빡거림 방지
  if(a=='start'){
   mgr.startAutoRefresh(1, {
    url: "test1.html",
    method: "post",
    scripts: true,
    refreshNow : true,
    callback : function(){

    }
   });
  }else {
   //IE6이상버전에서 이상하게 작동하지 않음! 소스를 까봐야 겠음!
   mgr.stopAutoRefresh();
  }

  var techbug =0;
  mgr.on("update",function(){
   Ext.get("techbugNo").update(techbug++);
  });
 }

 function testFunc1(){   
  Ext.get("specialDiv").load({    //보통은 load로 처리하여 요기를 Updater로 사용하는것이 바람직
         url: "./test1.html",
         scripts: true,
         params: "",
         text: "Loading Foo..."
    });

 }
</script>
</body>
</html>


IE7, Chrome, FF3, Opera9.64 에서 테스트결과




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

Ext js 틀고정 그리드  (1) 2009.11.15
Extjs Core 3.0 Beta 릴리즈  (4) 2009.04.07
Examples Of ExtJS in Action  (2) 2009.03.31
extjs RowAction 붙이기  (1) 2009.03.31
extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Post by 넥스트리소프트 데꾸벅(techbug)
, |



ext3.0 베타버전이 릴리즈 됐다.
1.01a 버전부터 써오면서 라이센스정책을 쭈욱 지켜봤는데 이번 ext core 3.0는  MIT라이센스로 배포가 되었다.
즉 아무렇게 배포,수정이 가능하다는 얘기다.

현재 안정버전인 ext2.2나 그 이전 버전의 경우 core 파일이 obfuscating되어 있었던 반면 이번 ext3.0 Beta버전의 경우는 MIT정책에 따라 모든 소스가 친절하게도 주석까지 달려있어서 분석하기도 한층 쉬워졌다.
시간되시는 분들은 한번씩 까(?) 보시는것도 괜찮을듯... ㅎㅎ 데꾸벅 생각이였음돠~

 



 

JSONP 소스

Ext.ns('Ext.ux');

Ext.ux.JSONP = (function(){
    var _queue = [],
        _current = null,
        _nextRequest = function() {
            _current = null;
            if(_queue.length) {
                _current = _queue.shift();
       _current.script.src = _current.url + '?' + _current.params;
       document.getElementsByTagName('head')[0].appendChild(_current.script);
            }
        };

    return {
        request: function(url, o) {
            if(!url) {
                return;
            }
            var me = this;

            o.params = o.params || {};
            if(o.callbackKey) {
                o.params[o.callbackKey] = 'Ext.ux.JSONP.callback';
            }
            var params = Ext.urlEncode(o.params);

            var script = document.createElement('script');
        
 script.type = 'text/javascript';

            if(o.isRawJSON) {
                if(Ext.isIE) {
                    Ext.fly(script).on('readystatechange', function() {
                        if(script.readyState == 'complete') {
                            var data = script.innerHTML;
                            if(data.length) {
                                me.callback(Ext.decode(data));
                            }
                        }
                    });
                }
                else {
                     Ext.fly(script).on('load', function() {
                        var data = script.innerHTML;
                        if(data.length) {
                            me.callback(Ext.decode(data));
                        }
                    });
                }
            }

            _queue.push({
                url: url,
                script: script,
                callback: o.callback || function(){},
                scope: o.scope || window,
                params: params || null
            });

            if(!_current) {
                _nextRequest();
            }
        },

        callback: function(json) {
            _current.callback.apply(_current.scope, [json]);
            Ext.fly(_current.script).removeAllListeners();
            document.getElementsByTagName('head')[0].removeChild(_current.script);
            _nextRequest();
        }
    }
})();


 

 

 JSONP를 이용한 Flickr이미지 가져오기

<script>
Ext.onReady(function() {
    var resultTemplate = new Ext.Template.from('result-template');
   
    var updateResults = function(data) {
        Ext.fly('resultset').update('');
        Ext.each(data.items, function() {
            this.src = this.media.m;
            resultTemplate.append('resultset', this);
        });
    }
   
    Ext.get('search-form').on('submit', function(ev) {
        ev.preventDefault();
        Ext.ux.JSONP.request('http://api.flickr.com/services/feeds/photos_public.gne', {
            callbackKey: 'jsoncallback',
            params: {
                format: 'json',
                tags: Ext.fly('search-value').dom.value,
                tagmode: 'all',
                lang: 'en-us'                           
            },
            callback: updateResults
        });
        return false;
    });
})
</script>

 

<form id="search-form" action="#">
    <input type="text" value="ExtJS, Screenshot" id="search-value">
    <button type="submit" id="search-button">Search</button>           
</form>

<div id="resultset"></div>

<textarea id="result-template" style="display: none">
    <div class="result">
        <div class="header">
            <h4>{title}</h4>
            <h5>{author}</h5>
        </div>
        <img src="{src}" title="{title}">
    </div>
</textarea>



 

 jQuery를 이용한 Flickr이미지 가져오기

  $.getJSON(
    "http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?",
    function(data){
      $.each(data.items, function(i,item){
        $("<img/>").attr("src", item.media.m).attr("onclick","location.href="+item.media.link).appendTo("#result");
        $(document.body).unblock();
      });
    });

 

 참고로 Rhio.Kim's Extjs 2.2.1 Class Diagram

 

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

Ext js 틀고정 그리드  (1) 2009.11.15
Ext.Updater 깜빡거림 방지  (0) 2009.05.06
Examples Of ExtJS in Action  (2) 2009.03.31
extjs RowAction 붙이기  (1) 2009.03.31
extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Post by 넥스트리소프트 데꾸벅(techbug)
, |



Manning에서 나온 Extjs In Action 의 샘플파일을 링크걸어놓은 사이트가 있어 올린다.
고환율때문에 사기는 그렇고 아직 MEAP(Manning Early Access Program)상태라 어둠의 경로로 얻으려고 했으나 좋은건 사주는게... ㅡ.,ㅡ;



General Apps

  • CRM : [ demo ]
  • Ajax Yahoo IM : [ demo ]
  • Mail Center (Webmail) : [ demo ]
  • File Sports : [ demo ]
  • SeeMe (using Google Maps) : [ demo ]
  • ExtPlorer : [ demo ]
  • Group Office : [ demo ] (demo/demo)

Development Tools

Books

  • ExtJS In Action [ link ]
  • Learning ExtJS [ link ]



--------[ 이건 그냥 덤으로.. ^^ ] -------------------------------------------------------
IE7   [ 4.00 Kb ]
astar   [ 4.00 Kb ]
clock   [ 4.00 Kb ]
cssdropshadow   [ 4.00 Kb ]
gzoutput   [ 4.00 Kb ]
loopsbench   [ 4.00 Kb ]
pwdsecurity   [ 4.00 Kb ]
tris   [ 4.00 Kb ]
DOM.js   [ 2.42 Kb ]
ColorFader.html   [ 1.20 Kb ]
ColorFader.js   [ 3.01 Kb ]
CssSwitcher.js   [ 1.35 Kb ]
LoadVars.html.old   [ 2.48 Kb ]
FolderTree.swf   [ 58.21 Kb ]
LoadVars.html   [ 3.41 Kb ]
LoadVars.js.old   [ 9.11 Kb ]
LoadVars.js   [ 7.92 Kb ]
Sqlitei_Example.html   [ 12.56 Kb ]
SimpleSWF.js   [ 911 bytes ]
TEST_PHP4_MYSQL.zip   [ 1.51 Kb ]
TEST_SqliteMPTTA.zip   [ 1.37 Kb ]
adsense.html   [ 9.34 Kb ]
bigdollar.html   [ 2.96 Kb ]
browser_agent.html   [ 899 bytes ]
cssswitcher.html   [ 3.48 Kb ]
fap.example.js   [ 3.89 Kb ]
fakedom.html   [ 2.05 Kb ]
testf5.html   [ 1.16 Kb ]
flashPlayerVersion.html   [ 694 bytes ]
flashPlayerVersion.js   [ 1.11 Kb ]
jsbenchmark.html   [ 9.19 Kb ]
multipletest.html   [ 1.66 Kb ]
myfolder_flash.xml   [ 26.70 Kb ]
onload.html   [ 501 bytes ]
onload2.html   [ 1.89 Kb ]
onload3.html   [ 542 bytes ]
onload4.html   [ 2.61 Kb ]
onmousewheel.html   [ 33.91 Kb ]
onmousewheel.js   [ 1.18 Kb ]
othertest.swf   [ 810 bytes ]
othertest.txt   [ 652 bytes ]
simpleswf.html   [ 277 bytes ]
simpleswf.zip   [ 20.68 Kb ]
singletest.html   [ 1.17 Kb ]
singletest.swf   [ 765 bytes ]
singletest.txt   [ 656 bytes ]
singletesthalfheight.txt   [ 654 bytes ]
wheelscroll.gif   [ 1.90 Kb ]
testf5.swf   [ 629 bytes ]
testf5.txt   [ 648 bytes ]

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

Ext.Updater 깜빡거림 방지  (0) 2009.05.06
Extjs Core 3.0 Beta 릴리즈  (4) 2009.04.07
extjs RowAction 붙이기  (1) 2009.03.31
extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Extjs 3.0 Roadmap  (10) 2008.11.12
Post by 넥스트리소프트 데꾸벅(techbug)
, |


기본적으로 grid의 View에 editor와 같이 다른 컴포넌트를 붙이기는 기존 소스보다 컴포넌트를 붙이는 소스가 더 많이 들어가는 경우가 있다.
데꾸벅의 경우도 프로젝트중 각각의 그리드 Row마다 progress bar를 붙여야 하는 경우가 생겼었는데..
포럼글을 뒤지다가 다음과 같은 좋은 사용자 플러그인이 있어 분석을 위해 소스를 까 보았다.



위 그림에서 보는바와 같이 그리드위에 버튼을 만들어 올릴때 new Ext.button이 아닌 new Ext.Action을 사용하여 사용자 플러그인을 사용한 소스이다.
소스 API  및 관련 포럼글 

HTML
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 <link rel="stylesheet" type="text/css" href="./css/icons.css">
 <link rel="stylesheet" type="text/css" href="./css/Ext.ux.grid.RowActions.css">
    <link rel="stylesheet" type="text/css" href="../../ext/ext-2.2/resources/css/ext-all.css" />
    <script type="text/javascript" src="../../ext/ext-2.2/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="../../ext/ext-2.2/ext-all.js"></script>
 <script type="text/javascript" src="./js/Ext.ux.grid.RowActions.js"></script>
 <script type="text/javascript" src="./js/Ext.ux.Toast.js"></script>
 <script type="text/javascript" src="rowactions.js"></script>
</head>
<body></body>
</html>





JS

Ext.BLANK_IMAGE_URL = '';
Ext.ns('Example');
Example.version = '1.0';

Example.Grid = Ext.extend(Ext.grid.GridPanel, {

  initComponent:function() {

   // Create RowActions Plugin
   this.action = new Ext.ux.grid.RowActions({
    header:'Actions'
   ,autoWidth:true
   ,keepSelection:true
   ,actions:[{
     iconIndex:'action1'
    ,qtipIndex:'qtip1'
    ,iconCls:'icon-open'
    ,tooltip:'Open'
   },{
     iconCls:'icon-wrench'
    ,tooltip:'Configure'
    ,qtipIndex:'qtip2'
    ,iconIndex:'action2'
    ,hideIndex:'hide2'
   },{
     iconIndex:'action3'
    ,qtipIndex:'qtip3'
    ,iconCls:'icon-user'
    ,tooltip:'User'
    ,style:'background-color:yellow'
   }]
   ,groupActions:[{
     iconCls:'icon-del-table'
    ,qtip:'Remove Table'
   },{
     iconCls:'icon-add-table'
    ,qtip:'Add Table - with callback'
    ,callback:function(grid, records, action, groupId) {
     Ext.ux.Message.msg('Callback: icon-add-table'
         , 'Group: <b>{0}</b>, action: <b>{1}</b>, records: <b>{2}</b>'
         , groupId
         , action
         , records.length);
    }
   },{
     iconCls:'icon-graph'
    ,qtip:'View Graph'
    ,align:'left'
   }]
   ,callbacks:{
    'icon-plus':function(grid, record, action, row, col) {
     Ext.ux.Message.msg('Callback: icon-plus'
         , 'You have clicked row: <b>{0}</b>, action: <b>{0}</b>'
         , row
         , action);
    }
   }
  });

  this.action.on({
   action:function(grid, record, action, row, col) {
    Ext.ux.Message.msg('Event: action', '클릭한 Row: <b>{0}</b>, 액션: <b>{1}</b>'
        , row
        , action);
   }
   ,beforeaction:function() {
    Ext.ux.Message.msg('Event: beforeaction', '핸들러에서 false반환시 액션취소.');
   }
   ,beforegroupaction:function() {
    Ext.ux.Message.msg('Event: beforegroupaction', '핸들러에서 false반환시 액션취소');
   }
   ,groupaction:function(grid, records, action, groupId) {
    Ext.ux.Message.msg('Event: groupaction', 'Group: <b>{0}</b>, action: <b>{1}</b>, records: <b>{2}</b>', groupId, action, records.length);
   }
  });

  Ext.apply(this, {
    store:new Ext.data.Store({
    reader:new Ext.data.JsonReader({
      id:'company'
     ,totalProperty:'totalCount'
     ,root:'rows'
     ,fields:[
      {name: 'company'}
        ,{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
        ,{name: 'industry'}
        ,{name: 'desc'}
        ,{name: 'action1', type: 'string'}
        ,{name: 'action2', type: 'string'}
        ,{name: 'action3', type: 'string'}
        ,{name: 'qtip1', type: 'string'}
        ,{name: 'qtip2', type: 'string'}
        ,{name: 'qtip3', type: 'string'}
        ,{name: 'hide2', type: 'boolean'}
     ]
    })
    ,proxy:new Ext.data.HttpProxy({url:'jsonData.json'})
    ,sortInfo:{field:'company', direction:'ASC'}
    ,listeners:{
     load:{scope:this, fn:function() {
      this.getSelectionModel().selectFirstRow();
     }}
    }
   })
   ,columns:[
    {id:'company',header: "Company", width: 40, sortable: true, dataIndex: 'company'}
    ,{header: "Industry", width: 20, sortable: true, dataIndex: 'industry'}
    ,{header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
    ,this.action
   ]
   ,plugins:[this.action]
   ,loadMask:true
   ,viewConfig:{forceFit:true}
  });

  this.bbar = new Ext.PagingToolbar({
    store:this.store
   ,displayInfo:true
   ,pageSize:10
  });

  Example.Grid.superclass.initComponent.apply(this, arguments);
 }


 ,onRender:function() {
  Example.Grid.superclass.onRender.apply(this, arguments);
  this.store.load({params:{start:0, limit:10}});
 }
});

//컴포넌트 등록
Ext.reg('techbuggrid', Example.Grid);

Ext.onReady(function() {
    Ext.QuickTips.init();

    var win = new Ext.Window({
         width:600
  ,minWidth:320
        ,id:'agwin'
        ,height:400
        ,layout:'fit'
        ,border:false
  ,plain:true
        ,closable:false
        ,title:"그리드 Row Action 처리하기"
  ,items:{xtype:'techbuggrid',id:'actiongrid'}
    });
    win.show();
});




사용자 플러그인.JS

Ext.ns('Ext.ux.grid');
if('function' !== typeof RegExp.escape) {
 RegExp.escape = function(s) {
  if('string' !== typeof s) {
   return s;
  }
  return s.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1');
 };
}

Ext.ux.grid.RowActions = function(config) {
 Ext.apply(this, config);
 this.addEvents(
   'beforeaction'
  ,'action'
  ,'beforegroupaction'
  ,'groupaction'
 );
 Ext.ux.grid.RowActions.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, {
  actionEvent:'click'
 ,autoWidth:true
 ,dataIndex:''
 ,header:''
 ,keepSelection:false
 ,menuDisabled:true
 ,sortable:false
 ,tplGroup:
   '<tpl for="actions">'
  +'<div class="ux-grow-action-item<tpl if="\'right\'===align"> ux-action-right</tpl> '
  +'{cls}" style="{style}" qtip="{qtip}">{text}</div>'
  +'</tpl>'
 ,tplRow:
   '<div class="ux-row-action">'
  +'<tpl for="actions">'
  +'<div class="ux-row-action-item {cls} <tpl if="text">'
  +'ux-row-action-text</tpl>" style="{hide}{style}" qtip="{qtip}">'
  +'<tpl if="text"><span qtip="{qtip}">{text}</span></tpl></div>'
  +'</tpl>'
  +'</div>'
 ,hideMode:'visiblity'
 ,widthIntercept:4
 ,widthSlope:21
 ,init:function(grid) {
  this.grid = grid;
  if(!this.tpl) {
   this.tpl = this.processActions(this.actions);
  }
  if(this.autoWidth) {
   this.width =  this.widthSlope * this.actions.length + this.widthIntercept;
   this.fixed = true;
  }
  var view = grid.getView();
  var cfg = {scope:this};
  cfg[this.actionEvent] = this.onClick;
  grid.afterRender = grid.afterRender.createSequence(function() {
   view.mainBody.on(cfg);
   grid.on('destroy', this.purgeListeners, this);
  }, this);

  if(!this.renderer) {
   this.renderer = function(value, cell, record, row, col, store) {
    cell.css += (cell.css ? ' ' : '') + 'ux-row-action-cell';
    return this.tpl.apply(this.getData(value, cell, record, row, col, store));
   }.createDelegate(this);
  }

  if(view.groupTextTpl && this.groupActions) {
   view.interceptMouse = view.interceptMouse.createInterceptor(function(e) {
    if(e.getTarget('.ux-grow-action-item')) {
     return false;
    }
   });
   view.groupTextTpl =
     '<div class="ux-grow-action-text">' + view.groupTextTpl +'</div>'
    +this.processActions(this.groupActions, this.tplGroup).apply()
   ;
  }

  if(true === this.keepSelection) {
   grid.processEvent = grid.processEvent.createInterceptor(function(name, e) {
    if('mousedown' === name) {
     return !this.getAction(e);
    }
   }, this);
  }
  
 }
 ,getData:function(value, cell, record, row, col, store) {
  return record.data || {};
 }

 ,processActions:function(actions, template) {
  var acts = [];

  Ext.each(actions, function(a, i) {
   if(a.iconCls && 'function' === typeof (a.callback || a.cb)) {
    this.callbacks = this.callbacks || {};
    this.callbacks[a.iconCls] = a.callback || a.cb;
   }

   var o = {
     cls:a.iconIndex ? '{' + a.iconIndex + '}' : (a.iconCls ? a.iconCls : '')
    ,qtip:a.qtipIndex ? '{' + a.qtipIndex + '}' : (a.tooltip || a.qtip ? a.tooltip || a.qtip : '')
    ,text:a.textIndex ? '{' + a.textIndex + '}' : (a.text ? a.text : '')
    ,hide:a.hideIndex
     ? '<tpl if="' + a.hideIndex + '">'
      + ('display' === this.hideMode ? 'display:none' :'visibility:hidden') + ';</tpl>'
     : (a.hide ? ('display' === this.hideMode ? 'display:none' :'visibility:hidden;') : '')
    ,align:a.align || 'right'
    ,style:a.style ? a.style : ''
   };
   acts.push(o);

  }, this);

  var xt = new Ext.XTemplate(template || this.tplRow);
  return new Ext.XTemplate(xt.apply({actions:acts}));

 }
 ,getAction:function(e) {
  var action = false;
  var t = e.getTarget('.ux-row-action-item');
  if(t) {
   action = t.className.replace(/ux-row-action-item /, '');
   if(action) {
    action = action.replace(/ ux-row-action-text/, '');
    action = action.trim();
   }
  }
  return action;
 }
 ,onClick:function(e, target) {

  var view = this.grid.getView();

  var row = e.getTarget('.x-grid3-row');
  var col = view.findCellIndex(target.parentNode.parentNode);
  var action = this.getAction(e);

  if(false !== row && false !== col && false !== action) {
   var record = this.grid.store.getAt(row.rowIndex);

   if(this.callbacks && 'function' === typeof this.callbacks[action]) {
    this.callbacks[action](this.grid, record, action, row.rowIndex, col);
   }

   if(true !== this.eventsSuspended && false === this.fireEvent('beforeaction', this.grid, record, action, row.rowIndex, col)) {
    return;
   }
   else if(true !== this.eventsSuspended) {
    this.fireEvent('action', this.grid, record, action, row.rowIndex, col);
   }

  }

  t = e.getTarget('.ux-grow-action-item');
  if(t) {
   var group = view.findGroup(target);
   var groupId = group ? group.id.replace(/ext-gen[0-9]+-gp-/, '') : null;

   var records;
   if(groupId) {
    var re = new RegExp(RegExp.escape(groupId));
    records = this.grid.store.queryBy(function(r) {
     return r._groupId.match(re);
    });
    records = records ? records.items : [];
   }
   action = t.className.replace(/ux-grow-action-item (ux-action-right )*/, '');

   if('function' === typeof this.callbacks[action]) {
    this.callbacks[action](this.grid, records, action, groupId);
   }

   if(true !== this.eventsSuspended && false === this.fireEvent('beforegroupaction', this.grid, records, action, groupId)) {
    return false;
   }
   this.fireEvent('groupaction', this.grid, records, action, groupId);
  }
 }

});

Ext.reg('rowactions', Ext.ux.grid.RowActions);




요기 괜찮은 사용자 플러그인도 함 보세요





















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

Extjs Core 3.0 Beta 릴리즈  (4) 2009.04.07
Examples Of ExtJS in Action  (2) 2009.03.31
extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Extjs 3.0 Roadmap  (10) 2008.11.12
Ext2.2 Release  (11) 2008.08.07
Post by 넥스트리소프트 데꾸벅(techbug)
, |

얼마전 부터 방명록에 몇몇 분들이 iframe 안에서 grid panel이 동작하지 않는다고 하여 포럼글을 찾아보던중 다음과 같은 버그가 존재하였다.

FF3, Opera, IE6에서 document를  load시 load타임이 맞지 않아서 생기는 버그였다. 기존 2.1버전에서 2.2 버전으로 업그레이드 하면서 생긴 버그이다. 만일  ext의 프리미엄 멤버라면 svn을 통해서 해당 내용들을 모두 업데이트 받았겠지만 가난한 나같은 개발자들은 그러지 못한 관계로 포스팅한다. 

먼저 가장 최근의 ext-2.2 버전을 다운로드한다. ( 같은 버전이라도 버그패치되는 대로 패치하기 때문에 매일매일 다를수 있다. 다운로드 받거든 이미 받았던 버전과 용량체크를 해 보면 바로 알수 있다.. ㅡ"ㅡ; )

ext-all-debug.js 를 이용한다면 다음과 같이 3개 메쏘드를 바꿔준다.

fireDocReady(),  initDocReady(), onDocumentRead()

1580줄 fireDocReady를 다음과 같이 바꿔준다.

 var fireDocReady = function(){
        docReadyState = true;
        if(Ext.isGecko) {
             document.removeEventListener("DOMContentLoaded", fireDocReady, false);
        }

        if(docReadyProcId){
             clearInterval(docReadyProcId);
             docReadyProcId = null;
        }

        if(docReadyEvent && !Ext.isReady){
            Ext.isReady = true;
            docReadyEvent.fire();
            docReadyEvent.clearListeners();
        }
    };


1597줄의 initDocReady를 다음과 같이 바꿔준다.

    var initDocReady = function(){
        docReadyEvent = new Ext.util.Event();

        if(Ext.isReady){ return;}

        E.on(window, 'load', fireDocReady);

        if(Ext.isGecko) {
            document.addEventListener('DOMContentLoaded', fireDocReady, false);
        }
        else if(Ext.isIE){
                                 
            document.onreadystatechange = function(){
                if(document.readyState == 'complete'){
                     document.onreadystatechange = null;
                     fireDocReady();
                }
            };
                                
             if(window == top){   //Use the default readystatechange/onload as primary detection mechanism for frames 
                   var doScrollChk = function(){
                    try{
                       document.documentElement.doScroll('left');
                       Ext.isReady || fireDocReady();
                   }catch(e){
                       Ext.isReady || setTimeout(doScrollChk ,0);
                   }
                  };
                  doScrollChk();
               }
          
        } else if(Ext.isOpera){
              document.addEventListener( 'DOMContentLoaded', function () {
                    if (Ext.isReady) return;
                    for (var i = 0; i < document.styleSheets.length; i++)
                          if (document.styleSheets[i].disabled) {
                                  setTimeout( arguments.callee, 0 );
                                  return;
                          }
                           Ext.isReady || fireDocReady();
                           document.removeEventListener("DOMContentLoaded", arguments.callee, false);

                }, false);
        }
        else if(Ext.isSafari){
           var re = /complete|loaded/i;
            docReadyProcId = setInterval(function(){
                if(re.test(document.readyState) ) {
                    Ext.isReady || fireDocReady();
                 }
            }, 10);
        }
    };


1743줄의 onDocumentReady를 다음과 같이 바꿔준다.

     onDocumentReady : function(fn, scope, options){
            if(!docReadyEvent){
                initDocReady();
            }
            if(docReadyState || Ext.isReady){
                options || (options = {});
                fn.defer(options.delay||1, scope);    ----------> 0을 1로 바꿔주면 된다.  defer(1)과 같은 효과
            }else{
                docReadyEvent.addListener(fn, scope, options);
            }
        },




직접 ext-all-debug.js를 수정하였을 경우는 다음과 같이 하면 되고 각각의 컴포넌트들을 수정하려면 아래 eventManager관련 파일을 찾아 덮어쓴후 다시 빌드하여 ext-all.js로 만들어 쓰면 된다. 





사족 : 조만간 올초에 3.0 이 나올텐데 다시 업그레이드 되겠죠~ 이클립스용 ext designer와 standalone designer도 벌써 3.0 버전용이 나와 있더군요.. ㅡ.,ㅡ;











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

Examples Of ExtJS in Action  (2) 2009.03.31
extjs RowAction 붙이기  (1) 2009.03.31
Extjs 3.0 Roadmap  (10) 2008.11.12
Ext2.2 Release  (11) 2008.08.07
ExtJS 2.1 릴리즈  (4) 2008.04.22
Post by 넥스트리소프트 데꾸벅(techbug)
, |

Extjs 3.0 Roadmap

Scripter/EXTJS / 2008. 11. 12. 16:42

벌써 2.2가 Release되고 3.0이 Extjs Forum글에서 언급되고 있다.
Extjs Roadmap을 포스팅한지가 지난 4월이였는데 이번주 Ajaxian사이트에 올라온 Extjs Designer동영상을 보니 다시금 불끈~~!!


Ext JS 2.1 (Released April 21, 2008)

View the release notes for full details.

  • Full REST support
  • Remote component loading samples
  • First class support for non-Ajax form submits
  • Upgrade all adapter libraries to current versions
  • Additional support for AIR platform
  • Grid filtering sample
  • StatusBar
  • Slider
  • CheckBox/Radio group controls (deferred to 2.2)

Ext JS 2.2 (Released August 4, 2008)

View the release notes for full details.

  • Styled Checkbox and Radio button controls
  • CheckBox/Radio group controls
  • Browser history support
  • XML TreeLoader sample
  • FileUpload field sample
  • MultiSelect and ItemSelector sample
  • Firefox 3.0 support
  • Slider (already released in 2.1)
  • Ext.Ajax enhancements (deferred to 3.0)
  • Support for reset styles scoped to Ext components only (deferred to 3.0 to coincide with additional CSS refactoring to enable easier custom theming)

Ext JS 3.0 (Early 2009)

  • All new lightweight, high-speed core base library
  • Flash Charting API
  • Ext.Direct - Remoting and data streaming/comet support
  • Integrated client-server data binding/marshaling of updates
  • ListView component
  • Enhanced Button and Toolbar components
  • ARIA/Section 508 accessibility improvements
  • CSS updates for reset style scoping and easier custom theming
  • Update the Ext event registration model
  • Ext.Ajax enhancements
  • Browser history support (released in 2.2)


출처 : http://extjs.com/products/extjs/roadmap.php

아래 동영상은 3.0 Release시 같이 출시된 designer tool 이다... 흠.. 이거 쓰면 괜찮겠는데.. Trust Form Designer랑 비슷하다.. ㅡ.,ㅡ;







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

extjs RowAction 붙이기  (1) 2009.03.31
extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Ext2.2 Release  (11) 2008.08.07
ExtJS 2.1 릴리즈  (4) 2008.04.22
RESTFul 한 ExtJS  (0) 2008.04.16
Post by 넥스트리소프트 데꾸벅(techbug)
, |

Ext2.2 Release

Scripter/EXTJS / 2008. 8. 7. 10:30

일전에 포스팅한 extjs 로드맵에서 언급했듯이 extjs 2.2 가 릴리즈 됐다.

그리고 데꾸벅의 한글화 부분(ext-2.2/build/locale/ext-lang-ko.js)도 추가적으로 패키징되어 들어가 있다. 캬햐햐
/**
 * Korean Translations By nicetip
 * 05 September 2007
 * Modify by techbug / 25 February 2008
 */
트리로더 확장과 불여우2에서 스크롤문제(overflow)와 IE6 버그(relative)일때 스크롤문제부분(스쿨학우여러분 땡큐 많이 참조했어요.. ^^)도 부분적으로 채택이 됐네요.. 크크..
FF3 Beta1에서 에러나던 버튼위치부분은... 쿨럭.. ㅡ.,ㅡ;

2.2 (2008 여름) : 릴리즈노트

  • Ext.Ajax 기능향상
  • Ext컴포넌트만의 초점을 맞춘(scoped) REST style지원
  • XML을 포함한 추가적인 TreeLoader 데이타로딩
  • 슬라이더 컴포넌트
  • 파일업로드 필드

릴리즈노트에 나타난 새롭게 추가된 기능들은 다음과 같다.

[ CheckboxGroup / RadioGroup ]

... },{
    xtype: 'checkboxgroup',
    fieldLabel: 'Multi-Column (horizontal)',
    columns: 3,
    items: [
        {boxLabel: 'Item 1', name: 'cb-horiz-1'},
        {boxLabel: 'Item 2', name: 'cb-horiz-2', checked: true},
        {boxLabel: 'Item 3', name: 'cb-horiz-3'},
        {boxLabel: 'Item 4', name: 'cb-horiz-4'},
        {boxLabel: 'Item 5', name: 'cb-horiz-5'}
    ]
},{ ...

각 개별적인 item으로만 적용했던 라디어버튼과 체크박스를 그룹핑하여 만든 컴포넌트가 추가되었다. [샘플보기]
사용자 삽입 이미지



[히스토리컴포넌트 추가] [샘플보기]
listeners: {
    'tabchange': function(tabPanel, tab){
        // Ignore tab1 since it is a separate tab panel and we're managing history for it also.
        // We'll use its handler instead in that case so we don't get duplicate nav events for sub tabs.
        if(tab.id != 'tab1'){
            Ext.History.add(tabPanel.id + tokenDelimiter + tab.id);
        }
    }
}

사용자 삽입 이미지

위의 탭기능중 sub-tab3 를 선택한위 상단 탭을 클릭햇을 경우 다시 돌아왔을때 sub-tab3이 선택되어 있게 만들어준다. 이전 버전에서는 별도의 글로벌변수로 선언해준뒤 사용했었는데 이번 릴리즈에는 포함되어 나와서 상당히 편할듯하다..
히스토리 컴포넌트 사용시에는 URL 핸들링이 하는지 URL자체가 변하는 효과를 볼수 있다.
사용자 삽입 이미지


[MultiSelect / ItemSelector] [데모보기]
이 기능은 사용자 확장기능에 올라왔던 기능인데 이번 릴리즈에 포함되었있다.
전통적인 방식의 <select multiple> 기능을 extjs로 구현한 것이다.
사용자 삽입 이미지


[FileUploadField][데모보기]
사용자 확장기능에서 가장 많이 찾던 swfFileUpload에서 차용한듯한 소스가 이번 릴리즈에 포함되어 있다. ajax fileupload를 찾는 국내 사용자에게는 상당히 유용한 컴포넌트가 될듯하다. Form 컴포넌트에 포함되어 있다.
var fibasic = new Ext.form.FileUploadField({
    renderTo: 'fi-basic',
    width: 400
});


[XmlTreeLoader] [데모보기]
사용자 확장기능이다. 이전 treeLoader와 사용법은 비슷하다.


[GMapPanel][데모보기]
사용자 삽입 이미지
구글맵을 보여줄수 있는 panel이 추가되었다.
이것또한 사용자 확장기능이였으나 이번 릴리즈에 포함되었다.








사실 새롭게 추가된 기능들만 보자면 로드맵에 나왔던 내용들은 거의 없다.
향상된 기능들이라면 Firefox3 및 IE8 버전을 지원하다는 내용과 드래그앤드랍 기능(특정DnD영역그리드패널 Row의 드래그엔 드랍, 폼내널에서의 드래그엔드랍), 그리도 속도 향상, status기능등이 많이 향상되었다.

또한 축약어 들이 많이 등장했는데 Ext.MessageBox()를 msg()와 같이 표현한다던가, 브라우저 버전에 대한 글로벌 변수 Ext.isGecko2, Ext.isGecko3 등이 더 추가되었다.

그외 아래 링크로 확인하시기 바랍니다.






오픈소스 라이센스에 대한 이해~!! 는 다음 파일을 참조하세요








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

extjs 2.2의 IE에서 iframe document load 버그패치  (0) 2009.01.21
Extjs 3.0 Roadmap  (10) 2008.11.12
ExtJS 2.1 릴리즈  (4) 2008.04.22
RESTFul 한 ExtJS  (0) 2008.04.16
Extjs Qtips 사용하기  (2) 2008.04.16
Post by 넥스트리소프트 데꾸벅(techbug)
, |

ExtJS 2.1 릴리즈

Scripter/EXTJS / 2008. 4. 22. 09:55

프로젝트가 바빠서 한동안 들리지 않았더니만...
extjs 2.1 이 릴리즈됐다.

많은 변화가 있었는데 가장 큰 변화라면 라이센스정책이 기존의 LPGL에서 3가지로 구분되어 나뉘어졌다. YUI에서 출발한 extjs는 아닐거라고 생각했는데 유료로 전환해 버렸네~
우선 가장 이번 2.1버전에서 달라진 점(Release Note)을 찾아본다면 아래와 같다.

  1. 라이센스 정책 변화
  2. Full REST 지원
  3. Ext.StatusBar 컴포넌트 추가 (IE7,FF에서 보안정책때문에 애먹어서 구현하고 있었는데.. ㅠ.,ㅠ)
  4. 컴포넌트 Config옵션 Remote Loading지원
  5. Grid 필터링 추가
  6. Layout Browser Sample 추가
  7. Spotlight 기능추가
  8. 아이콘 팝업 추가
  9. 슬라이드기능 추가
  10. validation기능 추가(향상이라고 해야 하나?)
  11. Card Panel Layout추가 (Wizard기능)
  12. Markup re-Rendering기능 향상

라이센스정책
많은 개발자들의 활발한 활동과 커뮤니티의 활성화로 Ext가 안정화 되면서 Ext개발자와 공헌자들이 계속 개발을 할수 있게 하기위해 라이센스 정책을 변경함 ( 뭐~ 다들 일케 말하더라~ )  여기서 말하는 듀얼 라이센스 정책이라는것은 개발된 애플리케이션의 소스를 공개하지 않는다면 commercial License이고 공개한다면 Open Source가 된다는 거다.. ㅡ.,ㅡ;
둘다 Open Source이긴 하지만.. 돈있으면 소스공개하지 말고 commercial로 쓰고 돈없으면 소스 공개해서 OpenSource로 까발리라는 얘기다~~

1. Commercial License :
 Quid Pro Quo 원칙에 근거하여 배포할 목적으로 애플리케이션을 개발한다면 적정한 수 만큼의 라이센스를 구입해야 함. (듀얼 라이센스정책)
 - 구매한 소스에 대해서는 상업용으로 전환 가능.
  - GPL에 의해 소스를 수정할수 있음.
2. Open Source
 기존의 LGPL에서 GPL에 따른 라이센스 정책
3. OEM / Reseller License




샘플보기 : http://www.extjs.com/deploy/dev/examples/samples.html


헉... Firebug를 켠 상태로 들어갔더니만 이런 메세제가 ㅡ.,ㅡ; firebug detecting기능을..

사용자 삽입 이미지





그리드 필터링 기능 : 언제가 사용자중에 이 기능을 extesion으로 올렸던것이 추가되었다.

사용자 삽입 이미지






Sportlight기능 : 설치하기나 step으로 나눈것 설명할때 편하겠군효~~
사용자 삽입 이미지






Drag & Drop 기능향상

사용자 삽입 이미지









슬라이드 기능 (기존 scriptaculous사이트에서 보던 예제를 extjs로... ㅡ.,ㅡ;)


사용자 삽입 이미지






StatusBar 기능
사용자 삽입 이미지






흠.. StatusBar기능 괜찮네~ 이것도 사용자 extension이였던 기능이였는데..
사용자 삽입 이미지







개발시 가장 까다로웠던 validation check기능이 많이 추가되었다.
사용자 삽입 이미지







CardLayout을 이용한 Wizard기능 추가
사용자 삽입 이미지






흠... 이건.. 추가라고 할것도 없이 사용자 편의성을 위해 아이콘추가 ㅡ.,ㅡ;
사용자 삽입 이미지







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

Extjs 3.0 Roadmap  (10) 2008.11.12
Ext2.2 Release  (11) 2008.08.07
RESTFul 한 ExtJS  (0) 2008.04.16
Extjs Qtips 사용하기  (2) 2008.04.16
Extjs 기본레이아웃에 각종 패널붙이기  (12) 2008.04.16
Post by 넥스트리소프트 데꾸벅(techbug)
, |

RESTFul 한 ExtJS

Scripter/EXTJS / 2008. 4. 16. 19:49


오늘따라 많이 포스팅한다는 느낌이... ( 데꾸벅 무리하다... ㅡ.,ㅡ; )
사실 뒤에 앉아 있는 울 겸댕이 윤부장의 친구분이 이 블로그를 보고 계신다기에 팁으로 올려본다.

RESTFul 한 Web application을 개발하기 위하여 부단히 노력하고 계신분들을 위한 포스트~
크헬헬~


먼저 이곳을 먼저 보세요.. : RESTful Web Services (번역중~ 크헬헬)



이제껏 RESFul하다고 하면서 개발할때 사실 2.0이전 버전에서는 모든 데이타를 parameter값들에 의해서 일일이 encodeURIComponent까지 친절히 적어주면서 작업을 하였으나 2.0 버전부터는 REST를 Full 지원하게 되면서 상당히 작업하기가 편해진것은 사실이다.

Request를 보낼때 다음과 같이 보내면 된다.....

    Ext.Ajax.request({
        url: 'process.json',
        method:'delete',
        jsonData: {foo:'bar'},   //보낼 json타입을 파라미터 값이아니라 json객체로 ..
        headers: {
            'Content-Type': 'application/json-rpc' // application/json이 아님에 주의
        }
    });


단, 아직은 완전한 REST 같지 않아서 다음과 같이 오버라이드해야 한다.


Ext.override(Ext.form.Action.Submit, {
    run : function(){
        var o = this.options;
        var method = this.getMethod();
        var isGet = method == 'GET';
        if(o.clientValidation === false || this.form.isValid()){
            Ext.Ajax.request(Ext.apply(this.createCallback(o), {
                form:this.form.el.dom,
                url:this.getUrl(isGet),
                method: method,
                params:!isGet ? this.getParams() : null,
                isUpload: this.form.fileUpload
            }));
        }else if (o.clientValidation !== false){ // client validation failed
            this.failureType = Ext.form.Action.CLIENT_INVALID;
            this.form.afterAction(this, false);
        }
    }
});

Ext.lib.Ajax.request = function(method, uri, cb, data, options) {
    if(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){
            if (!hs || !hs['Content-Type']){
                this.initHeader('Content-Type', 'text/xml', false);
            }
            method = (method ? method : (options.method ? options.method : 'POST'));
            data = options.xmlData;
        }else if(options.jsonData){
            if (!hs || !hs['Content-Type']){
                this.initHeader('Content-Type', 'application/json', false);
            }
            method = (method ? method : (options.method ? options.method : 'POST'));
            data = typeof options.jsonData == 'object' ? Ext.encode(options.jsonData) : options.jsonData;
        }
    }
   
    return this.asyncRequest(method, uri, cb, data);
};

Ext.lib.Ajax.asyncRequest = function(method, uri, callback, postData) {
    var o = this.getConnectionObject();
    if (!o) {
        return null;
    }
    else {
        o.conn.open(method, uri, true);
       
        if (this.useDefaultXhrHeader) {
            if (!this.defaultHeaders['X-Requested-With']) {
                this.initHeader('X-Requested-With', this.defaultXhrHeader, true);
            }
        }
       
        if(postData && this.useDefaultHeader && !this.headers['Content-Type']){
            this.initHeader('Content-Type', this.defaultPostHeader);
        }
       
        if (this.hasDefaultHeaders || this.hasHeaders) {
            this.setHeader(o);
        }
       
        this.handleReadyState(o, callback);
        o.conn.send(postData || null);
       
        return o;
    }
};

참고 : extjs.com 포럼글 중에서





 

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

Ext2.2 Release  (11) 2008.08.07
ExtJS 2.1 릴리즈  (4) 2008.04.22
Extjs Qtips 사용하기  (2) 2008.04.16
Extjs 기본레이아웃에 각종 패널붙이기  (12) 2008.04.16
ExtJS를 이용한 EditorGrid 붙이기  (2) 2008.04.07
Post by 넥스트리소프트 데꾸벅(techbug)
, |

HTML작업을 하다 보면 Tooltip을 사용할 일이 많아진다.
예를 들어

<img src="이미지 주소" alt="이미지설명"  title="이미지설명" />

<a href="" title="링크설명">

<table summary="테이블에 대한 간략한 소개">

 와 같이 사용하는데 사용자 마음대로 원하는 형태로 나오기가 어렵다는 단점이 있다. 또한 일정시간이 지나면 자동으로 없어지므로 사용하기가 까다롭다.

단 이미지가 없을 경우 엑박(엑스박스)을 없애기 이미지에 대한 내용이 들어가게 해주는 기능이 있으나 이럴경우 ExtJS를 사용하여 다음과 같이 작성해 줄수 있다.

 MARKUP

<div id="아이디" ext:qtitle="툴팀 타이틀" ext:qtip="데꾸벅<br />머리아포">

 JAVAScript (ExtJS)

Ext.QuickTips.init();
Ext.QuickTips.getQuickTip();

 과 같이 적어주면 자동으로 Ext.Tooltip() 과 같은 효과를 볼수 있다.


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

ExtJS 2.1 릴리즈  (4) 2008.04.22
RESTFul 한 ExtJS  (0) 2008.04.16
Extjs 기본레이아웃에 각종 패널붙이기  (12) 2008.04.16
ExtJS를 이용한 EditorGrid 붙이기  (2) 2008.04.07
ExtJS 로드맵  (0) 2008.04.01
Post by 넥스트리소프트 데꾸벅(techbug)
, |
사내 위키에 올렸던 글을 다시 정리하여 포스팅하다!
기본 레이아웃 viewport에 각 Region마다 서로다른 패널(panel)을 붙여봅니다.

Basic Concept

각 각의 패널(Panel)에 대하여 알아본다. 기본적인 레이아웃은 이미 앞장에서 설명했던 소스를 이용하여 각각의 Region에 서로 다른 Panel을 붙여본다. 서로다른 패널들이 레이아웃에서 어떻게 붙고(append)되고 자동으로 생성되며(Create) 삭제(remove and destroy)되는지에 대해서 알아본다. 최종적으로 재사용할 컴포넌트모듈을 작성하여 적용하는 방법(Step4)을 소개한다.
각 패널들에 대한 상세한 옵션에 대해서는 장차 설명하기로 하고 여기에서는 기본적인 레이아웃에 패널을 넣는 방식에 대해서 알아본다.

최종소스 :







Step 1.  Basic Layout and BoxComponent

우선 이전에 포스팅했던 기본레이아웃 그리기를 준비한다.

[basicLayout.html]

<html>
<head>
<title>Basic Layout</title>
<link rel="stylesheet" type="text/css" href="../../../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]

// 좌측 패널 (region : west ) 클래스
WestArea
= function() {
 WestArea.superclass.constructor.call(this, {
  region : 'west',
  title : 'WEST',
  collapsible : true,
  collapsed : false,
  width : 300,
  minSize : 100,
  split : true,
  layout : 'fit',
  margins : '5 0 5 5',
  cmargins : '5 5 5 5',
  html : '좌측'
 })
};
Ext.extend(WestArea, Ext.Panel, {});

// 우측 패널 (region: east) 클래스
EastArea
= function() {
 EastArea.superclass.constructor.call(this, {
  region : 'east',
  title : 'EAST',
  collapsible : true,
  collapsed : false,
  width : 300,
  minSize : 100,
  split : true,
  layout : 'fit',
  margins : '5 5 5 0',
  cmargins : '5 5 5 5',
  html : '우측'
 })
};
Ext.extend(EastArea, Ext.Panel, {});

// 중앙 컨텐츠 패널 (region : center) 클래스
CenterArea
= function() {
 CenterArea.superclass.constructor.call(this, {
  region : 'center',
  title : 'CENTER',
  layout : 'fit',
  margins : '5 0 5 0',
  html : '<div id="_CONTENTS_AREA_">컨텐츠 영역입니다.</div>'
 })
};
Ext.extend(CenterArea, Ext.Panel, {});

// 메인 클래스
BasicLayoutClass = function() {
 return {
  init : function() {
   Ext.QuickTips.init();
   this.viewport = new Ext.Viewport( {
    layout : 'border',
    items : [this.WestPanel = new WestArea(),
      this.EastPanel = new EastArea(),
      this.CenterPanel = new CenterArea()]
   });

   this.viewport.doLayout();
   this.viewport.syncSize();
  }
 }
}();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);


 위와 같이 각각의 region마다 별도의 파일로 클래스를 관리한다.

  • WestArea.js
  • EastArea.js
  • CenterArea.js
  • basicLayout.js

사용자 삽입 이미지










Step 2.  Tab Panel - center regionEdit section

중앙컨텐츠 패널(center region : CenterArea.js )에 여러개의 TabPanel을 넣어보자

CenterArea = function(viewport) {
 this.viewport = viewport;
 CenterArea.superclass.constructor.call(this, {
  region : 'center',
  title : false,
  // TabPanel일 경우 Nested된 Panel의 타이틀이 영역을 차지하므로 title은 항상 false가 되어야함.
  margins : '5 0 5 0',
  deferredRender : false, // CotainerLayout이 로드될때 defer없이 렌더링한다.
  tabPosition : 'top', // Tab이 패널의 위쪽에 위치하게 한다. ( 'top'/'bottom'
  // 두가지를 지원하며 scrolling TabPanel은 'top'만
  // 지원한다.)
  activeTab : 0, // 처음 로드(렌더링)될때 첫번째(tab index : 0 ) 탭이 activate되게 한다.
  // 기본 Panel을 사용하거나 다른Panel이 Nested 될수 있다.
  items : [this.FirstPanel = new Ext.Panel( {
   id : 'CENTER_TAB_01',
   title : '첫번째 탭',
   autoScroll : true,
   html : "첫번째"
  }), this.SecondPanel = new Ext.Panel( {
   id : 'CENTER_TAB_02',
   title : '두번째 탭',
   autoScroll : true,
   html : "두번째"

  }), this.ThirdPanel = new Ext.Panel( {
   id : 'CENTER_TAB_03',
   title : '세번째 탭',
   autoScroll : true,
   closable : true,
   html : "세번째"
  })]
 })
  // activate - 탭이 활성화 될때 일어나는(fire)되는 이벤트
 this.SecondPanel.on('activate', function(panel) {
  alert(panel.title + '이 활성화 되었습니다.' + panel.getId());
 }, this);

 // deactivate - 탭이 비활성화될때 일어나는 이벤트
 this.SecondPanel.on('deactivate', function(panel) {
  alert(panel.title + '이 비활성화 되었습니다.' + panel.getId());
 }, this);

 // beforedestory - 탭이 닫히기전에 일어나는 이벤트
 this.ThirdPanel.on('beforedestroy', function(panel) {
  alert(panel.title + '이 닫혔습니다.');
 }, this);
};
Ext.extend(CenterArea, Ext.TabPanel, {});

탭패널의 경우 한 화면에 여러개의 패널(Panel)이 필요할 경우 유용하며 연관된 다를 데이타를 사용할때 상당히 유용하다. 기본적인 event는 위에서는 3가지만 열거했지만 위 3가지 만으로도 충분히 다양한 효과를 낼수 있을 것이다. 또한 각 탭 마다 다양한 패널을 추가할수 있으며 기본 ContainerLayout도 추가할수 있으므로 여러가지형태의 레이아웃을 잡을때 상당히 유용하다.
예를 들어 이전장에서 기본레이아웃에 그리드를 추가하는 방법을 배웠는데 첫번째 탭패널에 그리드 레이아웃을 넣어 해당 레이아웃을 테스트 해보기 바란다. 또한 각 탭이 activate될때 그리드의 데이타스토어를 다시 로드할수 있도록 이벤트를 걸어주거나 다른 액션을 취할수 있다.

참고: Ext.TabPanel은 Ext.layout.CardLayout을 사용하므로 deferredRender의 경우 Ext.layout.CardLayout의 deferredRender를 참조한다.

사용자 삽입 이미지



위의 소스중 탭패널이 바로 borderLayout에 속한(region:center)일 경우에는 title 이 나타나지 않는데 기본 Layout에 Nested된 TabPanel일 경우는 기본 Layout의 region에 title을 넣으면 된다. 실제로 작업을 하다보면 위의 방법보다 아래방법을 많이 사용하게 된다. 탭패널안의 탭패널이 들어갈 경우도 발생할수 있으므로 Nested된 패널로 기본 cotainerLayout을 잡은후 넣는 방법이 좋다.

CenterArea = function(viewport) {
 this.viewport = viewport;
 CenterArea.superclass.constructor.call(this, {
  region : 'center',
  title : 'CENTER',
  layout : 'fit',
  margins : '5 0 5 0',
  items : [ // Nested된 TabPanel
   new Ext.TabPanel( {
    deferredRender : false,
    tabPosition : 'bottom',
    activeTab : 0,
    border : false,
    enableTabScroll : true,
    items : [this.FirstPanel = new Ext.Panel( {
     id : 'CENTER_TAB_01',
     title : '첫번째 탭',
     autoScroll : true,
     html : "첫번째"
    }), this.SecondPanel = new Ext.Panel( {
     id : 'CENTER_TAB_02',
     title : '두번째 탭',
     autoScroll : true,
     html : "두번째"

    }), this.ThirdPanel = new Ext.Panel( {
     id : 'CENTER_TAB_03',
     title : '세번째 탭',
     autoScroll : true,
     closable : true,
     html : "세번째"
    })]
   })]
  })
};
Ext.extend(CenterArea, Ext.Panel, {}); //기본 ContainerLayout

  위 소스를 응용하면 탭안의 탭패널, 그안의 탭패널을 넣는 방식으로 계단식 탭패널이 가능하다.

사용자 삽입 이미지








Step 3. Accordian Panel and Tree Panel - west regionEdit section

좌측패널(WestArea.js, west region)에 Accordian Panel을 붙여본다. 각각의 패널에는 tree Panel 및 여러 Panel이 올수 있다.

WestArea = function(viewport){
 this.viewport = viewport;
 WestArea.superclass.constructor.call(this,{
  region : 'west',
  title : 'WEST',
  collapsible : true,
  collapsed:false,
  width : 300,
  minSize:100,
  split : true,
  margins:'5 0 5 5',
  cmargins : '5 5 5 5',
  // Accordion Layout을 사용
  layout:'accordion',
  // Accordion Layout의 config option
  layoutConfig:{
   // 각각의 패널 처음 로드시 collapse먼저 하겠는지 확인
   collapseFirst:false,    
   // 패널 로드시 animated되게 할것인지
   animate:true,      
   // 타이틀을 눌렀을때 collapse할것인지
   titleCollapse: true,    
   // 각각의 패널중 activate될때 맨 처음으로 오게 할것인지
   activeOnTop: false,    
   // 패널의 내용에 상관없이 패널의 높이를 owerContainer의 높이와 맞출것인지 처리
   fill:true      
  },
  items: [
   this.FirstPanel = new Ext.Panel({
    title:'첫번째',
    border:false,
    html:'<p>트리가 들어갈 곳입니다.</p>'
   }),
   this.SecondPanel = new Ext.Panel({
    title:'두번째',
    border:false,
    html:'<p>두번째 패널의 내용이 들어갑니다.</p>'
   })
  ]
 })
};
Ext.extend(WestArea, Ext.Panel,{ });

accordion Layout으로 바꿨으니 이제 각각의 패널에 알맞은(?) 패널들을 넣어보자. 우선 첫번째 패널에 TreePanel을 넣어보자

WestArea = function(viewport) {
 this.viewport = viewport;
 WestArea.superclass.constructor.call(this, {
  region : 'west',
  title : 'WEST',
  collapsible : true,
  collapsed : false,
  width : 300,
  minSize : 100,
  split : true,
  margins : '5 0 5 5',
  cmargins : '5 5 5 5',
  layout : 'accordion',
  layoutConfig : {
   collapseFirst : false,
   animate : true,
   titleCollapse : true,
   activeOnTop : false,
   fill : true
  },
  items : [this.FirstPanel = new Ext.tree.TreePanel( {
   title : '첫번째',
   border : false,
   layout : 'fit',
   loader : new Ext.tree.TreeLoader( {
    dataUrl : 'menu.js',
    baseParams : {}
   }),
   rootVisible : true,
   lines : true,
   autoScroll : true,
   root : new Ext.tree.AsyncTreeNode( {
    id : '_MENU_PANEL_',
    text : '트리메뉴의 Root',
    draggable : false,
    expanded : true
   })
  }), this.SecondPanel = new Ext.Panel( {
   title : '두번째',
   border : false,
   html : '<p>두번째 패널의 내용이 들어갑니다.</p>'
  })]
 })
};
Ext.extend(WestArea, Ext.Panel, {});

사용자 삽입 이미지







Step 4. Common Modules

이미 Step1,2,3 에서 접해봐서 알겠지만 각각의 패널에는 기본 ContainerLayout만 있으면 어떤 패널이던지 가져다 붙일수 있다는것을 알았을 것이다.
그럼 각각의 공통적으로 사용되는 패널의 경우 공통모듈화 하여 사용자가 정의한 모듈로써 사용하게 된다면 어느 패널에서든지 마음대로 붙였다 떼었다 할수 있을 것이다.


[basicLayout.html]

<html>
<head>
<title>Basic Layout</title>
<link rel="stylesheet" type="text/css" href="../../../resources/css/ext-all.css" />
<script type="text/javascript" src="../../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../../adapter/ext/localXHR.js"></script>
<script type="text/javascript" src="../../../ext-all.js"></script>
<script type="text/javascript" src="Modules.js"></script>
<script type="text/javascript" src="WestArea.js"></script>
<script type="text/javascript" src="EaseArea.js"></script>
<script type="text/javascript" src="CenterArea.js"></script>
<script type="text/javascript" src="basicLayout.js"></script>
</head>
<body id="basicLayoutBody"></body>
</html>

[Modules.js]

Ext.namespace("Ext.techbug");

// 공통으로 사용되는 트리메뉴
Ext.techbug.TreeMenu = function(config) {
 Ext.apply(this, config);
 Ext.techbug.TreeMenu.superclass.constructor.call(this, {
  loader : new Ext.tree.TreeLoader( {
   dataUrl : 'menu.js',
   baseParams : {}
  }),
  rootVisible : true,
  lines : true,
  autoScroll : true,
  root : new Ext.tree.AsyncTreeNode( {
   id : '_MENU_PANEL_',
   text : '트리메뉴의 Root',
   draggable : false,
   expanded : true
  })
 })
};
Ext.extend(Ext.techbug.TreeMenu, Ext.tree.TreePanel, {});

[WestArea.js]

WestArea = function(viewport) {
 this.viewport = viewport;
 WestArea.superclass.constructor.call(this, {
  region : 'west',
  title : 'WEST',
  collapsible : true,
  collapsed : false,
  width : 300,
  minSize : 100,
  split : true,
  margins : '5 0 5 5',
  cmargins : '5 5 5 5',
  layout : 'accordion',
  layoutConfig : {
   collapseFirst : false,
   animate : true,
   titleCollapse : true,
   activeOnTop : false,
   fill : true
  },
  items : [
  // 공통모듈로 사용하는 트리메뉴로 변경
    this.FirstPanel = new Ext.techbug.TreeMenu( {
     title : '첫번째',
     border : false,
     layout : 'fit'
    }),
this.SecondPanel = new Ext.Panel( {
     title : '두번째',
     border : false,
     html : '<p>두번째 패널의 내용이 들어갑니다.</p>'
    })]
 })
};
Ext.extend(WestArea, Ext.Panel, {});







 

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

RESTFul 한 ExtJS  (0) 2008.04.16
Extjs Qtips 사용하기  (2) 2008.04.16
ExtJS를 이용한 EditorGrid 붙이기  (2) 2008.04.07
ExtJS 로드맵  (0) 2008.04.01
ExtJS를 이용한 Password Meter  (0) 2008.04.01
Post by 넥스트리소프트 데꾸벅(techbug)
, |
사내(넥스트리)위키에 올렸던 자료를 다시 정리하여 포스팅한다.

일반그리드와 달리 에디터그리드의 경우 해당 Cell에 해당하는 값을 변경할수 있다. 각각의 선택된 셀값과 추가하는 방법 및 해당 값들을 셋팅하는 방법, ActionComplete후 dirtyFlag를 삭제하는 방법등에 대해서 알아보자! 에디터 그리드는 이미 이전장에서 사용되었던 기본그리드를 사용하였으므로 반드시 이전장을 참조하도록 한다. 추가적으로 FormPanel에 대하여 미리 알아 보도록 한다.

최종소스 :

사용자 삽입 이미지


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>
</body>
</html>

basicGrid.js

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'
                }, this.myRecordObj = Ext.data.Record.create([ {
                    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.EditorGridPanel( {
                store : this.store,
                columns : [ {
                    id : 'company',
                    header : "Company",
                    width : 160,
                    sortable : true,
                    dataIndex : 'company',
                    editor : new Ext.form.TextField( {
                        allowBlank : true
                    })
                }, {
                    header : "Price",
                    width : 75,
                    sortable : true,
                    renderer : 'usMoney',
                    dataIndex : 'price',
                    editor : new Ext.form.NumberField( {
                        allowBlank : false,
                        allowNegative : false
                    })
                }, {
                    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',
                    editor : new Ext.form.DateField( {
                        allowBlank : false,
                        minValue : '08/21/00',
                        disabledDays : [0, 2, 6],
                        disabledDaysText : '이날짜는 선택안되요'
                    })
                }

                ],
                stripeRows : true,
                autoExpandColumn : 'company',
                loadMask : {
                    msg : '데이타 로드중'
                },
                clicksToEdit : 1, // Edit하기 위해 Cell을 클릭해야 하는 횟수 (최대2, 최소1)
                sm : new Ext.grid.RowSelectionModel( {
                    singleSelect : true
                }),
                view : new Ext.grid.GridView( {
                    forceFit : true,
                    enableRowBody : true,
                    emptyText : 'No Record found'
                }),
                height : 350,
                width : 1024,
                title : '기본 그리드',
                tbar : [
                        {
                            text : '저장',
                            scope : this,
                            handler : this.saveAllChanges
                        },
                        '-',
                        {
                            text : '추가',
                            scope : this,
                            handler : this.addRecord
                        },
                        '-',
                        {
                            text : '삭제',
                            scope : this,
                            handler : function() {
                                var selectedKeys = this.grid.selModel.selections.keys;
                                if (selectedKeys.length > 0) {
                                    Ext.MessageBox.confirm('확인',
                                            '정말 삭제할래요? 삭제하면 복구안돼요..',
                                            this.deleteRecord, this);
                                } else {
                                    alert('삭제할 Row를 선택하세요');
                                }

                            }
                        }, '-', {
                            text : '새로고침',
                            scope : this,
                            handler : function() {
                                this.store.reload()
                            }
                        }]
            });

            this.grid.render('DIV_GRID_HERE');
            this.store.load();
            this.grid.on('afteredit', this.afterCellEditEventHandler, this); // Cell을
            // 변경후
            // 일어나는
            // 이벤트
        },

        // CEll변경후 일어나는 이벤트
        afterCellEditEventHandler : function(editEvent) { // Cell을 변경후 일어나는
            // 이벤트
            var gridField = editEvent.field;
            var gridValue = editEvent.value;
            editEvent.record.set('company', editEvent.record.get('company')
                    + '변경필트:' + gridField + '변경된값' + gridValue);
            this.updateCell(editEvent);
        },

        // 삭제하는 루틴
        deleteRecord : function(btn) {
            if (btn == 'yes') {
                var selectedRows = this.grid.selModel.selections.items;
                var selectedKeys = this.grid.selModel.selections.keys;
                var selectedRecord = this.grid.selModel.getSelected();
                alert(selectedRecord.get('company') + '\n' + selectedRows
                        + '\n' + selectedKeys + '\n지우는 Ajax Call 후에 다시 리로드한다.');
                this.store.reload();
            }
        },

        // 새로운 Row를 추가하기
        addRecord : function(btn) {
            var r = new this.myRecordObj( {
                // this.myRecordObj는
                // this.store.reader의 Record Field를
                // 말함.
                company : '새 회사명',
                price : 0.00,
                change : 0.00,
                pctChange : 0.00,
                lastChange : new Date(),
                newRecord : 'yes', // Update할때 트리거로 이용하기 위해서
                id : 0
            });
            this.grid.stopEditing();
            this.store.insert(0, r);
            this.grid.startEditing(0, 0); // 추가할 rowIndex, colIndex 위치
        },

        // 수정된 Cell만 저장하기
        updateCell : function(editEvent) {
            var isNewRecord = editEvent.record.data.newRecord;
            // 업데이트할때 사용될
            // 트리거('yes'면 새로운 레코드)
            /** ** AJAX 통신 모듈 들어가는곳 ***** */

            // DB를 성공적으로 업데이트 후 처리할 루틴
            if (isNewRecord == 'yes') {
                // 저장한후 새로운 레코드가 아님을 표시
                editEvent.record.set('newRecord', 'no');
                // isDirty Flag 없애기
                this.store.commitChanges();
            } else {
                this.store.commitChanges();
            }

            // 잘못됐을경우는 this.sotre.rejectChanges(); 로 리복한다.
            this.store.rejectChanges();

        },

        //변경된것들 모두 저장하기
        saveAllChanges : function() {
            var modifyRecords = this.store.getModifiedRecords(); //수정된 레코드 모두 찾기
            var modifyLen = modifyRecords.length;
            if (modifyLen > 0) {
                for (var i = 0;i < modifyLen; i++) {
                    /* 각 Row마다 AJAX Call을 하거나 배열로 모두 넘기는 Ajax Call Method가 들어간다.*/
                    var modifyCompany = modifyRecords[i].get('company')
                }
            }

        }

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






 






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

Extjs Qtips 사용하기  (2) 2008.04.16
Extjs 기본레이아웃에 각종 패널붙이기  (12) 2008.04.16
ExtJS 로드맵  (0) 2008.04.01
ExtJS를 이용한 Password Meter  (0) 2008.04.01
Ext JS Ext.ux.YoutubePlayer  (0) 2008.03.29
Post by 넥스트리소프트 데꾸벅(techbug)
, |

ExtJS 로드맵

Scripter/EXTJS / 2008. 4. 1. 22:58
매일 프로젝트를 수행하면서 Ext의 새로운 버전이 나올때마다 현재 작업버전과 호환이 될까 노심초사하면서 테스팅하던 때가 있었는데 이번에 Extjs의 Roadmap이 공개가 되었다.

올 겨울에 나올 Ext3.0은 chart기능과 Comet기능까지 추가 된다니 기대가 되긴하는데 프레임웍 자체가 너무 방대해지는건 아닐까 걱정해 본다... (스크립트언어인데..방대해지면.. 문제가... ㅡ.,ㅡ;)


ExtJS의 로드맵 --------------------------------------------
2008년의 목표는 2.x 버전의 새로운 컴포넌트 추가와 현재 몇몇 기능을 향상하는 선이다.
앞으로 3.0에서 보는봐와 같이  챠트와, Comet(실시간 통신-Ajax는 Reqeust에 의한 Response방식인데 비해 Coment은 일반 어플리케이션처럼 릴레이션을 물고 있다 :데꾸벅 주) 을 추가하여 큰그림을 그리고 있다.... 이하 생략...원문참조


2.1 (2008 봄)

  • RESTFul 지원 (그리 RESTFul 하지 않던데.. ㅡ.,ㅡ;)
  • 원격컴포넌트 로딩
  • 전통적인 비Ajax적인 Submit 지원클래스
  • 다른 JS라이브러리 Adapter 업그레이드
  • AIR 플랫폼 지원추가
  • 그리드 필터링
  • 체크박스/라이오 그룹
  • 상태바

2.2 (2008 여름)

  • Ext.Ajax 기능향상
  • Ext컴포넌트만의 초점을 맞춘(scoped) reset style지원
  • XML을 포함한 추가적인 TreeLoader 데이타로딩
  • 슬라이더 컴포넌트
  • 파일업로드 필드

3.0 (2008 겨울/2009 초)

  • Ext 이벤트 등록모델 업데이트
  • 플래쉬 챠트 API ( Gwt-Ext에는 구글 챠트를 이용하던데..)
  • Comet/Bayeux 지원
  • 통합된 client-server 데이타 바인딩/marshaling for Ext components
  • Browser history support (흠.. window.event.hash값을 이용한걸까? 기대되네..)
  • ListView
  • Section 508(장애자 지원정책:웹접근성) 기능향상


여튼 3.0이 기대되는건 사실이자나...  ^^ 



Post by 넥스트리소프트 데꾸벅(techbug)
, |
PHP스쿨의 질문내용중에 패스워드 미터에 대해 물어보던 내용이 있어서 일전에 꼬불쳐 두었던 URL을 정리하여 올린다.
사용자 삽입 이미지


아래 소스는 ExtJS의 User extension Plugin 이다


[HTML]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Ext.ux.PasswordMeter</title>
<link rel="stylesheet" type="text/css" href="../ext-1.1.1/resources/css/ext-all.css" />

<script type="text/javascript" src="../ext-1.1.1/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../ext-1.1.1/ext-all.js"></script>
		<script type="text/javascript" src="Ext.ux.PasswordMeter.js"></script>
<script type="text/javascript" src="passwordmeter.js"></script>
<link rel="stylesheet" type="text/css" href="passwordmeter.css" />

<!-- Common Styles for the examples -->
<link rel="stylesheet" type="text/css" href="../ext-1.1.1/examples/examples.css" />
</head>
<body>


<div style="width:300px;">
<div class="x-box-tl"><div class="x-box-tr"><div class="x-box-tc"></div></div></div>
    <div class="x-box-ml"><div class="x-box-mr"><div class="x-box-mc">
<h3 style="margin-bottom:5px;">Simple Form</h3>
<div id="form-ct">

</div>
</div></div></div>
<div class="x-box-bl"><div class="x-box-br"><div class="x-box-bc"></div></div></div>
</div>
<div class="x-form-clear"></div>
</body>
</html>





[Javascript]

Ext.onReady(function(){

Ext.QuickTips.init();

// turn on validation errors beside the field globally
Ext.form.Field.prototype.msgTarget = 'side';

/*
* ================ Simple form =======================
*/
var simple = new Ext.form.Form({
labelWidth: 75, // label settings here cascade unless overridden
url:'save-form.php'
});
simple.add(
new Ext.form.TextField({
fieldLabel: 'First Name',
name: 'first',
width:175,
allowBlank:false
}),

new Ext.form.TextField({
fieldLabel: 'Last Name',
name: 'last',
width:175
}),

new Ext.form.TextField({
fieldLabel: 'Company',
name: 'company',
width:175
}),

new Ext.form.TextField({
fieldLabel: 'Email',
name: 'email',
vtype:'email',
width:175
}),
new Ext.ux.PasswordMeter({
fieldLabel: 'Password',
name: 'password',
width:175
})
);

simple.addButton('Save');
simple.addButton('Cancel');

simple.render('form-ct');
});



[JQuery로 만든 Password Meter ]

<html>
<head>
<title>Jquery</title>
<style type="text/css">
<!--
.password {
font-size : 12px;
border : 1px solid #cc9933;
width : 200px;
font-family : arial, sans-serif;
}
.pstrength-minchar {
font-size : 10px;
}
-->
</style>
</head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.pstrength-min.1.2.js"></script>
<script type="text/javascript">
$(function() {
$('.password').pstrength();
});
</script>
<body>
<INPUT class="password" type=password name="Password">
</body>
<html>



[참고 사이트]
http://justwild.us/examples/password/
http://www.codeandcoffee.com/2007/07/16/how-to-make-a-password-strength-meter-like-google-v20/
http://testcases.pagebakers.com/PasswordMeter/
http://www.passwordmeter.com/
http://ajaxorized.com/?p=14
http://simplythebest.net/scripts/ajax/ajax_password_strength.html
http://phiras.wordpress.com/2007/04/08/password-strength-meter-a-jquery-plugin/








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

ExtJS를 이용한 EditorGrid 붙이기  (2) 2008.04.07
ExtJS 로드맵  (0) 2008.04.01
Ext JS Ext.ux.YoutubePlayer  (0) 2008.03.29
Extjs를 이용한 간단한 form submit  (0) 2008.03.14
Extjs를 이용한 ExtPHP, PHP-Ext 프로젝트 오픈  (0) 2008.03.07
Post by 넥스트리소프트 데꾸벅(techbug)
, |
YouTube API 와 Extjs를 이용한 Youtube player extension이 공개되었다.

데모페이지 : http://www.siteartwork.de/youtubeplayer_demo/
공식페이지 : http://www.siteartwork.de/youtubeplayer/
사용자 삽입 이미지

일전에 LiveGrid를 공개했던 Thorsten Suckow-Homberg가 공개한 YouTube Player는 개발자들이 쉽게 Youtube API를 이용하여 Ext application에 embed하도록 하였다.
간단한 기능으로는
  • 버퍼링 상태
  • 플레이백 슬라이더
  • 음소거
  • 볼륨컨트롤
등 기존 미디어플레이어의 기능을 많이 내포하고 있다.
당연히 오픈소스이므로 꽁짜다.. ^^

다운로드 :


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

ExtJS 로드맵  (0) 2008.04.01
ExtJS를 이용한 Password Meter  (0) 2008.04.01
Extjs를 이용한 간단한 form submit  (0) 2008.03.14
Extjs를 이용한 ExtPHP, PHP-Ext 프로젝트 오픈  (0) 2008.03.07
Extjs Grouping Header Grid Plugins  (13) 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)
, |

Gwt-Ext
, .Net-Ext에 이어 PHP-Ext 프로젝트가 오픈했다.
처음 데꾸벅이 Rails를 공부할때 봤던 Extjs가 이제는 많은 개발자들의 관심의 대상이 된것은사용자들에게 익숙한 윈도우즈시스템과 같은 화려한 UI 덕분이 아닐까 싶다.
프로젝트를 오픈한 Sergei Wlater Guerra는 PHP-Ext를 "PHP4, PHP5로  막강한 UI Layer로 쓰여진 오픈소스 위젯 라이브러리"라고 정의했다.

아직은 0.8.1이지만 좀더 나은 발전을 기원하며... 후훗!!


PHP 소스

echo Ext::onReady(
        Javascript::stm(ExtQuickTips::init()),
        Javascript::assign("data",Javascript::valueToJavascript($myData)),
        //Javascript::valueToJavascript($myData),
        $store->getJavascript(false, "ds"),
        $italicRenderer,
        $changeRenderer,
        $pctChangeRenderer,
        $colModel->getJavascript(false, "colModel"),
        $gridForm->getJavascript(false, "gridForm")
);


$gridForm = new ExtFormPanel("company-form");
$gridForm->Frame = true;
$gridForm->LabelAlign = EXT_FORM_LABEL_ALIGN_LEFT;
$gridForm->Title = "Company Data";
$gridForm->BodyStyle = "padding: 5px;";
$gridForm->Width = 750;
$gridForm->Layout = EXT_CONTAINER_LAYOUTS_COLUMN;






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

Ext JS Ext.ux.YoutubePlayer  (0) 2008.03.29
Extjs를 이용한 간단한 form submit  (0) 2008.03.14
Extjs Grouping Header Grid Plugins  (13) 2008.03.06
Extjs 크로스 도메인 관련  (0) 2008.02.28
Extjs 한국어로 나타내기 (locale설정)  (9) 2008.02.27
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)
, |
서버없이 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)
, |
한국어 로케일 설정파일을 수정할 일이 생겨 뜯어보다 보니
이번 2.0.2 버전에 추가된 부분은 빠져있는 것을 발견했다.
extjs의 locale 담당 개발자에게 메일을 보내 다음 패키징때 추가적으로 넣어달라고 부탁했는데 다음 패키징땐 소스에 데꾸벅이란 닉을 발견할수  있을까? 크크크

사용자 삽입 이미지













위와 같이 사용하려면 HTML에
    <script type="text/javascript" src="../../ext-all.js"></script>
    <script type="text/javascript" src="../../source/locale/ext-lang-ko.js" charset="utf-8"></script>
와 같이 넣으면 된다.


Post by 넥스트리소프트 데꾸벅(techbug)
, |
ExtJS에서 RESTful하게 HTTP status를 관리하게 생겼다. ㅠ.,ㅠ;
API 및 ExtJS의 코어을 뜯어본 결과 이넘들은 xhr.status >=200 && xhr.status <300 인경우만 처리하고 나머지는 모두 xhr.status 를 13030 으로 주고 있었다.

ext-base.js 에 보면 아래와 같이 처리 ㅠ.,ㅠ;

{
            if (!J) {
                this.releaseObject(I);
                return
            }
            var G,
            F;
            try {
                if (I.conn.status !== undefined && I.conn.status != 0) {
                    G = I.conn.status
                } else {
                    G = 13030
                }
            } catch(H) {
                G = 13030
            }
            if (G >= 200 && G < 300) {
                F = this.createResponseObject(I, J.argument);
                if (J.success) {
                    if (!J.scope) {
                        J.success(F)
                    } else {
                        J.success.apply(J.scope, [F])
                    }
                }
            } else {
                switch (G) {
                case 12002:
                case 12029:
                case 12030:
                case 12031:
                case 12152:
                case 13030:
                    F = this.createExceptionObject(I.tId, J.argument, (E ? E: false));
                    if (J.failure) {
                        if (!J.scope) {
                            J.failure(F)
                        } else {
                            J.failure.apply(J.scope, [F])
                        }
                    }
                    break;
                default:
                    F = this.createResponseObject(I, J.argument);
                    if (J.failure) {
                        if (!J.scope) {
                            J.failure(F)
                        } else {
                            J.failure.apply(J.scope, [F])
                        }
                    }
                }
            }
            this.releaseObject(I);
            F = null
        }



그리고 나서 모두 exception처리를 하고 있었는데

ScriptTagProxy의 경우 loadexception ( Object This, Object o, Object arg, Object e ) 에서 argument형태로 처리되고 있으며
Ext.Ajax의 경우는 requestexception : ( Connection conn, Object response, Object options ) 형태로 처리되고 있다.

기존의 예외처리를 위의 2개 이벤트를 사용해 처리해 줘야 하는데 아래와 같이 처리하면 되겠다..  ....

 Ext.Ajax.on('requestexception', function(c, r, o) {
            debugger;
            if (r.status == 402) {
              
                //alert('Your session has expired');
              
            }
            else{
                  var error = r.responseText;
                 if (!error) error = r.statusText;
                 App.aSendError.createWindow(error);
                
            }
        })


Ext.Ajax.on('beforerequest', function(conn, options) {
console.log(arguments);
} );

Ext.Ajax.on('requestcomplete', function(conn, response, options) {
console.log(arguments);
} );

Ext.Ajax.on('requestexception', function(conn, response, options) {
console.log(arguments);
} );




Ext.Ajax.request({
       url: '/techbug.json?method=saveBranch',
       success: function(response) {
               branchList_store.load();
               branchList.reconfigure(branchList_store, branchList_cm);
            Ext.MessageBox.alert('Status', 'Your changes were saved successfully.');
       },
       failure: function(response) {
               if(response.getResponseHeader.CustomHeader == "585")
                Ext.MessageBox.alert('Status', 'You do not have permission to do this.');
            else
               Ext.MessageBox.alert('Status', 'There was an unknown error saving your changes.<P>Please try again.<P>Detailed Error: ' + response.getResponseHeader.CustomHeaderMessage);
       },
       headers: {
          
       },
       params: {
           ....
       }
})



    Ext.data.HttpProxy.on("loadexception", function(options, response, e) {
        // do stuff for all load failures
    });
    Ext.data.HttpProxy.on("load", function(options, arg) {
        // do stuff for all load successes
    });


 
Ext.Ajax.on('requestexception', function(c, r, o) {
            debugger;
            if (r.status == 402) {
              
                //alert('Your session has expired');
              
            }
            else{
                  var error = r.responseText;
                 if (!error) error = r.statusText;
                 App.aSendError.createWindow(error);
                
            }
        })



// set up the connection to load the initial data
var loadConn = new Ext.data.Connection();
loadConn.request({method: 'POST', timeout: 120000, url: 'get_tasks_for_job.cfm' , params: { client_code: localClient, job: localJob, employee: localEmployee } });
// handle the response
loadConn.on('requestcomplete', function(sender, param) {
    try
    {
        var response = Ext.util.JSON.decode(param.responseText);
    } catch (e) {
        alert('There was a problem loading the tasks for this job.');
        return;
    }
    if (response.success == 'Y') {
        // the init function will run loadData() as needed
        localTaskData = response.data;
        Ext.taskEditApp.main.init();
    } else {
        alert('There was a problem loading the tasks for this job.');
    }
}, { scope: this });
loadConn.on('requestexception', function(conn, response, options) {
    alert('failed!!!');
    for (property in response) {
        alert(property);
        alert(response[property]);
    }       
}, { scope: this }); 

ds.load({params:{start:0, limit:pageCount},
    callback:function(record, options, success){
        if(!success){
            alert('error occure.');
            Ext.get('grid_id').unmask();
        }
    }
});




참고URL :
Post by 넥스트리소프트 데꾸벅(techbug)
, |
이전블로그(http://blog.naver.com/techbug)에 포스팅했던 글을 백업받아 옵니다.
--------[이후]-----------------------


GWT Ext 2.0 이 릴리즈됐다.
2007년 ajax framework and libraries에서 prototype.js, jQuery다음 3위를 차지하더니만 선전하고 있는 extjs 아자...
맞바람을 탄다는 기분은 이런 것이라... 데꾸벅 뽯팅!

관련사이트 :
http://ajaxian.com/archives/gwt-ext-20-released
http://gwt-ext.com/
http://gwt-ext.googlecode.com/files/gwtext-2.0.1.zip

데모 : http://gwt-ext.com/demo/


사용자 삽입 이미지

Post by 넥스트리소프트 데꾸벅(techbug)
, |
ExtJS 관련 IDE를 정리하다. 관련URL : http://ajaxian.com/archives/ext-js-ide-support-roundup
ExtJS 2.0 API탑재 Aptana plug-in download : http://orsox.mocis.at/download.php?view.1

사용자 삽입 이미지




There’s been a lot of talk lately about the different IDEs and the support they offer for the various JavaScript libraries. Ext’s uber-coder, Jack Slocum, has put up a blog entry explaining which IDEs support the Ext JS framework:

The Ext 2.0 API is very extensive and remembering all of the functions, properties or configs available is virtually impossible. The API documentation is very thorough, but it would be nice if IDEs would provide code assist options in JavaScript as they do in other languages such as Java and C#. Luckily, there are some IDEs and plugins available that do just that — and also have direct support for Ext 2.0.

Included in the mix are:

This great news and it shows that IDE vendors are taking JavaScript frameworks seriously. Anything that makes development easier is definitely welcome.



Post by 넥스트리소프트 데꾸벅(techbug)
, |
사용자 삽입 이미지

















Jaroslav Benc는 XML을 이용하여 Java 프로젝트용 ExtJS JSP Taglib 생성기를 만들었다.

아직 완변하진 않지만 그의 사이트에 포스트된 로드맵을 보자면

  • Hibernate integration - HibernateStore component
  • DWR integration etc.
  • Eclipse plugin
  • UX Tags: Ext.ux.*, Ext.portal.*, Ext.feedreader.*, Ext.desktop.*
을 지원한다고 하는데 ExtJS의 config 옵션을 모두 xml 어트리뷰트로 처리가능하며 모든 ID값 (ExtJS에서 Ext.getCmp("id") )으로 처리된


참조사이트 : http://www.exttld.com/index.php

Post by 넥스트리소프트 데꾸벅(techbug)
, |
사용자 삽입 이미지
John Le Drew씨의 Simplicity PHP Application Framework은 규모가 크고 트래픽이 많은 사이트에 적합하다.

이번에 facelift된 그의 사이트는 Ajax UI Library인 extjs를 채택함으로써 좀더 Web2.0 애플리케이션의 면모를 갖추고 있다.
Ajax 인터페이스(extjs)를 이용한 관리자콘솔 및 stub controller, 데이타베이스 모델링까지 포함하고 있다.
소스를 함 뜯어볼 가치는 있는것 같네...




출처 : https://launchpad.net/simplicity/
Post by 넥스트리소프트 데꾸벅(techbug)
, |

Extjs v2.02 릴리즈

Scripter/EXTJS / 2008. 2. 26. 09:29
Adobe AIR를 지원해주는 Extjs 2.02버전이 릴리즈 되었다.

Adobe AIR팀과 ExtJS개발팀의 협업으로 탄생한 2.02버전에서는 AIR API를 완벽지원하고 있다.
오래전 Jack Slocum의 블로그에서 봤던 간단한 태스크관리 샘플을 봤을때의 충격이 채가시지 않은채 발표된 이번 버전에서는 AIR API를 패키징하여 Ext.air 형태로 다음과 같이 배포된다.

- 네이티브 윈도우 관리하기
- AIR beta3에서 소개되었던 데이타베이스 동기화
- 네이티브 드래그엔 드롭과 클립보드 접근
- 사운드 새생
- 시스템 트레이에 AIR 애플리케이션 miniming하기(아래소스 참조)

var win = new Ext.air.NativeWindow({
    id: 'mainWindow',
    instance: window.nativeWindow,
 
    // System tray config
    minimizeToTray: true,
    trayIcon: 'ext-air/resources/icons/extlogo16.png',
    trayTip: 'Simple Tasks',
    trayMenu : [{
        text: 'Open Simple Tasks',
        handler: function(){
            win.activate();
        }
    }, '-', {
        text: 'Exit',
        handler: function(){
            air.NativeApplication.nativeApplication.exit();
        }
    }]
});

트리뷰(treeview) 콤보박스의 리스트트리 샘플
사용자 삽입 이미지

extjs listtree





















Custom Grid Columns
사용자 삽입 이미지



















Switch : 버튼 모음 테두리 (메타포어기능)
사용자 삽입 이미지









Ext V2.02 다운로드 : 다운로드하기(http://extjs.com/download)

데꾸벅 블로그에서 바로 다운로드 받기 :

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

Basic Concept

이번 장은 이전에 작성해놓은 기본레이아웃잡기 및 기본그리드기리기를 이용하여 레이아웃의 좌측영역(west region)에 그리드를 붙이는 작업을 진행한다.
이전장을 충분히 숙지하였다는 가정하에 Grid를 붙여보자

기본소스 :


<그리드 해부도>

사용자 삽입 이미지









Step 1.  왼쪽 기본 패널을 Grid패널로 변경

basicLayout_AttachedGrid.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_AttachedGrid.js"></script>
</head>
<body id="basicLayoutBody"></body>
</html>

 

basicLayout_AttachedGrid.js

// 그리드 패널 클래스 시작
LeftArea = function(viewport){ //왼쪽 패널(LeftArea)을 정의한다.
this.viewport = viewport;

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'
}])
});
// 왼쪽 패널의 config옵션을 정의한다.
LeftArea.superclass.constructor.call(this, {
region: 'west', // 굵게 표시된 부분은 기본레이아웃을 잡는 config옵션이다.
title: 'WEST',
collapsible: true,
collapsed: false,
width: 250,
split: true,
layout: 'fit',
margins: '5 0 5 5',
cmargins: '5 5 5 5',
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,
emptyText: 'No Record found'
})
});
};

//기본레이아웃에서는 Ext.Panel을 상속받았지만
//여기서는 그리드패널이므로 Ext.grid.GridPanel을 상속받음.
Ext.extend(LeftArea, Ext.grid.GridPanel, {
});


// 그리드 패널 클래스 끝



// 메인 레이아웃 클래스 시작
BasicLayoutClass = function(){
return {
init: function(){
Ext.QuickTips.init();
this.viewport = new Ext.Viewport({
layout: 'border',
items: [this.WestPanel = new LeftArea(this), 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();

// 좌측그리드의 데이타 스토어를 화면을 최초 그릴때 데이타를 로드한다.
this.WestPanel.store.load();
}
}
}
();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);
// 메인 레이아웃 클래스 끝

 기본레이아웃에서 달라진 부분은 왼쪽 패널(LeftArea)이  기본패널(Ext.Panel)에서 그리드패널(Ext.grid.GridPanel)로 바뀌었을 뿐이다. 

위소스중 기본패널의 config옵션( 굵게 표현된 부분)에 Grid 옵션이 더 추가되었을 뿐이다. 대신 왼쪽 그리드패널의 데이타스토어를 메인레이아웃에서 호출(this.WestPanel.store.load();)하였다.
Step2에서는 좌측 그리드패널 클래스를 별도의 파일로 추가하여 정리해보자

 

Step 2. 좌측그리드패널 클래스 별도 파일로 관리하기

basicLayout_AttachedGrid.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>
//좌측 그리드 패널클래스 별도 파일로 분리, 반드시 main 레이아웃 클래스보다 먼저 호출
<script type="text/javascript" src="basicLayout_LeftGrid.js"></script>
<script type="text/javascript" src="basicLayout_AttachedGrid.js"></script>
</head>
<body id="basicLayoutBody"></body>
</html>

basicLayout_AttachedGrid.js

BasicLayoutClass = function(){
    return {
        init: function(){
            Ext.QuickTips.init();
            this.viewport = new Ext.Viewport({
                layout: 'border',
                items: [this.WestPanel = new LeftArea(this),
                //좌측 그리드 클래스 호출
                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();
            //좌측 패널의 public method호출
            this.WestPanel.loadGridDataSet();
        }
    }
}
();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);

basicLayout_LeftGrid.js

LeftArea = function(viewport){

    this.viewport = viewport;

    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'
        }])
    });

    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',
        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,
            emptyText: 'No Record found'
        })
    });
};

Ext.extend(LeftArea, Ext.grid.GridPanel, {
    //public Method 정의 부분
    loadGridDataSet: function(){
        this.store.load();
    }


});


위의 Step1과 변경된것이라고는 좌측그리드패널 클래스를 별도 파일로 분리하고 클래스내의 public 메쏘드를 메인레이아웃에서 호출한다는것이다.
그러나 이 러한 구조의 클래스는 항상 메인 레이아웃에서 특정 레이아웃일때만 사용된다 예를 들어  좌측그리드패널클래스(LeftArea)의 config option중에 레이아웃에 해당하는 값들이 이미 들어가 있기 때문에 다른 구조의 레이아웃에서는 이 클래스를 호출하여 사용할수 없다는 단점이 있다.

 

 

 

Step 3. 클래스 리팩토링

basicLayout_LeftGrid.js
LeftArea = function(viewport, config){

    this.viewport = viewport;
    Ext.apply(this, config);

    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'
        }])
    });
    // 레이아웃과 관련된 부분을 제외하고 순수 그리드 config option 부분만 사용
    LeftArea.superclass.constructor.call(this, {
        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,
            emptyText: 'No Record found'
        })
    });
};

Ext.extend(LeftArea, Ext.grid.GridPanel, {
    loadGridDataSet: function(){
        this.store.load();
    }
});



 

basicLayout_AttachedGrid.js

BasicLayoutClass = function(){
return {
init: function(){
Ext.QuickTips.init();
//레이아웃과 관련된 내용을 메인레이아웃 클래스 밖으로 빼냄
this.WestPanel = new LeftArea(this, {
region: 'west',
title: 'WEST',
collapsible: true,
collapsed: false,
width: 650,
split: true,
layout: 'fit',
margins: '5 0 5 5',
cmargins: '5 5 5 5'
}), this.viewport = new Ext.Viewport({
layout: 'border',
items: [this.WestPanel, 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();
this.WestPanel.loadGridDataSet();
}
}
}
();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);

위 와 같이 작성되었을 경우는 별도 파일로 빼낸 BasicLayout_LeftGrid.js 파일은 다른 곳에서도 재사용할수 있다. 그러나 ExtJS Panel특성상 바로 Ext.apply()를 통하여 해당 config option을 LayoutContainer에 적용할수 없다. 위 소스를 실행 시켜 보면  왼쪽 판넬의 collapseMode가  잘못 적용되어 container 속성까지 변경되었다. 이럴 경우에는 NestedLayout Manager를 사용하여 다음과 같이 표현하면 된다.


basicLayout_AttachedGrid.js

BasicLayoutClass = function(){
    return {
        init: function(){
            Ext.QuickTips.init();
            
            this.viewport = new Ext.Viewport({
                layout: 'border',
                items: [{
                    region: 'west', // 일반 Ext.panel로 설정하여 왼쪽
                    title: 'WEST',
                    collapsible: true,
                    collapsed: false,
                    width: 650,
                    split: true,
                    margins: '5 0 5 5',
                    cmargins: '5 5 5 5',
                    layout: 'border',
                    items: [ // 일반 Ext.panel에 Nested된 좌측패널
                        this.WestPanel = new LeftArea(this, {
                        region: 'center',
                        layout: 'fit',
                        border: false
                    })]
                }, 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();
            this.WestPanel.loadGridDataSet();
        }
    }
}
();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);

 

Step 4. 그리드 판넬에 새로고침 메뉴넣기

basicLayout_AttachedGrid.js

BasicLayoutClass = function(){
return {
init: function(){
Ext.QuickTips.init();

this.viewport = new Ext.Viewport({
layout: 'border',
items: [{
region: 'west',
title: 'WEST',
collapsible: true,
collapsed: false,
width: 650,
split: true,
margins: '5 0 5 5',
cmargins: '5 5 5 5',
layout: 'border',
items: [this.WestPanel = new LeftArea(this, {
region: 'center',
layout: 'fit',
border: false,
tbar: [{
text: '새로고침',
scope: this,
handler: function(){
this.WestPanel.store.reload();
}
}]
})]
}, 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();
this.WestPanel.loadGridDataSet();
}
}
}
();
Ext.EventManager.onDocumentReady(BasicLayoutClass.init, BasicLayoutClass, true);

 위의 Toolbar 를 basicLayout_LeftGrid.js를 이용하여 붙일수도 있으나 사용하는곳 마다 툴바가 틀릴수 있으므로 되도록이면 좌측 그리드패널 클래스를 호출하는 메인 클래스에 붙이는것이 재사용성에 좋다. 넥스트리소프트의 경우는 모두 좌측그리드패널 클래스 안에 private로 선언되어 있다. 어느것이 좋을지는 사용하는곳마다 틀리므로 해당 상황을 보면서 붙여보도록 한다.

넥스트리의 경우는 모두 클래스안에 private method로 해당 클래스내에서만 사용하는 메쏘드를 두었으며 필요에 따라 외부클래스에서 호출하는 식으로 되어 있다. 취향에 따라 맞춰 하면 될듯... ^^

 

 

 

Step 5. 선택시 우측컨텐츠 판넬(cente region Basic fit layout)의 body 업데이트하기

basicLayout_AttachedGrid.js

BasicLayoutClass = function(){
return {
init: function(){
Ext.QuickTips.init();

this.viewport = new Ext.Viewport({
layout: 'border',
items: [{
region: 'west',
title: 'WEST',
collapsible: true,
collapsed: false,
width: 650,
split: true,
margins: '5 0 5 5',
cmargins: '5 5 5 5',
layout: 'border',
items: [this.WestPanel = new LeftArea(this, {
region: 'center',
layout: 'fit',
border: false,
tbar: [{
text: '새로고침',
scope: this,
handler: function(){
this.WestPanel.store.reload();
}
}]
})]
}, 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();
this.WestPanel.loadGridDataSet();
//Row선택시 발생하는 이벤트를 this.updateCenter() 메쏘드에서 실행
this.WestPanel.getSelectionModel().on('rowselect', this.updateCenter, this);
},

// 선택된 Row의 데이타를 가져와서 오른쪽 center Region의 Body에 업데이트한다.
updateCenter: function(selectionModel, index, record){
// 선택된 Row의 데이타를 가져온다.
var data_company = record.get('company');
var data_price = record.get('price');
var data_change = record.get('change');
var data_pctChange = record.get('pctChange');
var data_lastChange = record.get('lastChange');
// update할 HTML생성
var updateHTML = "<table cellpadding=2 cellspacing=1 border=1 width='100%'>" +
"<tr><td>Company</td><td>" +
data_company +
"</td></tr>" +
"<tr><td>Price</td><td>" +
data_price +
"</td></tr>" +
"<tr><td>Change</td><td>" +
data_change +
"</td></tr>" +
"<tr><td>Percentage</td><td>" +
data_pctChange +
"</td></tr>" +
"<tr><td>Last</td><td>" +
data_lastChange +
"</td></tr>" +
"</table>";

Ext.get("_CONTENTS_AREA_").update(updateHTML); //update
},



//다른 방법으로 선택된 데이타 모두 가져오기
updateCenter1: function(selectionModel, index, record){
var updateHTML = "<table cellpadding=2 cellspacing=1 border=1 width='100%'>"
// 선택된 데이타의 컬럼을 모두 가져오기
var cmc = this.WestPanel.getColumnModel().getColumnCount();
for (var i = 0; i < cmc; i++) {
//선택된 데이타의 컬럼Head (그리드 헤더 가져오기)
var columnName = this.WestPanel.getColumnModel().getColumnHeader(i);
//그리드 헤더의 데이타 인덱스 코드 가져오기
var columnCode = this.WestPanel.getColumnModel().getDataIndex(i);
var updateHTML = updateHTML + "<tr><td>" + columnName + "</td><td>" + record.get(columnCode);
+"</td></tr>" //데이타 가져와서 업데이트할 HTML 셋팅하기
}
var updateHTML = updateHTML + "</table>";
Ext.get("_CONTENTS_AREA_").update(updateHTML);

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

위 의 소스에서 updateCenter() 메쏘드는 this.WestPanel의 데이타 및 데이타인덱스 코드값을 모두 알고 있을 경우(대부분 소스까보면 다 나옴.. ㅡ.,ㅡ;) 사용하기 편리하고 아래의 updateCenter1() 메쏘드의 경우는 데이타 인덱스값을 모르거나 동적으로 필드값들이 셋팅 되는 경우에 상당히 유용하게 사용할수 있다. 넥스트리에서는 용어사전에 등록된 용어필드들을 자동으로 가져올때 사용되었던 소스이다.

 

 

Post by 넥스트리소프트 데꾸벅(techbug)
, |
eclipse로 extjs2.0 버전을 사용하고 싶으면 다음과 같이 하면된다.

extjs2.0 버전 플러그인 다운로드 : http://support.aptana.com/asap/browse/STU-547

사용자 삽입 이미지

extjs2.01 최신버전은 아래에서 다운로드 받으세요!
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)
, |