ПОДЕЛИТЬСЯ

НОВОСТИ

Веб-студией "Новая парадигма" разработан сайт для Фонда "Институт социально-экономических и политических исследований" Подробнее...
По данным авторитетного портала Рейтинг Рунета Система Управления Контентом CMS/CMF MODx в России занимает 4-ое место по популярности. Подробнее...
Исследовательским отделом веб-студии "Новая парадигма" были произведены исследования, насколько хорошо фреймворк MODx Revolution работает при значительном увеличении числа документов. Подробнее...

Итак перейдем наверно к самому интересному инструментарию в Эксте (после драг`н`дропов ИМХО). Сразу определимся с терминами, которые я тут буду использовать (терминология расшифрована мной по моему разумению, потому не претендует на звание официальной документации):

 

1) Хранилище (Store). Объект Ext.data.Store. Хранит в себе структурированные данные. Некоторые разновидности: Ext.data.JsonStore , Ext.data.ArrayStore . По сути это производные от своего старшего брата, просто с соответствующими Ридерами в комплекте.

 

2) Grid. Объект Ext.grid.GridPanel. Таблица для отображения данных из Хранилища. Grid плотно связан с объектом Store и без него существовать не может. Grid отображает данные из Store и чтобы в нем что-то изменилось, надо на самом деле изменять данные в Store. Это может и несколько запутано, но на самом деле очень продуманно и правильно! Такого я больше нигде не видел. Дальше в примерах я постараюсь раскрыть ключевые моменты и съакцентировать на них внимание.

 

3) Запись (Record). Объект Ext.data.Record . Все данные в Store - табличные, хранятся построчно (Это если образно выражаться). То есть чтобы изменить какой-то элемент, нужно обратиться к соответствующей записи (Record), а в ней уже к соответственной колонке (к соответственному элементу ).

 

4) Reader. Объект Ext.data.ArrayReader || Ext.data.JsonReader || Ext.data.DataReader . Используется для чтения данных в Store. Необходимо указывать в случае использование "чистого" Хранилища (Ext.data.Store) и зависит от типа входных данных (массив, Объект или уже подготовленные данные через Ext.data.Record.create()). В случае использование специфичных хранилищ (Ext.data.ArrayStore, Ext.data.JsonStore или Ext.data.DirectStore), соответствующие Reader будут инициализированы автоматически.

 

5) ColumnModel. Объект Ext.grid.ColumnModel. Обязательный для Grid. Описывает структуру колонок Grid. Как уже ранее говорилось, все данные в Store хранятся в виде записей (Records). Так вот эта модель Колонок связана со структурой этих Рекордов, точнее с элементами этих Рекордов. В данной модели прописываются отношения колонок к этим элементам. Вся красота заключается в том, что вы, используя единое хранилище, можете использовать в качестве источника данных для нескольких клиентов, все они будут работать синхронно и правильно. Колонок может быть меньше чем элементов в Рекорде, то есть если мы не хотим чтобы через UI можно было работать с определенными элементами Рекорда, значит не прописываем эти элементы в ColumnModel. Мы можем указать такие свойства, как видимость по умолчанию или нет (то есть при инициализации колонка будет скрыта, и чтобы ее увидеть, нужно будет через меню управления колонками сделать ее видимой), можем указать тип данных, указать можно ли сортировать данные в Grid по данной колонке или нет, навешать колонке функцию-рендерер для обработки вводимых в нее данных на лету (к примеру если мы хотим чтобы вводимые числа с запятой округлялись до целого или более сложные операции, такие как взаимодействие с другими объектами в зависимости от вводимых данных). В общем это действительно очень мощный инструмент и мы постараемся как можно больше моментов разобрать в следующем примере.

 

На самом деле это не все участники этой сложной системы (не говоря уже про Прокси Ext.data.HttpProxy, позволяющий сконфигурить взаимодействие с сервером в автоматическом режиме до 4-ех событий (Получение первичных данных, сохранение, обновление, удаление)). Но этого материала достаточно для понимания на достаточном уровне. Вообще по-моему Grid - одна из сложнейших областей ExtJS в плане понимания. Сам я потратил двое суток прежде чем начал боле менее свободно ориентироваться в этой области. Слишком уж тут много завязанного друг на друге, а как я не искал, в инете не нашел реально подробного и понятного примера. Надеюсь у меня тут получилось заполнить этот пробел и вам будет легко освоить этот раздел.

 

 

Grid  = {
	// Базовые параметры нашего окна
	getWinParams: function(){
		return {
			width: 550,
			height: 300,
			title: 'Хранилища и таблицы',
			buttons:[{
				text: 'ОК',
				handler:function(){
					Grid.win.close();	
				}
			}],
			listeners: {
				close: function(){
					Grid.win = false;	
				}	
			},
			layout: 'fit',
			bodyStyle: {
				'background-color': '#FFFFFF'	
			}
		}	
	},
	
	// Функция инициализации окна
	getWindow: function(params){
		!params ? params = this.getWinParams() : "";
		if(!this.win){
			this.win = new Ext.Window(params);
		}
		return this.win;
	},
	
	// Ридер данных из объекта
	getJsonReader: function(){
		this.JsonReader = new Ext.data.JsonReader({
			root: 'data',		// Элемент объекта, содержащий данные
			idProperty: 'id',	// Коланка, содержащая Уникальные данные ID
			fields: [			// Описание колонок, выводимых в Grid
				{name:'id', type: 'int', allowBlank: false},	 
				{name: 'firstName', allowBlank: false}, 
				{name: 'middleName'}, 
				{name: 'lastName', allowBlank: false},
				{name: 'age', type: 'int'},
				{name: 'weight', type: 'float'}
			]

		});
		return this.JsonReader;
	},
	
	// Данные для Хранилища
	getInputData:function(){
		return { 
				data:[{
					id: 0,
					firstName: 'Михаил',
					lastName: 'Попов',
					middleName: 'Алефтинович',
					age: 43,
					weight: 65.23
				},{
					id: 1,
					firstName: 'Алексей',
					lastName: 'Пупкин',
					age: 25, 
					middleName: 'Петрович' 
				},{
					id: 2,
					firstName: 'Анна',
					middleName: 'Олеговна',
					lastName: 'Иванова',
					age: 31,
					weight: 55.14
				},{
					id: 3,
					firstName: 'Николай',
					lastName: 'Соснов',
					age: 27, 
					middleName: 'Владимирович',
					weight: 78.32
				},{
					id: 4,
					firstName: 'Елена',
					middleName: 'Георгиевна',
					lastName: 'Седова',
					age: 26,
					weight: 57.00 
				}]	
			}
	},
	
	// Получаем хранилище данных
	getStore: function(){
		reader = this.getJsonReader();
		this.store = new Ext.data.Store({
			id: 'store',
			reader: reader,
			// Получаем входные данные для работы
			data: this.getInputData()
		});
		return this.store;
	},
	
	// Получаем Таблицу для отображения данных
	getGrid: function(){
		store = this.getStore();
		this.grid = new Ext.grid.GridPanel({
			store: store,		// хранилище. Важно! Required!
			cm: new Ext.grid.ColumnModel({	// Модель колонок
				columns: [		// Колонки
					{header: 'Фамилия', dataIndex: 'lastName' },
					{header: 'Имя', dataIndex: 'firstName', sortable: false },
					{header: 'Отчество', dataIndex: 'middleName', hidden: true },
					{header: 'Возраст', dataIndex: 'age' },
					{header: 'Вес', dataIndex: 'weight' }						  
				],
				// Флаг что по умолчанию колонки Sortable. по умолчанию false
				defaultSortable: true	
			})
		})
		return this.grid;
	},
	
	showGrid: function(){
		if(this.win) return false;
		
		// Инициализируем окно
		this.getWindow();
		
		// Инициализируем Грид
		this.getGrid();
		
		// Добавляем Грид к Окну
		this.win.add(this.grid);
		
		// Выводим окно
		this.win.show();
	}
}

 

 

Итак разберем все по порядку и подробно.

 

В первую очередь для существования Grid необходим Store, то есть Хранилище. Вообще можно конечно использовать и чистый Стор (как мы сделали и в нашем примере), но лучше изначально определиться с типом входящих данных и выбрать соответствующий Стор изначально, к примеру Ext.data.JsonStore. Во-первых, будет проще сконфигурить, ибо соответствующий Ридер будет автоматически использован и конечный код окажется компактней и менее запутанный. Во-вторых, проще работать с таким кодом, ибо только глянув на используемые объекты сразу становится понятно с каким типом данных мы будем работать (Объект или Массив) и легче будет разобраться с кодом и спланировать изначально план работы. Повторюсь что в нашем примере мы использоватли чистый Store.

 

// Получаем хранилище данных
  getStore: function(){
      reader = this.getJsonReader();
      this.store = new Ext.data.Store({
          id: 'store',
          reader: reader,
          // Получаем входные данные для работы
          data: this.getInputData()
      });
      return this.store;
  }

 

Рассмотрим внимательно данную функцию. В ней мы инициализируем Хранилище

 

this.store = new Ext.data.Store()


Но для Хранилища как минимум требуется Reader, если это чистый Store (если производный, соответствующий Reader будет автоматически создан, о чем я уже говорил ранее), и данные. Данные мы можем передать сразу в переменную data в подходящем виде (как мы это сделали в нашем случае), либо загрузить через метод loadData(data) объекта Store опять таки в подходящем виде, либо через его же метод load(), если у нас опрделен для Store url, либо сконфигурин для него же proxy. В нашем случае мы получаем Reader через функцию reader = this.getJsonReader(); Изучим и эту функцию.

 

// Ридер данных из объекта
  getJsonReader: function(){
      this.JsonReader = new Ext.data.JsonReader({
          root: 'data',        // Элемент объекта, содержащий данные
          idProperty: 'id',    // Коланка, содержащая Уникальные данные ID
          fields: [            // Описание колонок, выводимых в Grid
              {name:'id', type: 'int', allowBlank: false},    
              {name: 'firstName', allowBlank: false},
              {name: 'middleName'},
              {name: 'lastName', allowBlank: false},
              {name: 'age', type: 'int'},
              {name: 'weight', type: 'float'}
          ]
      });
      return this.JsonReader;
  }

 

Рассмотрим каждый параметр конфига объекта Reader.

 

1) root: 'data'. В данном случае мы указываем какой из элементов объекта входящих данных будет использоваться в качестве источника данных. В нашем случае в объекте есть такое элемент 'data', вот его и будет читать Reader. Если весь входящий объект - источник данных, этот параметр следует опустить.

 

2) idProperty: 'id'. Так же не обязательный параметр. Указывает какая колонка в данных будет считаться индексом. Совпадения не допустимы, то есть если у нас во входящих данных уже была какая-то запись с id: 5, то если будет еще строка с id: 5 , такая строка будет проигнорирована и не будет добавлена в Store, и в Grid соответственно.

 

3) fields. Описание колонок, которые следует сформировать при чтении входящих данных. То есть не обязательно использовать все входящие данные, если нам нужна только часть их. Тут мы для каждой колонки можем определить тип данных, заголовок, можно/нельзя оставлять пустым и другие свойства. В нашем случае мы определили для всех полей имена, для некоторых определили тип данных (type: int, float), можно/нельзя оставить пустыми (allowBlank). Обратите внимание что это модель описывается именно для Store, и в этом виде данные будут храниться в нем, а не в Grid. Для Грида нужно будет прописать ColumnModel, которая будет использовать эти поля.

 

В общем Reader для Store у нас создан и передан объекту Store в качестве параметра reader.
Параметр id мы припустим, тут комментарии излишни. Параметр не обязательный.
data: this.getInputData() Собственно наши данные для Store. Структура - Json. Прелесть использования данной модели в том, что нам во-первых не нужно следить за последовательностью необходимых нам элементов в объекте данных, а во-вторых у нас вообще могут быть не все входящие элементы, Reader прочтет то, что есть. Вот рассмотрим структуру Данных.

 

   getInputData:function(){
      return {
          data:[{
              id: 0,
              firstName: 'Михаил',
              lastName: 'Попов',
              middleName: 'Алефтинович',
              age: 43,
              weight: 65.23
          },{
              id: 1,
              firstName: 'Алексей',
              lastName: 'Пупкин',
              age: 25,
              middleName: 'Петрович'
          },{
              id: 2,
              firstName: 'Анна',
              middleName: 'Олеговна',
              lastName: 'Иванова',
              age: 31,
              weight: 55.14
          },{
              id: 3,
              firstName: 'Николай',
              lastName: 'Соснов',
              age: 27,
              middleName: 'Владимирович',
              weight: 78.32
          },{
              id: 4,
              firstName: 'Елена',
              middleName: 'Георгиевна',
              lastName: 'Седова',
              age: 26,
              weight: 57.00
          }]    
      }
  }

 

Как вы видите, последовательность элементов типа firstName, lastName и других не всегда совпадает, а в некоторых случиях и вовсе отсутствуют.

 

В общем Store у нас есть. И данные в нем уже. Теперь Store можно использовать для Grid. Инициализируем Grid.

 

getGrid: function(){
	store = this.getStore();
	this.grid = new Ext.grid.GridPanel({
		store: store,		// хранилище. Важно! Required!
		cm: new Ext.grid.ColumnModel({	// Модель колонок
			columns: [		// Колонки
				{header: 'Фамилия', dataIndex: 'lastName' },
				{header: 'Имя', dataIndex: 'firstName', sortable: false },
				{header: 'Отчество', dataIndex: 'middleName', hidden: true },
				{header: 'Возраст', dataIndex: 'age' },
				{header: 'Вес', dataIndex: 'weight' }						  
			],
			// Флаг что по умолчанию колонки Sortable. по умолчанию false
			defaultSortable: true	
		})
	})
	return this.grid;
}


Что мы тут имеем? В качестве обязательного параметра передаем Гриду Store (store: store). Таким образом мы установили связь Грида со Стором и все изменения в Хранилище будут отражаться на Гриде.
cm: new Ext.grid.ColumnModel . cm (сокращенное название параметра Грида colModel). Обязательный параметр. Ну не может таблица существовать без колонок! Описывается структура колонок таблицы. Я выше уже описывал подробно эту модель в пункте 5 нашего словоря. Обязательно прочтите еще раз.
defaultSortable: true Указывает на то, что по умолчанию все колонки sortable: true . ( По умолчанию false )

 

Остается только собрать это все в кучу

 

showGrid: function(){
	if(this.win) return false;
	
	// Инициализируем окно
	this.getWindow();
	
	// Инициализируем Грид
	this.getGrid();
	
	// Добавляем Грид к Окну
	this.win.add(this.grid);
	
	// Выводим окно
	this.win.show();
}

 

Вот и все! Мы имеем Хранилище с данными и интерфейс для отображения их (Grid). Но на самом деле это еще не все. Давайте посмотрим внимательно что же именно мы получили? А получили мы следующее:

 

1) Хранилище для данных, которые могут использоваться как единый актуальный обменник данными с разграничением видимости и прочими полезными хитростями. Особенно вдохнавляют упомянутые уже выше Proxy, а главное методы commitChanges() и rejectChanges() (аналог commit и rollback в СУБД). Согласитесь это уже достойно уважения и внимания!

 

2) Мощный инструмент для UI - Ext.data.Grid. На самом деле мощный! Внимательно его прокликайте и перекликайте. Вы найдете много полезных фишек в нем, типа Ресортинг данных в таблице, скрытие/показ отдельных колонок и т.п. А еще мы создали пока что базовый Grid, который выглядит как инструмент для просмотра данных в Storee. На самом деле если расширить функционал нашего Грида, получится мощный инструмент для UI, с помощью которого очень удобно работать с данными, редактировать их, добавлять новые, удалять не нужные, работать сразу с группой строк и т.п., а еще навешать контекстное меню, рендеры для отдельных колонок, указать типы данных и вообще сказка получается! Обязательно посмотрите примеры с Гридами в Примерах, там есть что глянуть :-)

 

Если вам помог мой материал и вы есть на Хабрахабре, плюсание карму в дар :-)