From fbced961ea78c4253ee2454c4cbe4edeadbcdbfa Mon Sep 17 00:00:00 2001
From: Woraprat <73052317+aeworaprat@users.noreply.github.com>
Date: Thu, 9 Mar 2023 00:32:35 +0700
Subject: [PATCH] update frontend

---
 html/exhi/application/controllers/General.php |   71 +-
 .../application/controllers/Instructor.php    |   12 +-
 html/exhi/application/controllers/Tag.php     |    4 +-
 .../application/models/general/Da_general.php |   12 +
 .../application/models/general/M_general.php  |    7 +
 .../application/models/member/M_member.php    |    8 +
 .../views/general/v_general_select.php        |  256 +++-
 .../views/template/now-ui-kit/javascript.php  |   12 +-
 html/exhi/assets/plugins/page.css             |    1 +
 html/exhi/assets/plugins/page.js              | 1179 +++++++++++++++++
 10 files changed, 1472 insertions(+), 90 deletions(-)
 create mode 100644 html/exhi/assets/plugins/page.css
 create mode 100644 html/exhi/assets/plugins/page.js

diff --git a/html/exhi/application/controllers/General.php b/html/exhi/application/controllers/General.php
index 6b3980e..b2d860c 100644
--- a/html/exhi/application/controllers/General.php
+++ b/html/exhi/application/controllers/General.php
@@ -235,32 +235,49 @@ class general extends Exhibition_Controller
         $this->load->view('general/v_search', $data);
     }
 
-    public function show_select($id)
-    {
-        $this->load->model('project/M_project', 'project');
-        $this->load->model('project_tag/M_project_tag', 'project_tag');
-        $this->load->model('detail/M_detail', 'detail');
-        $this->load->model('member/M_member', 'member');
-        $this->load->model('cluster/M_cluster', 'cluster');
-        $this->load->model('team/M_team', 'team');
-        $this->load->model('instructor/M_instructor', 'instructor');
-        $data['project'] =  $this->project->get_project_by_id($id)->row();
-        $select = '';
-        if ($data['project']->cluster_id != 0) {
-            $select = $this->cluster->get_cluster_by_id($data['project']->cluster_id)->row();
-        } else if ($data['project']->team_id != 0) {
-            $select  = $this->team->get_team_by_id($data['project']->team_id)->row();
-        } else {
-            $select  = $this->instructor->get_by_id($data['project']->instructor_id)->row();
-        }
-        $data['project']->select = $select->name;
-        $data['project_tag'] =  $this->project_tag->get_by_id($id)->result();
-        $data['detail'] = $this->detail->get_detail_by_project_id($id)->result();
-        $data['member'] = $this->member->get_member_by_project_id($id)->result();
-        // echo '<pre>';
-        // print_r($data);
-        // echo '</pre>';
+    public function show_select($id){
+		$this->load->model('project/M_project','project');
+		$this->load->model('project_tag/M_project_tag','project_tag');
+		$this->load->model('detail/M_detail','detail');
+		$this->load->model('member/M_member','member');
+		$this->load->model('cluster/M_cluster','cluster');
+		$this->load->model('team/M_team','team');
+		$this->load->model('instructor/M_instructor','instructor');
+		$data['project'] =  $this->project->get_project_by_id($id)->row();
+		$select = '';
+		if($data['project']->cluster_id != 0){
+			$select = $this->cluster->get_cluster_by_id($data['project']->cluster_id)->row();
+		}else if($data['project']->team_id != 0){
+			$select  = $this->team->get_team_by_id($data['project']->team_id)->row();
+		}else{
+			$select  = $this->instructor->get_by_id($data['project']->instructor_id)->row();
+		}
+		$data['project']->select = $select->name;
+		$data['project_tag'] =  $this->project_tag->get_by_id($id)->result();
+		$data['detail'] = $this->detail->get_detail_by_project_id($id)->result();
+		$data['member'] = $this->member->get_member_frontend($id)->result();
+		// echo '<pre>';
+		// print_r($data);
+		// echo '</pre>';
+		
+		$this->output_frontend('general/v_general_select', $data);
+	}
 
-        $this->output_frontend('general/v_general_select', $data);
-    }
+	public function get_comment()
+	{
+		$id = $this->input->post('id');
+		$this->load->model('general/M_general', 'general');
+		$data['comment'] = $this->general->get_comment_all($id)->result();
+		echo json_encode($data);
+	}
+
+	public function insert_comment(){
+		$sender = $this->input->post('sender');
+		$detail = $this->input->post('detail');
+		$id = $this->input->post('id');
+		$this->load->model('general/M_general', 'general');
+		$this->general->insert_comment($sender, $detail, date("Y-m-d"), $id);
+		$data['message'] = true;
+		echo json_encode($data);
+	}
 }
diff --git a/html/exhi/application/controllers/Instructor.php b/html/exhi/application/controllers/Instructor.php
index 571f738..8078f01 100644
--- a/html/exhi/application/controllers/Instructor.php
+++ b/html/exhi/application/controllers/Instructor.php
@@ -27,10 +27,18 @@ class Instructor extends Exhibition_Controller {
 	}
 
 	public function insert_instructor(){
+		$data['message'] = true ;
 		$name = $this->input->post('name');
 		$this->load->model('instructor/M_instructor','instructor');
-		$this->instructor->insert_instructor($name);
-		$data['message'] = true ;
+		$instructors = $this->instructor->get_all()->result();
+		foreach($instructors as $instructor){
+			if(strtoupper($instructor->name) == strtoupper($name)){
+				$data['message'] = false ;
+			}
+		}
+		if($data['message'] == true){ 
+			$this->instructor->insert_instructor($name);
+		}
         echo json_encode($data);
 	}
 
diff --git a/html/exhi/application/controllers/Tag.php b/html/exhi/application/controllers/Tag.php
index 7279251..4e5cbf9 100644
--- a/html/exhi/application/controllers/Tag.php
+++ b/html/exhi/application/controllers/Tag.php
@@ -36,7 +36,9 @@ class Tag extends Exhibition_Controller {
 				$data['message'] = false ;
 			}
 		}
-		$this->tag->insert_tag($name);
+		if($data['message'] == true){ 
+			$this->tag->insert_tag($name);
+		}
         echo json_encode($data);
 	}
 
diff --git a/html/exhi/application/models/general/Da_general.php b/html/exhi/application/models/general/Da_general.php
index 1b0009b..0af0274 100644
--- a/html/exhi/application/models/general/Da_general.php
+++ b/html/exhi/application/models/general/Da_general.php
@@ -10,4 +10,16 @@ class Da_general extends Exhibition_Model
     {
         parent::__construct();
     }
+
+	public function insert_comment($sender, $detail, $date, $id){
+		$sql = "INSERT INTO {$this->db_name}.comment 
+		(
+            sender,
+			detail,
+			date,
+			project_id
+        )
+        VALUES (?,?,?,?);";
+        $this->db->query($sql, [$sender, $detail, $date, $id]);     
+	}
 }
diff --git a/html/exhi/application/models/general/M_general.php b/html/exhi/application/models/general/M_general.php
index a88dc5e..f8f5017 100644
--- a/html/exhi/application/models/general/M_general.php
+++ b/html/exhi/application/models/general/M_general.php
@@ -177,4 +177,11 @@ class M_general extends Da_general
         $query = $this->db->query($sql);
         return $query;
     }
+
+	public function get_comment_all($id){
+		$sql = "SELECT * FROM {$this->db_name}.comment
+		Where project_id = $id";
+        $query = $this->db->query($sql);
+        return $query;
+	}
 }
diff --git a/html/exhi/application/models/member/M_member.php b/html/exhi/application/models/member/M_member.php
index 27739bc..8325ac7 100644
--- a/html/exhi/application/models/member/M_member.php
+++ b/html/exhi/application/models/member/M_member.php
@@ -17,4 +17,12 @@ class M_member extends Da_member
         $query = $this->db->query($sql);
         return $query;
 	}
+
+	public function get_member_frontend($id){
+		$sql = "SELECT user.user_id, user.student_id, user.prefix, user.first_name, user.last_name, user.email, member.image FROM {$this->db_name}.member
+		join user on member.user_id = user.user_id
+		Where project_id = $id";
+        $query = $this->db->query($sql);
+        return $query;
+	}
 }
diff --git a/html/exhi/application/views/general/v_general_select.php b/html/exhi/application/views/general/v_general_select.php
index 9a02977..0e17ea7 100644
--- a/html/exhi/application/views/general/v_general_select.php
+++ b/html/exhi/application/views/general/v_general_select.php
@@ -3,6 +3,7 @@
 <!-- <link rel="stylesheet" href="https://cdn.datatables.net/1.11.4/css/dataTables.bootstrap4.min.css" /> -->
 
 <script src="https://cdn.datatables.net/1.11.4/js/jquery.dataTables.min.js"></script>
+<link href="<?php echo base_url().'assets/plugins/page.css'?>" rel="stylesheet" />
 
 
 <style>
@@ -63,8 +64,8 @@ body {
 }
 
 .card:hover {
-    transform: scale(1.02);
-    box-shadow: 0 10px 20px rgba(0, 0, 0, .12), 0 4px 8px rgba(0, 0, 0, .06);
+    /* transform: scale(1.02);
+    box-shadow: 0 10px 20px rgba(0, 0, 0, .12), 0 4px 8px rgba(0, 0, 0, .06); */
 }
 
 .section {
@@ -143,6 +144,23 @@ span{
     margin-left: auto;
     margin-right: auto;
 }
+.text-primary-custom {
+	color: #007bff !important
+}
+.form-control:focus {
+    border: 1px solid #767170;
+    box-shadow: none;
+    outline: 0!important;
+    color: #2c2c2c;
+}
+.bg-light {
+	background-color: while !important;
+}
+.center {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
 </style>
 <div class="wrapper">
     <div class="container">
@@ -185,14 +203,18 @@ span{
 								</div>
 								<div class="col-6">
 									<div class="row">
-										<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image1 ?>" alt="">
-										</a>
+										<div class="col-12">
+											<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
+												<img class="image-detail center" src="<?php echo base_url().$d->image1 ?>" alt="">
+											</a>
+										</div>
 									</div>
 									<div class="row">
+										<div class="col-12">
 										<a href="<?php echo base_url().$d->image2 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image2 ?>" alt="">
+											<img class="image-detail center" src="<?php echo base_url().$d->image2 ?>" alt="">
 										</a>
+										</div>
 									</div>
 								</div>
 							</div>
@@ -203,14 +225,18 @@ span{
 							<div class="row">
 								<div class="col-6">
 									<div class="row">
-										<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image1 ?>" alt="">
-										</a>
+										<div class="col-12">
+											<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
+												<img class="image-detail center" src="<?php echo base_url().$d->image1 ?>" alt="">
+											</a>
+										</div>
 									</div>
 									<div class="row">
+										<div class="col-12">
 										<a href="<?php echo base_url().$d->image2 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image2 ?>" alt="">
+											<img class="image-detail center" src="<?php echo base_url().$d->image2 ?>" alt="">
 										</a>
+										</div>
 									</div>
 								</div>
 								<div class="col-6">
@@ -223,45 +249,74 @@ span{
 						<div class="card-body " style='padding: 20px; margin-left: 5px;'>
 							<div class="row">
 								<div class="col-6">
-									<div class="row">
-										<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image1 ?>" alt="">
-										</a>
-									</div>
-									<div class="row">
-										<a href="<?php echo base_url().$d->image2 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image2 ?>" alt="">
-										</a>
-									</div>
+									<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
+										<img class="image-detail" src="<?php echo base_url().$d->image1 ?>" alt="">
+									</a>
+								</div>
+								<div class="col-6">
+									<a href="<?php echo base_url().$d->image2 ?>" data-toggle="lightbox" data-gallery="example-gallery">
+										<img class="image-detail" src="<?php echo base_url().$d->image2 ?>" alt="">
+									</a>
 								</div>
 							</div>
-							<div class="col-6">
+							<div class="col-12">
 									<?php echo $d->description ?>
 							</div>
 						</div>
 						<?php } ?>
 						<?php if($d->template == 4){ ?>
-						<div class="card-body " style='padding: 20px; margin-left: 5px;'>
+						<div class="card-body" style='padding: 20px; margin-left: 5px; text-align: justify;'>
 							<div class="row">
 								<div class="col-12">
 									<?php echo $d->description ?>
 								</div>
 								<div class="col-6">
 									<a href="<?php echo base_url().$d->image1 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image1 ?>" alt="">
+										<img class="image-detail" src="<?php echo base_url().$d->image1 ?>" alt="">
 									</a>
 								</div>
 								<div class="col-6">
-										<a href="<?php echo base_url().$d->image2 ?>" data-toggle="lightbox" data-gallery="example-gallery">
-											<img class="image-detail" src="<?php echo base_url().$d->image2 ?>" alt="">
-										</a>
+									<a href="<?php echo base_url().$d->image2 ?>" data-toggle="lightbox" data-gallery="example-gallery">
+										<img class="image-detail" src="<?php echo base_url().$d->image2 ?>" alt="">
+									</a>
 								</div>
 							</div>
 						</div>
 						<?php } ?>
 					<?php } ?>
 					<hr>
-                </div>
+					<div class="text-center">
+						<h4 class="title" style="margin-top: 0px;">สมาชิก</h4>
+					</div>
+					<div class="row">
+					<?php foreach($member as $m){ ?>
+						<div class="col-md-4 article-loop" id="">
+							<div class="card">
+								<div class="text-center">
+									<img class="my-image" src='<?php echo base_url().$m->image ?>'>
+								</div>	
+								<div class="card-body">
+									<h5><?php echo $m->prefix.$m->first_name.' '.$m->last_name ?></h5>
+									<span><h5><i class="fa fa-envelope icon"></i>  :  <?php echo $m->email ?></h5</span><br>
+								</div>
+							</div>
+						</div>
+					<?php } ?>
+					</div>
+					<hr>
+					<div class="text-center">
+						<h4 class="title" style="margin-top: 0px;">ความคิดเห็น</h4>
+					</div>
+					<div class="bg-light-custom p-2">
+					<div class="form-group">
+						<input type="text" id="sender" value="" placeholder="ชื่อ-นามสกุล" class="form-control">
+					</div>
+					<textarea class="form-control ml-1 shadow-none textarea" placeholder="เขียนความคิดเห็น..." id="detail"></textarea></div>
+                    	<div class="mt-2 text-right"><button class="btn btn-info btn-sm shadow-none" type="button" onclick="insert_comment()">เพิ่มความคิดเห็น</button></div>
+                	</div>
+					<div class="data-container"></div>
+					<div id="demo"></div>
+				</div>
             </div>
         </div>
     </div>
@@ -270,47 +325,146 @@ span{
 <div class="container" >
 	<div class="row">
 		<div class="col-md-5" >
-			<b style="font-size:16px;">CLICKNEXT COMPANY LIMITED</b> <br>
-			<span>128/323-333  30th FL.  Phayathai Plaza Bldg.  Phayathai road, Thung Phayathai, Ratchathewi, Bangkok, 10400, Thailand. </span>
-		</div>
-		<div class="col-md-3 ml-auto" style="margin-top: 20px;">
-			<i class="fa fa-phone icon"></i>  <span>02-217-7900</span>  <br>
-			<i class="fa fa-envelope icon"></i> <span>Email : info@clicknext.com </span> 
+			<b style="font-size:16px;">คณะวิทยาการสารสนเทศ</b> <br>
+			<span>169 ถนนลงหาดบางแสน ตำบลแสนสุข</span><br>
+			<span>อำเภอเมือง จังหวัดชลบุรี 20131</span>
 		</div>
-		<div class="col-md-3 ml-auto" style="margin-top: 20px;">
-		<a style="color: black;" target="_blank" href="https://www.facebook.com/clicknext/">
-			<i class="fa-brands fa-facebook icon"></i>    
-			<span>Clicknext Facebook Fanpage</span> 
-		</a>
-		<br>
-		<a style="color: black;" target="_blank" href="https://www.youtube.com/channel/UCm3Qapv2K4lj4h6F4aod1Fw">
-			<i class="fa-brands fa-youtube icon"></i>
-			<span>Youtube Channel</span> 
-		</a>
+		<div class="col-md-7 ml-auto" style="margin-top: 20px;">
+			<i class="fa fa-phone icon"></i>  <span>+66 (0)38-103061,+66 (0)38-10309</span>  <br>
+			<i class="fa fa-envelope icon"></i> <span>Email : pr@informatics.buu.ac.th </span> 
 		</div>
     </div>
 </div>
 </div>
 </footer>
-
 <footer class="footer-bar">
-    
     <div class=" container ">
-
         <div class="row justify-content-md-center">
             <div class="col-md-12">
-                <div style="text-align: left;" data-mce-style="text-align: left;"><span style="font-size: 12px; font-family: Barlow_Regular0, Tahoma; color: rgb(157, 157, 157);" open="" sans="" color:="" rgb="" 157="" data-mce-style="font-size: 16px; font-family: Barlow_Regular0, Tahoma; color: #9d9d9d;">Copyright © ClickNext Co.,Ltd All right reserved.</span></div>
+                <div style="text-align: left;" data-mce-style="text-align: left;"><span style="font-size: 12px; font-family: Barlow_Regular0, Tahoma; color: rgb(157, 157, 157);" open="" sans="" color:="" rgb="" 157="" data-mce-style="font-size: 16px; font-family: Barlow_Regular0, Tahoma; color: #9d9d9d;">Copyright © 2018-2022 Faculty of Informatics, Burapha University. All rights reserved.</span></div>
             </div>
         </div>
-
     </div>
-    
-
-
 </footer>
+<script src="<?php echo base_url()?>assets/plugins/page.js"></script>
 <script>
+	
+	$(document).ready(function() {
+		get_comment()
+	});
 	$(document).on('click', '[data-toggle="lightbox"]', function(event) {
 		event.preventDefault();
 		$(this).ekkoLightbox();
     });
+
+	function get_comment(){
+		$.ajax({
+        type: 'post',
+        url: "<?php echo site_url() . '/General/get_comment'; ?>",
+        data: {
+			'id': '<?php echo $project->project_id ?>'
+        },
+        dataType: 'json',
+        success: function(data) {
+            create_table(data.comment);
+		}
+    })
+	}
+
+	function create_table(datas){
+		var container = $('#demo');
+		$('#demo').pagination({
+			dataSource: datas,
+			pageSize: 5,
+			callback: function(data, pagination) {
+					dataHtml = ''
+					dataHtml += '<div class="headings d-flex justify-content-between align-items-center mb-3">'
+                    dataHtml += '<h6>ความคิดเห็นทั้งหมด ('+datas.length+')</h6>'
+                    dataHtml += '</div>'
+					$.each(data, function (index, item) {
+						console.log(item)
+						dataHtml += '<div class="card p-3 mt-2">';
+						dataHtml += '<div class="d-flex justify-content-between align-items-center">';
+						dataHtml += '<div class="user d-flex flex-row align-items-center">';
+						dataHtml += '<span><div class="font-weight-bold text-primary-custom">'+item.sender+'</div><br> <div>'+item.detail+'</div></span>';
+						dataHtml += '</div>';
+						dataHtml += ' <small>'+convertDate(item.date)+'</small>';
+						dataHtml += '</div>';
+						dataHtml += '</div>';
+
+					});
+				container.prev().html(dataHtml);
+			}
+		})
+	}
+
+	function convertDate(dateString){
+		var p = dateString.split(/\D/g)
+		return [p[2],p[1],p[0] ].join("/")
+	}
+
+	const Toast = Swal.mixin({
+		toast: true,
+		position: 'bottom-start',
+		showConfirmButton: false,
+		timer: 1500,
+		timerProgressBar: true,
+		didOpen: (toast) => {
+			toast.addEventListener('mouseenter', Swal.stopTimer)
+			toast.addEventListener('mouseleave', Swal.resumeTimer)
+    	}
+	})
+
+	function insert_comment(){
+		let sender = $('#sender').val()
+		let detail = $('#detail').val()
+		if(sender == '' || detail == ''){
+			Toast.fire({
+				icon: 'error',
+				title: 'กรุณากรอกข้อมูลให้ครบถ้วน',
+			})
+		}else{
+			Swal.fire({
+				title: ' คุณยืนยันที่จะเพิ่มความคิดเห็นหรือไม่',
+				text: "",
+				icon: 'question',
+				showCancelButton: true,
+				confirmButtonColor: '#3177ce',
+				cancelButtonColor: ' white ',
+				confirmButtonText: 'ใช่',
+				cancelButtonText:  '<Font color=black> ไม่ <Font>',
+				reverseButtons: true
+		}).then((result) => {
+			if (result.value) {
+				$.ajax({
+					type: 'post',
+					url: "<?php echo site_url().'/General/insert_comment'; ?>",
+					data: {
+						'sender': sender,
+						'detail': detail,
+						'id': '<?php echo $project->project_id ?>'
+					},
+					dataType: 'json',
+					success: function(data) {
+						if (data['message'] == true) {
+							Toast.fire({
+								icon: 'success',
+								title: 'เพิ่มข้อมูลสำเร็จ',
+							}).then((result) => {
+								get_comment()
+							})
+						}else {
+							Toast.fire({
+								icon: 'error',
+								title: "เพิ่มข้อมูลไม่สำเร็จ",
+							})
+						}
+					}
+				});
+			}
+		})
+		$(".swal2-cancel").css("border", "1px solid #A79E9E");
+		}
+
+	}
 </script>
diff --git a/html/exhi/application/views/template/now-ui-kit/javascript.php b/html/exhi/application/views/template/now-ui-kit/javascript.php
index f1179fc..9c2f83b 100644
--- a/html/exhi/application/views/template/now-ui-kit/javascript.php
+++ b/html/exhi/application/views/template/now-ui-kit/javascript.php
@@ -1,16 +1,10 @@
+
 <script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/core/popper.min.js" ></script> 
 <script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/core/jquery.min.js" ></script> 
 <script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/core/bootstrap.min.js" ></script> 
 <script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/plugins/bootstrap-switch.js" ></script>
 <script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/plugins/nouislider.min.js" ></script> 
-<script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/now-ui-kit.js" ></script> 
+<script src="<?php echo base_url()?>assets/template/now-ui-kit-master/assets/js/now-ui-kit.js"></script> 
 <script src="<?php echo base_url()?>assets/plugins/fontawesome/js/all.min.js" ></script> 
-
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.js"></script>
-
-
-
-
-
-
-
+<script src="<?php echo base_url()?>assets/template/AdminLTE-3.2.0/plugins/sweetalert2/sweetalert2.all.min.js"></script>
diff --git a/html/exhi/assets/plugins/page.css b/html/exhi/assets/plugins/page.css
new file mode 100644
index 0000000..b6225cc
--- /dev/null
+++ b/html/exhi/assets/plugins/page.css
@@ -0,0 +1 @@
+.paginationjs{display:flex;line-height:1.6;font-family:Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,sans-serif;font-size:14px;box-sizing:initial}.paginationjs:after{display:table;content:" ";clear:both}.paginationjs .paginationjs-pages{float:left;margin-left:10px}.paginationjs .paginationjs-pages ul{float:left;margin:0;padding:0}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input,.paginationjs .paginationjs-size-changer{margin-left:10px;float:left;font-size:14px}.paginationjs .paginationjs-pages li{float:left;border:1px solid #aaa;border-right:none;list-style:none}.paginationjs .paginationjs-pages li>a{min-width:30px;height:28px;line-height:28px;display:block;background:#fff;font-size:14px;color:#333;text-decoration:none;text-align:center;cursor:pointer}.paginationjs .paginationjs-pages li>a:hover{background:#eee}.paginationjs .paginationjs-pages li.active{border:none}.paginationjs .paginationjs-pages li.active>a{height:30px;line-height:30px;background:#aaa;color:#fff;cursor:default}.paginationjs .paginationjs-pages li.disabled>a{opacity:.3;cursor:default}.paginationjs .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs .paginationjs-pages li:first-child,.paginationjs .paginationjs-pages li:first-child>a{border-radius:3px 0 0 3px}.paginationjs .paginationjs-pages li:last-child{border-right:1px solid #aaa;border-radius:0 3px 3px 0}.paginationjs .paginationjs-pages li:last-child>a{border-radius:0 3px 3px 0}.paginationjs .paginationjs-size-changer>select{height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;padding:0;font-size:14px;text-align:center;vertical-align:baseline;outline:0;box-shadow:none;box-sizing:initial}.paginationjs .paginationjs-go-input>input[type=text]{width:30px;height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;padding:0;font-size:14px;text-align:center;vertical-align:baseline;outline:0;box-shadow:none;box-sizing:initial}.paginationjs .paginationjs-go-button>input[type=button]{min-width:40px;height:30px;line-height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;text-align:center;padding:0 8px;font-size:14px;vertical-align:baseline;outline:0;box-shadow:none;color:#333;cursor:pointer;vertical-align:middle\9}.paginationjs .paginationjs-go-button>input[type=button]:hover{background-color:#f8f8f8}.paginationjs .paginationjs-nav{float:left;height:30px;line-height:30px;font-size:14px}.paginationjs.paginationjs-small{font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li>a{min-width:26px;height:24px;line-height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li.active>a{height:26px;line-height:26px}.paginationjs.paginationjs-small .paginationjs-size-changer{font-size:12px}.paginationjs.paginationjs-small .paginationjs-size-changer>select{height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-input{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-input>input[type=text]{width:26px;height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button>input[type=button]{min-width:30px;height:26px;line-height:24px;padding:0 6px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-nav{height:26px;line-height:26px;font-size:12px}.paginationjs.paginationjs-big{font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li>a{min-width:36px;height:34px;line-height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li.active>a{height:36px;line-height:36px}.paginationjs.paginationjs-big .paginationjs-size-changer{font-size:16px}.paginationjs.paginationjs-big .paginationjs-size-changer>select{height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-input{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{width:36px;height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button>input[type=button]{min-width:50px;height:36px;line-height:34px;padding:0 12px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-nav{height:36px;line-height:36px;font-size:16px}.paginationjs>:first-child{margin-left:0}.paginationjs.paginationjs-theme-blue .paginationjs-pages li{border-color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a{color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a:hover{background:#e9f4fc}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.active>a{background:#289de9;color:#fff}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-blue .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-blue .paginationjs-size-changer>select{border-color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]{background:#289de9;border-color:#289de9;color:#fff}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]:hover{background-color:#3ca5ea}.paginationjs.paginationjs-theme-green .paginationjs-pages li{border-color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a{color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a:hover{background:#ebf4eb}.paginationjs.paginationjs-theme-green .paginationjs-pages li.active>a{background:#449d44;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-green .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-green .paginationjs-size-changer>select{border-color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]{background:#449d44;border-color:#449d44;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]:hover{background-color:#55a555}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li{border-color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a{color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a:hover{background:#fdf5e9}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.active>a{background:#ec971f;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-yellow .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-yellow .paginationjs-size-changer>select{border-color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]{background:#ec971f;border-color:#ec971f;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]:hover{background-color:#eea135}.paginationjs.paginationjs-theme-red .paginationjs-pages li{border-color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a{color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a:hover{background:#faeaea}.paginationjs.paginationjs-theme-red .paginationjs-pages li.active>a{background:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-red .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-red .paginationjs-size-changer>select{border-color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]{background:#c9302c;border-color:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]:hover{background-color:#ce4541}.paginationjs .paginationjs-pages li.paginationjs-next{border-right:1px solid #aaa\9}.paginationjs .paginationjs-size-changer{margin-left:5px\9}.paginationjs .paginationjs-size-changer>select{line-height:28px\9;vertical-align:middle\9}.paginationjs .paginationjs-go-input{margin-left:5px\9}.paginationjs .paginationjs-go-input>input[type=text]{line-height:28px\9;vertical-align:middle\9}.paginationjs .paginationjs-go-button{margin-left:5px\9}.paginationjs.paginationjs-big .paginationjs-pages li>a{line-height:36px\9}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{height:36px\9;line-height:36px\9}
\ No newline at end of file
diff --git a/html/exhi/assets/plugins/page.js b/html/exhi/assets/plugins/page.js
new file mode 100644
index 0000000..3b7fefc
--- /dev/null
+++ b/html/exhi/assets/plugins/page.js
@@ -0,0 +1,1179 @@
+/*
+ * pagination.js 2.5.0
+ * A jQuery plugin to provide simple yet fully customisable pagination.
+ * https://github.com/superRaytin/paginationjs
+ *
+ * Homepage: http://pagination.js.org
+ *
+ * Copyright 2014-2100, superRaytin
+ * Released under the MIT license.
+ */
+
+(function(global, $) {
+
+  if (typeof $ === 'undefined') {
+    throwError('Pagination requires jQuery.');
+  }
+
+  var pluginName = 'pagination';
+
+  var pluginHookMethod = 'addHook';
+
+  var eventPrefix = '__pagination-';
+
+  if ($.fn.pagination) {
+    throwError('plugin conflicted, the name "pagination" has been taken by another jQuery plugin.');
+  }
+
+  $.fn[pluginName] = function(options) {
+
+    if (typeof options === 'undefined') {
+      return this;
+    }
+
+    var container = $(this);
+
+    var attributes = $.extend({}, $.fn[pluginName].defaults, options);
+
+    var pagination = {
+
+      initialize: function() {
+        var self = this;
+
+        // Cache data for current instance
+        if (!container.data('pagination')) {
+          container.data('pagination', {});
+        }
+
+        if (self.callHook('beforeInit') === false) return;
+
+        // Pagination has been initialized, destroy it
+        if (container.data('pagination').initialized) {
+          $('.paginationjs', container).remove();
+        }
+
+        // Whether to disable Pagination at the initialization
+        self.disabled = !!attributes.disabled;
+
+        // Model will be passed to the callback function
+        var model = self.model = {
+          pageRange: attributes.pageRange,
+          pageSize: attributes.pageSize
+        };
+
+        // Parse dataSource to find available paging data
+        self.parseDataSource(attributes.dataSource, function(dataSource) {
+
+          // Asynchronous mode
+          self.isAsync = Helpers.isString(dataSource);
+          if (Helpers.isArray(dataSource)) {
+            model.totalNumber = attributes.totalNumber = dataSource.length;
+          }
+
+          // Asynchronous mode and a 'totalNumberLocator' has been specified
+          self.isDynamicTotalNumber = self.isAsync && attributes.totalNumberLocator;
+
+          var el = self.render(true);
+
+          // Add extra className to the pagination element
+            if (attributes.className) {
+            el.addClass(attributes.className);
+          }
+
+          model.el = el;
+
+          // Append / prepend pagination element to the container
+          container[attributes.position === 'bottom' ? 'append' : 'prepend'](el);
+
+          // Bind events
+          self.observer();
+
+          // Mark pagination has been initialized
+          container.data('pagination').initialized = true;
+
+          // Call hook after initialization
+          self.callHook('afterInit', el);
+        });
+      },
+
+      render: function(isBoot) {
+        var self = this;
+        var model = self.model;
+        var el = model.el || $('<div class="paginationjs"></div>');
+        var isForced = isBoot !== true;
+
+        self.callHook('beforeRender', isForced);
+
+        var currentPage = model.pageNumber || attributes.pageNumber;
+        var pageRange = attributes.pageRange || 0;
+        var totalPage = self.getTotalPage();
+
+        var rangeStart = currentPage - pageRange;
+        var rangeEnd = currentPage + pageRange;
+
+        if (rangeEnd > totalPage) {
+          rangeEnd = totalPage;
+          rangeStart = totalPage - pageRange * 2;
+          rangeStart = rangeStart < 1 ? 1 : rangeStart;
+        }
+
+        if (rangeStart <= 1) {
+          rangeStart = 1;
+          rangeEnd = Math.min(pageRange * 2 + 1, totalPage);
+        }
+
+        el.html(self.generateHTML({
+          currentPage: currentPage,
+          pageRange: pageRange,
+          rangeStart: rangeStart,
+          rangeEnd: rangeEnd
+        }));
+
+        // Whether to hide pagination when there is only one page
+        if (attributes.hideOnlyOnePage) {
+          el[totalPage <= 1 ? 'hide' : 'show']();
+        }
+
+        self.callHook('afterRender', isForced);
+
+        return el;
+      },
+
+      getPageLinkTag: function(index) {
+        var pageLink = attributes.pageLink;
+        return pageLink ? `<a href="${pageLink}">${index}</a>` : `<a>${index}</a>`;
+      },
+
+      // Generate HTML for page numbers
+      generatePageNumbersHTML: function(args) {
+        var self = this;
+        var currentPage = args.currentPage;
+        var totalPage = self.getTotalPage();
+        var getPageLinkTag = self.getPageLinkTag;
+        var rangeStart = args.rangeStart;
+        var rangeEnd = args.rangeEnd;
+        var html = '';
+        var i;
+
+        var ellipsisText = attributes.ellipsisText;
+
+        var classPrefix = attributes.classPrefix;
+        var pageClassName = attributes.pageClassName || '';
+        var activeClassName = attributes.activeClassName || '';
+        var disableClassName = attributes.disableClassName || '';
+
+        // Display all page numbers if page range disabled
+        if (attributes.pageRange === null) {
+          for (i = 1; i <= totalPage; i++) {
+            if (i == currentPage) {
+              html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName} ${activeClassName}" data-num="${i}"><a>${i}</a></li>`;
+            } else {
+              html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
+            }
+          }
+          return html;
+        }
+
+        if (rangeStart <= 3) {
+          for (i = 1; i < rangeStart; i++) {
+            if (i == currentPage) {
+              html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName} ${activeClassName}" data-num="${i}"><a>${i}</a></li>`;
+            } else {
+              html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
+            }
+          }
+        } else {
+          if (!attributes.hideFirstOnEllipsisShow) {
+            html += `<li class="${classPrefix}-page ${classPrefix}-first J-paginationjs-page ${pageClassName}" data-num="1">${getPageLinkTag(1)}</li>`;
+          }
+          html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`;
+        }
+
+        for (i = rangeStart; i <= rangeEnd; i++) {
+          if (i == currentPage) {
+            html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName} ${activeClassName}" data-num="${i}"><a>${i}</a></li>`;
+          } else {
+            html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
+          }
+        }
+
+        if (rangeEnd >= totalPage - 2) {
+          for (i = rangeEnd + 1; i <= totalPage; i++) {
+            html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
+          }
+        } else {
+          html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`;
+
+          if (!attributes.hideLastOnEllipsisShow) {
+            html += `<li class="${classPrefix}-page ${classPrefix}-last J-paginationjs-page ${pageClassName}" data-num="${totalPage}">${getPageLinkTag(totalPage)}</li>`;
+          }
+        }
+
+        return html;
+      },
+
+      // Generate HTML content
+      generateHTML: function(args) {
+        var self = this;
+        var currentPage = args.currentPage;
+        var totalPage = self.getTotalPage();
+        var getPageLinkTag = self.getPageLinkTag;
+
+        var totalNumber = self.getTotalNumber();
+
+        var pageSize = attributes.pageSize;
+        var showPrevious = attributes.showPrevious;
+        var showNext = attributes.showNext;
+        var showPageNumbers = attributes.showPageNumbers;
+        var showNavigator = attributes.showNavigator;
+        var showSizeChanger = attributes.showSizeChanger;
+        var sizeChangerOptions = attributes.sizeChangerOptions;
+        var showGoInput = attributes.showGoInput;
+        var showGoButton = attributes.showGoButton;
+
+        var prevText = attributes.prevText;
+        var nextText = attributes.nextText;
+        var goButtonText = attributes.goButtonText;
+
+        var classPrefix = attributes.classPrefix;
+        var disableClassName = attributes.disableClassName || '';
+        var ulClassName = attributes.ulClassName || '';
+        var prevClassName = attributes.prevClassName || '';
+        var nextClassName = attributes.nextClassName || '';
+
+        var html = '';
+        var sizeSelect = `<select class="J-paginationjs-size-select">`;
+        var goInput = '<input type="text" class="J-paginationjs-go-pagenumber">';
+        var goButton = `<input type="button" class="J-paginationjs-go-button" value="${goButtonText}">`;
+        var formattedString;
+
+        var formatSizeChanger = typeof attributes.formatSizeChanger === 'function' ? attributes.formatSizeChanger(currentPage, totalPage, totalNumber) : attributes.formatSizeChanger;
+        var formatNavigator = typeof attributes.formatNavigator === 'function' ? attributes.formatNavigator(currentPage, totalPage, totalNumber) : attributes.formatNavigator;
+        var formatGoInput = typeof attributes.formatGoInput === 'function' ? attributes.formatGoInput(goInput, currentPage, totalPage, totalNumber) : attributes.formatGoInput;
+        var formatGoButton = typeof attributes.formatGoButton === 'function' ? attributes.formatGoButton(goButton, currentPage, totalPage, totalNumber) : attributes.formatGoButton;
+
+        var autoHidePrevious = typeof attributes.autoHidePrevious === 'function' ? attributes.autoHidePrevious() : attributes.autoHidePrevious;
+        var autoHideNext = typeof attributes.autoHideNext === 'function' ? attributes.autoHideNext() : attributes.autoHideNext;
+
+        var header = typeof attributes.header === 'function' ? attributes.header(currentPage, totalPage, totalNumber) : attributes.header;
+        var footer = typeof attributes.footer === 'function' ? attributes.footer(currentPage, totalPage, totalNumber) : attributes.footer;
+
+        // Prepend extra contents to the pagination buttons
+        if (header) {
+          formattedString = self.replaceVariables(header, {
+            currentPage: currentPage,
+            totalPage: totalPage,
+            totalNumber: totalNumber
+          });
+          html += formattedString;
+        }
+
+        // Whether to display navigator
+        if (showNavigator) {
+          if (formatNavigator) {
+            formattedString = self.replaceVariables(formatNavigator, {
+              currentPage: currentPage,
+              totalPage: totalPage,
+              totalNumber: totalNumber,
+              rangeStart: (currentPage - 1) * pageSize + 1,
+              rangeEnd: Math.min(currentPage * pageSize, totalNumber)
+            });
+            html += `<div class="${classPrefix}-nav J-paginationjs-nav">${formattedString}</div>`;
+          }
+        }
+
+        if (showPrevious || showPageNumbers || showNext) {
+          html += '<div class="paginationjs-pages">';
+
+          if (ulClassName) {
+            html += `<ul class="${ulClassName}">`;
+          } else {
+            html += '<ul>';
+          }
+
+          // Whether to display Previous button
+          if (showPrevious) {
+            if (currentPage <= 1) {
+              if (!autoHidePrevious) {
+                html += `<li class="${classPrefix}-prev ${disableClassName} ${prevClassName}"><a>${prevText}</a></li>`;
+              }
+            } else {
+              html += `<li class="${classPrefix}-prev J-paginationjs-previous ${prevClassName}" data-num="${currentPage - 1}" title="Previous page">${getPageLinkTag(prevText)}</li>`;
+            }
+          }
+
+          // Whether to display page numbers
+          if (showPageNumbers) {
+            html += self.generatePageNumbersHTML(args);
+          }
+
+          // Whether to display Next button
+          if (showNext) {
+            if (currentPage >= totalPage) {
+              if (!autoHideNext) {
+                html += `<li class="${classPrefix}-next ${disableClassName} ${nextClassName}"><a>${nextText}</a></li>`;
+              }
+            } else {
+              html += `<li class="${classPrefix}-next J-paginationjs-next ${nextClassName}" data-num="${currentPage + 1}" title="Next page">${getPageLinkTag(nextText)}</li>`;
+            }
+          }
+          html += `</ul></div>`;
+        }
+
+        if (showSizeChanger) {
+          if (Helpers.isArray(sizeChangerOptions)) {
+            if (sizeChangerOptions.indexOf(pageSize) === -1) {
+              sizeChangerOptions.unshift(pageSize);
+              sizeChangerOptions.sort((a, b) => a - b);
+            }
+            for (let i = 0; i < sizeChangerOptions.length; i++) {
+              sizeSelect += `<option value="${sizeChangerOptions[i]}"${(sizeChangerOptions[i] === pageSize ? ' selected' : '')}>${sizeChangerOptions[i]} / page</option>`;
+            }
+            sizeSelect += `</select>`;
+            formattedString = sizeSelect;
+
+            if (formatSizeChanger) {
+              formattedString = self.replaceVariables(formatSizeChanger, {
+                length: sizeSelect,
+                total: totalNumber
+              });
+            }
+            html += `<div class="paginationjs-size-changer">${formattedString}</div>`;
+          }
+        }
+
+        // Whether to display Go input
+        if (showGoInput) {
+          if (formatGoInput) {
+            formattedString = self.replaceVariables(formatGoInput, {
+              currentPage: currentPage,
+              totalPage: totalPage,
+              totalNumber: totalNumber,
+              input: goInput
+            });
+            html += `<div class="${classPrefix}-go-input">${formattedString}</div>`;
+          }
+        }
+
+        // Whether to display Go button
+        if (showGoButton) {
+          if (formatGoButton) {
+            formattedString = self.replaceVariables(formatGoButton, {
+              currentPage: currentPage,
+              totalPage: totalPage,
+              totalNumber: totalNumber,
+              button: goButton
+            });
+            html += `<div class="${classPrefix}-go-button">${formattedString}</div>`;
+          }
+        }
+
+        // Append extra contents to the pagination buttons
+        if (footer) {
+          formattedString = self.replaceVariables(footer, {
+            currentPage: currentPage,
+            totalPage: totalPage,
+            totalNumber: totalNumber
+          });
+          html += formattedString;
+        }
+
+        return html;
+      },
+
+      // dataSource is a request URL and a 'totalNumberLocator' function specified
+      // execute it to find out 'totalNumber' from the response
+      findTotalNumberFromRemoteResponse: function(response) {
+        var self = this;
+        self.model.totalNumber = attributes.totalNumberLocator(response);
+      },
+
+      // Go to the specified page
+      go: function(number, callback) {
+        var self = this;
+        var model = self.model;
+
+        if (self.disabled) return;
+
+        var pageNumber = number;
+        pageNumber = parseInt(pageNumber);
+
+        if (!pageNumber || pageNumber < 1) return;
+
+        var pageSize = attributes.pageSize;
+        var totalNumber = self.getTotalNumber();
+        var totalPage = self.getTotalPage();
+
+        if (totalNumber > 0 && pageNumber > totalPage) return;
+
+        // Pick paging data in synchronous mode
+        if (!self.isAsync) {
+          render(self.getPagingData(pageNumber));
+          return;
+        }
+
+        var postData = {};
+        var alias = attributes.alias || {};
+        var pageSizeName = alias.pageSize ? alias.pageSize : 'pageSize';
+        var pageNumberName = alias.pageNumber ? alias.pageNumber : 'pageNumber';
+        postData[pageSizeName] = pageSize;
+        postData[pageNumberName] = pageNumber;
+
+        var ajaxParams = typeof attributes.ajax === 'function' ? attributes.ajax() : attributes.ajax;
+
+        // If the pageNumber's value starts with 0 via Ajax
+        if (ajaxParams && ajaxParams.pageNumberStartWithZero) {
+          postData[pageNumberName] = pageNumber - 1;
+          delete ajaxParams.pageNumberStartWithZero;
+        }
+
+        var formatAjaxParams = {
+          type: 'get',
+          cache: false,
+          data: {},
+          contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
+          dataType: 'json',
+          async: true
+        };
+
+        $.extend(true, formatAjaxParams, ajaxParams);
+        $.extend(formatAjaxParams.data, postData);
+
+        formatAjaxParams.url = attributes.dataSource;
+        formatAjaxParams.success = function(response) {
+          self.model.originalResponse = response;
+          if (self.isDynamicTotalNumber) {
+            self.findTotalNumberFromRemoteResponse(response);
+          } else {
+            self.model.totalNumber = attributes.totalNumber;
+          }
+
+          var finalData = self.filterDataWithLocator(response);
+          render(finalData);
+        };
+        formatAjaxParams.error = function(jqXHR, textStatus, errorThrown) {
+          attributes.formatAjaxError && attributes.formatAjaxError(jqXHR, textStatus, errorThrown);
+          self.enable();
+        };
+
+        self.disable();
+
+        if (attributes.ajaxFunction) {
+          attributes.ajaxFunction(formatAjaxParams);
+        } else {
+          $.ajax(formatAjaxParams);
+        }
+
+        function render(data) {
+          if (self.callHook('beforePaging', pageNumber) === false) return false;
+
+          // Pagination direction
+          model.direction = typeof model.pageNumber === 'undefined' ? 0 : (pageNumber > model.pageNumber ? 1 : -1);
+
+          model.pageNumber = pageNumber;
+
+          self.render();
+
+          if (self.disabled && self.isAsync) {
+            // enable pagination
+            self.enable();
+          }
+
+          // cache model data
+          container.data('pagination').model = model;
+
+          // format result data before callback invoked
+          if (attributes.formatResult) {
+            var cloneData = $.extend(true, [], data);
+            if (!Helpers.isArray(data = attributes.formatResult(cloneData))) {
+              data = cloneData;
+            }
+          }
+
+          container.data('pagination').currentPageData = data;
+
+          self.doCallback(data, callback);
+
+          self.callHook('afterPaging', pageNumber);
+
+          if (pageNumber == 1) {
+            self.callHook('afterIsFirstPage');
+          } else if (pageNumber == self.getTotalPage()) {
+            self.callHook('afterIsLastPage');
+          }
+        }
+      },
+
+      doCallback: function(data, customCallback) {
+        var self = this;
+        var model = self.model;
+
+        if (typeof customCallback === 'function') {
+          customCallback(data, model);
+        } else if (typeof attributes.callback === 'function') {
+          attributes.callback(data, model);
+        }
+      },
+
+      destroy: function() {
+        if (this.callHook('beforeDestroy') === false) return;
+
+        this.model.el.remove();
+        container.off();
+
+        // Remove style element
+        $('#paginationjs-style').remove();
+
+        this.callHook('afterDestroy');
+      },
+
+      previous: function(callback) {
+        this.go(this.model.pageNumber - 1, callback);
+      },
+
+      next: function(callback) {
+        this.go(this.model.pageNumber + 1, callback);
+      },
+
+      disable: function() {
+        var self = this;
+        var source = self.isAsync ? 'async' : 'sync';
+
+        if (self.callHook('beforeDisable', source) === false) return;
+
+        self.disabled = true;
+        self.model.disabled = true;
+
+        self.callHook('afterDisable', source);
+      },
+
+      enable: function() {
+        var self = this;
+        var source = self.isAsync ? 'async' : 'sync';
+
+        if (self.callHook('beforeEnable', source) === false) return;
+
+        self.disabled = false;
+        self.model.disabled = false;
+
+        self.callHook('afterEnable', source);
+      },
+
+      refresh: function(callback) {
+        this.go(this.model.pageNumber, callback);
+      },
+
+      show: function() {
+        var self = this;
+
+        if (self.model.el.is(':visible')) return;
+
+        self.model.el.show();
+      },
+
+      hide: function() {
+        var self = this;
+
+        if (!self.model.el.is(':visible')) return;
+
+        self.model.el.hide();
+      },
+
+      // Replace variables for template string
+      replaceVariables: function(template, variables) {
+        var formattedString;
+
+        for (var key in variables) {
+          var value = variables[key];
+          var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img');
+
+          formattedString = (formattedString || template).replace(regexp, value);
+        }
+
+        return formattedString;
+      },
+
+      getPagingData: function(number) {
+        var pageSize = attributes.pageSize;
+        var dataSource = attributes.dataSource;
+        var totalNumber = this.getTotalNumber();
+
+        var start = pageSize * (number - 1) + 1;
+        var end = Math.min(number * pageSize, totalNumber);
+
+        return dataSource.slice(start - 1, end);
+      },
+
+      getTotalNumber: function() {
+        return this.model.totalNumber || attributes.totalNumber || 0;
+      },
+
+      getTotalPage: function() {
+        return Math.ceil(this.getTotalNumber() / attributes.pageSize);
+      },
+
+      getLocator: function(locator) {
+        var result;
+
+        if (typeof locator === 'string') {
+          result = locator;
+        } else if (typeof locator === 'function') {
+          result = locator();
+        } else {
+          throwError('"locator" is incorrect. Expect string or function type.');
+        }
+
+        return result;
+      },
+
+      // Filter data with "locator"
+      filterDataWithLocator: function(dataSource) {
+        var locator = this.getLocator(attributes.locator);
+        var filteredData;
+
+        // Datasource is an Object, use "locator" to locate available data
+        if (Helpers.isObject(dataSource)) {
+          try {
+            $.each(locator.split('.'), function(index, item) {
+              filteredData = (filteredData ? filteredData : dataSource)[item];
+            });
+          }
+          catch (e) {
+            // ignore
+          }
+
+          if (!filteredData) {
+            throwError('dataSource.' + locator + ' is undefined.');
+          } else if (!Helpers.isArray(filteredData)) {
+            throwError('dataSource.' + locator + ' should be an Array.');
+          }
+        }
+
+        return filteredData || dataSource;
+      },
+
+      parseDataSource: function(dataSource, callback) {
+        var self = this;
+
+        if (Helpers.isObject(dataSource)) {
+          callback(attributes.dataSource = self.filterDataWithLocator(dataSource));
+        } else if (Helpers.isArray(dataSource)) {
+          callback(attributes.dataSource = dataSource);
+        } else if (typeof dataSource === 'function') {
+          attributes.dataSource(function(data) {
+            if (!Helpers.isArray(data)) {
+              throwError('The parameter of "done" Function should be an Array.');
+            }
+            self.parseDataSource.call(self, data, callback);
+          });
+        } else if (typeof dataSource === 'string') {
+          if (/^https?|file:/.test(dataSource)) {
+            attributes.ajaxDataType = 'jsonp';
+          }
+          callback(dataSource);
+        } else {
+          throwError('Unexpected dataSource type');
+        }
+      },
+
+      callHook: function(hook) {
+        var paginationData = container.data('pagination') || {};
+        var result;
+
+        var args = Array.prototype.slice.apply(arguments);
+        args.shift();
+
+        if (attributes[hook] && typeof attributes[hook] === 'function') {
+          if (attributes[hook].apply(global, args) === false) {
+            result = false;
+          }
+        }
+
+        if (paginationData.hooks && paginationData.hooks[hook]) {
+          $.each(paginationData.hooks[hook], function(index, item) {
+            if (item.apply(global, args) === false) {
+              result = false;
+            }
+          });
+        }
+
+        return result !== false;
+      },
+
+      observer: function() {
+        var self = this;
+        var el = self.model.el;
+
+        // Go to specified page number
+        container.on(eventPrefix + 'go', function(event, pageNumber, done) {
+          if (typeof pageNumber === 'string') {
+            pageNumber = parseInt(pageNumber.trim());
+          }
+
+          if (!pageNumber) return;
+
+          if (typeof pageNumber !== 'number') {
+            throwError('"pageNumber" is incorrect. (Number)');
+          }
+
+          self.go(pageNumber, done);
+        });
+
+        // Page number button click listener
+        el.on('click', '.J-paginationjs-page', function(event) {
+          var current = $(event.currentTarget);
+          var pageNumber = current.attr('data-num').trim();
+
+          if (!pageNumber || current.hasClass(attributes.disableClassName) || current.hasClass(attributes.activeClassName)) return;
+
+          if (self.callHook('beforePageOnClick', event, pageNumber) === false) return false;
+
+          self.go(pageNumber);
+
+          self.callHook('afterPageOnClick', event, pageNumber);
+
+          if (!attributes.pageLink) return false;
+        });
+
+        // Previous button click listener
+        el.on('click', '.J-paginationjs-previous', function(event) {
+          var current = $(event.currentTarget);
+          var pageNumber = current.attr('data-num').trim();
+
+          if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
+
+          if (self.callHook('beforePreviousOnClick', event, pageNumber) === false) return false;
+
+          self.go(pageNumber);
+
+          self.callHook('afterPreviousOnClick', event, pageNumber);
+
+          if (!attributes.pageLink) return false;
+        });
+
+        // Next button click listener
+        el.on('click', '.J-paginationjs-next', function(event) {
+          var current = $(event.currentTarget);
+          var pageNumber = current.attr('data-num').trim();
+
+          if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
+
+          if (self.callHook('beforeNextOnClick', event, pageNumber) === false) return false;
+
+          self.go(pageNumber);
+
+          self.callHook('afterNextOnClick', event, pageNumber);
+
+          if (!attributes.pageLink) return false;
+        });
+
+        // Go button click listener
+        el.on('click', '.J-paginationjs-go-button', function(event) {
+          var pageNumber = $('.J-paginationjs-go-pagenumber', el).val();
+
+          if (self.callHook('beforeGoButtonOnClick', event, pageNumber) === false) return false;
+
+          container.trigger(eventPrefix + 'go', pageNumber);
+
+          self.callHook('afterGoButtonOnClick', event, pageNumber);
+        });
+
+        // go input enter keyup listener
+        el.on('keyup', '.J-paginationjs-go-pagenumber', function(event) {
+          if (event.which === 13) {
+            var pageNumber = $(event.currentTarget).val();
+
+            if (self.callHook('beforeGoInputOnEnter', event, pageNumber) === false) return false;
+
+            container.trigger(eventPrefix + 'go', pageNumber);
+
+            // Maintain the cursor
+            $('.J-paginationjs-go-pagenumber', el).focus();
+
+            self.callHook('afterGoInputOnEnter', event, pageNumber);
+          }
+        });
+
+        el.on('change', '.J-paginationjs-size-select', function(event) {
+          var current = $(event.currentTarget);
+          var size = parseInt(current.val());
+          var currentPage = self.model.pageNumber || attributes.pageNumber;
+
+          if (typeof size !== 'number') return;
+
+          if (self.callHook('beforeSizeSelectorChange', event, size) === false) return false;
+
+          attributes.pageSize = size;
+          self.model.pageSize = size;
+          self.model.totalPage = self.getTotalPage();
+          if (currentPage > self.model.totalPage) {
+            currentPage = self.model.totalPage;
+          }
+          self.go(currentPage);
+
+          self.callHook('afterSizeSelectorChange', event, size);
+
+          if (!attributes.pageLink) return false;
+        });
+
+        // Previous page
+        container.on(eventPrefix + 'previous', function(event, done) {
+          self.previous(done);
+        });
+
+        // Next page
+        container.on(eventPrefix + 'next', function(event, done) {
+          self.next(done);
+        });
+
+        // Disable
+        container.on(eventPrefix + 'disable', function() {
+          self.disable();
+        });
+
+        // Enable
+        container.on(eventPrefix + 'enable', function() {
+          self.enable();
+        });
+
+        // Refresh
+        container.on(eventPrefix + 'refresh', function(event, done) {
+          self.refresh(done);
+        });
+
+        // Show
+        container.on(eventPrefix + 'show', function() {
+          self.show();
+        });
+
+        // Hide
+        container.on(eventPrefix + 'hide', function() {
+          self.hide();
+        });
+
+        // Destroy
+        container.on(eventPrefix + 'destroy', function() {
+          self.destroy();
+        });
+
+        // Whether to load the default page
+        var validTotalPage = Math.max(self.getTotalPage(), 1)
+        var defaultPageNumber = attributes.pageNumber;
+        
+        // Default pageNumber should be 1 when totalNumber is dynamic
+        if (self.isDynamicTotalNumber) {
+          if (attributes.resetPageNumberOnInit) defaultPageNumber = 1;
+        }
+
+        if (attributes.triggerPagingOnInit) {
+          container.trigger(eventPrefix + 'go', Math.min(defaultPageNumber, validTotalPage));
+        }
+      }
+    };
+
+    // Pagination has been initialized
+    if (container.data('pagination') && container.data('pagination').initialized === true) {
+      // Handle events
+      if (isNumeric(options)) {
+        // eg: container.pagination(5)
+        container.trigger.call(this, eventPrefix + 'go', options, arguments[1]);
+        return this;
+      } else if (typeof options === 'string') {
+        var args = Array.prototype.slice.apply(arguments);
+          args[0] = eventPrefix + args[0];
+
+        switch (options) {
+          case 'previous':
+          case 'next':
+          case 'go':
+          case 'disable':
+          case 'enable':
+          case 'refresh':
+          case 'show':
+          case 'hide':
+          case 'destroy':
+            container.trigger.apply(this, args);
+            break;
+          case 'getSelectedPageNum':
+          case 'getCurrentPageNum':
+            if (container.data('pagination').model) {
+              return container.data('pagination').model.pageNumber;
+            } else {
+              return container.data('pagination').attributes.pageNumber;
+            }
+          case 'getTotalPage':
+            return Math.ceil(container.data('pagination').model.totalNumber / container.data('pagination').model.pageSize);
+          case 'getSelectedPageData':
+          case 'getCurrentPageData':
+            return container.data('pagination').currentPageData;
+          // Whether pagination has been disabled
+          case 'isDisabled':
+            return container.data('pagination').model.disabled === true;
+          default:
+            throwError('Unknown action: ' + options);
+        }
+        return this;
+      } else {
+        // Uninstall the old instance before initializing a new one
+        uninstallPlugin(container);
+      }
+    } else {
+      if (!Helpers.isObject(options)) throwError('Illegal options');
+    }
+
+    // Check parameters
+    parameterChecker(attributes);
+
+    pagination.initialize();
+
+    return this;
+  };
+
+  // Instance defaults
+  $.fn[pluginName].defaults = {
+
+    // Data source
+    // Array | String | Function | Object
+    //dataSource: '',
+
+    // String | Function
+    //locator: 'data',
+
+    // Function
+    //totalNumberLocator: function() {},
+
+    // Total number of data items
+    totalNumber: 0,
+
+    // Default page number
+    pageNumber: 1,
+
+    // Number of data items per page
+    pageSize: 10,
+
+    // Page range (pages around current page)
+    pageRange: 2,
+
+    // Whether to display the 'Previous' button
+    showPrevious: true,
+
+    // Whether to display the 'Next' button
+    showNext: true,
+
+    // Whether to display the page buttons
+    showPageNumbers: true,
+
+    showNavigator: false,
+
+    // Whether to display the 'Go' input
+    showGoInput: false,
+
+    // Whether to display the 'Go' button
+    showGoButton: false,
+
+    showSizeChanger: false,
+
+    sizeChangerOptions: [10, 20, 50, 100],
+
+    // Page link
+    pageLink: '',
+
+    // 'Previous' text
+    prevText: '&lsaquo;',
+
+    // 'Next' text
+    nextText: '&rsaquo;',
+
+    // Ellipsis text
+    ellipsisText: '...',
+
+    // 'Go' button text
+    goButtonText: 'Go',
+
+    // Additional class name(s) for the Pagination container
+    //className: '',
+
+    classPrefix: 'paginationjs',
+
+    activeClassName: 'active',
+
+    // class name when disabled
+    disableClassName: 'disabled',
+
+    //ulClassName: '',
+
+    //pageClassName: '',
+
+    //prevClassName: '',
+
+    //nextClassName: '',
+
+    formatNavigator: 'Total <%= totalNumber %> items',
+
+    formatGoInput: '<%= input %>',
+
+    formatGoButton: '<%= button %>',
+
+    // position in the container
+    position: 'bottom',
+
+    // Auto hide previous button when current page is the first
+    autoHidePrevious: false,
+
+    // Auto hide next button when current page is the last
+    autoHideNext: false,
+
+    //header: '',
+
+    //footer: '',
+
+    //alias: {},
+
+    // Whether to trigger pagination at initialization
+    triggerPagingOnInit: true,
+
+    // Whether to reset page number at initialization, it works only if dataSource is a URL and totalNumberLocator is specified
+    resetPageNumberOnInit: true,
+
+    // Whether to hide pagination when less than one page
+    hideOnlyOnePage: false,
+
+    hideFirstOnEllipsisShow: false,
+
+    hideLastOnEllipsisShow: false,
+
+    // Customize item's innerHTML
+    callback: function() {}
+  };
+
+  // Hook register
+  $.fn[pluginHookMethod] = function(hook, callback) {
+    if (arguments.length < 2) {
+      throwError('Expect 2 arguments at least.');
+    }
+
+    if (typeof callback !== 'function') {
+      throwError('callback should be a function.');
+    }
+
+    var container = $(this);
+    var paginationData = container.data('pagination');
+
+    if (!paginationData) {
+      container.data('pagination', {});
+      paginationData = container.data('pagination');
+    }
+
+    !paginationData.hooks && (paginationData.hooks = {});
+
+    //paginationData.hooks[hook] = callback;
+    paginationData.hooks[hook] = paginationData.hooks[hook] || [];
+    paginationData.hooks[hook].push(callback);
+
+  };
+
+  // Static method
+  $[pluginName] = function(selector, options) {
+    if (arguments.length < 2) {
+      throwError('Requires two parameters.');
+    }
+
+    var container;
+
+    // 'selector' is a jQuery object
+    if (typeof selector !== 'string' && selector instanceof jQuery) {
+      container = selector;
+    } else {
+      container = $(selector);
+    }
+
+    if (!container.length) return;
+
+    container.pagination(options);
+
+    return container;
+  };
+
+  // ============================================================
+  // helpers
+  // ============================================================
+
+  var Helpers = {};
+
+  // Throw error
+  function throwError(content) {
+    throw new Error('Pagination: ' + content);
+  }
+
+  // Check parameters
+  function parameterChecker(args) {
+    if (!args.dataSource) {
+      throwError('"dataSource" is required.');
+    }
+
+    if (typeof args.dataSource === 'string') {
+      if (args.totalNumberLocator === undefined) {
+        if (args.totalNumber === undefined) {
+          throwError('"totalNumber" is required.');
+        } else if (!isNumeric(args.totalNumber)) {
+          throwError('"totalNumber" is incorrect. Expect numberic type');
+        }
+      } else {
+        if (typeof args.totalNumberLocator !== 'function') {
+          throwError('"totalNumberLocator" should be a Function.');
+        }
+      }
+    } else if (Helpers.isObject(args.dataSource)) {
+      if (typeof args.locator === 'undefined') {
+        throwError('"dataSource" is an Object, please specify a "locator".');
+      } else if (typeof args.locator !== 'string' && typeof args.locator !== 'function') {
+        throwError('' + args.locator + ' is incorrect. Expect string or function type');
+      }
+    }
+
+    if (args.formatResult !== undefined && typeof args.formatResult !== 'function') {
+      throwError('"formatResult" should be a Function.');
+    }
+  }
+
+  // uninstall plugin
+  function uninstallPlugin(target) {
+    var events = ['go', 'previous', 'next', 'disable', 'enable', 'refresh', 'show', 'hide', 'destroy'];
+
+    // off all events
+    $.each(events, function(index, value) {
+      target.off(eventPrefix + value);
+    });
+
+    // reset pagination data
+    target.data('pagination', {});
+
+    // remove pagination element
+    $('.paginationjs', target).remove();
+  }
+
+  // Object type detection
+  function getObjectType(object, tmp) {
+    return ( (tmp = typeof(object)) == "object" ? object == null && "null" || Object.prototype.toString.call(object).slice(8, -1) : tmp ).toLowerCase();
+  }
+
+  function isNumeric(n) {
+    return !isNaN(parseFloat(n)) && isFinite(n);
+  }
+
+  $.each(['Object', 'Array', 'String'], function(index, name) {
+    Helpers['is' + name] = function(object) {
+      return getObjectType(object) === name.toLowerCase();
+    };
+  });
+
+  /*
+   * export via AMD or CommonJS
+   * */
+  if (typeof define === 'function' && define.amd) {
+    define(function() {
+      return $;
+    });
+  }
+
+})(this, window.jQuery);
-- 
GitLab