admin 설정

코딩 연습/Django 2019. 5. 22. 16:31

admin에서 model 등록하기

  • admin에 등록하고자하는 모델을 import한다.
  • @admin.register() 장식자(decorator)를 사용하여 등록한다.
  • 모델 클래스처럼 admin 클래스를 작성하고 별다른 변경사항이 없는 경우 pass 처리한다.
from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
	pass

admin 등록 후 Post 모델에 저장된 데이터가 노출된다

 

모델에 저장된 data를 원하는 필드에 맞게 노출시키기

  • list_display는 노출시키고자 하는 필드를 선택
  • list_display_links는 등록한 필드의 데이터에 링크를 걸어 세부내역을 확인할 수 있다
  • search_fields는 해당 필드의 데이터를 검색할 수 있게 해준다.
from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title']
    list_display_links = ['id', 'title']
    search_fields = ['title']

ID 필드가 노출되며, 두 필드 모두 링크가 걸려있고, TITLE 검색까지 가능해졌다

 

블로그 이미지

쵸잇

,

폼 샘플 형태

 

나는 Title, Top text, Author 3가지를 입력 받아 책 표지에 제목, 부제, 저자 형태로 출력할 수 있는 폼을 만들고 싶다.

 

 

1) [forms.py]를 앱 폴더에 추가하여 폼의 이름과 필드명, 데이터타입을 지정한다.

- 책의 표지에 쓸 데이터를 입력 받으므로 [CoverForm]으로 지정

- title, top_text, author 모두 문자로 입력 받을 것이므로 [CharField()]를 사용

from django import forms

class CoverForm(forms.Form):
    title = forms.CharField()
    top_text = forms.CharField()
    author = forms.CharField() 

 

 

2) [forms.py]에서 만든 [CoverForm]을 import하여 [views.py]에서 로직을 임시로 작성한다.

- [CoverForm()]을 변수 [form]에 담아 context(ctx)를 통해 [index.html]로 render한다.

from django.shortcuts import render
from .forms import CoverForm

# Create your views here.
def index(request):
	form = CoverForm()
    ctx = {
        'form' : form,
    }
    return render(request, 'cover/index.html', ctx)

 

 

3) [index.html]에서 <form>태그를 작성한다.

- 우리가 웹브라우저에서 볼 수 있는 모양의 폼을 드디어 만든다.

- [action=""]은 입력 받은 데이터를 현재 페이지에서 처리하도록 한다.

- [method="post"]는 데이터를 저장할 수 있게 처리를 해주는 메소드이다.

- 따라서 폼을 통해 입력한 데이터를 현재 페이지에 전송하여 DB로 저장할 수 있게 기본 세팅을 하는 것이다.

<form action="" method="post">
    {% csrf_token %}
      {{ form }}
    <input type="submit" value="Generator O RLY">
</form>

 

 

짜잔, 완성된 폼이다. 

 

샘플과 비교하면 1줄로 작성된게 어색하게 느껴진다

 

<table> 태그를 활용하여 줄바꿈을 할 수 있다. 

<form action="" method="post">
    {% csrf_token %}
    <table>
      {{ form }}
    </table>
    <input type="submit" value="Generator O RLY">
form>

 

샘플과 똑같은 모습을 갖췄다

 

4) [views.py]에서 다시 로직을 구체적으로 구성한다.

- 웹브라우저에서 입력 받아 전송된 데이터를 [index 함수]를 통해 가공을 거친다.

- [CoverForm(request.POST)]는 폼과 폼에 입력된 데이터를 포함하고 있다.

- [is_valid() 메소드]는 데이터의 유효성을 검사하는데, [CharField()]에 해당하는지 검증하는 것이다.

- [cleaned_data]는 데이터를 딕셔너리 타입으로 제공하도록 해준다. 

- 완료시 cover 앱의 index 이름의 URL로 redirect시킨다. 

from django.shortcuts import render
from .forms import CoverForm

# Create your views here.
def index(request):
    if request.method == "POST":
        form = CoverForm(request.POST)
        if form.is_valid():
            form.cleaned_data
            return redirect('cover:index')
    else:
        form = CoverForm()

    ctx = {
        'form' : form,
    }
    return render(request, 'cover/index.html', ctx)
블로그 이미지

쵸잇

,
Internal Server Error: /new/
Traceback (most recent call last):
  File "C:\Users\user\dev\facebook_project\django_env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "C:\Users\user\dev\facebook_project\django_env\lib\site-packages\django\db\backends\sqlite3\base.py", line 383, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: NOT NULL constraint failed: facebook_article.title

처음보는 낯선 에러였는데, models.py에서 Article클래스 중 title 필드의 값에 null=True를 추가로 기입하니 완료!

블로그 이미지

쵸잇

,

이 에러 때문에 얼마나 고생했던지 수명이 짧아졌을 정도다.
구글링을 하면 대부분이 .py파일을 .txt파일로 실행한 경우에 에러가 발생하는데,
나와 같은 경우 Django로 만든 웹사이트를 runserver하면 명령프롬프트에서는 해당 에러가 나타나고,
웹브라우저에서는 A server error occurred. Please contact the administrator.라는 문장이 노출되었다.
디버그 화면이 나와야하는데 그렇지 않아서 어떻게 손을 써야할지 몰랐다.
사실 힌트는 이미 수십번 찾았다.

open(encoding="utf-8")

템플릿의 유니코드를 utf-8로 인코딩해주는 짧은 코드를 적재적소에 작성하면 되는 것이다.
그 적재적소를 찾는데 오랜 시간이 걸린 것 같다.
[https://chibychi.blogspot.com/2019/04/django-unicodedecodeerror.html?showComment=1556001026219#c6818858330997373051\](https://chibychi.blogspot.com/2019/04/django-unicodedecodeerror.html?showComment=1556001026219#c6818858330997373051)
바로 이 분 덕분에 정확한 위치를 찾아서 코드를 집어넣었다.

가상환경 폴더에서 Lib\site-packages\django\views\debug.py를 찾아

def get_traceback_html(self):
    """Return HTML version of debug 500 HTTP error page."""
    with Path(CURRENT_DIR, 'templates', 'technical_500.html').open() as fh:
        t = DEBUG_ENGINE.from_string(fh.read())
    c = Context(self.get_traceback_data(), use_l10n=False)
    return t.render(c)

비어있던 open() 메서드에 encoding="utf-8"을 기입했다.

def get_traceback\_html(self): 
    """Return HTML version of debug 500 HTTP error page.""" 
    with Path(CURRENT_DIR, 'templates', 'technical_500.html').open(encoding="utf-8") as fh: 
        t = DEBUG\_ENGINE.from_string(fh.read()) 
    c = Context(self.get_traceback_data(), use_l10n=False) 
    return t.render(c) 

가상환경 활성화를 꼭 잊지 않고 실행해야한다.

블로그 이미지

쵸잇

,

강의를 따라 만든 블로그에서 혼자서 새로운 기능 추가하기


  • 원하는 기능: Django Admin에서 카테고리를 선택하는 것을 폼을 통해 만들고 싶었다




HTML Form 요소들 중에 <select> 요소 사용하기


  • HTML Form 요소들을 확인해볼 수 있다. (https://www.w3schools.com/html/html_form_elements.asp)

  • <select> 요소를 사용하면 drop-down list 형태로 만들 수 있다.

  • selected 속성은 옵션을 우선 선택해놓게 한다. 나의 경우 "Category"를 지정했다.

  • disabled 속성은 해당 옵션이 사용되지 않도록 한다. 따라서 "Category"는 타이틀 역할이다.


1
2
3
4
5
<select name="category">
    <option selected="selected" disabled>Category</option>
    <option value="dv">Development</option>
    <option value="ps">Personal</option>
</select>
cs





  • 완성된 모습이다. 기본값처럼 Category가 보이지만 옵션을 disabled한 상태이므로 실제로 선택은 되지 않는다.





블로그 이미지

쵸잇

,

Component State컴포넌트 안에서 쓸 수 있는 객체이다.

그리고 this.setState()를 활용하여 리액트만의 장점을 확인해보자



import React, { Component } from 'react';
import './App.css';
// Movie.js에서 Movie 클래스를 가져온다
import Movie from './Movie'

// extends를 사용하여 Component를 가져와야 App 이름의 컴포넌트를 만들 수 있다
class App extends Component {

  // state는 컴포넌트 안에서 사용되는 객체
  // 특징은 state가 바뀔 때마다 render()가 실행된다

  state = {
    movies: [
      {
        title: "Matrix",
        poster: "https://cdn.vox-cdn.com/thumbor/veUKpCnSKflt86VfCpTfyUqEfYQ=/0x0:1280x720/1200x800/filters:focal(538x258:742x462)/cdn.vox-cdn.com/uploads/chorus_image/image/52198011/c6f5a81cb0fcc1c0e5ae3cba9cc4f40ae35476cb.0.jpeg",
      },
      {
        title: "Oldboy",
        poster: "https://static01.nyt.com/images/2018/10/18/arts/18terracehouse/18terracehouse-articleLarge.jpg?quality=75&auto=webp&disable=upscale",
      },
      {
        title: "Terrace House",
        poster: "https://cdn.vox-cdn.com/thumbor/GltBWgYa-A_SKHFx2CKHEr93Zvw=/0x0:5760x3840/1200x0/filters:focal(0x0:5760x3840):no_upscale()/cdn.vox-cdn.com/uploads/chorus_asset/file/9898277/_DSC_I1A7700.JPG",
      },
      {
        title: "Suits",
        poster: "https://i.ytimg.com/vi/4qBl7_t0M_U/maxresdefault.jpg",
      },
      {
        title: 'Heart Signal',
        poster: 'https://t1.daumcdn.net/cfile/tistory/2165C83C58A206D523',
      }
    ]
  }



App 컴포넌트 밖에 있던 movies 객체를 컴포넌트 안에 위치한 state에 담았다.


  • state는 컴포넌트 안에서만 쓸 수 있는 객체이다.

  • 특이점은 함수를 통해 state에 담긴 값이 변경될 경우 자동으로 render() 메소드를 재실행시킨다.


// 컴포넌트가 마운트(트리에 삽입되면)되면 작동하는 메소드
  // state 값을 설정하면 render를 재실행한다
  componentDidMount(){
    // ()는 function()과 같은 의미
    setTimeout(() => {
      this.setState({
        movies: [
          // state 객체에서 영화정보가 담긴 movies 값을 불러온다
          ...this.state.movies,

          // 새로 추가할 데이터를 입력한다
          {
            title: 'Heart Signal',
            poster: 'https://t1.daumcdn.net/cfile/tistory/2165C83C58A206D523',
          }
      })
    }, 5000)
  }



마운트 후 componentDidMount() 메소드가 호출된다.


  • setTimeout()this.setState() 원하는 시간(5000=5초)이 지나서 호출되도록 하기 위함이다.
  • () => {}function(){}과 동일한 내용이다.
  • ...this.state.movies를 기존 데이터인 moives를 전부 읽어오는 것이다.
  • 따라서 기존 데이터에 새로운 영화 데이터를 추가 입력하여 state 값에 변화를 주었다.   



  render() {
    return (
<div className="App">
        {/* 리스트에 map 메소드를 사용하여 각 객체의 데이터를 컴포넌트로 전달 */}
        {/* index는 각 객체에 id 값을 넣어주며 key값으로 전달한다 */}
        {this.state.movies.map((movie, index) => {
          return <Movie title={movie.title} poster={movie.poster} key={index}/>
        })}
      </div>
    );
  }
}

export default App;



state의 값이 변화가 되면서 render() 메소드는 재실행된다.


  • state에 추가로 저장된 movies 데이터를 Movie 컴포넌트로 전달하여 기존 영화정보에서 5초 후 더 늘어난 영화 정보가 노출된다.




삼항연산자를 활용하여 리액트만의 극적인 효과 나타내기

동시에 나만의 메소드 만들기



class App extends Component {

  state = {
  }

  componentDidMount(){
    setTimeout(() => {
      this.setState({
        movies: [
          {
            title: "Matrix",
            poster: "https://cdn.vox-cdn.com/thumbor/veUKpCnSKflt86VfCpTfyUqEfYQ=/0x0:1280x720/1200x800/filters:focal(538x258:742x462)/cdn.vox-cdn.com/uploads/chorus_image/image/52198011/c6f5a81cb0fcc1c0e5ae3cba9cc4f40ae35476cb.0.jpeg",
          },
          {
            title: "Oldboy",
            poster: "https://static01.nyt.com/images/2018/10/18/arts/18terracehouse/18terracehouse-articleLarge.jpg?quality=75&auto=webp&disable=upscale",
          },
          {
            title: "Terrace House",
            poster: "https://cdn.vox-cdn.com/thumbor/GltBWgYa-A_SKHFx2CKHEr93Zvw=/0x0:5760x3840/1200x0/filters:focal(0x0:5760x3840):no_upscale()/cdn.vox-cdn.com/uploads/chorus_asset/file/9898277/_DSC_I1A7700.JPG",
          },
          {
            title: "Suits",
            poster: "https://i.ytimg.com/vi/4qBl7_t0M_U/maxresdefault.jpg",
          },
          {
            title: 'Heart Signal',
            poster: 'https://t1.daumcdn.net/cfile/tistory/2165C83C58A206D523',
          }
        ]
      })
    }, 5000)
  }



state에 작성했던 movies 값을 비워두자


  • this.setState()movies 값을 그대로 가져온다.



  // _(언더바)를 사용해 나만의 메소드와 리액트 자체 메소드에 구분을 주었다.
  // state에서 movies 값을 map() 메소드에 적용하여 새로운 배열을 만들어 movies 객체에 담아서 반환
  _renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      return <Movie title={movie.title} poster={movie.poster} key={index}/>
    })
    return movies
  }



render() 메소드 안에 작성했던 map() 메소드를 가져온다


  • _renderMovies는 나만의 메소드이므로 리액트 자체 메소드와 구분 짓기 위해 _(언더바)를 사용한다.
  • map()을 통해 만들어진 배열을 새로이 movies 변수에 담아서 반환한다.



  render() {
    return (
      <div className="App">
        {/* 삼항연산자 사용 */}
        {/* 컴포넌트 state에서 movies 값이 있다면 _renderMovies()를 실행하고 없으면 'Loading' 문자 출력 */}
        {this.state.movies ? this._renderMovies() : 'Loading'}
      </div>
    );
  }
}

export default App;



항연산자를 사용하여 state에서 movies 객체의 존재에 따라 데이터 출력을 달리한다.


  • statemovies 객체가 존재하면 _renderMovies()를 실행하여 영화 정보가 출력되도록 한다.
  • statemovies 객체가 없다면 'Loading' 문자만 웹브라우저에 노출된다.
  • 따라서 state에서 아무런 값이 없는 상태로 마운트된 후 5초가 지나서 componentDidMount()가 실행되어 statemovies 객체가 삽입된다.
  • state에 값에 변화가 생기면서 render()를 재실행한 후 _renderMovies()를 통해 Movie 컴포넌트에 영화 정보를 전달하여 웹브라우저에 노출시킨다.

 

'코딩 연습 > React' 카테고리의 다른 글

PropTypes로 데이터 형태 확인하기  (0) 2019.01.07
Array.prototype.map() 활용하기  (0) 2019.01.07
블로그 이미지

쵸잇

,

PropTypes으로 데이터 형태 확인하기

데이터 형태 확인을 통해 많은 버그를 잡을 수 있다. 우선 객체내 각 요소에 부합하는 propTypes를 할당한다 



import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './Movie.css';

class Movie extends Component {

    // 부모 컴포넌트에서 받은 데이터 형태를 규정하여 이와 다를 경우 에러가 발생한다
    static propTypes = {
        // isRequired는 필수사항으로 지정해준다. 부모컴포넌트에서 값이 없을 경우 오류발생
        title: PropTypes.string.isRequired,
        poster: PropTypes.string,
    }

    render() {
        return(
            <div>
                {/* MoviePoster 컴포넌트에 poster 값을 담아준다 */}
                <MoviePoster poster={this.props.poster} />
                {/* 부모컴포넌트에서 전달 받은 title 값을 출력한다 */}
                <h1>{this.props.title}</h1>
            </div>
        )
    }
}

class MoviePoster extends Component {
   
    // 부모 컴포넌트에서 받은 데이터를 체크하고, 지정한 타입과 다르면 에러 발생
    static propTypes = {
        poster: PropTypes.string.isRequired,
    }

    render() {
        return(
            // 부모컴포넌트인 Movie 컴포넌트에서 전달 받은 poster 값을 출력한다
            <img src={this.props.poster} />
        )
    }
}

export default Movie



객체내 각 요소에 PropTypes를 규정하면 데이터 형태를 확인한 후에 다음 단계로 넘어간다


  • PropTypes에는 string, number, bool, func 등 여러 타입을 사용할 수 있다.
  • isRequired를 사용하게 되면 해당 요소가 존재하지 않을시 에러를 발생시킨다. 따라서 반드시 요소 값을 입력해야한다.
  • PropTypes를 확인하고 유효한 경우에만 this.props.poster로 값이 전달된다.


'코딩 연습 > React' 카테고리의 다른 글

Component State with this.setState()  (0) 2019.01.10
Array.prototype.map() 활용하기  (0) 2019.01.07
블로그 이미지

쵸잇

,

map() 메소드를 활용하여 각 배열의 값을 일일이 컴포넌트에 전달하도록 코드를 작성하는 번거로움을 줄여보자



import React, { Component } from 'react';
import './App.css';
// Movie.js에서 Movie 클래스를 가져온다
import Movie from './Movie'

const movieTitles = [
  "Matrix",
  "Oldboy",
  "Terrace House",
  "Suits",
]

const movieImages = [
  "https://cdn.vox-cdn.com/thumbor/veUKpCnSKflt86VfCpTfyUqEfYQ=/0x0:1280x720/1200x800/filters:focal(538x258:742x462)/cdn.vox-cdn.com/uploads/chorus_image/image/52198011/c6f5a81cb0fcc1c0e5ae3cba9cc4f40ae35476cb.0.jpeg",
  "https://static01.nyt.com/images/2018/10/18/arts/18terracehouse/18terracehouse-articleLarge.jpg?quality=75&auto=webp&disable=upscale",
  "https://cdn.vox-cdn.com/thumbor/GltBWgYa-A_SKHFx2CKHEr93Zvw=/0x0:5760x3840/1200x0/filters:focal(0x0:5760x3840):no_upscale()/cdn.vox-cdn.com/uploads/chorus_asset/file/9898277/_DSC_I1A7700.JPG",
  "https://i.ytimg.com/vi/4qBl7_t0M_U/maxresdefault.jpg",
]


// extends를 사용하여 Component를 가져와야 App 이름의 컴포넌트를 만들 수 있다
class App extends Component {
  render() {
    return (
      <div className="App">
        {/* Movie 컴포넌트에 title, poster 값을 각각 담아준다 */}
        <Movie title={movieTitles[0]} poster={movieImages[0]} />
        <Movie title={movieTitles[1]} poster={movieImages[1]} />
        <Movie title={movieTitles[2]} poster={movieImages[2]} />
        <Movie title={movieTitles[3]} poster={movieImages[3]} />
      </div>
    );
  }
}

export default App;




배열의 요소가 늘어날수록 그만큼 컴포넌트에 전달하는 데이터가 늘어 코드량이 늘어난다.


  • movieTitles, movieImages 변수를 movies로 통합하여 객체 형태로 2개의 데이터를 담아 배열에 담는다.
  • map() 메소드를 활용하여 반복적인 코드를 줄인다.




import React, { Component } from 'react';
import './App.css';
// Movie.js에서 Movie 클래스를 가져온다
import Movie from './Movie'

const movies = [
  {
    title: "Matrix",
    poster: "https://cdn.vox-cdn.com/thumbor/veUKpCnSKflt86VfCpTfyUqEfYQ=/0x0:1280x720/1200x800/filters:focal(538x258:742x462)/cdn.vox-cdn.com/uploads/chorus_image/image/52198011/c6f5a81cb0fcc1c0e5ae3cba9cc4f40ae35476cb.0.jpeg",
  },
  {
    title: "Oldboy",
    poster: "https://static01.nyt.com/images/2018/10/18/arts/18terracehouse/18terracehouse-articleLarge.jpg?quality=75&auto=webp&disable=upscale",
  },
  {
    title: "Terrace House",
    poster: "https://cdn.vox-cdn.com/thumbor/GltBWgYa-A_SKHFx2CKHEr93Zvw=/0x0:5760x3840/1200x0/filters:focal(0x0:5760x3840):no_upscale()/cdn.vox-cdn.com/uploads/chorus_asset/file/9898277/_DSC_I1A7700.JPG",
  },
  {
    title: "Suits",
    poster: "https://i.ytimg.com/vi/4qBl7_t0M_U/maxresdefault.jpg",
  }
]

// extends를 사용하여 Component를 가져와야 App 이름의 컴포넌트를 만들 수 있다
class App extends Component {
  render() {
    return (
      <div className="App">
        {/* movies 배열에 map() 메소드를 사용하여 각 객체의 데이터를 컴포넌트로 전달 */}
        {/* index는 각 객체에 id 값을 넣어주며 key값으로 전달한다 */}
        {movies.map((movie, index) => {
          return <Movie title={movie.title} poster={movie.poster} key={index}/>
        })}
      </div>
    );
  }
}

export default App;


 

map() 메소드는 배열의 모든 요소에 하나씩 작성된 함수를 통해 새로운 배열 형태로 반환한다.

각 요소에 id 값을 주기 위해 index를 추가로 작성할 수 있다.


'코딩 연습 > React' 카테고리의 다른 글

Component State with this.setState()  (0) 2019.01.10
PropTypes로 데이터 형태 확인하기  (0) 2019.01.07
블로그 이미지

쵸잇

,

Moment 앱 따라하기

배경화면으로 쓰일 이미지를 랜덤으로 출력 위해 Math 내장객체에서 floor(), random() 메소드를 활용한다.


html 파일에서 body 요소를 가져온다.


  • 웹브라우저 화면 전체를 이미지로 씌울 예정이므로 body를 통째로 가져온다.


const body = document.querySelector("body");




1부터 시작하는 정수를 랜덤하게 뽑는 함수를 만든다.


  • Math.random()은 1이하의 실수를 랜덤으로 반환한다.
  • Math.floor()는 실수를 소수점 내림하여 정수 형태로 반환한다.
  • 0부터 숫자가 반환되므로 더하기 1을 하여 숫자 1부터 변수로 지정되도록 한다.


// 가지고 있는 배경사진 갯수
const IMG_NUMBER = 3;
function
genRandom(){

    // Math.random()은 1 이하의 실수를 랜덤으로 출력한다 (0.XXXXXX 형태로)
    // 3을 곱한다해도 숫자 3을 넘지 못하는 실수 형태의 숫자를 출력한다

    // Math.floor()는 소수점 내림을 하여 실수 형태를 정수로 만든다
    // 따라서 Math.floor(Math.random())는 "0"만 출력한다
    // Math.floor(Math.random() * 3)은 "0, 1, 2"까지 출력 가능하다
    // Math.ceil()은 소수점 올림을 한다
   
    // IMG_NUMBER는 3 이므로
    const number = Math.floor(Math.random() * IMG_NUMBER) + 1;
   
    // 0, 1, 2 중에 하나를 반환한다
    return number;
}



  • 배경사진의 개수가 3개라서 IMG_NUMBER 변수에 3을 담았다.
  • 이미지 개수에 맞게 임의로 하나의 숫자를 뽑아서 해당 숫자의 파일명을 가진 이미지를 가져올 것이다.



랜덤으로 뽑힌 숫자랑 같은 이름의 이미지 파일을 호출한다.


  • 랜덤으로 뽑은 숫자를 함수의 인자로 사용한다.
  • new Image() 메소드를 사용하여 html<img>를 만드는데, 이는 우리가 출력할 <img src="">가 담긴다.
  • <img src="">images 폴더에 있는 이미지 파일을 지정한다.
  • 그 이미지 파일은 랜덤으로 가져와야 하므로 imgNumber 파라미터를 통해 랜덤 숫자를 받는다


function paintImage(imgNumber) {

    // <img> 태그를 만들어준다
    const image = new Image();
    // document.createElement('img')와 같은 코드를 나타낸다
  
    // <img src=""> 이미지 파일의 소스를 입력한다
    // 랜덤 숫자에 +1을 하는 이유는 이미지 파일이 1부터 시작하므로
    image.src = `images/${imgNumber}.jpg`;
  
    // <img src="" class=""> 이미지 효과를 위해 클래스를 추가한다
    image.classList.add("bgImage");
   
    // <body>에 완성된 <image>를 자식요소로 삽입하기
    body.appendChild(image);
}


  • 배경사진이 원본 그대로 노출되면 웹브라우저에 맞지 않으므로, CSS를 통해 약간의 수정을 한다.
  • 완성된 image 변수를 body에 자식요소로 추가한다



배경사진의 크기를 조절하고 동시에 애니메이션 효과를 준다. 


  • animation 속성을 사용하기 앞서 @keyframes를 작성한다.
  • 투명도(opacity)를 0에서 1로 변화를 주어 자연스럽게 화면이 전환될 수 있게 한다.
  • top, left 크기를 0로 하여 텍스트로 인해 비어있는 상단, 왼쪽 공간을 제거한다. 
  • 웹브라우저 화면 크기에 맞도록 widthheight 비율을 100%로 작성한다. 
  • 텍스트가 배경사진 앞에서 노출될 수 있도록 z-index 속성을 이용해 레이어 순서를 조절한다.


/* 애니메이션으로 쓸 효과(fadeIn)를 설정한다 */
/* 사진의 투명도를 조절하여 안보이다가 보이게 한다 */
@keyframes fadeIn {
    from {
        /* 투명도 설정 */
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.bgImage {
    /* 절대적인 위치를 계산한다 */
    position: absolute;
    top: 0;
    left: 0;
    /* 웹브라우저에 맞게 사이즈를 조절한다 */
    width: 100%;
    height: 100%;

    /* 쌓여있는 레이어 순서. -1을 입력하면 사진이 뒤로 밀리고 텍스트가 앞으로 온다 */
    z-index: -1;

    /* 0.5초 서서히 사진이 나타난다 */
    animation: fadeIn 0.5s linear;
}



genRandom() 함수와 printImage() 함수를 순차적으로 실행시킨다.


  • genRandom() 함수를 통해 랜덤으로 뽑은 숫자를 printImage() 함수의 인자로 사용한다


function init(){
    // 랜덤으로 받은 숫자를 변수 지정
    const randomNumber = genRandom();

    // 받은 숫자를 이미지를 선택하는 함수에 전달한다
    paintImage(randomNumber);
}

init();


블로그 이미지

쵸잇

,

filter() 메서드는 주어진 판별 함수를 통과하는 요소를 모아 새로운 배열로 만들어 반환합니다.

forEach() 메소드처럼 배열 내 각 요소에 함수를 적용할 수 있는데, 다른 점은 함수를 통해 반환된 결과를 모아 새로운 배열을 만들어준다는 것이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
let toDos = [];
 
function deleteToDo(event){
    const btn = event.target; #버튼 태그 지정
    const li = btn.parentNode; #버튼이 포함된 리스트
 
    toDoList.removeChild(li); #리스트 제거
 
    const cleanToDos = toDos.filter(function(toDo){
        return toDo.id !== parseInt(li.id); #리스트에서 제거된 id와 다른 값들로 새 배열 추가
    });
    toDos = cleanToDos; #새 배열 저장
    saveToDos(); #로컬스토리지에 문자열로 파싱 후 저장
}
cs


toDoList는 html 상에서 작성한 toDo가 출력되는 리스트인데,


그 중 하나의 toDo를 삭제하여, 그 toDo가 가진 id 값과 toDos 변수인 배열 안에 객체들의 id 값을 비교하는데,

배열 안에서 id 값이 다른 것들만 따로 모아 새로운 이름(cleanToDos)의 배열을 만든다는 것이다.


아직 설명하는 방법도 어려울 정도이다.


인강 들으면서 가장 이해 안되었던 부분이다. 이해하는데 정말 오랜 시간이 걸린듯;






출처

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

블로그 이미지

쵸잇

,