").addClass("slider" + (this.settings.classSuffix || "")).css({
+ position: "relative",
+ userSelect: "none",
+ boxSizing: "border-box"
+ }).insertBefore(this.input);
+ if (this.input.attr("id")) {
+ this.slider.attr("id", this.input.attr("id") + "-slider");
+ }
+ this.track = this.createDivElement("track").css({
+ width: "100%"
+ });
+ if (this.settings.highlight) {
+ this.highlightTrack = this.createDivElement("highlight-track").css({
+ width: "0"
+ });
+ }
+ this.dragger = this.createDivElement("dragger");
+ this.slider.css({
+ minHeight: this.dragger.outerHeight(),
+ marginLeft: this.dragger.outerWidth() / 2,
+ marginRight: this.dragger.outerWidth() / 2
+ });
+ this.track.css({
+ marginTop: this.track.outerHeight() / -2
+ });
+ if (this.settings.highlight) {
+ this.highlightTrack.css({
+ marginTop: this.track.outerHeight() / -2
+ });
+ }
+ this.dragger.css({
+ marginTop: this.dragger.outerWidth() / -2,
+ marginLeft: this.dragger.outerWidth() / -2
+ });
+ this.track.mousedown(function(e) {
+ return _this.trackEvent(e);
+ });
+ if (this.settings.highlight) {
+ this.highlightTrack.mousedown(function(e) {
+ return _this.trackEvent(e);
+ });
+ }
+ this.dragger.mousedown(function(e) {
+ if (e.which !== 1) {
+ return;
+ }
+ _this.dragging = true;
+ _this.dragger.addClass("dragging");
+ _this.domDrag(e.pageX, e.pageY);
+ return false;
+ });
+ $("body").mousemove(function(e) {
+ if (_this.dragging) {
+ _this.domDrag(e.pageX, e.pageY);
+ return $("body").css({
+ cursor: "pointer"
+ });
+ }
+ }).mouseup(function(e) {
+ if (_this.dragging) {
+ _this.dragging = false;
+ _this.dragger.removeClass("dragging");
+ return $("body").css({
+ cursor: "auto"
+ });
+ }
+ });
+ this.pagePos = 0;
+ if (this.input.val() === "") {
+ this.value = this.getRange().min;
+ this.input.val(this.value);
+ } else {
+ this.value = this.nearestValidValue(this.input.val());
+ }
+ this.setSliderPositionFromValue(this.value);
+ ratio = this.valueToRatio(this.value);
+ this.input.trigger("slider:ready", {
+ value: this.value,
+ ratio: ratio,
+ position: ratio * this.slider.outerWidth(),
+ el: this.slider
+ });
+ }
+
+ SimpleSlider.prototype.createDivElement = function(classname) {
+ var item;
+ item = $("
").addClass(classname).css({
+ position: "absolute",
+ top: "50%",
+ userSelect: "none",
+ cursor: "pointer"
+ }).appendTo(this.slider);
+ return item;
+ };
+
+ SimpleSlider.prototype.setRatio = function(ratio) {
+ var value;
+ ratio = Math.min(1, ratio);
+ ratio = Math.max(0, ratio);
+ value = this.ratioToValue(ratio);
+ this.setSliderPositionFromValue(value);
+ return this.valueChanged(value, ratio, "setRatio");
+ };
+
+ SimpleSlider.prototype.setValue = function(value) {
+ var ratio;
+ value = this.nearestValidValue(value);
+ ratio = this.valueToRatio(value);
+ this.setSliderPositionFromValue(value);
+ return this.valueChanged(value, ratio, "setValue");
+ };
+
+ SimpleSlider.prototype.trackEvent = function(e) {
+ if (e.which !== 1) {
+ return;
+ }
+ this.domDrag(e.pageX, e.pageY, true);
+ this.dragging = true;
+ return false;
+ };
+
+ SimpleSlider.prototype.domDrag = function(pageX, pageY, animate) {
+ var pagePos, ratio, value;
+ if (animate == null) {
+ animate = false;
+ }
+ pagePos = pageX - this.slider.offset().left;
+ pagePos = Math.min(this.slider.outerWidth(), pagePos);
+ pagePos = Math.max(0, pagePos);
+ if (this.pagePos !== pagePos) {
+ this.pagePos = pagePos;
+ ratio = pagePos / this.slider.outerWidth();
+ value = this.ratioToValue(ratio);
+ this.valueChanged(value, ratio, "domDrag");
+ if (this.settings.snap) {
+ return this.setSliderPositionFromValue(value, animate);
+ } else {
+ return this.setSliderPosition(pagePos, animate);
+ }
+ }
+ };
+
+ SimpleSlider.prototype.setSliderPosition = function(position, animate) {
+ if (animate == null) {
+ animate = false;
+ }
+ if (animate && this.settings.animate) {
+ this.dragger.animate({
+ left: position
+ }, 200);
+ if (this.settings.highlight) {
+ return this.highlightTrack.animate({
+ width: position
+ }, 200);
+ }
+ } else {
+ this.dragger.css({
+ left: position
+ });
+ if (this.settings.highlight) {
+ return this.highlightTrack.css({
+ width: position
+ });
+ }
+ }
+ };
+
+ SimpleSlider.prototype.setSliderPositionFromValue = function(value, animate) {
+ var ratio;
+ if (animate == null) {
+ animate = false;
+ }
+ ratio = this.valueToRatio(value);
+ return this.setSliderPosition(ratio * this.slider.outerWidth(), animate);
+ };
+
+ SimpleSlider.prototype.getRange = function() {
+ if (this.settings.allowedValues) {
+ return {
+ min: Math.min.apply(Math, this.settings.allowedValues),
+ max: Math.max.apply(Math, this.settings.allowedValues)
+ };
+ } else if (this.settings.range) {
+ return {
+ min: parseFloat(this.settings.range[0]),
+ max: parseFloat(this.settings.range[1])
+ };
+ } else {
+ return {
+ min: 0,
+ max: 1
+ };
+ }
+ };
+
+ SimpleSlider.prototype.nearestValidValue = function(rawValue) {
+ var closest, maxSteps, range, steps;
+ range = this.getRange();
+ rawValue = Math.min(range.max, rawValue);
+ rawValue = Math.max(range.min, rawValue);
+ if (this.settings.allowedValues) {
+ closest = null;
+ $.each(this.settings.allowedValues, function() {
+ if (closest === null || Math.abs(this - rawValue) < Math.abs(closest - rawValue)) {
+ return closest = this;
+ }
+ });
+ return closest;
+ } else if (this.settings.step) {
+ maxSteps = (range.max - range.min) / this.settings.step;
+ steps = Math.floor((rawValue - range.min) / this.settings.step);
+ if ((rawValue - range.min) % this.settings.step > this.settings.step / 2 && steps < maxSteps) {
+ steps += 1;
+ }
+ return steps * this.settings.step + range.min;
+ } else {
+ return rawValue;
+ }
+ };
+
+ SimpleSlider.prototype.valueToRatio = function(value) {
+ var allowedVal, closest, closestIdx, idx, range, _i, _len, _ref;
+ if (this.settings.equalSteps) {
+ _ref = this.settings.allowedValues;
+ for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
+ allowedVal = _ref[idx];
+ if (!(typeof closest !== "undefined" && closest !== null) || Math.abs(allowedVal - value) < Math.abs(closest - value)) {
+ closest = allowedVal;
+ closestIdx = idx;
+ }
+ }
+ if (this.settings.snapMid) {
+ return (closestIdx + 0.5) / this.settings.allowedValues.length;
+ } else {
+ return closestIdx / (this.settings.allowedValues.length - 1);
+ }
+ } else {
+ range = this.getRange();
+ return (value - range.min) / (range.max - range.min);
+ }
+ };
+
+ SimpleSlider.prototype.ratioToValue = function(ratio) {
+ var idx, range, rawValue, step, steps;
+ if (this.settings.equalSteps) {
+ steps = this.settings.allowedValues.length;
+ step = Math.round(ratio * steps - 0.5);
+ idx = Math.min(step, this.settings.allowedValues.length - 1);
+ return this.settings.allowedValues[idx];
+ } else {
+ range = this.getRange();
+ rawValue = ratio * (range.max - range.min) + range.min;
+ return this.nearestValidValue(rawValue);
+ }
+ };
+
+ SimpleSlider.prototype.valueChanged = function(value, ratio, trigger) {
+ var eventData;
+ if (value.toString() === this.value.toString()) {
+ return;
+ }
+ this.value = value;
+ eventData = {
+ value: value,
+ ratio: ratio,
+ position: ratio * this.slider.outerWidth(),
+ trigger: trigger,
+ el: this.slider
+ };
+ return this.input.val(value).trigger($.Event("change", eventData)).trigger("slider:changed", eventData);
+ };
+
+ return SimpleSlider;
+
+ })();
+ $.extend($.fn, {
+ simpleSlider: function() {
+ var params, publicMethods, settingsOrMethod;
+ settingsOrMethod = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+ publicMethods = ["setRatio", "setValue"];
+ return $(this).each(function() {
+ var obj, settings;
+ if (settingsOrMethod && __indexOf.call(publicMethods, settingsOrMethod) >= 0) {
+ obj = $(this).data("slider-object");
+ return obj[settingsOrMethod].apply(obj, params);
+ } else {
+ settings = settingsOrMethod;
+ return $(this).data("slider-object", new SimpleSlider($(this), settings));
+ }
+ });
+ }
+ });
+ return $(function() {
+ return $("[data-slider]").each(function() {
+ var $el, allowedValues, settings, x;
+ $el = $(this);
+ settings = {};
+ allowedValues = $el.data("slider-values");
+ if (allowedValues) {
+ settings.allowedValues = (function() {
+ var _i, _len, _ref, _results;
+ _ref = allowedValues.split(",");
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ x = _ref[_i];
+ _results.push(parseFloat(x));
+ }
+ return _results;
+ })();
+ }
+ if ($el.data("slider-range")) {
+ settings.range = $el.data("slider-range").split(",");
+ }
+ if ($el.data("slider-step")) {
+ settings.step = $el.data("slider-step");
+ }
+ settings.snap = $el.data("slider-snap");
+ settings.equalSteps = $el.data("slider-equal-steps");
+ if ($el.data("slider-theme")) {
+ settings.theme = $el.data("slider-theme");
+ }
+ if ($el.attr("data-slider-highlight")) {
+ settings.highlight = $el.data("slider-highlight");
+ }
+ if ($el.data("slider-animate") != null) {
+ settings.animate = $el.data("slider-animate");
+ }
+ return $el.simpleSlider(settings);
+ });
+ });
+})(this.jQuery || this.Zepto, this);
diff --git a/src/main/sql/mysql.sql b/src/main/sql/mysql.sql
index 4726deb..66545c5 100644
--- a/src/main/sql/mysql.sql
+++ b/src/main/sql/mysql.sql
@@ -24,3 +24,27 @@ CREATE TABLE `SESSIONS` (
REFERENCES `USERS` (`ID`)
ON UPDATE CASCADE ON DELETE CASCADE
);
+
+CREATE TABLE `ITEMS` (
+ `ID` BIGINT PRIMARY KEY AUTO_INCREMENT,
+ `TITLE` VARCHAR(128) NOT NULL,
+ `DESCRIPTION` VARCHAR(4096) NULL,
+ `SESSION_ID` BIGINT NOT NULL,
+ FOREIGN KEY (`SESSION_ID`)
+ REFERENCES `SESSIONS` (`ID`)
+ ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE TABLE `ESTIMATES` (
+ `ITEM_ID` BIGINT NOT NULL,
+ `USER_ID` BIGINT NOT NULL,
+ `ESTIMATE` INT NOT NULL,
+ FOREIGN KEY (`ITEM_ID`)
+ REFERENCES `ITEMS` (`ID`)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ FOREIGN KEY (`USER_ID`)
+ REFERENCES `USERS` (`ID`)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ PRIMARY KEY (`USER_ID`, `ITEM_ID`)
+);
+
diff --git a/src/test/java/org/lbogdanov/poker/core/DurationTest.java b/src/test/java/org/lbogdanov/poker/core/DurationTest.java
deleted file mode 100644
index 9a240c6..0000000
--- a/src/test/java/org/lbogdanov/poker/core/DurationTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.lbogdanov.poker.core;
-
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.lbogdanov.poker.core.Duration.MINUTES_PER_DAY;
-import static org.lbogdanov.poker.core.Duration.MINUTES_PER_HOUR;
-import static org.lbogdanov.poker.core.Duration.MINUTES_PER_WEEK;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-/**
- * Tests for {@link Duration} class.
- *
- * @author Alexandra Fomina
- *
- */
-public class DurationTest {
-
- @Rule
- public ExpectedException thrown = ExpectedException.none();
-
- /**
- * Test for {@link Duration#Duration(int)} with invalid input.
- */
- @Test(expected = IllegalArgumentException.class)
- public void testInvalidArgument() {
- new Duration(-1);
- }
-
- /**
- * Test for {@link Duration#parse(String)}.
- */
- @Test
- public void testParse() {
- Duration[] durations = {new Duration(30), new Duration(MINUTES_PER_HOUR),
- new Duration(MINUTES_PER_DAY), new Duration(MINUTES_PER_WEEK)};
-
- assertArrayEquals(durations, Duration.parse("30m,1h,1d,1w").toArray());
- assertArrayEquals(durations, Duration.parse("30m 1h 1d 1w").toArray());
- assertArrayEquals(durations, Duration.parse("30m;1h;1d;1w").toArray());
- }
-
- /**
- * Test for {@link Duration#parse(String)} with invalid input.
- */
- @Test
- public void testParseWithInvalidInput1() {
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("q");
- Duration.parse("qwerty");
- }
-
- /**
- * Test for {@link Duration#parse(String)} with invalid input.
- */
- @Test
- public void testParseWithInvalidInput2() {
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("12y");
- Duration.parse("12y 5w 3h");
- }
-
- /**
- * Test for {@link Duration#parse(String)} with invalid input.
- */
- @Test
- public void testParseWithInvalidInput3() {
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage(".");
- Duration.parse("12d.5w.3h");
- }
-
- /**
- * Test for {@link Duration#parse(String)} with invalid input.
- */
- @Test
- public void testParseWithInvalidInput4() {
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("12");
- Duration.parse("2h 12");
- }
-
- /**
- * Test for {@link Duration#toString()}.
- */
- @Test
- public void testToString() {
- assertEquals("1w 1d 1h 1m", new Duration(MINUTES_PER_WEEK + MINUTES_PER_DAY + MINUTES_PER_HOUR + 1).toString());
- assertEquals("0", new Duration().toString());
- assertEquals("30m", new Duration(30).toString());
- assertEquals("1d", new Duration(MINUTES_PER_DAY).toString());
- }
-
-}
diff --git a/src/test/java/org/lbogdanov/poker/core/EstimateTest.java b/src/test/java/org/lbogdanov/poker/core/EstimateTest.java
new file mode 100644
index 0000000..4612659
--- /dev/null
+++ b/src/test/java/org/lbogdanov/poker/core/EstimateTest.java
@@ -0,0 +1,97 @@
+package org.lbogdanov.poker.core;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.lbogdanov.poker.core.Estimate.MINUTES_PER_DAY;
+import static org.lbogdanov.poker.core.Estimate.MINUTES_PER_HOUR;
+import static org.lbogdanov.poker.core.Estimate.MINUTES_PER_WEEK;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests for {@link Estimate} class.
+ *
+ * @author Alexandra Fomina
+ *
+ */
+public class EstimateTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ /**
+ * Test for {@link Estimate#Estimate(int)} with invalid input.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidArgument() {
+ new Estimate(-1);
+ }
+
+ /**
+ * Test for {@link Estimate#parse(String)}.
+ */
+ @Test
+ public void testParse() {
+ Estimate[] Estimates = {new Estimate(30), new Estimate(MINUTES_PER_HOUR),
+ new Estimate(MINUTES_PER_DAY), new Estimate(MINUTES_PER_WEEK)};
+
+ assertArrayEquals(Estimates, Estimate.parse("30m,1h,1d,1w").toArray());
+ assertArrayEquals(Estimates, Estimate.parse("30m 1h 1d 1w").toArray());
+ assertArrayEquals(Estimates, Estimate.parse("30m;1h;1d;1w").toArray());
+ }
+
+ /**
+ * Test for {@link Estimate#parse(String)} with invalid input.
+ */
+ @Test
+ public void testParseWithInvalidInput1() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("q");
+ Estimate.parse("qwerty");
+ }
+
+ /**
+ * Test for {@link Estimate#parse(String)} with invalid input.
+ */
+ @Test
+ public void testParseWithInvalidInput2() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("12y");
+ Estimate.parse("12y 5w 3h");
+ }
+
+ /**
+ * Test for {@link Estimate#parse(String)} with invalid input.
+ */
+ @Test
+ public void testParseWithInvalidInput3() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(".");
+ Estimate.parse("12d.5w.3h");
+ }
+
+ /**
+ * Test for {@link Estimate#parse(String)} with invalid input.
+ */
+ @Test
+ public void testParseWithInvalidInput4() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("12");
+ Estimate.parse("2h 12");
+ }
+
+ /**
+ * Test for {@link Estimate#toString()}.
+ */
+ @Test
+ public void testToString() {
+ assertEquals("1w 1d 1h 1m", new Estimate(MINUTES_PER_WEEK + MINUTES_PER_DAY + MINUTES_PER_HOUR + 1).toString());
+ assertEquals("0", new Estimate().toString());
+ assertEquals("30m", new Estimate(30).toString());
+ assertEquals("1d", new Estimate(MINUTES_PER_DAY).toString());
+ }
+
+}