12/08/2018, 10:51

Tạo jQuery plugin sử dụng jQuery UI Widget Factory

Trong một thời gian dài, cách duy nhất để viết các điều khiển tùy chỉnh trong jQuery là mở rộng namespace $$fn. Điều này làm việc tốt với các widget đơn giản, tuy nhiên, khi bạn bắt đầu xây dựng thêm widget trạng thái, nó nhanh chóng trở nên cồng kềnh. Để hỗ trợ cho quá trình xây dựng các widget, ...

Trong một thời gian dài, cách duy nhất để viết các điều khiển tùy chỉnh trong jQuery là mở rộng namespace $$fn. Điều này làm việc tốt với các widget đơn giản, tuy nhiên, khi bạn bắt đầu xây dựng thêm widget trạng thái, nó nhanh chóng trở nên cồng kềnh. Để hỗ trợ cho quá trình xây dựng các widget, đội jQuery UI giới thiệu Widget Factory.

Widget Factory là một thành phần của jQuery UI Core, cung cấp một cách hướng đối tượng để quản lý vòng đời của một widget. Trong vòng đời của một widget bao gồm những hoạt động sau:

  • Creating and destroying a widget
  • Changing widget options
  • Making "super" calls in subclassed widgets
  • Event notifications

Để khám phá API này, chúng ta sẽ xây dựng một widget biểu đồ đơn giản dạng thanh.

The Bullet Chart Widget

Chúng ta sẽ cùng xây dựng Bullet Chart Widget, đây là một biến thể của biểu đồ dạng thanh có hình dạng như sau:

bullet.png

Biểu đồ này bao gồm một tập hợp các thanh và các marker chồng lên nhau để biểu diễn hiệu suất tương đối. Có một định lượng để biểu diễn phạm vi thực tế của giá trị. Bằng cách xếp chồng các thanh và marker theo cách này, nhiều thông tin có thể được chuyển tải mà không ảnh hưởng tới khả năng đọc.

Mã HTML cho biểu đồ này như sau:

 
<div class="chart bullet-chart">
  <div class="legend" style="">
    <div class="legend-item">
      <span class="legend-symbol marker green"></span>
      <span class="legend-label">Green Line</span>
    </div>
  </div>
  <div class="chart-container" style="awidth: 86%;">
    <div class="tick-bar">
      <div class="tick" style="left: 0%;"></div>
      <div class="tick-label" style="left: 0%;">0</div>
      <div class="tick" style="left: 25%;"></div>
      <div class="tick-label" style="left: 25%;">25</div>
      <div class="tick" style="left: 50%;"></div>
      <div class="tick-label" style="left: 50%;">50</div>
      <div class="tick" style="left: 75%;"></div>
      <div class="tick-label" style="left: 75%;">75</div>
      <div class="tick" style="left: 100%;"></div>
      <div class="tick-label" style="left: 100%;">100</div>
    </div>

    <div class="bar" style="left: 0px; awidth: 78%;" bar-index="0"></div>
    <div class="bar blue" style="left: 0px; awidth: 60%;" bar-index="1"></div>

    <div class="marker green" style="left: 80%;" marker-index="0"></div>
    <div class="marker red" style="left: 60%;" marker-index="1"></div>
  </div>
</div>

Chúng ta sẽ tạo ra một widget tên là jquery.bulletchart, sẽ tự động tạo ra đoạn mã HTML này từ các dữ liệu được cung cấp. Sau khi đã tạo được widget jquery.bulletchart chúng ta gọi nó như sau:

$('.chart').bulletchart({
  size: 90,
  bars: [
    { title: 'Projected Target', value: 78, css: ' },
    { title: 'Actual Target', value: 60, css: 'blue' }
  ],
  markers: [
    { title: 'Green Line', value: 80, css: 'green' },
    { title: 'Minimum Threshold', value: 60, css: 'red' }
  ],

  ticks: [0, 25, 50, 75, 100]
});

Tất cả các giá trị là tỷ lệ phần trăm. Tùy chọn size có thể được sử dụng khi bạn muốn có một số bullet chart được đặt cạnh nhau với kích thước tương đối. Tùy chọn ticks được sử dụng để đưa các nhãn đánh dấu các mốc giá trị trên thanh trạng thái. Mỗi marker và thanh bar được quy định bởi một mảng các đối tượng thể hiện các thuộc tính tiêu đề, giá trị và css.

Building the Widget

Bây giờ chúng ta hãy bắt tay vào xây dựng widget trên. Một widget được tạo ra bằng cách gọi $$widget () với tên của widget và một đối tượng có chứa các phương thức thể hiện của nó.

jQuery.widget(name[, base], prototype);

Chúng ta sẽ chỉ làm việc với tên và đối số prototype. Đối với bullet chart, cơ bản thì widget ban đầu trông giống như sau:

$.widget('nt.bulletchart', {
  options: {},
  _create: function () {},
  _destroy: function () {},
  _setOption: function (key, value) {}
});

Khuyến cáo rằng bạn luôn luôn có namespace cho tên widget của bạn. Trong trường hợp này, chúng ta đang đặt tên widget là 'nt.bulletchart' với namspace 'nt'. Tất cả các widget jQuery UI đều có namespace 'ui'. Mặc dù chúng ta đang đặt tên các widget với một namespace kèm theo, nhưng lời gọi để tạo ra widget trên một phần tử lại không bao gồm namespace đó. Vì vậy, để tạo ra một bullet chart, chúng ta sẽ chỉ cần gọi $('#element').bulletchart().

Các thuộc tính thể hiện được quy định sau tên của widget. Theo quy ước, tất cả các phương thức riêng tư của các widget phải được bắt đầu bằng '_'. Có một số thuộc tính đặc biệt mà widget factory cần có. Chúng bao gồm options, _create, _destroy và _setOption.

  • options: Đây là tùy chọn mặc định cho các widget
  • _create: Widget factory gọi phương thức này đầu tiên khi các widget được khởi tạo. Nó được sử dụng để tạo ra các DOM ban đầu và đính kèm các event handler.
  • _init: Sau khi gọi phương thức _create, factory sẽ gọi đến phương thức _init. Điều này thường được sử dụng để thiết lập lại các widget về trạng thái ban đầu. Mỗi lần widget được tạo ra, gọi constructor của widget, ví dụ: $$.bulletchart (), cũng sẽ thiết lập lại các widget. _init được gọi từ bên trong.
  • _setOption: Được gọi khi bạn thiết lập một tùy chọn trên widget, với lời gọi như sau: $ ('# elem'). bulletchart ('option', 'size', 100). Tiếp theo đây chúng ta sẽ thấy những cách khác để thiết lập tùy chọn cho widget.

Creating the initial DOM with _create

Widget bulletchart của chúng ta được tạo ra bởi phương thức _create. Đây là nơi mà chúng ta xây dựng các cấu trúc cơ bản cho biểu đồ. Hàm _create có thể được nhìn thấy dưới đây. Bạn sẽ nhận thấy rằng không có nhiều việc được thực hiện tại đây, ngoài việc tạo ra các container cấp ngoài cùng. Công việc thực tế của việc tạo ra các DOM cho các thanh bar, các marker và tick xảy ra trong phương thức _setOption. Điều này có vẻ hơi phản trực giác để bắt đầu, nhưng có một lý do chính đáng cho điều đó.

_create: function () {
  this.element.addClass('bullet-chart');
  this._container = $('<div class="chart-container"></div>')
    .appendTo(this.element);
  this._setOptions({
    'size': this.options.size,
    'ticks': this.options.ticks,
    'bars': this.options.bars,
    'markers': this.options.markers
  });
}

Lưu ý rằng các thanh bar, marker và tick cũng có thể được thay đổi bởi các thiết lập tùy chọn trong widget. Nếu chúng ta để các đoạn code xây dựng bên trong hàm _create, chúng ta sẽ lặp lại các đoạn code đó bên trong _setOption. Bằng cách đặt các đoạn code đó bên trong _setOption và gọi nó từ _create chúng ta loại bỏ được sự trùng lặp và cũng tập trung trong việc khởi tạo.

Ngoài ra, đoạn code trên cho bạn một cách khác để thiết lập các option trong widget. Với phương thức _setOptions, bạn có thể thiết lập nhiều tùy chọn trong một lần thực hiện. Bên trong, factory sẽ thực hiện từng lời gọi đến _setOption cho mỗi tùy chọn.

The _setOption method

Đối với các bullet chart, phương thức _setOption xử lý tạo ra các marker, thanh bar, tick và bất kỳ sự thay đổi nào đối với các thuộc tính này. Nó hoạt động bằng cách xóa các phần tử hiện có và tái tạo chúng dựa trên các giá trị mới.

Phương thức _setOption nhận cả hai tùy chọn key và value như các đối số. Điều quan trọng là tên của tùy chọn, nó phải tương ứng với một trong các key trong tùy chọn mặc định. Ví dụ, để thay đổi các thanh bar trên widget, bạn cần thực hiện lời gọi sau đây:

$('#elem').bulletchart('option', 'bars', [{
    title: 'New Marker', value: 50
}])

Phương thức _setOption cho bulletchart như sau:

_setOption: function (key, value) {
  var self = this;
  var prev = this.options[key];
  var fnMap = {
      'bars': function () {
        createBars(value, self);
      },
      'markers': function () {
        createMarkers(value, self);
      },
      'ticks': function () { createTickBar(value, self); },
      'size': function () {
        self.element.find('.chart-container').css('awidth', value + '%');
      }
    };
  this._super(key, value);
  if (key in fnMap) {
    fnMap[key]();
    this._triggerOptionChanged(key, prev, value);
  }
}

Ở đây, chúng ta tạo ra một object đơn giản chứa các tên tùy chọn và các function tương ứng. Sử dụng object này, chúng ta chỉ làm việc trên các lựa chọn hợp lệ và âm thầm bỏ qua những cái không hợp lệ. Có hai việc xảy ra ở đây: một lời gọi đến _super() và bắn ra sự kiện option thay đổi. Chúng ta sẽ xem xét chúng sau này.

Đối với mỗi tùy chọn thay đổi, chúng ta gọi đến một phương thức helper cụ thể. Phương thức helper createBars, createMarkers và createTickBar được quy định bên ngoài widget. Bởi vì chúng là như nhau cho tất cả các widget và không cần phải được tạo riêng cho mỗi thể hiện của widget.

// Creation functions
function createTickBar(ticks, widget) {
    // Clear existing
    widget._container.find('.tick-bar').remove();

    var tickBar = $('<div class="tick-bar"></div>');
    $.each(ticks, function (idx, tick) {
      var t = $('<div class="tick"></div>').css('left', tick + '%');
      var tl = $('<div class="tick-label"></div>').css('left', tick + '%').text(tick);
      tickBar.append(t);
      tickBar.append(tl);
    });
    widget._container.append(tickBar);
  }

  function createMarkers(markers, widget) {
    // Clear existing
    widget._container.find('.marker').remove();

    $.each(markers, function (idx, m) {
      var marker = $('<div class="marker"></div>').css({ left: m.value + '%' }).addClass(m.css).attr('marker-index', idx);
      widget._container.append(marker);
    });
  }

  function createBars(bars, widget) {
    // Clear existing
    widget._container.find('.bar').remove();
    $.each(bars, function (idx, bar) {
      var bar = $('<div class="bar"></div>').css({ left: 0, awidth: '0%' }).addClass(bar.css).attr('bar-index', idx).animate({
          awidth: bar.value + '%'
        });
      widget._container.append(bar);
    });

  }

Tất cả các function tính toán dựa trên tỷ lệ phần trăm. Điều này đảm bảo rằng biểu đồ vẫn hoạt động tốt đẹp khi bạn thay đổi kích thước các phần tử bao bọc bên ngoài.

The Default Options

Nếu không có bất kỳ tùy chọn nào được xác định khi tạo các widget, tùy chọn mặc định sẽ được sử dụng. Đây là vai trò của thuộc tính options. Đối với các bulletchart, tùy chọn mặc định của chúng ta như sau:

$.widget('nt.bulletchart', {
    options: {
      // percentage: 0 - 100
      size: 100,
      //  [{ title: 'Sample Bar', value: 75, css: ' }],
      bars: [],
      //  [{ title: 'Sample Marker', value: 60, css: ' }],
      markers: [],
      // ticks -- percent values
      ticks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
    },
    ...
}

Chúng ta bắt đầu với size là 100%, không có bar, marker và các nấc chia tỷ lệ cách nhau 10%. Với những giá trị mặc định đó, biểu đồ của ta sẽ trông giống như:

default-chart.png

Đến đây, chúng ta đã biết làm thế nào để tạo ra các widget bằng phương thức _create và cập nhật nó bằng cách sử dụng _setOption. Có một phương thức vòng đời khác, sẽ được gọi khi bạn xóa bỏ một widget. Đây là phương thức _destroy. Khi bạn gọi $$'#elem').Bulletchart('destroy'), bên trong widget factory sẽ gọi _destroy trong thể hiện của widget của bạn. Các widget có trách nhiệm loại bỏ tất cả mọi thứ mà nó đưa vào DOM. Điều này có thể bao gồm các class và các phần tử DOM khác đã được thêm vào trong phương thức _create. Đây cũng là một nơi tốt để gỡ bỏ các event handler đã được khai báo. Phương thức _destroy chính là đối nghịch của phương thức _create.

Đối với bullet chart widget, phương thức _destroy chỉ đơn giản như sau:

_destroy: function () {
  this.element.removeClass('bullet-chart');
  this.element.empty();
},

Ở bài viết sau, chúng ta sẽ tiếp tục tim hiểu Subclassing, bắt sự kiện và một vài phương thức đặc biệt khác.

0