jQuery საკუთარი ხელით – ნაწილი 2

მაშ ასე, განვაგრძობ პოსტების სერიას “jQuery საკუთარი ხელით” და გთავაზობთ მეორე ნაწილს. პირველ ნაწილში ლაპარაკი გვქონდა ამ ბიბლიოთეკის ძირითად შემადგენელ ნაწილებსა და მისი მუშაობის ზოგად პრინციპებზე. ასევე პირველ ნაწილში შევქმენით ბიბლიოთეკის კარკასი კოდური სახელწოდებით myQuery და სადემონსტრაციოდ დავამატეთ ერთი მეთოდი.

დასაწყისისთვის უკვე გაგვაჩნია საკმარისი ინფორმაცია და მოდულის მკაფიო მონახაზი რომელზე დაყრდონბითაც შეგვიძლია გადავიდეთ რეალიზაციის შემდეგ ნაწილზე. ამ ნაწილში ლაპარაკი მექნება DOM ელემენტის class ატრიბუტსა და მასთან სამუშაო მეთოდებზე.

DOM ელემენტის class ატრიბუტს საკმაოზე უფრო მეტად მნიშვნელოვანი როლი აკისრია და ეს ატრიბუტი განსაკუთრებულ ყურადღებას იმსახურებს. ძირითადად ეს ატრიბუტი DOM ელემენტისათვის CSS კლასების მისანიჭებლად გამოიყენება, თუმცა მინდა აღვნიშნო, რომ, “class” ატრიბუტი არის ზოგადი დანიშნულების ატრიბუტი და არ არის განსაზღვრული მხოლოდ CSS კლასების მისანიჭებლად. ასევე აღსანიშნავია რომ შესაძლებელია DOM ელემენტის ამ ატრიბუტს შესაძლებელია ერთდროულად მივანიჭოთ არის[space] სიმბოლოთი გამოყოფილი ერთზე მეტი კლასი.

ელემენტის აღნიშნული ატრიბუტისათვის მნიშვნელობის მინიჭება და ამ მნიშვნელობის წაკითხვა ხორცილდება DOM ელემენტის className თვისების მეშვეობით. მაგ:

1
2
3
4
5
6
7
8
var el = document.getElementById('element-identifier');
el.className = 'some-css-class';
//ან
el.className = 'some-css-class some-other-class just-class';
//მნიშვნელობის წაკითხვა
var className = el.className;
//კლასის განადგურება
el.className = '';

მიუხედავად იმისა რომ ელემენტის className თვისება შესანიშნავად ართმევს თავს დაკისრებულ მოვალეობას იგი მოუხერხებელია მთელ რიგ შემთხვევებში. კერძოდ კი:

  • თუ ატრიბუტი შეიცავს კლასების ჩამონათვალს შეუძლებელია მარტივი გზით დავადგინოთ გააჩნია თუ არა ელემენტს ეს თუ ის კონკრეტული კლასი რადგან ამ შემთხვევაში შედარების “==” ოპერატორი უსარგებლოა;
  • თუ ატრიბუტი შეიცავს კლასების ჩამონათვალს არ არსებობს მარტივი გზა კონკრეტული კლასის მოსაშორებლად;
  • თუ ატრიბუტი შეიცავს ერთდროულად რამდენიმე კლასს შეუძლებელია რომელიმე კონკრეტულ კლასს შევუცვალოთ პრიორიტეტი(პრიორიტეტი ამ შემთხვევაში ენიჭება უკიდურეს მარჯვენა კლასს);

ასეთი შემთხვევებისათვის jQuery აღჭურვილია რამდენიმე შესანიშნავი მეთოდით რომელიც გამოიყენება ექსკლუზიურად class ატრიბუტთან სამუშაოდ. კერძოდ კი:

  • addClass – ელემენტისათვის ახალი კლასის დასამატებლად;
  • hasClass – შესამოწმებლად, გააჩნია თუ არა ელემენტს ესა თუ ის კონკრეტული კლასი;
  • removeClass – ელემენტისათვის კონკრეტული კლასის მოსაშორებლად;
  • toggleClass – კლასის “გადასართავად”. იგულისხმება რომ თუ ელემენტს გააჩნია მეთოდისათვის პარამეტრად გადაცემული კლასი მაშინ იგი უნდა მოშორდეს, წინააღმდეგ შემთხვევაში კი დაემატოს ელემენტს.

ჩამოთვლილი მეთოდების რეალიზაცია ძალიან მარტივია. ამისათვის სულ რამდენიმე სტრიქონი კოდია საჭირო:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var className = {

  add: function(el, value) {
    className.remove(el, value);
    var classNames = el.className.split(/\s+/);
    classNames.push(value);
    el.className = classNames.join(' ');
  },

  remove: function(el, value) {
    var regex = new RegExp('(^|\\s)' + value + '(\\s|$)', 'g');
    el.className = el.className.replace(regex, '$1$2');
  },

  has: function(el, value) {
    var regex = new RegExp('(^|\\s)' + value + '(\\s|$)');
    return regex.test(el.className);
  },

  toggle: function(el, value) {
    if (className.has(el, value)) {
      className.remove(el, value);
    } else {
      className.add(el, value);
    }
  }

};

className” ობიექტი შედგება ოთხი add, has, remove და toggle ზოგადი დანიშნულების მეთოდებისგან და ყოველი მათგანი ღებულობს ორ პარამეტრს – DOM ელემენტი და სასურველი კლასის სახელი. მეთოდები საკმაოდ მარტივია რადგან მუშობა ხდება ჩვეულებრივ სტრიქონთან. თუმცა “add” მეთოდის შემთხვევაში უნდა აღინიშნოს რომ კლასის სახელის დამატება აუცილებლად ხდება ჩამონათვალის ბოლოში რათა გარანტირებული იყოს ახლად დამატებული კლასის პრიორიტეტულობა.

ნაჩვენები className თამამად შეგვიძლია დავამატოთ ჩვენს “myQuery მოდულში ნებისმიერ ადგილას. თუმცა ამ ობიექტის მეთოდები ჯერჯერობით უხილავი იქნება გარე სამყაროსთვის, ამისათვის საჭიროა თავად myQuery მოდულის prototype სექციაში დავამატოთ შესაბამისი API მეთოდები რომლის მეშვეობითაც მოხდება ახლად რეალიზებული მეთოდების გამოძახება. რეალიზაცია:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
myQuery.prototype = {

   //other methds

  addClass: function(name) {
    this.each(function() {
      className.add(this, name);
    });
    return this;
  },

  hasClass: function(name) {
    return this[0] ? className(this[0], name) : false;
  },

  removeClass: function(name) {
    this.each(function() {
      className.remove(this, name);
    });
    return this;
  },

  toggleClass: function(name) {
    this.each(function() {
      className.toggle(this, name);
    });
    return this;
  }

}

როგორც ხედავთ რეალიზაცია ძალიან მარტივია, თუმცა რამდენიმე დეტალს უფრო ვრცლას შევეხები. პირველ რიგში ყუარდღება მიაქციეთ რომ hacClass მეთოდის გარდა ყველა მეთოდი თავის შესაბამის ოპერაციას ახორციელებს მიმდინარე myQuery ობიექტის მიერ ნაპოვნ ყველა DOM ელემენტზე. მეორე რიგში ყურადღებას იმსახურებს თავად each() მეთოდის გამოყენება რომლის რეალიზაციაც გავაკეთეთ პოსტის პირველ ნაწილში. საინტერესოა ის რომ ელემენტების იტერაციისათვის გამოვიყენეთ თავად მოდულის ჩვენს მიერ რეალიზებული API მეთოდი და არა ტრადიციულად ციკლის ოპერატორი. და ასევე ყურადღება მიაქციეთ რომ ყველა მეთოდი(კვლავ hasClass მეთოდის გარდა) აბრუნებს მიმდინარე myQuery ობიექტს, ცხადია ეს აუცილებელია ჯაჭვურობის შესანარჩუნებლად.

ახლად დამატებული მეთოდების მეშვეობით უკვე შესაძლებელია საკმაოდ საინტერესო შედეგების მიღება. მაგალითისათვის გადახედეთ შემდეგ კოდს:

1
2
3
4
5
6
7
8
9
$('div p a') //find all "A" tags within "P" tags
  .removeClass('unnecessary-class') //remove class "unnecessary-class"
  .addClass('some-new-class') //add class "some-new-class"
  .toggleClass('some-other-class') //toggle class "some-other-class"
  .each(function() { //iterate through all "A" tags
     console.log(this.className); //print class name to the console output
  });

var value = $('#element-identifier').hasClass('some-class-of-intereset');

ჩემის აზრით საკმოად შთამბეჭდავი შედეგია არც თუ ისე რთული და მოცულობითი კოდის კვალობაზე :)

დროა გავაერთიანოთ პოსტის პირველი და მიმდინარე ნაწილის შედეგები:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
(function() {
 
  /**
   * @class myQuery class
   * @param query String|DOM Element
   * @param context DOM Element|DOM Element Collection
   */

  var myQuery = function(query, context) {
   
    //if no context provided use "document" as default
    context = context || document;
   
    //if context is DOM document or Node put it in array
    if (!(context instanceof Array)) {
      context = [context];
    }
   
    //resulting array of found DOM elements
    var result = [];
   
    try {
     
      //loop through each context and find elements
      for (var i = 0, length = context.length; i < length; i++) {
     
        //find elements within context
        var nodes = context[i].querySelectorAll(query);
     
        //build array from DOM List object
        for (var j = 0, len = nodes.length; j < len;) {
          result.push(nodes[j++]);
        }
       
      }
     
    } catch(ex) {
      result = [];
    }
   
    //count of found DOM elements
    //will be updated with Array's push() method
    this.length = 0;
   
    //add all found elements to "myQuery" instance
    Array.prototype.push.apply(
      this, //current instance of myClass object
      result //array of found DOM elements
    );

  };
 
  //utility methods for working with "class" attribute
  var className = {

    add: function(el, value) {
      className.remove(el, value);
      var classNames = el.className.split(/\s+/);
      classNames.push(value);
      el.className = classNames.join(' ');
    },
 
    remove: function(el, value) {
      var regex = new RegExp('(^|\\s)' + value + '(\\s|$)', 'g');
      el.className = el.className.replace(regex, '$1$2');
    },
 
    has: function(el, value) {
      var regex = new RegExp('(^|\\s)' + value + '(\\s|$)');
      return regex.test(el.className);
    },
 
    toggle: function(el, value) {
      if (className.has(el, value)) {
        className.remove(el, value);
      } else {
        className.add(el, value);
      }
    }
 
  };

  //myQuery class methods
  myQuery.prototype = {
 
    each: function(callback) {
     for (var i = 0, el; (el = this[i++]);) {
       callback.call(el, i);
     }
     return this;
    },
   
    addClass: function(name) {
      this.each(function() {
        className.add(this, name);
      });
      return this;
    },
 
    hasClass: function(name) {
      return this[0] ? className(this[0], name) : false;
    },
 
    removeClass: function(name) {
      this.each(function() {
        className.remove(this, name);
      });
      return this;
    },
 
    toggleClass: function(name) {
      this.each(function() {
        className.toggle(this, name);
      });
      return this;
    }
   
  };
 
  /**
   * Add "$" function to global context
   * function accepts same arguments as myQuery class
   *
   * @param query String|DOM Element
   * @param context DOM Element|DOM Element Collection
   */

  window.$ = function(query, context) {
    //return new "myQuery" instance
    return new myQuery(query, context);
  };

})();

ტეგები: , , ,

დატოვე კომენტარი:

ქართული კლავიატურა, ჩართვა/გამორთვა კლავიშით "~"