ListTable::setConfig()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
1
<?php
2
namespace Fwlib\Html;
3
4
use Fwlib\Bridge\Adodb;
5
use Fwlib\Bridge\Smarty;
6
use Fwlib\Util\UtilContainerAwareTrait;
7
8
/**
9
 * Html generator: list table
10
 *
11
 * Give table head, data and other necessary information, generate table html.
12
 *
13
 * Some html/style operation use jQuery.
14
 *
15
 * @codeCoverageIgnore
16
 * @deprecated  Use ListView instead
17
 *
18
 * @copyright   Copyright 2003-2015 Fwolf
19
 * @license     http://www.gnu.org/licenses/lgpl.html LGPL-3.0+
20
 */
21
class ListTable
22
{
23
    use UtilContainerAwareTrait;
24
25
26
    /**
27
     * If column in data and title does not match, fitMode option will
28
     * determine which columns will be used, its value defines here.
29
     */
30
    // Fit to title, drop data whose index is not in title index
31
    const FIT_TO_TITLE  = 0;
32
33
    // Fit to data, drop title whose index is not in data index
34
    const FIT_TO_DATA   = 1;
35
36
    // Fit to intersection of title and data, got fewest column
37
    const FIT_INTERSECTION = 2;
38
39
    // Fit to union of title and data, got most column
40
    const FIT_UNION     = 3;
41
42
    /**
43
     * Config array
44
     *
45
     * @var array
46
     */
47
    protected $configs = [];
48
49
    /**
50
     * Information generated in treatment
51
     *
52
     * Default value is given according to default config.
53
     *
54
     * @var array
55
     */
56
    protected $info = [
57
        // Class for root element
58
        'class'         => 'ListTable',
59
        // Class prefix for non-root elements
60
        'classPrefix'   => 'ListTable-',
61
        // Id of root element
62
        'id'            => 'ListTable-1',
63
        // Id prefix for non-root elements
64
        'idPrefix'      => 'ListTable-1-',
65
        // Orderby enabled column and default direction
66
        'orderByColumn' => [],
67
        // Current page number
68
        'page'          => 1,
69
        // Max page number
70
        'pageMax'       => 1,
71
        // Parsed pager text
72
        'pagerTextBody' => '',
73
        'totalRows'     => -1,
74
    ];
75
76
    /**
77
     * List data array
78
     *
79
     * @var array
80
     */
81
    protected $listData = [];
82
83
    /**
84
     * List title array, show as table title
85
     *
86
     * @var array
87
     */
88
    protected $listTitle = [];
89
90
    /**
91
     * Page url param array
92
     *
93
     * @var array
94
     */
95
    protected $param = [];
96
97
    /**
98
     * Template object
99
     *
100
     * @var Smarty
101
     */
102
    protected $tpl = null;
103
104
    /**
105
     * Prefix of var assigned to template
106
     *
107
     * Should keep sync with var name in template file, in most case this
108
     * property need not change.
109
     *
110
     * @var string
111
     */
112
    protected $tplVarPrefix = 'listTable';
113
114
    /**
115
     * Array of url, computed for show in tpl
116
     *
117
     * {
118
     *  base      : Original page url
119
     *  form      : Page jump form target url
120
     *  obCur     : Link on current orderBy head, to reverse order
121
     *  obOther   : Link on in-active orderBy head(their default dir add in tpl)
122
     *  pageFirst : First page link
123
     *  pageLast  : Last page link
124
     *  pageNext  : Next page link
125
     *  pagePrev  : Prev page link
126
     * }
127
     *
128
     * @var array
129
     */
130
    protected $url = [
131
        'base'      => '',
132
        'form'      => '',
133
        'obCur'     => '',
134
        'obOther'   => '',
135
        'pageFirst' => '',
136
        'pageLast'  => '',
137
        'pageNext'  => '',
138
        'pagePrev'  => '',
139
    ];
140
141
142
    /**
143
     * Constructor
144
     *
145
     * If there are multiple list to show in one page, MUST set 'id' in
146
     * config.
147
     *
148
     * @param   Smarty  $tpl
149
     * @param   array   $configs
150
     */
151
    public function __construct($tpl, array $configs = [])
152
    {
153
        $this->tpl = $tpl;
154
155
        // Config will effect setData, so set it first.
156
        $this->setDefaultConfigs();
157
        $this->setConfigs($configs);
158
159
        $this->tpl->assignByRef("{$this->tplVarPrefix}Config", $this->configs);
160
        $this->tpl->assignByRef("{$this->tplVarPrefix}Info", $this->info);
161
        $this->tpl->assignByRef("{$this->tplVarPrefix}Url", $this->url);
162
163
        $this->tpl->assignByRef("{$this->tplVarPrefix}Data", $this->listData);
164
        $this->tpl->assignByRef("{$this->tplVarPrefix}Title", $this->listTitle);
165
    }
166
167
168
    /**
169
     * Fit each row in data with given keys
170
     *
171
     * If row index is not in given keys, it will be dropped.
172
     * If given keys is not in row index, it will be created with value
173
     * $this->configs['fitEmpty'].
174
     *
175
     * @param   array   $key
176
     */
177
    protected function fitData(array $key)
178
    {
179
        // Do search on first row for speed
180
        $keyAdd = [];
181
        $keyDel = [];
182
        reset($this->listData);
183
        $row = current($this->listData);
184
185
        // Drop key not in key list
186
        foreach ((array)$row as $k => $v) {
187
            if (!in_array($k, $key)) {
188
                $keyDel[] = $k;
189
            }
190
        }
191
        // Add key not exists
192
        foreach ($key as $k) {
193
            // isset() will return false if array key exists but value is null
194
            if (!array_key_exists($k, $row)) {
195
                $keyAdd[] = $k;
196
            }
197
        }
198
199
200
        if (empty($keyAdd) && empty($keyDel)) {
201
            return;
202
        }
203
204
205
        $fitEmpty = $this->configs['fitEmpty'];
206
        foreach ($this->listData as &$row) {
207
            foreach ((array)$keyDel as $k) {
208
                unset($row[$k]);
209
            }
210
211
            foreach ((array)$keyAdd as $k) {
212
                $row[$k] = $fitEmpty;
213
            }
214
        }
215
        unset($row);
216
    }
217
218
219
    /**
220
     * Fit title with given keys
221
     *
222
     * Drop title value not in given keys, and create new if given keys is not
223
     * exists in title array.
224
     *
225
     * @param   array   $key
226
     */
227
    protected function fitTitle(array $key)
228
    {
229
        // Title index not in key list
230
        foreach ($this->listTitle as $k => $v) {
231
            if (!in_array($k, $key)) {
232
                unset($this->listTitle[$k]);
233
            }
234
        }
235
236
        // Key not exist in title
237
        foreach ($key as $k) {
238
            if (!isset($this->listTitle[$k])) {
239
                // Title value is same as key
240
                $this->listTitle[$k] = $k;
241
            }
242
        }
243
    }
244
245
246
    /**
247
     * Fit data and title if their key are different
248
     *
249
     * Notice: data have multi row(2 dim), and 2nd dimension must use assoc
250
     * index. Title have only 1 row(1 dim), integer or assoc indexed.
251
     *
252
     * @see $config['fitMode']
253
     */
254
    protected function fitTitleWithData()
255
    {
256
        if (empty($this->listData) || empty($this->listTitle)) {
257
            return;
258
        }
259
260
        // Will compare by array keys
261
        // For data, will use it's first/current row,
262
        // For title, will use original value if hasn't assoc index
263
        $keyOfData = array_keys(current($this->listData));
264
        $keyOfTitle = array_keys($this->listTitle);
265
266
        if ($keyOfData == $keyOfTitle) {
267
            return;
268
        }
269
270
271
        $ar = [];
272 View Code Duplication
        switch ($this->configs['fitMode']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
            case self::FIT_TO_TITLE:
274
                $ar = $keyOfTitle;
275
                break;
276
277
            case self::FIT_TO_DATA:
278
                $ar = $keyOfData;
279
                break;
280
281
            case self::FIT_INTERSECTION:
282
                $ar = array_intersect($keyOfTitle, $keyOfData);
283
                break;
284
285
            case self::FIT_UNION:
286
                $ar = array_unique(array_merge($keyOfTitle, $keyOfData));
287
                break;
288
            default:
289
        }
290
291
        $this->fitTitle($ar);
292
        $this->fitData($ar);
293
    }
294
295
296
    /**
297
     * Format list data use closure function
298
     *
299
     * Closure function $formatFunction must have 1 param and define as
300
     * reference, eg: function (&row) {}.
301
     *
302
     * @param   callback    $formatFunction
303
     * @return  $this
304
     */
305
    public function formatData($formatFunction)
306
    {
307
        foreach ($this->listData as &$row) {
308
            $formatFunction($row);
309
        }
310
        unset($row);
311
312
        return $this;
313
    }
314
315
316
    /**
317
     * Generate url with some modification
318
     *
319
     * @param   array   $modify
320
     * @param   array   $exclude
321
     * @return  string
322
     * @see $this->param
323
     */
324
    protected function genUrl($modify = null, $exclude = null)
325
    {
326
        $param = $this->param;
327
328
        foreach ((array)$modify as $k => $v) {
329
            $param[addslashes($k)] = addslashes($v);
330
        }
331
332
        foreach ((array)$exclude as $k) {
333
            unset($param[$k]);
334
        }
335
336
        $url = '';
337
        foreach ($param as $k => $v) {
338
            $url .= "&$k=$v";
339
        }
340
        if (!empty($url)) {
341
            $url{0} = '?';
342
        }
343
        $url = $this->url['base'] . $url;
344
345
        return $url;
346
    }
347
348
349
    /**
350
     * Get full output html
351
     *
352
     * @return  string
353
     */
354
    public function getHtml()
355
    {
356
        $this->readRequest(true);
357
        $this->setPager();
358
359
        return $this->tpl->fetch($this->configs['tpl']);
360
    }
361
362
363
    /**
364
     * Get config for generate SQL
365
     *
366
     * The result will use as config in SqlGenerator, include:
367
     * - LIMIT
368
     * - ORDERBY
369
     *
370
     * When there are multiple list on single page, second list must set
371
     * $forcenew to true.
372
     *
373
     * @param   boolean $forcenew
374
     * @return  array
375
     * @see Fwlib\Db\SqlGenerator
376
     */
377
    public function getSqlConfig($forcenew = false)
378
    {
379
        $this->readRequest($forcenew);
380
381
        $ar = [];
382
383
        $ar['LIMIT'] = $this->configs['pageSize'] * ($this->info['page'] - 1)
384
            . ', ' . $this->configs['pageSize'];
385
386
        if (!empty($this->configs['orderBy'])) {
387
            // orderBy_idx is column name
388
            $ar['ORDERBY'] = $this->configs['orderBy']
389
                . ' ' . $this->configs['orderByDir'];
390
        }
391
392
        return $ar;
393
    }
394
395
396
    /**
397
     * Read http param and parse
398
     *
399
     * @param   boolean $forcenew
400
     * @return  $this
401
     */
402
    protected function readRequest($forcenew = false)
403
    {
404
        $arrayUtil = $this->getUtilContainer()->getArray();
405
406
        // Avoid duplicate
407
        if (!empty($this->url['base']) && !$forcenew) {
408
            return $this;
409
        }
410
411
412
        $this->param = &$_GET;
413
        if (!get_magic_quotes_gpc()) {
414
            foreach ($this->param as &$value) {
415
                $value = addslashes($value);
416
            }
417
            unset($value);
418
        }
419
420
        // :NOTICE: Will got only url of backend if ProxyPass used
421
        // Can treat as below before new ListTable obj.
422
        /*
423
        $ar = array('/needless1/', '/needless1/');
424
        $_SERVER['REQUEST_URI'] = str_replace($ar, '/', $_SERVER['REQUEST_URI']);
425
        $_SERVER['SCRIPT_NAME'] = str_replace($ar, '/', $_SERVER['SCRIPT_NAME']);
426
        */
427
428
        $httpUtil = $this->getUtilContainer()->getHttp();
429
        $this->url['base'] = $httpUtil->getSelfUrl(false);
430
431
        $page = $arrayUtil->getIdx($this->param, $this->configs['pageParam'], 1);
432
        $this->setPage($page);
433
434
435
        // Always treat orderBy
436
        $orderBy = '';
437
        $dir = '';
438
        if (isset($this->param[$this->configs['orderByParam']])) {
439
            $orderBy = $this->param[$this->configs['orderByParam']];
440
            $dir = $arrayUtil->getIdx(
441
                $this->param,
442
                $this->configs['orderByParam'] . 'Dir',
443
                ''
444
            );
445
        }
446
        $this->setOrderBy($orderBy, $dir);
447
448
        return $this;
449
    }
450
451
452
    /**
453
     * Set single config
454
     *
455
     * @param   string  $key
456
     * @param   mixed   $value
457
     * @return  ListTable
458
     */
459
    public function setConfig($key, $value)
460
    {
461
        $this->configs[$key] = $value;
462
463
        if ('class' == $key) {
464
            $this->setId($this->configs['id'], $value);
465
466
        } elseif ('id' == $key) {
467
            $this->setId($value, $this->configs['class']);
468
        }
469
470
        return $this;
471
    }
472
473
474
    /**
475
     * Set multiple configs
476
     *
477
     * @param   array   $configs
478
     * @return  ListTable
479
     */
480
    public function setConfigs(array $configs)
481
    {
482
        $this->configs = array_merge($this->configs, $configs);
483
484
        if (isset($configs['class']) || isset($configs['id'])) {
485
            $this->setId(
486
                isset($configs['id']) ? $configs['id']
487
                : $this->configs['id'],
488
                isset($configs['class']) ? $configs['class']
489
                : $this->configs['class']
490
            );
491
        }
492
493
494
        return $this;
495
    }
496
497
498
    /**
499
     * Set list data and title
500
     *
501
     * @param   array   $listData
502
     * @param   array   $listTitle
503
     * @param   boolean $updateTotalRows
504
     * @return  $this
505
     */
506
    public function setData($listData, $listTitle = null, $updateTotalRows = false)
507
    {
508
        $this->listData = $listData;
509
        if ($updateTotalRows || (-1 == $this->info['totalRows'])) {
510
            $this->info['totalRows'] = count($listData);
511
        }
512
513
        if (!is_null($listTitle)) {
514
            $this->listTitle = $listTitle;
515
        }
516
517
        // Same number of items maybe index diff, so always do fit.
518
        $this->fitTitleWithData();
519
520
        return $this;
521
    }
522
523
524
    /**
525
     * Set db query
526
     *
527
     * Will query total rows and list data by set db connection and config,
528
     * will overwrite exists $listData.
529
     *
530
     * @param   Adodb   $db
531
     * @param   array   $config
532
     * @return  $this
533
     */
534
    public function setDbQuery($db, $config)
535
    {
536
        // Get totalRows
537
        $this->info['totalRows'] = $db->execute(
538
            array_merge($config, ['SELECT' => 'COUNT(1) AS c'])
539
        )
540
        ->fields['c'];
541
542
        // Query data
543
        $rs = $db->execute(
544
            array_merge($config, $this->getSqlConfig(true))
545
        );
546
        $this->listData = $rs->GetArray();
547
548
        $this->fitTitleWithData();
549
550
        return $this;
551
    }
552
553
554
    /**
555
     * Set default configs
556
     */
557
    protected function setDefaultConfigs()
558
    {
559
        /** @noinspection SpellCheckingInspection */
560
        $this->setConfigs(
561
            [
562
                // Notice: this is NOT actual class and id used in template,
563
                // see $this->info and $this->setId() for details.
564
565
                // Classname for root element, and prefix of inherit elements
566
                // Should not be empty.
567
                'class'             => 'list-table',
568
                // Id of this list, default 1, will use class as prefix if not empty.
569
                // Can be string or integer, should not be empty.
570
                'id'                => 1,
571
                //'code_prefix'       => 'fl_lt',     // Used in id/class in html and css.
572
573
574
                // Color schema: light blue
575
                // Color should assign using CSS as possible as it could,
576
                // below color will use jQuery to assign.
577
                // :TODO: Use pure CSS ?
578
                'enableColorTh'     => true,
579
                'colorBgTh'         => '#d0dcff',   // 表头(thead)
580
581
                'enableColorTr'     => true,
582
                'colorBgTrEven'     => '#fff',      // 偶数行
583
                'colorBgTrOdd'      => '#eef2ff',   // 奇数行,tbody后从0开始算
584
585
                'enableColorHover'  => true,
586
                'colorBgTrHover'    => '#e3e3de',   // 鼠标指向时变色
587
                //'color_bg_th'       => '#d0dcff',   // 表头(thead)
588
                //'color_bg_tr_even'  => '#fff',      // 偶数行
589
                //'color_bg_tr_hover' => '#e3e3de',   // 鼠标指向时变色
590
                //'color_bg_tr_odd'   => '#eef2ff',   // 奇数行,tbody后从0开始算
591
592
593
                // Data and title fit mode, default FIT_TO_TITLE
594
                'fitMode'           => self::FIT_TO_TITLE,
595
596
                // If a value in data is empty, display with this value.
597
                // Not for title, which will use field name.
598
                'fitEmpty'          => '&nbsp;',
599
                //'fit_data_title'    => 0,
600
                //'fit_empty'         => '&nbsp;',
601
602
603
                // Enable orderBy on those column, empty to disable orderBy
604
                // Format: {[column, direction],}
605
                // First [] is default, and default direction is ASC.
606
                'orderByColumn'     => [],
607
                // Which column to orderBy,
608
                'orderBy'           => '',
609
                // Orderby direction, ASC or DESC
610
                'orderByDir'        => 'ASC',
611
                // Get param for orderBy
612
                'orderByParam'      => 'ob',
613
                // Preserved, will be auto generated if orderBy enabled
614
                'orderByText'       => '',
615
                // Orderby text/symbol for ASC and DESC
616
                'orderByTextAsc'    => '↑',
617
                'orderByTextDesc'   => '↓',
618
                // More orderBy symbol
619
                // &#8592 = ← &#8593 = ↑ &#8594 = → &#8595 = ↓
620
                // &#8710 = ∆ &#8711 = ∇
621
                //'orderby'           => 0,           // 0=off, 1=on
622
                //'orderby_dir'       => 'ASC',
623
                //'orderby_idx'       => '',          // Idx of th ar
624
                //'orderby_param'     => 'o',
625
                //'orderby_text'      => '',
626
                //'orderby_text_asc'  => '↑',
627
                //'orderby_text_desc' => '↓',
628
629
630
                // Get param for identify current page
631
                'pageParam'         => 'p',
632
                // How many rows to display each page
633
                'pageSize'          => 10,
634
                //'page_cur'          => 1,
635
                //'pageParam'        => 'p',// Used in url to set page no.
636
                //'page_size'         => 10,
637
638
639
                // Show pager above list table
640
                'pagerAbove'        => true,
641
                // Show pager below list table
642
                'pagerBelow'        => true,
643
                // Pager message template
644
                'pagerTextFirst'    => '首页',
645
                'pagerTextPrev'     => '上一页',
646
                'pagerTextNext'     => '下一页',
647
                'pagerTextLast'     => '尾页',
648
                'pagerTextBody'     =>
649
                    '共{totalRows}条记录,每页显示{pageSize}条,当前为第{page}/{pageMax}页',
650
                'pagerTextJump1'    => '转到第',
651
                'pagerTextJump2'    => '页',
652
                'pagerTextJumpButton'   => '转',
653
                // Spacer between pager text plistTitles
654
                'pagerTextSpacer'   => ' | ',
655
                //'pager'             => false,       // Is or not use pager
656
                //'pager_bottom'      => true,        // Is or not use pager bottom, used when pager=true
657
                // This is a message template
658
                // When display, use key append by '_value'
659
                //'pager_text_cur'    =>
660
                //    '共{rows_total}条记录,每页显示{page_size}条,当前为第{page_cur}/{page_max}页',
661
                //'pager_text_first'  => '首页',
662
                //'pager_text_goto1'  => '转到第',
663
                //'pager_text_goto2'  => '页',
664
                //'pager_text_goto3'  => '转',
665
                //'pager_text_last'   => '尾页',
666
                //'pager_text_next'   => '下一页',
667
                //'pager_text_prev'   => '上一页',
668
                //'pager_text_spacer' => ' | ',       // To be between below texts.
669
                //'pager_top'         => true,        // Is or not use pager top, used when pager=true
670
671
672
                //'rows_total'        => 0,
673
                // Template file path
674
                'tpl'               => __DIR__ . '/list-table.tpl',
675
676
                // Add custom string in td/th/tr tag, eg: nowrap='nowrap'
677
                // td/th can use index same with data array index,
678
                // tr can use int index whose value is string too.
679
                // :TODO: tr int index is converted to string ?
680
                // For tr of th row, use th instead.
681
                'tdAdd'             => [],
682
                'thAdd'             => [],
683
                'trAdd'             => [],
684
                //'td_add'            => array(),
685
                //'th_add'            => array(),
686
                //'tr_add'            => array(),
687
            ]
688
        );
689
    }
690
691
692
    /**
693
     * Set id and class of list table
694
     *
695
     * Id and class should not have space or other special chars not allowed
696
     * by js and css in it.
697
     *
698
     * @param   string  $id
699
     * @param   string  $class
700
     * @return  $this
701
     */
702
    public function setId($id, $class = null)
703
    {
704
        $class = trim($class);
705
        // Class should not be empty
706
        if (empty($class)) {
707
            $class = $this->configs['class'];    // For later use
708
        } else {
709
            $this->configs['class'] = $class;
710
        }
711
712
        $this->info['class'] = $class;
713
714
        // Class may have multiple value split by space, use first one as
715
        // prefix of other elements.
716
        if (false !== strpos($class, ' ')) {
717
            $class = strstr($class, ' ', true);
718
        }
719
        $this->info['classPrefix'] = $class . '__';
720
721
722
        $id = trim($id);
723
        // Avoid 0, which is empty
724
        if (0 == strlen($id)) {
725
            $id = 1;
726
        }
727
        $this->configs['id'] = $id;
728
        $this->info['idPrefix'] = $this->info['classPrefix'] . $id . '__';
729
        $this->info['id'] = $this->info['classPrefix'] . $id;
730
731
732
        // Change pageParam, eg: p1, pa
733
        if ('0' != $id && '1' != $id) {
734
            $this->configs['pageParam'] = 'p' . $id;
735
        }
736
        // Useless, readRequest() will call it
737
        //$this->setPage();
738
739
        // Change orderBy param
740
        $this->configs['orderByParam'] = 'ob' . $id;
741
742
        return $this;
743
    }
744
745
746
    /**
747
     * Set orderBy info
748
     *
749
     * Did not validate orderBy key exists in data or title array.
750
     *
751
     * @param   mixed   $key
752
     * @param   string  $dir    ASC/DESC
753
     */
754
    public function setOrderBy($key = null, $dir = null)
755
    {
756
        // Parse orderBy config
757
        $orderByColumn = [];
758
        foreach ((array)$this->configs['orderByColumn'] as $v) {
759
            $orderByColumn[$v[0]] = [
760
                $v[0],
761
                (isset($v[1])) ? $v[1] : 'ASC',
762
            ];
763
        }
764
        $this->info['orderByColumn'] = $orderByColumn;
765
766
767
        // Check orderBy param, if fail, use config default
768
        if (!isset($orderByColumn[$key])) {
769
            list($key, $dir) = current($orderByColumn);
770
        } elseif (empty($dir)) {
771
            $dir = $orderByColumn[$key][1];
772
        }
773
        $this->configs['orderBy'] = $key;
774
775
776
        $dir = strtoupper($dir);
777
        if ('ASC' == $dir) {
778
            $dirReverse = 'DESC';
779
            $this->configs['orderByDir'] = 'ASC';
780
            $this->configs['orderByText'] = $this->configs['orderByTextAsc'];
781
        } else {
782
            $dirReverse = 'ASC';
783
            $this->configs['orderByDir'] = 'DESC';
784
            $this->configs['orderByText'] = $this->configs['orderByTextDesc'];
785
        }
786
787
788
        // Url param
789
        $ob = $this->configs['orderByParam'];
790
        // Change orderBy will clear page param
791
        // Orderby index is appended in template by each th, remove here
792
        $this->url['obCur'] = $this->genUrl(
793
            ["{$ob}Dir" => $dirReverse],
794
            [$ob, $this->configs['pageParam']]
795
        );
796
797
        // Other column orderBy will clear direction
798
        // Added pageParam is dummy, to keep url start with '?', fit tpl later
799
        $this->url['obOther'] = $this->genUrl(
800
            [$this->configs['pageParam'] => 1],
801
            [$ob, "{$ob}Dir"]
802
        );
803
    }
804
805
806
    /**
807
     * Check and set current page
808
     *
809
     * @param   int     $page   Page num param come from outer
810
     * @return  $this
811
     */
812
    protected function setPage($page = null)
813
    {
814
        $arrayUtil = $this->getUtilContainer()->getArray();
815
816
        if (is_null($page)) {
817
            $page = $arrayUtil->getIdx(
818
                $this->param,
819
                $this->configs['pageParam'],
820
                1
821
            );
822
        }
823
        $page = intval($page);
824
825
826
        // Compare with min and max page number
827
        if (1 > $page) {
828
            $page = 1;
829
        }
830
        if (0 < $this->info['totalRows']
831
            && 0 < $this->configs['pageSize']
832
        ) {
833
            $this->info['pageMax'] =
834
                ceil($this->info['totalRows'] / $this->configs['pageSize']);
835
            $page = min($page, $this->info['pageMax']);
836
        }
837
838
839
        $this->info['page'] = $page;
840
        return $this;
841
    }
842
843
844
    /**
845
     * Set pager info
846
     *
847
     * Will execute even pager above/below are both disabled.
848
     *
849
     * @return  $this
850
     * @see     $config
851
     */
852
    protected function setPager()
853
    {
854
        $page      = $this->info['page'];
855
        $pageMax   = $this->info['pageMax'];
856
        $pageSize  = $this->configs['pageSize'];
857
        $totalRows = $this->info['totalRows'];
858
859
860
        // If data rows exceeds pageSize, trim it
861
        if (count($this->listData) > $pageSize) {
862
            // If page = 3/5, trim page 1, 2 first
863
            for ($i = 0; $i < ($page - 1) * $pageSize; $i ++) {
864
                unset($this->listData[$i]);
865
            }
866
            // Then trim page 4, 5
867
            for ($i = $page * $pageSize; $i < $pageMax * $pageSize; $i ++) {
868
                unset($this->listData[$i]);
869
            }
870
        }
871
872
        $this->info['pagerTextBody'] = str_replace(
873
            ['{page}', '{pageMax}', '{totalRows}', '{pageSize}'],
874
            [$page, $pageMax, $totalRows, $pageSize],
875
            $this->configs['pagerTextBody']
876
        );
877
878
        // Generate url for pager
879
        //$this->url['base'] = GetSelfUrl(true);   // Move to readRequest()
880
        if (1 < $page) {
881
            // Not first page
882
            $this->url['pageFirst'] = $this->genUrl(
883
                [$this->configs['pageParam'] => 1]
884
            );
885
            $this->url['pagePrev'] = $this->genUrl(
886
                [$this->configs['pageParam'] => $page - 1]
887
            );
888
        } else {
889
            $this->url['pageFirst'] = '';
890
            $this->url['pagePrev'] = '';
891
        }
892
        if ($page < $pageMax) {
893
            // Not last page
894
            $this->url['pageNext'] = $this->genUrl(
895
                [$this->configs['pageParam'] => $page + 1]
896
            );
897
            $this->url['pageLast'] = $this->genUrl(
898
                [$this->configs['pageParam'] => $pageMax]
899
            );
900
        } else {
901
            $this->url['pageNext'] = '';
902
            $this->url['pageLast'] = '';
903
        }
904
905
        // Form submit target url
906
        $this->url['form'] = $this->genUrl(
907
            null,
908
            [$this->configs['pageParam']]
909
        );
910
911
        // Assign hidden input
912
        if (!empty($this->param)) {
913
            $s = '';
914
            foreach ($this->param as $k => $v) {
915
                $s .= "<input type='hidden' name='$k' value='$v' />\n";
916
            }
917
            $this->tpl->assign("{$this->tplVarPrefix}PagerHidden", $s);
918
        }
919
920
        return $this;
921
    }
922
923
924
    /**
925
     * Set ListTable title
926
     *
927
     * Usually used together with setDbQuery(), which need not set listData
928
     * from outside(use setData() method).
929
     *
930
     * In common this method should call before setDbQuery().
931
     *
932
     * @param   array   $title
933
     * @return  $this
934
     */
935
    public function setTitle($title)
936
    {
937
        $this->listTitle = $title;
938
939
        return $this;
940
    }
941
942
943
    /**
944
     * Set totalRows
945
     *
946
     * @param   int     $totalRows
947
     * @return  $this
948
     */
949
    public function setTotalRows($totalRows)
950
    {
951
        $this->info['totalRows'] = $totalRows;
952
953
        return $this;
954
    }
955
}
956