├── LICENSE ├── README.md └── application └── libraries └── Datatables.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cahya DSN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ci-datatables 2 | Library for server side datatables implementation on codeigniter. CI Datatables is a wrapper class/library based on the native Datatables server-side implementation by Allan Jardine 3 | found at http://datatables.net/examples/data_sources/server_side.html for CodeIgniter 4 | 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 6 | 7 | ## Features 8 | 1. Easy to use. Generates json using only a few lines of code. 9 | 2. Support for table joins (left, right, outer, inner, left outer, right outer). 10 | 3. Able to define custom columns, and filters. 11 | 4. Editable custom variables with callback function support. 12 | 5. Supports generation of tables using non utf-8 charsets. 13 | 14 | ## Requirements 15 | jQuery 1.5+ 16 | DataTables 1.10+ 17 | CodeIgniter 3.* 18 | 19 | ## Installation 20 | To install the library, copy the libraries/datatables.php file into your application/libraries folder. 21 | 22 | 23 | ## Function Reference 24 | 25 | ### $this->setdb($database = 'default') 26 | Sets the name of the database configuration, if need another apart of default, 27 | The name of the database its optional. 28 | 29 | Note: you can use method chaining for more compact syntax. 30 | ### $this->datatables->select($columns); 31 | Sets which columns to fetch from the specified table. 32 | 33 | ``` 34 | $this->datatables->select('id, name, age, gender'); 35 | ``` 36 | *This function also accepts an optional second parameter. 37 | ### $this->datatables->from($table); 38 | Sets the primary table in which data will be fetched from. 39 | 40 | ``` 41 | $this->datatables->from('mytable'); 42 | ``` 43 | ### $this->datatables->join($table, $fk, [$type]); 44 | Sets join statement variables. If you want DataTables to include data from another table, you can define an a table along with its columns and foreign key relationship. 45 | 46 | ``` 47 | $this->datatables->join('mystates', 'mycountries.state_id = mystates.id', 'left'); 48 | ``` 49 | *This function also accepts an optional third parameter for specifying the join type. 50 | ### $this->datatables->where(); 51 | Sets filtering variable to facilitate custom filters. 52 | For additional filtering conditions, the WHERE clause works like CodeIgniter's. So you can do statements like: 53 | 54 | ``` 55 | $this->datatables->where('id', '5'); 56 | ``` 57 | 58 | ``` 59 | $this->datatables->where('age >', '25'); 60 | ``` 61 | 62 | ``` 63 | $this->datatables->where('position != "admin"'); 64 | ``` 65 | 66 | ``` 67 | $array = array('name' => 'J%', 'status' => 'Single'); 68 | $this->datatables->where($array); 69 | ``` 70 | 71 | ### $this->datatables->filter(); 72 | Sets filtering variable to facilitate custom filters. Adds "(filtered from xxx total entries)" to datatables. Same usage with 'where()' method. 73 | ### $this->datatables->add_column($column, $content, [$match_replacement]); 74 | Sets additional column variables to facilitate custom columns. You can also make your own custom column definitions via the following syntax: 75 | *match_replacement is optional only needed if you have $1 to $n matches in your content pattern 76 | 77 | ``` 78 | $this->datatables->add_column('edit', 'EDIT', 'id'); 79 | ``` 80 | ### $this->datatables->edit_column($column, $content, $match_replacement); 81 | Sets additional column variables for editing columns. You can also make your own custom column definitions via the following syntax: 82 | *match_replacement is needed in order to replace $1 to $n matches in your content pattern 83 | 84 | ``` 85 | $this->datatables->edit_column('username', '$2', 'id, username'); 86 | ``` 87 | ### $this->datatables->unset_column($column); 88 | ### $this->datatables->generate([$output], [$charset]); 89 | Builds all the necessary query segments and performs the main query based on results set from chained statements. 90 | *optional parameter output (default is json) can be set to 'raw' to return a non-paginated array of the aaData and sColumns 91 | *optional parameter charset (default is UTF-8) is used when working with non utf-8 characters for json outputs (may decrease performance and be unstable) 92 | 93 | ``` 94 | $this->datatables->generate(); 95 | ``` 96 | 97 | ``` 98 | $this->datatables->generate('json', 'ISO-8859-1'); 99 | ``` 100 | 101 | ``` 102 | $results = $this->datatables->generate('raw'); 103 | $data['aaData'] = $results['aaData']; 104 | $data['sColumns'] = $results['sColumns']; 105 | $this->load->view('downloads/csv', $data); 106 | ``` 107 | 108 | ## Method Chaining 109 | 110 | Method chaining allows you to simplify your syntax by connecting multiple functions. Consider this example: 111 | ``` 112 | function list_all() 113 | { 114 | $this->datatables 115 | ->select('id, name, age, gender') 116 | ->from('tbl_profile') 117 | ->join('tbl_local', 'tbl_profile.local_id = tbl_local.id') 118 | ->select('country') 119 | ->join('tbl_states', 'tbl_profile.state_id = tbl_states.id') 120 | ->select('state') 121 | ->add_column('view', 'View', 'id') 122 | ->add_column('edit', 'Edit', 'id') 123 | ->add_column('delete', 'Delete', 'id'); 124 | 125 | $data['result'] = $this->datatables->generate(); 126 | $this->load->view('ajax', $data); 127 | } 128 | ``` 129 | -------------------------------------------------------------------------------- /application/libraries/Datatables.php: -------------------------------------------------------------------------------- 1 | 13 | * @link http://github.com/cahyadsn/ci-datatables 14 | */ 15 | 16 | class Datatables 17 | { 18 | /** 19 | * Global container variables for chained argument results 20 | * 21 | */ 22 | protected $ci; 23 | protected $table; 24 | protected $select = array(); 25 | protected $joins = array(); 26 | protected $columns = array(); 27 | protected $where = array(); 28 | protected $filter = array(); 29 | protected $add_columns = array(); 30 | protected $edit_columns = array(); 31 | protected $unset_columns = array(); 32 | 33 | /** 34 | * Copies an instance of CI 35 | */ 36 | public function __construct() 37 | { 38 | $this->ci =& get_instance(); 39 | } 40 | 41 | /** 42 | * Set a specific database event the default, if vopid reconnect the default 43 | * 44 | * @param string $databasename 45 | * @return void 46 | */ 47 | public function setdb($databasename = NULL) 48 | { 49 | if($databasename == NULL) $databasename = 'default'; 50 | $this->ci->load->database($databasename); 51 | } 52 | 53 | 54 | /** 55 | * Generates the SELECT portion of the query 56 | * 57 | * @param string $columns 58 | * @param bool $backtick_protect 59 | * @return mixed 60 | */ 61 | public function select($columns, $backtick_protect = TRUE) 62 | { 63 | foreach ($this->explode(',', $columns) as $val){ 64 | $column = trim(preg_replace('/(.*)\s+as\s+(\w*)/i', '$2', $val)); 65 | $this->columns[] = $column; 66 | $this->select[$column] = trim(preg_replace('/(.*)\s+as\s+(\w*)/i', '$1', $val)); 67 | } 68 | $this->ci->db->select($columns, $backtick_protect); 69 | return $this; 70 | } 71 | 72 | /** 73 | * Generates the FROM portion of the query 74 | * 75 | * @param string $table 76 | * @return mixed 77 | */ 78 | public function from($table) 79 | { 80 | $this->table = $table; 81 | $this->ci->db->from($table); 82 | return $this; 83 | } 84 | 85 | /** 86 | * Generates the JOIN portion of the query 87 | * 88 | * @param string $table 89 | * @param string $fk 90 | * @param string $type 91 | * @return mixed 92 | */ 93 | public function join($table, $fk, $type = NULL) 94 | { 95 | $this->joins[] = array($table, $fk, $type); 96 | $this->ci->db->join($table, $fk, $type); 97 | return $this; 98 | } 99 | 100 | /** 101 | * Generates the WHERE portion of the query 102 | * 103 | * @param mixed $key_condition 104 | * @param string $val 105 | * @param bool $backtick_protect 106 | * @return mixed 107 | */ 108 | public function where($key_condition, $val = NULL, $backtick_protect = TRUE) 109 | { 110 | $this->where[] = array($key_condition, $val, $backtick_protect); 111 | $this->ci->db->where($key_condition, $val, $backtick_protect); 112 | return $this; 113 | } 114 | 115 | /** 116 | * Generates the WHERE portion of the query 117 | * 118 | * @param mixed $key_condition 119 | * @param string $val 120 | * @param bool $backtick_protect 121 | * @return mixed 122 | */ 123 | public function filter($key_condition, $val = NULL, $backtick_protect = TRUE) 124 | { 125 | $this->filter[] = array($key_condition, $val, $backtick_protect); 126 | return $this; 127 | } 128 | 129 | /** 130 | * Sets additional column variables for adding custom columns 131 | * 132 | * @param string $column 133 | * @param string $content 134 | * @param string $match_replacement 135 | * @return mixed 136 | */ 137 | public function addColumn($column, $content, $match_replacement = NULL) 138 | { 139 | $this->add_columns[$column] = array('content' => $content, 'replacement' => $this->explode(',', $match_replacement)); 140 | return $this; 141 | } 142 | 143 | /** 144 | * Sets additional column variables for editing columns 145 | * 146 | * @param string $column 147 | * @param string $content 148 | * @param string $match_replacement 149 | * @return mixed 150 | */ 151 | public function editColumn($column, $content, $match_replacement) 152 | { 153 | $this->edit_columns[$column][] = array('content' => $content, 'replacement' => $this->explode(',', $match_replacement)); 154 | return $this; 155 | } 156 | 157 | /** 158 | * Unset column 159 | * 160 | * @param string $column 161 | * @return mixed 162 | */ 163 | public function unsetColumn($column) 164 | { 165 | $this->unset_columns[] = $column; 166 | return $this; 167 | } 168 | 169 | /** 170 | * Builds all the necessary query segments and performs the main query based on results set from chained statements 171 | * 172 | * @param string charset 173 | * @return string 174 | */ 175 | public function generate($charset = 'UTF-8') 176 | { 177 | $this->getPaging(); 178 | $this->getOrdering(); 179 | $this->getFiltering(); 180 | return $this->produceOutput($charset); 181 | } 182 | 183 | /** 184 | * Generates the LIMIT portion of the query 185 | * 186 | * @return mixed 187 | */ 188 | protected function getPaging() 189 | { 190 | $iStart = $this->ci->input->post('iDisplayStart'); 191 | $iLength = $this->ci->input->post('iDisplayLength'); 192 | $this->ci->db->limit(($iLength != '' && $iLength != '-1')? $iLength : 10, ($iStart)? $iStart : 0); 193 | } 194 | 195 | /** 196 | * Generates the ORDER BY portion of the query 197 | * 198 | * @return mixed 199 | */ 200 | protected function getOrdering() 201 | { 202 | if ($this->checkMDataprop()) { 203 | $mColArray = $this->getMDataprop(); 204 | } elseif ($this->ci->input->post('sColumns')) { 205 | $mColArray = explode(',', $this->ci->input->post('sColumns')); 206 | } else { 207 | $mColArray = $this->columns; 208 | } 209 | $mColArray = array_values(array_diff($mColArray, $this->unset_columns)); 210 | $columns = array_values(array_diff($this->columns, $this->unset_columns)); 211 | for ($i = 0; $i < intval($this->ci->input->post('iSortingCols')); $i++) { 212 | if ( 213 | isset($mColArray[intval($this->ci->input->post('iSortCol_' . $i))]) 214 | && in_array($mColArray[intval($this->ci->input->post('iSortCol_' . $i))], $columns) 215 | && $this->ci->input->post('bSortable_'.intval($this->ci->input->post('iSortCol_' . $i))) == 'true' 216 | ) { 217 | $this->ci->db->order_by($mColArray[intval($this->ci->input->post('iSortCol_' . $i))], $this->ci->input->post('sSortDir_' . $i)); 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * Generates the LIKE portion of the query 224 | * 225 | * @return mixed 226 | */ 227 | protected function getFiltering() 228 | { 229 | if ($this->checkMDataprop()) { 230 | $mColArray = $this->getMDataprop(); 231 | } elseif ($this->ci->input->post('sColumns')) { 232 | $mColArray = explode(',', $this->ci->input->post('sColumns')); 233 | } else { 234 | $mColArray = $this->columns; 235 | } 236 | $sWhere = ''; 237 | $sSearch = $this->ci->db->escape($this->ci->input->post('sSearch')); 238 | $mColArray = array_values(array_diff($mColArray, $this->unset_columns)); 239 | $columns = array_values(array_diff($this->columns, $this->unset_columns)); 240 | if ($sSearch != ''){ 241 | for ($i = 0; $i < count($mColArray); $i++) { 242 | if ($this->ci->input->post('bSearchable_' . $i) == 'true' && in_array($mColArray[$i], $columns)) { 243 | $sWhere .= $this->select[$mColArray[$i]] . " LIKE '%" . $sSearch . "%' OR "; 244 | } 245 | } 246 | } 247 | $sWhere = substr_replace($sWhere, '', -3); 248 | if ($sWhere != '') { 249 | $this->ci->db->where('(' . $sWhere . ')'); 250 | } 251 | for ($i = 0; $i < intval($this->ci->input->post('iColumns')); $i++) { 252 | if (isset($_POST['sSearch_' . $i]) && $this->ci->input->post('sSearch_' . $i) != '' && in_array($mColArray[$i], $columns)) { 253 | $miSearch = explode(',', $this->ci->input->post('sSearch_' . $i)); 254 | foreach ($miSearch as $val) { 255 | if (preg_match("/(<=|>=|=|<|>)(\s*)(.+)/i", trim($val), $matches)) { 256 | $this->ci->db->where($this->select[$mColArray[$i]].' '.$matches[1], $matches[3]); 257 | } else { 258 | $this->ci->db->where($this->select[$mColArray[$i]].' LIKE', '%'.$val.'%'); 259 | } 260 | } 261 | } 262 | } 263 | foreach ($this->filter as $val) { 264 | $this->ci->db->where($val[0], $val[1], $val[2]); 265 | } 266 | } 267 | 268 | /** 269 | * Compiles the select statement based on the other functions called and runs the query 270 | * 271 | * @return mixed 272 | */ 273 | protected function getDisplayResult() 274 | { 275 | return $this->ci->db->get(); 276 | } 277 | 278 | /** 279 | * Builds a JSON encoded string data 280 | * 281 | * @param string charset 282 | * @return string 283 | */ 284 | protected function produceOutput($charset) 285 | { 286 | $aaData = array(); 287 | $rResult = $this->getDisplayResult(); 288 | $iTotal = $this->getTotalResults(); 289 | $iFilteredTotal = $this->getTotalResults(true); 290 | foreach ($rResult->result_array() as $row_key => $row_val) { 291 | $aaData[$row_key] = ($this->checkMDataprop())? $row_val : array_values($row_val); 292 | foreach ($this->add_columns as $field => $val) { 293 | if ($this->checkMDataprop()) { 294 | $aaData[$row_key][$field] = $this->execReplace($val, $aaData[$row_key]); 295 | } else { 296 | $aaData[$row_key][] = $this->execReplace($val, $aaData[$row_key]); 297 | } 298 | } 299 | foreach ($this->edit_columns as $modkey => $modval) { 300 | foreach ($modval as $val) { 301 | $aaData[$row_key][($this->checkMDataprop())? $modkey : array_search($modkey, $this->columns)] = $this->execReplace($val, $aaData[$row_key]); 302 | } 303 | $aaData[$row_key] = array_diff_key($aaData[$row_key], ($this->checkMDataprop())? $this->unset_columns : array_intersect($this->columns, $this->unset_columns)); 304 | } 305 | if (!$this->checkMDataprop()) { 306 | $aaData[$row_key] = array_values($aaData[$row_key]); 307 | } 308 | } 309 | $sColumns = array_diff($this->columns, $this->unset_columns); 310 | $sColumns = array_merge_recursive($sColumns, array_keys($this->add_columns)); 311 | $sOutput = array 312 | ( 313 | 'sEcho' => intval($this->ci->input->post('sEcho')), 314 | 'iTotalRecords' => $iTotal, 315 | 'iTotalDisplayRecords' => $iFilteredTotal, 316 | 'aaData' => $aaData, 317 | 'sColumns' => implode(',', $sColumns) 318 | ); 319 | if (strtolower($charset) == 'utf-8') { 320 | return json_encode($sOutput); 321 | } else { 322 | return $this->jsonify($sOutput); 323 | } 324 | } 325 | 326 | /** 327 | * Get result count 328 | * 329 | * @return integer 330 | */ 331 | protected function getTotalResults($filtering = FALSE) 332 | { 333 | if ($filtering) { 334 | $this->getFiltering(); 335 | } 336 | foreach ($this->joins as $val) { 337 | $this->ci->db->join($val[0], $val[1], $val[2]); 338 | } 339 | foreach ($this->where as $val) { 340 | $this->ci->db->where($val[0], $val[1], $val[2]); 341 | } 342 | return $this->ci->db->count_all_results($this->table); 343 | } 344 | 345 | /** 346 | * Runs callback functions and makes replacements 347 | * 348 | * @param mixed $custom_val 349 | * @param mixed $row_data 350 | * @return string $custom_val['content'] 351 | */ 352 | protected function execReplace($custom_val, $row_data) 353 | { 354 | $replace_string = ''; 355 | if (isset($custom_val['replacement']) && is_array($custom_val['replacement'])){ 356 | foreach ($custom_val['replacement'] as $key => $val) { 357 | $sval = preg_replace("/(? $args_val) { 362 | $args_val = preg_replace("/(?columns))? ($row_data[($this->checkMDataprop())? $args_val : array_search($args_val, $this->columns)]) : $args_val; 364 | } 365 | $replace_string = call_user_func_array($func, $args); 366 | } elseif(in_array($sval, $this->columns)) { 367 | $replace_string = $row_data[($this->checkMDataprop())? $sval : array_search($sval, $this->columns)]; 368 | } else { 369 | $replace_string = $sval; 370 | } 371 | $custom_val['content'] = str_ireplace('$' . ($key + 1), $replace_string, $custom_val['content']); 372 | } 373 | } 374 | return $custom_val['content']; 375 | } 376 | 377 | /** 378 | * Check mDataprop 379 | * 380 | * @return bool 381 | */ 382 | protected function checkMDataprop() 383 | { 384 | if (!$this->ci->input->post('mDataProp_0')) return false; 385 | for ($i = 0; $i < intval($this->ci->input->post('iColumns')); $i++) { 386 | if (!is_numeric($this->ci->input->post('mDataProp_' . $i))) { 387 | return true; 388 | } 389 | } 390 | return false; 391 | } 392 | 393 | /** 394 | * Get mDataprop order 395 | * 396 | * @return mixed 397 | */ 398 | protected function getMDataprop() 399 | { 400 | $mDataProp = array(); 401 | for ($i = 0; $i < intval($this->ci->input->post('iColumns')); $i++) { 402 | $mDataProp[] = $this->ci->input->post('mDataProp_' . $i); 403 | } 404 | return $mDataProp; 405 | } 406 | 407 | /** 408 | * Return the difference of open and close characters 409 | * 410 | * @param string $str 411 | * @param string $open 412 | * @param string $close 413 | * @return string $retval 414 | */ 415 | protected function balanceChars($str, $open, $close) 416 | { 417 | $openCount = substr_count($str, $open); 418 | $closeCount = substr_count($str, $close); 419 | $retval = $openCount - $closeCount; 420 | return $retval; 421 | } 422 | 423 | /** 424 | * Explode, but ignore delimiter until closing characters are found 425 | * 426 | * @param string $delimiter 427 | * @param string $str 428 | * @param string $open 429 | * @param string $close 430 | * @return mixed $retval 431 | */ 432 | protected function explode($delimiter, $str, $open='(', $close=')') 433 | { 434 | $retval = array(); 435 | $hold = array(); 436 | $balance = 0; 437 | $parts = explode($delimiter, $str); 438 | foreach ($parts as $part) { 439 | $hold[] = $part; 440 | $balance += $this->balanceChars($part, $open, $close); 441 | if ($balance < 1) { 442 | $retval[] = implode($delimiter, $hold); 443 | $hold = array(); 444 | $balance = 0; 445 | } 446 | } 447 | if (count($hold) > 0) { 448 | $retval[] = implode($delimiter, $hold); 449 | } 450 | return $retval; 451 | } 452 | 453 | /** 454 | * Workaround for json_encode's UTF-8 encoding if a different charset needs to be used 455 | * 456 | * @param mixed result 457 | * @return string 458 | */ 459 | protected function jsonify($result = FALSE) 460 | { 461 | if(is_null($result)) return 'null'; 462 | if($result === FALSE) return 'false'; 463 | if($result === TRUE) return 'true'; 464 | if (is_scalar($result)) { 465 | if (is_float($result)) { 466 | return floatval(str_replace(',', '.', strval($result))); 467 | } 468 | if (is_string($result)) { 469 | static $jsonReplaces = array(array('\\', '/', '\n', '\t', '\r', '\b', '\f', '"'), array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"')); 470 | return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $result) . '"'; 471 | } else { 472 | return $result; 473 | } 474 | } 475 | $isList = TRUE; 476 | for ($i = 0, reset($result); $i < count($result); $i++, next($result)) { 477 | if (key($result) !== $i) { 478 | $isList = FALSE; 479 | break; 480 | } 481 | } 482 | $json = array(); 483 | if ($isList) { 484 | foreach ($result as $value) { 485 | $json[] = $this->jsonify($value); 486 | } 487 | return '[' . join(',', $json) . ']'; 488 | } else { 489 | foreach ($result as $key => $value) { 490 | $json[] = $this->jsonify($key) . ':' . $this->jsonify($value); 491 | } 492 | return '{' . join(',', $json) . '}'; 493 | } 494 | } 495 | } 496 | /* End of file Datatables.php */ 497 | /* Location: ./application/libraries/Datatables.php */ 498 | --------------------------------------------------------------------------------