2022.11.21 [MON]
- Day 75-
수업 주요 목차
- AOP(Cont.)
- Logging
- jQuery(cont.)
🤖Review
IoC /DI : 제어의 반전, 필요시 인스턴스를 직접 생성하는 것이 아니라 IoC 컨테이너가 생성한 인스턴스를 확보한다. 이 때 인스턴스를 주입 받는 것을 DI, 검색을 통해 확보하는 것을 DL이라고 한다. 결합도를 낮춰 생산성과 유지보수성이 높아진다. 또한 AOP 지원 환경을 제공한다.
Component 계열 Annotation: Spring container가 bean으로 생성(Reflection API-클래스 정보를 이용하여 실시간으로 객체를 생성할 수 있음)하고 관리(Singleton Design Pattern - 시스템상에서 단 하나의 인스턴스만 생성해 공유하여 사용하는 설계패턴)
- Presentation Layer : @Repository
- Service Layer : @Service @Autowired → type @Qualifier("") (@Resource, @Inject ...)
- Persistence Layer : @Controller
AOP : 관점지향 프로그래밍, 시스템을 Core Concern과 Cross-Cutting Concern의 관점으로 나누어 개발하고 크로스 커팅에 대한 모듈을 별도로 구축해서 시스템을 생산성 높고 유지보수성 높게 구축 관리하는 프로그래밍 방법. 반복적인 코드를 한번만 작성하여 사용하기 때문에 생산성이 높아지고 각각의 역할에 집중할 수 있어 응집도도 향상되며 각각의 코드가 더 전문화될 수 있다. 또한 모듈간 결합도가 낮아져 유지보수성도 높아진다.
Core Concern : 서비스의 주요 비즈니스 로직
Cross-Cutting Concern : 시스템 전반적으로 공통적, 반복적으로 필요로 하는 내용( 보안, 로깅, 트랜잭션 등...)
Advice : 공통기능이 적용되는 특정시점(before, after...)
Pointcut : Advice 적용 대상 범위 지정(하기 위한 표현식은 AspectJ기술 적용)
AOP 동작원리 → Proxy(대리인) Design Pattern
Proxy Design Pattern
- Proxy : 대리인을 의미, 대신해서 역할을 수행(cross-cutting concern 수행)
- 실세계의 예 :
- 아이유 매니저(Proxy)가 아이유 대신 팬관리, 이동수단관리, 일정관리와 같은 공통로직을 대행하고 실제 아이유는 core(노래, 연기 ...)에 집중하게 한다.
- 동일한 인터페이스를 구현하므로 아이유 팬들을 스프링 컨테이너로부터 확보한 객체가 실제 아이유가 아니라 아이유 매니저 임에도 인터페이스 동일하므로 실제 아이유와 소통하는 것으로 알게 된다.
- 공통로직은 아이유 매니저가 대행하게 된다.
- Proxy Design Pattern이 AOP 내부 구현의 원리로 이 패턴은 사용하는 측과 서비스를 제공하는 측의 매개자로서 소통 창구의 역할을 Proxy가 담당하고 이 때 Cross Cutting Concern을 대행한다.
- 사용하는 측에서 Core를 사용 또는 소통하고자 할 때 Spring Container에서는 IoC/ DI || DL로 Core가 아니라 동일한 인터페이스를 구현한 Proxy
Logging : Log4j, Logback, Slf4j
- 로깅 : 시스템 동작에 관한 정보를 제공하는 기록
- 주로 시스템 실행 정보 및 성능, 에러에 대한 정보를 저장, 제공
- 참고) sysout -> 개발 및 운영에 대한 로그 레벨을 지정할 숭 벗고 파일과 같은 곳에 저장할 수 도 없어 정보를 유지할 수 없음 -> logging 관련 기술에 필요
- 자바 진영에서 사용하는 로깅 프레임워크는 대표적으로 Log4j, Logback 등이 있음
- 로깅 프레임워크(또는 라이브러리)의 특징
- 설정파일(logback.xml, log4j.xml)에서 패키지별로 레벨 설정이 가능
- 지정한 레벨 등급 이상의 로그만 기록하고 저장하는 방식
- 파일로깅, 용량설정, 실행시 설정변경 적용 등 여러 기능이 제공
- 로그레벨 (log level) : 만약 로그레벨을 INFO로 설정하면 ERROR,WARN,INFO가 로깅
- ERROR
- WARN
- INFO
- DEBUG
- TRACE
- SLF4J(Simple Logging Facade) * Facade 사전적 의미 : 표면, 겉면 -> 인터페이스를 의미
- 로깅 파사드로서 여러 로깅 라이브러리 구현체(Log4jm Logback 등)를 하나의 통일된 방식으로 사용할 수 있는 방법을 제공(캡슐화
- 이 방식으로 개발하면 이후 로깅 구현체가 변경되어도 별도의 수정이 없다는 장점이 있음 (Just like dbcp)
🔎 Eclipse 실습 내용
1. Logging Test
- maven project로 convert 후 pom.xml에 추가되는 라이브러리들
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
- logback.xml (logging을 위해 src에 존재해야하는 xml 파일)
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan="true" scanPeriod="30 seconds"
주기적으로 configuration 파일을 읽어 Logback의 설정을 재구성
Application실행중일때 별도의 재시작없이 설정 파일을 수정하고 적용가능
scanPeriod default 1분
-->
<configuration scan="true" scanPeriod="30 seconds">
<!--
콘솔 로깅
encoder Pattern로그 출력 포맷
%d{HH:mm}은 로그 출력 시간 포맷
%-5level은 로그 레벨 5 고정폭 출력
%logger는 logger의 이름 출력 { } length
%msg 사용자 메시지 출력
%n 줄바꿈
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
<!--
파일 로깅
RollingFileAppender : 일별로 로그 파일을 백업하면서 로깅
maxHistory : 30일 지나면 오래된 순서부터 로그파일을 지워준다
-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>report.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>report-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
<!-- 특정 패키지 이하 로깅 레별을 별도로 설정 가능 -->
<logger name="org.springframework" level="warn"/>
<logger name="org.kosta.myproject" level="debug"/>
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
- LoggingDemoService
package org.kosta.myproject.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingDemoService {
private Logger logger=LoggerFactory.getLogger(getClass());
public void testLog() {
logger.trace("trace test"); // 가장 낮은 레벨
logger.debug("debug test");
logger.info("info test");
logger.warn("warn test");
logger.error("error test"); //가장 높은 레벨
}
public void testLog2(String message, String message2) {
logger.info("info log test message {} message2 {} ",message, message2);
}
}
SLF4J {} : 변수의 데이터로 출력 (내부적으로 String + 연산을 피하여 성능개선)
{} : 변수 데이터 로깅 위치 설정
logger.info("info log test message " + message + "message2" + message2);
이렇게도 가능하지만 이 경우에는 String 문자열에 + 연산을 수행해 새로운 문자열이 생성된다 → 성능저하
- Test
package test;
import org.kosta.myproject.model.LoggingDemoService;
public class TestLoggingDemoService {
public static void main(String[] args) {
LoggingDemoService service=new LoggingDemoService();
service.testLog();
service.testLog2("월드컵 4강진출 기원","제발!");
}
}
↓
- Console
18:37 DEBUG o.k.m.model.LoggingDemoService - debug test
18:37 INFO o.k.m.model.LoggingDemoService - info test
18:37 WARN o.k.m.model.LoggingDemoService - warn test
18:37 ERROR o.k.m.model.LoggingDemoService - error test
18:37 INFO o.k.m.model.LoggingDemoService - info log test message 월드컵 4강진출 기원 message2 제발!
- File
18:37 DEBUG o.k.m.model.LoggingDemoService - debug test
18:37 INFO o.k.m.model.LoggingDemoService - info test
18:37 WARN o.k.m.model.LoggingDemoService - warn test
18:37 ERROR o.k.m.model.LoggingDemoService - error test
18:37 INFO o.k.m.model.LoggingDemoService - info log test message 월드컵 4강진출 기원 message2 제발!
요구사항 현 시스템에서 서비스 중인 회원 서비스, 상품 서비스 등을 대상으로 1. Service 인터페이스 또는 클래스로 끝나는, 2. 접근제어자 및 리턴타입 관계없이, 3. find로 시작되는 메서드를 대상으로, 4. 검색 기능에 대한 검색어를 5. 특정 파일에 (report.log)에 기록(대상 클래스명, 검색일시, 검색어) *이 파일은 연월일별로 백업이 되어야 한다. |
2. Logging Framework을 이용해 각 Core에 로깅을 적용
- Config
package org.kosta.myproject.Config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration //스프링 설정파일
@ComponentScan("org.kosta.myproject") // Component 계열 Bean 생성 관리 및 DI 적용
public class AppConfig {
}
- Model
- Interfaces
package org.kosta.myproject.model;
public interface MemberService {
void findMemberById(String id);
void register(String memberInfo);
void findMemberByAddress(String address);
}
package org.kosta.myproject.model;
public interface ProductService {
void deleteProduct(String id);
void findProductById(String id);
void findProductListByMaker(String maker);
void findProductListByPriceAndMaker(int price, String maker);
}
-
- Implements
package org.kosta.myproject.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl implements MemberService {
private Logger logger=LoggerFactory.getLogger(getClass());
@Override
public void findMemberById(String id) {
System.out.println("core findMemberById id: "+id);
logger.debug("cross-cutting MemberServiceImpl findMemberById 검색어: {}",id);
}
@Override
public void register(String memberInfo) {
System.out.println("core register memberInfo: "+memberInfo);
}
@Override
public void findMemberByAddress(String address) {
System.out.println("core findMemberByAddress address: "+address);
logger.debug("cross-cutting MemberServiceImpl findMemberByAddress 검색어: {}",address);
}
}
package org.kosta.myproject.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
private Logger logger=LoggerFactory.getLogger(getClass());
@Override
public void deleteProduct(String id) {
System.out.println("core deleteProduct id: "+id);
}
@Override
public void findProductById(String id) {
System.out.println("core findProductById id: "+id);
logger.debug("cross-cutting ProductServiceImpl findProductById 검색어: {}",id);
}
@Override
public void findProductListByMaker(String maker) {
System.out.println("core findProductListByMaker maker: "+maker);
logger.debug("cross-cutting ProductServiceImpl findProductListByMaker 검색어: {}",maker);
}
@Override
public void findProductListByPriceAndMaker(int price, String maker) {
System.out.println("core findProductListByPriceAndMaker price: "+price+ " maker: "+maker);
logger.debug("cross-cutting ProductServiceImpl findProductListByPriceAndMaker 가격 검색어: {} 제조사 검색어: {}",price, maker);
}
}
- Test
package test;
import org.kosta.myproject.Config.AppConfig;
import org.kosta.myproject.model.MemberService;
import org.kosta.myproject.model.ProductService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAOPEx {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(ctx.getBean("memberServiceImpl"));
MemberService ms=(MemberService) ctx.getBean("memberServiceImpl");
ProductService ps=(ProductService) ctx.getBean("productServiceImpl");
// AOP 적용 전에는 실제 Core 객체가 스프링 컨테이너로부터 전달된다.
System.out.println(ms.getClass().getName());
System.out.println(ps.getClass().getName());
ms.findMemberById("java");
ms.findMemberByAddress("오리");
ms.register("손흥민 토트넘");
ps.deleteProduct("1");
ps.findProductById("1");
ps.findProductListByMaker("오뚜기");
ps.findProductListByPriceAndMaker(1000, "오뚜기");
ctx.close();
}
}
↓
- Console
org.kosta.myproject.model.MemberServiceImpl
org.kosta.myproject.model.ProductServiceImpl
core findMemberById id: java
2022-11-21 18:52:11 DEBUG o.k.m.model.MemberServiceImpl - cross-cutting MemberServiceImpl findMemberById 검색어: java
core findMemberByAddress address: 오리
2022-11-21 18:52:11 DEBUG o.k.m.model.MemberServiceImpl - cross-cutting MemberServiceImpl findMemberByAddress 검색어: 오리
core register memberInfo: 손흥민 토트넘
core deleteProduct id: 1
core findProductById id: 1
2022-11-21 18:52:11 DEBUG o.k.m.model.ProductServiceImpl - cross-cutting ProductServiceImpl findProductById 검색어: 1
core findProductListByMaker maker: 오뚜기
2022-11-21 18:52:11 DEBUG o.k.m.model.ProductServiceImpl - cross-cutting ProductServiceImpl findProductListByMaker 검색어: 오뚜기
core findProductListByPriceAndMaker price: 1000 maker: 오뚜기
2022-11-21 18:52:11 DEBUG o.k.m.model.ProductServiceImpl - cross-cutting ProductServiceImpl findProductListByPriceAndMaker 가격 검색어: 1000 제조사 검색어: 오뚜기
- File
2022-11-21 18:52:11 DEBUG o.k.m.model.MemberServiceImpl - cross-cutting MemberServiceImpl findMemberById 검색어: java
2022-11-21 18:52:11 DEBUG o.k.m.model.MemberServiceImpl - cross-cutting MemberServiceImpl findMemberByAddress 검색어: 오리
2022-11-21 18:52:11 DEBUG o.k.m.model.ProductServiceImpl - cross-cutting ProductServiceImpl findProductById 검색어: 1
2022-11-21 18:52:11 DEBUG o.k.m.model.ProductServiceImpl - cross-cutting ProductServiceImpl findProductListByMaker 검색어: 오뚜기
2022-11-21 18:52:11 DEBUG o.k.m.model.ProductServiceImpl - cross-cutting ProductServiceImpl findProductListByPriceAndMaker 가격 검색어: 1000 제조사 검색어: 오뚜기
이 방법의 경우 각각의 method에 로깅관련 코드를 반복적으로 작성해줘야하고 만약 코드에 변경이 생길 경우 각 메소드를 수정해야한다. (생산성과 유지보수성이 떨어진다.) 다음 예제에서 AOP를 사용하여 보완해보도록 하자!
3. Logging Framework와 AOP를 적용
AOP 적용단계 1. pom.xml에 aop lib 추가 2. org.kosta.myproject.aop.KeywordLoggingAspect : cross-cutting concern인 검색어 로깅 기능을 구현 3. Spring 설정 파일인 AppConfig에 AOP 설정 추가 |
- AppConfig
package org.kosta.myproject.Config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration //스프링 설정파일
@ComponentScan("org.kosta.myproject") // Component 계열 Bean 생성 관리 및 DI 적용
@EnableAspectJAutoProxy // Annotation기반 AOP 지원 설정
public class AppConfig {
}
- KeywordLoggingAspect
package org.kosta.myproject.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect //AOP 담당 객체임을 알림 - cross-cutting concern 공통 로직을 정의한 bean
@Component // Component 계열 Bean 생성 관리 및 DI 적용
public class KeywordLoggingAspect {
/*
After Advice : Cross Cutting Concern 공통 로직을 메서드 실행 후에 적용
pointcut : aop 적용 대상을 지정
public: 접근제어자 public
* : void를 포함한 모든 메서드 리턴타입
org.kosta.myproject.model : 패키지 하위 인터페이스 또는 클래스
*Service : Service로 끝나는 인터페이스 또는 클래스
find* : find로 시작하는 method
(..) : 매개변수가 0~*
*/
private Logger logger=LoggerFactory.getLogger(getClass());
@After("execution(public * org.kosta.myproject.model.*Service.find*(..))")
public void logging(JoinPoint point) { // Target(AOP 적용대상) 정보를 가지고 있는 객체
//실제 Target Core Class명을 받아온다.
String className=point.getTarget().getClass().getName();
//실제 Target Core Method명을 받아온다.
String methodName=point.getSignature().getName();
//JoinPoint로부터 Target이 전달받은 매개변수의 인자값들을 받아온다.
Object params[]=point.getArgs();
String keyword="";
for(int i=0;i<params.length;i++) {
keyword+=params[i]+" ";
}
// SLF {} : 변수의 데이터로 치환.할당
logger.debug("AOP Cross-Cutting Concern 로깅처리 core class: {} method: {} keyword: {}",className, methodName,keyword);
}
}
- Model
- Interfaces (위와 동일)
- Implements
package org.kosta.myproject.model;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl implements MemberService {
@Override
public void findMemberById(String id) {
System.out.println("core findMemberById id: "+id);
}
@Override
public void register(String memberInfo) {
System.out.println("core register memberInfo: "+memberInfo);
}
@Override
public void findMemberByAddress(String address) {
System.out.println("core findMemberByAddress address: "+address);
}
}
package org.kosta.myproject.model;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
//private Logger logger=LoggerFactory.getLogger(getClass());
@Override
public void deleteProduct(String id) {
System.out.println("core deleteProduct id: "+id);
}
@Override
public void findProductById(String id) {
System.out.println("core findProductById id: "+id);
}
@Override
public void findProductListByMaker(String maker) {
System.out.println("core findProductListByMaker maker: "+maker);
}
@Override
public void findProductListByPriceAndMaker(int price, String maker) {
System.out.println("core findProductListByPriceAndMaker price: "+price+ " maker: "+maker);
}
}
- Test
package test;
import org.kosta.myproject.Config.AppConfig;
import org.kosta.myproject.model.MemberService;
import org.kosta.myproject.model.ProductService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAOPSolution {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(ctx.getBean("memberServiceImpl"));
MemberService ms=(MemberService) ctx.getBean("memberServiceImpl");
ProductService ps=(ProductService) ctx.getBean("productServiceImpl");
// AOP 적용 전에는 실제 Core 객체가 스프링 컨테이너로부터 전달된다.
//AOP 적용하므로 실제 Core 구현체가 아니라 Proxy 객체가 Spring Container로 부터 전달된다.
System.out.println(ms.getClass().getName());
System.out.println(ps.getClass().getName());
ms.findMemberById("java");
ms.findMemberByAddress("오리");
ms.register("손흥민 토트넘");
ps.deleteProduct("1");
ps.findProductById("1");
ps.findProductListByMaker("오뚜기");
ps.findProductListByPriceAndMaker(1000, "오뚜기");
ctx.close();
}
}
↓
위와 동일한 결과, BUT 훨씬 간결하고 생산성과 유지보수성이 높고 전문화된 코드
4. jQuery 1
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style type="text/css">
.a{
background-color: rgb(255, 196, 0);
}
.b{
background-color: rgb(116, 255, 107);
}
#result{
background-color: pink;
}
</style>
<title>jquery study </title>
</head>
<body>
<div class="container pt-3">
<ol class="a food">
<li>치킨</li>
<li>피자</li>
<li>콜라</li>
</ol>
<ul class="food b">
<li>소주</li>
<li>맥주</li>
<li>와인</li>
</ul>
<ol>
<li>하와이</li>
<li>제주도</li>
<li>훗카이도</li>
</ol>
<br><br>
<span id="result"></span>
<hr>
<div class="student">아이유</div>
<div class="teacher">강하늘</div>
</div>
<script type="text/javascript">
$(document).ready(function() { //document가 ready되면 1번 실행되는 익명함수 : 현 페이지 행위 지정(이벤트 핸들링)
//food class 요소 하위의 li를 클릭하면 자신의 텍스트를 id result span 영역에 출력
$(".food li").click(function() {
$("#result").html("<font color=white>"+$(this).text()+"</font>");
});
//student 와 teacher 처럼 여러 클래스들을 선택할 때에는 .클래스명,.클래스명으로 선택한다.
$(".student, .teacher").click(function() {
alert($(this).text());
});
});
</script>
</body>
</html>
↓
5. jQuery 2 (사진 감추기, 보이기)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<title>jquery study </title>
</head>
<body>
<div class="container pt-3">
<img src="록키.jfif" id="imgView"><br><br>
<button type="button" id="hideBtn">사진감추기</button>
<button type="button" id="showBtn">사진보기</button>
</div>
</body>
<script type="text/javascript">
$(document).ready(function() {
$("#hideBtn").click(function() {
$(imgView).hide(3000);
});
$("#showBtn").click(function(){
$(imgView).show(3000);
});
})
</script>
</html>
↓
![]() |
![]() |
![]() |
![]() |
![]() |
6. jQuery 3 (val()함수, return confirm)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<title>jquery study </title>
</head>
<body>
<div class="container pt-3">
<input type="text" id="nickName" placeholder="닉네임"><button type="button" id="testBtn">테스트버튼</button>
<form action="test.jsp" id="regForm">
<input type="text" name="info" placeholder="등록정보">
<button type="submit">등록하기</button>
</form>
<script type="text/javascript">
$(document).ready(function(){
//id testBtn 클릭시 id nickName에 사용자가 입력한 값을 alert로 출력하도록 이벤트 틍록
//form 입력양식은 jquery val()함수 - get,set
$("#testBtn").click(function() {
if($("#nickName").val()!=""){
alert($("#nickName").val());
$("#nickName").val("").focus();
} else{
alert("닉네임을 입력하세요");
$("#nickName").focus();
}
}); //click
//id regForm 이 confirm 되기 전 confirm이 동작되어 등록하시겠습니까? 확인 누르면 전송, 취소 누르면 전송 X
/* $("#regForm").submit(function(){
return confirm("등록하시겠습니까?") //return false를 하면 전송되지 않는다
}) */
$("button[type=submit]").click(function() {
return confirm("등록하시겠습니까?")
})
}); //ready
</script>
</div>
</body>
</html>
↓
![]() |
![]() |
![]() |
![]() |
![]() |
![]() 취소 클릭시 |
7. jQuery 4 (토글)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<script
src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.slim.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<title>jquery toggle</title>
<style type="text/css">
#detailInfo {
display: none;
}
#toggleSpan {
background-color: yellow;
}
</style>
</head>
<body>
<div class="container pt-3">
<!-- 정보 더보기와 접기가 번갈아 가면서 표현된다
detailInfo의 정보가 보이는 상태이면 접기가 표현되고
detailInfo의 정보가 보이지 않는 상태이면 정보 더보기가 표현된다
toggleSpan 을 클릭하면 아래 detailInfo가 보이는 상태와
보이지 않는 상태로 번갈아 가면서 수행한다
-->
<script type="text/javascript">
$(document).ready(function() {
$("#toggleSpan").click(function() {
//slideToggle() : show와 hide를 번갈아가면서 수행한다.
// 첫번째 매개변수는 duration, 두번째 매개변수는 complete(동작 완료 후 실행되는 함수)
$("#detailInfo").slideToggle(1000, function(){
//정보 더보기가 실행되어 정보가 더 보이는 상태인지, 아니면 감추어진 상태인지를 확인
if($(this).css("display")=="none"){ //감추어진 상태면 true, 보이는 상태이면 false
$("#toggleSpan").text("정보 더보기");
} else{
$("#toggleSpan").text("접기");
}
}); //slide Toggle
}); //click
}); // ready
</script>
<span id="toggleSpan">정보 더보기</span> <br>
<br>
<p id="detailInfo">
본명 이지은(李知恩)[1]<br>
<br> 출생 1993년 5월 16일, 서울특별시 성동구 송정동[2] / 24세<br>
<br> 국적 대한민국파일:대한민국 국기.png<br>
<br> 본관 전주 이씨[3]<br>
<br> 신체 161.7cm, 44kg[4][5], A형<br>
<br> 가족 부모님, 남동생<br>
<br>
</p>
</div>
</body>
</html>
↓
![]() |
![]() |
//오늘의 숙제
복습!!