понедельник, 15 декабря 2008 г.

Учебник: работа с данными в Ext

Основная концепция данных
- Stores - Store это хранилище данных для таблиц (Grid). Это кэш объектов Ext.data.Record, который содержит те поля, которые вы указываете. Поля могут быть доступны по имени.
-Proxies - Хранилище Store использует объект Proxy для получения данных из заданного их месторасположения. Если у вас допустим просто javascript массив прямо в коде страницы, то вам понадобиться MemoryProxy, конструктор которого просто работает с этими данными. Если вы получаете данные с сервера, расположенного в том же домене, то вам нужен HttpProxy. Proxy достаточно прозрачен. Он всего лишь обеспечивает доступ к порциям данных. Вы назначаете объект Reader взависимости от того, какие данные ожидаются. Вам лучше использовать ArrayReader если у вас просто MemoryProxy, который рабоает с со значениями массивов.
-Readers - Reader, является конструктором для определения данных, как бы расставляя поля. Он выделяет отдельные поля из порции данных и передает их массиву записей, которые передаются в хранилище с помощью метода loadRecords, который и обновляет кэш хранилища.

Основные классы данных
-Ext.data.Connection - класс обеспечивает подключение к соответствующему домену страницы, позволяя делать запросы как к статическому URL, так и к динамическому, указанному в процессе запроса.
-Ext.data.Record - экземпляры этого класса одновременно и определяют описание информации и описание для использования информации записей в хранилище Ext.data.Store или любом другом коде которому нужно иметь доступ к записям, сохраненным в Ext.data.Store объекте.
-Ext.data.SortTypes - Этот класс является уникальным и не может быть создан напрямую.
-Ext.data.Tree -Представляет структуру дерева данных и все события узлов дерева.
-Ext.data.Node -не указано

Источник: http://extjs.com/learn/Manual:Data

среда, 10 декабря 2008 г.

Расширение Ext для новичков

Источник: http://extjs.com/learn/Tutorial:Extending_Ext_for_Newbies

Обычно вы хотите привнести мощь объектно-ориентированного проектирования с помощью классов. Вы делаете это по нескольким причинам как объяснено в этом учебнике.

Классы, используемые многократно

Иногда вам нужны будут компоненты, конфигурационные опции которых вы хотите использовать многократно. Например, у вас может быть набор панелей с одной и той же шириной и высотой, но с разными заголовками. Это назывется предопределенные (предварительно-сконфигурированные) классы.

Модель Конструктора

Один вариант решения это использование функции конструктора, как например здесь:
// функция - конструктор
var MyPanel = function(config) {
Ext.apply(this, {
// создайте нужные опции здесь

width: 300,
height: 300
});
MyPanel.superclass.constructor.apply(this, arguments);

};
// MyPanel расширяет возможности Ext.Panel
Ext.extend(MyPanel, Ext.Panel, {});
 
var myfirstpanel = new MyPanel({

title: 'My First Panel'
});
 
var mysecondpanel = new MyPanel({

title: 'My Second Panel'
});


Фабрика шаблонов (палитр)
Другой путь для создания предопределенных объектов это использование фабрики шаблонов дизайна. Фабричная функция (предопределенная функция) возвращает новый экземпляр объекта. Этот подход не требует расширения классов (краткий обзор не позволяет обсудить это подробно). Фабрика шаблонов это альтернативный вышеупомянутому метод который особенно полезен когда все, что вы хотите сделать, это задать конфигурирование свойств "по-умолчанию", не расширяя/переопрделяя базовый класс.
function createMyPanel(config) {
return new Ext.Panel(Ext.apply({//опции по-умолчанию идут здесь

width: 300,
height: 300
}, config));
};
 
var myfirstpanel = createMyPanel({

title: 'My First Panel'
});
 
var mysecondpanel = createMyPanel({
title: 'My Second Panel'

});


Расширение функциональности

Другая причина, по которой вы хотите использовать ОО классы это когда вы хотите расширить функциональность другого класса. Допустим вы хотите добавить функцию в вышеупомянутую панель и переопределить существующую функцию. Есть один путь сделать это:
// Конструктор
var MyPanel = function(config) {
//Повторно-используемые опции идут здесь
Ext.apply(this,
{

width: 300,
height: 300
}
});
// вызов конструктора суперкласса для сохранения функциональности класса-родителя
MyPanel.superclass.constructor.apply(this, arguments);
// Здесь вы можете добавить функциональность, нужную вам,

//например обработку событий.
this.on('click', function() {alert("You Clicked " + this.title);}, this);

};
// MyPanel расширяет Ext.Panel
Ext.extend(MyPanel, Ext.Panel, {
// Здесь вы можете добавить статические переменные класса, переменные, которые будут иметь
//одинаковое значение для всех объектов класса.

// Если вы не уверены нужно ли включать его в конструктор выше. Не помещайте никакого объекта
// созданного с помощью 'new' или 'xtype' здесь. Безопаснее положить его в конфигурационных
// опциях в конструкторе.
 
// Новая функция добавлена
myNewFunction: function() {

},
// Переопределить существующую функцию
onRender: function() {
MyPanel.superclass.onRender.apply(this, arguments);
this.myNewFunction();
}

});
 
var myfirstpanel = new MyPanel({
title: 'My First Panel'

});
 
var mysecondpanel = new MyPanel({
title: 'My Second Panel'

});



Другой путь для создания конструктора выше показан @Condor :
var MyPanel = function(config) {
// вызов конструктора суперкласса для сохранения функциональности класса-родителя
MyPanel.superclass.constructor.call(this, Ext.apply({

//опции по-умолчанию идут здесь
width: 300,
height: 300
}, config));
 
// После конструктора суперкласса добавьте требуемую функциональность

//для объекта (обработка событий, прослушивание)
this.on('click', function() {alert("You Clicked " + this.title);}, this);

};


Вышеупомянутый способ расширения Ext класса это модель конструктора. Другой путь расширения Ext класса - использование initComponent, один из шаблонных методов Ext JS созданный специально для этого. Как вы можете догадаться этот метод применяется только для расширения Ext компонентов. Вот пример использования:
var MyPanel = Ext.extend(Ext.Panel, {
// Здесь добавляются статические переменные
//
// If you are not sure put it in the constructor above. Do not put
// any object created with 'new' or 'xtype' here. It is safer to
// put it in the config option of the constructor.

// New function added
initComponent: function() {
//Reusable config options here
Ext.apply(this,
width: 300,
height: 300
});
// And Call the superclass to preserve baseclass functionality
MyPanel.superclass.initComponent.apply(this, arguments);
// Here you can add functionality that requires the object to
// exist, like event handling.
this.on(
'click',
function() {
alert("You Clicked " + this.title);
},
this
);
},
myNewFunction: function() {
},
// Override an existing function
onRender: function() {
MyPanel.superclass.onRender.apply(this, arguments);
this.myNewFunction();
}
});

Первое что вы получите это оповещение, что нет конструктора. Ext создает конструктор сам. Конструктор создается Ext с помощью вызова initComponent. Это широко используемый метод, который вы найдете в продвинутых учебниках и примерах. Но запомните сейчас, что он делает тоже самое, что и модель конструктора.

Предпочитаемый путь для обработки событий (listeners) это добавить их после вызова суперкласса в конструктор или initComponent.
MyPanel.superclass.constructor.apply(this, arguments);
// Here you can add functionality that requires the object to exist,
// like event handling.
this.on(

'click',
function() {
alert("You Clicked " + this.title);
},
this

);


В случае фабричного метода можно добавить обработчки событий вне фабричного метода, как здесь:
myFirstPanel.on(
'click',
function() {
alert("You Clicked " + this.title);
},
myFirstPanel //scope

);


Есть и другие пути обработки событий. Но они более для продвинутых пользователей

вторник, 9 декабря 2008 г.

Проектирование больших приложений в Ext

Вступление

Я решил написать эту статью для тех пользователей Ext2.x которые уже выросли из детских потребностей, создавая одну HTML страницу c простыми скриптами которые создают одно простое окно или форму, для тех кто уже решил что Ext - это путь, и для тех кто борется со слишком большими файлами, трудными для поиска и чувствующих, что их приложениям нужна структура.

Количество проблем и путей их решения равно количеству людей, работающих над этим. Способ, который я опишу в тексте, не единственный, и я не хочу сказать что приложение можно написать только моим способом, иначе будет неверно. Ничего подобного.

Все что я хочу сказать, то что этот вариант рабочий, достаточно структурированный, легко разрабатываемый - просто сказать - Он работает!

Что такое Большое приложение?

Если вы сделали Viewport с BorderLayout, таблицой и формой и все это находится в одном файле - это большое приложение, не так ли? Если вы создали десяток окон с таблицами, формами или границами - это большое приложение, да?

У немцев есть отличные слова: Jein - комбинация слов Да и Нет.
Ответ для обоих вариантов - Jein (и да и нет). Когда приложение становится большим? Ответ простой: оно становится большим когда вы чувствуете, что оно большое. Это начинается тогда, когда у вас у самих начинаются проблемы в ориентировании в множестве файлов или вы не можете найти нужный код в одном файле., когда вы уже не понимаете взаимосвязь компонентов и так далее. Я это пишу, но представьте когда у программиста с в 2-3 раза большим опытом появляется это чувство?
Мы может просто установить, что каждое приложение становится большим, когда оно хорошо написано и у нас не вызовет проблем добавиьт в него дополнительные возможности, дописать новый код, новые CSS правила и так далее.
Лучший и безопасный вариант старта нового приложения это чувство : я начинаю большое приложение!

Файлы и директории
Это первое что нам нужно организовать. Всегда существует корневая директория DocumentRoot на Apache или другом HTTP сервере, так что все поддиректории которые я опишу позже расположены относительно нее.
Рекомендуемая структура директорий такова:
./css (optionally link)
./ext (ссылка)
./img (ссылка)
./js
index.html

Ссылка в вышеупомянутой структуре означает мягкую ссылку, указывающую на реальное месторасположение файлов. Это нужно для того, когда вы например, скачаете новую версию Ext в директорию, то просто измените ссылку на нее, без изменений кода, ссылающегося на нее и можете испытать что все работает правильно в новой версии. Если да, можно оставить все как есть, если нет, просто изменить ссылку на прежнюю.

* css содержит все таблицы стилей. Если у вас есть глобальные стили с цветами компании или шрифтами вы можете создать и эту директорию как ссылку.
* ext ссылается на библиотеку Ext JS как было описано выше
* img ссылается на ваши изображения. Также она может иметь поддиректорию с иконками.
* js будет содержать все JavaScript файлы приложения.
* index.html HTML файл, который является стартовой точкой приложения. Вы можете назвать его как захотите и можете добавить другие файлы, например файлы для процесса аутентификации. В любом случает должна быть одна стартовая точка - файл.
* опционально вы можете создать директорию или ссылку на серверную часть приложения (у меня например ./classes). Вы можете называть их как пожелаете, но придерживаться целостности для приложений, которые пишите (./server, ./php хорошие примеры)

index.html

Самое минимальное содержание файла index.html такое

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="./ext/resources/css/ext-all.css">

<link rel="stylesheet" type="text/css" href="./css/application.css">
<script type="text/javascript" src="./ext/adapter/ext/ext-base.js"></script>

<script type="text/javascript" src="./ext/ext-all-debug.js"></script>
<script type="text/javascript" src="./js/application.js"></script>

<title>A Big Application</title>
</head>
<body></body>
</html>


Чтобы я рекомендовал сделать в файле выше, добавить описательный заголовок не только в этот файл но и во все создаваемые файлы. Также маркер End of file имеет значение. Смотрите File Patterns как пример таких заголовков.

js/application.js

Нам нужен файл, где будет располагаться функция onReady, давайте назовем его application.js. Его минимальное содержимое такое:
// vim: sw=4:ts=4:nu:nospell:fdc=4
/**
* An Application
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008, by Ing. Jozef Sakáloš
* @date 2. April 2008
* @version $Id$
*
* @license application.js is licensed under the terms of the Open Source
* LGPL 3.0 license. Commercial use is permitted to the extent that the
* code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/

 
/*global Ext, Application */
 
Ext.BLANK_IMAGE_URL = './ext/resources/images/default/s.gif';
Ext.ns('Application');

 
// application main entry point
Ext.onReady(function() {
 
Ext.QuickTips.init();

 
// code here
 
}); // eo function onReady
 
// eof


Ваш заголовок и подвал может изменяться но удостоверьтесь что установили Ext.BLANK_IMAGE_URL на ваш сервер. Это путь на прозрачное изображение 1х1 пикселей которое используется Ext как указатель расположения ихображения, и если он указывает на неверное месторасположение вы можете получить проблемы при отрисовке, такие как потеря изображений в элементах, отсутствие иконок или что-то подобное.
Вам может также понадобится новой глобальной переменной объекта для приложения ( здесь это Application).
В чем вы должны быть уверены то что Ext.onReady это главная начальная точка приложения.

css/application.css

Здесь будут храниться ваши таблицы стилей, в этом файле. Если вам нужно всего лишь пару правил, то создавать отдельный файл нет необходимости, и будет лучше просто включить его в заголовок страницы.
Обратное тоже истина, помните, вы пишите большое приложение, поэтому все должно быть на своем месте. Если вы напишите таблицу стилей прямо в заголовке файла, то рано или поздно у вас могут возникнуть проблемы при отображении и вы забудете где вы сохранили эти стили.

Ошибочный путь

Что обычно следует когда мы начинаем создавать что то? Давайте напишем. Итак, мы сидим и начинаем писать:
var vp = new Ext.Viewport({
layout:'border'
,items:[

new Ext.grid.GridPanel({
store:new Ext.data.Store({

proxy:new Ext.data.HttpProxy({ ...


Подождите минутку. С таким подходом у нас будет 10000 строк в нашем application.js очень скоро и это то, чего мы хотим в последнюю очередь. Очевидно, мы пропустили какой-то шаг, как будто мы не могли написать это все сразу в index.html?

Правильный путь: Разбиваем все на части

Даже самое сложное состоит из маленьких подсистем, а те состоят из еще более мелких частей, которые тоже состоят из каких-либо элементов. И при написании больших приложений нет исключения. Теперь настало время определить эти части, компоненты и взаимосвязи между ними.

Итак, сядьте, обдумайте все, набросайте схему, сделайте список, но в результате вы должны знать компоненты - хотя бы самые основные, из которых состоит ваше приложение.

Предопределенные классы

Теперь, когда вы проанализировали приложение и определили его компоненты вы можете начать писать. Но как? Самый лучший подход - написать расширение для классов Ext компонент, у которых есть все конфигурационные опции, или они передаются встроенными в конфигурационный объект. Я использую такие предопределенные классы расширения так как они удобнее и более функциональны чем базовые Ext классы, но их главное назначение - это их настройка для своих нужд. Например, чтобы создать таблицу Персонал со специфичной моделью колонок, хранилищем, опциями сортировки, редакторами и тд.
Если у нас такой подход, наше конфигурационное Окно могло мы выглядеть так:
var win = new Ext.Window({
title:'Personnel'
,width:600

,height:400
,items:{xtype:'personnelgrid'}
});
win.show();


Создание предопределенных классов.

(Запомните: Следующая структура может работать не во всех ситуациях, есть исключения)

Давайте обсудим один пример:

Application.PersonnelGrid = Ext.extend(Ext.grid.GridPanel, {
border:false

,initComponent:function() {
Ext.apply(this, {
store:new Ext.data.Store({...})

,columns:[{...}, {...}]
,plugins:[...]

,viewConfig:{forceFit:true}
,tbar:[...]
,bbar:[...]

});
 
Application.PersonnelGrid.superclass.initComponent.apply(this, arguments);
} // eo function initComponent

 
,onRender:function() {
this.store.load();

 
Application.PersonnelGrid.superclass.onRender.apply(this, arguments);
} // eo function onRender

});
 
Ext.reg('personnelgrid', Application.PersonnelGrid);


Что мы здесь делаем? Мы расширяем класс Ext.grid.GridPanel создавая новый класс (расширение) Application.PersonelGrid и мы регистрируем его как новый xtype с именем personnelgrid.
Мы передаем главноей табличной панели все конфигурационные опции необходимые для специфичной таблицы персонала. С этой точки у нас есть новый компонент, строительный блок для нашего приложения, который мы сможем использовать где-либо (окна, регион, отдельно-стоящий) где нам понадобится список персонала. Мы можем создать его так:
var pg = new Application.PersonnelGrid();


или с помощью xtype (для ленивых)
var win = new Ext.Window({
items:{xtype:'personnelgrid'}

,....
});


Организация предопределенных классов

Код выше не должен и не нужно запускать его внутри функции onReady, потому что он ничего не делает с DOM структурой документа; он только создает JavaScript объект. Вот почему он может и должен быть написан в отдельном файле (js/Application.PersonnelGrid.js) и он может и должен быть включен в заголовок index.html файла как:
<script type="text/javascript" src="./js/Application.PersonnelGrid.js></script>

Шаг за шагом мы продолжаем создавать наши предопределенные классы, складывать их в /js директорию, включать их в index.html и строить наше приложение из них, как пазл, собирая из кусочков.
Выглядит хорошо, так?

В любом случае, есть еще что добавить.

Межкомпонентное взаимодействие

Представьте что нам нужен экран с каймой со списком ссылок на западе и таб-панелью в центре. Нажатие ссылки в списке должно создавать новую закладку в центре. Теперь, куда мы должны положить логику этого события и процедуру обработки? На запад или в центр?
Ни в одно из них. Почему? Если у нас есть предопределенные классы которые создают и показывают ссылки на западе (слева) и мы положили всю логику в них, то они больше не могут существовать без центрального региона. Мы можем просто не сможем их использовать без центра, так как не сможем создавать закладки в центре.
Если положить логику в центр - результат будет тот же: центр не сможет существовать без запада.
Единственный компонент, который будет независим в этом случае это их контейнер - border layout, и это единственно правильное место куда следует поместить межкомпонентную связь.
Что мы затем должны сделать? Контейнер (с border layout) должен прослушивать все события с запада и должен создавать закладки в центре как ответ на эти клики. Пример такого межкомпонентного взаимодействия можно найти по ссылке http://examples.extjs.eu/?ex=compcomm

Система представлений

По мере создания приложения число файлов растет, число включений JavaScript файлов тоже (у меня в одном приложении включается около 80 файлов и их количество каждый день растет). Это може уменьшить производительность и продуктивность системы.
Лучший путь решения - это слияние всех javascript файлов и создание одного большого файла, и минимизирование его с помощью JavaScript или других инструментов сжатия. Также вам не понадобится отладка библиотеки Ext для готовой системы.

На продуктивной системе нам надо включить:

  • ext-all.js

  • app-all.js and

  • application.js


Для дополнительной информации о минимизации исходных кодов и создания комбинированных файлов у нас есть другой учебник, который раскрывает эту тему более подробно (http://extjs.com/learn/Tutorial:Building_Ext_From_Source).

Заключение
Вот и все почти... Есть специфичные техники для специальных Ext классов, есть множество других ноу-хау сервер-клиентских приложений, но выше изложена практически основная концепция.
Удачного кодинга!

Источник: http://extjs.com/learn/Tutorial:Writing_a_Big_Application_in_Ext

понедельник, 8 декабря 2008 г.

Учебник: Grid PHP SQL Часть 12

Создание контекстного меню (правой кнопки)

Это будет последняя часть учебника!!! Знаю, знаю... было очень весело читать! Ок, достаточно! Теперь мы поговорим о контекстных меню или как их обычно называют меню открывающиеся по правому клику мышки.

Во-первых мы записываем номер ряда, на котором мы нажали:

var PresidentListingSelectedRow;


У нас будет простое меню с тремя пунктами: изменение текущей записи, удаление и печать таблицы.
Ок, давайте определим четыре функции. Первая будет вызываться каждый раз при нажатии правой кнопки мыши на таблице:

  function onPresidentListingEditorGridContextMenu(grid, rowIndex, e) {
e.stopEvent();
var coords = e.getXY();
PresidentListingContextMenu.rowRecord = grid.store.getAt(rowIndex);
grid.selModel.selectRow(rowIndex);
PresidentListingSelectedRow=rowIndex;
PresidentListingContextMenu.showAt([coords[0], coords[1]]);
}


Ок, как вы видите мы показываем переменную названную PresidentListingContextMenu. Мы ее еще не создали, мы сделаем это сразу после этого. Давайте определим три функции которые будут вызываться для каждого пункта меню:

  function modifyPresidentContextMenu(){
PresidentListingEditorGrid.startEditing(PresidentListingSelectedRow,1);
}

function deletePresidentContextMenu(){
confirmDeletePresidents();
}

function printListingContextMenu(){
printListing();
}


Как видите, это те функции, которые мы уже определили. Ок, давайте определим ContextMenu:
  PresidentListingContextMenu = new Ext.menu.Menu({
id: 'PresidentListingEditorGridContextMenu',
items: [
{ text: 'Modify this President', handler: modifyPresidentContextMenu },
{ text: 'Delete this President', handler: deletePresidentContextMenu },
'-',
{ text: 'Print this grid', handler: printListingContextMenu }
]
});


Последнее, что нам понадобится это перехватчик событий (actionlistener) который будет вызывать функцию onPresidentListingEditorGridContextMenu при нажатии правой кнопки (я пытался найти еще более длинное имя для этой функции, но... :))
  PresidentListingEditorGrid.addListener('rowcontextmenu', onPresidentListingEditorGridContextMenu);


Это конец учебника. Надеюсь он помог вам понять как работать с таблицами в ext. Теперь вы понимаете ее код. Не забывайте проверять наши материалы и играйте с различными методами, свойствами и событиями.

Вы можете оставить отзыв на форуме http://extjs.com/forum/showthread.php?p=144865#post144865
For more advanced features and usage of grids, I want to once again refer you to Michael Lecomte's tutorial.
Для более продвинутых функций таблиц посмотрите учебник Michael Lecomte's
Happy Griding!http://extjs.com/learn/Tutorial:Ext20_Grid_Editor_PHP_MySQL


Nicolas Bize.

Что запомнить из этой части

* Перехватчик событий необходимо прикреплять к таблице
* Вам не надо пересоздавать контекстное меню каждый раз заново, так как это глобальная переменная :)

Материалы этой части можно скачать по ссылке http://nicolas.bize.free.fr/ext/tutorial/Tutorial%20Part%2012.zip

Учебник: Grid PHP SQL Часть 11

Печать вашей таблицы.

Отлично... Я собираюсь сказать одну вещь до того, как буду заспамлен тоннами писем с критикой... Метод, описанный здесь для печати вашей таблицы не единственно возможный и не самый оригинальный. Это просто быстрый пример того, как можно распечатать таблицу.

Наш метод - это просто создание HTML файла и заполнение его массивом, содержащим отображаемые в таблице данные (все данные, не только 15 выведенных в данный момент)

Во-первых, мы добавим кнопку Print на нашу панель инструментов:


         ,'-', {
text: 'Print',
tooltip: 'Print me!',
handler: printListing,
iconCls:'print' // defined in our css
}


Давайте теперь определим функцию printListing(). Эта функция вызовет php функцию которая создаст HTML файл таблицы:

    function printListing(){
var searchquery = "";
var searchfirstname = "";
var searchlastname = "";
var searchparty = "";
var searchenteringoffice = "";
var searchleavingoffice = "";
var win; // our popup window
// check if we do have some search data...
if(PresidentsDataStore.baseParams.query!==null){searchquery = PresidentsDataStore.baseParams.query;}
if(PresidentsDataStore.baseParams.firstname!==null){searchfirstname = PresidentsDataStore.baseParams.firstname;}
if(PresidentsDataStore.baseParams.lastname!==null){searchlastname = PresidentsDataStore.baseParams.lastname;}
if(PresidentsDataStore.baseParams.party!==null){searchparty = PresidentsDataStore.baseParams.party;}
if(PresidentsDataStore.baseParams.enteringoffice!==null){searchenteringoffice = PresidentsDataStore.baseParams.enteringoffice;}
if(PresidentsDataStore.baseParams.leavingoffice!==null){searchleavingoffice = PresidentsDataStore.baseParams.leavingoffice;}

Ext.Ajax.request({
waitMsg: 'Please Wait...',
url: 'database.php',
params: {
task: "PRINT",
// we have to send all of the search

query: searchquery, // if we are doing a quicksearch, use this
firstname : searchfirstname, // if we are doing advanced search, use this
lastname : searchlastname,
party : searchparty,
enteringoffice : searchenteringoffice,
leavingoffice : searchleavingoffice,
currentlisting: PresidentsDataStore.baseParams.task // this tells us if we are searching or not
},
success: function(response){
var result=eval(response.responseText);
switch(result){
case 1:
win = window.open('./presidentslist.html','presidentslist','height=400,width=600,resizable=1,scrollbars=1, menubar=1');
win.print();
break;
default:
Ext.MessageBox.alert('Uh hu...','Unable to print the grid!');
break;
}
},
failure: function(response){
var result=response.responseText;
Ext.MessageBox.alert('error','could not connect to the database. retry later');
}
});
}


Ок, вы можете удивится почему мы должны передавать так много параметров... Дело в том что... мы хотим распечатать всю нашу таблицу а наш DataStore содержит лишь 15 записей. Поэтому наш PHP файл собирается сделать запросы заново еще раз. не ограничивая поиск частичными запросами. Затем наш database.php сложит все это в файл listpresidents.html.

Вот наш database.php файл (сначала выбор)
    case "PRINT":
printPresidents();
break;


А затем функция печати:
function printPresidents()
{
// Ok since we limited our queries to 15 entries, we need to do a query again and get everything.
if($_POST['currentlisting']=='LISTING'){ // we are doing a basic listing or using the quicksearch
$query = "SELECT * FROM presidents pr, parties pa WHERE pr.IDparty = pa.IDparty";
// Here we check if we have a query parameter :
if (isset($_POST['query']) && ($_POST['query'] != "")){
$query .= " AND (pr.firstname LIKE '%".$_POST['query']."%' OR pr.lastname LIKE '%".$_POST['query']."%')";
}
$query .= " ORDER BY tookoffice";
$result = mysql_query($query);
$nbrows = mysql_num_rows($result);
} else if($_POST['currentlisting']=='SEARCH'){
$query = "SELECT * FROM presidents pr, parties pa WHERE pr.IDparty = pa.IDparty";
$firstname = $_POST['firstname'];
$lastname = $_POST['lastname'];
$enteringoffice = $_POST['enteringoffice'];
$leavingoffice = $_POST['leavingoffice'];
$party = $_POST['party'];

if($firstname != ''){
$query .= " AND firstname LIKE '%".$firstname."%'";
};
if($lastname != ''){
$query .= " AND lastname LIKE '%".$lastname."%'";
};
if($party != ''){
$query .= " AND pr.IDparty = '".$party."'";
};
if ($enteringoffice) {
$query .= " AND tookoffice >= '".$enteringoffice."'";
};
if ($leavingoffice) {
$query .= " AND leftoffice <= '".$leavingoffice."'";
};
$result = mysql_query($query);
$nbrows = mysql_num_rows($result);
}

// We now have our array, let's build our HTML file :
$file = fopen("presidentslist.html",'w');
fwrite($file, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html xmlns='http://www.w3.org/1999/xhtml'><head><meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1' /><title>Printing the Grid</title><link rel='stylesheet' type='text/css' href='printstyle.css'/></head>");
fwrite($file, "<body><table summary='Presidents List'><caption>The Presidents of the United States</caption><thead><tr><th scope='col'>First Name</th><th scope='col'>Last Name</th><th scope='col'>Party</th><th scope='col'>Entering Office</th><th scope='col'>Leaving Office</th><th scope='col'>Income</th></tr></thead><tfoot><tr><th scope='row'>Total</th><td colspan='4'>");
fwrite($file, $nbrows);
fwrite($file, " presidents</td></tr></tfoot><tbody>");
for($i = 0; $i< $nbrows; $i++){
$data = mysql_fetch_array($result);
fwrite($file,'<tr');
if($i%1==0){
fwrite($file," class='odd'");
}
fwrite($file, "><th scope='row' id='r97'>");
fwrite($file, $data['firstname']);
fwrite($file,"</th><td>");
fwrite($file, $data['lastname']);
fwrite($file, "</td><td>");
fwrite($file, $data['name']);
fwrite($file, "</td><td>");
fwrite($file, codeDate($data['tookoffice']));
fwrite($file, "</td><td>");
fwrite($file, codeDate($data['leftoffice']));
fwrite($file, "</td><td> $");
fwrite($file, $data['income']);
fwrite($file, "</td></tr>");
}
fwrite($file, "</tbody></table></body></html>");
fclose($file);
echo '1'; // we are done.

}


На самом деле это очень просто. Мы просто вставляем запросы без ограничения количества записей и сохраняет полученный массив в HTML файл. Затем мы возвращаем 1.
Как будут выглядеть результаты на самом деле зависит от вас!
Некоторые предпочитают решения на основе экспорта в PDF формат. Вы можете найти эту информацию в топиках форумов.

Что запомнить:
Этот способ нельзя применить везде
Он не показывает колонки в нужном выбранном порядке и видимости
Это не самое лучшее решение.

Файлы этой части можно скачать здесь http://nicolas.bize.free.fr/ext/tutorial/Tutorial%20Part%2011.zip

Учебник: Grid PHP SQL Часть 10

Компонент быстрого поиска.

Отлично... Мы почти прошли этот учебник!!! Скоро вы сможете создавать свои собственные впечатляющие таблицы. Давайте изучим компонет QuickSearch.
Мы уже прошли тему как осуществлять обычный поиск по таблице. Для этого нужно чтобы пользователь нажал кнопку Search и ввел поля поиска... Ок, чтож, вы поймете что это может оказаться очень сложной задачей для большинства пользователей. И тут появляется мистер быстрый поиск QuickSearch. Компонент QuickSearch это просто текстовое поле в котором пользователь вводит текст для поиска, нажимает Ввод и тогда... А дальнейшее зависит от того как мы это сделаем..

Давайте продолжим и сделаем так ,чтобы текст искался всей таблице.
Отлично, до того как мы начнем нам нужно добавить еще один .js файл в заголовок HTML файла:
<script type="text/javascript" src="searchfield.js"></script>


Вы можете найти файл в папке examples/form архива extjs или скачайте его по ссылке http://extjs.com/deploy/dev/examples/form/SearchField.js
Во-первых мы просто добавим его на нашу панель инструментов. Нам просто надо установить некоторые основные параметры, которые передаем в datastore.
, '-', new Ext.app.SearchField({
store: PresidentsDataStore,
params: {start: 0, limit: 15},
width: 120
})


Ок, если вы достаточно опытны, мы можем просто открыть файл searchfield.js в любимом Блокноте и посмотреть что там внутри... И там вы увидите параметр называемый query который посылается при перезагрузке datastore. Да... Мы знаем что есть что! Теперь.. DataStore президентов может быть как в LISTING режиме так и в режиме SEARCHING (зависит от параметров). Итак, кнопка QuickSearch будет просто добавлять строку введенную в текстовое поле как дополнительный параметр.
Я просто собираюсь реализовать это при простом получении списка президентов. Я вам покажу как это сделать при поиске (быстрый поиск на существующем поиске)
Давайте откроем наш файл database.php.Теперь, когда мы обращаемся к функции getList() мы должны проверить нет ли у нас параметра query. И если есть мы должны найти похожие данные в нашей таблице. Теперь наша новая функци getList выглядит так:
  $query = "SELECT * FROM presidents pr, parties pa WHERE pr.IDparty = pa.IDparty";
// Here we check if we have a query parameter :
if (isset($_POST['query'])){
$query .= " AND (pr.firstname LIKE '%".addslashes($_POST['query'])."%' OR pr.lastname LIKE '%".addslashes($_POST['query'])."%')";
}
$result = mysql_query($query);
$nbrows = mysql_num_rows($result);
$start = (integer) (isset($_POST['start']) ? $_POST['start'] : $_GET['start']);
$end = (integer) (isset($_POST['limit']) ? $_POST['limit'] : $_GET['limit']);
$limit = $query." LIMIT ".$start.",".$end;
$result = mysql_query($limit);
[...]

Это так просто! :) Ну теперь вы можете брать параметр query, убрать все пробелы, разбить по словам для лучшего поиска. Вы даже можете делать поиск по годам и так далее... Реализация поиска, который мыуже сделали достаточно проста. Просто проверяйте переменную query до того, как сделаете запрос. Вот и все!

Скачать файлы проекта можно по ссылке http://nicolas.bize.free.fr/ext/tutorial/Tutorial%20Part%2010.zip

Учебник: Grid PHP SQL Часть 9

Панель страниц

Ну что, это снова вы... Да... Ок, давайте поговорим о панели страниц, которую вы можете увидеть внизу таблицы. Она мне всегда очень нравилась. Самое хорошее, что реализовать ее очень просто. Так давайте быстро сделаем ее!

Элемент PagingToolbar должен знать в основном две вещи: как много записей вы хотите отображать на странице и куда отсылать информацию о текущей странице.(мы будем посылать эту информацию в DataStore)
Итак... Сразу после панели инструментов в EditorGrid, давайте добавим нижнюю панель:
      bbar: new Ext.PagingToolbar({
pageSize: 15,
store: PresidentsDataStore,
displayInfo: true
})

Мы будем показывать по 15 президентов на странице. Мы уже почти все сделали! Единственная вещь теперь - как сообщить нашему PHP скрипту как мы будем делать наш постраничный просмотр. Элемент PagingToolbar использует два параметра - start (стартовая запись) и limit (количество записей). Они будут доступны в серверном скрипте и мы должны лишь только учесть их в нашем SQL запросе.
Давайте убедимся что посылаем эти параметры каждый раз, когда нам нужен список президентов: Каждую PresidentsDataStore.reload() или PresidentsDataStore.load() мы трансформируем в:
  PresidentsDataStore.reload({params: {start: 0, limit: 15}});

Сделайте изменения сами (они встречаются в mainscript.js) только в 3 местах)


Серверная часть
Это очень просто. Теперь, когда мы имеем переменные start и limit, нам нужно получить их значения и изменить конец нашего SQL запроса.
Вот изменения в фукции getList():
  $query = "SELECT * FROM presidents pr, parties pa WHERE pr.IDparty = pa.IDparty";
$result = mysql_query($query);
$nbrows = mysql_num_rows($result);
// Add this for the paging bar:
$start = (integer) (isset($_POST['start']) ? $_POST['start'] : $_GET['start']);
$end = (integer) (isset($_POST['limit']) ? $_POST['limit'] : $_GET['limit']);
$limit = $query." LIMIT ".$start.",".$end;
$result = mysql_query($limit);

Мы все еще хотим делать первый запрос так, чтбы получить общее количество записей (nbrows), но обратно в Ext будем отсылать массим содержащий 15 или менее записей, которые были запрошены.
Давайте не забудем сделать эти же изменения в функции поиска searchPresidents().

Файл с материалами этой части учебника здесь http://nicolas.bize.free.fr/ext/tutorial/Tutorial%20Part%209.zip

пятница, 5 декабря 2008 г.

Учебник: Grid PHP SQL Часть 8

Поиск по таблице

Отлично.. Как вы можете видеть мы может форматировать нашу таблицу так как захотим. Сегодня мы собираемся научиться как что-нибудь искать в таблице. Легко найти Джона Кеннеди, не потому конечно, что знаешь дату его избрания, но так же потому, что в нашей таблице всего 40 записей. Другое дело, если мы включили всех политиков США... Следовательно мы должны дать пользователю возможность сужать поиск.

Создание формы поиска

Когда мы создали форму мы объявили некоторые поля как глобальные переменные и работали с ними через методы name.setValue() или getValue(). Помните, что из-за этого мы можем иметь только одну форму для добавления записей. В нашем случае это не ограничивает ничего, а целью было сосредоточиться на табличной системе ext. Однако, чтобы показать как подобные вещи следует делать мы создадим следующую форму без глобальных переменных. Следовательно мы сможем открывать несколько форм поиска одновременно. Это очень полезно в наши дни.
Вот как мы сделаем. Когда мы наживаем на кнопку Advanced Search (Расширенный поиск) мы хотим, чтобы открылось новое окно для поиска с пустыми полями и готовое для поиска. Мы бы хотели сделать окно с расширенным поиском по нескольким полям.

Сначала, довайте добавим кнопку поиска Search в панель инструментов (в PresidentListingEditorGrid):
          }, '-', {
text: 'Search',
tooltip: 'Advanced Search',
handler: startAdvancedSearch, // search function
iconCls:'search' // we'll need to add this to our css
}

Давайте определим нашу функцию startAdvancedSearch. Вот немного для начала:
  function startAdvancedSearch(){
// local vars
var PresidentSearchForm;
var PresidentSearchWindow;
var SearchFirstNameItem;
var SearchLastNameItem;
var SearchPartyItem;
var SearchEnteringItem;
var SearchLeavingItem;

SearchFirstNameItem = new Ext.form.TextField({
fieldLabel: 'First Name',
maxLength: 20,
anchor : '95%',
maskRe: /([a-zA-Z0-9\s]+)$/
});

SearchLastNameItem = new Ext.form.TextField({
fieldLabel: 'Last Name',
maxLength: 20,
anchor : '95%',
maskRe: /([a-zA-Z0-9\s]+)$/
});

SearchPartyItem = new Ext.form.ComboBox({
fieldLabel: 'Party',
store:new Ext.data.SimpleStore({
fields:['partyValue', 'partyName'],
data: [['1','No Party'],['2','Federalist'],['3','Democratic-Republican'],['4','Democratic'],['5','Whig'],['6','Republican']]
}),
mode: 'local',
displayField: 'partyName',
valueField: 'partyValue',
anchor:'95%',
triggerAction: 'all'
});

SearchEnteringItem = new Ext.form.DateField({
fieldLabel: 'Entered Office after',
format : 'm/d/Y',
anchor:'95%'
});

SearchLeavingItem = new Ext.form.DateField({
fieldLabel: 'Left Office before',
format : 'm/d/Y',
anchor:'95%'
});
}

Знакомо выглядит? Так и должно быть!!! Это почти тоже самое что мы делали при создании формы.. За исключеним того, что мы не указываем никакие идентификаторы (запомните, что они должны быть уникальны, мы не можем иметь несколько элементов с одинаковыми id) Другое огромное отличие в том, что каждое поле, форма или окно определено прямо в функции. Нам не нужно беспокоится о скрытии окна, мы просто можем открыть другое окно как только это закроется.
Продолжим:
     PresidentSearchForm = new Ext.FormPanel({
labelAlign: 'top',
bodyStyle: 'padding: 5px',
width: 300,
items: [{
layout: 'form',
border: false,
items: [ SearchFirstNameItem,SearchLastNameItem,SearchPartyItem,SearchEnteringItem, SearchLeavingItem ],
buttons: [{
text: 'Search',
handler: listSearch
},{
text: 'Close',
handler: resetSearch
}]
}]
});

PresidentSearchWindow = new Ext.Window({
title: 'President Search',
closable:true,
width: 200,
height: 400,
plain:true,
layout: 'fit',
items: PresidentSearchForm
});


Важный момент в этом коде - просто вызовы двух функций, listSearch и resetSearch, которые должны быть определены здесь же локально (смотри следующий параграф) чтобы позволить им получить доступ к локальным значениям полей. У нас есть кнопки Search (искать) и Close (закрыть). Кнопка Search будет запускать поиск а кнопка Close, то очень интересно, также будет выполнять поиск. Вот почему...
    function listSearch(){
// render according to a SQL date format.
var startDate = "";
var endDate = "";
if(SearchEnteringItem.getValue() != "") {
startDate = SearchEnteringItem.getValue().format('Y-m-d');
}
if(SearchLeavingItem.getValue() != "") {
endDate = SearchLeavingItem.getValue().format('Y-m-d');
}

// change the store parameters
PresidentsDataStore.baseParams = {
task: 'SEARCH',
firstname: SearchFirstNameItem.getValue(),
lastname : SearchLastNameItem.getValue(),
party : SearchPartyItem.getValue(),
enteringoffice : startDate,
leavingoffice : endDate
};
// Cause the datastore to do another query :
PresidentsDataStore.reload();
}

function resetSearch(){
// reset the store parameters
PresidentsDataStore.baseParams = {
task: 'LISTING'
};
// Cause the datastore to do another query :
PresidentsDataStore.reload();
PresidentSearchWindow.close();
}

// once all is done, show the search window
PresidentSearchWindow.show();

Видите почему? Все что мы делаем это берем оригинальный datastore и изменяем его параметры. Мы затем просим его перезагрузится. А это выполняет новый запрос. Результаты затем отображаются так же как и в начале учебника.
Это объясняет функцию resetSearch. Чтобы сбросить поиск, мы должны установить оригинальные параметры DataStore (а это просто задача listing). Затем мы перезагружаем наш DataStore.

Маленькое упражнение с запросами
Вы уже использовали эти Ajax запросы? Серверный скрипт просто обрабатывает это вызов как любой другой... Давайте запишем ( в database.php)

Сначала в выборе :
    case "SEARCH":
searchPresidents();
break;


а затем идет наша функция searchPresidents()
function searchPresidents()
{
$query = "SELECT * FROM presidents pr, parties pa WHERE pr.IDparty = pa.IDparty";

$firstname = $_POST['firstname'];
$lastname = $_POST['lastname'];
$enteringoffice = $_POST['enteringoffice'];
$leavingoffice = $_POST['leavingoffice'];
$party = $_POST['party'];

if($firstname != ''){
$query .= " AND firstname LIKE '%".$firstname."%'";
};
if($lastname != ''){
$query .= " AND lastname LIKE '%".$lastname."%'";
};
if($party != ''){
$query .= " AND pr.IDparty = '".$party."'";
};
if ($enteringoffice) {
$query .= " AND tookoffice >= '".$enteringoffice."'";
};
if ($leavingoffice) {
$query .= " AND leftoffice <= '".$leavingoffice."'";
};

$result = mysql_query($query);
$nbrows = mysql_num_rows($result);
if($nbrows>0){
while($rec = mysql_fetch_array($result)){
// render the right date format
$rec['tookoffice']=codeDate($rec['tookoffice']);
$rec['leftoffice']=codeDate($rec['leftoffice']);
$arr[] = $rec;
}
$jsonresult = JEncode($arr);
echo '({"total":"'.$nbrows.'","results":'.$jsonresult.'})';
} else {
echo '({"total":"0", "results":""})';
}
}

Итак мы рассмотрели простой поиск. И он работает достаточно хорошо. Теперь у вас может быть сотни записей а найти любую из них вы сможете очень быстро.

Что запомнить из этой части:
id обьектов должны быть уникальны
Работа с параметрами DataStore

Файлы этой части можно скачать здесь

Источник: http://extjs.com/learn/w/index.php?title=Tutorial:Grid_PHP_SQL_Part8

Учебник: Grid PHP SQL Часть 7

Настраиваемые CSS стили

Отлично, рад видеть, что вы все еще со мной! Если вы зашли так далеко, то вы уже наверное обладаете знаниями о редактировании, добавлении и удалении записей в таблице. Вы наверное провели часы, играясь с таблицей и потратили много времени, показывая свои достижения детям, супруге, родителям а может даже своей собаке. Наиболее вероятно, они ответили: "Отлично! Но есть ли вариант изменить css стиль вот тех ячеек??" Чтож, звучит загадочно.. ЭТО ВОЗМОЖНО! И... это на самом деле не очень сложно...

Давайте начнем сначала

В каждую колонку, добавленную в ColumnModel, мы можем добавить параметр, называемый renderer (построитель). Этот параметр просто функция, которая позволяет нам сказать Ext как отображать ячейки в этой колонке.
Функция постройки имеет 6 параметров: (data (данные), cell (ячейка), rowIndex (индекс ряда), columnIndex (индекс колонки), store). Играя с этими значениями мы можем установить любой стиль в нужном месте. На самом деле, это почти то же самое, что мы уже сделали в нашем NumberField... (мы написали '$' прямо за значением)

Играем с цветами

Мы найдем президентов, которые имели больше всего голосов. Для этого мы изменим цвет ячейки голосов в соответствии с её значением. Нам надо изменить функцию постройки колонки Income:
        renderer: function(value, cell){ 
var str = '';
if(value > 1000000){
str = "<span style='color:#336600;'>$ " + value + "</span>";
} else if (value > 100000 && value < 1000000){
str = "<span style='color:#FF9900;'>$ " + value + "</span>";
} else {
str = "<span style='color:#CC0000;'>$ " + value + "</span>";
}
return str;
},


Играем с css стилями

Вы наверное показывали вашу таблицу друзьям а те очень сердились, что не могут изменить идентификаторы ID президентов (эта колонка доступна для редактирования) и должно быть убили вашу мышку так, как будто играли в Diablo очень много времени. Давайте убережемся от таких друзей и изменим css стили ячеек. Это просто делается через cell.css поле.
Давайте добавим следующий код в style.css :
.readonlycell { 
background-color:#CCCCCC !important;
}


Теперь для IDPresident и IDParty давайте добавить построитель, который установит css стили для ячеек:
        renderer: function(value, cell){ 
cell.css = "readonlycell";
return value;
}


Запомните, функция построителя должна возвращать какие-то данные. Иначе вы не увидите ничего...

Любой стиль для любой ячейки
Как вы можете видеть, вы можете изменять ячейки так как захотите. В данном примере, мы изменили фоновое изображение для имени и фамилии президента. Просто так, без причины.. Я бы не советовар перегружать вашу таблицу большим количеством css стилей, так как иначе ваши глаза просто заболят, когда таблица появится на экране.

Что запомнить из этой части:
Используйте css стили лишь для того, чтобы было понятно и удобно работать с таблицой.

Файлы этой части можно скачать здесь

Источник: http://extjs.com/learn/w/index.php?title=Tutorial:Grid_PHP_SQL_Part7

Учебник: Grid PHP SQL Часть 6

Удаление записи

В предыдущих частях мы создали редактируемую таблицу с функцией добавления записей в нее. Давайте подумаем. Мы еще многое не реализовали, но уже написали пару сотен строк кода...
Это показывает насколько важно структурировать код. Есть множество топиков на форумах и учебников как правильно писать Ext код. Убедитесь, что используете Firefox и отладчик (Firebug) и проверяйте результаты в IE. Вы можете проверять ваши .js файлы с помощью JSLint чтобы убедится в их корректности. Очень легко потеряться в Ext коде, так как большинство структур построено в Json формате. Правильно расставляйте отступы. Если вам еще не понятно, посмотрите ЧАВО от Mickael's по таблицам.

Эта часть учебника научит нас удалять записи из таблицы. Это будет очень просто. Мы просто выбираем ряд таблицы и нажимаем кнопку Delete (Удалить), Ajax запрос посылается и трансформируется в SQL запрос, как мы делали в других случаях. Вы просто можете попробовать сделать это и посмотреть как это легко.

А что делать если мы хотим удалить несколько рядов. Как это сделать?? Вы можете выбирать носколько рядов с помощью CTRL или SHIFT.. Видите? Чтобы удалить все эти ряды мы должны их все обработать и мы это сделаем с помощью цикла и создадим массив который будет содержать все идентификаторы IDPresidents для каждого ряда. Затем мы просто перекодируем массив и отошлем его на сервер как параметр.

Давайте посмотрим, как это сделать:
  var selections = PresidentListingEditorGrid.selModel.getSelections();
var prez = [];
for(i = 0; i< PresidentListingEditorGrid.selModel.getCount(); i++){
prez.push(selections[i].json.IDpresident);
}
var encoded_array = Ext.encode(prez);

Как вы можете видеть этот код достаточно прямолинейный. Давайте внедрим его в наш скрипт.
Во-первых давайте добавим кнопку удаления на панель инструментов (мы также добавили CSS код), вот так:
  tbar: [
{
text: 'Add a President',
tooltip: 'Great tooltips...',
iconCls:'add', // reference to our css
handler: displayFormWindow
}, '-', {
text: 'Delete selection',
tooltip: 'Jose, can you seeeee??',
handler: confirmDeletePresidents, // Confirm before deleting
iconCls:'remove'
}]

Так как люди, которые будут использовать ваши приложения не всегда сообразительны (я не исключение) и могут просто нажать кнопку удаления случайно, нам нужно спросить подтверждение для удаления с помощью функции:
  function confirmDeletePresidents(){
if(PresidentListingEditorGrid.selModel.getCount() == 1) // only one president is selected here
{
Ext.MessageBox.confirm('Confirmation','Do you not like that president at all?', deletePresidents);
} else if(PresidentListingEditorGrid.selModel.getCount() > 1){
Ext.MessageBox.confirm('Confirmation','Delete those presidents?', deletePresidents);
} else {
Ext.MessageBox.alert('Uh oh...','You can\'t really delete something you haven\'t selected huh?');
}
}


Полная функция удаления

Итак, что мы имеем:
  function deletePresidents(btn){
if(btn=='yes'){
var selections = PresidentListingEditorGrid.selModel.getSelections();
var prez = [];
for(i = 0; i< PresidentListingEditorGrid.selModel.getCount(); i++){
prez.push(selections[i].json.IDpresident);
}
var encoded_array = Ext.encode(prez);
Ext.Ajax.request({
waitMsg: 'Please Wait',
url: 'database.php',
params: {
task: "DELETEPRES",
ids: encoded_array
},
success: function(response){
var result=eval(response.responseText);
switch(result){
case 1: // Success : simply reload
PresidentsDataStore.reload();
break;
default:
Ext.MessageBox.alert('Warning','Could not delete the entire selection.');
break;
}
},
failure: function(response){
var result=response.responseText;
Ext.MessageBox.alert('error','could not connect to the database. retry later');
}
});
}
}


Теперь подкорректируем серверный скрипт database.php:

Наши варианты выбора пополнятся:
    case "DELETEPRES":
deletePresidents();
break;

А теперь нам нужно реализовать функцию deletePresidents(). Эта небольшая часть, которая строит запрос взависимости от выбора. На не нужно делать столько же запросов, сколько нам нужно удалить записей. Мы должны сделать все удаления в одном запросе. (Firebug очень здесь поможет) В любом случаем запрос создается следующей функцией:
function deletePresidents(){
$ids = $_POST['ids']; // Get our array back and translate it :
if (version_compare(PHP_VERSION,"5.2","<")) {
require_once("./JSON.php");
$json = new Services_JSON();
$idpres = $json->decode(stripslashes($ids));
} else {
$idpres = json_decode(stripslashes($ids));
}

// You could do some checkups here and return '0' or other error consts.

// Make a single query to delete all of the presidents at the same time :
if(sizeof($idpres)<1){
echo '0';
} else if (sizeof($idpres) == 1){
$query = "DELETE FROM presidents WHERE IDpresident = ".$idpres[0];
mysql_query($query);
} else {
$query = "DELETE FROM presidents WHERE ";
for($i = 0; $i < sizeof($idpres); $i++){
$query = $query . "IDpresident = ".$idpres[$i];
if($i<sizeof($idpres)-1){
$query = $query . " OR ";
}
}
mysql_query($query);
}
// echo $query; This helps me find out what the heck is going on in Firebug...
echo '1';
}


Что надо запомнить из этой части:
SelectionModels
Firebug сохранит много вашего времени

Файлы этой части вы найдете здесь

Источник: http://extjs.com/learn/w/index.php?title=Tutorial:Grid_PHP_SQL_Part6

Учебник: Grid PHP SQL Часть 5

Добавление новой записи

Отлично, мы создали нашу таблицу и теперь даже можем вносить в нее изменения. Они записываются прямо в нашу SQL таблицу. Эта часть учебника научит вас добавлять новые записи в таблицу.

Есть много путей сделать это. Один из вариантов - это вставка пустой новой записи и ее редактирование. Это вариант, предпочитаемый M. LeCompte. Мы собираемся сделать это несколько другим способом. Мы создадим новое окно, которое будет содержать форму для заполнения данных президента.

Создание формы
Отлично, давайте посмотрим, какие поля формы нам понадобятся:
* Текстовое поле имени президента
* Текстовое поле фамилии
* Два поля даты - для ввода дат президентсва.
* Числовое поле для ввода поступлений
* Выпадающий список для партии

Все это надо поместить в форму и отобразить в окне. Давайте сделаем это вначале скрипта:
  var PresidentCreateForm;
var PresidentCreateWindow;

Чтобы каджому все было ясно и понятно мы будем создавать каждое поле до того, как добавлять его в форму. Каждое поле будет глобальной переменной (это не лучший вариант, позже вы сможете создавать различные идентификаторы для каждого поля и ссылаться на них) Просто запомните, вам бы не захотелось иметь 35078 глобальных переменных в программе... Также, почему мы делаем сейчас все таким способом - это защита от запуска двух форм одновременно.
Запомните: Если вы еще не ознакомились с нашей "подложкой" для кода приложения, позже вам лучше посмотреть учебник по Подложке (Структуре) Приложений сделаный Jozef Sakalos.
Давайте определим наши поля вначале скрипта:
  var FirstNameField;
var LastNameField;
var EnteringOfficeField;
var LeavingOfficeField;
var IncomeField;
var PartyField;

Нам нужно написать код который описывает назначения каждого поля: (внутри функции onReady):
  FirstNameField = new Ext.form.TextField({
id: 'FirstNameField',
fieldLabel: 'First Name',
maxLength: 20,
allowBlank: false,
anchor : '95%',
maskRe: /([a-zA-Z0-9\s]+)$/
});

LastNameField = new Ext.form.TextField({
id: 'LastNameField',
fieldLabel: 'Last Name',
maxLength: 20,
allowBlank: false,
anchor : '95%',
maskRe: /([a-zA-Z0-9\s]+)$/
});

EnteringOfficeField = new Ext.form.DateField({
id:'EnteringOfficeField',
fieldLabel: 'Entering Office',
format : 'm/d/Y',
allowBlank: false,
anchor:'95%'
});

LeavingOfficeField = new Ext.form.DateField({
id:'LeavingOfficeField',
fieldLabel: 'Leaving Office',
allowBlank: false,
format : 'm/d/Y',
anchor:'95%'
});

IncomeField = new Ext.form.NumberField({
id:'IncomeField',
fieldLabel: 'Income',
allowNegative: false,
allowBlank: false,
anchor:'95%'
});

Все очень просто и понятно. Теперь у нас есть поля... Вам нравится? Запомните, что поле должно быть связано с данными в SQL таблице. В 3 Части мы говорили об этом при использовании DataStore. В 4 части мы видим три различных варианта заполнения нашего выпадающего списка.
В этой части учебника мы продолжим использование SimpleStore. Вам следует держать в памяти, что заполнение нашего списка из предопределенного HTML списка может создать некоторые проблемы совместимости броузера (я подумал о бедных пользователях IE немножко...)
  PartyField = new Ext.form.ComboBox({
id:'PartyField',
fieldLabel: 'Party',
store:new Ext.data.SimpleStore({
fields:['partyValue', 'partyName'],
data: [['1','No Party'],['2','Federalist'],['3','Democratic-Republican'],['4','Democratic'],['5','Whig'],['6','Republican']]
}),
mode: 'local',
displayField: 'partyName',
allowBlank: false,
valueField: 'partyValue',
anchor:'95%',
triggerAction: 'all'
});


Создание формы

Теперь все поля созданы и готовы, надо создать форму для них:
  PresidentCreateForm = new Ext.FormPanel({
labelAlign: 'top',
bodyStyle:'padding:5px',
width: 600,
items: [{
layout:'column',
border:false,
items:[{
columnWidth:0.5,
layout: 'form',
border:false,
items: [FirstNameField, LastNameField, PartyField]
},{
columnWidth:0.5,
layout: 'form',
border:false,
items: [EnteringOfficeField, LeavingOfficeField, IncomeField]
}]
}],
buttons: [{
text: 'Save and Close',
handler: createThePresident
},{
text: 'Cancel',
handler: function(){
// because of the global vars, we can only instantiate one window... so let's just hide it.
PresidentCreateWindow.hide();
}
}]
});

PresidentCreateWindow= new Ext.Window({
id: 'PresidentCreateWindow',
title: 'Creating a New President',
closable:true,
width: 610,
height: 250,
plain:true,
layout: 'fit',
items: PresidentCreateForm
});


Ок... неплохо да? Недо запомнить одну важную вещь, то что каждый вложенный элемент имеет поле layout (подложка). Не забудьте его, так как это тема множества топиков на форумах... Ну хорошо, теперь у нас есть отличная форма. До того, как мы ее покажем, нам нужно перехватывать события кнопок и создать функции для каждого из них для сохранения данных. Хорошая штука в том, что мы так создали форму в том мы можем напрямую получать значения полей с помощью методов setValue() или getValue().

Давайте сначала создадим небольшие функции для работы с формой.
  // reset the Form before opening it
function resetPresidentForm(){
FirstNameField.setValue('');
LastNameField.setValue('');
EnteringOfficeField.setValue('');
LeavingOfficeField.setValue('');
IncomeField.setValue('');
PartyField.setValue('');
}

// check if the form is valid
function isPresidentFormValid(){
return(FirstNameField.isValid() && LastNameField.isValid() && EnteringOfficeField.isValid() && LeavingOfficeField.isValid() && IncomeField.isValid() && PartyField.isValid());
}

// display or bring forth the form
function displayFormWindow(){
if(!PresidentCreateWindow.isVisible()){
resetPresidentForm();
PresidentCreateWindow.show();
} else {
PresidentCreateWindow.toFront();
}
}


Создание президента
Теперь нам нужно реализовать метод сохранения. Вы найдете его достаточно простым по сравнению с тем, что мы делали при редактировании ячеек:
  function createThePresident(){
if(isPresidentFormValid()){
Ext.Ajax.request({
waitMsg: 'Please wait...',
url: 'database.php',
params: {
task: "CREATEPRES",
firstname: FirstNameField.getValue(),
lastname: LastNameField.getValue(),
enteringoffice: EnteringOfficeField.getValue().format('Y-m-d'),
leavingoffice: LeavingOfficeField.getValue().format('Y-m-d'),
income: IncomeField.getValue(),
party: PartyField.getValue()
},
success: function(response){
var result=eval(response.responseText);
switch(result){
case 1:
Ext.MessageBox.alert('Creation OK','The president was created successfully.');
PresidentsDataStore.reload();
PresidentCreateWindow.hide();
break;
default:
Ext.MessageBox.alert('Warning','Could not create the president.');
break;
}
},
failure: function(response){
var result=response.responseText;
Ext.MessageBox.alert('error','could not connect to the database. retry later');
}
});
} else {
Ext.MessageBox.alert('Warning', 'Your Form is not valid!');
}
}

Отлично... Это было неплохо, да? Теперь все что нам нужно это каким-нибудь образом вызывать displayFormWindow(). Мы можем делать это через контекстное меню (правый клик) который опишем позже, или мы можем сделать это с помощью панели инструментов. Давайте сделаем эту панель:
Нам просто нужно добавить следующее в наш конструктор EditorGrid:
      tbar: [{
text: 'Add a president',
tooltip: 'Great Tooltip',
iconCls:'add', // this is defined in our styles.css
handler: displayFormWindow
}]


Последние штрихи

Отлично. Мы еще не все сделали. Нам нужна серверная обработка запроса CREATEPRES.
Идем в файл database.php и вставим ее:
В выборе:
    case "CREATEPRES":
createPresident();
break;

И напишем саму функцию:
function createPresident(){

$firstname = addslashes($_POST['firstname']);
$lastname = addslashes($_POST['lastname']);
$enteringoffice = $_POST['enteringoffice'];
$leavingoffice = $_POST['leavingoffice'];
$income = $_POST['income'];
$party = $_POST['party'];

// Here we should probably do some database checking, to make sure that we do not have the same entry twice for ex... And we would return a different error code (ECHO '0' or whatever you want...) For now we'll pretend like the entry is valid.
$query = "INSERT INTO presidents (`IDpresident` ,`IDparty` ,`firstname` ,`lastname` ,`tookoffice` ,`leftoffice` ,`income` ) VALUES (NULL , '$party', '$firstname', '$lastname', '$enteringoffice', '$leavingoffice', '$income')";
$result = mysql_query($query);
echo '1';
}


Вот теперь вы можете добавлять президентов в вашу таблицу.

Что надо запомнить из этой части:
-Обязательно про SimpleStore

Файлы этой части учебника доступны здесь

Иточник: http://extjs.com/learn/Tutorial:Grid_PHP_SQL_Part5

Учебник: Grid PHP SQL Часть 4

Редактирование таблицы
Отлично, теперь у нас есть таблица отображающая материал. Вы уже можете увидесть все отличные возможности, которые предоставляет ext. Вы можете изменять или перемещать колонки. Вы можете решать, что показывать, а что нет. И самое лучшее, вы можете сделать маленькие красные треугольники в углах ячеек при редактировании.
Теперь вы спросите меня.Что на самом деле происходит когда я редактирую содержимое ячейки? Чтож, ответ достаточно простой... Ничего. Однако, Ext будет делать множество полезных вещей, он позволяет нам обрабатывать сохранение данных ,что означает... Это то что мы должны запрограммировать сами. Об этом и пойдет речь в этой части.

Ограничим некоторые поля.
До того, как мы двинемся вперед, есть одна вещь, которую нам нужно исправить. Как вы можете видеть в SQL таблицах, политические партии это предопределенный список (6 партий вобщем) и он не должен выходить за пределы этого списка.
Лучший путь реализовать это - изменить текстовое поле на выпадающий список в ColumnModel и задать ему различные свойства. Есть два простых варианта:
Вы можете создать новый DataStore который будет подключаться к database.php и запрашивать список партий для отображения. Просто подключите выпадающий список к этому DataStore и все сделано. Это особенно полезно, когда данные могут изменится за это время, но это не случай нашей программы.
Вы можете создать невидимый список в главном HTML файле и обновлять значения своего списка в соотетствии с этим. Это хороший подход, когда вы полностью уверены в статичности данных. Это делается через команду "transform".
Вы можете создать Simplestore, SimpleStore это DataStore который определен прямо в скрипте. Он затем доступен для изменения, но не подключен к прокси и не запрашивает никакие данные.
Теперь, это было бы очень хорошей практикой сделать все с помощью DataStore. Я уверен что это на самом деле вариант, который вы будете употребляять чаще всего и я бы посоветовал вам сделать это сейчас. В качестве маленькой подсказки я напишу изменения в ColumnModel, которые надо сделать.
{ 
header: 'Party',
dataIndex: 'Name',
width: 120,
editor: new Ext.form.ComboBox({
store: PartiesListingDataStore, // or whatever you've called it
displayField:'Name', // we have two indexes, ID and Name
typeAhead: true,
mode: 'remote',
triggerAction: 'all',
selectOnFocus:true
}),
hidden: false
}

Вы его получили? Отлично, тогда давайте перейдем к третьему варианту решения с помощью SimpleStore.

Нам понадобится хранилище, содержащее все наши статичные данные. Давайте сделаем его сейчас.
   header: 'Party',
dataIndex: 'PartyName',
width: 150,
editor: new Ext.form.ComboBox({
typeAhead: true,
triggerAction: 'all',
store:new Ext.data.SimpleStore({
fields:['partyValue', 'partyName'],
data: [['1','No Party'],['2','Federalist'],['3','Democratic-Republican'],['4','Democratic'],['5','Whig'],['6','Republican']]
}),
mode: 'local',
displayField: 'partyName',
valueField: 'partyValue',
listClass: 'x-combo-list-small'
})


Как вы можете видеть, мы можем определить какое поле отображать а какое поле использовать как значение. Теперь это выглядит привлекательно. Это означает что мы может напрямую посылать нашему PHP скрипту значение партии (IDparty) и нам не надо дополнительных запросов для этого.
Вот мы и сделали это. Испытайте его работу, вам понравится так, как будто вы только что попробовали вкусный шоколад.

Редактирование

И вот начинается сердце этой части... Как нам сохранять измененные внесения. Есть несколько путей решения этой проблемы...
Вы можете сохранять ряд каждый раз, как только какие то данные изменились. Это будет хорошо работать если у вас несколько независимых ячеек. Это не будет лучшим решеним, если вам надо сохранить много данных.
Вы можете пройтись циклом по таблице и лишь сохранить только те ячейки, которые были изменены в данной точке (регулярное автосохранение или кнопка для сохранения)
Вы можете просто сохранить всю таблицу в какое-то время.
Вы можете ничего не сохранять а пойти съесть чизбургер.
Выбор по-правде за вами, но это зависит от того, что надо сохранять в данный момент. Я не могу сказать что один метод луше чем другой...
Мы выберем первый вариант и будем сохранять президента каждый раз когда одна из этих ячеек изменяется. Мы сделаем это с помощью функции saveThePresident. Эта функция будет делать запрос к файлу database.php с видом задачи 'UPDATEPRES' и отсылать все данные строки. Затем она будет ожидать ответ от нашего серверного PHP скрипта. Все просто: database.php будет отсылать 1 если все сохранено удачно или 0 в другом случае. Если измение было успешно, мы просто перезагрузим наше хранилище и изменения автоматически отобразятся в таблице.
  function saveThePresident(oGrid_event){
Ext.Ajax.request({
waitMsg: 'Please wait...',
url: 'database.php',
params: {
task: "UPDATEPRES",
IDpresident: oGrid_event.record.data.IDpresident,
FirstName: oGrid_event.record.data.FirstName,
LastName: oGrid_event.record.data.LastName,
PartyName: oGrid_event.record.data.PartyName,
TookOffice: oGrid_event.record.data.TookOffice.format('Y-m-d'), // this time we'll format it thanks to ext
LeftOffice: oGrid_event.record.data.LeftOffice.format('Y-m-d'),
Income: oGrid_event.record.data.Income
},
success: function(response){
var result=eval(response.responseText);
switch(result){
case 1:
PresidentsDataStore.commitChanges(); // changes successful, get rid of the red triangles
PresidentsDataStore.reload(); // reload our datastore.
break;
default:
Ext.MessageBox.alert('Uh uh...','We couldn\'t save him...');
break;
}
},
failure: function(response){
var result=response.responseText;
Ext.MessageBox.alert('error','could not connect to the database. retry later');
}
});
}

Ок, теперь, когда наша функция готова нам нужно прикрепить ее к событию изменения содержимого ячейки пользователем.
Это делается в самом конце скрипта.
PresidentListingEditorGrid.on('afteredit', saveThePresident);

Запомните: место, где она будет вставлена неудачное. Я бы мог вставить эту строку в конструктор или сразу после того как моя таблица была создана.
Отлично, клиентская часть сделана. Теперь нам надо перейти к нашему серверу и исправить наш файл database.php file.
Сперва нам надо будет распознавать название задачи, поэтому добавим еще одно значение в список выбора:
    case "UPDATEPRES":
updatePresident();
break;

Этого недостаточно. Давайте напишем функцию updatePresident()
function updatePresident()
{
$IDpresident = $_POST['IDpresident'];
$FirstName = addslashes($_POST['FirstName']);
$LastName = addslashes($_POST['LastName']);
$PartyName = $_POST['PartyName'];
$TookOffice = $_POST['TookOffice'];
$LeftOffice = $_POST['LeftOffice'];
$Income = $_POST['Income'];

// First, find the $IDparty
$query = "SELECT IDParty FROM parties WHERE Name='".$PartyName."'";
$result = mysql_query($query);
if(mysql_num_rows($result)>0){
$arr = mysql_fetch_array($result);
$IDparty = $arr['IDParty'];
} else {
echo '0'; // failure
}

// Now update the president
$query = "UPDATE presidents SET firstname = '$FirstName', lastname = '$LastName', tookoffice = '$TookOffice', leftoffice =

'$LeftOffice', IDparty = '$IDparty', income='$Income' WHERE IDpresident=$IDpresident";
$result = mysql_query($query);
echo '1'; // success
}


Отлично, теперь протестируйте то что у нас получилось. Увидели как это работает? Нравиться?

Запомните: Мы просто сделали два запроса (хотя можно было бы сделать и с помощью одного - Догадалиь как?)
один - для получения IDparty для выбранной партии а другой запрос для самого изменения. Запомните, что эти запросы не проверяют ошибки при запросе.

Вы можете скачать файлы этого задания здесь

четверг, 4 декабря 2008 г.

Учебник: Grid PHP SQL Часть 3

Создание в Ext таблицы (Grid)

Отлично... Время отобразить наши результаты в прекрасной ext таблице... До этого ознакомимся с двумя вещами DataStores и ColumnModels.
Давайте начнем писать на файл mainscript.js :
var PresidentsDataStore;         // это будет наш datastore
var PresidentsColumnModel; // это будет columnmodel
var PresidentListingEditorGrid;
var PresidentListingWindow;

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

DataStores
DataStore это массивы в которых хранятся данные (как вы уже наверное и сами догадались). Их назначение в том, чтобы получать данные из разных типов источников - от статических XML массивов до... динамических JSON источников!! Таблица (Grid) должна быть связана с DataStore. Если вы хотите изменить содержимое таблицы, вам в общем случае нужно изменить содержимое DataStore, а затем вызвать метод refresh().
Нам нужен DataStore которое будет подключяться к нашему файлу database.php, посылать задачу 'LISTING' и затем считывать результаты. Мы затем дадим соответствующие имена, которые сохранены в массие. Вот как мы построим простой DataStore:
  PresidentsDataStore = new Ext.data.Store({
id: 'PresidentsDataStore',
proxy: new Ext.data.HttpProxy({
url: 'database.php', // File to connect to
method: 'POST'
}),
baseParams:{task: "LISTING"}, // this parameter asks for listing
reader: new Ext.data.JsonReader({ // we tell the datastore where to get his data from
root: 'results',
totalProperty: 'total',
id: 'id'
},[
{name: 'IDpresident', type: 'int', mapping: 'IDpresident'},
{name: 'FirstName', type: 'string', mapping: 'firstname'},
{name: 'LastName', type: 'string', mapping: 'lastname'},
{name: 'IDparty', type: 'int', mapping: 'IDparty'},
{name: 'PartyName', type: 'string', mapping: 'name'},
{name: 'TookOffice', type: 'date', mapping: 'tookoffice'},
{name: 'LeftOffice', type: 'date', mapping: 'leftoffice'},
{name: 'Income', type: 'float', mapping: 'income'}
]),
sortInfo:{field: 'IDpresident', direction: "ASC"}
});

Ок, не так уж и сложно?

ColumnModels
Теперь нам нужно определить ColumnModel для нашей таблицы. Проще говоря, модель колонок указывает Ext то, как мы опеределили колонки таблицы. Будем мы отображать числа? Строки? Хотим ли мы сделать их редактируемыми? По факту, ColumnModel может делать гораздо больше чем мы обсудим в учебнике.

Теперь, мы просто отобразим основные колонки, как вы видите в следующем коде:
  PresidentsColumnModel = new Ext.grid.ColumnModel(
[{
header: '#',
readOnly: true,
dataIndex: 'IDpresident', // this is where the mapped name is important!
width: 50,
hidden: false
},{
header: 'First Name',
dataIndex: 'FirstName',
width: 150,
editor: new Ext.form.TextField({ // rules about editing
allowBlank: false,
maxLength: 20,
maskRe: /([a-zA-Z0-9\s]+)$/ // alphanumeric + spaces allowed
})
},{
header: 'Last Name',
dataIndex: 'LastName',
width: 150,
editor: new Ext.form.TextField({
allowBlank: false,
maxLength: 20,
maskRe: /([a-zA-Z0-9\s]+)$/
})
},{
header: 'ID party',
readOnly: true,
dataIndex: 'IDparty',
width: 50,
hidden: true // we don't necessarily want to see this...
},{
header: 'Party',
dataIndex: 'PartyName',
width: 150,
readOnly: true
},{
header: "Income",
dataIndex: 'Income',
width: 150,
renderer: function(v){ return '$ ' + v; }, // we tell Ext how to display the number
editor: new Ext.form.NumberField({
allowBlank: false,
decimalSeparator : ',',
allowDecimals: true,
allowNegative: false,
blankText: '0',
maxLength: 11
})
}]
);
PresidentsColumnModel.defaultSortable= true;


Часть, которую вы очень долго ждали.
Ок! Теперь, когда мы установили источник данных для чтения и настроили способ для отображения данных в таблице мы готовы создать саму таблицу!! Следующая часть кода - просто как кусок пирожного. Убедитесь, что не забыли вызвать load() в DataStore (load() запросит наш файл database.php):
  PresidentListingEditorGrid =  new Ext.grid.EditorGridPanel({
id: 'PresidentListingEditorGrid',
store: PresidentsDataStore, // the datastore is defined here
cm: PresidentsColumnModel, // the columnmodel is defined here
enableColLock:false,
clicksToEdit:1,
selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
});

PresidentListingWindow = new Ext.Window({
id: 'PresidentListingWindow',
title: 'The Presidents of the USA',
closable:true,
width:700,
height:350,
plain:true,
layout: 'fit',
items: PresidentListingEditorGrid // We'll just put the grid in for now...
});

PresidentsDataStore.load(); // Load the data
PresidentListingWindow.show(); // Display our window

});


Что надо запомнить из этой части:
* Таблица должна иметь как datastore так и columnmodel для корректного отображения данных
* datastore необходимо перезагружать если в базе данных были сделаны изменения.

Материалы этой части можно скачать здесь

Источник: http://extjs.com/learn/Tutorial:Grid_PHP_SQL_Part3