สำหรับเนื้อหานี้จะเป็นตอนที่สอง ต่อจากตอนที่แล้ว ที่เราวางโครงสร้างรูปแบบ
คร่าวๆ ของ app ว่าจะเป็นในลักษณะไหน
แนวทางใช้งาน ionic material สร้างระบบสมาชิก ร่วมกับ SQLite
https://www.ninenik.com/content.php?arti_id=734 via @ninenik
โดยในตอนที่สอง จะเป็นการประยุกต์ให้สามารถใช้งานได้จริง ตั้งแต่การสมัคร
สมาชิก แล้วบันทึกลงฐานข้อมูล จากนั้นลิ้งค์ไปหน้าโพรไฟล์ ดึงข้อมูลจากฐาน
ข้อมูลมาแสดงในหน้าโพรไฟล์ และเมื่อล็อกเอาท์ออกจากระบบ ก็จะแสดงหน้า
ล็อกอิน ให้ผู้ใช้ทำการกรอก username และ password ใหม่อีกครั้งถ้าต้องการ
ล็อกอิน ประมาณนี้เป็นต้น
ก่อนอื่น เราต้องตรวจสอบ cordova plugin ที่จำเป็นเพิ่มเติมก่อน ซึ่งจะได้แก่
SQLite - สำหรับใช้งาน db cordova-sqlite-storage 1.4.8 "Cordova sqlite storage plugin" Toast - สำหรับแสดงสถานะการทำงาน cordova-plugin-x-toast 2.5.2 "Toast" Dialogs - สำหรับขึ้นข้อความแจ้งเตือน หรือแจ้งการทำงานcordova-plugin-dialogs cordova-plugin-dialogs 1.2.1 "Notification" SpinnerDialog - สำหรับขึ้นแสดง loading พร้อมข้อความ (เคยติดตั้งแล้วในบทความ map) cordova-plugin-spinner-dialog 1.3.1 "SpinnerDialog"
1. ตรวจสอบ plugin ด้วยคำสั่ง
C:\phonegap\learn001>phonegap plugin ls
หากยังไม่ได้ติดตั้ง plugin ข้างต้น ให้ทำการติดตั้งด้วยคำสั่ง ดังนี้
ติดตั้ง plugin SQLite ด้วยคำสั่ง
C:\phonegap\learn001>cordova plugin add cordova-sqlite-storage
ติดตั้ง plugin Toast ด้วยคำสั่ง
C:\phonegap\learn001>cordova plugin add cordova-plugin-x-toast
ติดตั้ง plugin Dialogs ด้วยคำสั่ง
C:\phonegap\learn001>cordova plugin add cordova-plugin-dialogs
ติดตั้ง plugin SpinnerDialog ด้วยคำสั่ง
C:\phonegap\learn001>cordova plugin add cordova-plugin-spinner-dialog
2. กำหนดในส่วนของ controller ของหน้าล็อกอิน หน้าสมัครสมาชิก และหน้าโพรไฟล์
ในส่วนของ controller ให้เปิดไฟล์ controllers.js ในโฟลเดอร์ js ขึ้นมา
แล้วปรับโค้ดให้เป็นดังนี้ (คำอธิบายแสดงในโค้ด หากมีอธิบายเพิ่มเติมจะแสดงด้านล่างโค้ด)
AppCtrl ส่วนของ controller หลัก
.controller('AppCtrl', function($scope, $ionicModal, $ionicPopover, $timeout, $ionicPopup) { $scope.loginData = {}; $scope.isExpanded = false; $scope.hasHeaderFabLeft = false; $scope.hasHeaderFabRight = false; var navIcons = document.getElementsByClassName('ion-navicon'); for (var i = 0; i < navIcons.length; i++) { navIcons.addEventListener('click', function() { this.classList.toggle('active'); }); } //////////////////////////////////////// // Layout Methods //////////////////////////////////////// $scope.hideNavBar = function() { document.getElementsByTagName('ion-nav-bar')[0].style.display = 'none'; }; $scope.showNavBar = function() { document.getElementsByTagName('ion-nav-bar')[0].style.display = 'block'; }; $scope.noHeader = function() { var content = document.getElementsByTagName('ion-content'); for (var i = 0; i < content.length; i++) { if (content[i].classList.contains('has-header')) { content[i].classList.toggle('has-header'); } } }; $scope.setExpanded = function(bool) { $scope.isExpanded = bool; }; $scope.setHeaderFab = function(location) { var hasHeaderFabLeft = false; var hasHeaderFabRight = false; switch (location) { case 'left': hasHeaderFabLeft = true; break; case 'right': hasHeaderFabRight = true; break; } $scope.hasHeaderFabLeft = hasHeaderFabLeft; $scope.hasHeaderFabRight = hasHeaderFabRight; }; $scope.hasHeader = function() { var content = document.getElementsByTagName('ion-content'); for (var i = 0; i < content.length; i++) { if (!content[i].classList.contains('has-header')) { content[i].classList.toggle('has-header'); } } }; $scope.hideHeader = function() { $scope.hideNavBar(); $scope.noHeader(); }; $scope.showHeader = function() { $scope.showNavBar(); $scope.hasHeader(); }; $scope.clearFabs = function() { var fabs = document.getElementsByClassName('button-fab'); if (fabs.length && fabs.length > 1) { fabs[0].remove(); } }; $scope.noShadow = function() { var headerBar = document.getElementsByTagName('ion-header-bar'); for (var i = 0; i < headerBar.length; i++) { if (!headerBar[i].classList.contains('no-shadow')) { headerBar[i].classList.add('no-shadow'); } } }; $scope.hasShadow = function() { var headerBar = document.getElementsByTagName('ion-header-bar'); for (var i = 0; i < headerBar.length; i++) { if (headerBar[i].classList.contains('no-shadow')) { headerBar[i].classList.remove('no-shadow'); } } }; // ตัวแปรสำหรับกำหนด การซ่อนหรือแสดงเมนูสมาชิก false คือซ่อนเมนูสมาชิก $scope.showMemberMenu = false; // สร้างฟังก์ชั่น สำหรับเรียกใช้ เพื่อกำหนดการ กำหนดค่าตัวแปรเพื่อซ่อนหรือแสดงเมนู $scope.setMemberMenu = function(status){ $scope.showMemberMenu = status; }; // กำหนดตัวแปรไว้สำหรับเก็บ id ของสมาชิกที่สมัครในเครื่องนั้นๆ $scope.sesMemberID = null; // สร้างฟังก์ชั่นสำหรับกำหนดค่า id ให้สามารถเรียกใช้งานจาก $scope หลักได้ $scope.setMemberID = function(memID){ $scope.sesMemberID = memID; }; // สร้างฟังก์ชั่นสำหรับเรียกดูค่า id ของสมาชิกจาก $scope หลักได้ $scope.getMemberID = function(){ return $scope.sesMemberID; }; })
สังเกตว่าเรามีการเพิ่ม ตัวแปร sesMemberID เข้ามาเพื่อใช้เก็บค่า member_id ของสมาชิก
รวมทั้งมีการสร้างฟังก์ชั่นกำหนดค่า และฟังก์ชั่นเรียกใช้งานค่า member_id เพื่อไว้สำหรับเรียกใช้
จาก controller ย่อยอื่นๆ ได้
LoginCtrl ส่วนของ controller หน้า login
การทำงานของหน้านี้คือ เริ่มกำหนดค่าว่างในตัวแปรที่ใช้ในฟอร์มล็อกอิน ตรวจสอบข้อมูลที่กรอก
ในฟอร์มล็อกอินว่ากรอกข้อมูลครบหรือไม่ จากนั้นส่งค่าข้อมูลไปตรวจสอบกับ db ว่ามีข้อมูล
ของสมาชิกนั้นหรือไม่ หากมีข้อมูลก็ให้เข้าสู่ระบบ และไปยังหน้าโพรไฟล์
.controller('LoginCtrl', function($scope, $timeout, $stateParams, ionicMaterialInk ,$ionicPlatform, $cordovaDialogs, $cordovaToast, $state) { $scope.$parent.clearFabs(); $scope.$parent.hasShadow(); $timeout(function() { $scope.$parent.hideHeader(); }, 0); // กำนหดค่าเริ่มต้นของฟอร์มหน้าสมัครสมาชิก ให้เป็นค่าว่าง $scope.login = { input_user:'', input_pass:'' }; // เรียกใช้ฟังก์ชั่นจาก AppCtrl หลัก เพื่อซ่อนเมนูสมาชิก โดยส่งค่า false เข้าไป $scope.$parent.setMemberMenu(false); // สร้างฟังก์ชั่นสำหรับเรียกใช้ Toast plugin $scope.showToast = function(str, duration, position){ $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin return $cordovaToast .show(str, duration, position) .then(function(success) { // success }, function (error) { // error }); }); }; var db = null; $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin // ตรวจสอบและทำการเชื่อมต่อกับ db db = window.sqlitePlugin.openDatabase({ name: 'my.db', location: 'default' }, function (db) { $scope.showToast('Open DB Success','long','bottom'); }, function (error) { $scope.showToast('Open database ERROR: ' + JSON.stringify(error),'long','bottom'); }); }); // สร้างฟังก์ชั่นสำหรับล็อกอิน รับค่า data จาก object login $scope.loginMember = function(data){ $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin // ตรวจสอบถ้ากรอกข้อมูลไม่ครบ if(data.input_user=="" || data.input_pass==""){ $cordovaDialogs.alert('โปรดกรอกข้อมูลให้ครบถ้วน', 'ข้อมูลจำเป็น', 'ตกลง') .then(function() { return false; }); return false; } // แสดงข้อมูลที่ส่งเข้ามา เวลาใช้จริงสามารถลบออกได้ $scope.showToast(' user: ' + data.input_user + ' pass: ' + data.input_pass +'', 'long', 'bottom'); // เริ่มทำงานของคำสั่ง db db.transaction(function (tx) { // กำหนดคำสั่ง sql ค่าที่รับมาตรวจสอบ แทนด้วย ? // คำสั่งนี้คือเช็คว่า มีสมาชิกที่ user และ pass ตรงหรือไม่ ตัวเล็กตัวใหญ่มีค่าต่างกัน var query = "SELECT member_id, member_user, member_phone" + " FROM member WHERE member_user = ? AND member_pass = ? "; // ทำคำสั่ง sql ส่งค่าเข้าไปให้ตรงกับจำนวน ในที่นี้มี data.input_user และ data.input_pass tx.executeSql(query, [data.input_user, data.input_pass], function (tx, resultSet) { // ถ้าพบรายการใน db if(resultSet.rows.length){ // นำค่า ID ของสมาชิกไปเก็บในตัวแปร sesMemberID ด้วยฟังก์ชั่น setMemberID // ที่อยู่ใน controller หลัก $scope.$parent.setMemberID(resultSet.rows.item(0).member_id); // ขึ้นแจ้ง เข้าสู่ระบบสำเร็จ และเปลี่ยนไปหน้า profile ด้วย $stat.go() $cordovaDialogs.alert('เข้าสู่ระบบสำเร็จ', 'เข้าสู่ระบบ', 'ตกลง') .then(function() { $state.go('app.profile'); }); }else{ // ไม่พบข้อมูล ขึ้นแจ้งเตือนว่ามีข้อผิดพลาด $cordovaDialogs.alert('ชื่อหรือรหัสผ่านไม่ถูกต้อง', 'เกิดข้อผิดพลาด', 'ตกลง') .then(function() { return false; }); return false; } }, function (tx, error) { // ข้อความแจ้ง SELECT error $scope.showToast('SELECT error: ' + error.message,'long','bottom'); }); }, function(error) { $scope.showToast('transaction error: ' + error.message,'long','bottom'); }, function() { $scope.showToast('transaction ok','long','bottom'); }); }); }; ionicMaterialInk.displayEffect(); })
สังเกตว่าใน LoginCtrl มีการเรียกใช้งาน $ionicPlatform, $cordovaDialogs, $cordovaToast, $state เพิ่มเข้ามา โดย
$ionicPlatform สำหรับตรวจสอบความพร้อมของอุปกรณ์ก่อนเรียกใช้ plugin
$cordovaDialogs สำหรับ alert แจ้งเตือนข้อความ
$cordovaToast สำหรับข้อความแจ้งสถานะ
$state สำหรับเชื่อมโยงหรือลิ้งค์ไปหน้าต่างๆ
RegistCtrl ส่วนของ controller หน้า register
การทำงานหน้านี้คือ เริ่มกำหนดตัวแปรค่าว่างให้กับข้อมูลในฟอร์มสมัครสมาชิก
ทำการตรวจสอบการกรอกข้อมูลในฟอร์มก่อนส่งข้อมูล ทำการส่งข้อมูลไปบันทึกใน db
จากนั้นลิ้งค์ไปยังหน้าโพรไฟล์
.controller('RegistCtrl', function($scope, $timeout, $stateParams, ionicMaterialInk ,$ionicPlatform, $cordovaDialogs, $cordovaToast, $state) { $scope.$parent.clearFabs(); $scope.$parent.hasShadow(); $timeout(function() { $scope.$parent.hideHeader(); }, 0); // กำนหดค่าเริ่มต้นของฟอร์มหน้าสมัครสมาชิก ให้เป็นค่าว่าง $scope.reg = { input_user:'', input_pass:'', input_phone:'' }; // เรียกใช้ฟังก์ชั่นจาก AppCtrl หลัก เพื่อซ่อนเมนูสมาชิก โดยส่งค่า false เข้าไป $scope.$parent.setMemberMenu(false); // สร้างฟังก์ชั่นสำหรับเรียกใช้ Toast plugin $scope.showToast = function(str, duration, position){ $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin return $cordovaToast .show(str, duration, position) .then(function(success) { // success }, function (error) { // error }); }); }; var db = null; $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin // ตรวจสอบและทำการเชื่อมต่อกับ db db = window.sqlitePlugin.openDatabase({ name: 'my.db', location: 'default' }, function (db) { // เชื่อมต่อ db สำเร็จ $scope.showToast('Open DB Success','long','bottom'); // เริ่มทำงานของคำสั่ง db db.transaction(function (tx) { // เวลามีการทดสอบและเพิ่มฟิลด์หรือแก้ไขตารางควรเปิดคอมเม้นการลบตารางก่อน // นั้นหมายถึงตารางจะถูกสร้างและจัดรูปแบบใหม่ทุกครั้ง ข้อมูลตารางจะรีเซ็ต ล้างค่า // พอว่างโครงสร้างตารางเรียบร้อยแล้ว ให้ปิดคอมเม้นไว้เหมือนเดิม เพื่อให้ข้อมูลยังคงอยู่ // tx.executeSql('DROP TABLE IF EXISTS member'); // ทำคำสั่งสร้างตาราง member ถ้ายังไม่มี tx.executeSql('' + 'CREATE TABLE IF NOT EXISTS member ' + '(member_id integer primary key,' + 'member_user text, ' + 'member_pass text,' + 'member_phone text)' + ''); }, function (error) { $scope.showToast('transaction error: ' + error.message,'long','bottom'); }, function () { $scope.showToast('transaction ok','long','bottom'); }); }, function (error) { $scope.showToast('Open database ERROR: ' + JSON.stringify(error),'long','bottom'); }); }); // สร้างฟังก์ชั่นสำหรับสมัครสมาชิก รับค่า data จาก object reg $scope.registerMember = function(data){ $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin // ตรวจสอบให้กรอกข้อมูลให้ครบถ้วน if(data.input_user=="" || data.input_pass=="" || data.input_phone==""){ $cordovaDialogs.alert('โปรดกรอกข้อมูลให้ครบถ้วน', 'ข้อมูลจำเป็น', 'ตกลง') .then(function() { return false; }); return false; } // แสดงข้อมูลที่ส่งเข้ามา เวลาใช้จริงสามารถลบออกได้ $scope.showToast(' user: ' + data.input_user + ' pass: ' + data.input_pass + ' phone: ' + data.input_phone +'', 'long', 'bottom'); // เริ่มทำงานของคำสั่ง db db.transaction(function (tx) { // จัดรูปแบบคำสั่ง sql สำหรับบันทึกข้อมูล ค่าที่รับมาบันทึกจะแทนด้วย ? // ในที่นี้จะส่งมาแค่ 3 ค่า ส่วน member_id นั้นเป็น PRIMARY KEY เป็น auto incremet // โดยอัตโนมัติ var query = "INSERT INTO member" + " (member_user, member_pass, member_phone) " + " VALUES (?,?,?)"; // ทำงานคำสั่ง sql tx.executeSql(query, [data.input_user, data.input_pass, data.input_phone], function(tx, res) { // เมื่อทำการบันทึกข้อมูลสำเร็จ // ใช้ค่า member_id จาก res.insertId แล้วกำหนดค่าด้วยฟังก์ชั่น setMemberID() $scope.$parent.setMemberID(res.insertId); $scope.showToast('insertId: ' + res.insertId + ' -- probably 1','long','bottom'); $scope.showToast('rowsAffected: ' + res.rowsAffected + ' -- should be 1','long','bottom'); // แจ้งการสมัครสมาชิกสำเร็จ และให้ไปที่หน้า profile $cordovaDialogs.alert('ทำการสัมครสมัครสมาชิกเรียบร้อยแล้ว', 'สมัครสมาชิกใหม่', 'ตกลง') .then(function() { $state.go('app.profile'); }); }, function(tx, error) { $scope.showToast('INSERT error: ' + error.message,'long','bottom'); }); }, function(error) { $scope.showToast('transaction error: ' + error.message,'long','bottom'); }, function() { $scope.showToast('transaction ok','long','bottom'); }); }); }; ionicMaterialInk.displayEffect(); })
ProfileCtrl ส่วนของ controller หน้า profile
การทำงานของหน้านี้ทำการเชื่อมกับ db แล้วไปดึงข้อมูลของสมาชิกที่ทำการล็อกอิน
สำหรับ หรือสมาชิกที่สมัครสมาชิกมาแสดง โดยอิงการดึงค่าจาก member_id ที่มีการเก็บ
ค่าไว้ใน $scope ที่อยู่ใน controller หลัก เมื่อพบข้อมูลก็นำมากำหนดในตัวแปร แล้วเรียกใช้
งานในหน้าโพรไฟล์อีกที
.controller('ProfileCtrl', function( $scope, $stateParams, $timeout, ionicMaterialMotion, ionicMaterialInk, $ionicPlatform, $cordovaSpinnerDialog, $cordovaToast) { $scope.$parent.showHeader(); $scope.$parent.clearFabs(); $scope.isExpanded = false; $scope.$parent.setExpanded(false); $scope.$parent.setHeaderFab(false); $scope.$parent.hasShadow(); // กำหนดตัวแปรสำหรับค่าเริ่มต้นของข้อมูลหน้าโพรไลฟ์ $scope.member_user = ''; $scope.member_phone = ''; // กำหนดให้แสดงเมนูสมาชิก $scope.$parent.setMemberMenu(true); // สร้างฟังก์ชั่นสำหรับเรียกใช้ Toast plugin $scope.showToast = function(str, duration, position){ $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin return $cordovaToast .show(str, duration, position) .then(function(success) { // success }, function (error) { // error }); }); }; var db = null; $ionicPlatform.ready(function() { // เตรียมก่อนเรียกใช้ plugin // ตรวจสอบและทำการเชื่อมต่อกับ db db = window.sqlitePlugin.openDatabase({ name: 'my.db', location: 'default' }, function (db) { // เชื่อมต่อ db สำเร็จ $scope.showToast('Open DB Success','long','bottom'); // แสดง spin dialog plugin จะใช้หรือไม่ก็ได้ นำมาใช้เผื่อใครไปประยุกต์เพิ่ม $cordovaSpinnerDialog.show(null,"รอสักครู่..กำลังโหลดข้อมูล"); // แสดง loading $timeout(function() { // ซ่อนอัตโนมัติใน 300 มิลลิวินาที $cordovaSpinnerDialog.hide(); }, 300); // เริ่มทำงานของคำสั่ง db db.transaction(function (tx) { // เตรียมคำสั่ง sql ดึงข้อมูลสมาชิกอิงจาก member_id ที่เราเก็บไว้ใน $scope หลัก var query = "SELECT member_user, member_phone" + " FROM member WHERE member_id = ?"; // ดึงค่า member_id จาก $scope หลักด้วยฟังก์ชั่น getMemberID() var id_member = $scope.$parent.getMemberID(); // ทำคำสั่ง sql ดึงข้อมูลตาม member_id tx.executeSql(query, [id_member], function (tx, resultSet) { // ถ้าพบข้อมูล if(resultSet.rows.length){ // นำข้อมูลที่ได้ มาไว้ในตัวแปร member_user และ member_phone // เพื่อแสดงในหน้า profile $scope.member_user = resultSet.rows.item(0).member_user; $scope.member_phone = resultSet.rows.item(0).member_phone; } $cordovaSpinnerDialog.hide(); }, function (tx, error) { $scope.showToast('SELECT error: ' + error.message,'long','bottom'); }); }, function (error) { $scope.showToast('transaction error: ' + error.message,'long','bottom'); }, function () { $scope.showToast('transaction ok','long','bottom'); }); }, function (error) { $scope.showToast('Open database ERROR: ' + JSON.stringify(error),'long','bottom'); }); }); $timeout(function() { ionicMaterialMotion.slideUp({ selector: '.slide-up' }); }, 300); $timeout(function() { ionicMaterialMotion.fadeSlideInRight({ startVelocity: 3000 }); }, 700); ionicMaterialInk.displayEffect(); })
3. ทำการ build apk ไฟล์แล้วนำไปทดสอบติดตั้งบนมือถือ
ด้วยคำสั่ง
C:\phonegap\learn001>phonegap build android
ให้ทำการทดสอบติดตั้งในมือถือ android ของเรา จะได้หน้าตา app ประมาณนี้
ในตัวอย่างมีส่วนของแผนที่ด้วย หากต้องการเห็นผลแผนที่ด้วย
อาจจะต้อง รีสตาร์ทเครื่องหนึ่งครั้ง