Статьи

Построить Ajax Data Grid с помощью CodeIgniter и jQuery

На этом уроке мы создадим библиотеку CodeIgniter, которая позволит нам автоматически генерировать сетки данных для управления любой таблицей базы данных. Я объясню каждый шаг, необходимый для создания этого класса; таким образом, вы, вероятно, изучите некоторые новые методы / концепции ООП в процессе!

В качестве бонуса мы продолжим писать код jQuery, который позволит пользователю обновлять содержимое сетки данных, не дожидаясь обновления страницы.


В этом руководстве предполагается, что вы немного разбираетесь в платформах CodeIgniter и jQuery.

Сетка данных — это таблица, которая отображает содержимое базы данных или таблицы вместе с элементами управления сортировкой.

Сетка данных — это таблица, которая отображает содержимое базы данных или таблицы вместе с элементами управления сортировкой. В этом уроке нам будет предложено предоставить эту функцию, а также избавить пользователя от ожидания обновления страницы при каждом выполнении операции. Благодаря jQuery это будет довольно простой задачей!

А как насчет пользователей, у которых не включен Javascript? Не волнуйтесь, мы их тоже компенсируем!


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

01
02
03
04
05
06
07
08
09
10
<?php
class Datagrid{
    private $hide_pk_col = true;
    private $hide_cols = array();
    private $tbl_name = »;
    private $pk_col = »;
    private $headings = array();
    private $tbl_fields = array();
}
?>

Класс Datagrid вполне может быть добавлен в папку application / library, но мы собираемся добавить его в качестве помощника в среду CodeIgniter. Почему? Поскольку загрузка библиотек не позволяет нам передавать аргументы в конструктор класса, то загрузка его в качестве помощника решит проблему. Этот момент будет иметь больше смысла для вас, когда мы закончим писать конструктор.

01
02
03
04
05
06
07
08
09
10
11
12
public function __construct($tbl_name, $pk_col = ‘id’){
    $this->CI =& get_instance();
    $this->CI->load->database();
    $this->tbl_fields = $this->CI->db->list_fields($tbl_name);
    if(!in_array($pk_col,$this->tbl_fields)){
        throw new Exception(«Primary key column ‘$pk_col’ not found in table ‘$tbl_name'»);
    }
    $this->tbl_name = $tbl_name;
    $this->pk_col = $pk_col;
    $this->CI->load->library(‘table’);
     
}

У нас уже много чего происходит; но не волнуйтесь, я вам все объясню в следующем абзаце.

Конструктор принимает два аргумента: первый — имя таблицы в вашей базе данных, которую вы хотите отобразить как сетку данных для пользователя; второй параметр — это имя столбца, служащего первичным ключом для этой таблицы (подробнее об этом позже). Внутри тела конструктора мы создаем экземпляр объекта CodeIgniter, объекта базы данных и класса / библиотеки HTML-таблиц. Все это будет необходимо в течение всего времени существования объекта Datagrid и уже встроено в инфраструктуру CI. Обратите внимание, что мы также проверяем, действительно ли первичный ключ существует в данной таблице, и, в случае его отсутствия, мы выдаем исключение, сообщающее об ошибке. Теперь переменная-член $this->tbl_fields будет доступна для дальнейшего использования, поэтому нам не придется снова извлекать базу данных.

«Мы можем использовать команду $CI->db->list_fields($tbl_name) для извлечения имен всех полей таблицы. Однако для повышения производительности я рекомендую кэшировать результаты».

1
2
3
public function setHeadings(array $headings){
    $this->headings = array_merge($this->headings, $headings);
}

Это позволяет вам настраивать заголовки таблицы таблицы данных, то есть с ее помощью вы можете перезаписывать исходные имена столбцов для определенных полей таблицы. Требуется ассоциативный array , например: regdate => «Date Registration». Вместо просто технического «Regdate» в качестве заголовка столбца для этого типа данных у нас на месте более удобочитаемый заголовок. Код, ответственный за применение заголовков, будет раскрыт в ближайшее время.

1
2
3
4
5
6
public function ignoreFields(array $fields){
    foreach($fields as $f){
        if($f!=$this->pk_col)
            $this->hide_cols[] = $f;
    }
}

ignoreFields получает array содержащий поля, которые должны игнорироваться при извлечении данных из базы данных. Это полезно, когда у нас есть таблицы с большим количеством полей, но мы хотим скрыть только пару из них. Этот метод достаточно умен, чтобы отследить попытку игнорировать поле первичного ключа, а затем пропустить это. Это так, потому что первичный ключ нельзя игнорировать по техническим причинам (вскоре вы поймете, почему). Тем не менее, если вы хотите скрыть столбец первичного ключа от появления в пользовательском интерфейсе, вы можете использовать метод hidePkCol :

1
2
3
public function hidePkCol($bool){
    $this->hide_pk_col = (bool)$bool;
}

Этот метод получает логическое значение, указывающее, хотим ли мы скрыть столбец первичного ключа, чтобы он не отображался в сетке данных. Иногда уродливой идеей является отображение данных pkey , которые обычно представляют собой числовой код без какого-либо значения для пользователя.

Следующий экземпляр метода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function _selectFields(){
    foreach($this->tbl_fields as $field){
        if(!in_array($field,$this->hide_cols)){
            $this->CI->db->select($field);
            // hide pk column heading?
            if($field==$this->pk_col && $this->hide_pk_col) continue;
                $headings[]= isset($this->headings[$field]) ?
        }
    }
    if(!empty($headings)){
        // prepend a checkbox for toggling
        array_unshift($headings,»<input type=’checkbox’ class=’check_toggler’>»);
        $this->CI->table->set_heading($headings);
    }
     
}

Здесь у нас есть вспомогательный метод; вот почему он имеет модификатор «private» и имеет префикс с подчеркиванием (кодовое соглашение). Он будет использоваться методом generate() который вскоре будет объяснен, для выбора соответствующих полей таблицы, а также соответствующих заголовков для object таблицы (генератора). Обратите внимание на следующую строку:

1
$headings[]= isset($this->headings[$field]) ?

Здесь мы применяем настраиваемые заголовки или прибегаем к заголовкам по умолчанию, если они не указаны. Если предполагается, что столбец pk скрыт от отображения, его заголовок будет пропущен. Также обратите внимание на следующую строку:

1
array_unshift($headings,»<input type=’checkbox’ class=’dg_check_toggler’>»);

Приведенная выше команда указывает программе добавить в качестве первого заголовка таблицы флажок «Мастер». Этот флажок отличается от других флажков в сетке тем, что он позволяет пользователю установить или снять все флажки за один раз. Эта переключающая функциональность будет реализована через несколько секунд с помощью простого фрагмента кода jQuery.

Теперь приходит то, что делает реальную работу для нас:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public function generate(){
    $this->_selectFields();
    $rows = $this->CI->db
            ->from($this->tbl_name)
            ->get()
            ->result_array();
    foreach($rows as &$row){
        $id = $row[$this->pk_col];
         
        // prepend a checkbox to enable selection of items/rows
        array_unshift($row, «<input class=’dg_check_item’ type=’checkbox’ name=’dg_item[]’ value=’$id’ />»);
         
        // hide pk column cell?
        if($this->hide_pk_col){
            unset($row[$this->pk_col]);
        }
    }
     
    return $this->CI->table->generate($rows);
}

Метод generate , как следует из его названия, отвечает за создание самой сетки данных. Вы должны вызывать этот метод только после того, как настроили объект в соответствии со своими потребностями. Сначала он вызывает метод $this->_selectFields() для выполнения действий, которые мы объяснили ранее. Теперь он должен извлечь все строки из базы данных, а затем перебрать их, добавив флажки в начало каждой строки:

1
2
// prepend a checkbox to enable selection of items/rows
array_unshift($row, «<input class=’dg_check_item’ type=’checkbox’ name=’dg_item[]’ value=’$id’ />»);

Внутри цикла foreach в методе generate , если флаг $this->hide_pk_col установлен в true , мы должны сбросить запись первичного ключа в $row array чтобы он не отображался в виде столбца, когда $this->CI->table object $this->CI->table обрабатывает все строки и генерирует окончательный вывод html. На этом этапе можно удалить первичный ключ, если это необходимо, потому что эта информация нам больше не нужна.

Но что делает пользователь с выбранными / отмеченными строками? Чтобы ответить на это, я подготовил еще несколько методов. Первый позволяет нам создавать «кнопки действий», не зная каких-либо технических подробностей о том, как система сетки работает внутри:

1
2
3
public static function createButton($action_name, $label){
    return «<input type=’submit’ class=’$action_name’ name=’dg_action[$action_name]’ value=’$label’ />»;
}

Просто передайте имя действия в качестве первого аргумента и второго аргумента, чтобы указать метку для сгенерированной кнопки. Атрибут class автоматически генерируется для этой кнопки, поэтому мы можем поиграть с ним легче, когда работаем с ним в нашем JavaScript. Но как мы узнаем, была ли нажата определенная кнопка действия пользователем? Ответ можно найти в следующем методе:

1
2
3
4
5
6
public static function getPostAction(){
// get name of submitted action (if any)
    if(isset($_POST[‘dg_action’])){
        return key($_POST[‘dg_action’]);
    }
}

Ага! Еще один статический метод, который помогает нам, когда мы имеем дело с формами. Если какая-либо сетка данных была отправлена, этот метод вернет имя действия (или «операции»), связанного с этим событием отправки. Кроме того, еще одним удобным инструментом для обработки наших форм данных является …

1
2
3
4
5
6
public static function getPostItems(){
    if(!empty($_POST[‘dg_item’])){
        return $_POST[‘dg_item’];
    }
    return array();
}

… который возвращает array содержащий выбранные ids чтобы вы могли отслеживать, какие строки были выбраны в сетке, а затем выполнять с ними определенные действия. В качестве примера того, что можно сделать с помощью выбора id строк, я подготовил другой метод — этот метод является экземпляром, а не статическим, поскольку он использует ресурсы экземпляра объекта для ведения бизнеса. :

1
2
3
4
5
6
7
8
public function deletePostSelection(){
// remove selected items from the db
    if(!empty($_POST[‘dg_item’]))
        return $this->CI->db
            ->from($this->tbl_name)
            ->where_in($this->pk_col,$_POST[‘dg_item’])
            ->delete();
}

Если был установлен хотя бы один флажок, метод deletePostSelection() сгенерирует и выполнит инструкцию SQL, подобную следующей (предположим, $tbl_name='my_table' и $pk_col='id' ):

1
DELETE FROM my_table WHERE id IN (1,5,7,3,etc…)

… который будет эффективно удалять выбранные строки из постоянного слоя. Вы можете добавить больше операций в сетку данных, но это будет зависеть от специфики вашего проекта. В качестве подсказки вы можете расширить этот класс, скажем, до InboxDatagrid , поэтому, помимо метода deletePostSelection , он может включать дополнительные операции, такие как moveSelectedMessagesTo($place) и т. Д.

Теперь, если вы пошли по этому уроку шаг за шагом, у вас должно получилось что-то похожее на следующее:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
class Datagrid{
     
    private $hide_pk_col = true;
    private $hide_cols = array();
    private $tbl_name = »;
    private $pk_col = »;
    private $headings = array();
    private $tbl_fields = array();
     
    function __construct($tbl_name, $pk_col = ‘id’){
        $this->CI =& get_instance();
        $this->CI->load->database();
        $this->tbl_fields = $this->CI->db->list_fields($tbl_name);
        if(!in_array($pk_col,$this->tbl_fields)){
            throw new Exception(«Primary key column ‘$pk_col’ not found in table ‘$tbl_name'»);
        }
        $this->tbl_name = $tbl_name;
        $this->pk_col = $pk_col;
        $this->CI->load->library(‘table’);
         
    }
     
    public function setHeadings(array $headings){
        $this->headings = array_merge($this->headings, $headings);
    }
     
    public function hidePkCol($bool){
        $this->hide_pk_col = (bool)$bool;
    }
     
    public function ignoreFields(array $fields){
        foreach($fields as $f){
            if($f!=$this->pk_col)
                $this->hide_cols[] = $f;
        }
    }
     
    private function _selectFields(){
        foreach($this->tbl_fields as $field){
            if(!in_array($field,$this->hide_cols)){
                $this->CI->db->select($field);
                // hide pk column heading?
                if($field==$this->pk_col && $this->hide_pk_col) continue;
                $headings[]= isset($this->headings[$field]) ?
            }
        }
        if(!empty($headings)){
            // prepend a checkbox for toggling
            array_unshift($headings,»<input type=’checkbox’ class=’dg_check_toggler’>»);
            $this->CI->table->set_heading($headings);
        }
         
    }
     
    public function generate(){
        $this->_selectFields();
        $rows = $this->CI->db
                ->from($this->tbl_name)
                ->get()
                ->result_array();
        foreach($rows as &$row){
            $id = $row[$this->pk_col];
             
            // prepend a checkbox to enable selection of items
            array_unshift($row, «<input class=’dg_check_item’ type=’checkbox’ name=’dg_item[]’ value=’$id’ />»);
             
            // hide pk column?
            if($this->hide_pk_col){
                unset($row[$this->pk_col]);
            }
        }
         
        return $this->CI->table->generate($rows);
    }
     
    public static function createButton($action_name, $label){
        return «<input type=’submit’ class=’$action_name’ name=’dg_action[$action_name]’ value=’$label’ />»;
    }
     
    public static function getPostAction(){
    // get name of submitted action (if any)
        if(isset($_POST[‘dg_action’])){
            return key($_POST[‘dg_action’]);
        }
    }
     
    public static function getPostItems(){
        if(!empty($_POST[‘dg_item’])){
            return $_POST[‘dg_item’];
        }
        return array();
    }
     
    public function deletePostSelection(){
    // remove selected items from the db
        if(!empty($_POST[‘dg_item’]))
            return $this->CI->db
                ->from($this->tbl_name)
                ->where_in($this->pk_col,$_POST[‘dg_item’])
                ->delete();
    }
 
}

Примечание: не забудьте сохранить этот файл как datagrid_helper.php и поместить его в «application / helper /»


Теперь мы создадим простой тестовый контроллер и загрузим класс Datagrid в качестве помощника в его конструктор. Но перед этим мы должны определить фиктивную таблицу базы данных и заполнить ее некоторыми примерами данных.

Выполните следующий SQL для создания базы данных и пользовательской таблицы:

1
2
3
4
5
6
7
8
CREATE DATABASE `dg_test`;
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(80) NOT NULL,
  `password` varchar(32) NOT NULL,
  `email` varchar(255) NOT NULL,
  UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;

Далее, давайте добавим к нему некоторых пользователей:

1
2
3
4
5
INSERT INTO `users` (`id`, `username`, `password`, `email`) VALUES
(1, ‘david’, ‘12345’, ‘[email protected]’),
(2, ‘maria’, ‘464y3y’, ‘[email protected]’),
(3, ‘alejandro’, ‘a42352fawet’, ‘[email protected]’),
(4, ’emma’, ‘f22a3455b2′, ’[email protected]’);

Теперь сохраните следующий код как « test.php » и добавьте его в папку «application / controllers»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
class Test extends CI_Controller{
 
    function __construct(){
        parent::__construct();
        $this->load->helper(array(‘datagrid’,’url’));
        $this->Datagrid = new Datagrid(‘users’,’id’);
    }
     
    function index(){
        $this->load->helper(‘form’);
        $this->load->library(‘session’);
 
        $this->Datagrid->hidePkCol(true);
        $this->Datagrid->setHeadings(array(’email’=>’E-mail’));
        $this->Datagrid->ignoreFields(array(‘password’));
         
        if($error = $this->session->flashdata(‘form_error’)){
            echo «<font color=red>$error</font>»;
        }
        echo form_open(‘test/proc’);
        echo $this->Datagrid->generate();
        echo Datagrid::createButton(‘delete’,’Delete’);
        echo form_close();
    }
     
    function proc($request_type = »){
        $this->load->helper(‘url’);
        if($action = Datagrid::getPostAction()){
            $error = «»;
            switch($action){
                case ‘delete’ :
                    if(!$this->Datagrid->deletePostSelection()){
                        $error = ‘Items could not be deleted’;
                    }
                break;
            }
            if($request_type!=’ajax’){
                $this->load->library(‘session’);
                $this->session->set_flashdata(‘form_error’,$error);
                redirect(‘test/index’);
            } else {
                echo json_encode(array(‘error’ => $error));
            }
        } else {
            die(«Bad Request»);
        }
    }
 
}
?>

Экземпляр этого класса создается и передается как ссылка на член $this->Datagrid . Обратите внимание, что мы будем получать данные из таблицы с именем «users», первичным ключом которой является столбец «id»; затем в методе index мы предпринимаем следующие шаги: настраиваем объект Datagrid, визуализируем его внутри формы с добавленной к нему кнопкой удаления и проверяем, все ли работает должным образом:

Ответ: Метод « Test::proc() » заботится об обработке формы и выборе правильной операции для применения к id которые были выбраны отправителем формы. Он также заботится о AJAX-запросах, поэтому он возвращает объект JSON обратно клиенту. Эта функция, поддерживающая AJAX, пригодится, когда jQuery вступит в действие, что происходит прямо сейчас!

«Всегда разумно создавать веб-приложения, которые компенсируют, когда JavaScript / AJAX недоступен. Таким образом, некоторые пользователи будут иметь более богатый и быстрый опыт, в то время как те, у кого не включен JavaScript, все равно смогут нормально использовать приложение».


Когда пользователь нажимает кнопку (или любую другую кнопку действия), мы хотели бы, возможно, предотвратить загрузку страницы и необходимость повторной генерации всего; это может заставить пользователя нашего приложения заснуть! Обойти эту проблему не составит труда, если мы будем придерживаться библиотеки jQuery. Поскольку это не учебник для начинающих, я не буду подробно останавливаться на том, как получить библиотеку, как включить ее на страницу и т. Д. Вы должны знать, что эти шаги самостоятельно.

Создайте папку с именем « js », добавьте в нее библиотеку jQuery и создайте файл представления с именем users.php . Откройте этот новый файл и добавьте:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<html>
<head>
    <title>Users Management</title>
    <script src=»<?php echo base_url(); ?>js/jquery-1.6.3.min.js»></script>
    <script src=»<?php echo base_url(); ?>js/datagrid.js»></script>
</head>
<body>
<?php
        $this->Datagrid->hidePkCol(true);
        if($error = $this->session->flashdata(‘form_error’)){
            echo «<font color=red>$error</font>»;
        }
        echo form_open(‘test/proc’,array(‘class’=>’dg_form’));
        echo $this->Datagrid->generate();
        echo Datagrid::createButton(‘delete’,’Delete’);
        echo form_close();
?>
</body>
</html>

Вы поняли, что мы переместили код из Test::index в новый скрипт вида? Это означает, что мы должны соответствующим образом изменить метод Test::index() :

1
2
3
4
5
function index(){
    $this->load->helper(‘form’);
    $this->load->library(‘session’);
    $this->load->view(‘users’);
}

Так-то лучше. Если вы хотите добавить некоторые стили в сетку, вы можете использовать следующий CSS (или создать лучший макет самостоятельно):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
.dg_form table{
       border:1px solid silver;
   }
    
   .dg_form th{
       background-color:gray;
       font-family:»Courier New», Courier, mono;
       font-size:12px;
   }
    
   .dg_form td{
       background-color:gainsboro;
       font-size:12px;
   }
    
   .dg_form input[type=submit]{
       margin-top:2px;
   }

Теперь, пожалуйста, создайте файл «datagrid.js», поместите его в каталог «js» и начните с этого кода:

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

В качестве альтернативы мы могли бы начать с чего-то вроде: $('.dg_form').submit(function(e){...}) . Однако, поскольку я хочу отслеживать, какая кнопка была нажата, и извлекать имя выбранного действия на его основе, я предпочитаю привязывать обработчик событий к самой кнопке отправки, а затем пройтись по иерархии узлов, чтобы найти форму, которая нажатая кнопка принадлежит:

1
2
3
4
// finds the form
var $form = $(this).parents(‘form’);
// extracts the name of the action
var action_name = $(this).attr(‘class’).replace(«dg_action_»,»»);

Затем мы добавляем скрытый элемент ввода внутри элемента формы, чтобы указать, какое действие отправляется:

1
2
3
4
// create the hidden input
var action_control = $(‘<input type=»hidden» name=»dg_action[‘+action_name+’]» value=1 />’);
// add to the form
$form.append(action_control);

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

1
action_control.remove();

«Не забывайте: функция игнорирует кнопку отправки, отклоняя ее как еще один кусок мусорной разметки!»

Затем мы переходим к получению атрибута action из элемента формы и добавляем строку « /ajax » к этому URL, поэтому метод будет знать, что на самом деле это запрос AJAX. После этого мы используем функцию jQuery.post чтобы отправить данные для обработки соответствующим контроллером на стороне сервера, а затем перехватить событие ответа зарегистрированным обратным вызовом / закрытием:

Обратите внимание, что мы просим кодировать ответ как «json», поскольку передаем эту строку в качестве четвертого аргумента функции $.post . Содержание обратного вызова, связанного с ответом сервера, должно быть довольно простым для понимания; он определяет наличие ошибки и, если да, предупреждает об этом. В противном случае это будет означать, что действие было успешно обработано (в этом случае, если это действие «», мы удаляем строки, связанные с id которые были выбраны пользователем).


Единственное, чего сейчас не хватает, это функции переключения, которую я обещал ранее. Мы должны зарегистрировать функцию обратного вызова для того момента, когда установлен флажок «Master», у которого атрибут класса установлен в « dg_check_toggler ». Добавьте следующий фрагмент кода после предыдущего:

Когда установлен флажок «toggler», если он переходит в состояние «checked», тогда все строки из соответствующей сетки данных будут проверяться одновременно; иначе все будет непроверено.


Мы не достигли вершины айсберга, когда речь заходит о сетках данных для более сложных систем управления контентом. Другие функции, которые могут оказаться полезными:

  • Сортировка сетки данных по именам столбцов
  • Пагинационные ссылки для просмотра сетки данных
  • Изменить / Изменить ссылки для обновления данных одной строки
  • Механизм поиска для фильтрации результатов

Спасибо за прочтение. Если вы хотите получить дополнительный урок, дайте мне знать в комментариях!