잊지 않겠습니다.

angular2 form

angularjs2 2016. 9. 13. 14:03

angular2 form

기존 angularjs에서 문제가 되었던 form이 크게 향상되었습니다. 기존 form의 문제점은 다음과 같습니다.

  1. form의 생성시기를 알 수 없습니다. div tag를 통해서 생성되는 ng-form은 생성시기를 알 수 없기 때문에, form을 검사하기 위해서는 null error를 꼭 check하는 것이 좋습니다.
  2. form error text가 html에 담긴다. form에서 발생되는 error를 표시하는 방법이 모두 html에 기술되어야지 됩니다. 이에 대한 구현의 문제는 html이 과도하게 길어지는 문제가 발생하게 되고, ng-if의 남발이 발생하게 됩니다.

angular2는 NgForm의 제어 영역을 component로 이동시켜서 기존의 html에서 제어되는 것이 아닌 component에서 제어되는 것으로 구현을 변경하였습니다. 물론, 기존과 같은 패턴 역시 사용가능합니다.

Form Validation - Old Pattern

기존 ngForm을 이용하는 방법과 거의 유사합니다.

<form #heroForm="ngForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" [(ngModel)]="hero.name" #name="ngModel" class="form-control" required minlength="4" maxlength="24"> <div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger"> <div [hidden]="!name.errors.required"> Name is required </div> <div [hidden]="!name.errors.minlength"> Name must be at least 4 characters long. </div> <div [hidden]="!name.errors.maxlength"> Name cannot be more than 24 characters long. </div> </div> </div>
export class HeroFormTemplate1Component { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; active = true; onSubmit() { this.submitted = true; } addHero() { this.hero = new Hero(42, '', ''); } }

각 form의 이름을 이용하고 [(ngModel)]을 이용해서 model과 값을 binding시켜 사용합니다. 여기서 주의할 점은 #name입니다. 이는 꼭 ngModel을 적어줘야지 되며, ngModel을 이용해서 heroForm과 binding이 되게 됩니다.

이 code의 최고 문제는 html에 error message가 그대로 노출된다는 점입니다. 이를 해결해주기 위해서는 다음과 같이 구성합니다.

Form Validation - new Pattern

새로운 패턴에서 확인할 점은 NgForm의 생성과 Error Message의 관리 포인트가 Component로 넘어왔다는 점입니다. 먼저 Component에서 FormBuilder를 Import합니다.

import { FormGroup, FormBuilder, Validators } from '@angular/forms';

그리고, ngOnInit에서 FormGroup을 생성시켜줍니다.

constructor(private fb: FormBuilder) { } ngOnInit(): void { this.buildForm(); } buildForm(): void { this.heroForm = this.fb.group({ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), ] ], 'alterEgo': [this.hero.alterEgo, [ Validators.required ]], 'power': [this.hero.power, Validators.required] }); this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); // (re)set validation messages now }

FormBuilder.group은 각 Form element name에 따른 Validators를 갖습니다. Form Validation 조건들을 각 Component에서 처리가 가능해지는 것입니다. 또한 각 error에 대한 message 처리 역시 Component에서 가능하게 됩니다. 이는 위 코드 중 onValueChanged에서 처리 가능합니다.

onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm; for (const field in this.formErrors) { this.formErrors[field] = ""; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + " "; } } }
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" formControlName="name" required> <div *ngIf="formErrors.name" class="alert alert-danger"> {{ formErrors.name }} </div> </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" id="alterEgo" class="form-control" formControlName="alterEgo" required> <div *ngIf="formErrors.alterEgo" class="alert alert-danger"> {{ formErrors.alterEgo }} </div> </div> <div class="form-group"> <label for="power">Hero Power</label> <select id="power" class="form-control" formControlName="power" required> <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> <div *ngIf="formErrors.power" class="alert alert-danger"> {{ formErrors.power }} </div> </div> <button type="submit" class="btn btn-default" [disabled]="!heroForm.valid">Submit</button> <button type="button" class="btn btn-default" (click)="addHero()">New Hero</button> </form>

CustomeValidation 처리

CustomValidation의 경우에는 특수한 함수 형태를 반환해야지 됩니다. ValidatorFn을 반환하는 함수를 작성하고, 그 함수를 FormGroup.group 함수에 Binding시켜주면 됩니다.

import { ValidatorFn } from '@angular/forms'; forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): { [key: string]: any } => { const name = control.value; const no = nameRe.test(name); return no ? { 'forbiddenName': { name } } : null; }; } this.heroForm = this.fb.group({ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), this.forbiddenNameValidator(/bob/i) ] ], 'alterEgo': [this.hero.alterEgo, [ Validators.required ]], 'power': [this.hero.power, Validators.required] });

Summary

angular2에서의 FormValidation은 많은 발전을 가지고 왔습니다. 가장 큰 변화는 Component에서 Error Message 및 조건들을 관리하기 편해졌다는 것입니다. 기존 html에서 처리하는 방법 역시 가지고 있지만, 개인적으로는 최대한 사용하지 않는것이 좋아보입니다.

또한, 다국어 처리에 있어서도 더 나은 방법이 될 수 있습니다.

Posted by Y2K
,
directive는 test하기에 가장 어려운 객체입니다. VIEW의 html element이 특정 HTML로 변경되는 process를 통과해야지 되며, 이 HTML에 event가 발생했을 때의 동작을 모두 테스트해야지 되기 때문입니다. 다음과 같은 directive code를 sample로 한번 알아보도록 하겠습니다.
angular.module('fmsmobilewebApp').directive('codeDropdown', function (ClassificationService, $timeout) {
  return {
    template: '<select class="ds-select" ng-model=ngModel ng-options="o.id as o.name for o in optionData" ></select> <button ng-click="load()">로드</button>',
    restrict: 'E',
    scope: {
      ngModel: '=',
      codeType: '@',
      parentCodeId: '@',
      styleValue: '@',
      emptyData:'@'
    },
    link: function postLink(scope, element, attrs) {
      scope.optionData = [];

      var init = function() {
        ClassificationService.getList(scope.codeType, scope.parentCodeId, listup);
      };

      scope.load = function() {
        init();
      };

      var listup = function(codes) {
        var items = [];
        if(scope.emptyData) {
          items.push({ text: scope.emptyData, value: ''});
        }
        for(var i = 0 ; i < codes.length ; i++) {
          items.push(codes[i]);
        }
        if(scope.ngModel == null || scope.ngModel == '') {
          scope.ngModel = items[0].value;
        }
        if(scope.styleValue != null && scope.styleValue != '') {
          $(element).find('select').attr('style', scope.styleValue);
        }
        scope.optionData = items;
        $timeout(function() {
          scope.$apply();
        });
      };
    }
  };
});

위 directive는 comboBox과 button을 표시하는 directive입니다. button을 click하면 load method가 호출되는 구조로 되어 있습니다.
scope의 형태를 한번 봐주시길 바랍니다.

이 directive는 다음과 같은 특징을 가지고 있습니다.

* Service에 대한 dependency를 갖는다.
* VIEW(html) 요소를 directive의 template으로 치환한다.
* controller의 scope의 value에 대한 bi-direction을 갖는 scope variable이 존재한다. (ng-model)
* button에 대한 외부 event가 존재한다.
  <button ng-click="load()">로드</button>

이와 같은 directive를 테스트 하기 위한 방법에 대해서 알아보도록 하겠습니다.

Service에 대한 dependency

directive는 생성되는 시점이 controller나 service와는 조금 다릅니다. controller는 URL의 호출시에 controller를 생성시키지만, directive의 경우에는 page의 loading이 다 마쳐진 이후에 $compile 과정을 통해서 directive가 생성되게 됩니다. 따라서, directive를 사용하기 위해서 필요한 html element, css의 선언이전에 dependency를 inject를 해야지 됩니다.

따라서, beforeEach에서 provider를 얻어내, service를 재정의해서 inject할 필요가 있습니다. 다음은 위 directive의 ClassificationService를 재정의하는 code입니다.

  beforeEach(module('fmsmobilewebApp', function($provide) {
    var classificationService = {
        getList: function(codeType, parentCodeId, func) {
          var codes = [];
          for(var i = 0 ; i < 20 ; i++) {
            codes.push({ id: 'id' + i.toString(), name: 'name-' + i.toString() });
          }
          if(func) {
            func(codes);
          }
          return true;
        }
      };
    $provide.value('ClassificationService', classificationService);
  }));

provider에 사용되는 ClassificationService를 setting해주는 형태입니다. 기본적으로 unit test는 whitebox test기법이기 때문에, 우리가 사용하는 service와 그 method에 대한 정보는 아는 것으로 가정하기 때문에 최소한으로 필요한 service code만을 재정의해서 만들어주는 것이 좋습니다.

VIEW(html)의 치환

지금까지 모든 angularJS의 unit test는 test 대상이 되는 객체를 얻어와서 test를 진행했습니다. 그렇지만 directive의 경우에는 좀 다릅니다. directive의 생성은 html이 있어야지 됩니다. html을 치환하는 과정을 compile이라고 합니다. 다음은 compile을 진행하는 과정을 test code에서 나타냅니다.

  it('code dropdown test code', inject(function ($compile) {
    element = angular.element('<code-dropdown ng-model="ngModel" code-type="system" parent-code-id="0801"></code-dropdown>');
    element = $compile(element)(scope);
    // display directive's compiled html
    console.log(element.html());
  });

눈으로 html을 보게 되면, 정상적인 test code라고 할 수 없습니다. 다음과 같이 만들어져야지 되는 html element의 숫자를 확인하는 것이 좋습니다.

    element = angular.element('<code-dropdown ng-model="ngModel" code-type="system" parent-code-id="0801"></code-dropdown>');
    element = $compile(element)(scope);
    //sync parent scope and directive scope
    scope.$digest();
    expect(directiveScope.optionData.length).not.toBe(0);
    expect(element.find('option').length).toBe(20);

scope의 binding

directive의 scope의 value는 크게 두가지 type이 있습니다. bi-direction으로 동작하는 scope의 property와 내부에서만 사용되는 isolateScope가 바로 그것입니다. 기본적으로 directive는 scope의 type이 =으로 지정된 경우를 제외하고 isolateScope를 생성하게 됩니다. directive내부의 scope를 접근하기 위해서는 기존 test code에서 사용한 scope로는 접근이 불가능합니다. 이 부분이 기존의 controller code와의 가장 큰 차이가 됩니다.

directive의 scope를 얻어내는 test code는 다음과 같습니다.

    element = angular.element('<code-dropdown ng-model="ngModel" code-type="system" parent-code-id="0801"></code-dropdown>');
    element = $compile(element)(scope);
    //sync parent scope and directive scope
    scope.$digest();

    //access directive scope
    expect(element.isolateScope).toBeDefined();
    expect(element.isolateScope()).not.toBeNull();

    var directiveScope = element.isolateScope();
    expect(directiveScope.ngModel).toBe('id0');

    //change parent scope value
    scope.ngModel = 'id2';
    scope.$digest();
    expect(directiveScope.ngModel).toBe('id2');

    directiveScope.ngModel = 'id1';
    directiveScope.$digest();
    expect(scope.ngModel).toBe('id1');

마지막 6 line을 주의깊게 볼 필요가 있습니다. parent scope value와 bi-direction 되어 있기 때문에 parent scope의 값을 변경하거나, directive의 scope 값을 변경하는 것 모두 양쪽에 영향을 주고 있는 것을 볼 수 있습니다.

button과 같은 event가 binding 되어 있는 경우

button과 같은 사용자의 event가 binding이 되어 있는 경우에는 event에 대한 처리를 code로 구성할 수 있습니다.
다음 test code를 보는 것이 설명이 더 빠릅니다.

    //Click Event
    element.find('button')[0].click();
    expect(directiveScope.optionData.length).not.toBe(0);
    expect(element.find('option').length).toBe(20);

element에서 event를 handling시킬 button을 찾아낸 후, click event를 전달할 수 있습니다.

추가: 외부 html을 template으로 사용하는 경우

directive를 작성할 때, template을 html 파일로 따로 관리하는 경우가 많습니다. directive의 크기가 너무 크거나, html로 관리하는 것이 관리 focus상 유리할 때 종종 이런 방법을 택하지요. 외부 html을 사용하는 경우, directive code는 다음과 같이 구성됩니다.

angular.module('fmsmobilewebApp').directive('loading', function () {
  return {
    templateUrl:'templates/loading.html',
    restrict: 'E',
    require: '^ngModel',
    transclude: true,
    scope: {
      ngModel: '='
    }

  };
});

여기서 문제가 되는 것은 templateUrl의 위치는 yo기준으로 app folder위치의 상대값입니다. 그렇지만 우리가 grunt test를 통해서 테스트를 실행하는 path는 ../app 이 됩니다. 따라서, 저 url은 절대로 로드할 수 없는 위치가 됩니다. 이 경우, 다음과 같은 karma.conf.js 수정이 필요합니다.

먼저, loading 해야지 되는 html을 files에 추가합니다.

    files: [
      // bower components
      'app/bower_components/jquery/dist/jquery.js',
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/bower_components/angular-cookies/angular-cookies.js',
      'app/bower_components/angular-sanitize/angular-sanitize.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-touch/angular-touch.min.js',
      'app/bower_components/lodash/dist/lodash.min.js',
      'app/bower_components/mobile-angular-ui/dist/js/mobile-angular-ui.min.js',
      'app/bower_components/restangular/dist/restangular.min.js',
      'app/bower_components/angular-local-storage/angular-local-storage.min.js',
      // 3rd party library
      'app/library/kendo/js/kendo.all.min.js',
      // Test target
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      // Test code
      'test/mock/**/*.js',
      'test/spec/**/*.js',
      // Template Files
      'app/templates/*.html'
    ],

다음에, preprocessor 단계에서 html로 로딩된 항목을 javascript로 변경시켜, cache에 저장합니다.

    preprocessors: {
      'app/templates/*.html': 'html2js'
    },

이제 저장된 html은 $templateCache를 통해서 얻어올 수 있게 됩니다. test code의 beforeEach에서 다음과 같이 처리해줍니다.

  beforeEach(inject(function($templateCache) {
    template = $templateCache.get('app/templates/loading.html');
    expect(template).not.toBeNull();
    $templateCache.put('templates/loading.html', template);
  }));

$templateCache에서 먼저 로딩된 template을 읽어주고, directive에서 사용하는 templateUrl 값에 그 값을 넣어주면, directive가 $compile될 때 templateCache값을 참고해서 처리하게 됩니다.

Summary

지금까지 angularJS를 이용한 web application의 unit test 방법에 대해서 알아봤습니다. 약간의 정리를 하자면, 다음 원칙을 지켜가면서 test code를 작성하면 좋습니다. 이는 개발 code 작성도 비슷한 느낌이 될 수 있을것입니다.

  1. filter, non HTTP service 부터 test 및 code를 작성합니다.
  2. HTTP service의 경우, 호출되는 URL과 parameter에 대한 test를 작성합니다.
  3. HTTP service의 경우, input이 json type인 경우에는 input data에 대한 validator를 따로 작성해서 test하는 것이 좋습니다.
  4. Controller는 사용자 scenario를 정해서 TDD(test driven development)로 작성하는 것이 좋습니다.

제일 중요한 것은 unit test는 white box test입니다. 개발자가 내용을 다 안다는 것을 가정하고 있습니다. 최종적인 UI에 대한 test는 반드시 하는 것이 좋습니다. unit test는 UI에 대한 통합 테스트를 진행하기 쉽게 좀더 신경쓸 내용들을 줄이는 용도로 사용하는 것이지, UI에 대한 통합 테스트를 대치할 수는 없습니다.

모두들 Happy coding 하세요.


Posted by Y2K
,

angularJS를 이용한 single document web application을 개발 할 때, 내용에 대한 정리가 필요해서 한번 정리해봅니다.

angularJS MVVM 이해하기


(그림의 출처는 http://www.mimul.com 입니다.)

Spring MVC를 이용하고 있다면, MVC의 개념에 대해서는 잘 이해하고 있을겁니다. 개인적으로 javascript framework에서 이야기하는 MVVM이라는 것은 약간의 marketing 용어라는 느낌이 강하게 들기 때문에, 거의 기존의 MVC와 동일하게 생각하면 됩니다.

Model

데이터 관리 및 통신등의 역활을 담당합니다. 이는 Spring MVC를 이용한 개발시에 표현되는 @Service와 동일하게 생각해도 됩니다. Service와 Factory로 구성이 되며 이는 singleton 여부와 동일하게 생각하면 됩니다. Service는 singleton으로, factory는 prototype으로요.

주로 사용되는 것은 REST API를 호출하는 call layer로 구성이 되는 경우가 많습니다. 이에 대한 결과에 대한 caching과 logic을 처리하는 것이 주가 되는데 개인적으로 추천을 한다면 Restangular module을 사용하는 것이 보다 더 편하고 좋은 코드 가독성을 가지고 올 수 있습니다. (https://github.com/mgonto/restangular)

Controller

DOM event handling과 $scope를 이용한 DataBinding 역활을 담당합니다. Spring MVC에서의 Controller와 Model의 역활이 합쳐져 있다고 이해하시면 편합니다. @Controller : Controller class, Model: $scope 로 생각하시면 좋습니다. DataBinding과 DOM event handling을 모두 가지고 있기 때문에 $scope의 코드의 method 들은 최대한 간결하고 명확한 naming이 좋습니다.

View

HTML 영역입니다. View의 표현이 중복되는 것이 많아진다면 directive를 만들어서 처리를 하면 HTML의 재활용도를 높일 수 있습니다. 다만, 저같이 기억력이 안좋은 사람에게는 사용할 때마다 directive의 code를 다시 한번 확인해야지 되는 단점을 가질 수 도 있습니다.;

angularJS를 이용한 개발 시 유의점

Memory leak

이 상황은 single document application을 구성할 때, 가장 주의해야지 되는 내용입니다. 지금까지 javascript로 개발하는 상황과 많은 차이를 가지고 오는 부분이 이 memory leak 부분이라고 생각합니다. 이 부분에 대한 주의가 필요합니다. memory leak이 발생되는 상황임을 알 수 있는 방법은 chrome의 profile에서 memory snapshot을 얻어서 확인 할 수 있습니다. 일단 3가지 질문을 통해서 javascript의 memory leak이 발생되고 있는지를 확인 할 수 있습니다.

page가 너무 많은 memory를 사용하는 경우

너무나 많은 memory를 사용하고 있다면 chrome의 timeline memory view와 chrome task manager를 통해서 확인 할 수 있습니다. 경험적으로는 사용하지 않는 DOM을 계속해서 가지고 있는 경우. (unbind DOM, fragmented DOM)이 계속해서 생성이 되는 경우에 주로 이런 현상이 발생됩니다. 더 이상 필요없게 되는 DOM에 대한 event listener를 unbind 시키는 것이 매우! 중요합니다.

page가 memory leak에서 자유로운지

Chrome Profile에서 볼 수 있는 Object allocation tracker를 이용해서 javascript object의 할당을 확인해보는 것이 좋습니다. snapshot을 이용해서 GC가 일어날 때와 일어나지 않았을 때의 memory 차이를 알아보고 page가 이동이 된 후도 계속 남는 memory가 많은지를 확인해봐야지 됩니다.

Forcing GC가 자주 일어나는지

chrome timeline memory view를 이용하면 GC가 얼마나 자주 일어나는지를 확인할 수 있습니다. 이것이 자주 일어난다면 너무나 많은 객체들이 생성되고 있다는 것을 의미합니다. 객체를 너무나 많이 생성하고 있는 것에 대한 고민이 필요합니다.

위의 상황들중에서 가장 문제가 될 수 있는 것은 javascript의 fragmented DOM 또는 unbinded DOM에 대한 event listener로 인한 memory leak 입니다. 이는 jQuery UI를 angularJS single document application에서 사용하는 경우, 자주 발생할 수 있는데, 이는 각각의 component 들에 대한 event binding이 정상적으로 clear 되지 못해서 발생되는 문제입니다. 이 부분에 대해서는 다음 Blog post를 참조해보세요.

http://rinat.io/blog/angularjs-and-jquery-plugins-memory-leaks

이러한 unbinded DOM의 event listener를 해지하기 위해서는 $destroy event의 처리가 필요합니다.
다음과 같은 code snippet가 각 controller에서 위치해야지 됩니다. 이 부분은 사용되는 javascript UI framework 등에서 어떻게 memory에서 모든 객체들을 제거할 수 있는지에 대한 document를 참고하시는 것이 좋습니다. (jquery 기반의 UI component는 destroy() method를 갖는 것이 일반적입니다.)

$scope.on('$destroy', function() {
    var dateTimePicker = $scope.element('#dateTimePicker');
    if(dateTimePicker != null) {
        dateTimePicker = dateTimePicker.datetimepicker();
        dateTimePicker.destroy();
    }
});

또한, $destroy에서는 controller에서 사용되고 있는 모든 timer들의 해재역시 처리해주는 것이 좋습니다.

Directive의 올바른 이해와 사용

Directive를 올바르게 사용하는 것은 매우 중요한 문제입니다. 가장 우선적으로, Directive의 scope의 정의를 정확히 아는 것이 중요합니다. Controller의 $scope와 독립적인 scope가 생성 되나, 양방향 또는 단방향으로 controller와 통신을 하는 것이 가능합니다.

예를 들어, scope를 다음과 같이 선언할 수 있습니다.

    scope: {
      title: '@',
      parentCodeId: '@',
      data: '@',
      ngModel: '=',
      text: '=',
      name: '@',
      nullItem: '@',
      styleValue: '@'
    },

title, parentCodeId, data, name, nullItem, styleValue의 경우에는 attribute를 이용한 값 설정과 동일합니다. directive의 scope를 변경하는 것으로 controller가 변경되지 않습니다. 그렇지만, ngModel, text와 같이 ‘=’로 선언된 내용은 다릅니다. ‘=’로 선언된 내용은 directive를 통해서 수정된 값이 controller의 값을 변경시키게 됩니다.

이러한 양방향 DataBinding을 이용하게 되는 경우, Directive에서 event를 호출할 필요가 거의 없습니다. 최대한 event를 호출해서 처리하는 코드양을 줄이는 것이 속도면에서 유리합니다.

DataBinding의 처리

angularJS에서의 DataBinding은 $scope의 모든 변수에 대해서 이전의 값과 현재의 값을 비교하여, 값이 다른 경우 DOM을 갱신하는 방식입니다. $scope의 변수의 값을 비교하는 타이밍은 아래와 같습니다.

  • scope.$apply()가 호출될 때.
  • DOM event가 발생될 때. (onchange, onclick etc…)
  • http 응답이 발생된 경우. ($http, $resource)
  • url이 변경되는 경우
  • $timeout event가 발생되는 경우

이 부분에 대한 개선은 다음과 같이 처리하는 것이 좋습니다.

기본적인 angularJS module의 이용

jQuery와 angularJS를 혼용해서 사용하게 되는 경우가 많은데, 위와 같은 문제 때문에 최대한 angularJS의 모듈들을 사용하는 것이 좋습니다. 가장 좋은 것은 jQuery method를 사용하지 않는것이 좋습니다. 예를 들어 jQuery의 기본 $.ajax는 가장 자주 사용되는 http ajax 통신 method입니다. 그렇지만, 이 코드를 사용하는 경우, scope의 DOM update가 되지 않습니다. 따라서, $http service를 이용하는 것이 좋습니다.

또한, window의 경우에도 $window, document는 $document를 이용하는 것이 좋습니다. 그리고, WebSocket을 이용하는 경우도 역시 마찬가지의 문제가 발생됩니다. WebSocket의 promise를 반환하고 그 값을 얻어낼 때, $apply() method를 호출해주어야지 됩니다.

$watch 는 최대한 가볍게

$watch method는 잦은 호출이 발생될 수 있는 code입니다. 다음과 같은 처리가 반드시 필요합니다.

slow — Anything faster than 50ms is imperceptible to humans and thus can be considered as "instant".
limited — You can't really show more than about 2000 pieces of information to a human on a single page. Anything more than that is really bad UI, and humans can't process this anyway.

TIP

이 부분은 개인적으로 사용해보니 좋았던 TIP들입니다. 다른 분들은 다르게 느끼실 수 있는 부분입니다.

Parent Controller를 이용한 Controller common method의 공유

공통적으로 사용하고 싶은 method들을 MainController에 넣어두면 Controller를 작성하기가 매우 편해집니다. 예를 들어 back() 처리라던지, Confirm/Alert 과 같은 알람기능이나 resize에 대한 기본적인 처리와 같은것을 넣어두면 매우 유용하게 사용가능합니다. 이를 지원하기 위해서 기존 ng-view 상단에 ng-controller를 지정해서 처리해주면 됩니다.

    <div ng-controller="MainCtrl">
      <section ng-view="" onload="completedRender()"></section>
    </div>
angular.module('fmsPublicWebApp').controller('MainCtrl', function ($scope, $timeout, $window, Codeservice, $location) { 
    $scope.showGlobalMessage = function(type, title, message) {

    }
});
$http, $resource보다는 Restangular를 사용

bower를 이용해서 쉽게 추가 가능한 Restangular를 사용하면 좋은 가독성을 갖는 코드를 짜는 것이 가능합니다. 약간 옆으로 길어지는 코드 pattern이 생기긴 하지만, 직관적인 호출이라는 강점을 가질 수 있습니다.

jQuery Library를 사용할 때는 주의할것!

최대한 jquery에 의존하지 않고 사용하는 것이 더 편합니다. angularJS는 jqLite라는 jquery의 mini version을 가지고 있습니다. 기본적인 jQuery select와 같은 기능들은 대부분 가지고 있기 때문에 angular.element를 사용하는 것이 더 좋습니다.

그리고, jQuery UI component를 사용하게 되는 경우에는, directive로 새로 만들어서 사용하는 것을 강력 권장합니다. 위에서 이야기드린 것처럼, jQuery UI library의 destroy를 정확하게 호출하지 않는 경우에는 memory leak이라는 매우 미묘한 문제를 가지고 오게 됩니다. 개발이 한참 진행된 이후에 memory leak을 발견하게 되는 경우에는 찾기가 정말 힘들어질 수 있습니다.

$scope에는 외부에 노출되어야지 되는 변수 및 method만을 정의 한다.

$scope의 값은 angularjs의 dirty-search의 대상입니다. 불필요한 내부값 및 내부 method 들을 $scope에서 사용해서 굳이 성능저하를 만들 필요는 없습니다.

Summary

AngularJS는 정말로 재미있는 Framework입니다. 특히 single document application을 개발할 때, 그 장점을 더 살릴수 있습니다. 그렇지만, 다음 항목들에 대해서는 좀더 주의가 필요합니다.

  1. memory leak
  2. DataBinding의 이해
  3. 재사용 가능한 HTML과 Controller


Posted by Y2K
,

Bower는 maven, gradle을 이용한 java에서의 open source library의 버젼관리를 javascript client library에 옮겨온 개념입니다.

bower 소개

Bower는 maven, gradle과 비슷하게 bower.json과 .bowerrc 두개의 파일로 설정이 관리가 됩니다.

  • .bowerrc : Bower에서 source와 dist를 다운받을 위치를 지정합니다.
  • bower.json : Bower에서 다운받을 외부 component의 버젼 및 Project의 name, version을 지정합니다. 이는 maven, gradle에서 project와 비슷한 값을 지정하게 됩니다.

.bowerrc

{
  "directory": "components"
}

bower.json

{
  "name": "my-project",
  "version": "0.0.1",
  "dependencies": {
    "modernizr": "~2.6.2",
    "bootstrap": "~2.2.2",
    "angular-strap": "~0.7.0",
    "angular-ui" : "0.4.0",
    "ngInfiniteScroll" : "1.0.0",
    "angular-underscore" : "",
    "underscore" : "",
    "angular-bootstrap" : "~0.2.0",
    "font-awesome" : "3.0.2",
    "emoji" : "0.1.2"
  }
}

bower의 설치

bower는 node.js를 이용해서 작성되어있습니다. 먼저 node.js를 설치하는 것이 필요합니다. node.js의 설치방법은 너무나 많은 곳들에서 소개가 되고 있기 때문에 넘어가기로 하겠습니다.
Bower는 전역으로 사용되는 application이기 때문에, npm install -g를 이용해서 설치를 해야지 됩니다. 최종적으로 사용하기 위해서는 bower-installer 역시 같이 설치해주는 것이 좋습니다.

sudo npm install -g bower
sudo npm install -g bower-installer

bower의 활용

기본적으로 mvnrepository와 같이 사용할 library를 http://bower.io/search/ 에서 검색해서 찾는 것이 가능합니다. bower를 이용해서 library를 설치하기 위해서는 다음 command를 실행하면 됩니다.

bower install jquery

위는 jquery를 지정해서 설치하게 됩니다. 버젼이 따로 적혀있지 않는 경우, 가장 최신의 버젼을 다운받아 사용하게 됩니다. 특정하게 버젼을 지정하기 위해서는 다음과 같이 작성할 수 있습니다.

bower install jquery#1.11.0

설치와 bower.json 파일 기록을 동시에 할 수 있습니다.

bower install jquery#1.11.0 --save
bower install jquery#1.11.0 -s

bower-installer

bower를 통해서 component들을 설치하게 되면 source 코드와 min file 모두가 다운받게 됩니다. 그렇지만, 사용하게 될 component 들은 대다수 min file들만이 사용되게 됩니다. 이를 위해 설치한 component들의 min file 들만을 사용할 위치로 copy 해주는 tool이 bower-installer 입니다. bower-install는 bower.json에서 설정하며, 다음과 같이 설정할 수 있습니다.

    "install" : {
        "path" : {
            //"파일 확장자" : "copy될 위치"
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "woff" : "src/main/webapp/lib/fonts"
        },
        "sources" : {
            "component 이름" : [
                "copy 할 file 이름"
            ]
        }
    }

bower.json을 수정한 후, bower-installer를 실행하면 위치에 맞게 file이 모두 copy되는 것을 볼 수 있습니다.

bower와 gradle java war project의 연결

gradle을 이용한 war project의 경우, src/main/webapp에 사용되는 모든 code들과 javascript가 위치하게 됩니다. 여기 위치에 bower_component를 모두 다 다운받아서 처리하게 되면 url이 지저분해지는 경향이 있습니다. 이를 개발 방향에 맞게 정리해주는 것이 필요합니다. 이 때, bower-installer를 사용하면 원하는 directory 구조를 만들 수 있습니다.

제가 개인적으로 생각하는 좋은 구조는 다음과 같습니다.

project
- src
    - main
        - java
        - resources
        - webapp
    - test
        - java
        - resources
 .bowerrc
 .bower.json
 build.gradle
 setting.gradle

.bowerrc

{
    "directory" : "bower_components"
}

bower.json (bootstrap과 jquery를 설치한 상태입니다.)

{
    "name" : "bookstore-web",
    "version" : "0.0.0.1",
    "dependencies" : {
        "jquery" : "1.11.0",
        "bootstrap" : "3.1.1"
    },
    "install" : {
        "path" : {
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "eot" : "src/main/webapp/lib/fonts",
            "svg" : "src/main/webapp/lib/fonts",
            "ttf" : "src/main/webapp/lib/fonts",
            "woff" : "src/main/webapp/lib/fonts"
        },
        "sources" : {
            "jquery" : [
                    "bower_components/jquery/dist/jquery.min.js"
                ],
            "bootstrap" : [
                    "bower_components/bootstrap/dist/css/bootstrap.min.css",
                    "bower_components/bootstrap/dist/css/bootstrap-theme.min.css",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff",
                    "bower_components/bootstrap/dist/js/bootstrap.min.js"
                ]
        }
    }
}

위와 같이 설정해주면 bower-installer를 실행하면 src/main/webapp/lib 안에 모든 javascript component를 위치할 수 있게 됩니다. 그런데, 이 과정 역시 gradle에 추가하는 것이 보다 더 사용하기 편합니다. 다음과 같이 gradle task를 추가합니다.

import org.apache.tools.ant.taskdefs.condition.Os
task bowerInstaller(type:Exec) {
    if(Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', 'bower-installer'
    } else {
        commandLine 'bower-installer'
    }
}

이제 gradle bowerInstaller commmand를 통해 bower-installer를 실행할 수 있습니다. bowerInstaller를 따로 실행시켜줘도 좋지만, war로 배포될 때 자동으로 포함될 수 있도록 war task에 dependency를 추가하도록 합니다.

war {
    dependsOn bowerInstaller
}

이제 gradle war를 통해서도 모든 component들을 같이 관리할 수 있습니다. gradle의 놀라운 확장성에 대해서 다시 한번 감탄하게 됩니다. ^^

마치면서

bower는 java에서의 maven, gradle과 같이 dependencies에 대한 관리 툴입니다. 따라서 기존 관행처럼 모든 javascript를 SCM에 올릴 필요가 더이상 없어집니다. 버젼 관리와 같이 개발되는 application에서 사용되고 있는 component에 대한 관리와 같이 maven, gradle을 사용할 때와 같은 매우 멋진 작업들이 가능하게 됩니다. 모두 bower를 한번 써봅시다. ^^

Posted by Y2K
,
javascript에서 자주 class화 시켰으면 좋을것 같은데.. 하는 일이 자주 벌어진다.
일단 기본 목적이 화면에 표시되는 방법을 제어하는 경우가 더 많기 때문에, 기존 WinForm의 객체들처럼 각각 만들어져있다면 사용하기에 정말 편할 것 같아서 class와 상속 들에 대해서 알아보게 되었다.

먼저, 기본적으로 javascript에서는 class라는 키워드는 존재하지 않는다. function 키워드를 이용해서 객체를 선언 하게 된다.

1. class의 선언

function Product() {    this.name = '';    this.price = 0;    this.SetValues = function(name, price) {        this.name = name;        this.price = price;    };            this.ShowName = function(id) {        this.ShowAlert();        $('#' + id).html('Product' + this.name);    };        this.ShowAlert = function() {        console.debug('this is product message ' + this.name);    };}

class의 선언은 매우 단순한것이. 일반적인 함수의 형태를 취하면서 만들어지게 된다. prototype을 선언해서 만드는 법도 가능하지만, 코드의 가독성이 이게 더 편한 것 같아서. 이 방법을 주로 사용하는 것이 더 나아보인다.
private method나 private property의 경우에는 var 키워드를 이용해서 만들어주고, public의 경우 this 키워드를 이용해서 선언할 수 있다.

2. class의 상속

function KrProduct() {    Product.call(this);    this.ShowAlert = function() {        console.debug('this is krProduct message ' + this.name);            };}

call method를 이용해서 class를 상속한다.
override 를 하고 싶은 경우, 기존의 method를 동일하게 재 선언해주면 된다.
주의점은 javascript의 객체를 만들때는 protected가 없다는 것이다. 상속된 객체에서 절대로 private method나 property는 사용하지 못한다. 이를 고려해서 만들어줘야지 된다. (객체의 캡슐화에는 매우 불리한 것 같다.)

3. 실행 결과.
$(function() {    $('#testButton').click(function() {        var product = new Product();        var product2 = new KrProduct();        var product3 = new KrProduct();        product.SetValues("Product Name", 0);        product2.SetValues("Product2 Name", 1);        product3.SetValues("Product3 Name", 2);         product.ShowName('testDisplay');        product2.ShowName('testDisplay2');        product3.ShowName('testDisplay3');    });})


override 시킨 method가 차례대로 실행되는 것을 알 수 있다.

Posted by Y2K
,
회사에서 디자이너분이 필요하다고 해서 예제 간단히 뚝딱뚝딱.

<html>
<head>
<script src="jquery-1.2.6.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" language="javascript">
$(function(){
$('#clickMe1').click(function(){
$('#info').load('load.html #movie', function() {
$(this).hide().appendTo('context').slideDown();
});
$(this).attr("src", 'images\\00buy_05.gif');
$('#clickMe2').attr("src", "images\\00buy_19.gif");
});
$('#clickMe2').click(function() {
$('#info').load('load.html #music', function() {
$(this).hide().appendTo('context').slideDown();
});
$(this).attr("src", 'images\\00buy_21.gif');
$('#clickMe1').attr("src", "images\\00buy_03.gif");
});
$('#clickMe3').click(function(){
$('#info').load('load.html #poem', function() {
$(this).hide().appendTo('context').slideDown();
});
$(this).html("Changed Text");
});
});
</script>
<script type="text/javascript" language="javascript">
function show_alert(){
alert('show');
}
</script>
</head>

<body>
<img id="clickMe1" src="images\00buy_03.gif" onclick="javascript:show_alert();"/>
<img id="clickMe2" src="images\00buy_19.gif"/>
<a href="#" id="clickMe3">Click Me3</a>
<div id="info">
<div id="context">
</div>
</div>
</body>
</html>

재미있기도 하고, 너무나 동작하는 것이 신기한 jquery. ^^
Posted by Y2K
,

jQuery 기본

기타 자료들 2009. 1. 29. 18:06

자료를 뒤지다가 jQuery에 대해서 잘 정리된 자료가 있어서 업어온 정보. 

(원본 : http://funnygangstar.tistory.com/category/jQuery)


1. jQuery를 잘(?) 사용하기 위한 기본 환경 셋팅

- 아래의 사이트에서 
jQuery 라이브러리(jquery-1.2.6.js)를 다운로드 받는다.

  http://code.google.com/p/jqueryjs/downloads/detail?name=jquery-1.2.6.js&can=1&q=


- VisualStudio
에서 jQuery의 인텔리전스 기능을 사용하기 위해서는 아래의 순서대로 실행한다.

  1) VisualStudio 2008 SP1을 설치한다.

  2) VisualStudio의 패치를 실시한다.

      http://code.msdn.microsoft.com/KB958502/Release/ProjectReleases.aspx?ReleaseId=1736

  3) jQuery-vsdoc.js 파일을 다운로드한다. ( ~vsdoc.js 파일은 js 파일의 인텔리전스를 위한 도움말 파일임)

      http://code.google.com/p/jqueryjs/downloads/detail?name=jquery-1.2.6-vsdoc.js&can=1&q=



2. jQuery
의 시작
  
jQuery를 사용하기 위해서는 로 시작하거나 jQuery로 시작하면 된다.

  ) $(“div”)   or    jQuery(“div”)

페이지가 로드 되었을때 특정한 jQuery를 실행시키기 위해서는 아래의 세가지 방법이 있을 수 있으며 마지막 방법이

 일반적으로 가장 많이 사용된다.

  1) window.onload = function() {

            alert('aaa');

            }

  2) $(document).ready(function(){

            alert('aaaa');

            });

  3) $(function(){

            alert('aaaa');

            });

일반적인 HTML 페이지의 로딩이벤트에서는 첫번째 방법처럼 사용하게되고 단점은 단 한번밖에는 사용할 수 없다는 단점이 있습니다만두번쨰나 세번째 처럼 jQuery를 사용하게 되면 페이지 로드 이벤트시 여러 개의 함수를 호출할 수 있다는 장점이 있습니다.



3. jQuery
로 엘리먼트 선택하기
3-1 알아두어야할 기본 사항
  조작하려는 
HTML 엘리먼트 선택하는 방법

  1) a => <a> 태그로 시작하는 모든 엘리먼트를 선택한다.

    ) $(“a”)

  2) #ID => ID에 지정한 id를 가지는 모든 엘리먼트를 선택한다.

    ) $(“div#myID)

  3) .CLASS => CLASS에 지정한 class를 가지는 모든 엘리먼트를 선택한다.

    ) $(“tr.myClass)

3-2 
기본형 선택방식
  
1) 엘리먼트 사이에 스페이스를 두면 하위의 엘리먼트를 선택하게 된다.

    ) $(“ul li a”)  è ul 태그 하위에 li 태그 하위에 있는 태그를 선택한다.

     주의할 것은 첫번째 나오는 엘리먼트(위에서는 ul 태그)라고 해서 전체 HTML에서 최상위에 있는 엘리먼트를 지칭하지는

       않는다는 것이며또한 스페이스는 하위 엘리먼트를 선택하는 것이지 바로 아래의 엘리먼트를 선택하는 것이 아니다

    )

        <body>

          <div id="all">

            <a href="http://www.naver.com">네이버링크</a>

            <div>

              <div><b><a href="http://www.daum.net">5-2</a></b></div>

            </div>

          </div>

        </body>

      와 같은 HTML이 있을 경우 $(“div a”)라고 한다면 위에서 두가지 <a> 태그 모두를 선택하게 된다.

2) 자주 쓰이는 셀렉터

   - *  => 모든 엘리먼트 => $("ul li *")

   - E  => 태그명이 E인 모든 엘리먼트  => $(“div”)

   - E F  => E 엘리먼트의 자손이면서 태그명이 F인 모든 엘리먼트  => $(“div a”)

   - E > F  => E의 바로 아래 자식이면서 태그명이 F인 모든 엘리먼트  => $(“div > a”)

   - E:has(F)  => 태그명이 F인 엘리먼트를 하나 이상 가지는 모든 엘리먼트  =>  $(“ul li ul:has(li)”)

   - E.C  =>  클래스명 C를 가지는 모든 엘리먼트, E를 생략하면 *.C와 동일  =>  $(“ul:myList”)

   - E#I  =>  아이디가 I인 엘리먼트 엘리먼트, E를 생략하면 *#I와 동일  =>  $(“div#someID”)

   - E[A]  =>  어트리뷰트 A를 가지는 엘리먼트 E  => $(“img[src”)

   - E[A=V]  =>  값이 V인 어트리뷰트 A를 가지는 엘리먼트E  =>  $(“table[id=languages]”)

   - E[A^=V]  =>  값이 V로 시작하는 어트리뷰트 A를가지는 엘리먼트  => $(“table[id^=lan])

   - E[A$=V]  =>  값이 V로 끝나는 어트리뷰트 A를 가지는 엘리먼트  =>  $(“table[id$=ges])

   - E[A*=V]  =>  값에 V가 들어있는 어트리뷰트 A를가지는엘리먼트  => $(“table[id*=g])

3) 하나의 $()에 콤마(,)를 사용하여 여러 요소를 접근할 수 있습니다.

   div와 p요소를 선택하고자 한다면

   $('div,p')



3-3 
위치 기반 선택 방식

  - B:first  => 페이지에서 첫번째 B요소와 매칭

  - B:last  =>  페이지에서 마지막 B요소와 매칭

  - B:first-child  =>  첫번째 B요소와 매칭

  - B:last-child  =>  마지막 B요소와 매칭

  - B:only-child  =>  B요소가 하나일 경우 B요소와 매칭

  - B:nth-child(n)  =>  n번째 B요소와 매칭(n:one-based)

  - B:nth-child(odd|even)  =>  /짝수 B요소와 매칭

  - B:nth-child(Xn+Y)  =>  X배수 + Y인 B요소와 매칭(n:zero-based)

  - B:even  =>  짝수 B요소와 매칭

  - B:odd  =>  홀수 B요소와 매칭

  - B:eq(n)  =>  n번째 B요소와 매칭(n:zeno-based)

  - B:gt(n)  =>  n번째 초과 B요소와 매칭

  - B:lt(n)  =>  n이하 B요소와 매칭


* :nth-child 
선택자는 1부터 시작되는 반면에 :eq, :gt, :lt 선택자는 0부터 시작됩니다.



3-4 
사용자 정의형 선택 방식

  - B:animated  =>  jQuery 애니메이션 메서드로 애니메이션이 활성화된 B요소와 매칭

  - B:button  => 버튼 형태(button, input[@type=submit], input[@type=reset], input[@type=button])의 B요소와 매칭

  - B:checkbox  =>  input[@type=checkbox]인 B요소와 매칭

  - B:enabled  =>  활성화 상태인 B요소와 매칭

  - B:file  =>  input[@type=file]인 B요소와 매칭

  - B:header  =>  h1~h6인 B요소와 매칭

  - B:hidden  =>  비가시 상태 또는 input[@type=hidden]인 B요소와 매칭

  - B:image  =>  input[@type=image]인 B요소와 매칭

  - B:input  =>  입력 요소(input, select, textarea)인 B요소와 매칭

  - B:not(f)  =>  :(콜론)으로 시작하는 f를 제외한 B요소와 매칭

  - B:parent  =>  자식요소가 있는 B요소와 매칭

  - B:password  =>  input[@type=password]인 B요소와 매칭

  - B:radio  =>  input[@type=radio]인 B요소와 매칭

  - B:reset  =>  input[@type=reset]인 B요소와 매칭

  - B:selected  =>  option요소 중 현재 선택된 B인요소와 매칭

  - B:submit  =>  input[@type=submit]인 B요소와 매칭

  - B:text  =>  input[@type=text]인 B요소와 매칭

  - B:visible  =>  가시 상태인 B요소와 매칭



4. jQuery의 몇가지 특색
  
1) 체이닝 호출

  - jQuery는 메소드를 연속으로 호출 할 수 있다.

   ) $(‘#div’).slideup(2000).css("background-color", "#fff0ff").click(function(){ alert(‘completed’); });

2) 유틸리티 함수 지원

  - $. 을 사용하면  jQuery에서 지원하는 유틸리티 함수를 사용할 수 있다.

   ) $.get(),    $.ajax() …….

3) 엘리먼트 추가/삭제

  - jQuery를 사용하면 손쉽게 HTML 엘리먼트를 추가 삭제할 수 있다. appent(), appentTo(), insertAfter()등의 메소드 사용

   ) $('#chk').append('<input id=Checkbox6 type=checkbox checked=true />');

4) HTML 어트리뷰트 변경

  - HTML 엘리먼트의 어트리뷰트를 변경할 수 있다.

   ) $('#Img1').attr('width', '200');

 5) value 값과 text 

  - HTML 엘리먼트의 value 어트리뷰트를 조작하기 위해서는 val()/val(메소드를 사용하며,

    엘리먼트 사이에 있는 Text값 (: <label>텍스트값</label>)을 조작하기 위해서는  text(), text(메소드를 사용한다

Posted by Y2K
,

jQuery를 이용한 css 변경

선택된 DOM object에서 .css(attribute, value) 로 변경이 가능하다.
신기하고 재미있는 기능들이 많은 것 같다. 여러모로.

<html>
    <head>
    <title>This is my first web file</title>
    <script type="text/javascript" language="javascript" src="jquery-1.3.1.min.js"></script>   
    <script type="text/javascript" language="javascript">
        function append_table_context() {
            $("#tableContext").append("<tr><td>F.Name</td><td>L.Name</td></tr>");
        }       
    </script>
    <style type="text/css">
        #box
        {
            background : red;
            width : 300px;
            height : 400px;
        }
        p a
        {
            color:Red;
        }
    </style>   
    <script type="text/javascript" language="javascript">
        $(function() {
            $('a').click(function() {
                $('#box').slideDown();
                $('p a').css('color', 'blue');
            });
        });  
    </script>       
    </head>
    <body>      
        <div id="box" style="display:none"></div>
        <a href="#">Click Me!</a>
       
        <br/>
       
        <p>
         * Sizzle CSS Selector <a href="#">Engine</a> - v0.9.3Copyright 2009, The Dojo Foundation*  Released under the MIT, BSD, and GPL Licenses.*
           More information: http://sizzlejs.com/
        </p>
       
        <input id="btnInput" type="button" value="click me" onclick="javascript:append_table_context();"/>       
        <table>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
            </tr>
            <tbody id="tableContext">
            </tbody>
        </table>
    </body>
</html>

Day 3까지 보니. 코드를 어떻게 적어야지 될지 약간의 감이 오는 것 같다. ^^
<html>
    <head>
    <title>This is my first web file</title>
    <script type="text/javascript" language="javascript" src="jquery-1.3.1.min.js"></script>   
    <script type="text/javascript" language="javascript">
        function append_table_context() {
            $("#tableContext").append("<tr><td>F.Name</td><td>L.Name</td></tr>");
        }       
    </script>
    <style type="text/css">
        #box
        {
            background : red;
            width : 300px;
            height : 300px;
            position : relative;
        }
        p a
        {
            color:Red;
        }
    </style>   
    <script type="text/javascript" language="javascript">
        $(function() {
            $('#box').click(function() {
                $(this).animate({
                    "left" : "300px"
                    }, 4000);      
            $(this).animate( { "width" : "50px" } );
            });
        });
    </script>       
    </head>
    <body>      
        <div id="box"></div>
        <a href="#">Click Me!</a>
    </body>
</html>

일단, jQuery에 대한 기본적인 function 사용법을 제대로 익히는 것이 가장 필요한 일인듯.

Posted by Y2K
,

http://blog.themeforest.net/tutorials/jquery-for-absolute-beginners-video-series/

에서 재미있는 비디오 강습을 해서 짬짬히 살펴보고 있는 중이다.
가장 많이 사용되고 있는 javascript framework 중 하나이고, asp.net과 가장 같이 잘 사용될 수 있는 javascript framework이기 때문에 더욱더 관심이 가는 내용.

<html>
    <head>
    <title>This is my first web file</title>
    <script type="text/javascript" language="javascript" src="jquery-1.3.1.min.js"></script>   
    <script type="text/javascript" language="javascript">
        function append_table_context() {
            $("#tableContext").append("<tr><td>F.Name</td><td>L.Name</td></tr>");
        }       
    </script>
    <style type="text/css">
        #box
        {
            background : red;
            width : 300px;
            height : 400px;
        }
    </style>   
    <script type="text/javascript" language="javascript">
        $(function() {
            $('a').click(function() {
                $('#box').fadeOut('fast');
            });
        });  
    </script>       
    </head>
    <body>      
        <div id="box">
        <a href="#">Click Me!</a>
        </div>
        <br/>
               
        <input id="btnInput" type="button" value="click me" onclick="javascript:append_table_context();"/>       
        <table>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
            </tr>
            <tbody id="tableContext">
            </tbody>
        </table>
    </body>
</html>

하나의 박스가 사라지는 내용과 table에 하나씩 추가하는 내용이 있는 소스.
Posted by Y2K
,

새롭게 하나의 일을 시작하기 전에 이런 방법으로 해보자 하고 찾아보고 있는 중에 전에 하던 MicrosoftAjax.js를 좀 잘 써보자는 생각이 생겼다. table에서 pagination을 지원하고, 각 항목을 ajax call을 통해서 얻어오는 과정은 다음과 같다.

1. Web.config에서 web service의 호출 방법으로 get / post method의 추가. 

2. WebServicedml class attribute에 [ScriptService]의 추가
: 까먹고 안해주면 가장 오랫동안 헛갈리게 되는 사항이다. 이 부분에 대해서 안해주면 exception이 나타나게 되는데 exception을 내용은 다음과 같다. 

3. Sys.Net.WebServiceProxy.invoke를 통한 web service 호출
Sys.Net.WebServiceProxy.invoke("/BoardService.asmx", "GetBoardList", false, { "pageNumber": page_number }, display_board);

ScriptService를 통해서 오는 결과는 JSON format과 동일하게 된다. 따라서, JSON을 javascript에서 이용하는 방법으로 간단히 사용하면 된다. 

예를 들어 다음과 같은 class를 반환하는 WebService가 있을때, 
    public class Data
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

javascript에서는 다음과 같이 사용하면 된다.
      function display_name(data) {
        alert(data.FirstName + data.LastName);
      }

Posted by Y2K
,