[extjs] 스토어

역할

모델을 감쌈

데이터 로딩 및 저장하는 프록시 설정

정렬, 필터링, 그룹핑의 추가 기능

리더 클래스(Reader Class)

서버로부터 모델 인스턴스나 스토어를 로딩할 때 원본 데이터의 디코딩을 담당

[extjs] 프록시

역할

데이터의 로딩 및 저장

클라이언트 프록시

로컬 저장소에 사용. 브라우저를 종료하고 다시 열었을 때도 값이 저장되어 있음.

html5의 새로운 기능. 일부 브라우저만 지원

  • IE 8.0 이상
  • FF 3.5 이상
  • Safari 4.0 이상
  • Chrome 4.0 이상
  • Opera 10.5 이상
  • iPhone 2.0 이상
  • Android 2.0 이상

인스턴스 생성이 가능한 클라이언트 프록시

  • Ext.data.proxy.LocalStorage
  • Ext.data.proxy.SessionStorage
  • Ext.data.proxy.Memory

로컬 스토리지 프록시

//로컬 스토리지 정의
Ext.define('UserPreference', {
 extend: 'Ext.data.Model',
 fields: [
  {name: 'id', type: 'int'},
  {name: 'description', type: 'string'}
 ],
 proxy: {
  type: 'localstorage',
  id: 'userpreference'
 }
}); 

//생성
var store = Ext.create('Ext.data.Store', {
 model: 'UserPreference'
});

//로드 및 데이터 저장
store.load();
store.add({description: 'Blue theme'});
store.add({description: 'Loiane Groner'});
store.sync();

//모델의 데이터 직접 관리 가능
var userPref = Ext.ModelManager.create({
 description: 'Favorite JS Framework: extjs'
}, 'UserPreference');
userPref.save();

//브라우저를 닫고 다시 열었을 때 데이터 로드
store.load(functin(records, operation, success){
 var userPref, i;
 for(i=0; i<records.length; i++) {
  userPref = records[i].data;
  console.log(userPref.id + " " + userPref.description);
 }
});

세션 스토리지 프록시

Ext.define('UserPreference', {
 extend: 'Ext.data.Model',
 fields: [
  {name: 'id', type: 'int'},
  {name: 'description', type: 'string'}
 ],
 proxy: {
  type: 'sessionstorage',
  id: 'userpreference'
 }
});

var store = Ext.create('Ext.data.Store', {
 model: 'UserPrefeence'
});

store.load();
store.add({description: 'Blue thene'});
store.add({description: 'Loiane Groner'});
store.sync();

메모리 프록시

안라인 데이터 로딩할 때 주로 사용하는 헬퍼 프록시. 페이지가 초기화될 때 포함된 모든 내용을 잃어버리므로, 임시 페이지에 유용.

Ext.define({
 extend: 'Ext.data.Model',
 fields: [
  {name: 'id', type: 'int'},
  {name: 'name', type: 'string'}
 ],
});

var data= {
 genders: [{
  id: 1,
  name: 'Femail'
 }, {
  id: 2,
  name: 'Male'
 }, {
  id: 3,
  name: 'Unknown'
 }]
};

var store = Ext.create('Ext.data.Store', {
 authoLoad: true,
 model: 'Gender',
 data: data,
 proxy: {
  type: 'memory',
  reader: {
   type: 'json', root: 'genders'
  }
 },
});

//데이터 스토어를 사용하는 콤보박스 생성
var comboBox = Ext.create('Ext.form.field.ComboBox', {
 fieldLabel: 'Gender',
 renderTo: 'genderCombo',
 displayField: 'name',
 width: 200,
 labelWidth: 50,
 store: store,
 queryMode: 'local',
 typeAhead: false
});

서버 프록시

웹 서버로부터 http 요청으로 데이터를 로딩하거나 저장할 때 사용.

인스턴스 생성이 가능한 서버 프록시

  • Ext.data.proxy.Ajax
  • Ext.data.proxy.Rest
  • Ext.data.proxy.JsonP
  • Ext.data.proxy.Direct

Ajax 프록시

//모델 설정
Ext.define('Book', {
 extend: 'Ext.data.Model',
 fields: [
  {name: 'id', type: 'int'},
  {name: 'title', type: 'string'},
  {name: 'pages', type: 'int'}
 ],
 proxy: {
  type: 'ajax',
  url: 'data/books.json'
 }
});

//동일한 다른 코드
var ajaxProxy = Ext.create('Ext.data.proxy.Ajax', {
 url:'data/book.json',
 model: 'Book',
 render: 'json'
});
Ext.define('Book', {
 extend: 'Ext.data.Model',
 fields: [
  {name: 'id', type: 'int'},
  {name: 'title', type: 'string'},
  {name: 'pages', type: 'int'}
 ],
 proxy: ajaxProxy
});

//스토어 생성, 데이터 로딩 후 리스트 출력
var store = Ext.create('Ext.data.Store', {
 model: 'Book'
});

store.load(function(records){
 var book, i;
 for(i=0; i<records.length; i++){
  book = records[i].data;
  console.log(book.id+" "+book.title);
 }
});

파라미터 옵션

  • filterParam : 필터링용 파라미터 이름. 기본값은 filter
  • groupParam : 그룹핑용 파라미터 이름. 기본값은 group
  • pageParam : 페이지 선택용 파라미터 이름. 기본값은 page
  • startParam : 페이징용 시작 파라미터 이름. 기본값은 start
  • limitParam : 페이징용 제한 파라미터 이름. 기본값은 limit
  • sortParam : 정렬용 파라미터 이름. 기본값은 sort
  • extraParams : 서버에 보내는 모든 요청에 보낼 파라미터 이름들. 이름이 중복될 경우 오버라이드.

Operation(Ext.data.Operation)

프록시의 읽기 또는 쓰기 동작 때 실행되는 클래스.

operation 옵션

  • action : 읽기, 생성, 업데이트, 삭제와 같은 실행하고자 하는 모든 동작.
  • batch : Ext.data.Batch 객체의 일부
  • filters : 필터의 배열
  • group : 그룹 설정
  • limit : 서버로부터 로딩할 모델 인스턴수의 개수 제한
  • sorters : 정렬의 배열
  • start : 로딩할 모델의 초기화 값. 페이징에 사용.

filters, group, limit, sorters, start는 읽기 요청에만 사용 가능

//book 5개 로딩
var operation = Ext.create('Ext.data.Operation', {
 action: 'read',
 startParam: 'firstRecord',
 start: 0,
 limit: 5
});
proxy.read(operation);

//특정 페이지를 로딩 + 정렬 설정
var operation = Ext.create('Ext.data.Operation', {
 action: 'read',
 page: 5,
 sorters: [
  Ext.create('Ext.util.Sorter', {
   property: 'pages',
   direction: 'DESC'
  });
 ]
});
proxy.read(operation);

프록시가 만드는 URL 요청 : 1) /books?firstRecord=0&limit=5 2) /books?page=5

요청 URL의 가독성을 위한 코드 수정

var proxy = Ext.create('Ext.data.proxy.Ajax', {
 url: '/books',
 model: 'Book',
 encodeSorters: function(sorters) {
  var length = sorters.length;
  var sortStrs = [];
  var sorter, i;
  for(i=0 ; i<length; i++) {
   sorter = sorters[i];
   sortStrs[i] = sorter.property+','+sorter.direction;
  }
  return sortStrs.join(";");
 },
 encodeFilters: function(filters) {
  var length = filters.length;
  var filterStrs = [];
  var filter, i;
  for(i=0; i<length ; i++) {
   filter = filters[i];
   filterStrs[i] = filter.property+','+filter.value;
  }
  return filterStrs.join(";");
 }
});

AjaxProxy의 제약사항

애플리케이션이 배포된 도메인과 같은 도메인의 URL만 호출이 가능함. 다른 서버로 Ajax 호출을 만들 수 없음.

Rest 프록시

AjaxProxy의 서브클래스. 모든 CRUD 동작을 REST 형식으로 만듦.
(CRUD : Create, Read, Update, Delete)

다른 서버로 Ajax 호출을 만들 수 없음.

JsonP 프록시

애플리케이션이 배포되어 있는 도메인과 다른 도메인의 요청을 만들 수 있음

//추가되는 태그
<script src="http://mydomain/url?callback=someCallback" />
Ext.define('Blog', {
 extend: 'Ext.data.Model',
 fields: [
  {name: 'lang', type: 'string'},
  {name: 'url', type: 'string'}
 ],
 type: 'jsonp',
 url: 'http://requestdomain.com/test/test.php'
});

var store = Ext.create('Ext.data.Store', {
 model: 'Blog'
});
store.load(function(records) {
 var blog, i;
 for(i=0; i<records.length; i++) {
  blog = records[i].data;
  console.log(blog.id+":"+blog.url);
 }
});

[extjs] 모델 클래스

필드 선언

Ext.define('Patient', {
 extend: 'Ext.data.Mode',
 fields: [
  {name: 'name'},
  {name: 'age', type: 'int'},
  {name: 'phone', type: 'string'},
  {name: 'weight', type: 'float'},
  {name: 'weightKg', type: 'float',
    convert: function(value, record) {
     var weghtPounds = record.get('weight');
     return Math.round(weightPounds * 0.45359237);
    }
   }
 ]
});

필드 type 종류

  • auto(기본)
  • int
  • float
  • string
  • date
  • boolean

모델 인스턴스 생성 방법

  1. Ext.create 함수로 객체 생성과 필드의 값을 채워 생성
    var patient = Ext.craete('Patient', {
     name: 'Loiane Groner',
     age: 25,
     gender: 'F',
     phone:'9876-5432',
     weight:150
    });
    
  2. 모델 관리자의 create 메소드 사용
    var patient = Ext.ModelMgr.create({
     name:'Loiane Groner',
     age: 25,
     gender:'F',
     phone: '9876-5432',
     weight:150
    }, 'Patient');
    

    로딩과 저장을 모델로부터 직접 수행할 수 있으므로 스토어 클래스가 필요 없음.

생성한 모델 인스턴스 호출

patient.get('name');
patient.get('weightKg');

 모델 유효성 검사

Ext.define('Patient', {
 extend: 'Ext.data.Mode',
 fields: [
  ...
 ],
 validation: [
  {type: 'presence', field: 'age'},
  {type: 'length', field: 'name', min:2, max:60},
  {type: 'format', field: 'name', matcher: /([a-z ]+)/},
  {type: 'inclusion', field: 'gender', list:['M', 'F']},
  {type: 'exclusion', field: 'weight', list:[0]}
 ]
});
  1. presence : 값이 존재하는지 체크. 0 유효, 공백(empty string)은 유효하지 않음
  2. length: 문자열의 길이 체크. min~max. min과 max는 선택사항
  3. format : 정규식에 유효한지 체크
  4. inclusion : 값이 리스트에 포함되어 있는지 체크
  5. exclusion: 값이 리스트에 포함되어 있지 않은지 체크
  • validation 체크 방법
    var errors = patient.validate();
    errors.isValid(); //유효한 경우 true, 유효하지 않으면 false 리턴
    errors.items; //모든 에러 목록 리턴
    errors.getByField('name'); //name 필드에 대한 에러 확인
    

프록시와 스토어를 이용한 로딩과 저장

  1. Blog 모델에 proxy 설정
    레스트(Rest) 방식으로 URL data/blogs/의 데이터를 로딩하는 프록시를 설정함.

    Ext.define('Blog', {
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'name', type: 'string'},
      {name: 'url', type: 'string'}
     ],
     proxy: {
      type: 'rest',
      url: 'data/blogs',
      format: 'json',
      reader: {
       type: 'json',
       root: 'blogs'
      }
     }
    });
    

    프록시 정보 재사용

    var store = Ext.create('Ext.data.Store', {
     model: 'Blog'
    });
    
    store.load(function(record){
     Ext.MessageBox.alert('Testing extjs 4 Models', "Loaded " + store.getCount() + " records");
    });
    

    스토어 내부에서 프록시 선언

    Ext.define('Blog', {
     extend: 'Ext.data.Model',
     fields:[
      {name: 'id', type: 'int'},
      {name: 'name', type: 'string'},
      {name: 'url', type: 'string'}
     ]
    });
    
  2. 모델로부터 직접 로딩
    URL data/blogs/1로부터 GET 방식으로 데이터 로딩.

    Blog.load(1, { //1 : 아이디
     success: function(blog) {
      console.log("blog: " + blog.get('url'));
     }
    });
    
    var store = Ext.create('Ext.data.Store', {
     model: 'blog',
     proxy: {
      type: 'rest',
      url: 'data/blogs',
      format: 'json',
      reader: {
       type: 'json',
       root: 'blogs'
      }
     }
    });
    
  3. 업데이트
    data/blogs/1에 PUT 요청.

    blog.set('name', 'Loiane');
    blog.save({
     success: function() {
      console.log('The blog was updated');
     }
    });
    
  4. 삭제
    data/blogs/1에 DELETE 요청.

    blog.destroy({
     success:function() {
      console.log('The blog was destroyed!');
     }
    });
    
  5. 생성
    var blog = Ext.create({
     name: 'Loiane Groner - Pt_BR',
     url: 'http://loiane.com'
    }, 'Blog');
    blog.save();
    

    새로 만든 blog의 아이디는 data/blogs에 POST요청을 보내고 응답으로 받는다.

    {
     "id": 2,
     "name": 'Loiane Groner - Pt_BR',
     "url": 'http://loiane.com'
    }
    

모델 관계 설정

  • belogsTo : 다대일(many-to-one) 관계
  • hasMany : 일대다(one-to-many) 관계
  1. hasMany : 작가(Author) – 책(Book) – 챕터(Chapter) 생성
    Ext.define('Author',{
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'name', type: 'string'}
     ],
     hasMany: {model: 'Book', foreignKey: 'authorId'} 
    //default primaryKey : Author.id, foreignKey : author_id
    });
    
    Ext.define('Book', {
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'title', type: 'string'},
      {name: 'pages', type: 'int'},
      {name: 'numChapters', type: 'int'},
      {name: 'authorId', type: 'int'}  
     ],
     hasMany: {model: 'Chapter', foreignKey: 'bookId'} 
    //default primaryKey : Book.id, foreignKey default: book_id
    });
    
    Ext.define('Chapter', {
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'number', type: 'int'},
      {name: 'title', type: 'string'},
      {name: 'bookId', type: 'int'}
     ]
    });
    

    hasMany 관계 옵션

    • model : 관계를 맺는 모델명
    • name : 모델에 생성되는 접근 함수 이름. 기본값은 관계모델 이름+s
    • primaryKey : 모델의 primary key의 이름. 기본값은 id.
    • foreignKey : 관계를 설정한 모델과 관계 맺는 모델의 foreign key 이름. 기본값은 관계를 설정한 모델+_id
    • filterProperty : 관계 스토어에 설정된 기본 필터로부터 선택적으로 오버라이드한 값. 설정하지 않으면 자동생성, foreignKey 설정에 따라 관계를 맺음.
  2. hasMany : 관계 맺은 모델 인스턴스 데이터에 접근
    Author.load(1, { //id가 1인 작가 정보 호출
     success: function(author){
      var books = author.books();// author의 id와 같은 값의 authorId를 가지고 있는 모든 book 리턴
      console.log("Author " + author.get('name') + " has written " + books.getCount() + " books");
      books.each(function(book){
       var title = book.get('title');
       var chapters = book.chapters();
       console.log("Book " + title + " has " + chapters.getCount() + " chapters");
       chapters.each(function(chapter){
        console.log(chapter.get('number') + " " + chapter.get('title'));
       });
      });
     }
    });
    

    필터를 적용할 경우

    hasMany: {model: 'Book', foreignKey: 'authorId', filterProperty: 'filter'}
    
    var store = Ext.create('Author', {filter: 'Loiane Groner'}).books();
    var store = Ext.create('Ext.data.Store', {
     model: 'Book', 
     filters: [
      {
       property: 'filter',
       value: 'Loiane Groner'
      }
     ]
    });
    
  3. hasMany : 새로운 객체를 상위 객체에 추가
    var author = Ext.ModelMgr.create({
     id: 2,
     name: 'Loiane Groner'
    }, 'Author'); //Author 객체 생성
    var books = author.books();
    book.add({
     title: 'extjs 4 : First Look',
     pages: 250,
     numChapters: 7
    }); //새로운 book 객체 추가
    books.sync(); //  추가된 book을 author에 추가
    
  4. belongsTo : 작가(Author) – 책(Book) – 챕터(Chapter) 생성
    Ext.define('Author', {
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'name', type: 'string'}
     ],
     hasMany: {model: 'Book', foreignKey: 'authorId'}
    });
    
    Ext.define('Book', {
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'name', type: 'string'},
      {name: 'pages', type: 'int'},
      {name: 'numChapters', type: 'int'},
      {name: 'authorId', type: 'int'}
     ],
     hasMany: {model: 'Chapter', foreignKey: 'bookId'},
     belongsTo: {model: 'Author', foreignKey: 'authorId'}
    });
    
    Ext.define('Chapter'{
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'number', type: 'int'},
      {name: 'title', type: 'string'},
      {name: bookId', type: 'int'}
     ],
     belogsTo: {model: 'Book', foreignKey: 'bookId'}
    });
    

    belongsTo 관계 옵션

    • model : 연결시킬 모델의 이름
    • primaryKey : owner 모델의 primary key의 이름. 기본값은 id.
    • foreignKey : owner 모델과 관계 맺는 모델의 foreign key 이름. 기본값은 owner모델+_id
    • getterName : owner 모델엥 추가되는 getter 함수. 기본값은 get+관계된 모델의 이름.
    • setterName : owner 모델에 추가되는 setter 함수. 기본값은 set+관계된 모델의 이름.
  5. belongsTo : 관계 맺은 모델 인스턴스 데이터에 접근
    Book.load(11, {
     success: function(book) {
      book.getAuthor(function (author) { //Author 객체에 설정된 프록시를 사용해 데이터 로딩.
       console.log("The author of this book is " + author.get('name'));
       callback: function(author, operation) {}, //book.getAuthor() 호출 시 사용. 항상 호출.
       success: function(author, operation) {}, //데이터 로딩 성공 시 호출
       faliure: function(author, operation) {}, //데이터 로딩 실패 시 호출
       scope: this //option. 함수들이 실행될 때 범위 지정.
      });
     }
    });
    

    getter, setter 함수 사용

    book.setAuthor(1);
    book.set('authorId', 1); //둘이 동일함.
    
    book.setAuthor(1, function(book. operation) {
     console.log(book.get('authorId')); //1 출력
    });
    book.setAuthor(1, {
     callback: function(book, operation) {},
     success: function(book, operation) {},
     failure: function(book, operation) {},
     scope: this
    });
    
  6. associations을 통해 hasMany, belongsTo 선언
    Ext.define('Book', {
     extend: 'Ext.data.Model',
     fields: [
      {name: 'id', type: 'int'},
      {name: 'title', type: 'string'},
      {name: 'pages', type: 'int'},
      {name: 'numChapters', type: 'int'},
      {name: 'authorId', type: 'int'}
     ],
     associations: [
      {type: 'hasMany', model: 'Chapter', foreignKey: 'bookId'},
      {type: 'belongsTo', model: 'Author', foreignKey: 'authorId'}
     ]
    });