[Thực hành] Quản lý điện thoại

12. JQuery & Ajax

Mục tiêu

Luyện tập triển khai AJAX trong một ứng dụng web sử dụng jQuery.

Mô tả

Trong phần này, chúng ta sẽ phát triển một ứng dụng quản lý điện thoại, trong đó có các chức năng:

  • Xem danh sách các điện thoại
  • Thêm một điện thoại mới
  • Chỉnh sửa thông tin điện thoại
  • Xoá một điện thoại

Tất các các thao tác của ứng dụng đều được thực hiện dựa trên AJAX.

Hướng dẫn

Bước 1: Tạo project Gradle với tên Phone-management

Bước 2: Thêm dependencies trong file build.gradle

compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
compile group: 'org.springframework', name: 'spring-core', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-beans', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-web', version: '4.3.17.RELEASE'
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
compile group: 'org.springframework', name: 'spring-orm', version: '4.3.17.RELEASE'
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.17.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.1.Final'
compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.3.0.Final'
compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version: '3.0.9.RELEASE'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.12.RELEASE'
//dependencies for Ajax
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5

Bước 3: Cấu hình cho project

C:\Users\haidt\AppData\Local\Temp\enhtmlclip\Image(263).png

Lớp AppInitializer là lớp cấu hình khởi tạo cho ứng dụng

 
package com.codegym;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{AppConfiguration.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

@Override
protected Filter[] getServletFilters() {

CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[] { characterEncodingFilter};
}
}

Lớp ApplicationConfig được dùng để cấu hình cho toàn bộ ứng dụng

package com.codegym;

import com.codegym.service.SmartphoneService;
import com.codegym.service.SmartphoneServiceImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@EnableSpringDataWebSupport
@ComponentScan("com.codegym")
@EnableJpaRepositories("com.codegym.repository")
public class AppConfiguration extends WebMvcConfigurerAdapter implements ApplicationContextAware {

private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

//Thymeleaf Configuration
@Bean
public SpringResourceTemplateResolver templateResolver(){
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}

@Bean
public TemplateEngine templateEngine(){
TemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}

@Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
//JPA configuration

@Bean
@Qualifier(value = "entityManager")
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[]{"com.codegym.model"});

JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}

@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/phones");
dataSource.setUsername("root");
dataSource.setPassword("kienkun1990");
return dataSource;
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}

Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}

@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages");
return messageSource;
}

@Bean
public SmartphoneService smartphoneService(){
return new SmartphoneServiceImpl();
}
}

Lưu ý: Cấu hình để sử dụng CSDL

– Tạo CSDL tên là phones

– Cấu hình tên truy cập (dataSource.setUsername(“ten_truy_cap”) và dataSource.setPassword(“mat_khau_truy_cap”)) và mật khẩu khi kết nối CSDL

 Bước 4: Tạo class Smartphone trong model

package com.codegym.model;


import javax.persistence.*;

@Entity
@Table(name="smartphones")
public class Smartphone {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String producer;
private String model;
private double price;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getProducer() {
return producer;
}

public void setProducer(String producer) {
this.producer = producer;
}

public String getModel() {
return model;
}

public void setModel(String model) {
this.model = model;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return producer+": "+model+" with price "+price;
}

}
Bước 5: Tạo SmartphoneRepository
package com.codegym.repository;

import com.codegym.model.Smartphone;
import org.springframework.data.repository.CrudRepository;

public interface SmartphoneRepository extends CrudRepository<Smartphone, Integer> {
}

Bước 6: Tạo SmartphoneService

package com.codegym.service;

import com.codegym.model.Smartphone;

public interface SmartphoneService {
Iterable<Smartphone> findAll();
Smartphone findById(Integer id);
Smartphone save(Smartphone phone);
Smartphone remove(Integer id);
}

và tạo SmartphoneServiceImpl

package com.codegym.service;

import com.codegym.model.Smartphone;
import com.codegym.repository.SmartphoneRepository;
import org.springframework.beans.factory.annotation.Autowired;

public class SmartphoneServiceImpl implements SmartphoneService {

@Autowired
private SmartphoneRepository smartphoneRepository;

@Override
public Iterable<Smartphone> findAll() {
return smartphoneRepository.findAll();
}

@Override
public Smartphone findById(Integer id) {
Smartphone smartphone = smartphoneRepository.findOne(id);
if(smartphone == null){
return null;
}
return smartphone;
}

@Override
public Smartphone save(Smartphone phone) {
return smartphoneRepository.save(phone);
}

@Override
public Smartphone remove(Integer id) {
Smartphone smartphone = findById(id);
smartphoneRepository.delete(id);
return smartphone;
}
}

Bước 7: Tạo controller HomeController

package com.codegym.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

@GetMapping("/")
public String showIndex(){
return "index";
}
}

và file index.html trong thư mục view

Bước 8: Cấu hình Tomcat và chạy ứng dụng

C:\Users\haidt\AppData\Local\Temp\enhtmlclip\Image(264).png

Bước 9: Tạo SmartphoneController để hiển thị màn hình tạo mới

package com.codegym.controller;

import com.codegym.model.Smartphone;
import com.codegym.service.SmartphoneService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping(value = "/smartphones")
public class SmartphoneController {

@Autowired
private SmartphoneService smartphoneService;

@RequestMapping(value = "/create", method = RequestMethod.GET)
public ModelAndView createSmartphonePage() {
ModelAndView mav = new ModelAndView("phones/new-phone");
mav.addObject("sPhone", new Smartphone());
return mav;
}

@RequestMapping(value = "/createnewPhone", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Smartphone createSmartphone(@RequestBody Smartphone smartphone) {
return smartphoneService.save(smartphone);
}

}

Tạo thư mục phones trong WEB-INF\views. Tạo file new-phone.html trong thư mục phones như sau:

 
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Create new Smartphone</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#newSmartphoneForm').submit(function(event) {
var producer = $('#producer').val();
var model = $('#model').val();
var price = $('#price').val();
var json = { "producer" : producer, "model" : model, "price": price};
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type : "POST",
data: JSON.stringify(json),
url: "/smartphones/createnewPhone",
success: function(smartphone) {
var respContent = "";
respContent += "<span class='success'>Smartphone was created: [";
respContent += smartphone.producer + " : ";
respContent += smartphone.model + " : " ;
respContent += smartphone.price + "]</span>"
$("#sPhoneFromResponse").html(respContent);
}
});
event.preventDefault();
});
});
</script>
</head>
<body>
<body>
<div id="container">
<h1>Create new Smartphone</h1>
<div>
<p>Here you can create new Smartphone:</p>
<div id="sPhoneFromResponse"></div>
</div>
<form id="newSmartphoneForm" th:object="${sPhone}">
<table>
<tbody>
<tr>
<td>Producer:</td>
<td>
<select id="producer">
<option value="NOKIA">Nokia</option>
<option selected="selected" value="IPHONE">iPhone</option>
<option value="HTC">HTC</option>
<option value="SAMSUNG">Samsung</option>
</select>
</td>
</tr>
<tr>
<td>Model:</td>
<td><input th:field="*{model}" /></td>
</tr>
<tr>
<td>Price:</td>
<td><input th:field="*{price}" /></td>
</tr>
<tr>
<td><input type="submit" value="Create" /></td>
<td></td>
</tr>
</tbody>
</table>
</form>
<a th:href="@{/smartphones}">List</a>
</div>
</body>
</html>

Trong file new-phone.html:

$(‘#newSmartphoneForm’).submit(function(event) là funtion được gọi khi submit from có id là newSmartphoneForm

var producer = $(‘#producer’).val();

var model = $(‘#model’).val();

var price = $(‘#price’).val(); thực hiện lấy dữ liệu từ các input tương ứng

var json = { “producer” : producer, “model” : model, “price”: price}; gán các biến vào các trường tương ứng của đối tượng json.

data: JSON.stringify(json),

url: “/smartphones/createnewPhone”

thực hiện truyền json theo đường dẫn “/smartphones/createnewPhone”

Trong Controller:

@RequestBody Smartphone smartphone thực hiện gán dữ liệu từ json nhận được vào các trường tương ứng của smartphone

 Chay thử sẽ có kết quả sau:

C:\Users\haidt\AppData\Local\Temp\enhtmlclip\Image(265).png

Bước 10: Tạo controller và view hiển thị danh sách điện thoại

@RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Iterable<Smartphone> allPhones(){
return smartphoneService.findAll();
}

@GetMapping("")
public ModelAndView allPhonesPage() {
ModelAndView modelAndView = new ModelAndView("phones/all-phones");

modelAndView.addObject("allphones", allPhones());
return modelAndView;
}

Tạo views phones/all-phone.html 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>List Smartphone</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
</head>
<body>
<div id="container">
<h1>All Smartphones</h1>
<a th:href="@{/smartphones/create}">Create</a>
<div>
<p>Here you can see a list of Smartphones:</p>
<div id="sPhoneFromResponse"></div>
</div>
<table border="1px" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>Producer</th>
<th>Model</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<th:block th:each="phone : ${allphones}">
<tr>
<td th:text="${phone.producer}"></td>
<td th:text="${phone.model}"></td>
<td th:text="${phone.price}"></td>
<td>
<a th:href="@{/smartphones/edit/__${phone.id}__}">Edit</a><br/>
<a th:href="@{/smartphones/delete/__${phone.id}__}">Delete</a><br/>
</td>
</tr>
</th:block>
</tbody>
</table>
<a th:href="@{/}">Home page</a>
</div>
</body>
</html>

Thêm mới 5 bản ghi và chạy thử ứng dụng:

C:\Users\haidt\AppData\Local\Temp\enhtmlclip\Image(266).png

Bước 11: Tạo sự kiện và Controller để xóa dữ liệu

Thêm đoạn script với nội dung như sau để gửi lệnh xóa

<script type="text/javascript">
$(document).ready(function () {
var deleteLink = $("a:contains('Delete')");
$(deleteLink).click(function (event) {
$.ajax({
url: $(event.target).attr("href"),
type: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");

},
success: function (smartphone) {
var respContent = "";
var rowToDelete = $(event.target).closest("tr");
rowToDelete.remove();
respContent += "<span class='success'>Smartphone was deleted: [";
respContent += smartphone.producer + " : ";
respContent += smartphone.model + " : ";
respContent += smartphone.price + "]</span>";
$("#sPhoneFromResponse").html(respContent);
}
});
event.preventDefault();
});
});
</script

Tạo Controller

@RequestMapping(value = "/delete/{id}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Smartphone deleteSmartphone(@PathVariable Integer id){
return smartphoneService.remove(id);
}

Chạy ứng dụng và click delete

C:\Users\haidt\AppData\Local\Temp\enhtmlclip\Image(267).png

Bước 12: Tạo Controller và views sửa

@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public ModelAndView editSmartphonePage(@PathVariable int id) {
ModelAndView mav = new ModelAndView("phones/edit-phone");
Smartphone smartphone = smartphoneService.findById(id);
mav.addObject("sPhone", smartphone);
return mav;
}

@RequestMapping(value = "/edit/{id}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Smartphone editSmartphone(@PathVariable int id, @RequestBody Smartphone smartphone) {
smartphone.setId(id);
return smartphoneService.save(smartphone);
}

Tạo views

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Edit Smartphone</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#editSmartphoneForm').submit(function (event) {
var producer = $('#producer').val();
var model = $('#model').val();
var price = $('#price').val();
var json = {"producer":producer, "model":model, "price":price};
$.ajax({
url: $("#editSmartphoneForm").attr("action"),
data: JSON.stringify(json),
type: "POST",

beforeSend: function (xhr) {
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
},
success: function (smartphone) {
var respContent = "";

respContent += "<span class='success'>Smartphone was edited: [";
respContent += smartphone.producer + " : ";
respContent += smartphone.model + " : ";
respContent += smartphone.price + "]</span>";
$("#sPhoneFromResponse").html(respContent);
}
});
event.preventDefault();
});
});
</script>
</head>
<body>
<div id="container">
<h1>Edit Smartphone</h1>
<div>
<p>Here you can edit Smartphone info:</p>
<div id="sPhoneFromResponse"></div>
</div>
<form id="editSmartphoneForm" th:object="${sPhone}">
<table>
<tbody>
<tr>
<td>Producer:</td>
<td>
<select id="producer" th:field="*{producer}">
<option value="NOKIA">Nokia</option>
<option value="IPHONE">iPhone</option>
<option value="HTC">HTC</option>
<option value="SAMSUNG">Samsung</option>
</select>
</td>
</tr>
<tr>
<td>Model:</td>
<td><input th:field="*{model}" /></td>
</tr>
<tr>
<td>Price:</td>
<td><input th:field="*{price}"/></td>
</tr>
<tr>
<td><input type="submit" value="Edit" /></td>
<td></td>
</tr>
</tbody>
</table>
</form>
<a th:href="@{/smartphones}">List</a>
</div>
</body>
</html>
Chạy thử ứng dụng
C:\Users\haidt\AppData\Local\Temp\enhtmlclip\Image(268).png

Tham khảo mã nguồn: https://github.com/fernandonguyen/PhoneManagement.git

Leave a Reply

Your email address will not be published.