<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

chromeSpec('chrome.socket', function(runningInBackground) {
  it('should contain definitions', function() {
    expect(chrome.socket).toBeDefined();
    expect(chrome.socket.destroy).toBeDefined();
    expect(chrome.socket.connect).toBeDefined();
    expect(chrome.socket.bind).toBeDefined();
    expect(chrome.socket.disconnect).toBeDefined();
    expect(chrome.socket.read).toBeDefined();
    expect(chrome.socket.write).toBeDefined();
    expect(chrome.socket.recvFrom).toBeDefined();
    expect(chrome.socket.sendTo).toBeDefined();
    expect(chrome.socket.listen).toBeDefined();
    expect(chrome.socket.accept).toBeDefined();
    expect(chrome.socket.setKeepAlive).toBeDefined();
    expect(chrome.socket.setNoDelay).toBeDefined();
    expect(chrome.socket.getInfo).toBeDefined();
    expect(chrome.socket.getNetworkList).toBeDefined();
  });

  it('should have properties', function() {
  });

  describe('TCP tests', function() {
    var addr = '127.0.0.1';
    var port = 1234;
    var arr = new Uint8Array(256);
    for (var i = 0; i < arr.length; i++) {
      arr[i] = i;
    }
    var data = arr.buffer;

    itWaitsForDone('accept connect read write', function(done) {
      chrome.socket.create('tcp', {}, function(socketInfo) {
        expect(socketInfo).toBeTruthy();
        expect(socketInfo.socketId).toBeDefined();

        chrome.socket.listen(socketInfo.socketId, addr, port, function(listenResult) {
          expect(listenResult).toEqual(0);

          chrome.socket.accept(socketInfo.socketId, function(acceptInfo) {
            expect(acceptInfo).toBeTruthy();
            expect(acceptInfo.resultCode).toEqual(0);
            expect(acceptInfo.socketId).toBeDefined();

            chrome.socket.read(acceptInfo.socketId, function(readResult) {
              expect(readResult).toBeTruthy();
              expect(readResult.resultCode).toBeGreaterThan(0);
              expect(readResult.data).toBeTruthy();

              expect(Object.prototype.toString.call(data).slice(8,-1)).toEqual('ArrayBuffer');
              expect(Object.prototype.toString.call(readResult.data).slice(8,-1)).toEqual('ArrayBuffer');

              var sent = new Uint8Array(data);
              var recv = new Uint8Array(readResult.data);

              expect(recv.length).toEqual(sent.length);
              for (var i = 0; i < recv.length; i++) {
                expect(recv[i]).toEqual(sent[i]);
              }

              chrome.socket.disconnect(acceptInfo.socketId);
              chrome.socket.destroy(acceptInfo.socketId);
              chrome.socket.disconnect(socketInfo.socketId);
              chrome.socket.destroy(socketInfo.socketId);

              done();
            });
          });

          chrome.socket.create('tcp', function(socketInfo) {
            expect(socketInfo).toBeTruthy();
            expect(socketInfo.socketId).toBeDefined();

            chrome.socket.connect(socketInfo.socketId, addr, port, function(connectResult) {
              expect(connectResult).toEqual(0);

              chrome.socket.write(socketInfo.socketId, data, function(writeResult) {
                expect(writeResult.bytesWritten).toBeGreaterThan(0);

                chrome.socket.disconnect(socketInfo.socketId);
                chrome.socket.destroy(socketInfo.socketId);
              });
            });
          });
        });
      });
    });

    itWaitsForDone('connect before accept', function(done) {
      chrome.socket.create('tcp', {}, function(socketInfo1) {
        expect(socketInfo1).toBeTruthy();
        expect(socketInfo1.socketId).toBeDefined();

        chrome.socket.listen(socketInfo1.socketId, addr, port, function(listenResult) {
          expect(listenResult).toEqual(0);

          chrome.socket.create('tcp', function(socketInfo2) {
            expect(socketInfo2).toBeTruthy();
            expect(socketInfo2.socketId).toBeDefined();

            chrome.socket.connect(socketInfo2.socketId, addr, port, function(connectResult) {
              expect(connectResult).toEqual(0);

              chrome.socket.disconnect(socketInfo2.socketId);
              chrome.socket.destroy(socketInfo2.socketId);
            });

            setTimeout(function() {
              chrome.socket.accept(socketInfo1.socketId, function(acceptInfo) {
                expect(acceptInfo).toBeTruthy();
                expect(acceptInfo.resultCode).toEqual(0);
                expect(acceptInfo.socketId).toBeDefined();

                chrome.socket.disconnect(acceptInfo.socketId);
                chrome.socket.destroy(acceptInfo.socketId);
                chrome.socket.disconnect(socketInfo1.socketId);
                chrome.socket.destroy(socketInfo1.socketId);

                done();
              });
            }, 100);
          });
        });
      });
    });

  });

  describe('UDP tests', function() {
    var addr = '127.0.0.1';
    var port = 1234;
    var arr = new Uint8Array(256);
    for (var i = 0; i < arr.length; i++) {
      arr[i] = i;
    }
    var data = arr.buffer;

    itWaitsForDone('bind recvFrom sendTo', function(done) {
      chrome.socket.create('udp', {}, function(socketInfo) {
        expect(socketInfo).toBeTruthy();
        expect(socketInfo.socketId).toBeDefined();

        chrome.socket.bind(socketInfo.socketId, addr, port, function(bindResult) {
          expect(bindResult).toEqual(0);

          chrome.socket.recvFrom(socketInfo.socketId, function(readResult) {
            expect(readResult).toBeTruthy();
            expect(readResult.resultCode).toBeGreaterThan(0);
            expect(readResult.data).toBeDefined();
            expect(readResult.address).toBeDefined();
            expect(readResult.port).toBeDefined();

            expect(Object.prototype.toString.call(data).slice(8,-1)).toEqual('ArrayBuffer');
            expect(Object.prototype.toString.call(readResult.data).slice(8,-1)).toEqual('ArrayBuffer');

            var sent = new Uint8Array(data);
            var recv = new Uint8Array(readResult.data);

            expect(recv.length).toEqual(sent.length);
            for (var i = 0; i < recv.length; i++) {
              expect(recv[i]).toEqual(sent[i]);
            }

            chrome.socket.sendTo(socketInfo.socketId, data, readResult.address, readResult.port, function(writeResult) {
              chrome.socket.destroy(socketInfo.socketId);
            });
          });

          chrome.socket.create('udp', function(socketInfo) {
            expect(socketInfo).toBeTruthy();
            expect(socketInfo.socketId).toBeDefined();

            chrome.socket.sendTo(socketInfo.socketId, data, addr, port, function(writeResult) {
              expect(writeResult).toBeTruthy();
              expect(writeResult.bytesWritten).toBeGreaterThan(0);

              chrome.socket.recvFrom(socketInfo.socketId, function(readResult) {
                chrome.socket.destroy(socketInfo.socketId);

                done();
              });
            });
          });
        });
      });
    });

    itWaitsForDone('bind connect x2 read write', function(done) {
      chrome.socket.create('udp', {}, function(socketInfo1) {
        expect(socketInfo1).toBeTruthy();
        expect(socketInfo1.socketId).toBeDefined();

        chrome.socket.bind(socketInfo1.socketId, addr, port, function(bindResult1) {
          expect(bindResult1).toEqual(0);

          chrome.socket.create('udp', function(socketInfo2) {
            expect(socketInfo2).toBeTruthy();
            expect(socketInfo2.socketId).toBeDefined();

            chrome.socket.bind(socketInfo2.socketId, addr, port+1, function(bindResult2) {
              expect(bindResult2).toEqual(0);

              chrome.socket.connect(socketInfo1.socketId, addr, port+1, function(connectResult1) {
                expect(connectResult1).toEqual(0);

                chrome.socket.connect(socketInfo2.socketId, addr, port, function(connectResult2) {
                  expect(connectResult2).toEqual(0);

                  chrome.socket.read(socketInfo1.socketId, function(readResult) {
                    expect(readResult).toBeTruthy();
                    expect(readResult.resultCode).toBeGreaterThan(0);
                    expect(readResult.data).toBeTruthy();

                    expect(Object.prototype.toString.call(data).slice(8,-1)).toEqual('ArrayBuffer');
                    expect(Object.prototype.toString.call(readResult.data).slice(8,-1)).toEqual('ArrayBuffer');

                    var sent = new Uint8Array(data);
                    var recv = new Uint8Array(readResult.data);

                    expect(recv.length).toEqual(sent.length);
                    for (var i = 0; i < recv.length; i++) {
                      expect(recv[i]).toEqual(sent[i]);
                    }

                    chrome.socket.destroy(socketInfo1.socketId);

                    done();
                  });

                  chrome.socket.write(socketInfo2.socketId, data, function(writeResult) {
                    expect(writeResult.bytesWritten).toBeGreaterThan(0);

                    chrome.socket.destroy(socketInfo2.socketId);
                  });

                });
              });

            });
          });

        });
      });
    });

  });
});
<commit_msg>Make sure port is available in tests<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

chromeSpec('chrome.socket', function(runningInBackground) {
  it('should contain definitions', function() {
    expect(chrome.socket).toBeDefined();
    expect(chrome.socket.destroy).toBeDefined();
    expect(chrome.socket.connect).toBeDefined();
    expect(chrome.socket.bind).toBeDefined();
    expect(chrome.socket.disconnect).toBeDefined();
    expect(chrome.socket.read).toBeDefined();
    expect(chrome.socket.write).toBeDefined();
    expect(chrome.socket.recvFrom).toBeDefined();
    expect(chrome.socket.sendTo).toBeDefined();
    expect(chrome.socket.listen).toBeDefined();
    expect(chrome.socket.accept).toBeDefined();
    expect(chrome.socket.setKeepAlive).toBeDefined();
    expect(chrome.socket.setNoDelay).toBeDefined();
    expect(chrome.socket.getInfo).toBeDefined();
    expect(chrome.socket.getNetworkList).toBeDefined();
  });

  it('should have properties', function() {
  });

  describe('TCP', function() {
    var addr = '127.0.0.1';
    var port = 1234;
    var arr = new Uint8Array(256);
    for (var i = 0; i < arr.length; i++) {
      arr[i] = i;
    }
    var data = arr.buffer;

    itWaitsForDone('port is available (sanity test)', function(done) {
      chrome.socket.create('tcp', function(createInfo) {
        chrome.socket.listen(createInfo.socketId, addr, port, function(listenResult) {
          expect(listenResult).toEqual(0);
          chrome.socket.destroy(createInfo.socketId);
        });
      });
    });

    itWaitsForDone('port is available (sanity test)', function(done) {
      chrome.socket.create('tcp', function(createInfo) {
        chrome.socket.listen(createInfo.socketId, addr, port, function(listenResult) {
          expect(listenResult).toEqual(0);
          chrome.socket.destroy(createInfo.socketId);
        });
      });
    });

    itWaitsForDone('accept connect read write', function(done) {
      chrome.socket.create('tcp', {}, function(socketInfo) {
        expect(socketInfo).toBeTruthy();
        expect(socketInfo.socketId).toBeDefined();

        chrome.socket.listen(socketInfo.socketId, addr, port, function(listenResult) {
          expect(listenResult).toEqual(0);

          chrome.socket.accept(socketInfo.socketId, function(acceptInfo) {
            expect(acceptInfo).toBeTruthy();
            expect(acceptInfo.resultCode).toEqual(0);
            expect(acceptInfo.socketId).toBeDefined();

            chrome.socket.read(acceptInfo.socketId, function(readResult) {
              expect(readResult).toBeTruthy();
              expect(readResult.resultCode).toBeGreaterThan(0);
              expect(readResult.data).toBeTruthy();

              expect(Object.prototype.toString.call(data).slice(8,-1)).toEqual('ArrayBuffer');
              expect(Object.prototype.toString.call(readResult.data).slice(8,-1)).toEqual('ArrayBuffer');

              var sent = new Uint8Array(data);
              var recv = new Uint8Array(readResult.data);

              expect(recv.length).toEqual(sent.length);
              for (var i = 0; i < recv.length; i++) {
                expect(recv[i]).toEqual(sent[i]);
              }

              chrome.socket.disconnect(acceptInfo.socketId);
              chrome.socket.destroy(acceptInfo.socketId);
              chrome.socket.disconnect(socketInfo.socketId);
              chrome.socket.destroy(socketInfo.socketId);

              done();
            });
          });

          chrome.socket.create('tcp', function(socketInfo) {
            expect(socketInfo).toBeTruthy();
            expect(socketInfo.socketId).toBeDefined();

            chrome.socket.connect(socketInfo.socketId, addr, port, function(connectResult) {
              expect(connectResult).toEqual(0);

              chrome.socket.write(socketInfo.socketId, data, function(writeResult) {
                expect(writeResult.bytesWritten).toBeGreaterThan(0);

                chrome.socket.disconnect(socketInfo.socketId);
                chrome.socket.destroy(socketInfo.socketId);
              });
            });
          });
        });
      });
    });

    itWaitsForDone('connect before accept', function(done) {
      chrome.socket.create('tcp', {}, function(socketInfo1) {
        expect(socketInfo1).toBeTruthy();
        expect(socketInfo1.socketId).toBeDefined();

        chrome.socket.listen(socketInfo1.socketId, addr, port, function(listenResult) {
          expect(listenResult).toEqual(0);

          chrome.socket.create('tcp', function(socketInfo2) {
            expect(socketInfo2).toBeTruthy();
            expect(socketInfo2.socketId).toBeDefined();

            chrome.socket.connect(socketInfo2.socketId, addr, port, function(connectResult) {
              expect(connectResult).toEqual(0);

              chrome.socket.disconnect(socketInfo2.socketId);
              chrome.socket.destroy(socketInfo2.socketId);
            });

            setTimeout(function() {
              chrome.socket.accept(socketInfo1.socketId, function(acceptInfo) {
                expect(acceptInfo).toBeTruthy();
                expect(acceptInfo.resultCode).toEqual(0);
                expect(acceptInfo.socketId).toBeDefined();

                chrome.socket.disconnect(acceptInfo.socketId);
                chrome.socket.destroy(acceptInfo.socketId);
                chrome.socket.disconnect(socketInfo1.socketId);
                chrome.socket.destroy(socketInfo1.socketId);

                done();
              });
            }, 100);
          });
        });
      });
    });

  });

  describe('UDP', function() {
    var addr = '127.0.0.1';
    var port = 1234;
    var arr = new Uint8Array(256);
    for (var i = 0; i < arr.length; i++) {
      arr[i] = i;
    }
    var data = arr.buffer;

    itWaitsForDone('bind recvFrom sendTo', function(done) {
      chrome.socket.create('udp', {}, function(socketInfo) {
        expect(socketInfo).toBeTruthy();
        expect(socketInfo.socketId).toBeDefined();

        chrome.socket.bind(socketInfo.socketId, addr, port, function(bindResult) {
          expect(bindResult).toEqual(0);

          chrome.socket.recvFrom(socketInfo.socketId, function(readResult) {
            expect(readResult).toBeTruthy();
            expect(readResult.resultCode).toBeGreaterThan(0);
            expect(readResult.data).toBeDefined();
            expect(readResult.address).toBeDefined();
            expect(readResult.port).toBeDefined();

            expect(Object.prototype.toString.call(data).slice(8,-1)).toEqual('ArrayBuffer');
            expect(Object.prototype.toString.call(readResult.data).slice(8,-1)).toEqual('ArrayBuffer');

            var sent = new Uint8Array(data);
            var recv = new Uint8Array(readResult.data);

            expect(recv.length).toEqual(sent.length);
            for (var i = 0; i < recv.length; i++) {
              expect(recv[i]).toEqual(sent[i]);
            }

            chrome.socket.sendTo(socketInfo.socketId, data, readResult.address, readResult.port, function(writeResult) {
              chrome.socket.destroy(socketInfo.socketId);
            });
          });

          chrome.socket.create('udp', function(socketInfo) {
            expect(socketInfo).toBeTruthy();
            expect(socketInfo.socketId).toBeDefined();

            chrome.socket.sendTo(socketInfo.socketId, data, addr, port, function(writeResult) {
              expect(writeResult).toBeTruthy();
              expect(writeResult.bytesWritten).toBeGreaterThan(0);

              chrome.socket.recvFrom(socketInfo.socketId, function(readResult) {
                chrome.socket.destroy(socketInfo.socketId);

                done();
              });
            });
          });
        });
      });
    });

    itWaitsForDone('bind connect x2 read write', function(done) {
      chrome.socket.create('udp', {}, function(socketInfo1) {
        expect(socketInfo1).toBeTruthy();
        expect(socketInfo1.socketId).toBeDefined();

        chrome.socket.bind(socketInfo1.socketId, addr, port, function(bindResult1) {
          expect(bindResult1).toEqual(0);

          chrome.socket.create('udp', function(socketInfo2) {
            expect(socketInfo2).toBeTruthy();
            expect(socketInfo2.socketId).toBeDefined();

            chrome.socket.bind(socketInfo2.socketId, addr, port+1, function(bindResult2) {
              expect(bindResult2).toEqual(0);

              chrome.socket.connect(socketInfo1.socketId, addr, port+1, function(connectResult1) {
                expect(connectResult1).toEqual(0);

                chrome.socket.connect(socketInfo2.socketId, addr, port, function(connectResult2) {
                  expect(connectResult2).toEqual(0);

                  chrome.socket.read(socketInfo1.socketId, function(readResult) {
                    expect(readResult).toBeTruthy();
                    expect(readResult.resultCode).toBeGreaterThan(0);
                    expect(readResult.data).toBeTruthy();

                    expect(Object.prototype.toString.call(data).slice(8,-1)).toEqual('ArrayBuffer');
                    expect(Object.prototype.toString.call(readResult.data).slice(8,-1)).toEqual('ArrayBuffer');

                    var sent = new Uint8Array(data);
                    var recv = new Uint8Array(readResult.data);

                    expect(recv.length).toEqual(sent.length);
                    for (var i = 0; i < recv.length; i++) {
                      expect(recv[i]).toEqual(sent[i]);
                    }

                    chrome.socket.destroy(socketInfo1.socketId);

                    done();
                  });

                  chrome.socket.write(socketInfo2.socketId, data, function(writeResult) {
                    expect(writeResult.bytesWritten).toBeGreaterThan(0);

                    chrome.socket.destroy(socketInfo2.socketId);
                  });

                });
              });

            });
          });

        });
      });
    });

  });
});
<|endoftext|>


<commit_before>Ext.ns('Ext.ux.touch');
Ext.ux.touch.DateTimePicker = Ext.extend(Ext.Picker, {

	/**
	 * @cfg {Number} minuteScale
	 * The distance between minute's timestamps the date time picker.  Defaults to 5
	 */
	minuteScale: 5,
    /**
     * @cfg {Number} yearFrom
     * The start year for the date picker.  Defaults to 1980
     */
    yearFrom: 2011,

    /**
     * @cfg {Number} yearTo
     * The last year for the date picker.  Defaults to the current year.
     */
    yearTo: new Date().getFullYear() + 1,

    /**
     * @cfg {String} monthText
     * The label to show for the month column. Defaults to 'Month'.
     */
    monthText: 'Month',

    /**
     * @cfg {String} dayText
     * The label to show for the day column. Defaults to 'Day'.
     */
    dayText: 'Day',

    /**
     * @cfg {String} yearText
     * The label to show for the year column. Defaults to 'Year'.
     */
    yearText: 'Year',

    /**
     * @cfg {String} hourText
     * The label to show for the hour column. Defaults to 'Hour'.
     */
    hourText: 'Hour',

	showYears: false,

	useShortMonthNames: true,
    /**
     * @cfg {String} minuteText
     * The label to show for the minute column. Defaults to 'Minute'.
     */
    minuteText: 'Minute',

    /**
     * @cfg {String} daynightText
     * The label to show for the daynight column. Defaults to 'AM/PM'.
     */
    daynightText: 'AM/PM',

    /**
     * @cfg {Object/Date} value
     * Default value for the field and the internal {@link Ext.ux.touch.DateTimePicker} component. Accepts an object of 'year',
     * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
     *
     * Examples:
     * {year: 1989, day: 1, month: 5} = 1st May 1989.
     * new Date() = current date
     */

    /**
     * @cfg {Array} slotOrder
     * An array of strings that specifies the order of the slots. Defaults to <tt>['month', 'day', 'year']</tt>.
     */
    slotOrder: [], //this.showYears ? [, 'year', 'hour', 'minute', 'daynight'] : ['month', 'day', 'hour', 'minute', 'daynight'],

    initComponent: function() {
        var yearsFrom = this.yearFrom,
            yearsTo = this.yearTo,
            years = [],
            days = [],
            months = [],
            hours = [],
            minutes = [],
            daynight = [],
            ln, tmp, i, daysInMonth;

		this.slotOrder.push('month', 'day');
		if (this.showYears) {
			this.slotOrder.push('year');
		}

		this.slotOrder.push('hour', 'minute', 'daynight');

        // swap values if user mixes them up.
        if (yearsFrom > yearsTo) {
            tmp = yearsFrom;
            yearsFrom = yearsTo;
            yearsTo = tmp;
        }

		if ( this.showYears ) {
        for (i = yearsFrom; i <= yearsTo; i++) {
            years.push({
                text: i,
                value: i
            });
        }}

        daysInMonth = this.getDaysInMonth(1, new Date().getFullYear());
        for (i = 0; i < daysInMonth; i++) {
            days.push({
                text: i + 1,
                value: i + 1
            });
        }

        for (i = 0, ln = Date.monthNames.length; i < ln; i++) {
            months.push({
                text: this.useShortMonthNames ? Date.monthNames[i].substring(0, 3) : Date.monthNames[i],
                value: i + 1
            });
        }

        for (i = 1; i <= 12; i++) {
            hours.push({
                text: i,
                value: i
            });
        }

        for (i = 0; i < 60; i += this.minuteScale) {
            minutes.push({
                text: i < 10 ? '0' + i : i,
                value: i
            });
        }

        daynight.push({
            text: 'AM',
            value: 'AM'
        }, {
            text: 'PM',
            value: 'PM'
        });

        this.slots = [];
        Ext.each(this.slotOrder, function(item) {
            this.slots.push(this.createSlot(item, days, months, years, hours, minutes, daynight));
        }, this);

        // We need to check if the value is a Date or an object
        // TODO add support for a string?
        if (this.value) {
            var value = this.value;

            if (Ext.isDate(value)) {
                this.value = {
                    day: value.getDay(),
                    year: value.getFullYear(),
                    month: value.getMonth() + 1
                };
            } else if (Ext.isObject(value)) {
                this.value = value;
            }
        }
        Ext.ux.touch.DateTimePicker.superclass.initComponent.call(this);
    },

    createSlot: function(name, days, months, years, hours, minutes, daynight) {
        switch (name) {
        case 'year':
            return {
                name: 'year',
                align: 'center',
                data: years,
                title: this.useTitles ? this.yearText : false,
                flex: 3
            };
        case 'month':
            return {
                name: name,
                align: 'right',
                data: months,
                title: this.useTitles ? this.monthText : false,
                    flex: this.showYears ? 4 : 3
            };
        case 'day':
            return {
                name: 'day',
                align: 'center',
                data: days,
                title: this.useTitles ? this.dayText : false,
                flex: 2
            };
        case 'hour':
            return {
                name: 'hour',
                align: 'center',
                data: hours,
                title: this.useTitles ? this.hourText : false,
                flex: 2
            };
        case 'minute':
            return {
                name: 'minute',
                align: 'center',
                data: minutes,
                title: this.useTitles ? this.minuteText : false,
                flex: 2
            };
        case 'daynight':
            return {
                name: 'daynight',
                align: 'center',
                data: daynight,
                title: this.useTitles ? this.daynightText : false,
                flex: this.showYears ? 2 : 3
            };
        }
    },

    // @private
    onSlotPick: function(slot, value) {
        var name = slot.name,
            date, daysInMonth, daySlot;

        if (name === "month" || name === "year") {
            daySlot = this.child('[name=day]');
            date = this.getValue();
            daysInMonth = this.getDaysInMonth(date.getMonth() + 1, date.getFullYear());
            daySlot.store.clearFilter();
            daySlot.store.filter({
                fn: function(r) {
                    return r.get('extra') === true || r.get('value') <= daysInMonth;
                }
            });
            daySlot.scroller.updateBoundary(true);
        }

        Ext.ux.touch.DateTimePicker.superclass.onSlotPick.call(this, slot, value);
    },

    /**
     * Gets the current value as a Date object
     * @return {Date} value
     */
    getValue: function() {
        var value = Ext.ux.touch.DateTimePicker.superclass.getValue.call(this),
            daysInMonth = this.getDaysInMonth(value.month, value.year),
            day = Math.min(value.day, daysInMonth);
        if (value.daynight === 'PM') {
            value.hour += 12;
        }
		if ( this.showYears ) {
	        return new Date(value.year, value.month - 1, day, value.hour, value.minute);
		} else {
			var cur_date = new Date();
			var full_year = cur_date.getFullYear();
			if ( value.month -1  < cur_date.getMonth() ) {
				full_year = full_year + 1;
			}
			return new Date(full_year, value.month - 1, day, value.hour, value.minute);
		}
    },

    // @private
    getDaysInMonth: function(month, year) {
        var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        return month == 2 && this.isLeapYear(year) ? 29 : daysInMonth[month - 1];
    },

    // @private
    isLeapYear: function(year) {
        return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)));
    },

	setToDate: function(now_date) {
		var now_values = {};
		now_values['year'] = now_date.getFullYear();
		now_values['month'] = now_date.getMonth() + 1;
		now_values['day'] = now_date.getDate();
		var mins = now_date.getMinutes();
        if ( mins % this.minuteScale > 0 ) {
            mins = mins + ( this.minuteScale - (mins % this.minuteScale) );
		}
		now_values['minute'] = mins;
		if ( mins === 0 && now_date.getMinutes() > 0 ) {
			now_values['hour'] = now_date.getHours() + 1;
		} else {
			now_values['hour'] = now_date.getHours();
		}
		this.setValue(now_values);
	},

    setValue: function(values, animated) {
        var key, slot, items = this.items.items,
            ln = items.length;

        // Value is an object with keys mapping to slot names
        if (!values) {
            for (var i = 0; i < ln; i++) {
                items[i].setValue(0);
            }
            return this;
        } else if (Ext.isDate(values)){
            this.setToDate(values, animated);
        }
        daynightVal = 'AM';
        for (key in values) {
            slot = this.child('[name=' + key + ']');
            if (slot) {
                if (key === 'hour' && values[key] > 12) {
                    daynightVal = 'PM';
                    values[key] -= 12;
                }
                slot.setValue(values[key], animated);
            }
        }
        slot = this.child('[name=daynight]');
        slot.setValue(daynightVal, animated);
        return this;
    }
});

Ext.reg('datetimepicker', Ext.ux.touch.DateTimePicker);

Ext.form.DateTimePicker = Ext.extend(Ext.form.Field, {
    ui: 'select',

    picker: null,

	display_format: 'd/m/Y h:i a',

    destroyPickerOnHide: false,

	init_to_now: true,

    initComponent: function() {
        this.addEvents(

            'change'
        );

        this.tabIndex = -1;
        this.useMask = true;

        Ext.form.Text.superclass.initComponent.apply(this, arguments);
    },

    getDatePicker: function() {
        if (!this.dateTimePicker) {
            if (this.picker instanceof Ext.ux.touch.DateTimePicker) {
                this.dateTimePicker = this.picker;
            } else {
                this.dateTimePicker = new Ext.ux.touch.DateTimePicker(Ext.apply(this.picker || {}));
            }

            this.dateTimePicker.setValue(this.value || null);

            this.dateTimePicker.on({
                scope : this,
                change: this.onPickerChange,
                hide  : this.onPickerHide
            });
        }

        return this.dateTimePicker;
    },

    onMaskTap: function() {
        if (Ext.form.DateTimePicker.superclass.onMaskTap.apply(this, arguments) !== true) {
            return false;
        }

        this.getDatePicker().show();
		if ( this.init_to_now ) {
            this.getDatePicker().setToDate(new Date());
		}
    },

    onPickerChange : function(picker, value) {
        this.setValue(value);
        this.fireEvent('change', this, this.getValue());
    },

    onPickerHide: function() {
        if (this.destroyPickerOnHide && this.dateTimePicker) {
            this.dateTimePicker.destroy();
        }
    },

    setValue: function(value, animated) {
        if (this.dateTimePicker) {
			if ( Ext.isDate(value) ) {
            	this.dateTimePicker.setToDate(value);
			} else {
            	this.dateTimePicker.setValue(value, animated);
			}
            this.value = (value != null) ? this.dateTimePicker.getValue() : null;
        } else {
            if (!Ext.isDate(value) && !Ext.isObject(value)) {
                value = null;
            }

            if (Ext.isObject(value)) {
                this.value = new Date(value.year, value.month-1, value.day, value.hours, value.minutes);
            } else {
                this.value = value;
            }
        }

        if (this.rendered) {
            this.fieldEl.dom.value = this.getValue(true);
        }

        return this;
    },

    getValue: function(format) {
        var value = this.value || null;
        return (format && Ext.isDate(value)) ? value.format(this.display_format) : value;
    },

    onDestroy: function() {
        if (this.dateTimePicker) {
            this.dateTimePicker.destroy();
        }

        Ext.form.DateTimePicker.superclass.onDestroy.call(this);
    }
});

Ext.reg('datetimepickerfield', Ext.form.DateTimePicker);
<commit_msg>wrong DateTimePicker behavior by rounding jump to the next day/month/year, other fixes (PM/AM, etc)<commit_after>Ext.ns('Ext.ux.touch');
Ext.ux.touch.DateTimePicker = Ext.extend(Ext.Picker, {

	/**
	 * @cfg {Number} minuteScale
	 * The distance between minute's timestamps the date time picker.  Defaults to 5
	 */
	minuteScale: 5,
    /**
     * @cfg {Number} yearFrom
     * The start year for the date picker.  Defaults to 1980
     */
    yearFrom: 2011,

    /**
     * @cfg {Number} yearTo
     * The last year for the date picker.  Defaults to the current year.
     */
    yearTo: new Date().getFullYear() + 1,

    /**
     * @cfg {String} monthText
     * The label to show for the month column. Defaults to 'Month'.
     */
    monthText: 'Month',

    /**
     * @cfg {String} dayText
     * The label to show for the day column. Defaults to 'Day'.
     */
    dayText: 'Day',

    /**
     * @cfg {String} yearText
     * The label to show for the year column. Defaults to 'Year'.
     */
    yearText: 'Year',

    /**
     * @cfg {String} hourText
     * The label to show for the hour column. Defaults to 'Hour'.
     */
    hourText: 'Hour',

	showYears: false,

	useShortMonthNames: true,
    /**
     * @cfg {String} minuteText
     * The label to show for the minute column. Defaults to 'Minute'.
     */
    minuteText: 'Minute',

    /**
     * @cfg {String} daynightText
     * The label to show for the daynight column. Defaults to 'AM/PM'.
     */
    daynightText: 'AM/PM',

    /**
     * @cfg {Object/Date} value
     * Default value for the field and the internal {@link Ext.ux.touch.DateTimePicker} component. Accepts an object of 'year',
     * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
     *
     * Examples:
     * {year: 1989, day: 1, month: 5} = 1st May 1989.
     * new Date() = current date
     */

    /**
     * @cfg {Array} slotOrder
     * An array of strings that specifies the order of the slots. Defaults to <tt>['month', 'day', 'year']</tt>.
     */
    slotOrder: [], //this.showYears ? [, 'year', 'hour', 'minute', 'daynight'] : ['month', 'day', 'hour', 'minute', 'daynight'],

    initComponent: function() {
        var yearsFrom = this.yearFrom,
            yearsTo = this.yearTo,
            years = [],
            days = [],
            months = [],
            hours = [],
            minutes = [],
            daynight = [],
            ln, tmp, i, daysInMonth;

		this.slotOrder.push('month', 'day');
		if (this.showYears) {
			this.slotOrder.push('year');
		}

		this.slotOrder.push('hour', 'minute', 'daynight');

        // swap values if user mixes them up.
        if (yearsFrom > yearsTo) {
            tmp = yearsFrom;
            yearsFrom = yearsTo;
            yearsTo = tmp;
        }

		if ( this.showYears ) {
        for (i = yearsFrom; i <= yearsTo; i++) {
            years.push({
                text: i,
                value: i
            });
        }}

        daysInMonth = this.getDaysInMonth(1, new Date().getFullYear());
        for (i = 0; i < daysInMonth; i++) {
            days.push({
                text: i + 1,
                value: i + 1
            });
        }

        for (i = 0, ln = Date.monthNames.length; i < ln; i++) {
            months.push({
                text: this.useShortMonthNames ? Date.monthNames[i].substring(0, 3) : Date.monthNames[i],
                value: i + 1
            });
        }

        for (i = 1; i <= 12; i++) {
            hours.push({
                text: i,
                value: i
            });
        }

        for (i = 0; i < 60; i += this.minuteScale) {
            minutes.push({
                text: i < 10 ? '0' + i : i,
                value: i
            });
        }

        daynight.push({
            text: 'AM',
            value: 'AM'
        }, {
            text: 'PM',
            value: 'PM'
        });

        this.slots = [];
        Ext.each(this.slotOrder, function(item) {
            this.slots.push(this.createSlot(item, days, months, years, hours, minutes, daynight));
        }, this);

        // We need to check if the value is a Date or an object
        // TODO add support for a string?
        if (this.value) {
            var value = this.value;

            if (Ext.isDate(value)) {
                this.value = {
                    day: value.getDay(),
                    year: value.getFullYear(),
                    month: value.getMonth() + 1
                };
            } else if (Ext.isObject(value)) {
                this.value = value;
            }
        }
        Ext.ux.touch.DateTimePicker.superclass.initComponent.call(this);
    },

    createSlot: function(name, days, months, years, hours, minutes, daynight) {
        switch (name) {
        case 'year':
            return {
                name: 'year',
                align: 'center',
                data: years,
                title: this.useTitles ? this.yearText : false,
                flex: 3
            };
        case 'month':
            return {
                name: name,
                align: 'right',
                data: months,
                title: this.useTitles ? this.monthText : false,
                    flex: this.showYears ? 4 : 3
            };
        case 'day':
            return {
                name: 'day',
                align: 'center',
                data: days,
                title: this.useTitles ? this.dayText : false,
                flex: 2
            };
        case 'hour':
            return {
                name: 'hour',
                align: 'center',
                data: hours,
                title: this.useTitles ? this.hourText : false,
                flex: 2
            };
        case 'minute':
            return {
                name: 'minute',
                align: 'center',
                data: minutes,
                title: this.useTitles ? this.minuteText : false,
                flex: 2
            };
        case 'daynight':
            return {
                name: 'daynight',
                align: 'center',
                data: daynight,
                title: this.useTitles ? this.daynightText : false,
                flex: this.showYears ? 2 : 3
            };
        }
    },

    // @private
    onSlotPick: function(slot, value) {
        var name = slot.name,
            date, daysInMonth, daySlot;

        if (name === "month" || name === "year") {
            daySlot = this.child('[name=day]');
            date = this.getValue();
            daysInMonth = this.getDaysInMonth(date.getMonth() + 1, date.getFullYear());
            daySlot.store.clearFilter();
            daySlot.store.filter({
                fn: function(r) {
                    return r.get('extra') === true || r.get('value') <= daysInMonth;
                }
            });
            daySlot.scroller.updateBoundary(true);
        }

        Ext.ux.touch.DateTimePicker.superclass.onSlotPick.call(this, slot, value);
    },

    /**
     * Gets the current value as a Date object
     * @return {Date} value
     */
    getValue: function() {
        var value = Ext.ux.touch.DateTimePicker.superclass.getValue.call(this),
            daysInMonth = this.getDaysInMonth(value.month, value.year),
            day = Math.min(value.day, daysInMonth),
            hours = value.hour;

        if (hours === 12) {
            hours = 0;
        }
        if (value.daynight === 'PM') {
            hours += 12;
		}
        return new Date(this.showYears ? value.year: new Date().getFullYear(), value.month - 1, day, hours, value.minute);
    },

    // @private
    getDaysInMonth: function(month, year) {
        var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        return month == 2 && this.isLeapYear(year) ? 29 : daysInMonth[month - 1];
    },

    // @private
    isLeapYear: function(year) {
        return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)));
    },

	setToDate: function(now_date) {
		var now_values = {};
        var minutes = now_date.getMinutes();
        var hours = now_date.getHours();
        var day = now_date.getDate();
        var month = now_date.getMonth();
        var year = now_date.getFullYear();

        if ( minutes % this.minuteScale > 0 ) {
            minutes = minutes + ( this.minuteScale - (minutes % this.minuteScale) );
        }
        if ( minutes > 59 ) {
            minutes = 0;
            hours += 1;
        }
        if (hours > 23) {
            hours = 0;
            day += 1;
		}
        if (day > this.getDaysInMonth(month + 1, year)) {
            day = 1;
            month +=1;
        }
        if (month > 11) {
            month = 0;
            year += 1;
        }

        Ext.apply(now_values,{
            minute: minutes,
            hour: hours,
            day: day,
            month: month + 1,
            year: year
        });

		this.setValue(now_values);
	},

    setValue: function(values, animated) {
        var slot, items = this.items.items,
            ln = items.length,
            daynightVal = 'AM';

        // Value is an object with keys mapping to slot names
        if (!values) {
            for (var i = 0; i < ln; i++) {
                items[i].setValue(0);
            }
            return this;
        } else if (Ext.isDate(values)){
            this.setToDate(values, animated);
            return;
        }

        var me = this;
        Ext.iterate (values, function(key, value) {
            slot = me.child('[name=' + key + ']');
            if (slot) {
                if (key === 'hour') {
                    if (value >= 12){
                    daynightVal = 'PM';
                }
                    if (value === 0) {
                        value = 12; // 12 AM
                    } else if (value > 12){
                        value -= 12;
            }
        }
                slot.setValue(value, animated);
            }
        });
        slot = this.child('[name=daynight]');
        slot.setValue(daynightVal, animated);
        return this;
    }
});

Ext.reg('datetimepicker', Ext.ux.touch.DateTimePicker);

Ext.form.DateTimePicker = Ext.extend(Ext.form.Field, {
    ui: 'select',

    picker: null,

	display_format: 'd/m/Y h:i a',

    destroyPickerOnHide: false,

	init_to_now: true,

    initComponent: function() {
        this.addEvents(

            'change'
        );

        this.tabIndex = -1;
        this.useMask = true;

        Ext.form.Text.superclass.initComponent.apply(this, arguments);
    },

    getDatePicker: function() {
        if (!this.dateTimePicker) {
            if (this.picker instanceof Ext.ux.touch.DateTimePicker) {
                this.dateTimePicker = this.picker;
            } else {
                this.dateTimePicker = new Ext.ux.touch.DateTimePicker(Ext.apply(this.picker || {}));
            }

            this.dateTimePicker.setValue(this.value || null);

            this.dateTimePicker.on({
                scope : this,
                change: this.onPickerChange,
                hide  : this.onPickerHide
            });
        }

        return this.dateTimePicker;
    },

    onMaskTap: function() {
        if (Ext.form.DateTimePicker.superclass.onMaskTap.apply(this, arguments) !== true) {
            return false;
        }

        this.getDatePicker().show();
		if ( this.init_to_now ) {
            this.getDatePicker().setToDate(new Date());
		}
    },

    onPickerChange : function(picker, value) {
        this.setValue(value);
        this.fireEvent('change', this, this.getValue());
    },

    onPickerHide: function() {
        if (this.destroyPickerOnHide && this.dateTimePicker) {
            this.dateTimePicker.destroy();
        }
    },

    setValue: function(value, animated) {
        if (this.dateTimePicker) {
			if ( Ext.isDate(value) ) {
            	this.dateTimePicker.setToDate(value);
			} else {
            	this.dateTimePicker.setValue(value, animated);
			}
            this.value = (value != null) ? this.dateTimePicker.getValue() : null;
        } else {
            if (!Ext.isDate(value) && !Ext.isObject(value)) {
                value = null;
            }

            if (Ext.isObject(value)) {
                this.value = new Date(value.year, value.month-1, value.day, value.hours, value.minutes);
            } else {
                this.value = value;
            }
        }

        if (this.rendered) {
            this.fieldEl.dom.value = this.getValue(!!this.display_format);
        }

        return this;
    },

    getValue: function(format) {
        var value = this.value || null;
        return (format && Ext.isDate(value)) ? value.format(this.display_format) : value;
    },

    onDestroy: function() {
        if (this.dateTimePicker) {
            this.dateTimePicker.destroy();
        }

        Ext.form.DateTimePicker.superclass.onDestroy.call(this);
    }
});

Ext.reg('datetimepickerfield', Ext.form.DateTimePicker);
<|endoftext|>


<commit_before>(function () {
    'use strict';

    var m = {},
        defaults,
        LocalTimer,
        inputCache,
        SearchVars,
        autosuggestCache = {},
        LocalCache = new Meteor.Collection(null);

    // Default values
    defaults = {
        'inputTimeout' : 200,
        'reactive' : true,
        'event' : 'keyup'
    };

    // Session Abstraction
    Session.setDefault('esVariables', {});

    SearchVars = {
        'set' : function (id, obj) {
            Session.set('esVariables_' + id, _.extend(Session.get('esVariables_'  + id) || {}, obj));
        },
        'get' : function (id, key) {
            return (Session.get('esVariables_' + id) || {})[key];
        }
    };

    // Error Messages
    m.specifyIndex = 'Specify an index, for example {{> esInput index="name"}}!';

    // A simple timer
    LocalTimer = {
        'timers' : {},
        'stop' : function (id) {
            clearTimeout(this.timers[id]);
            delete this.timers[id];
        },
        'runAt' : function (id, millSec, func, params) {
            var that = this;

            this.timers[id] = setTimeout(function () {
                func(params);
                that.stop(id);
            }, parseInt(millSec, 10));
        },
        'isRunning' : function (id) {
            return "undefined" !== typeof this.timers[id];
        }
    };

    // Add the search results to cache and process it
    function processSearchResults(index, data, isReactive)
    {
        // Just store the data
        if (!isReactive) {
            LocalCache.upsert(
                { _id : index },
                { _id : index, r : isReactive, d : data.results }
            );

            return;
        }

        // If it has to be reactive
        LocalCache.upsert(
            { _id : index },
            {
                _id : index,
                r : isReactive,
                d : _.map(data.results, function(doc) { return doc._id; })
            }
        );
    }

    // Perform a search with esInput
    function esInputSearch (conf) {
        var id = conf.easySearchID,
            searchValue = conf.value,
            index = conf.easySearchIndex;

        if (searchValue.length < 1) {
            SearchVars.set(id, { 'searching' : false, 'searchingDone' : false });
            LocalCache.upsert({ _id : index }, { _id : index, d : null });
            return;
        }

        EasySearch.search(index, searchValue, function (err, data) {
            if (err) {
                throw new Meteor.Error(500, "Searching failed");
            }

            inputCache[id] = searchValue;
            SearchVars.set(id, {
                'searching' : false,
                'total' : data.total,
                'searchingDone' : true,
                'searchResults' : data.results,
                'currentValue' : searchValue
            });

            processSearchResults(index, data, conf.easySearchReactive);
        });
    }

    // Generate an id used for the components
    function generateId(index, id) {
        var generatedId = index;

        if (!generatedId) {
            throw new Meteor.Error(400, m.specifyIndex);
        }

        if (id) {
            generatedId += '_' + id;
        }

        return generatedId;
    }

    /* esInput */

    Template.esInput.events({
        'keyup input' : function (e) {
            var i, id, index,
                searchValue,
                eventScope = this,
                input = $(e.target),
                reactive = this.reactive !== "false",
                timeout = this.timeout || defaults.inputTimeout,
                event = this.event || defaults.event,
                searchValue = input.val().trim(),
                keyCode = e.keyCode || e.which;

            // Pressed not enter with enter configuration
            if ("enter" === event && 13 !== keyCode) {
                return;
            }

            if (!_.isArray(this.index)) {
                this.index = [this.index];
            }

            // Reset values if search value is empty
            if (searchValue.length === 0) {
                _.each(this.index, function (index) {
                    var id = generateId(index, eventScope.id);
                    inputCache = '';
                    SearchVars.set(id, { 'searchingDone' : false });
                    SearchVars.set(id, { 'searching' : false });
                });

                return;
            }

            // Only search when the value hasn't changed
            if (inputCache === searchValue) {
                return;
            }

            // Run the search at the specified timeout
            inputCache = searchValue;

            _.each(this.index, function (index) {
                id = generateId(index, eventScope.id);
                SearchVars.set(id, { 'searching' : false });

                // If already running, stop the timer
                if (LocalTimer.isRunning(id)) {
                    LocalTimer.stop(id);
                }

                // Set to default limit
                EasySearch.changeProperty(index, 'limit', EasySearch.getIndex(index).defaultLimit);

                LocalTimer.runAt(id, timeout, esInputSearch, {
                    value : searchValue,
                    easySearchID : id,
                    easySearchIndex : index,
                    easySearchReactive : reactive
                });

                SearchVars.set(id, { 'searchingDone' : false, 'searching' : true });
            });
        },
        'keydown input' : function (e) {
            if ($(e.target).val().length === 0) {
                SearchVars.set(generateId(this.index, this.id), { 'currentValue' : '' });
            }
        }
    });

    /* esEach */
    UI.registerHelper('esEach', function () {
        return Template.esEach;
    });

    Template.esEach.helpers({
        'elasticSearchDoc' : function () {
            var config,
                indexConf,
                isReactive,
                options = this.options || null,
                doc = LocalCache.findOne(this.index);

            if (!_.isObject(doc)) {
                return [];
            }

            if (SearchVars.get(generateId(this.index, this.id), 'searching')) {
                return [];
            }

            isReactive = doc.r;

            // Not reactive
            if (doc && doc.d && !isReactive) {
                return doc.d;
            }

            // Is reactive
            if (doc && doc.d) {
                config = EasySearch.getIndexes();
                indexConf = config[this.index];

                if (options) {
                    return indexConf.collection.find({ _id : { $in : doc.d } }, options);
                }

                // TODO: Find a more performant way than this for ordering the documents
                return _.sortBy(indexConf.collection.find({ _id : { $in : doc.d } }).fetch(), function (document) {
                    return doc.d.indexOf(document._id);
                });
            }
        }
    });

    /* ifEsIsSearching */
    Template.ifEsIsSearching.isSearching = function () {
        var id = generateId(this.index, this.id),
            isSearching = SearchVars.get(id, 'searching');

        return isSearching ? isSearching : null;
    };

    /* ifEsHasNoResults */
    Template.ifEsHasNoResults.hasNoResults = function () {
        var id = generateId(this.index, this.id),
            cache = LocalCache.findOne(this.index);

        if (SearchVars.get(id, 'searching')) {
            return false;
        }

        return cache && _.isArray(cache.d) && cache.d.length === 0;
    }

    /* esLoadMoreButton */
    Template.esLoadMoreButton.rendered = function () {
        SearchVars.set(generateId(this.data.index, this.data.id), { 'currentLimit' :  EasySearch.getIndex(this.data.index).limit });
    };

    Template.esLoadMoreButton.helpers({
        'content' : function () {
            return this.content ? this.content : 'Load more';
        },
        'hasMoreResults' : function () {
            var id = generateId(this.index, this.id);

            return !SearchVars.get(id, 'searching')
                    && (SearchVars.get(id, 'total') > SearchVars.get(id, 'currentLimit'));
        }
    });

    Template.esLoadMoreButton.events({
        'click button' : function () {
            var templateScope = this,
                id = generateId(this.index, this.id),
                currentLimit = EasySearch.getIndexes()[this.index].limit,
                howManyMore = this.howMany ? this.howMany : 10;

            templateScope.reactive = this.reactive !== "false";

            EasySearch.changeProperty(this.index, 'limit', currentLimit + howManyMore);
            EasySearch.search(this.index, SearchVars.get(id, 'currentValue'), function (err, data) {
                if (err) {
                    throw new Meteor.Error(500, "Searching failed");
                }

                SearchVars.set(id, { 'total' : data.total });
                SearchVars.set(id, { 'currentLimit' : currentLimit + howManyMore });
                processSearchResults(templateScope.index, data, templateScope.reactive);
            });
        }
    });

    /* esAutosuggest */
    Template.esAutosuggest.created = function () {
        var tplScope = this,
            id = generateId(tplScope.data.index, tplScope.data.id);

        SearchVars.set(id, { 'autosuggestSelected' : [] });

        Deps.autorun(function () {
            autosuggestCache.value = SearchVars.get(id, 'currentValue');
        });
    }

    Template.esAutosuggest.rendered = function () {
        var tplScope = this,
            id = generateId(tplScope.data.index, tplScope.data.id);

        Deps.autorun(function () {
            var input = $(tplScope.find('input')),
                wrapper = $(tplScope.find('div')),
                values = SearchVars.get(id, 'autosuggestSelected');

            // Reposition the input when a value gets added
            input.css('padding-left', wrapper.find('.selected.values').width());
        });
    };

    Template.esAutosuggest.helpers({
        'selectedValue' : function () {
            var id = generateId(this.index, this.id);

            return _.map(SearchVars.get(id, 'autosuggestSelected'), function (val) {
                return {
                    'id' : id,
                    'docId' : val.id,
                    'value' : val.value
                };
            });
        },
        'selected' : function () {
            var suggestionsSelected = SearchVars.get(generateId(this.options.index, this.options.id), 'suggestionsSelected');
            return _.isObject(suggestionsSelected) && suggestionsSelected.id === this.id ? 'selected' : '';
        },
        'isHidden' : function () {
            var id = generateId(this.index, this.id),
                inputValue = SearchVars.get(id, 'currentValue'),
                searchingDone = SearchVars.get(id, 'searchingDone');

            return (inputValue && inputValue.length > 0) && searchingDone  ? 'show' : 'hide';
        },
        'snippets' : function (options) {
            var regex, firstFieldValue, searchValue, parts,
                index = EasySearch.getIndex(options.index),
                firstField  = _.isArray(index.field) ? index.field[0] : index.field;

            firstFieldValue = this[firstField];
            searchValue = autosuggestCache.value,
            regex = new RegExp(searchValue + '(.+)?', "i");
            parts = firstFieldValue.split(regex);

            // Not found in the first field
            if (parts.length === 1) {
                return {
                    'pre' : firstFieldValue,
                    'value' : firstFieldValue,
                    'id' : this._id,
                    'options' : options
                };
            }

            return {
                'pre' : parts[0],
                'found' : (new RegExp(searchValue, 'i')).exec(firstFieldValue).shift(),
                'post' : parts[1],
                'value' : firstFieldValue,
                'id' : this._id,
                'options' : options
            };
        }
    });

    Template.esAutosuggest.events({
        'click .suggestion:not(.remove)' : function (e) {
            var values,
                id = generateId(this.options.index, this.options.id),
                wrapper = $(e.target).closest('.es-autosuggest-wrapper'),
                input = wrapper.find('input');

            // Add a box in front of the input which is the selected "value"
            values = SearchVars.get(id, 'autosuggestSelected');
            values.push({ 'id' : this.id, 'value' : this.value });
            SearchVars.set(id, { 'autosuggestSelected' :  values, 'currentValue' : '' });

            input.val('').keyup().keypress().keydown();

            e.preventDefault();
        },
        'click .remove' : function (e) {
            var tplScope = this;

            // Removes a suggestion
            SearchVars.set(this.id, { 'autosuggestSelected' :  _.reject(
                SearchVars.get(this.id, 'autosuggestSelected'), function (val) {
                    return val.id === tplScope.docId;
                })
            });
        },
        'keydown input' : function (e) {
            var selected, currentValues,
                id = generateId(this.index, this.id), input = $(e.target),
                selected = SearchVars.get(id, 'suggestionsSelected');

            if (!$(e.target).val() && !e.which === 8) {
                return;
            }

            if (e.which === 13 && selected && _.isObject(selected)) {
                // Enter
                currentValues = SearchVars.get(id, 'autosuggestSelected');
                currentValues.push(selected);

                SearchVars.set(id, {
                    'autosuggestSelected' : currentValues,
                });

                $(e.target).val('');
                SearchVars.set(id, { 'suggestionsSelected' : '' });
            } else if (e.which === 40 || e.which === 38) {
                // Down or Up
                var incrementalValue,
                    toBeSelectedDoc, firstField,
                    index = EasySearch.getIndex(this.index[0]),
                    suggestions = SearchVars.get(id, 'searchResults');

                if (!index || !suggestions) {
                    return;
                }

                firstField = firstField  = _.isArray(index.field) ? index.field[0] : index.field;

                // If there's none selected
                if (!selected && e.which === 40) {
                    selected = {
                        'pos' : -1
                    };
                } else if (!selected && e.which === 38) {
                    selected = {
                        'pos' : suggestions.length
                    };
                }

                // Take the one below or up, decide by key
                incrementalValue = e.which === 40 ? 1 :  -1;

                toBeSelectedDoc = suggestions[selected.pos + incrementalValue];

                if (!toBeSelectedDoc) {
                    return;
                }

                SearchVars.set(id, { 'suggestionsSelected' : {
                    'value' : toBeSelectedDoc[firstField],
                    'id' : toBeSelectedDoc._id,
                    'pos' : selected.pos + incrementalValue
                }});

                e.preventDefault();
            } else if (e.which === 8 && input.val().length === 0) {
                // Remove
                SearchVars.set(id, {
                    'autosuggestSelected' : _.initial(SearchVars.get(id, 'autosuggestSelected'))
                });
            }
        },
        'keyup input' : function () {
            var id = generateId(this.index, this.id);

            if (SearchVars.get(id, 'searching')) {
                SearchVars.set(id, { 'suggestionsSelected' : '' });
            }
        }
    });

    EasySearch.ComponentVariables = SearchVars;
    EasySearch.Components = {
        'generateId' : generateId
    };
})();
<commit_msg>Also set currentValue to empty when the input field is empty<commit_after>(function () {
    'use strict';

    var m = {},
        defaults,
        LocalTimer,
        inputCache,
        SearchVars,
        autosuggestCache = {},
        LocalCache = new Meteor.Collection(null);

    // Default values
    defaults = {
        'inputTimeout' : 200,
        'reactive' : true,
        'event' : 'keyup'
    };

    // Session Abstraction
    Session.setDefault('esVariables', {});

    SearchVars = {
        'set' : function (id, obj) {
            Session.set('esVariables_' + id, _.extend(Session.get('esVariables_'  + id) || {}, obj));
        },
        'get' : function (id, key) {
            return (Session.get('esVariables_' + id) || {})[key];
        }
    };

    // Error Messages
    m.specifyIndex = 'Specify an index, for example {{> esInput index="name"}}!';

    // A simple timer
    LocalTimer = {
        'timers' : {},
        'stop' : function (id) {
            clearTimeout(this.timers[id]);
            delete this.timers[id];
        },
        'runAt' : function (id, millSec, func, params) {
            var that = this;

            this.timers[id] = setTimeout(function () {
                func(params);
                that.stop(id);
            }, parseInt(millSec, 10));
        },
        'isRunning' : function (id) {
            return "undefined" !== typeof this.timers[id];
        }
    };

    // Add the search results to cache and process it
    function processSearchResults(index, data, isReactive)
    {
        // Just store the data
        if (!isReactive) {
            LocalCache.upsert(
                { _id : index },
                { _id : index, r : isReactive, d : data.results }
            );

            return;
        }

        // If it has to be reactive
        LocalCache.upsert(
            { _id : index },
            {
                _id : index,
                r : isReactive,
                d : _.map(data.results, function(doc) { return doc._id; })
            }
        );
    }

    // Perform a search with esInput
    function esInputSearch (conf) {
        var id = conf.easySearchID,
            searchValue = conf.value,
            index = conf.easySearchIndex;

        if (searchValue.length < 1) {
            SearchVars.set(id, { 'searching' : false, 'searchingDone' : false });
            LocalCache.upsert({ _id : index }, { _id : index, d : null });
            return;
        }

        EasySearch.search(index, searchValue, function (err, data) {
            if (err) {
                throw new Meteor.Error(500, "Searching failed");
            }

            inputCache[id] = searchValue;
            SearchVars.set(id, {
                'searching' : false,
                'total' : data.total,
                'searchingDone' : true,
                'searchResults' : data.results,
                'currentValue' : searchValue
            });

            processSearchResults(index, data, conf.easySearchReactive);
        });
    }

    // Generate an id used for the components
    function generateId(index, id) {
        var generatedId = index;

        if (!generatedId) {
            throw new Meteor.Error(400, m.specifyIndex);
        }

        if (id) {
            generatedId += '_' + id;
        }

        return generatedId;
    }

    /* esInput */

    Template.esInput.events({
        'keyup input' : function (e) {
            var i, id, index,
                searchValue,
                eventScope = this,
                input = $(e.target),
                reactive = this.reactive !== "false",
                timeout = this.timeout || defaults.inputTimeout,
                event = this.event || defaults.event,
                searchValue = input.val().trim(),
                keyCode = e.keyCode || e.which;

            // Pressed not enter with enter configuration
            if ("enter" === event && 13 !== keyCode) {
                return;
            }

            if (!_.isArray(this.index)) {
                this.index = [this.index];
            }

            // Reset values if search value is empty
            if (searchValue.length === 0) {
                _.each(this.index, function (index) {
                    var id = generateId(index, eventScope.id);
                    inputCache = '';
                    SearchVars.set(id, { 'searchingDone' : false, 'currentValue' : '', 'searching' : false });
                });

                return;
            }

            // Only search when the value hasn't changed
            if (inputCache === searchValue) {
                return;
            }

            // Run the search at the specified timeout
            inputCache = searchValue;

            _.each(this.index, function (index) {
                id = generateId(index, eventScope.id);
                SearchVars.set(id, { 'searching' : false });

                // If already running, stop the timer
                if (LocalTimer.isRunning(id)) {
                    LocalTimer.stop(id);
                }

                // Set to default limit
                EasySearch.changeProperty(index, 'limit', EasySearch.getIndex(index).defaultLimit);

                LocalTimer.runAt(id, timeout, esInputSearch, {
                    value : searchValue,
                    easySearchID : id,
                    easySearchIndex : index,
                    easySearchReactive : reactive
                });

                SearchVars.set(id, { 'searchingDone' : false, 'searching' : true });
            });
        },
        'keydown input' : function (e) {
            if ($(e.target).val().length === 0) {
                SearchVars.set(generateId(this.index, this.id), { 'currentValue' : '' });
            }
        }
    });

    /* esEach */
    UI.registerHelper('esEach', function () {
        return Template.esEach;
    });

    Template.esEach.helpers({
        'elasticSearchDoc' : function () {
            var config,
                indexConf,
                isReactive,
                options = this.options || null,
                doc = LocalCache.findOne(this.index);

            if (!_.isObject(doc)) {
                return [];
            }

            if (SearchVars.get(generateId(this.index, this.id), 'searching')) {
                return [];
            }

            isReactive = doc.r;

            // Not reactive
            if (doc && doc.d && !isReactive) {
                return doc.d;
            }

            // Is reactive
            if (doc && doc.d) {
                config = EasySearch.getIndexes();
                indexConf = config[this.index];

                if (options) {
                    return indexConf.collection.find({ _id : { $in : doc.d } }, options);
                }

                // TODO: Find a more performant way than this for ordering the documents
                return _.sortBy(indexConf.collection.find({ _id : { $in : doc.d } }).fetch(), function (document) {
                    return doc.d.indexOf(document._id);
                });
            }
        }
    });

    /* ifEsIsSearching */
    Template.ifEsIsSearching.isSearching = function () {
        var id = generateId(this.index, this.id),
            isSearching = SearchVars.get(id, 'searching');

        return isSearching ? isSearching : null;
    };

    /* ifEsHasNoResults */
    Template.ifEsHasNoResults.hasNoResults = function () {
        var id = generateId(this.index, this.id),
            cache = LocalCache.findOne(this.index),
            searchValue = SearchVars.get(id, 'currentValue');

        if (SearchVars.get(id, 'searching') || (_.isString(searchValue) && searchValue.length === 0)) {
            return false;
        }

        return cache && _.isArray(cache.d) && cache.d.length === 0;
    }

    /* esLoadMoreButton */
    Template.esLoadMoreButton.rendered = function () {
        SearchVars.set(generateId(this.data.index, this.data.id), { 'currentLimit' :  EasySearch.getIndex(this.data.index).limit });
    };

    Template.esLoadMoreButton.helpers({
        'content' : function () {
            return this.content ? this.content : 'Load more';
        },
        'hasMoreResults' : function () {
            var id = generateId(this.index, this.id);

            return !SearchVars.get(id, 'searching')
                    && (SearchVars.get(id, 'total') > SearchVars.get(id, 'currentLimit'));
        }
    });

    Template.esLoadMoreButton.events({
        'click button' : function () {
            var templateScope = this,
                id = generateId(this.index, this.id),
                currentLimit = EasySearch.getIndexes()[this.index].limit,
                howManyMore = this.howMany ? this.howMany : 10;

            templateScope.reactive = this.reactive !== "false";

            EasySearch.changeProperty(this.index, 'limit', currentLimit + howManyMore);
            EasySearch.search(this.index, SearchVars.get(id, 'currentValue'), function (err, data) {
                if (err) {
                    throw new Meteor.Error(500, "Searching failed");
                }

                SearchVars.set(id, { 'total' : data.total });
                SearchVars.set(id, { 'currentLimit' : currentLimit + howManyMore });
                processSearchResults(templateScope.index, data, templateScope.reactive);
            });
        }
    });

    /* esAutosuggest */
    Template.esAutosuggest.created = function () {
        var tplScope = this,
            id = generateId(tplScope.data.index, tplScope.data.id);

        SearchVars.set(id, { 'autosuggestSelected' : [] });

        Deps.autorun(function () {
            autosuggestCache.value = SearchVars.get(id, 'currentValue');
        });
    }

    Template.esAutosuggest.rendered = function () {
        var tplScope = this,
            id = generateId(tplScope.data.index, tplScope.data.id);

        Deps.autorun(function () {
            var input = $(tplScope.find('input')),
                wrapper = $(tplScope.find('div')),
                values = SearchVars.get(id, 'autosuggestSelected');

            // Reposition the input when a value gets added
            input.css('padding-left', wrapper.find('.selected.values').width());
        });
    };

    Template.esAutosuggest.helpers({
        'selectedValue' : function () {
            var id = generateId(this.index, this.id);

            return _.map(SearchVars.get(id, 'autosuggestSelected'), function (val) {
                return {
                    'id' : id,
                    'docId' : val.id,
                    'value' : val.value
                };
            });
        },
        'selected' : function () {
            var suggestionsSelected = SearchVars.get(generateId(this.options.index, this.options.id), 'suggestionsSelected');
            return _.isObject(suggestionsSelected) && suggestionsSelected.id === this.id ? 'selected' : '';
        },
        'isHidden' : function () {
            var id = generateId(this.index, this.id),
                inputValue = SearchVars.get(id, 'currentValue'),
                searchingDone = SearchVars.get(id, 'searchingDone');

            return (inputValue && inputValue.length > 0) && searchingDone  ? 'show' : 'hide';
        },
        'snippets' : function (options) {
            var regex, firstFieldValue, searchValue, parts,
                index = EasySearch.getIndex(options.index),
                firstField  = _.isArray(index.field) ? index.field[0] : index.field;

            firstFieldValue = this[firstField];
            searchValue = autosuggestCache.value,
            regex = new RegExp(searchValue + '(.+)?', "i");
            parts = firstFieldValue.split(regex);

            // Not found in the first field
            if (parts.length === 1) {
                return {
                    'pre' : firstFieldValue,
                    'value' : firstFieldValue,
                    'id' : this._id,
                    'options' : options
                };
            }

            return {
                'pre' : parts[0],
                'found' : (new RegExp(searchValue, 'i')).exec(firstFieldValue).shift(),
                'post' : parts[1],
                'value' : firstFieldValue,
                'id' : this._id,
                'options' : options
            };
        }
    });

    Template.esAutosuggest.events({
        'click .suggestion:not(.remove)' : function (e) {
            var values,
                id = generateId(this.options.index, this.options.id),
                wrapper = $(e.target).closest('.es-autosuggest-wrapper'),
                input = wrapper.find('input');

            // Add a box in front of the input which is the selected "value"
            values = SearchVars.get(id, 'autosuggestSelected');
            values.push({ 'id' : this.id, 'value' : this.value });
            SearchVars.set(id, { 'autosuggestSelected' :  values, 'currentValue' : '' });

            input.val('').keyup().keypress().keydown();

            e.preventDefault();
        },
        'click .remove' : function (e) {
            var tplScope = this;

            // Removes a suggestion
            SearchVars.set(this.id, { 'autosuggestSelected' :  _.reject(
                SearchVars.get(this.id, 'autosuggestSelected'), function (val) {
                    return val.id === tplScope.docId;
                })
            });
        },
        'keydown input' : function (e) {
            var selected, currentValues,
                id = generateId(this.index, this.id), input = $(e.target),
                selected = SearchVars.get(id, 'suggestionsSelected');

            if (!$(e.target).val() && !e.which === 8) {
                return;
            }

            if (e.which === 13 && selected && _.isObject(selected)) {
                // Enter
                currentValues = SearchVars.get(id, 'autosuggestSelected');
                currentValues.push(selected);

                SearchVars.set(id, {
                    'autosuggestSelected' : currentValues,
                });

                $(e.target).val('');
                SearchVars.set(id, { 'suggestionsSelected' : '' });
            } else if (e.which === 40 || e.which === 38) {
                // Down or Up
                var incrementalValue,
                    toBeSelectedDoc, firstField,
                    index = EasySearch.getIndex(this.index[0]),
                    suggestions = SearchVars.get(id, 'searchResults');

                if (!index || !suggestions) {
                    return;
                }

                firstField = firstField  = _.isArray(index.field) ? index.field[0] : index.field;

                // If there's none selected
                if (!selected && e.which === 40) {
                    selected = {
                        'pos' : -1
                    };
                } else if (!selected && e.which === 38) {
                    selected = {
                        'pos' : suggestions.length
                    };
                }

                // Take the one below or up, decide by key
                incrementalValue = e.which === 40 ? 1 :  -1;

                toBeSelectedDoc = suggestions[selected.pos + incrementalValue];

                if (!toBeSelectedDoc) {
                    return;
                }

                SearchVars.set(id, { 'suggestionsSelected' : {
                    'value' : toBeSelectedDoc[firstField],
                    'id' : toBeSelectedDoc._id,
                    'pos' : selected.pos + incrementalValue
                }});

                e.preventDefault();
            } else if (e.which === 8 && input.val().length === 0) {
                // Remove
                SearchVars.set(id, {
                    'autosuggestSelected' : _.initial(SearchVars.get(id, 'autosuggestSelected'))
                });
            }
        },
        'keyup input' : function () {
            var id = generateId(this.index, this.id);

            if (SearchVars.get(id, 'searching')) {
                SearchVars.set(id, { 'suggestionsSelected' : '' });
            }
        }
    });

    EasySearch.ComponentVariables = SearchVars;
    EasySearch.Components = {
        'generateId' : generateId
    };
})();
<|endoftext|>


<commit_before>(function($){

	var tokens = {
		UNICODE: /\\[0-9a-f]{1,6}(?:\r\n|[ \n\r\t\f])?/,
		ESCAPE: /(?:UNICODE)|\\[^\n\r\f0-9a-f]/,
		NONASCII: /[^\x00-\x7F]/,
		NMSTART: /[_a-z]|(?:NONASCII)|(?:ESCAPE)/,
		NMCHAR: /[_a-z0-9-]|(?:NONASCII)|(?:ESCAPE)/,
		IDENT: /-?(?:NMSTART)(?:NMCHAR)*/,

		NL: /\n|\r\n|\r|\f/,

		STRING: /(?:STRING1)|(?:STRING2)|(?:STRINGBARE)/,
		STRING1: /"(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\"])*"/,
		STRING2: /'(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\'])*'/,
		STRINGBARE: /(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\]])*/,

		FUNCTION: /(?:IDENT)\(\)/,

		INTEGER: /[0-9]+/,

		WITHN: /([-+])?(INTEGER)?(n)\s*(?:([-+])\s*(INTEGER))?/,
		WITHOUTN: /([-+])?(INTEGER)/
	}

	var rx = {
		not: /:not\(/,
		not_end: /\)/,

 		tag: /((?:IDENT)|\*)/,
		id: /#(IDENT)/,
		cls: /\.(IDENT)/,
		attr: /\[\s*(IDENT)\s*(?:([^=]?=)\s*(STRING)\s*)?\]/,
		pseudo_el: /(?::(first-line|first-letter|before|after))|(?:::((?:FUNCTION)|(?:IDENT)))/,
		pseudo_cls_nth: /:nth-child\(\s*(?:(?:WITHN)|(?:WITHOUTN)|(odd|even))\s*\)/,
		pseudo_cls: /:(IDENT)/,

		comb: /\s*(\+|~|>)\s*|\s+/,
		comma: /\s*,\s*/,
		important: /\s+!important\s*$/
	}

	/* Replace placeholders with actual regex, and mark all as case insensitive */
	var token = /[A-Z][A-Z0-9]+/;
	for (var k in rx) {
		var src = rx[k].source;
		while (m = src.match(token)) src = src.replace(m[0], tokens[m[0]].source);
		rx[k] = new RegExp(src, 'gi');
	}

	/**
	 * A string that matches itself against regexii, and keeps track of how much of itself has been matched
	 */
	var ConsumableString = Base.extend({
		init: function(str) {
			this.str = str;
			this.pos = 0;
		},
		match: function(rx) {
			var m;
			rx.lastIndex = this.pos;
			if ((m = rx.exec(this.str)) && m.index == this.pos ) {
				this.pos = rx.lastIndex ? rx.lastIndex : this.str.length ;
				return m;
			}
			return null;
		},
		peek: function(rx) {
			var m;
			rx.lastIndex = this.pos;
			if ((m = rx.exec(this.str)) && m.index == this.pos ) return m;
			return null;
		},
		showpos: function() {
			return this.str.slice(0,this.pos)+'<HERE>' + this.str.slice(this.pos);
		},
		done: function() {
			return this.pos == this.str.length;
		}
	})

	/* A base class that all Selectors inherit off */
	var SelectorBase = Base.extend({});

	/**
	 * A class representing a Simple Selector, as per the CSS3 selector spec
	 */
	var SimpleSelector = SelectorBase.extend({
		init: function() {
			this.tag = null;
			this.id = null;
			this.classes = [];
			this.attrs = [];
			this.nots = [];
			this.pseudo_classes = [];
			this.pseudo_els = [];
		},
		parse: function(selector) {
			var m;

			/* Pull out the initial tag first, if there is one */
			if (m = selector.match(rx.tag)) this.tag = m[1];

			/* Then for each selection type, try and find a match */
			do {
				if (m = selector.match(rx.not)) {
					this.nots[this.nots.length] = SelectorsGroup().parse(selector)
					if (!(m = selector.match(rx.not_end))) {
						throw 'Invalid :not term in selector';
					}
				}
				else if (m = selector.match(rx.id))         this.id = m[1];
				else if (m = selector.match(rx.cls))        this.classes[this.classes.length] = m[1];
				else if (m = selector.match(rx.attr))       this.attrs[this.attrs.length] = [ m[1], m[2], m[3] ];
				else if (m = selector.match(rx.pseudo_el))  this.pseudo_els[this.pseudo_els.length] = m[1] || m[2];
				else if (m = selector.match(rx.pseudo_cls_nth)) {
					if (m[3]) {
						var a = parseInt((m[1]||'')+(m[2]||'1'));
						var b = parseInt((m[4]||'')+(m[5]||'0'));
					}
					else {
						var a = m[8] ? 2 : 0;
						var b = m[8] ? (4-m[8].length) : parseInt((m[6]||'')+m[7]);
					}
					this.pseudo_classes[this.pseudo_classes.length] = ['nth-child', [a, b]];
				}
				else if (m = selector.match(rx.pseudo_cls)) this.pseudo_classes[this.pseudo_classes.length] = [m[1]];

			} while(m && !selector.done());

			return this;
		}
	})

	/**
	 * A class representing a Selector, as per the CSS3 selector spec
	 */
	var Selector = SelectorBase.extend({
		init: function(){
			this.parts = [];
		},
		parse: function(cons){
			this.parts[this.parts.length] = SimpleSelector().parse(cons);

			while (!cons.done() && !cons.peek(rx.comma) && (m = cons.match(rx.comb))) {
				this.parts[this.parts.length] = m[1] || ' ';
				this.parts[this.parts.length] = SimpleSelector().parse(cons);
			}

			return this.parts.length == 1 ? this.parts[0] : this;
		}
	});

	/**
	 * A class representing a sequence of selectors, as per the CSS3 selector spec
	 */
	var SelectorsGroup = SelectorBase.extend({
		init: function(){
			this.parts = [];
		},
		parse: function(cons){
			this.parts[this.parts.length] = Selector().parse(cons);

			while (!cons.done() && (m = cons.match(rx.comma))) {
				this.parts[this.parts.length] = Selector().parse(cons);
			}

			return this.parts.length == 1 ? this.parts[0] : this;
		}
	});

	$.selector = function(s){
		var cons = ConsumableString(s);
		var res = SelectorsGroup().parse(cons);

		if (!cons.done()) throw 'Could not parse selector - ' + cons.showpos() ;
		else return res;
	}

	$.selector.SelectorBase = SelectorBase;
	$.selector.SimpleSelector = SimpleSelector;
	$.selector.Selector = Selector;
	$.selector.SelectorsGroup = SelectorsGroup;

})(jQuery)
<commit_msg>Save original selector text in selector object after parsing. @todo: Canonicalize selector by parsing and rebuilding<commit_after>(function($){

	var tokens = {
		UNICODE: /\\[0-9a-f]{1,6}(?:\r\n|[ \n\r\t\f])?/,
		ESCAPE: /(?:UNICODE)|\\[^\n\r\f0-9a-f]/,
		NONASCII: /[^\x00-\x7F]/,
		NMSTART: /[_a-z]|(?:NONASCII)|(?:ESCAPE)/,
		NMCHAR: /[_a-z0-9-]|(?:NONASCII)|(?:ESCAPE)/,
		IDENT: /-?(?:NMSTART)(?:NMCHAR)*/,

		NL: /\n|\r\n|\r|\f/,

		STRING: /(?:STRING1)|(?:STRING2)|(?:STRINGBARE)/,
		STRING1: /"(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\"])*"/,
		STRING2: /'(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\'])*'/,
		STRINGBARE: /(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\]])*/,

		FUNCTION: /(?:IDENT)\(\)/,

		INTEGER: /[0-9]+/,

		WITHN: /([-+])?(INTEGER)?(n)\s*(?:([-+])\s*(INTEGER))?/,
		WITHOUTN: /([-+])?(INTEGER)/
	}

	var rx = {
		not: /:not\(/,
		not_end: /\)/,

 		tag: /((?:IDENT)|\*)/,
		id: /#(IDENT)/,
		cls: /\.(IDENT)/,
		attr: /\[\s*(IDENT)\s*(?:([^=]?=)\s*(STRING)\s*)?\]/,
		pseudo_el: /(?::(first-line|first-letter|before|after))|(?:::((?:FUNCTION)|(?:IDENT)))/,
		pseudo_cls_nth: /:nth-child\(\s*(?:(?:WITHN)|(?:WITHOUTN)|(odd|even))\s*\)/,
		pseudo_cls: /:(IDENT)/,

		comb: /\s*(\+|~|>)\s*|\s+/,
		comma: /\s*,\s*/,
		important: /\s+!important\s*$/
	}

	/* Replace placeholders with actual regex, and mark all as case insensitive */
	var token = /[A-Z][A-Z0-9]+/;
	for (var k in rx) {
		var src = rx[k].source;
		while (m = src.match(token)) src = src.replace(m[0], tokens[m[0]].source);
		rx[k] = new RegExp(src, 'gi');
	}

	/**
	 * A string that matches itself against regexii, and keeps track of how much of itself has been matched
	 */
	var ConsumableString = Base.extend({
		init: function(str) {
			this.str = str;
			this.pos = 0;
		},
		match: function(rx) {
			var m;
			rx.lastIndex = this.pos;
			if ((m = rx.exec(this.str)) && m.index == this.pos ) {
				this.pos = rx.lastIndex ? rx.lastIndex : this.str.length ;
				return m;
			}
			return null;
		},
		peek: function(rx) {
			var m;
			rx.lastIndex = this.pos;
			if ((m = rx.exec(this.str)) && m.index == this.pos ) return m;
			return null;
		},
		showpos: function() {
			return this.str.slice(0,this.pos)+'<HERE>' + this.str.slice(this.pos);
		},
		done: function() {
			return this.pos == this.str.length;
		}
	})

	/* A base class that all Selectors inherit off */
	var SelectorBase = Base.extend({});

	/**
	 * A class representing a Simple Selector, as per the CSS3 selector spec
	 */
	var SimpleSelector = SelectorBase.extend({
		init: function() {
			this.tag = null;
			this.id = null;
			this.classes = [];
			this.attrs = [];
			this.nots = [];
			this.pseudo_classes = [];
			this.pseudo_els = [];
		},
		parse: function(selector) {
			var m;

			/* Pull out the initial tag first, if there is one */
			if (m = selector.match(rx.tag)) this.tag = m[1];

			/* Then for each selection type, try and find a match */
			do {
				if (m = selector.match(rx.not)) {
					this.nots[this.nots.length] = SelectorsGroup().parse(selector)
					if (!(m = selector.match(rx.not_end))) {
						throw 'Invalid :not term in selector';
					}
				}
				else if (m = selector.match(rx.id))         this.id = m[1];
				else if (m = selector.match(rx.cls))        this.classes[this.classes.length] = m[1];
				else if (m = selector.match(rx.attr))       this.attrs[this.attrs.length] = [ m[1], m[2], m[3] ];
				else if (m = selector.match(rx.pseudo_el))  this.pseudo_els[this.pseudo_els.length] = m[1] || m[2];
				else if (m = selector.match(rx.pseudo_cls_nth)) {
					if (m[3]) {
						var a = parseInt((m[1]||'')+(m[2]||'1'));
						var b = parseInt((m[4]||'')+(m[5]||'0'));
					}
					else {
						var a = m[8] ? 2 : 0;
						var b = m[8] ? (4-m[8].length) : parseInt((m[6]||'')+m[7]);
					}
					this.pseudo_classes[this.pseudo_classes.length] = ['nth-child', [a, b]];
				}
				else if (m = selector.match(rx.pseudo_cls)) this.pseudo_classes[this.pseudo_classes.length] = [m[1]];

			} while(m && !selector.done());

			return this;
		}
	})

	/**
	 * A class representing a Selector, as per the CSS3 selector spec
	 */
	var Selector = SelectorBase.extend({
		init: function(){
			this.parts = [];
		},
		parse: function(cons){
			this.parts[this.parts.length] = SimpleSelector().parse(cons);

			while (!cons.done() && !cons.peek(rx.comma) && (m = cons.match(rx.comb))) {
				this.parts[this.parts.length] = m[1] || ' ';
				this.parts[this.parts.length] = SimpleSelector().parse(cons);
			}

			return this.parts.length == 1 ? this.parts[0] : this;
		}
	});

	/**
	 * A class representing a sequence of selectors, as per the CSS3 selector spec
	 */
	var SelectorsGroup = SelectorBase.extend({
		init: function(){
			this.parts = [];
		},
		parse: function(cons){
			this.parts[this.parts.length] = Selector().parse(cons);

			while (!cons.done() && (m = cons.match(rx.comma))) {
				this.parts[this.parts.length] = Selector().parse(cons);
			}

			return this.parts.length == 1 ? this.parts[0] : this;
		}
	});

	$.selector = function(s){
		var cons = ConsumableString(s);
		var res = SelectorsGroup().parse(cons);

		res.selector = s;

		if (!cons.done()) throw 'Could not parse selector - ' + cons.showpos() ;
		else return res;
	}

	$.selector.SelectorBase = SelectorBase;
	$.selector.SimpleSelector = SimpleSelector;
	$.selector.Selector = Selector;
	$.selector.SelectorsGroup = SelectorsGroup;

})(jQuery)
<|endoftext|>