จากที่เราได้ดูในส่วนของไฟล์ app.js ของไฟล์ demo ของ ionic material
และทำความเข้าใจไปบางส่วนนั้นแล้ว ต่อไปเราจะมาดูส่วนของ contrller ของ
แต่ละ state หรือเข้าใจอย่างง่ายก็คือชุดคำสั่งที่ใช้ทำงานในแต่ละหน้าของ app
ที่เราสร้างขึ้น
มาดูไฟล์ controllers.js
/* global angular, document, window */ 'use strict'; angular.module('starter.controllers', []) .controller('AppCtrl', function($scope, $ionicModal, $ionicPopover, $timeout) { // Form data for the login modal $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(); } }; }) .controller('LoginCtrl', function($scope, $timeout, $stateParams, ionicMaterialInk) { $scope.$parent.clearFabs(); $timeout(function() { $scope.$parent.hideHeader(); }, 0); ionicMaterialInk.displayEffect(); }) .controller('FriendsCtrl', function($scope, $stateParams, $timeout, ionicMaterialInk, ionicMaterialMotion) { // Set Header $scope.$parent.showHeader(); $scope.$parent.clearFabs(); $scope.$parent.setHeaderFab('left'); // Delay expansion $timeout(function() { $scope.isExpanded = true; $scope.$parent.setExpanded(true); }, 300); // Set Motion ionicMaterialMotion.fadeSlideInRight(); // Set Ink ionicMaterialInk.displayEffect(); }) .controller('ProfileCtrl', function($scope, $stateParams, $timeout, ionicMaterialMotion, ionicMaterialInk) { // Set Header $scope.$parent.showHeader(); $scope.$parent.clearFabs(); $scope.isExpanded = false; $scope.$parent.setExpanded(false); $scope.$parent.setHeaderFab(false); // Set Motion $timeout(function() { ionicMaterialMotion.slideUp({ selector: '.slide-up' }); }, 300); $timeout(function() { ionicMaterialMotion.fadeSlideInRight({ startVelocity: 3000 }); }, 700); // Set Ink ionicMaterialInk.displayEffect(); }) .controller('ActivityCtrl', function($scope, $stateParams, $timeout, ionicMaterialMotion, ionicMaterialInk) { $scope.$parent.showHeader(); $scope.$parent.clearFabs(); $scope.isExpanded = true; $scope.$parent.setExpanded(true); $scope.$parent.setHeaderFab('right'); $timeout(function() { ionicMaterialMotion.fadeSlideIn({ selector: '.animate-fade-slide-in .item' }); }, 200); // Activate ink for controller ionicMaterialInk.displayEffect(); }) .controller('GalleryCtrl', function($scope, $stateParams, $timeout, ionicMaterialInk, ionicMaterialMotion) { $scope.$parent.showHeader(); $scope.$parent.clearFabs(); $scope.isExpanded = true; $scope.$parent.setExpanded(true); $scope.$parent.setHeaderFab(false); // Activate ink for controller ionicMaterialInk.displayEffect(); ionicMaterialMotion.pushDown({ selector: '.push-down' }); ionicMaterialMotion.fadeSlideInRight({ selector: '.animate-fade-slide-in .item' }); }) ;
อย่างที่เรารู้มาแล้วว่า ไฟล์ app.js นั้นมีการ inject โมดูลที่ชื่อ starter.controllers เข้าไปใช้งาน
ซึ่ง starter.controllers นั้นมาจากการลงทะเบียนโมดูลไฟล์ controllers.js นั่นเอง
จะแยกอธิบายที่ละส่วนให้เขาใจอย่างง่ายดังนี้
'use strict'; // ต้องประกาศตัวแปรก่อนเสมอถ้าจะใช้งาน โดยมี var นำหน้า angular.module('starter.controllers', []) // ลงทะเบียนชื่อโมดูลในการใช้งาน
ต่อไปก็ส่วนของ controller แรก AppCtrl ส่วนนี้จะใช้ในการสร้างฟังก์ชั่นต่าง
ที่จำเป็นต้องเรียกใช้งานใน app รวมถึงการกำหนดค่าตัวแปรต่างๆ ด้วย
ดูโค้ดและคำอธิบายตามด้านล่าง
// ส่วนของ controller แรก โดยเป็น controller ใน app state เป็น parent state ทำงานก่อนส่วนอื่น .controller('AppCtrl', function($scope, $ionicModal, $ionicPopover, $timeout) { // ถ้าเราต้องการกำหนดค่าตัวแปร สำหรับใช้งาน ก็สามารถกำหนดในส่วนนี้ได้เลย $scope.loginData = {}; // ตัวแปร object สำหรับเก็บข้อมูลการ login $scope.isExpanded = false; // เก็บตัวแปรการขยายออกของส่วน header $scope.hasHeaderFabLeft = false; // เก็บตัวแปรปุ่ม fab ส่วนหัวที่แสดงด้านซ้าย $scope.hasHeaderFabRight = false; // เก็บตัวแปรปุ่ม fab ส่วนหัวที่แสดงด้านขวา // สร้าง navIcons เก็บ element ที่มี css class ชื่อ ion-navicon ทั้งหมด var navIcons = document.getElementsByClassName('ion-navicon'); for (var i = 0; i < navIcons.length; i++) { // แล้ววนลูป จากตัวแรกถึงตัวสุดท้าย navIcons.addEventListener('click', function() { //ผูก event เมื่อ คลิกเข้าไป this.classList.toggle('active'); // ถ้าคลิกให้เพิ่ม css class ชื่อ active เข้าไป }); } //////////////////////////////////////// // สร้างฟังก์ชั่นการจัดากร layout ไว้ใช้งาน //////////////////////////////////////// // ฟังก์ชั่นซ่อน navbar หรือแถบบาร์ส่วนหัว $scope.hideNavBar = function() { document.getElementsByTagName('ion-nav-bar')[0].style.display = 'none'; }; // ฟังก์ชั่นแสดง navbar หรือแถบบาร์ส่วนหัว $scope.showNavBar = function() { document.getElementsByTagName('ion-nav-bar')[0].style.display = 'block'; }; // ฟังก์ชั่นลบ css class เพื่อซ่อนส่วนของ header ของเนื้อหา $scope.noHeader = function() { // สร้าง content เก็บ element ที่มี css class ชื่อ ion-content ทั้งหมด var content = document.getElementsByTagName('ion-content'); for (var i = 0; i < content.length; i++) { // แล้ววนลูป จากตัวแรกถึงตัวสุดท้าย if (content[i].classList.contains('has-header')) { // ถ้าตัวนั้น มี css class ชื่อ has-header content[i].classList.toggle('has-header'); // .ให้เอาออก } } }; // สร้างฟังก์ชั่นการย่อ ขยายสว่นเนื้อหา ส่งค่าเข้าไป เป็น true หรือ false $scope.setExpanded = function(bool) { $scope.isExpanded = bool; }; // สร้างฟังก์ชั่น กำหนดของปุ่ม fab โดยให้สามารถเลือกส่งค่าตำแหน่ง ซ้าย ขวา เข้าไปได้ $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; }; // ฟังก์ชั่นเพิ่ม css class เพื่อแสดงส่วนของ header ของเนื้อหา $scope.hasHeader = function() { // สร้าง content เก็บ element ที่มี css class ชื่อ ion-content ทั้งหมด var content = document.getElementsByTagName('ion-content'); for (var i = 0; i < content.length; i++) {// แล้ววนลูป จากตัวแรกถึงตัวสุดท้าย if (!content[i].classList.contains('has-header')) {// ถ้าตัวนั้น ไม่มี css class ชื่อ has-header content[i].classList.toggle('has-header'); // ให้เพิ่มเข้าไป } } }; // สร้างฟังก์ชั่นทำงาน การซ่อนส่วนของ header $scope.hideHeader = function() { $scope.hideNavBar(); // เรียกใช้ฟังก์ชั่นซ่อน แถบบาร์ด้านบนก่อน $scope.noHeader(); // เรียกใช้ฟังก์ชั่นลบส่วนของ header ออกโดยการลบ css class ชื่อ has-header }; // สร้างฟังก์ชั่นทำงาน การแสดงส่วนของ header $scope.showHeader = function() { $scope.showNavBar(); // เรียกใช้ฟังก์ชั่นแสดง แถบบาร์ด้านบนก่อน $scope.hasHeader();// เรียกใช้ฟังก์ชั่นแสดงส่วนของ header โดยการเพิ่ม css class ชื่อ has-header }; // สร้างฟังก์ชั่นเครียร์ปุ่ม fab $scope.clearFabs = function() { // สร้าง fabs เก็บ element ที่มี css class ชื่อ button-fab ทั้งหมด var fabs = document.getElementsByClassName('button-fab'); if (fabs.length && fabs.length > 1) { // ถ้ามีจำนวนมากก่่า 1 fabs[0].remove(); // ให้ลบตัวแรกที่มีอยู่ก่อนหน้า ออก } }; })
ต่อไปเรามาดูการเรียกใช้ ฟังก์ชั่นต่างๆ ที่เราได้สร้างในหน้าอื่นๆ
อย่างที่เราเข้าใจแล้วว่า demo app นั้นเมื่อเข้ามาครั้งแรก หากไม่มีการเลือก
state ตัว app จะพาไปที่หน้า /app/login ด้วยคำส่ัง ด้านล่างในไฟล์ app.js
$urlRouterProvider.otherwise('/app/login');
ดังนั้นลำดับการทำงานของ controller ก็จะเป็น controller ใน app state และ
login state ตามลำดับ สำหรับ controller ใน app state ที่ชื่อ AppCtrl ที่เราได้กล่าว
ไปแล้วในโค้ดด้านบน ต่อไปเรามาดูการทำงานของ controller ใน login state ที่ชื่อ
LoginCtrl คำอธิบายในโค้ดด้านล่าง
// การทำงาน controller ใน login state ที่ชื่อ LoginCtrl .controller('LoginCtrl', function($scope, $timeout, $stateParams, ionicMaterialInk) { // เรียกใช้งานฟังก์ชั่น clearFabs() เพื่อไม่แสดง ปุ่ม fab // สังเกตว่าเรียกผ่าน $scope.$parent เพราะเป็นฟังก์ชั่นจาก controller ที่เป็น parent อีกที $scope.$parent.clearFabs(); // หน่างเวลา 0 มิลลิวินาที แล้วซ่อน ส่วนของ header ของเนื้อหา $timeout(function() { // เรียกใช้งานฟักง์ชั่น hideHeader() จาก parent $scope.$parent.hideHeader(); }, 0); // การหน่วงเวลา ที่เป็น 0 มิลลิวินาที เหมือนจะไม่มีผลอะไร แต่ความจริงเป็นการคั่นให้สคริปทงานได้ถูกต้อง // สังเกตว่ามีการ inject ionicMaterialInk เข้ามาเพื่อใช้งาน ionicMaterialInk.displayEffect(); // สร้าง effect สำหรับ link })
ดูตัวอย่างประกอบ หน้า login
เราจะเห็นว่าหน้า login นั้นจะไม่มีในส่วนของ header ทั้ง header bar และปุ่ม fab
เพราะจากคำสั่งใน controller ในหน้า login ได้เรียกใช้งานคำสั่งการซ่อนและไม่แสดง
ส่วนต่างๆ ที่กล่าวมาแล้วข้างต้น ต่อไปเมื่อเราคลิกที่ปุ่ม login ก็จะไปในหน้า
profile state และทำงานในส่วนของ controller ที่ชื่อ ProfileCtrl
มาดูคำส่่งและการทำงานในหน้า profile
// การทำงาน controller ใน profile state ที่ชื่อ ProfileCtrl .controller('ProfileCtrl', function($scope, $stateParams, $timeout, ionicMaterialMotion, ionicMaterialInk) { // การกำหนดต่าต่างๆ ของส่วน header $scope.$parent.showHeader(); // .แสดง header $scope.$parent.clearFabs(); // เคลียร์ปุ่ม fab $scope.isExpanded = false; // กำหนดค่าเริ่มต้นให้ ไม่ขยายส่วนของ header $scope.$parent.setExpanded(false);// เรียกใช้ฟังก์ชั่นเพื่อกำหนดค่าให้ การขยายส่วน header $scope.$parent.setHeaderFab(false); // เรียกใช้ฟังก์ชั่นเพื่อกำหนดให้ไม่แสดงปุ่ม fab // delay การใช้งาน effect ส่วนที่ 1 $timeout(function() { ionicMaterialMotion.slideUp({ selector: '.slide-up' // ให้ element ที่มี selector ชื่อ slide-up ใช้งาน effect slideUp }); // selector ในที่นี้คือชื่อ css class ที่อยู่ในไฟล์ profile.html }, 300); // delay การใช้งาน effect ส่วนที่ 2 $timeout(function() { ionicMaterialMotion.fadeSlideInRight({ startVelocity: 3000 // ความเร้วในการแสดง }); // จะเห็นว่าส่วนนี้ไม่ได้กำหนดในนี้ว่าเป็น effect ของอะไร แต่จะไปกำหนดใน css class แทน }, 700); // สังเกตว่ามีการ inject ionicMaterialInk เข้ามาเพื่อใช้งาน ionicMaterialInk.displayEffect(); // สร้าง effect สำหรับ link })
ดูตัวอย่างประกอบ หน้า profile
จากรูปเราจะเห็นว่า มีการแสดง header bar แต่ไม่ขยายเป็นสองบรรทัด
เพราะไม่ได้มีการแสดงปุ่ม fab ส่วนบน
effect ส่วนที่ 1 ใช้กับ รูปโปรไฟล์ และชื่อ กำหนดในโค้ดตามด้านบน
effect ส่วนที่ 2 ใช้กับรายการ following สามรายการด้านล่าง กำหนดแค่อัดตราเร็ว
แต่รูปแบบ effect กำหนดในไฟล์ profile.html ดูโค้ดบางส่วนของไฟล์ profile.html
ที่กำหนด effect
<div class="list animate-fade-slide-in-right"> <a ui-sref="app.friends" class="item item-avatar item-icon-right"> <img src="img/jon-snow.jpg"> <h2>Jon Snow</h2> <p>Da illest illegitimate</p> <i class="icon ion-chatbubble muted"></i> </a> <a ui-sref="app.friends" class="item item-avatar item-icon-right"> <img src="img/sansa.jpg"> <h2>Sansa Stark</h2> <p>& Joffrey <strike>sitting</strike> sat in a tree</p> <i class="icon ion-chatbubble muted"></i> </a> <a ui-sref="app.friends" class="item item-avatar item-icon-right"> <img src="img/tyrion.jpg"> <h2>Tyrion Lannister</h2> <p>B.A.M.F. imp</p> <i class="icon ion-chatbubble muted"></i> </a> </div>
จากโค้ดจะเห็นว่ามีการใช้งาน css class ที่ชื่อ animate-fade-slide-in-right
คำให้เคลือนไหวในลักษณะค่อยแสดงชัดขึ้นมาจากทางซ้าย โดยกำหนดอัตราเร็วไว้ใน
controller เป็นต้น
ต่อไปเรามาดูอีกหน้า เพื่อให้เห็นว่า เมื่อมีการแสดง header และขยาย header bar
รวมทั่้งปุ่ม fab ด้วยจะเป็นไปลักษณะอย่างไร เมื่อเรากดเข้ามาที่หน้า friend state
ก็จะทำงานในส่วนของ controller ที่ชื่อ FriendsCtrl ดังนี้
// การทำงาน controller ใน friend state ที่ชื่อ FriendsCtrl .controller('FriendsCtrl', function($scope, $stateParams, $timeout, ionicMaterialInk, ionicMaterialMotion) { // การกำหนดต่าต่างๆ ของส่วน header $scope.$parent.showHeader(); // .แสดง header $scope.$parent.clearFabs(); // เคลียร์ปุ่ม fab $scope.$parent.setHeaderFab('left'); // เรียกใช้ฟังก์ชั่นว่าให้ปุ่ม fab อยู่ด้านซ้าย // delay การขยาย header เพื่อรองรับปุ่ม fab $timeout(function() { $scope.isExpanded = true; // กำหนดค่าเริ่มต้นให้ ให้ขยายส่วนของเนื้อหาใน ion-content ให้รองรับ header $scope.$parent.setExpanded(true); // เรียกใช้ฟังก์ชั่นเพื่อกำหนดค่าให้ มีการขยายส่วน header ของ ion-nav-bar }, 300); // กำหนด effect การแสดงส่วนที่กำหนด ionicMaterialMotion.fadeSlideInRight(); // สังเกตว่ามีการ inject ionicMaterialInk เข้ามาเพื่อใช้งาน ionicMaterialInk.displayEffect(); // สร้าง effect สำหรับ link })
ดูตัวอย่างประกอบ หน้า friend
จากรูป เราจะเห็นว่าหน้านี้มีการแสดงส่วนของ header สองบรรทัด รองรับปุ่ม fab
จึงมีการขยายส่วนของเนื้อหาให้รองรับการแสดงปุ่มดังกล่าวตามคำสั่งการทำงาน
ข้างด้น
และสำหรับ cotroller ส่วนอื่นๆ ก็มีรูปแบบการทำงานที่ไม่ต่างกัน สามารถดูแล้วเข้าใจได้
ไม่ยากนัก จากตัวอย่าง demo เราจะเห็นว่า เป็นเพียงตัวอย่างการนำเสนอการแสดงผล
เป็นส่วนใหญ่ เพื่อให้เห็นรูปแบบและ หน้าตาของ app สำหรับคำสั่งและการทำงานในเชิงลึก
ขึ้นกับเราจะประยุกต์ให้เป็นไปในทิศทางไหน ionic material จึงเปรียบเสมือน theme
ที่ทำให้เราจัดรูปแบบ app ได้ง่ายขึ้นนั้นเอง
ตัวอย่าง demo app
ขอจบส่วนของการอธิบายการทำงานของ demo app ไว้เท่านี้ เนื้อหาต่อๆ ไป
เราจะมาประยุกต์ใช้งานกัน รอติดตาม