개발팁2020. 3. 3. 12:52

C# 언어에서는 string.Format 또는 String.Format 이 굉장히 유용하게 문자열을 생성하는 메서드인데,

자바스크립트에서는 이런 기능이 없어 아쉽다.

 

String.Format("{0}, {1}!", "Hello", "World");

=> Hello, World!

 

자바스크립트에서는 문자열 객체가 String 이므로, String 객체안에다 만들것이고,

자바스크립트가 일반적으로 카멜 케이스 (Camel Case) 를 사용하므로, 소문자 f 로 시작하는 format 이라는 함수를 만들 것이다.

 

C# 언어의 Format 의 기능은 다양하고 상당히 많지만, 여기서는 단순위 replace 만 할것이다.

 

C# 의 String.Format 의 기능이 궁금하다면 아래 링크에서 확인할 수 있다.

 

https://docs.microsoft.com/ko-kr/dotnet/api/system.string.format

 

String.Format 메서드 (System)

 

지정된 형식에 따라 개체의 값을 문자열로 변환하여 다른 문자열에 삽입 합니다.Converts the value of object

docs.microsoft.com

 

여기서는 단순히 {0}, {1}, ... {n} 으로만 치환할 것이다.

 

String.format = function() {
	let args = arguments;

	return args[0].replace(/{(\d+)}/g, function(match, num) {
		num = Number(num) + 1;
		return typeof(args[num]) != undefined ? args[num] : match;
    });
}

 

 

파라미터 갯수가 정해지 있지 않기 때문에, 함수 구문 ( function() ) 에 파라미터명은 넣지 않았다.

첫번째 파라미터는 문자열로 { 문자와 숫자(정규식의 \d) } 을 만나면, 뒤의 n+1 번째 파라미터로 replace 하게 된다.

 

 

"Hello" + ", " + "World" + "!";

=> Hello, World!



String.format("{0}, {1}!", "Hello", "World");

=> Hello, World!

 

 

그외에도 변수를 이용해서 치환이 가능하기 때문에, 사용도 편하고, 가독성도 훨씬 좋을것이다.

 

Posted by 헝개
개발팁2020. 2. 16. 22:30

HTML 에서 고정레이어라고 하면, 특정위치에 고정되어 있어, 화면 스크롤바의 이동과 상관없이

항상 같은 위치에 고정된 레이어를 말한다.

 

여기서, 다이나믹 고정레이어라고 하면, 필요할때 만 고정된다는 것을 말한다.

 

요즘 흔히 쓰이는 UI ( 또는 UX ) 구조이다.

예를 들면, G마켓의 상품페이지에보면

 

 

중간쯤의 버튼 바(bar) 가 보인다.  상세설명, 상품평, 상품문의, 교환/반품 버튼인데.. 이 부분이 컨텐츠의 특정위치를 차지하고 있다.

 

 

상품설명을 보기 위해, 스크롤을 내리면, 상단에 고정이 되며, 스크롤을 올리면, 다시 원래 위치에 표시된다.

 


샘플은, jquery 를 이용할 것이며, vue.js 도 이용할것이다.

F12 키를 눌러 DevTool 에서, 티스토리 블로그를 샘플 화면으로 해서 상단 header 영역을 가지고 작업할 것이다.

 

 

var script = document.createElement('script');
script.onload = function () {
    console.log('jquery loaded');
};
script.src = "https://code.jquery.com/jquery-latest.min.js";

document.head.appendChild(script);

var script = document.createElement('script');
script.onload = function () {
    console.log('vue loaded');
};
script.src = "https://cdn.jsdelivr.net/npm/vue/dist/vue.js";

document.head.appendChild(script);

 

 

우선, 티스토리 블로그에 jquery, vue.js 가 없으니, 동적으로 로딩한다.

위 코드를 F12 키를 누르면 나오는 DevTool 의 Console 에 입력하고 엔터를 치면, 동적으로 javascript 파일이 로딩된다.

 

실제 코드에서는 js 파일을 프로젝트에 포함하거나, 아래처럼 CDN 에서 가져다 쓰면 된다.

 

<script  src="http://code.jquery.com/jquery-latest.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

 

 

동적으로 js 파일 로딩이 끝났으면,

 

샘플 vue 객체를 만들것이다.

 

원리는, scrollY 값을 watch 해서, 값이 바뀌면, 고정레이어 처리를 할것이다.

 

1. header 의 height 값보다 작으면, 고정레이어 취소

2. header 의 height 값보다 크면, 고정레이어 처리

 

고정레이어 처리는 단순히, stylesheet 로 postion: fixed 만 설정해주면 끝는다.

 

scrollY 값은 window scroll 이벤트 발생시 넣어줄 것이다.

 

new Vue({
	el: 'body',

	data: function() {
		return {
			scrollY: 0
		}
	},

	watch: {
		scrollY: function(newVal, oldVal) {
			if(scrollY > $('#header').outerHeight()) {
				$('#header').css("position", "fixed");
				$('#header').css("z-index", 99999);
				$('#header').css("top", "4px");
				$('#header').css("width", "100%");
			}
			else {
				$('#header').css("position", "");
			}
		}
	},

	created: function() {
		let self = this;
		window.addEventListener('scroll', function(a,b) {
			self.$data.scrollY = window.scrollY;
		});
	}

});

 

위코드를 DevTool 에서 실행하고 나면, 이제 티스토리 블로그 상단이 다이나믹 고정레이어로 바뀌게 된다.

 

 

위의 vue 코드는 vue.js 사용법을 예를 들기 위한 코드로, 쓸데없이 코드가 길다.

실제 javascript 로만 작성하면, 단순하고, 심플해진다.

참고로 확인해보만 아래와 같다.

 

		window.addEventListener('scroll', function(a,b) {
			if(window.scrollY > $('#header').outerHeight()) {
				$('#header').css("position", "fixed");
				$('#header').css("z-index", 99999);
				$('#header').css("top", "4px");
				$('#header').css("width", "100%");
			}
			else {
				$('#header').css("position", "");
			}
		});

 

Posted by 헝개
개발팁2019. 3. 28. 14:41

ToggleButton 은 버튼의 눌림을 표현 하는 방법으로,

한번 누르면 눌림상태로, 다시 누르면, 튀어 나온 상태를 말한다.


[Winform]

 

System.Windows.Forms 에는 별도의 ToggleButton 이 없고, 일반 Button 은 ToggleButton 처럼 사용할 수 없다.

 

System.Windows.Forms 의 CheckBox 나 RadioButton 의 Appearance 속성을 이용하여, ToggleButton 으로 사용할 수 있다.

 

일단, Panel 위에 CheckBox 나 RadioButton 을 올려놓는다.

 

한 Container 위에서, CheckBox 는 여러개 선택이 가능하고, RadioButton 은 단 1개만 선택이 가능하다.

 

 

CheckBox 나 RadioButton Control의 속성을 보면,

Appearance 의 기본값 Normal 을 Button 으로 바꿔준다.

 

 

이제 버튼의 모양도 동일하게 보여지게 된다.

 

 

System.Windows.Forms
CheckBox

버튼의 눌림은 .Checked = true, 눌림해제는 .Checked = false 로 체크박스나 라디오버튼으로 그대로 사용하며,

화면에 보여지는 형식만 다르다는것을 알 수 있다.

 

 

[WPF]

 

WPF 에는 ToggleButton 컨트롤이 있어서, 바로 사용이 가능하다.

 

<ToggleButton Height="25" Content="Level 1" IsChecked="{Binding level1_checked, UpdateSourceTrigger=PropertyChanged}" Checked="ShowScore"></ToggleButton>

 

Winform 에서 Checkbox 로 토글버튼을 만든것과 동일한 결과를 얻을수 있다.

 

추가적으로, RadioButton 으로 컨테이너 또는 그룹 안에서 하나의 토글 버튼만 선택이 가능하게 하기 위해서는,

Winform 처럼 RadioButton 으로 구현하면 된다.

 

WPF 의 RadioButton 컨트롤의 Style 속성을 지정하여, 토글버튼으로 보여지게 된다.

 

Style="{StaticResource {x:Type ToggleButton}}"

 

<RadioButton Height="25" Content="Level 1" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level1_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 2" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level2_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 3" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level3_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 4" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level4_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 5" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level5_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>

 

WPF RadioButton 을 이용한 토글버튼

 

Posted by 헝개
개발팁2019. 3. 19. 14:42

[C#] 파일시스템의 폴더감시 FileSystemWatcher

 

System.IO.FileSystemWatcher

 

System.IO 에 속한, FileSystemWatcher 를 이용하면, 폴더내의 파일시스템의 변화를 감지할 수 있다.

 

여기서 말하는 파일 시스템은, 파일과 폴더를 모두 포함한다.

 

감지할 수 있는 이벤트는

 

Changed - 파일이나 폴더의 변경
Created - 파일이나 폴더의 생성
Deleted - 파일이나 폴더의 삭제
Renamed - 파일이나 폴더의 이름 변경

 


실제 코드는 아래와 같이 구성하면 된다.

 

 

private void Run_Watcher()
{
 this.m_FolderWatcher = new FileSystemWatcher();

 this.m_FolderWatcher.Filter = "*.*";
 this.m_FolderWatcher.Path = "C:\\Program Files\\";
 this.m_FolderWatcher.IncludeSubdirectories = true;

 this.m_FolderWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
 this.m_FolderWatcher.Changed += new FileSystemEventHandler(Watcher_OnChanged);
 this.m_FolderWatcher.Created += new FileSystemEventHandler(Watcher_OnChanged);
 this.m_FolderWatcher.Deleted += new FileSystemEventHandler(Watcher_OnChanged);
 this.m_FolderWatcher.Renamed += new RenamedEventHandler(Watcher_OnRenamed);
 this.m_FolderWatcher.EnableRaisingEvents = true;
}

 

private void Watcher_OnChanged(object sender, FileSystemEventArgs e)
{
 Console.WriteLine(string.Format("{0} {1}", e.FullPath, e.ChangeType.ToString()));
}

 

private void Watcher_OnRenamed(object sender, RenamedEventArgs e)
{
 Console.WriteLine(string.Format("{0} {1} to {2}", e.OldFullPath, e.ChangeType.ToString(), e.Name));
}

 

 

 

Filter 라는것은, 지정된 경로(Path) 아래에서도, 모든 파일(*.*) 을 다 감시 할지, 특정 파일(ex : *.txt) 만 감시할지를 지정하게 된다.

 

IncludeSubdirectories 는 경로(Path) 아래의 모든 하위폴더를 다 포함할지 여부이다.

 

NotifyFilter 는 파일이나 폴더의 변경형식을 선택할 수 있다.

 

다수의 형식을 선택하기 위해 bitwise or 연산자를 사용한다.

8가지중에 선택할 수 있다.

 

FileName = 1,
DirectoryName = 2,
Attributes = 4,
Size = 8,
LastWrite = 16,
LastAccess = 32,
CreationTime = 64,
Security = 256

 

 

Changed, Created, Deleted 는 모두 event 파라미터가 동일(FileSystemEventArgs) 하기 때문에, 하나의 이벤트 핸들러로 연결시켰다.
(각자 별도의 이벤트 핸들러를 만들어도 상관은 없다.)


FileSystemEventArgs 의 ChangeType 으로, 어떤 변화인지 찾아 낼 수 있다. (Created, Changed, Deleted, Renamed)

 

마지막으로,
EnableRaisingEvents = true

 

를 해줌으로써, 파일시스템의 감시가 활성화 된다.

 

감시를 중단하고 싶을때는,

EnableRaisingEvents = false

 

로 해주면 된다.

실제로, 특정 폴더를 감시하는 프로그램은 아래와 같이 나오게 된다.

 

 

 

 

한가지 팁으로,

 

특정 FTP 경로를 감시하고 있다가, FTP 로 업로드가 될 경우, 자동으로 배포 하는 프로그램을 만든다면,

어떤 이벤트를 사용해야 할까?

Created? Changed?

 

정답은 2개를 다 사용해야 한다.

 

우선, FTP 로 파일이 업로드 시작되면, Created 이벤트가 발생한다.

그리고, 파일이 업로드가 진행되면, Changed 가 발생하게 된다.

 

여기서 중요한것은 FTP로 파일 업로드가 종료된(파일이 모두 업로드된) 시점을 알아야 한다.

이를 위해, 파일을 Write 모드로 Open 해서, Open 이 된다면, 정상적으로 업로드가 완료된것으로 보면 된다.

 

 
private void Watcher_OnCreated(object sender, FileSystemEventArgs e)
{
 string ext = Path.GetExtension(e.FullPath);

 this.m_Wacher_CreatedFileName = e.FullPath;
}

 

private void Watcher_OnChanged(object sender, FileSystemEventArgs e)
{
 if (e.ChangeType != WatcherChangeTypes.Changed)
  return;

 if (this.m_Wacher_CreatedFileName != e.FullPath)
  return;

 

 // 쓰기모드로 오픈이 가능해야, 파일복사가 완료된것으로 본다.
 try
 {
  FileStream fs = File.Open(e.FullPath, FileMode.Open);
  fs.Close();
  fs.Dispose();
 }
 catch(IOException)
 {
  return;
 }
}

 

 

위 코드는 실제로 사용되는 코드이다.

 

 

Posted by 헝개
개발팁2018. 12. 26. 16:07


윈도우 OS 에서는





일정한 시간 (화면보호기 시간) 이 지나면 화면보호기(또는 바로 잠금화면)가 나타나기도 하고,

윈도우키 + L 키를 누르면, 화면잠금 기능이 바로 동작한다.

모니터상에 표시된 내용을 감추고(배경이미지를 노출하여), 로그인되어 있는 사용자의 비밀번호를 올바르게 입력해야만 잠금이 풀리게 된다.


웹에서는 자바스크립트만으로 이러한 기능을 구현할 수 있다.




구현할 기능은 아래와 같다.


1. 일정시간이 지나면, 잠금화면의 레이어가 현재 화면 위에 뜬다. (레이어 Show)

2. 또는, 특정키를 누르면 ( 여기서는 Ctrl + Alt + D 로 정의할 것이다.) 1번의 잠금상태로 전환된다.

3. 비밀번호를 올바르게 입력하면, 잠금화면이 해제 (레이어가 Hide) 

4. 브라우저의 탭을 이용하여, 같은 브라우저 세션으로 접근하더라도, 잠금 화면은 계속 유지되어야 한다. (잠금해제를 하지 않으면, 사이트를 이용할 수 없다. 또는 재로그인)


레이어 UI 는 아래와 같이 구현하였다. 사용자 개발 환경에 맞게 구현하면 되고, 예제에서는 Bootstrap 를 이용하였다.


DIV 태그는 BODY 태그 아래에 두면 된다. 파셜뷰(닷넷) 로 구현해도 된다.


class="hide" 로 인해, 평상시에는 화면에 노출되지 않다가, 잠금기능이 동작하면 (class='show'), 전체화면을 덮어버리고, 95% 로 opacity 를 채워서 기존 내용을 확인이 어렵게 하고, 로그인한 유저 이름을 표시하고, 비밀번호를 입력받게 된다.


<div id="screen-lock" class="hide" style="opacity:0.95;">

    <div class="panel panel-warning" style="width:500px;margin: 168px 0px 0px -250px;left: 50%;position: absolute;">

        <!-- begin panel-heading -->

        <div class="panel-heading">

            <h4 class="panel-title" data-i18n="message.screenlock_message">screenlock_message</h4>

        </div>

        <!-- end panel-heading -->

        <!-- begin panel-body -->

        <div class="panel-body">

            <!-- begin fieldset -->

            <fieldset>

                <!-- begin row -->

                <div class="row">

                    <!-- begin col-8 -->

                    <div class="col-md-10 offset-md-1">

                        <!-- begin form-group -->

                        <div class="form-group row m-b-10">

                            <label class="col-md-5 col-form-label text-md-right" data-i18n="label.user_name">사용자명</label>

                            <div class="col-md-6">

                                <input class="form-control" readonly value="@ViewBag.user_info.UserName">

                            </div>

                        </div>

                        <!-- end form-group -->

                        <!-- begin form-group -->

                        <div class="form-group row m-b-10">

                            <label class="col-md-5 col-form-label text-md-right" data-i18n="label.password">비밀번호</label>

                            <div class="col-md-6">

                                <input type="password" id="lock_Password" class="form-control" />

                            </div>

                        </div>

                        <!-- end form-group -->

                    </div>

                    <!-- end col-8 -->

                    <div class="icon" style="padding-top: 5px;position: absolute;padding-left: 35px;">

                        <i class="fa fa-unlock fa-5x" style="color:#919191;"></i>

                    </div>

                    <div class="panel-footer" style="width:100%;text-align: center;">

                        <button id="LockPasswordCheckAction" class="btn btn-success" data-i18n="label.unlock">unlock</button>

                        <button class="btn btn-white LogoutAction" data-i18n="common.logout">logout</button>

                    </div>


                </div>

                <!-- end row -->

            </fieldset>

            <!-- end fieldset -->


        </div>

        <!-- end panel-body -->

    </div>

    <!-- end panel -->

</div>




이제 스크립트로 일정시간이 지났을때, 특정키를 눌렀을때, 잠금 화면을 노출할것이다.


screenlock.initLock = function() {


    screenlock.timerHandle = window.setTimeout(function () {

        screenlock.setLock();

    }, 30 * 1000 * 60); // 30분 경과시 잠금처리


 

    // Ctrl + Alt + L 키를 누려면 강제 잠금 모드로 전환한다.

    $("body").keydown(function (event) {

        if (event.ctrlKey && event.altKey && event.keyCode == 76) {

            screenlock.forceLock();

        }

    });


}


// 강제잠금 기능

screenlock.forceLock = function () {

    // 잠금처리

    $.cookie(screenlock.cookieName, 1, { path: '/' });


    $('#screen-lock').removeClass("hide");

    $('#screen-lock').addClass("show");

}




공통으로 호출되는 VIEW화면(html) 에서, (닷넷에서는 레이아웃에서 호출하면된다.)


$(document).ready() 에서 initLock() 를 호출해 주면된다.


일단은, screenlock.setLock 과 screenlock.forceLock 은 똑같이 구현해 준다.


30분이 흐르거나, Ctrl + Alt + L 키를 누르면, 잠금 화면이 나올것이다.


$.cookie(screenlock.cookieName, 1, { path: '/' });


쿠키에 값을 쓰는 이유는,


한번 잠금 상태일경우, 페이지 새로고침이나, 브라우저의 TAB 으로 접근하였을때, 쿠키값을 참고하여, 계속 잠금 상태를 유지하도록 하기 위함이다. (아래에서 구현)



잠금해제는, AJAX 요청으로 서버에서 비밀번호 검증하고 검증이 통과 하였을경우,


아래 코드를 실행해주면된다.


$.removeCookie(screenlock.cookieName, { path: '/' });

window.clearTimeout(screenlock.timerHandle);

// $.removeCookie(screenlock.initCookieName, { path: '/' }); // initCookieName 는 아래에서 다시 설명할 것이다.


$('#screen-lock').removeClass("show");

$('#screen-lock').addClass("hide");



잠금 기능과 해제 기능이 구현이 되었다면, initLock 을 아래와 같이 변경해보자.



screenlock.initLock = function () {

    let isLocked = $.cookie(screenlock.cookieName) != undefined;


     if (isLocked) {

        $('#screen-lock').removeClass("hide")

        $('#screen-lock').addClass("show")

    }

    else {

        screenlock.resetLock();

    }


    // Ctrl + Alt + L 키를 누려면 강제 잠금 모드로 전환한다.

    $("body").keydown(function (event) {

        if (event.ctrlKey && event.altKey && event.keyCode == 76) {

            screenlock.forceLock();

        }

    });


    return isLocked;

}


screenlock.resetLock = function () {

    // timeout 시간을 재설정한다.


    if (screenlock.timerHandle != undefined) {

        window.clearTimeout(screenlock.timerHandle);

    }


    $.cookie(screenlock.initCookieName, (new Date()).getTime(), { path: '/' });


    screenlock.timerHandle = window.setTimeout(function () {

        screenlock.setLock();

    }, 30 * 1000 * 60);

}


여러개의 TAB 을 사용할경우, 하나의 탭에서 잠금상태로 전환되면, (쿠키에 저장하여) 잠금상태를 계속 유지하게 되고,


또한, 여러개의 TAB 에서 1개의 TAB 에서 계속 사용하고 있으면, 나머지 TAB 이 잠기는것을 방지하기 위해,


initCookieName 을 쿠키에 저장할 것이다. 쿠키값은 현재 시간의 timestamp 값이다.


이 값을 사용하여, window.setTimeout 으로 지정된 시간이 지났더라도, 잠기지 않도록 구현할 것이다.


이제, screenlock.setLock 함수를 아래와 같이 다시 재정의 한다.


screenlock.setLock = function () {

    let initTime = $.cookie(screenlock.initCookieName);


    // 쿠키에 저장된 init 날짜를 비교하여, 시간이 경과한경우만, 잠금처리한다.

    // 창을 여러개 띄어놓고 작업할경우, 사용하지 않는 창에서 무조건 락이 걸리는 문제를 해결하기 위해 사용.

    if (initTime != undefined) {

        let curTime = (new Date()).getTime();


        let diff_min = (curTime - parseInt(initTime)) / 1000 / 60 * 1.1; // 10% 오차범위로 처리한다. * 1.1


        //console.log('diff_min', diff_min);


        if (diff_min < screenlock.lockTime) {

            screenlock.resetLock(screenlock.lockTime / 5);  // 1/5 간격으로 다시 검사하도록 한다.


            return;

        }

    }


    // 잠금처리

    screenlock.forceLock();

}



위의 코드는 window.setTimeout 을 사용하여, 30분이 지나면, 잠금기능이 동작하게 되어 있다.



화면이 새로고침 된다면, 이 시간은 다시 셋팅 (resetLock 함수) 이 되기 때문에, 문제가 없는 것처럼 보인다.


하지만 SPA (Single Page Application) 이나, 한 화면안에서 AJAX 요청으로 오래 작업을 하는 경우가 있다면, 사용중에 화면이 잠기는 문제가 있다.


사이트를 사용중일때는 (윈도우 사용이랑 별개) 잠기지 않게 해주어야 한다.


페이지 새로고침없는 상태에서도, 사이트를 사용중이라는 것에 대한 정의가 필요하다.


ex) AJAX 요청이 있다거나, key 를 누른다거나..


key 누르는 이벤트에 대헤서는 아래코드를 initLock 에서 구현할 수 있다.


$("body").keydown(function (event) {

      screenlock.resetLock();

});


이를 이용하여, mouse move 또는 mouse down 이벤트에 대해서도 구현이 가능하다.


두번째는 ajax 요청에 대한 처리이다.

$.ajax 를 이용한다면, $.ajaxSetup 에서 공통 옵션과 콜백함수를 정의할 수 있다. 


$.ajaxSetup({


    beforeSend: function (xhr) {


            if (this.url.indexOf("PasswordCheck") < 0) // 화면잠금에서 비밀번호 체크할때는 제외한다.

                screenLock.resetLock(); // 서버로 AJAX 요청이 올때마다, 화면잠금시간을 연장한다.

    }

});


이제 모든 기능이 구현되었다.



위에서는 잠금상태를 위한 초기 timestamp 값이랑, 잠금상태값을 쿠키에 저장하였지만,

쿠키는 매번 서버로 전송되기 때문에, 불필요한 값이 서버에 전송되기도 한다.


SessionStorage 로 변경도 가능하다.


Posted by 헝개
개발팁2018. 12. 19. 10:29

자바스크립트의 데이터타입에 따라, 숫자는 0으로 초기화 하고, 문자열은 "" 으로 초기화 한다던가 할 수 있다.

 

json 객체는 그 안에, 숫자,문자,배열,함수,nested 객체 등의 여러 형식을 포함 할 수 있다.

 

 

jquery 의 $.each 를 통해서, 객체 개개의 항목에 접근할 수 있고,

 

또 자바스크립트의 typeof 를 통해, 각 항목의 데이터 타입을 알아낼 수 있다.

 

 

typeof 로 알아 낼 수 있는 데이터타입은 6가지가 있다.

 

"number", "string", "boolean", "object", "function", "undefined"


참고 : https://msdn.microsoft.com/ko-kr/library/259s7zc1(v=vs.94).aspx

 

jquery 에서도 typeof 와 비슷한 함수를 제공하는데,

 

 

$.type 을 이용하면, typeof 의 6가지 외에,

 

"null", "array", "date", "error", "symbol", "regexp" 등의 형식도 알 수 있다.

 

참고 : https://api.jquery.com/jQuery.type/

 

 

실제로, typeof 에서는 대부분 object 로 구분하는 형식에 대해서, $.type 은 상세한 형식을 알려주는 것을 볼 수 있다.

 

typeof([])
"object"

 

$.type([])
"array"

 

typeof(null)
"object"

 

$.type(null)
"null"

 

typeof(/test/)
"object"

 

$.type(/test/)
"regexp"

 

 

이제, $.each 와 $.type 을 이용하여, object 를 초기화 하는 함수를 만들 수 있다.

 

$.ObjectInitialize = function (obj) {


    let self = this;
    self.obj = obj;

 

    $.each(obj, function (item_name, item) {


        let datatype = $.type(item);

 

        if (datatype == "number")
            self.obj[item_name] = 0;   // 정수는 0 으로 초기화


        else if (datatype == "string")
            self.obj[item_name] = '';   // 문자는 '' 으로 초기화


        else if (datatype == "boolean")
            self.obj[item_name] = false;   // boolean 는 false 으로 초기화


        else if (datatype == "array")
            self.obj[item_name] = [];   // 배열은 [] 으로 초기화


        else if (datatype == "object")
            self.obj[item_name] = {};   // object는  {} 으로 초기화


    });

 

 

 

배열은 [] 으로 초기화 하고, object 는 {} 으로 초기화 하고 있다.

 

a = { x:10, y:"10", z: { k:10, l:"abcd" } };

 

$.ObjectInitialize(a);

 

 

 

 

위에서는 nested object 를 처리하지 않고 있지만, 중첩 객체도 다시, 초기화를 할 수 있다.

 

$.ObjectInitialize = function (obj, deep) {


    let self = this;
    self.obj = obj;

 

    $.each(obj, function (item_name, item) {


        let datatype = $.type(item);

 

        if (datatype == "number")
            self.obj[item_name] = 0;   // 정수는 0 으로 초기화


        else if (datatype == "string")
            self.obj[item_name] = '';   // 문자는 '' 으로 초기화


        else if (datatype == "boolean")
            self.obj[item_name] = false;   // boolean 는 false 으로 초기화


        else if (datatype == "array")
            self.obj[item_name] = [];   // 배열은 [] 으로 초기화


        else if (datatype == "object") {
            if (deep)   // deep 적용 : object 를 다시 $.ObjectInitialize 를 호출하여 초기화 한다.
                $.ObjectInitialize(self.obj[item_name]);
            else
                self.obj[item_name] = {};   // object는  {} 으로 초기화
        }


    });

 

 

deep 옵션에 따라 nested objet 도 초기화 하도록 변경을 했다.

 

 

 

a = { x:10, y:"10", z: { k:10, l:"abcd" } };

 

$.ObjectInitialize(a, true);

 

 

 

Posted by 헝개
개발팁2018. 12. 14. 16:02

Vue Component 간의 통신하기 위한 방법으로 Event 를 발행하고, Event 를 구독하는 방법을 이용할 수 있다.

 

Vue 이벤트에는 전역이벤트 또는 Global 이벤트가 있고, Component 이벤트 가 있다.

 

 

1. 전역이벤트

 

EventBus 를 통해, 발행하고, 구독할수 있으며,

 

블특정 컴포넌트에서 발행한 이벤트를 구독할 수 있는 형태이다.

 

 

공통의 script 파일안에 전역으로 아래와 같이 선언한다.

 

var EventBus = new Vue();

 

 

EventBus 는 단지, Vue 객체이다.

 

이벤트 발행

 

EventBus.$emit('이벤트명', 전달할 데이터...);

 

이벤트 구독

 

EventBus.$on('이벤트명', function(전달받을 데이터...) { } );

 

사용예제)

 

1. 이벤트발행시

EventBus.$emit("log_class_request", self, findTab.LogClass);

 

2. 이벤트 구독시

EventBus.$on('log_class_request', function (sender, LogClass) {
                self.filter_LogClass = LogClass;
            }); 

 

 

 

2. Component 이벤트

 

컴포넌트 이벤트는 일반적인 언어에서의 이벤트 사용방법과 동일하다.

 

특정 컴포넌트 안에서 이벤트를 발생시키면,

 

해당 컴포넌트를 호출한 부모객체에서 이벤트를 구독하는 방식이다.

 

이벤트 발행

 

this.$emit('이벤트명', 전달할 데이터...);

 

이벤트 구독

 

컴포넌트객체명.$on('이벤트명', function(전달받을 데이터...) { } );

 

 

 사용예제)

 

1. 이벤트발행시

this.$emit('hero_selected', selected_data);

 

2. 이벤트 구독시

self.$children.hero_select = Hero.Select.init(self);  // 컴포넌트 생성


self.$children.hero_select.$on('hero_selected', function (data) {
   ............

});

 

 

Posted by 헝개
개발팁2018. 12. 14. 15:39

data: {

     my_object: {}

} 


 

this.$data.my_object.col1 = "";

this.$data.my_object.col2 = "";

 

위와같이 중첩된 데이터에 컬럼을 추가 하게 되면, my_object  Observable 데이터이지만,

col1, col2  Observable 데이터가 아니다.



콘솔 로그로 확인해보면,



$data 를 확인해보면, my_object 는 observer 로 되어 있는걸 확인할 수 있다.

get my_object / set my_object 라는 property 가 자동으로 생성이 된것이다.


하지만, my_object 를 펼쳐보면, col1, col2 에는 아무것도 없다.


즉, col1, col2 는 vue 의 반응형 데이터로 동작하지 않는다는 것이다.




nested object 에 반응형 데이터를 추가하기 위해서, Vue.set 문장으로 바꾸어 보자.


Vue.set(this.$data.my_object, 'col1', '');

Vue.set(this.$data.my_object, 'col2', '');

 


다시, 콘솔 로그로 확인해보자.





my_object 안에 col1, col2 모두 observable 데이터로 등록이 된것을 확인할 수 있다.



 

Posted by 헝개
개발팁2018. 11. 7. 12:40

ajax 는 말그대로, 비동기 통신을 하기 위한, 도구이다.


jquery 에서는 $.ajax 또는 jQuery.ajax 라는 메서드를 통해서, 쉽게 ajax 를 이용할 수 있다.


내부적으로는 XMLHttpRequest 를 이용하고 있으며, 여러가지 편의 기능, option 을 제공하고 있다.


이중에서도, 매번 ajax 로 요청할때, 동일하게 사용하는 option / 처리가 끝나고 결과에(response) 대해 성공/실패/항상 처리해야 할일을 


$.ajaxSetup 에 미리 정의해 놓으면, 코드양도 줄여주고, 간결해지게 된다.



여기서 다룰 내용은, $.ajax 요청이 비동기(asyncronous) 요청이기 때문에, 이를 위해, promise 처리를 하는 방법이다.


기본적으로, $.ajax 요청의 결과를 받는 방법으로 많이 사용되는것이,


complete


success


error


ajax 요청이 완료되고 결과(response) 가 나오면, 우선적으로, complete 에 정의된, 함수 (또는 콜백함수) 가 실행이 된다.


그리고 나서, 결과가 http 오류인지 http 성공(200 ok) 인지에 따라, success / error 에 정의된 함수 (또는 콜백함수) 가 실행이 된다.


이 방식은, 비동기 요청(ajax) 를 하고 나서, 처리결과에 따라, 다음 작업을 진행해야 할때마다, 매번 callback 함수를 사용해야 하는 문제를 발생시킨다. (불편한 코드의 문제...)


다행이도, $.ajax 는 위의 함수(또는 콜백함수) 기능외에 Promise 패턴이 적용되어 있다. (jquery  의 deferred 로 구현됨)



$.ajax({ url : '/post/url', data: 'param, ... } ).then( function(result, status, responseObj) {

성공일때 내용...

}, function (result, status, responseObj) {

실패일때 처리할 내용...

});


위와 같이 코드를 작성할 수 있다, 뒤의 실패일때의 function 부분은 생략도 가능하다.


이 코드는 다시 아래와 같은 형태로 변경이 가능다.


$.ajax({ url : '/post/url', data: 'param, ... } ).done( function(result, status, responseObj) {

성공일때 내용...

}).fail(function (result, status, responseObj) {

실패일때 처리할 내용...

});


then() 은 done(), fail() 로 분리해서 처리할수 있다.

성공/실패 여부와 상관없이 항상 실행해야 할 코드는 always() 에 넣어줄 수 있다.

 $.ajax({ url : '/post/url', data: 'param, ... } ).done( function(result, status, responseObj) {


성공일때 내용...


}).fail(function (result, status, responseObj) {


실패일때 처리할 내용...


}).always(function (result, status, responseObj) {


항상 처리할 내용...


});



done() 이나 fail() 이 먼저 실행되고 난 후에 always 가 실행이 될것이다.



여기서 주의할 점은, $.ajax 의 promise 패턴은 http response 의 성공(200 ok) 이냐 실패냐에 기인한다는 점이다.

이는, 논리적인 성공 실패가 아니라, 물리적인 성공/실패를 의미하는 것이다.




웹서버에서 오류가 났을때, http 오류가 아니라, 적당한 오류 코드 와 오류 메세지를 response 해준다면, 어떨까?


그렇다면, $.ajax 는 이를 성공으로 간주하게 되어, 성공(done) 에서 논리적인 오류인지를 체크해야 한다.




이를 간소화 하기 위해서 아래와 같이 $.ajax 에 또다른 Promise 패턴을 적용한 래퍼함수(Wrapper) 를 만들 수 있다.


jquery 에서는 promise 패턴을 구현하기 위해, $.Deferred 라는 것을 제공하고 있다.


(실제 promise 패턴을 이용하기 위해 Promise 객체가 있지만, IE 에서는 지원하지 않기때문에, 호환성을 위해, $.deferred 를 사용했다.)



성공, 실패에 대해서는, resolve / reject 함수를 제공하며, 결과는 promise() 를 리턴해주면 된다.


$.ajax.promise = function (options) {

    var deferred = $.Deferred();


    $.ajax(

        options

    ).done(function (result, status, responseObj) {

        if (!result.bSuccess)    // failed

        {

            deferred.reject(result, status);

            return;

        }


        //data = JSON.parse(result.sJsonData);

        data = ( result.sJsonData == null || result.sJsonData == "" ) ? null : JSON.parse(result.sJsonData);


        deferred.resolve(result, data);

    }).fail(function (result, status, responseObj) {

        deferred.reject(result, status);

    });


    return deferred.promise();

}



이 코드의 전제는 json 데이터를 받는다는 점이고, result 에 bSuccess 라는 속성이 존재한다는 점이다.


$.ajax 의 래퍼함수인 $.ajax.promise 를 이용하여, 위의 $.ajax promise 이용 예를 다시 호출해보면 아래와 같다.


 $.ajax.promise({ url : '/post/url', data: 'param, ... } ).done( function(result, data) {


성공일때 내용...


}).fail(function (result, status, responseObj) {


실패일때 처리할 내용...


}).always(function (result, status, responseObj) {


항상 처리할 내용...


});



이제, 위 코드의 성공/실패 코드는 물리적인 http 오류외에도, 논리적인 오류에도 대응되는 코드로 변경이 된다.


위 코드는 단일 ajax 코드를 실행하는 방법이고, jquery 에서는 여러개의 promise 패턴을 처리하는 방법으로,


$.when 이라는 함수를 체공한다.



var pr1 =  $.ajax.promise({ url : '/post/url', data: 'param, ... } );

var pr2 =  $.ajax.promise({ url : '/post/url', data: 'param, ... } );

var pr3 =  $.ajax.promise({ url : '/post/url', data: 'param, ... } );


$.when(pr1, pr2, pr3).done(function() {

      모두 성공일때...

});

비동기 메서드이기 때문에, 위의 3줄은 같은 시기에 실행이 되지만, 결과는 제각각 다르게 나올것이다.

하지만, 모든 실행이 끝나고 나서, 처리해야할 내용이 있을때,

done() 함수에 넣어주면 된다.


$.when 도 promise 패턴으로 구현되어 있기 때문에,


done / fail / then 모두 사용가능하다.



Posted by 헝개
개발팁2018. 11. 7. 11:58

자바스크립트에서 다국어 지원을 위한, 라이브러리로 i18n 을 사용하다면,


다국어 text 정의는, 언어 key 와 언어 text 쌍의 json 파일로 관리가 된다.


i18n.t('key.key2.key3')


특정 key 를 현재 언어의 다국어 text 로 변환할때, 위와 같이 javascript 에서 사용하거나,


<span data-i18n='key.key2.key3'></span>


위와 같이 태그 안에서 직접 key 를 호출할 수 있다.



만약, 현재언어의 key.key2.key3 라는 키가 없다면, 대체 text 가 출력되거나, 기본 언어(default language) 의 key 를 찾아서, text 를 출력하게 된다.



누락된 key 가 있는지, 각 언어마다 빠진 데이터가 있는지를 찾아내기는 힘든일이다.


이때, 누락된 데이터가 나오면, 자동으로 알려준다면, 얼마나 쉽게 빠진 text 를 추가할 수 있겠는가..


i18n의 sendMissing 이라는 기능을 이용하면, 쉽게 누락데이터를 관리할 수 있다.



i18n 초기화 함수인 $.i18n.init 의 option 으로 아래와 같은 코드를 작성하면 된다.



        sendMissing: true,              // 번역데이터가 없을때 서버에 전송한다.

        sendMissingTo: 'fallback',

        resPostPath: '/System/I18nMissingKey_Insert'    // 번역데이터가 없을때 서버에 POST 로 넘겨주는 URL


위의 코드 의미는, 누락데이터가 발생했을떼, 웹서버의 /System/I18nMissingKey_Insert 주소로 POST (포스팅) 을 하게 된다.


웹서버는 닷넷이든 자바이든 상관없이, form 방식의 post 데이터를 받아서 처리하면 된다.


실제 서버로 전송되는 데이터는 name / value 의 form data 이고, 아래와 같은 형태이다.


key1.key2.key3=기본text


FORM 데이터의 name 값이 정해진 값이 아니라, key 값을 그대로 전송하기 때문에, form 객체에서 첫번째 항목을 읽어서 처리하면 되겠다.


실제 ASP.NET MVC 에서 Form ModelBinder 로 받아서 첫번째 항목을 DB 에 저장하는 소스이다.

사용하는 언어에 맞게 코딩하면 된다.



        [HttpPost]

        public JsonResult I18nMissingKey_Insert([ModelBinder(typeof(FormRequestModelBinder))] JObject reqParam)

        {

            dynamic param = new

            {

                TKey = ((Newtonsoft.Json.Linq.JProperty)(reqParam.First)).Name, // Request 값중에 첫번째 값을 가져온다.

                DefaultValue = reqParam[((Newtonsoft.Json.Linq.JProperty)(reqParam.First)).Name].ToString(),

                LangCode = this.GetCurrentLang()

            };


            SpotResult result = (new Biz_I18nMissingKey()).Insert(param);


            return Json(result);

        } 

 



아래 화면은 실제 DB 에 저장된, 누락데이터를 조회하고, 등록하는 기능이다.

각 언어별 json 파일로 분리해서 저장하고 있다.




위 코드는 누락된 i18n 데이터만을 관리하고 있지만,


실제, i18n 으로 번역 key / text 를 각각의 json 파일로 관리하는것은 쉽지가 않다.


이를 편하게 관리하기 위해, 각각의 json 파일을 merge 해서, 하나의 datatable 로 관리하도록 한 화면이다.




Posted by 헝개