Subversion Repositories ALCASAR

Rev

Details | Last modification | View Log

Rev Author Line No. Line
2565 lucas.echa 1
(function ($) {
2
  'use strict';
3
 
4
  var DayScheduleSelector = function (el, options) {
5
    this.$el = $(el);
6
    this.options = $.extend({}, DayScheduleSelector.DEFAULTS, options);
7
    this.render();
8
    this.attachEvents();
9
    this.$selectingStart = null;
10
  }
11
 
12
  DayScheduleSelector.DEFAULTS = {
13
    days        : [0, 1, 2, 3, 4, 5, 6],  // Sun - Sat
14
    startTime   : '08:00',                // HH:mm format
15
    endTime     : '20:00',                // HH:mm format
16
    interval    : 30,                     // minutes
17
    stringDays  : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
18
    template    : '<div class="day-schedule-selector">'         +
19
                    '<table class="schedule-table">'            +
20
                      '<thead class="schedule-header"></thead>' +
21
                      '<tbody class="schedule-rows"></tbody>'   +
22
                    '</table>'                                  +
23
                  '<div>'
24
  };
25
 
26
  /**
27
   * Render the calendar UI
28
   * @public
29
   */
30
  DayScheduleSelector.prototype.render = function () {
31
    this.$el.html(this.options.template);
32
    this.renderHeader();
33
    this.renderRows();
34
  };
35
 
36
  /**
37
   * Render the calendar header
38
   * @public
39
   */
40
  DayScheduleSelector.prototype.renderHeader = function () {
41
    var stringDays = this.options.stringDays
42
      , days = this.options.days
43
      , html = '';
44
 
45
    $.each(days, function (i, _) { html += '<th>' + (stringDays[i] || '') + '</th>'; });
46
    this.$el.find('.schedule-header').html('<tr><th></th>' + html + '</tr>');
47
  };
48
 
49
  /**
50
   * Render the calendar rows, including the time slots and labels
51
   * @public
52
   */
53
  DayScheduleSelector.prototype.renderRows = function () {
54
    var start = this.options.startTime
55
      , end = this.options.endTime
56
      , interval = this.options.interval
57
      , days = this.options.days
58
      , $el = this.$el.find('.schedule-rows');
59
 
60
    $.each(generateDates(start, end, interval), function (i, d) {
61
      var daysInARow = $.map(new Array(days.length), function (_, i) {
62
        return '<td class="time-slot" data-time="' + hhmm(d) + '" data-day="' + days[i] + '"></td>'
63
      }).join();
64
 
65
      $el.append('<tr><td class="time-label">' + hmmAmPm(d) + '</td>' + daysInARow + '</tr>');
66
    });
67
  };
68
 
69
  /**
70
   * Is the day schedule selector in selecting mode?
71
   * @public
72
   */
73
  DayScheduleSelector.prototype.isSelecting = function () {
74
    return !!this.$selectingStart;
75
  }
76
 
77
  DayScheduleSelector.prototype.select = function ($slot) { $slot.attr('data-selected', 'selected'); }
78
  DayScheduleSelector.prototype.deselect = function ($slot) { $slot.removeAttr('data-selected'); }
79
 
80
  function isSlotSelected($slot) { return $slot.is('[data-selected]'); }
81
  function isSlotSelecting($slot) { return $slot.is('[data-selecting]'); }
82
 
83
  /**
84
   * Get the selected time slots given a starting and a ending slot
85
   * @private
86
   * @returns {Array} An array of selected time slots
87
   */
88
  function getSelection(plugin, $a, $b) {
89
    var $slots, small, large, temp;
90
    if (!$a.hasClass('time-slot') || !$b.hasClass('time-slot') ||
91
        ($a.data('day') != $b.data('day'))) { return []; }
92
    $slots = plugin.$el.find('.time-slot[data-day="' + $a.data('day') + '"]');
93
    small = $slots.index($a); large = $slots.index($b);
94
    if (small > large) { temp = small; small = large; large = temp; }
95
    return $slots.slice(small, large + 1);
96
  }
97
 
98
  DayScheduleSelector.prototype.attachEvents = function () {
99
    var plugin = this
100
      , options = this.options
101
      , $slots;
102
 
103
    this.$el.on('click', '.time-slot', function () {
104
      var day = $(this).data('day');
105
      if (!plugin.isSelecting()) {  // if we are not in selecting mode
106
        /*if (isSlotSelected($(this))) {
107
          plugin.deselect($(this));
108
          plugin.$el.trigger('selected.artsy.dayScheduleSelector', [getSelection(plugin, $(this), $(this))]);
109
        } else {*/  // then start selecting
110
          plugin.$selectingStart = $(this);
111
          plugin.$selectMode = isSlotSelected($(this));
112
          if(plugin.$selectMode === false) {
113
            $(this).attr('data-selecting', 'selecting');
114
          } else {
115
            $(this).attr('data-unselecting', 'unselecting');
116
          }
117
          plugin.$el.find('.time-slot').attr('data-disabled', 'disabled');
118
          plugin.$el.find('.time-slot[data-day="' + day + '"]').removeAttr('data-disabled');
119
        //}
120
      } else {  // if we are in selecting mode
121
        if (day == plugin.$selectingStart.data('day')) {  // if clicking on the same day column
122
          // then end of selection
123
          if(plugin.$selectMode === false) {
124
            plugin.$el.find('.time-slot[data-day="' + day + '"]').filter('[data-selecting]')
125
              .attr('data-selected', 'selected').removeAttr('data-selecting');
126
          } else {
127
            plugin.$el.find('.time-slot[data-day="' + day + '"]').filter('[data-unselecting]')
128
              .removeAttr('data-selected').removeAttr('data-unselecting');
129
          }
130
          plugin.$el.find('.time-slot').removeAttr('data-disabled');
131
          plugin.$el.trigger('selected.artsy.dayScheduleSelector', [getSelection(plugin, plugin.$selectingStart, $(this))]);
132
          plugin.$selectingStart = null;
133
          plugin.$selectMode = null;
134
        }
135
      }
136
    });
137
 
138
    this.$el.on('mouseover', '.time-slot', function () {
139
      var $slots, day, start, end, temp;
140
      if (plugin.isSelecting()) {  // if we are in selecting mode
141
        day = plugin.$selectingStart.data('day');
142
        $slots = plugin.$el.find('.time-slot[data-day="' + day + '"]');
143
        if (plugin.$selectMode === false) {
144
          $slots.filter('[data-selecting]').removeAttr('data-selecting');
145
        } else {
146
          $slots.filter('[data-unselecting]').removeAttr('data-unselecting');
147
        }
148
        start = $slots.index(plugin.$selectingStart);
149
        end = $slots.index(this);
150
        if (end < 0) return;  // not hovering on the same column
151
        if (start > end) { temp = start; start = end; end = temp; }
152
        if (plugin.$selectMode === false) {
153
          $slots.slice(start, end + 1).attr('data-selecting', 'selecting');
154
        } else {
155
          $slots.slice(start, end + 1).attr('data-unselecting', 'unselecting');
156
        }
157
      }
158
    });
159
  };
160
 
161
  /**
162
   * Serialize the selections
163
   * @public
164
   * @returns {Object} An object containing the selections of each day, e.g.
165
   *    {
166
   *      0: [],
167
   *      1: [["15:00", "16:30"]],
168
   *      2: [],
169
   *      3: [],
170
   *      5: [["09:00", "12:30"], ["15:00", "16:30"]],
171
   *      6: []
172
   *    }
173
   */
174
  DayScheduleSelector.prototype.serialize = function () {
175
    var plugin = this
176
      , selections = {};
177
 
178
    $.each(this.options.days, function (_, v) {
179
      var start, end;
180
      start = end = false; selections[v] = [];
181
      plugin.$el.find(".time-slot[data-day='" + v + "']").each(function () {
182
        // Start of selection
183
        if (isSlotSelected($(this)) && !start) {
184
          start = $(this).data('time');
185
        }
186
 
187
        // End of selection (I am not selected, so select until my previous one.)
188
        if (!isSlotSelected($(this)) && !!start) {
189
          end = $(this).data('time');
190
        }
191
 
192
        // End of selection (I am the last one :) .)
193
        if (isSlotSelected($(this)) && !!start && $(this).is(".time-slot[data-day='" + v + "']:last")) {
194
          end = secondsSinceMidnightToHhmm(
195
            hhmmToSecondsSinceMidnight($(this).data('time')) + plugin.options.interval * 60);
196
        }
197
 
198
        if (!!end) { selections[v].push([start, end]); start = end = false; }
199
      });
200
    })
201
    return selections;
202
  };
203
 
204
  /**
205
   * Deserialize the schedule and render on the UI
206
   * @public
207
   * @param {Object} schedule An object containing the schedule of each day, e.g.
208
   *    {
209
   *      0: [],
210
   *      1: [["15:00", "16:30"]],
211
   *      2: [],
212
   *      3: [],
213
   *      5: [["09:00", "12:30"], ["15:00", "16:30"]],
214
   *      6: []
215
   *    }
216
   */
217
  DayScheduleSelector.prototype.deserialize = function (schedule) {
218
    // Clean old data
219
    $(".time-slot").removeAttr('data-selected');
220
 
221
    var plugin = this, i;
222
    $.each(schedule, function(d, ds) {
223
      var $slots = plugin.$el.find('.time-slot[data-day="' + d + '"]');
224
      $.each(ds, function(_, s) {
225
        for (i = 0; i < $slots.length; i++) {
226
          if ($slots.eq(i).data('time') >= s[1]) { break; }
227
          if ($slots.eq(i).data('time') >= s[0]) {
228
            plugin.select($slots.eq(i));
229
          }
230
        }
231
      })
232
    });
233
  };
234
 
235
  // DayScheduleSelector Plugin Definition
236
  // =====================================
237
 
238
  function Plugin(option) {
239
    return this.each(function (){
240
      var $this   = $(this)
241
        , data    = $this.data('artsy.dayScheduleSelector')
242
        , options = typeof option == 'object' && option;
243
 
244
      if (!data) {
245
        $this.data('artsy.dayScheduleSelector', (data = new DayScheduleSelector(this, options)));
246
      }
247
    })
248
  }
249
 
250
  $.fn.dayScheduleSelector = Plugin;
251
 
252
  /**
253
   * Generate Date objects for each time slot in a day
254
   * @private
255
   * @param {String} start Start time in HH:mm format, e.g. "08:00"
256
   * @param {String} end End time in HH:mm format, e.g. "21:00"
257
   * @param {Number} interval Interval of each time slot in minutes, e.g. 30 (minutes)
258
   * @returns {Array} An array of Date objects representing the start time of the time slots
259
   */
260
  function generateDates(start, end, interval) {
261
    var numOfRows = Math.ceil(timeDiff(start, end) / interval);
262
    return $.map(new Array(numOfRows), function (_, i) {
263
      // need a dummy date to utilize the Date object
264
      return new Date(new Date(2000, 0, 1, start.split(':')[0], start.split(':')[1]).getTime() + i * interval * 60000);
265
    });
266
  }
267
 
268
  /**
269
   * Return time difference in minutes
270
   * @private
271
   */
272
  function timeDiff(start, end) {   // time in HH:mm format
273
    // need a dummy date to utilize the Date object
274
    return (new Date(2000, 0, 1, end.split(':')[0], end.split(':')[1]).getTime() -
275
            new Date(2000, 0, 1, start.split(':')[0], start.split(':')[1]).getTime()) / 60000;
276
  }
277
 
278
  /**
279
   * Convert a Date object to time in H:mm format with am/pm
280
   * @private
281
   * @returns {String} Time in H:mm format with am/pm, e.g. '9:30am'
282
   */
283
  function hmmAmPm(date) {
284
    var hours = date.getHours()
285
      , minutes = date.getMinutes()
286
      , ampm = hours >= 12 ? 'pm' : 'am';
287
    return hours + ':' + ('0' + minutes).slice(-2) + ampm;
288
  }
289
 
290
  /**
291
   * Convert a Date object to time in HH:mm format
292
   * @private
293
   * @returns {String} Time in HH:mm format, e.g. '09:30'
294
   */
295
  function hhmm(date) {
296
    var hours = date.getHours()
297
      , minutes = date.getMinutes();
298
    return ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
299
  }
300
 
301
  function hhmmToSecondsSinceMidnight(hhmm) {
302
    var h = hhmm.split(':')[0]
303
      , m = hhmm.split(':')[1];
304
    return parseInt(h, 10) * 60 * 60 + parseInt(m, 10) * 60;
305
  }
306
 
307
  /**
308
   * Convert seconds since midnight to HH:mm string, and simply
309
   * ignore the seconds.
310
   */
311
  function secondsSinceMidnightToHhmm(seconds) {
312
    var minutes = Math.floor(seconds / 60);
313
    return ('0' + Math.floor(minutes / 60)).slice(-2) + ':' +
314
           ('0' + (minutes % 60)).slice(-2);
315
  }
316
 
317
  // Expose some utility functions
318
  window.DayScheduleSelector = {
319
    ssmToHhmm: secondsSinceMidnightToHhmm,
320
    hhmmToSsm: hhmmToSecondsSinceMidnight
321
  };
322
 
323
})(jQuery);