설명

textarea에 image 및 tag 적용이 되지 않음

적용이 불가능하지는 않은듯하나 예외 사항이 많아 적용이 쉽지 않음

div contentEditable="true" 형태를 사용하여 image 및 text를 함께 작성

div contentEditable 형태는 form 형태로 전달하기 힘들기 때문에 hidden text를 사용하여 db에 내용을 전달

Page를 불러 올때 hidden 형태의 타입의 값을 div contentEditable 부분에 가져와서 보여줌

 

html

DB 에서 데이터를 조회하여 templates에 result.content_detail 값을 전달하여 처리하는 형태

content_detail은 hidden형태로 값을 전달하는 중간 역할

content_detail_edit는 실제로 데이터(이미지 및 텍스트)를 화면에 출력하는 역할

 

content_detail_image는 file 타입으로 이미지의 정보를 처리

content_img_width는 이미지의 width정보를 전달하기 위한 용도

<div class="form-group col-md-10">
    <div class="input-group">
        <div class="input-group-prepend iw-100" data-toggle="tooltip" data-container="body" title="content 내용">
            <button type="button" class="btn btn-warning iw-100" onclick="expandText()">Content *</button>
        </div>
        <input type="hidden" name="content_detail" id="content_detail" value = "{{ result.content_detail }}"/>
        <div contentEditable="true" style="min-height: 100px;height: 100%;max-width: 100%;white-space: pre-wrap; word-break: break-word;"class="form-control" id="content_detail_edit" required> </div>
    </div>
</div>

<div class="form-group col-md-2">
    <div class="input-group">
        <div class="custom-file">
            <input type="file" class="custom-file-input" id="content_detail_image" name="content_detail_image">
            <label class="custom-file-label" for="content_detail_image">Choose file</label>
        </div>
        <div class="input-group-append">
            <span class="input-group-text" onclick="goContent_Detail_Image();">Upload</span>
        </div>
    </div>
    Image Width <input style="width: 75px;" type="text" name="content_img_width" class="form-control"  id="content_img_width" value="800" />
</div>

javascript

function 내부에서 bsCustomFileInput.init();를 호출하여 파일 객체 초기화 진행

function 내부에서 hidden type의 데이터를 contentDeitable 타입의 값에 복사하여 page에 출력시 사용

 

goContent_Detail_Image함수에서

file객체의 정보를 ajax 형태로 python으로 전달

python으로 서버에 이미지 생성 성공시 저장된 위치 및 파일명을 기반으로, templates의 content_detail 및 content_detail_edit에 이미치 tag를 추가하여 출력

이미지는 content_img_width에 설정된 값으로 생성(너무 크게 생성되는것을 방지하기 위해 1024를 max로 처리)

 

goContent_Edit함수에서

content_detail_edit에 수정한 내용을 DB에 저장할 때는 content_detail로 값을 변경하여 전달

<!-- Tempusdominus Bootstrap 4 -->
<link rel="stylesheet" href="/static/plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css">

<!-- jQuery -->
<script src="/static/plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="/static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- bs-custom-file-input -->
<script src="/static/plugins/bs-custom-file-input/bs-custom-file-input.min.js"></script>


$(function () {
    // File 객체 초기화
    bsCustomFileInput.init();
  
    // hidden type의 데이터를 contentEditable 타입의 값에 복사
    Src_Val = $('#content_detail').val()
    document.getElementById('content_detail_edit').innerHTML = Src_Val;
    document.getElementById("content_detail_edit").focus();
});


function goContent_Detail_Image(){
    
    let content_id = $('#content_id').val()
    let file = $('#content_detail_image')[0].files[0]

    let form_data = new FormData()
    form_data.append("id_give", content_id)
    form_data.append("file_give", file)

    // Image를 서버에 저장
    $.ajax({
        url:'/content/detail_image',
        type:'POST',
        data: form_data,
        cache: false,
        contentType: false,
        processData: false,
        success: function(data){
            showAlert('content detail image insert successful!!!', data.filename, 2, 2000);

            // 이미지 최대 width는 1024로 제한
            img_width = $('#content_img_width').val()
            if ( img_width > 1024 ) img_width = 1024;

            Src_Val = $('#content_detail').val()
            Change_Val = Src_Val + '<br> <img width = "' + img_width + '" src="/static/img/content/' + data.filename + '">' 

            document.getElementById('content_detail').innerHTML = Change_Val;
            document.getElementById('content_detail_edit').innerHTML = Change_Val;

        },
        error: function(xhr, ajaxOptions, thrownError) {
            var fail_title = "content detail img upload Failed!!";
            var fail_msg = '';
            var err_msg = JSON.parse(xhr.responseText);

            if (thrownError == 'UNKNOWN'){
                fail_msg = err_msg.message + ' (' + xhr.status + ')'
            } else {
                fail_msg = err_msg.message + ' (' + thrownError + ',' + xhr.status + ')';
            }

            showAlert(fail_title,fail_msg,3,1500);
        }
    });
    return false;
}


function goContent_Edit(){
// ** Content 수정 ** //

    // content detail text 전환
    Src_Val = document.getElementById('content_detail_edit').innerHTML;
    $('#content_detail').val(Src_Val);

    var param = $('#content_form').serialize();

    // content 수정 요청
    $.ajax({
        url:'/content/edit',
        type:'POST',
        data: param,
        success: function(data){
            showAlert('content Edit Successful!!!', data.message, 2, 2000);
        },
        error: function(xhr, ajaxOptions, thrownError) {
            var fail_title = "content edit Failed!!";
            var fail_msg = '';
            var err_msg = JSON.parse(xhr.responseText);

            if (thrownError == 'UNKNOWN'){
                fail_msg = err_msg.message + ' (' + xhr.status + ')'
            } else {
                fail_msg = err_msg.message + ' (' + thrownError + ',' + xhr.status + ')';
            }

            showAlert(fail_title,fail_msg,3,1500);
        }
    });
    return false;
};

python

이미지 파일명은 content_id + prefix_date + filename을 조합하여, 이미지 content별 구분과 등록 순서를 확인 가능하도록 네이밍 처리함

이미지를 서버에 저장한 이후 화면에 이미지를 출력하기 위해 파일명을 리턴

from werkzeug import secure_filename

@bts_blueprint.route('content/detail_image', methods=['POST'])
def content_detail_image():

    print("##  content_detail_image  ##")

    try:
        file = request.files['file_give']
        content_id = request.form['id_give']
        #extention = file.filename.split('.')[-1]

        UploadPath = 'static/img/content/'
        
        now_date = datetime.datetime.now()
        prefix_date = now_date.strftime('_%Y%m%d_%H%M%S_')
        
        ImgFullname = UploadPath + content_id + prefix_date + file.filename
        
        #file.save(UploadPath, secure_filename(file.filename))
        file.save(ImgFullname)

        return jsonify(filename=content_id + prefix_date + file.filename), 200

    except (RuntimeError, TypeError, NameError, SQLAlchemyError) as e:
        print("#### content detail img upload Failed !! " + str(e))

        return jsonify(message=str(e)), 1002

참고

https://velog.io/@wrs0707/%EB%82%98%ED%99%80%EB%A1%9C%EC%9D%BC%EA%B8%B0%EC%9E%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0

 

https://blog.naver.com/ynskoh/221064660966

Linux chkconfig

chkconfig는 리눅스서버가 부팅될 때 실행될 프로그램을 설정하는 역활

서버 부팅시 프로그램 리스트와 런레벨별 실행여부 등을 확인하거나 설정

런레벨

레벨 상세 정보
0 종료
1 단일 사용자 모드
2 사용하지 않음
3 다중 사용자 모드 (CUI) - 터미널
4 사용하지 않음
5 다중 사용자 모드 (GUI) - X윈도우
6 재부팅

명령어

// 리스트 확인
  chkconfig --list [프로그램명]

// Linux 시작시 실행되도록 설정
  chkconfig [프로그램명] on

// Linux 시작시 실행되지 않도록 설정
  chkconfig [프로그램명] off

// Linux 런 레벨별로 시작시 실행되도록 설정
  chkconfig --level [레벨] [프로그램명] on

// Linux 런 레벨별로 시작시 실행되지 않도록 설정
  chkconfig --level [레벨] [프로그램명] off

참고

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=diceworld&logNo=220295874000

기능

- 라인 다중 차트(line multiple chart)

- 다중 차트 클릭 이벤트 제어(multiple chart click event control)

- 상시 레이블 출력( show label without mouseover)

html

canvas에 cMultiLineChart 생성

<canvas id="cMultiLineChart" style="min-height: 400px; height: 400px; max-height: 1000px; width: 700px; max-width: 100%;"></canvas>

javascript

chart.min.js : chart.js 3.x 사용을 위해 추가

chartjs-plugin-datalabels@2.0.0 : 마우스 오버없이 레이블을 항상 표시하기 위해 추가 ( chart.js 3.x 버전 부터는 2.0.0 사용)

 

[상시 레이블 출력 로직]

chart.js 3.x 버전에서 datalabels을 사용하기 위해서 반드시 chart config에 plugins: [ChartDataLabels] 추가 (중요)

plugins: [ChartDataLabels]를 추가하지 않는 경우, CDN을 추가하더라도 datalabels 인식이 되지 않음

 

data의 labels를 외부에서 입력 하는 경우

MultiLineChart.data.labels.push('월');

MultiLineChart.data.labels.push('화');

...

MultiLineChart.data.labels.push('일');

MultiLineChart.update();

 

group별 datasets의 data는 일반적으로 외부(DB)에서 조회 후 아래 형태로 입력

Chart_config.data.datasets[0].data.push(11);

Chart_config.data.datasets[0].data.push(22);

Chart_config.data.datasets[1].data.push(33);

Chart_config.data.datasets[1].data.push(44);

Chart_config.data.datasets[2].data.push(55);

Chart_config.data.datasets[2].data.push(66);

MultiLineChart.update();

 

datasets의 datalabels Color는 function을 사용하여 ctx값 추출하여 처리

예제는 value가 5 이상인 경우만 Color를 적용

function(ctx) {
    var value = ctx.dataset.data[ctx.dataIndex];
    return value > 5 ? ctx.dataset.backgroundColor : null;
}

color : label의 색상

backgroundColor : label 배경 색상

formatter : label 출력값

 

[클릭 이벤트 로직]

이벤트 발생 정보 조회

chart.js 3.x 이후 버전부터 getElementsAtEventForMode를 사용

var activePoints = C_Issue_analysis_range.getElementsAtEventForMode(evt, 'point', C_Issue_analysis_range.options);

chart.js 공식 가이드

 

클릭한 Chart의 그룹 인덱스 정보 ( 예제 에서는 Group 1, Group 2, Group 3 의 인덱스)
var datasetIndex = activePoints[0].datasetIndex

 

클릭한 그룹의 Value를 참조하기 위한 Index
var index = activePoints[0].index

 

Group label 정보

var label = MultiLineChart.data.datasets[datasetIndex].label;

 

클릭한 x좌표의 값 정보

var xlavel = MultiLineChart.scales["x"]._labelItems[index].label;

 

클릭한 Group의 Value값

var value = MultiLineChart.data.datasets[datasetIndex].data[index];

<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.0/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>

// Chart ctx Create
var Chart_ctx = document.getElementById('cMultiLineChart').getContext('2d');

// Chart Config Set
Chart_config = {
    plugins: [ChartDataLabels],
    type: 'line',
    data: {
        labels: ['월','화','수','목','금','토','일'],
        datasets: [
            {
                label: 'Group 1',
                data: [1,1,2,2,3,3,4],
                backgroundColor: 'rgba(255, 99, 132, 0.2)',
                borderColor: 'rgba(255, 99, 132, 1)',
                borderWidth: 3,
                datalabels: {
                    labels: {
                        value: {
                            align: 'top',
                            backgroundColor: function(ctx) {
                                var value = ctx.dataset.data[ctx.dataIndex];
                                return value > 0 ? ctx.dataset.backgroundColor : null;
                            },
                            borderWidth: 2,
                            borderRadius: 4,
                            color: function(ctx) {
                                var value = ctx.dataset.data[ctx.dataIndex];
                                return value > 0 ? 'black' : null;
                            },
                            padding: 4
                        }
                    }
                }
            },
            {
                label: 'Group 2',
                data: [4,4,5,5,0,0,1],
                backgroundColor: 'rgba(54, 162, 235, 0.2)',
                borderColor: 'rgba(54, 162, 235, 1)',
                borderWidth: 3,
                datalabels: {
                    labels: {
                        value: {
                            align: 'top',
                            backgroundColor: function(ctx) {
                                var value = ctx.dataset.data[ctx.dataIndex];
                                return value > 0 ? ctx.dataset.backgroundColor : null;
                            },
                            borderWidth: 2,
                            borderRadius: 4,
                            color: function(ctx) {
                                var value = ctx.dataset.data[ctx.dataIndex];
                                return value > 0 ? 'black' : null;
                            },
                            padding: 4
                        }
                    }
                }
            },
            {
                label: 'Group 3',
                data: [0,0,7,8,9,4,1],
                backgroundColor: 'rgba(50, 220, 20, 0.2)',
                borderColor: 'rgba(50, 220, 20, 1)',
                borderWidth: 3,
                datalabels: {
                    labels: {
                        value: {
                            align: 'top',
                            backgroundColor: function(ctx) {
                                var value = ctx.dataset.data[ctx.dataIndex];
                                return value > 5 ? ctx.dataset.backgroundColor : null;
                            },
                            borderWidth: 2,
                            borderRadius: 4,
                            color: function(ctx) {
                                var value = ctx.dataset.data[ctx.dataIndex];
                                return value > 5 ? 'black' : null;
                            },
                            padding: 4
                        }
                    }
                }
            }
        ]
    },
    options: {
        responsive: true,
        scales: {
            y: { 
                beginAtZero: true
            }
        },
        plugins: {
            legend: {
                position: 'left',
            },
            title: {
                display: true,
                text: '그룹별 주간 카운트',
                font: {
                    size: 30
                }
            }
       }
    }
};

// Chart Create
var MultiLineChart = new Chart(Chart_ctx, Chart_config);

// Chart Click Event
document.getElementById("cMultiLineChart").onclick = function(evt) {

    var activePoints = MultiLineChart.getElementsAtEventForMode(evt, 'point', MultiLineChart.options);

    var datasetIndex = activePoints[0].datasetIndex
    var index = activePoints[0].index

    var label = C_Issue_analysis_interval.data.datasets[datasetIndex].label;
    var xlavel = C_Issue_analysis_interval.scales["x"]._labelItems[index].label
    var value = C_Issue_analysis_interval.data.datasets[datasetIndex].data[index]
    
    //alert(label + "  :  " + value + "  :  " + xlavel);

    //Process
};

참고

https://v2_0_0--chartjs-plugin-datalabels.netlify.app/guide/getting-started.html#installation

 

Getting Started | chartjs-plugin-datalabels

Getting Started Installation npm (opens new window) (opens new window) CDN (opens new window) (opens new window) By default, https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels returns the latest (minified) version, however it's highly recommended (open

v2_0_0--chartjs-plugin-datalabels.netlify.app

https://www.chartjs.org/docs/3.2.1/samples/tooltip/interactions.html

https://www.chartjs.org/docs/latest/getting-started/v3-migration.html

https://www.chartjs.org/docs/latest/developers/api.html

https://stackoverflow.com/questions/50515985/get-ylabel-value-onclick-chart-js


to Top