Implement Event Ratings
BIN
app/assets/images/big-star.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
app/assets/images/cancel-off.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
app/assets/images/cancel-on.png
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
app/assets/images/mid-star.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/assets/images/star-half.png
Normal file
|
After Width: | Height: | Size: 667 B |
BIN
app/assets/images/star-off.png
Normal file
|
After Width: | Height: | Size: 685 B |
BIN
app/assets/images/star-on.png
Normal file
|
After Width: | Height: | Size: 631 B |
|
|
@ -47,6 +47,8 @@
|
|||
//= require unobtrusive_flash_bootstrap
|
||||
//= require countable
|
||||
//= require selectize
|
||||
//= require jquery.raty
|
||||
//= require ratyrate
|
||||
|
||||
$(document).ready(function() {
|
||||
$('a[disabled=disabled]').click(function(event){
|
||||
|
|
|
|||
760
app/assets/javascripts/jquery.raty.js
Normal file
|
|
@ -0,0 +1,760 @@
|
|||
/*!
|
||||
* jQuery Raty - A Star Rating Plugin
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* @author : Washington Botelho
|
||||
* @doc : http://wbotelhos.com/raty
|
||||
* @version : 2.7.0
|
||||
*
|
||||
*/
|
||||
|
||||
;
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var methods = {
|
||||
init: function(options) {
|
||||
return this.each(function() {
|
||||
this.self = $(this);
|
||||
|
||||
methods.destroy.call(this.self);
|
||||
|
||||
this.opt = $.extend(true, {}, $.fn.raty.defaults, options);
|
||||
|
||||
methods._adjustCallback.call(this);
|
||||
methods._adjustNumber.call(this);
|
||||
methods._adjustHints.call(this);
|
||||
|
||||
this.opt.score = methods._adjustedScore.call(this, this.opt.score);
|
||||
|
||||
if (this.opt.starType !== 'img') {
|
||||
methods._adjustStarType.call(this);
|
||||
}
|
||||
|
||||
methods._adjustPath.call(this);
|
||||
methods._createStars.call(this);
|
||||
|
||||
if (this.opt.cancel) {
|
||||
methods._createCancel.call(this);
|
||||
}
|
||||
|
||||
if (this.opt.precision) {
|
||||
methods._adjustPrecision.call(this);
|
||||
}
|
||||
|
||||
methods._createScore.call(this);
|
||||
methods._apply.call(this, this.opt.score);
|
||||
methods._setTitle.call(this, this.opt.score);
|
||||
methods._target.call(this, this.opt.score);
|
||||
|
||||
if (this.opt.readOnly) {
|
||||
methods._lock.call(this);
|
||||
} else {
|
||||
this.style.cursor = 'pointer';
|
||||
|
||||
methods._binds.call(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_adjustCallback: function() {
|
||||
var options = ['number', 'readOnly', 'score', 'scoreName', 'target'];
|
||||
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
if (typeof this.opt[options[i]] === 'function') {
|
||||
this.opt[options[i]] = this.opt[options[i]].call(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_adjustedScore: function(score) {
|
||||
if (!score) {
|
||||
return score;
|
||||
}
|
||||
|
||||
return methods._between(score, 0, this.opt.number);
|
||||
},
|
||||
|
||||
_adjustHints: function() {
|
||||
if (!this.opt.hints) {
|
||||
this.opt.hints = [];
|
||||
}
|
||||
|
||||
if (!this.opt.halfShow && !this.opt.half) {
|
||||
return;
|
||||
}
|
||||
|
||||
var steps = this.opt.precision ? 10 : 2;
|
||||
|
||||
for (var i = 0; i < this.opt.number; i++) {
|
||||
var group = this.opt.hints[i];
|
||||
|
||||
if (Object.prototype.toString.call(group) !== '[object Array]') {
|
||||
group = [group];
|
||||
}
|
||||
|
||||
this.opt.hints[i] = [];
|
||||
|
||||
for (var j = 0; j < steps; j++) {
|
||||
var
|
||||
hint = group[j],
|
||||
last = group[group.length - 1];
|
||||
|
||||
if (last === undefined) {
|
||||
last = null;
|
||||
}
|
||||
|
||||
this.opt.hints[i][j] = hint === undefined ? last : hint;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_adjustNumber: function() {
|
||||
this.opt.number = methods._between(this.opt.number, 1, this.opt.numberMax);
|
||||
},
|
||||
|
||||
_adjustPath: function() {
|
||||
this.opt.path = this.opt.path || '';
|
||||
|
||||
if (this.opt.path && this.opt.path.charAt(this.opt.path.length - 1) !== '/') {
|
||||
this.opt.path += '/';
|
||||
}
|
||||
},
|
||||
|
||||
_adjustPrecision: function() {
|
||||
this.opt.half = true;
|
||||
},
|
||||
|
||||
_adjustStarType: function() {
|
||||
var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn'];
|
||||
|
||||
this.opt.path = '';
|
||||
|
||||
for (var i = 0; i < replaces.length; i++) {
|
||||
this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-');
|
||||
}
|
||||
},
|
||||
|
||||
_apply: function(score) {
|
||||
methods._fill.call(this, score);
|
||||
|
||||
if (score) {
|
||||
if (score > 0) {
|
||||
this.score.val(score);
|
||||
}
|
||||
|
||||
methods._roundStars.call(this, score);
|
||||
}
|
||||
},
|
||||
|
||||
_between: function(value, min, max) {
|
||||
return Math.min(Math.max(parseFloat(value), min), max);
|
||||
},
|
||||
|
||||
_binds: function() {
|
||||
if (this.cancel) {
|
||||
methods._bindOverCancel.call(this);
|
||||
methods._bindClickCancel.call(this);
|
||||
methods._bindOutCancel.call(this);
|
||||
}
|
||||
|
||||
methods._bindOver.call(this);
|
||||
methods._bindClick.call(this);
|
||||
methods._bindOut.call(this);
|
||||
},
|
||||
|
||||
_bindClick: function() {
|
||||
var that = this;
|
||||
|
||||
that.stars.on('click.raty', function(evt) {
|
||||
var
|
||||
execute = true,
|
||||
score = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt'));
|
||||
|
||||
if (that.opt.click) {
|
||||
execute = that.opt.click.call(that, +score, evt);
|
||||
}
|
||||
|
||||
if (execute || execute === undefined) {
|
||||
if (that.opt.half && !that.opt.precision) {
|
||||
score = methods._roundHalfScore.call(that, score);
|
||||
}
|
||||
|
||||
methods._apply.call(that, score);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_bindClickCancel: function() {
|
||||
var that = this;
|
||||
|
||||
that.cancel.on('click.raty', function(evt) {
|
||||
that.score.removeAttr('value');
|
||||
|
||||
if (that.opt.click) {
|
||||
that.opt.click.call(that, null, evt);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_bindOut: function() {
|
||||
var that = this;
|
||||
|
||||
that.self.on('mouseleave.raty', function(evt) {
|
||||
var score = +that.score.val() || undefined;
|
||||
|
||||
methods._apply.call(that, score);
|
||||
methods._target.call(that, score, evt);
|
||||
methods._resetTitle.call(that);
|
||||
|
||||
if (that.opt.mouseout) {
|
||||
that.opt.mouseout.call(that, score, evt);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_bindOutCancel: function() {
|
||||
var that = this;
|
||||
|
||||
that.cancel.on('mouseleave.raty', function(evt) {
|
||||
var icon = that.opt.cancelOff;
|
||||
|
||||
if (that.opt.starType !== 'img') {
|
||||
icon = that.opt.cancelClass + ' ' + icon;
|
||||
}
|
||||
|
||||
methods._setIcon.call(that, this, icon);
|
||||
|
||||
if (that.opt.mouseout) {
|
||||
var score = +that.score.val() || undefined;
|
||||
|
||||
that.opt.mouseout.call(that, score, evt);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_bindOver: function() {
|
||||
var that = this,
|
||||
action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty';
|
||||
|
||||
that.stars.on(action, function(evt) {
|
||||
var score = methods._getScoreByPosition.call(that, evt, this);
|
||||
|
||||
methods._fill.call(that, score);
|
||||
|
||||
if (that.opt.half) {
|
||||
methods._roundStars.call(that, score, evt);
|
||||
methods._setTitle.call(that, score, evt);
|
||||
|
||||
that.self.data('score', score);
|
||||
}
|
||||
|
||||
methods._target.call(that, score, evt);
|
||||
|
||||
if (that.opt.mouseover) {
|
||||
that.opt.mouseover.call(that, score, evt);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_bindOverCancel: function() {
|
||||
var that = this;
|
||||
|
||||
that.cancel.on('mouseover.raty', function(evt) {
|
||||
var
|
||||
starOff = that.opt.path + that.opt.starOff,
|
||||
icon = that.opt.cancelOn;
|
||||
|
||||
if (that.opt.starType === 'img') {
|
||||
that.stars.attr('src', starOff);
|
||||
} else {
|
||||
icon = that.opt.cancelClass + ' ' + icon;
|
||||
|
||||
that.stars.attr('class', starOff);
|
||||
}
|
||||
|
||||
methods._setIcon.call(that, this, icon);
|
||||
methods._target.call(that, null, evt);
|
||||
|
||||
if (that.opt.mouseover) {
|
||||
that.opt.mouseover.call(that, null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_buildScoreField: function() {
|
||||
return $('<input />', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this);
|
||||
},
|
||||
|
||||
_createCancel: function() {
|
||||
var icon = this.opt.path + this.opt.cancelOff,
|
||||
cancel = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass });
|
||||
|
||||
if (this.opt.starType === 'img') {
|
||||
cancel.attr({ src: icon, alt: 'x' });
|
||||
} else {
|
||||
// TODO: use $.data
|
||||
cancel.attr('data-alt', 'x').addClass(icon);
|
||||
}
|
||||
|
||||
if (this.opt.cancelPlace === 'left') {
|
||||
this.self.prepend(' ').prepend(cancel);
|
||||
} else {
|
||||
this.self.append(' ').append(cancel);
|
||||
}
|
||||
|
||||
this.cancel = cancel;
|
||||
},
|
||||
|
||||
_createScore: function() {
|
||||
var score = $(this.opt.targetScore);
|
||||
|
||||
this.score = score.length ? score : methods._buildScoreField.call(this);
|
||||
},
|
||||
|
||||
_createStars: function() {
|
||||
for (var i = 1; i <= this.opt.number; i++) {
|
||||
var
|
||||
name = methods._nameForIndex.call(this, i),
|
||||
attrs = { alt: i, src: this.opt.path + this.opt[name] };
|
||||
|
||||
if (this.opt.starType !== 'img') {
|
||||
attrs = { 'data-alt': i, 'class': attrs.src }; // TODO: use $.data.
|
||||
}
|
||||
|
||||
attrs.title = methods._getHint.call(this, i);
|
||||
|
||||
$('<' + this.opt.starType + ' />', attrs).appendTo(this);
|
||||
|
||||
if (this.opt.space) {
|
||||
this.self.append(i < this.opt.number ? ' ' : '');
|
||||
}
|
||||
}
|
||||
|
||||
this.stars = this.self.children(this.opt.starType);
|
||||
},
|
||||
|
||||
_error: function(message) {
|
||||
$(this).text(message);
|
||||
|
||||
$.error(message);
|
||||
},
|
||||
|
||||
_fill: function(score) {
|
||||
var hash = 0;
|
||||
|
||||
for (var i = 1; i <= this.stars.length; i++) {
|
||||
var
|
||||
icon,
|
||||
star = this.stars[i - 1],
|
||||
turnOn = methods._turnOn.call(this, i, score);
|
||||
|
||||
if (this.opt.iconRange && this.opt.iconRange.length > hash) {
|
||||
var irange = this.opt.iconRange[hash];
|
||||
|
||||
icon = methods._getRangeIcon.call(this, irange, turnOn);
|
||||
|
||||
if (i <= irange.range) {
|
||||
methods._setIcon.call(this, star, icon);
|
||||
}
|
||||
|
||||
if (i === irange.range) {
|
||||
hash++;
|
||||
}
|
||||
} else {
|
||||
icon = this.opt[turnOn ? 'starOn' : 'starOff'];
|
||||
|
||||
methods._setIcon.call(this, star, icon);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getFirstDecimal: function(number) {
|
||||
var
|
||||
decimal = number.toString().split('.')[1],
|
||||
result = 0;
|
||||
|
||||
if (decimal) {
|
||||
result = parseInt(decimal.charAt(0), 10);
|
||||
|
||||
if (decimal.slice(1, 5) === '9999') {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_getRangeIcon: function(irange, turnOn) {
|
||||
return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff;
|
||||
},
|
||||
|
||||
_getScoreByPosition: function(evt, icon) {
|
||||
var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10);
|
||||
|
||||
if (this.opt.half) {
|
||||
var
|
||||
size = methods._getWidth.call(this),
|
||||
percent = parseFloat((evt.pageX - $(icon).offset().left) / size);
|
||||
|
||||
score = score - 1 + percent;
|
||||
}
|
||||
|
||||
return score;
|
||||
},
|
||||
|
||||
_getHint: function(score, evt) {
|
||||
if (score !== 0 && !score) {
|
||||
return this.opt.noRatedMsg;
|
||||
}
|
||||
|
||||
var
|
||||
decimal = methods._getFirstDecimal.call(this, score),
|
||||
integer = Math.ceil(score),
|
||||
group = this.opt.hints[(integer || 1) - 1],
|
||||
hint = group,
|
||||
set = !evt || this.move;
|
||||
|
||||
if (this.opt.precision) {
|
||||
if (set) {
|
||||
decimal = decimal === 0 ? 9 : decimal - 1;
|
||||
}
|
||||
|
||||
hint = group[decimal];
|
||||
} else if (this.opt.halfShow || this.opt.half) {
|
||||
decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0;
|
||||
|
||||
hint = group[decimal];
|
||||
}
|
||||
|
||||
return hint === '' ? '' : hint || score;
|
||||
},
|
||||
|
||||
_getWidth: function() {
|
||||
var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size'));
|
||||
|
||||
if (!width) {
|
||||
methods._error.call(this, 'Could not get the icon width!');
|
||||
}
|
||||
|
||||
return width;
|
||||
},
|
||||
|
||||
_lock: function() {
|
||||
var hint = methods._getHint.call(this, this.score.val());
|
||||
|
||||
this.style.cursor = '';
|
||||
this.title = hint;
|
||||
|
||||
this.score.prop('readonly', true);
|
||||
this.stars.prop('title', hint);
|
||||
|
||||
if (this.cancel) {
|
||||
this.cancel.hide();
|
||||
}
|
||||
|
||||
this.self.data('readonly', true);
|
||||
},
|
||||
|
||||
_nameForIndex: function(i) {
|
||||
return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff';
|
||||
},
|
||||
|
||||
_resetTitle: function(star) {
|
||||
for (var i = 0; i < this.opt.number; i++) {
|
||||
this.stars[i].title = methods._getHint.call(this, i + 1);
|
||||
}
|
||||
},
|
||||
|
||||
_roundHalfScore: function(score) {
|
||||
var integer = parseInt(score, 10),
|
||||
decimal = methods._getFirstDecimal.call(this, score);
|
||||
|
||||
if (decimal !== 0) {
|
||||
decimal = decimal > 5 ? 1 : 0.5;
|
||||
}
|
||||
|
||||
return integer + decimal;
|
||||
},
|
||||
|
||||
_roundStars: function(score, evt) {
|
||||
var
|
||||
decimal = (score % 1).toFixed(2),
|
||||
name ;
|
||||
|
||||
if (evt || this.move) {
|
||||
name = decimal > 0.5 ? 'starOn' : 'starHalf';
|
||||
} else if (decimal > this.opt.round.down) { // Up: [x.76 .. x.99]
|
||||
name = 'starOn';
|
||||
|
||||
if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 .. x.75]
|
||||
name = 'starHalf';
|
||||
} else if (decimal < this.opt.round.full) { // Down: [x.00 .. x.5]
|
||||
name = 'starOff';
|
||||
}
|
||||
}
|
||||
|
||||
if (name) {
|
||||
var
|
||||
icon = this.opt[name],
|
||||
star = this.stars[Math.ceil(score) - 1];
|
||||
|
||||
methods._setIcon.call(this, star, icon);
|
||||
} // Full down: [x.00 .. x.25]
|
||||
},
|
||||
|
||||
_setIcon: function(star, icon) {
|
||||
star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon;
|
||||
},
|
||||
|
||||
_setTarget: function(target, score) {
|
||||
if (score) {
|
||||
score = this.opt.targetFormat.toString().replace('{score}', score);
|
||||
}
|
||||
|
||||
if (target.is(':input')) {
|
||||
target.val(score);
|
||||
} else {
|
||||
target.html(score);
|
||||
}
|
||||
},
|
||||
|
||||
_setTitle: function(score, evt) {
|
||||
if (score) {
|
||||
var
|
||||
integer = parseInt(Math.ceil(score), 10),
|
||||
star = this.stars[integer - 1];
|
||||
|
||||
star.title = methods._getHint.call(this, score, evt);
|
||||
}
|
||||
},
|
||||
|
||||
_target: function(score, evt) {
|
||||
if (this.opt.target) {
|
||||
var target = $(this.opt.target);
|
||||
|
||||
if (!target.length) {
|
||||
methods._error.call(this, 'Target selector invalid or missing!');
|
||||
}
|
||||
|
||||
var mouseover = evt && evt.type === 'mouseover';
|
||||
|
||||
if (score === undefined) {
|
||||
score = this.opt.targetText;
|
||||
} else if (score === null) {
|
||||
score = mouseover ? this.opt.cancelHint : this.opt.targetText;
|
||||
} else {
|
||||
if (this.opt.targetType === 'hint') {
|
||||
score = methods._getHint.call(this, score, evt);
|
||||
} else if (this.opt.precision) {
|
||||
score = parseFloat(score).toFixed(1);
|
||||
}
|
||||
|
||||
var mousemove = evt && evt.type === 'mousemove';
|
||||
|
||||
if (!mouseover && !mousemove && !this.opt.targetKeep) {
|
||||
score = this.opt.targetText;
|
||||
}
|
||||
}
|
||||
|
||||
methods._setTarget.call(this, target, score);
|
||||
}
|
||||
},
|
||||
|
||||
_turnOn: function(i, score) {
|
||||
return this.opt.single ? (i === score) : (i <= score);
|
||||
},
|
||||
|
||||
_unlock: function() {
|
||||
this.style.cursor = 'pointer';
|
||||
this.removeAttribute('title');
|
||||
|
||||
this.score.removeAttr('readonly');
|
||||
|
||||
this.self.data('readonly', false);
|
||||
|
||||
for (var i = 0; i < this.opt.number; i++) {
|
||||
this.stars[i].title = methods._getHint.call(this, i + 1);
|
||||
}
|
||||
|
||||
if (this.cancel) {
|
||||
this.cancel.css('display', '');
|
||||
}
|
||||
},
|
||||
|
||||
cancel: function(click) {
|
||||
return this.each(function() {
|
||||
var self = $(this);
|
||||
|
||||
if (self.data('readonly') !== true) {
|
||||
methods[click ? 'click' : 'score'].call(self, null);
|
||||
|
||||
this.score.removeAttr('value');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
click: function(score) {
|
||||
return this.each(function() {
|
||||
if ($(this).data('readonly') !== true) {
|
||||
score = methods._adjustedScore.call(this, score);
|
||||
|
||||
methods._apply.call(this, score);
|
||||
|
||||
if (this.opt.click) {
|
||||
this.opt.click.call(this, score, $.Event('click'));
|
||||
}
|
||||
|
||||
methods._target.call(this, score);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
return this.each(function() {
|
||||
var self = $(this),
|
||||
raw = self.data('raw');
|
||||
|
||||
if (raw) {
|
||||
self.off('.raty').empty().css({ cursor: raw.style.cursor }).removeData('readonly');
|
||||
} else {
|
||||
self.data('raw', self.clone()[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getScore: function() {
|
||||
var score = [],
|
||||
value ;
|
||||
|
||||
this.each(function() {
|
||||
value = this.score.val();
|
||||
|
||||
score.push(value ? +value : undefined);
|
||||
});
|
||||
|
||||
return (score.length > 1) ? score : score[0];
|
||||
},
|
||||
|
||||
move: function(score) {
|
||||
return this.each(function() {
|
||||
var
|
||||
integer = parseInt(score, 10),
|
||||
decimal = methods._getFirstDecimal.call(this, score);
|
||||
|
||||
if (integer >= this.opt.number) {
|
||||
integer = this.opt.number - 1;
|
||||
decimal = 10;
|
||||
}
|
||||
|
||||
var
|
||||
width = methods._getWidth.call(this),
|
||||
steps = width / 10,
|
||||
star = $(this.stars[integer]),
|
||||
percent = star.offset().left + steps * decimal,
|
||||
evt = $.Event('mousemove', { pageX: percent });
|
||||
|
||||
this.move = true;
|
||||
|
||||
star.trigger(evt);
|
||||
|
||||
this.move = false;
|
||||
});
|
||||
},
|
||||
|
||||
readOnly: function(readonly) {
|
||||
return this.each(function() {
|
||||
var self = $(this);
|
||||
|
||||
if (self.data('readonly') !== readonly) {
|
||||
if (readonly) {
|
||||
self.off('.raty').children('img').off('.raty');
|
||||
|
||||
methods._lock.call(this);
|
||||
} else {
|
||||
methods._binds.call(this);
|
||||
methods._unlock.call(this);
|
||||
}
|
||||
|
||||
self.data('readonly', readonly);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
return methods.set.call(this, {});
|
||||
},
|
||||
|
||||
score: function() {
|
||||
var self = $(this);
|
||||
|
||||
return arguments.length ? methods.setScore.apply(self, arguments) : methods.getScore.call(self);
|
||||
},
|
||||
|
||||
set: function(options) {
|
||||
return this.each(function() {
|
||||
$(this).raty($.extend({}, this.opt, options));
|
||||
});
|
||||
},
|
||||
|
||||
setScore: function(score) {
|
||||
return this.each(function() {
|
||||
if ($(this).data('readonly') !== true) {
|
||||
score = methods._adjustedScore.call(this, score);
|
||||
|
||||
methods._apply.call(this, score);
|
||||
methods._target.call(this, score);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.raty = function(method) {
|
||||
if (methods[method]) {
|
||||
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
} else if (typeof method === 'object' || !method) {
|
||||
return methods.init.apply(this, arguments);
|
||||
} else {
|
||||
$.error('Method ' + method + ' does not exist!');
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.raty.defaults = {
|
||||
cancel : false,
|
||||
cancelClass : 'raty-cancel',
|
||||
cancelHint : 'Cancel this rating!',
|
||||
cancelOff : 'cancel-off.png',
|
||||
cancelOn : 'cancel-on.png',
|
||||
cancelPlace : 'left',
|
||||
click : undefined,
|
||||
half : false,
|
||||
halfShow : true,
|
||||
hints : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
|
||||
iconRange : undefined,
|
||||
mouseout : undefined,
|
||||
mouseover : undefined,
|
||||
noRatedMsg : 'Not rated yet!',
|
||||
number : 5,
|
||||
numberMax : 20,
|
||||
path : undefined,
|
||||
precision : false,
|
||||
readOnly : false,
|
||||
round : { down: 0.25, full: 0.6, up: 0.76 },
|
||||
score : undefined,
|
||||
scoreName : 'score',
|
||||
single : false,
|
||||
space : true,
|
||||
starHalf : 'star-half.png',
|
||||
starOff : 'star-off.png',
|
||||
starOn : 'star-on.png',
|
||||
starType : 'img',
|
||||
target : undefined,
|
||||
targetFormat : '{score}',
|
||||
targetKeep : false,
|
||||
targetScore : undefined,
|
||||
targetText : '',
|
||||
targetType : 'hint'
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
62
app/assets/javascripts/ratyrate.js.erb
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
$.fn.raty.defaults.half = false;
|
||||
$.fn.raty.defaults.halfShow = true;
|
||||
$.fn.raty.defaults.path = "/assets";
|
||||
$.fn.raty.defaults.cancel = false;
|
||||
|
||||
$(function(){
|
||||
$(".star").each(function() {
|
||||
var $readonly = ($(this).attr('data-readonly') == 'true');
|
||||
var $half = ($(this).attr('data-enable-half') == 'true');
|
||||
var $halfShow = ($(this).attr('data-half-show') == 'true');
|
||||
var $single = ($(this).attr('data-single') == 'true');
|
||||
$(this).raty({
|
||||
score: function() {
|
||||
return $(this).attr('data-rating')
|
||||
},
|
||||
number: function() {
|
||||
return $(this).attr('data-star-count')
|
||||
},
|
||||
half: $half,
|
||||
halfShow: $halfShow,
|
||||
single: $single,
|
||||
path: $(this).attr('data-star-path'),
|
||||
starOn: $(this).attr('data-star-on'),
|
||||
starOff: $(this).attr('data-star-off'),
|
||||
starHalf: $(this).attr('data-star-half'),
|
||||
cancel: $(this).attr('data-cancel'),
|
||||
cancelPlace: $(this).attr('data-cancel-place'),
|
||||
cancelHint: $(this).attr('data-cancel-hint'),
|
||||
cancelOn: $(this).attr('data-cancel-on'),
|
||||
cancelOff: $(this).attr('data-cancel-off'),
|
||||
noRatedMsg: $(this).attr('data-no-rated-message'),
|
||||
round: $(this).attr('data-round'),
|
||||
space: $(this).attr('data-space'),
|
||||
target: $(this).attr('data-target'),
|
||||
targetText: $(this).attr('data-target-text'),
|
||||
targetType: $(this).attr('data-target-type'),
|
||||
targetFormat: $(this).attr('data-target-format'),
|
||||
targetScoret: $(this).attr('data-target-score'),
|
||||
readOnly: $readonly,
|
||||
click: function(score, evt) {
|
||||
var _this = this;
|
||||
if (score == null) { score = 0; }
|
||||
$.post('<%= Rails.application.class.routes.url_helpers.rate_path %>',
|
||||
{
|
||||
score: score,
|
||||
dimension: $(this).attr('data-dimension'),
|
||||
id: $(this).attr('data-id'),
|
||||
klass: $(this).attr('data-classname')
|
||||
},
|
||||
function(data) {
|
||||
if(data) {
|
||||
// success code goes here ...
|
||||
|
||||
if ($(_this).attr('data-disable-after-rate') == 'true') {
|
||||
$(_this).raty('set', { readOnly: true, score: score });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
*= require formtastic-bootstrap
|
||||
*= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
|
||||
*= require osem
|
||||
*= require osem-rating
|
||||
*= require osem-schedule
|
||||
*= require osem-schedule-print
|
||||
*= require osem-dashboard
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/* Styling for voting on proposals*/
|
||||
.myrating.bright { background-image: image-url("star-bright.png"); }
|
||||
.myrating.glow { background-image: image-url("star-glow.png"); }
|
||||
.othersrating.bright { background-image: image-url("star-bright.png"); }
|
||||
.avgrating.bright { background-image: image-url("star-bright.png"); }
|
||||
.avgrating {
|
||||
background: image-url("star.png") 0 0;
|
||||
margin-right: -2px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
}
|
||||
.myrating, .othersrating {
|
||||
background: image-url("star.png") 0 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
|
@ -47,13 +47,12 @@ module Admin
|
|||
@event_types = @program.event_types
|
||||
@comments = @event.root_comments
|
||||
@comment_count = @event.comment_threads.count
|
||||
@ratings = @event.votes.includes(:user)
|
||||
@difficulty_levels = @program.difficulty_levels
|
||||
@versions = @event.versions |
|
||||
PaperTrail::Version.where(item_type: 'Commercial').where_object(commercialable_id: @event.id, commercialable_type: 'Event') |
|
||||
PaperTrail::Version.where(item_type: 'Commercial').where_object_changes(commercialable_id: @event.id, commercialable_type: 'Event') |
|
||||
PaperTrail::Version.where(item_type: 'Vote').where('object_changes LIKE ?', "%\nevent_id:\n- \n- #{@event.id}\n%") |
|
||||
PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%\nevent_id: #{@event.id}\n%")
|
||||
PaperTrail::Version.where(item_type: 'Commercial').where_object_changes(commercialable_id: @event.id, commercialable_type: 'Event')
|
||||
@votable_fields = VotableField.where(votable_type: 'Event', conference: @event.program.conference, enabled: true, for_admin: true)
|
||||
Event.vote(@votable_fields)
|
||||
end
|
||||
|
||||
def edit
|
||||
|
|
@ -142,24 +141,6 @@ module Admin
|
|||
update_state(:restart, 'Review started!')
|
||||
end
|
||||
|
||||
def vote
|
||||
@ratings = @event.votes.includes(:user)
|
||||
|
||||
if (votes = current_user.votes.find_by_event_id(params[:id]))
|
||||
votes.update_attributes(rating: params[:rating])
|
||||
else
|
||||
@myvote = @event.votes.build
|
||||
@myvote.user = current_user
|
||||
@myvote.rating = params[:rating]
|
||||
@myvote.save
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to admin_conference_program_event_path(@conference.short_title, @event) }
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
def registrations
|
||||
@event_registrations = @event.events_registrations
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ module Admin
|
|||
private
|
||||
|
||||
def program_params
|
||||
params.require(:program).permit(:rating, :schedule_public, :schedule_interval, :schedule_fluid, :languages, :blind_voting, :voting_start_date, :voting_end_date, :selected_schedule_id)
|
||||
params.require(:program).permit(:rating, :rating_enabled, :schedule_public, :schedule_interval, :schedule_fluid, :languages, :blind_voting, :voting_start_date, :voting_end_date, :selected_schedule_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
57
app/controllers/admin/votable_fields_controller.rb
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
module Admin
|
||||
class VotableFieldsController < ApplicationController
|
||||
load_and_authorize_resource :conference, find_by: :short_title
|
||||
load_and_authorize_resource :votable_field
|
||||
after_action :remove_rates, only: :destroy
|
||||
|
||||
def index; end
|
||||
|
||||
def edit; end
|
||||
|
||||
def new
|
||||
@votable_field = @conference.votable_fields.new
|
||||
end
|
||||
|
||||
def create
|
||||
@votable_field = @conference.votable_fields.new(votable_field_params)
|
||||
if @votable_field.save
|
||||
redirect_to admin_conference_votable_fields_path(@conference.short_title),
|
||||
notice: 'Votable field successfully created.'
|
||||
else
|
||||
flash[:error] = "Creating votable field failed: #{@votable_field.errors.full_messages.join('. ')}."
|
||||
redirect_to new_admin_conference_votable_field_path(@conference.short_title)
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @votable_field.update_attributes(votable_field_params)
|
||||
flash[:notice] = 'Votable field successfully updated'
|
||||
redirect_to admin_conference_votable_fields_path(@conference.short_title)
|
||||
else
|
||||
flash[:error] = "Votable field update failed: #{@votable_field.errors.full_messages.join('. ')}."
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @votable_field.destroy
|
||||
redirect_to admin_conference_votable_fields_path(@conference.short_title),
|
||||
notice: 'Votable field successfully destroyed.'
|
||||
else
|
||||
redirect_to admin_conference_votable_fields_path(@conference.short_title),
|
||||
error: 'Votable field could not be destroyed.' \
|
||||
"#{@votable_field.errors.full_messages.join('. ')}."
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_rates
|
||||
false unless Rate.where(dimension: @votable_field.title).destroy_all && RatingCache.where(dimension: @votable_field.title).destroy_all
|
||||
end
|
||||
|
||||
def votable_field_params
|
||||
params.require(:votable_field).permit(:title, :enabled, :votable_type, :conference_id, :for_admin, :stars)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,6 +15,8 @@ class ProposalsController < ApplicationController
|
|||
def show
|
||||
@event_schedule = @event.event_schedules.find_by(schedule_id: @program.selected_schedule_id)
|
||||
@speakers_ordered = @event.speakers_ordered
|
||||
@votable_fields = VotableField.where(votable_type: 'Event', conference: @event.program.conference, enabled: true, for_admin: false)
|
||||
Event.vote(@votable_fields)
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
|
|||
12
app/controllers/rater_controller.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
class RaterController < ApplicationController
|
||||
def create
|
||||
if user_signed_in?
|
||||
obj = params[:klass].classify.constantize.find(params[:id])
|
||||
obj.rate params[:score].to_f, current_user, params[:dimension]
|
||||
|
||||
render json: true
|
||||
else
|
||||
render json: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -510,17 +510,6 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def vote_change_description(version)
|
||||
user = current_or_last_object_state(version.item_type, version.item_id).user
|
||||
if version.event == 'create'
|
||||
version.previous.nil? ? 'voted on' : "re-added #{user.name}'s vote on"
|
||||
elsif version.event == 'update'
|
||||
"updated #{user.name}'s vote on"
|
||||
else
|
||||
"deleted #{user.name}'s vote on"
|
||||
end
|
||||
end
|
||||
|
||||
def user_change_description(version)
|
||||
if version.event == 'create'
|
||||
link_to_user(version.item_id) + ' signed up'
|
||||
|
|
@ -597,4 +586,12 @@ module ApplicationHelper
|
|||
end
|
||||
concurrent_events
|
||||
end
|
||||
|
||||
def raters(votable_field)
|
||||
users = []
|
||||
votable_field.each do |field|
|
||||
users += User.where(id: Rate.where(dimension: field.title).pluck(:rater_id)).pluck(:name)
|
||||
end
|
||||
users.uniq.join(', ')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ class Ability
|
|||
# ids of all the conferences for which the user has the 'organizer' role
|
||||
conf_ids_for_organizer = Conference.with_role(:organizer, user).pluck(:id)
|
||||
|
||||
can :manage, VotableField, conference_id: conf_ids_for_organizer
|
||||
can :manage, Resource, conference_id: conf_ids_for_organizer
|
||||
can [:new, :create], Conference if user.has_role?(:organizer, :any)
|
||||
can :manage, Conference, id: conf_ids_for_organizer
|
||||
|
|
@ -192,6 +193,7 @@ class Ability
|
|||
# ids of all the conferences for which the user has the 'cfp' role
|
||||
conf_ids_for_cfp = Conference.with_role(:cfp, user).pluck(:id)
|
||||
|
||||
can :manage, VotableField, conference_id: conf_ids_for_cfp
|
||||
can [:index, :show, :update], Resource, conference_id: conf_ids_for_cfp
|
||||
can :manage, Event, program: { conference_id: conf_ids_for_cfp }
|
||||
can :manage, EventType, program: { conference_id: conf_ids_for_cfp }
|
||||
|
|
@ -220,7 +222,6 @@ class Ability
|
|||
end
|
||||
|
||||
can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Event', conference_id: conf_ids_for_cfp
|
||||
can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Vote', conference_id: conf_ids_for_cfp
|
||||
can [:index, :revert_object, :revert_attribute], PaperTrail::Version do |version|
|
||||
version.item_type == 'Commercial' && conf_ids_for_cfp.include?(version.conference_id) &&
|
||||
(version.object.to_s.include?('Event') || version.object_changes.to_s.include?('Event'))
|
||||
|
|
|
|||
4
app/models/average_cache.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
class AverageCache < ActiveRecord::Base
|
||||
belongs_to :rater, class_name: 'User'
|
||||
belongs_to :rateable, polymorphic: true
|
||||
end
|
||||
|
|
@ -22,7 +22,7 @@ class Conference < ActiveRecord::Base
|
|||
has_many :supporters, through: :ticket_purchases, source: :user
|
||||
has_many :tickets, dependent: :destroy
|
||||
has_many :resources, dependent: :destroy
|
||||
|
||||
has_many :votable_fields, dependent: :destroy
|
||||
has_many :lodgings, dependent: :destroy
|
||||
has_many :registrations, dependent: :destroy
|
||||
has_many :participants, through: :registrations, source: :user
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
class Event < ActiveRecord::Base
|
||||
include ActiveRecord::Transitions
|
||||
|
||||
scope :vote, -> (votable_fields) { votable_fields.each { |field| ratyrate_rateable field.title } }
|
||||
|
||||
has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id }
|
||||
|
||||
acts_as_commentable
|
||||
|
|
@ -15,8 +18,6 @@ class Event < ActiveRecord::Base
|
|||
has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser'
|
||||
has_one :submitter, through: :submitter_event_user, source: :user
|
||||
|
||||
has_many :votes, dependent: :destroy
|
||||
has_many :voters, through: :votes, source: :user
|
||||
has_many :commercials, as: :commercialable, dependent: :destroy
|
||||
belongs_to :event_type
|
||||
|
||||
|
|
@ -92,33 +93,9 @@ class Event < ActiveRecord::Base
|
|||
registrations.count < max_attendees
|
||||
end
|
||||
|
||||
##
|
||||
# Finds the rating of the user for the event
|
||||
# ====Returns
|
||||
# * +integer+ -> the rating of the user for the event
|
||||
def user_rating(user)
|
||||
(vote = votes.find_by(user: user)) ? vote.rating : 0
|
||||
end
|
||||
|
||||
##
|
||||
# Checks if the event has votes
|
||||
# If a user is provided, it checks if the event has votes by the user
|
||||
# ====Returns
|
||||
# * +true+ -> If the event has votes (optionally, by the user)
|
||||
# * +false+ -> If the event does not have any votes (optionally, by the user)
|
||||
def voted?(user=nil)
|
||||
return votes.where(user: user).any? if user
|
||||
|
||||
votes.any?
|
||||
end
|
||||
|
||||
def average_rating
|
||||
@total_rating = 0
|
||||
votes.each do |vote|
|
||||
@total_rating = @total_rating + vote.rating
|
||||
end
|
||||
@total = votes.size
|
||||
@total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0
|
||||
def ended?
|
||||
timezone = program.conference.timezone
|
||||
Time.now.in_time_zone(timezone) > event_schedules.find_by(schedule: program.selected_schedule).end_time
|
||||
end
|
||||
|
||||
# get event speakers with the event sumbmitter at the first position
|
||||
|
|
|
|||
3
app/models/overall_average.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class OverallAverage < ActiveRecord::Base
|
||||
belongs_to :rateable, polymorphic: true
|
||||
end
|
||||
|
|
@ -53,11 +53,11 @@ class Program < ActiveRecord::Base
|
|||
accepts_nested_attributes_for :difficulty_levels, allow_destroy: true
|
||||
|
||||
# validates :conference_id, presence: true, uniqueness: true
|
||||
validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 10, only_integer: true }
|
||||
validates :schedule_interval, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 }, presence: true
|
||||
validate :schedule_interval_divisor_60
|
||||
validate :voting_start_date_before_end_date
|
||||
validate :voting_dates_exist
|
||||
validate :voting_dates_exist_for_blind_voting
|
||||
validate :voting_dates_exist_for_rating_enabled
|
||||
|
||||
after_create :create_event_types
|
||||
after_create :create_difficulty_levels
|
||||
|
|
@ -93,10 +93,10 @@ class Program < ActiveRecord::Base
|
|||
end
|
||||
|
||||
##
|
||||
# Checks if both voting_start_date and voting_end_date are set
|
||||
# Checks if both voting_start_date and voting_end_date are set when blind voting is enabled
|
||||
# ====Returns
|
||||
# Errors when the condition is not true
|
||||
def voting_dates_exist
|
||||
def voting_dates_exist_for_blind_voting
|
||||
errors.add(:voting_start_date, 'must be set, when blind voting is enabled') if blind_voting && !voting_start_date && !voting_end_date
|
||||
|
||||
errors.add(:voting_end_date, 'must be set, when blind voting is enabled') if blind_voting && !voting_start_date && !voting_end_date
|
||||
|
|
@ -106,6 +106,15 @@ class Program < ActiveRecord::Base
|
|||
errors.add(:voting_start_date, 'must be set, when voting_end_date is set') if voting_end_date && !voting_start_date
|
||||
end
|
||||
|
||||
##
|
||||
# Checks if both voting_start_date and voting_end_date are set when rating is enabled
|
||||
# ====Returns
|
||||
# Errors when the condition is not true
|
||||
def voting_dates_exist_for_rating_enabled
|
||||
errors.add(:voting_start_date, 'must be set, when voting is enabled') if rating_enabled && !voting_start_date && !voting_end_date
|
||||
errors.add(:voting_end_date, 'must be set, when voting is enabled') if rating_enabled && !voting_start_date && !voting_end_date
|
||||
end
|
||||
|
||||
##
|
||||
# Checks if voting_start_date is before voting_end_date
|
||||
# ====Returns
|
||||
|
|
@ -115,16 +124,7 @@ class Program < ActiveRecord::Base
|
|||
end
|
||||
|
||||
##
|
||||
# Checcks if the program has rating enabled
|
||||
#
|
||||
# ====Returns
|
||||
# * +false+ -> If rating is not enabled
|
||||
# * +true+ -> If rating is enabled
|
||||
def rating_enabled?
|
||||
rating && rating > 0
|
||||
end
|
||||
|
||||
##
|
||||
# Checks if the call for papers for the conference is currently open
|
||||
#
|
||||
# ====Returns
|
||||
|
|
|
|||
4
app/models/rate.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
class Rate < ActiveRecord::Base
|
||||
belongs_to :rater, class_name: 'User'
|
||||
belongs_to :rateable, polymorphic: true
|
||||
end
|
||||
3
app/models/rating_cache.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class RatingCache < ActiveRecord::Base
|
||||
belongs_to :cacheable, polymorphic: true
|
||||
end
|
||||
|
|
@ -5,6 +5,7 @@ class UserDisabled < StandardError
|
|||
end
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
ratyrate_rater
|
||||
rolify
|
||||
has_many :users_roles
|
||||
has_many :roles, through: :users_roles, dependent: :destroy
|
||||
|
|
@ -47,8 +48,6 @@ class User < ActiveRecord::Base
|
|||
has_many :ticket_purchases, dependent: :destroy
|
||||
has_many :payments, dependent: :destroy
|
||||
has_many :tickets, through: :ticket_purchases, source: :ticket
|
||||
has_many :votes, dependent: :destroy
|
||||
has_many :voted_events, through: :votes, source: :events
|
||||
has_many :subscriptions, dependent: :destroy
|
||||
accepts_nested_attributes_for :roles
|
||||
|
||||
|
|
|
|||
20
app/models/votable_field.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
class VotableField < ActiveRecord::Base
|
||||
belongs_to :conference
|
||||
validates :title, :votable_type, :stars, presence: true
|
||||
validates :title, uniqueness: {scope: :votable_type, message: 'already exsists for the selected votable type'}
|
||||
|
||||
VALID_VOTABLE_TYPES = %w(Event).freeze
|
||||
# ratyrate does not allow criterias to have spaces in them
|
||||
validate :no_spaces_in_title
|
||||
validate :correct_votable_type
|
||||
|
||||
private
|
||||
|
||||
def no_spaces_in_title
|
||||
errors.add(:title, 'should not have spaces') unless title.match(/\s/).nil?
|
||||
end
|
||||
|
||||
def correct_votable_type
|
||||
errors.add(:votable_type, "should be one of the following: #{VALID_VOTABLE_TYPES.join(', ')}") unless VALID_VOTABLE_TYPES.include? votable_type
|
||||
end
|
||||
end
|
||||
|
|
@ -1,14 +1,2 @@
|
|||
class Vote < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :event
|
||||
|
||||
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
|
||||
|
||||
delegate :name, to: :user
|
||||
|
||||
private
|
||||
|
||||
def conference_id
|
||||
event.program.conference_id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,10 +42,6 @@
|
|||
Yes
|
||||
- else
|
||||
No
|
||||
%dt
|
||||
Rating Levels
|
||||
%dd#rating
|
||||
= @program.rating
|
||||
.row
|
||||
.col-md-12.text-right
|
||||
= link_to(edit_admin_conference_program_cfp_path(@conference.short_title), class: 'btn btn-primary') do
|
||||
|
|
|
|||
|
|
@ -168,8 +168,8 @@
|
|||
%b Description
|
||||
%td= simple_format(@event.description)
|
||||
|
||||
- if @conference.program && @conference.program.rating && @conference.program.rating > 0
|
||||
= render partial: 'voting'
|
||||
- if @votable_fields.present?
|
||||
= render partial: 'voting'
|
||||
|
||||
.row
|
||||
= link_to "Comments (#{@comment_count})", '#', id: 'event-comment-link'
|
||||
|
|
|
|||
|
|
@ -1,80 +1,32 @@
|
|||
%table.table#myrating
|
||||
- if @program.show_voting?
|
||||
%tr
|
||||
%td.col-md-2
|
||||
%b Rating
|
||||
%td
|
||||
- if @event.average_rating.to_f > 0
|
||||
#{@event.average_rating}/#{@program.rating}
|
||||
- else
|
||||
Rating: 0/#{@program.rating}
|
||||
|
||||
- @program.rating.times do |counter|
|
||||
- if @event.average_rating.to_f.round == counter + 1
|
||||
= label_tag 'label_rating', '', class: 'avgrating', avgrate: true
|
||||
= javascript_tag "$('label[avgrate=true]').prevAll().andSelf().addClass('bright');"
|
||||
- else
|
||||
= label_tag 'label_rating', '', class: 'avgrating'
|
||||
%tr
|
||||
%td
|
||||
%b Voters
|
||||
%td
|
||||
= @event.voters.length
|
||||
- if @event.voters.length > 0
|
||||
(
|
||||
= @ratings.map {|x| "#{x.name}"}.join ', '
|
||||
)
|
||||
%tr
|
||||
%td.col-md-2
|
||||
%b Your vote
|
||||
%td
|
||||
- if @program.voting_period?
|
||||
- @program.rating.times do |counter|
|
||||
- if @event.voted?(current_user) && @event.user_rating(current_user) == counter + 1
|
||||
= link_to '', vote_admin_conference_program_event_path(@conference.short_title, @event, rating: counter + 1), remote: true, id: "label#{counter + 1}", class: 'myrating', voted: true
|
||||
- else
|
||||
= link_to '', vote_admin_conference_program_event_path(@conference.short_title, @event, rating: counter + 1), remote: true, id: "label#{counter + 1}", class: 'myrating'
|
||||
%br
|
||||
- else
|
||||
- @conference.program.rating.times do |counter|
|
||||
- if @event.voted?(current_user) && @event.user_rating(current_user) == counter + 1
|
||||
= label_tag "label#{counter + 1}", '', class: 'othersrating', voted: true
|
||||
= javascript_tag "$('label[voted=true]').prevAll().andSelf().addClass('bright');"
|
||||
- else
|
||||
= label_tag "label#{counter + 1}", '', class: 'othersrating'
|
||||
(#{voting_open_or_close(@program)})
|
||||
|
||||
- if @program.show_voting?
|
||||
- if @ratings.length > 0
|
||||
- @ratings.each do |rate|
|
||||
- unless rate.user_id == current_user.id
|
||||
%hr
|
||||
- if @program.rating_enabled
|
||||
- if @program.voting_period?
|
||||
- if @program.show_voting?
|
||||
%table.table
|
||||
%thead
|
||||
%td.col-md-2
|
||||
%b Overall Votes
|
||||
- @votable_fields.each do |field|
|
||||
%tr
|
||||
%td.col-md-2
|
||||
= field.title
|
||||
%td
|
||||
= rating_for @event, field.title, disable: true, star: field.stars
|
||||
%tr
|
||||
%td
|
||||
= rate.name
|
||||
Voters
|
||||
%td
|
||||
- @conference.program.rating.times do |counter|
|
||||
|
||||
- if @event.voted?(rate.user) && @event.user_rating(rate.user) == counter + 1
|
||||
= label_tag "label#{counter + 1}", "", class: 'othersrating', voted: true
|
||||
= javascript_tag "$('label[voted=true]').prevAll().andSelf().addClass('bright');"
|
||||
- else
|
||||
= label_tag "label#{counter + 1}", "", class: 'othersrating'
|
||||
:javascript
|
||||
$(function () {
|
||||
var checkedId = $("a[voted='true']").attr('id');
|
||||
$('a[id=' + checkedId + ']').prevAll().andSelf().addClass('bright');
|
||||
});
|
||||
|
||||
$(".myrating").hover(
|
||||
function() { // mouseover
|
||||
$(this).prevAll().andSelf().addClass('glow');
|
||||
},
|
||||
function() { // mouseout
|
||||
$(this).siblings().andSelf().removeClass('glow');
|
||||
}
|
||||
);
|
||||
|
||||
$(".myrating").click(function() {
|
||||
$(this).siblings().removeClass("bright");
|
||||
$(this).prevAll().andSelf().addClass("bright");
|
||||
});
|
||||
= raters(@votable_fields)
|
||||
%table.table
|
||||
%thead
|
||||
%td.col-md-2
|
||||
%b Your Votes
|
||||
- @votable_fields.each do |field|
|
||||
%tr
|
||||
%td.col-md-2
|
||||
= field.title
|
||||
%td
|
||||
= rating_for_user @event, current_user, field.title, star: field.stars
|
||||
- else
|
||||
%b
|
||||
= voting_open_or_close(@program)
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
- if @program.show_voting?
|
||||
#{event.average_rating}/#{@program.rating}
|
||||
%br
|
||||
#{pluralize(event.voters.length, 'voter')}
|
||||
%br
|
||||
- @program.rating.times do |counter|
|
||||
- if event.average_rating.to_f.round == counter + 1
|
||||
= label_tag 'label_rating', '', class: 'avgrating', avgrate: true
|
||||
= javascript_tag "$('label[avgrate=true]').prevAll().andSelf().addClass('bright');"
|
||||
- else
|
||||
= label_tag 'label_rating', '', class: 'avgrating'
|
||||
%br
|
||||
|
||||
- if event.voted?(current_user)
|
||||
%span.label.label-success
|
||||
Your rating: #{ event.user_rating(current_user) }
|
||||
- else
|
||||
%span.label.label-danger
|
||||
Not rated
|
||||
|
|
@ -50,9 +50,6 @@
|
|||
%b ID
|
||||
%th
|
||||
%b Title
|
||||
- if @program.rating_enabled?
|
||||
%th
|
||||
%b Rating
|
||||
%th
|
||||
%b Submitter
|
||||
%th
|
||||
|
|
@ -80,11 +77,6 @@
|
|||
= event.id
|
||||
%td
|
||||
= link_to event.title, admin_conference_program_event_path(@conference.short_title, event)
|
||||
|
||||
- if @program.rating_enabled?
|
||||
%td.col-md-1{ 'data-order' => "#{event.average_rating}" }
|
||||
= render partial: 'voting_index', locals: { event: event }
|
||||
|
||||
- if event.submitter && event.submitter.registrations && event.submitter.registrations.count < 1
|
||||
- bgcolor = '#F7819F'
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -36,10 +36,6 @@
|
|||
= event_change_description(version)
|
||||
= "event #{@event.title}"
|
||||
|
||||
- elsif version.item_type == 'Vote'
|
||||
= vote_change_description(version)
|
||||
= "event #{@event.title}"
|
||||
|
||||
- else
|
||||
= general_change_description(version)
|
||||
= link_to 'commercial',
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
$('table#myrating').replaceWith("<%= escape_javascript(render :partial => 'voting') %>");
|
||||
|
|
@ -7,11 +7,11 @@
|
|||
= semantic_form_for(@program, url: admin_conference_program_path(@conference.short_title), html: {multipart: true}) do |f|
|
||||
= f.input :schedule_public, label: 'Show Schedule on the home and splash page'
|
||||
= f.input :schedule_fluid, label: 'Allow submitters to change their event after it is scheduled'
|
||||
= f.input :rating, hint: 'Enter the number of different rating levels you want to have for voting on proposals. Enter 0 if you do not want to vote on proposals.'
|
||||
= f.input :languages, hint: "Enter the languages allowed for events as values of #{link_to('ISO 639-1', 'http://www.loc.gov/standards/iso639-2/php/code_list.php', target: "_blank")} language codes separated with commas. The first language would be the default language. Leave it blank if you do not want to specify languages.".html_safe
|
||||
= f.input :schedule_interval, hint: "It is the minimal time interval of your schedule. The value should be 5, 6, 10, 12, 15, 20, 30 or 60. Warning! Some events could be unscheduled when changing this value."
|
||||
= f.input :blind_voting, hint: 'Enable this feature if you do not want to show voting results and voters prior to user submitting a vote. For the feature to work you need to set the voting dates below as well'
|
||||
= f.input :voting_start_date, as: :string, input_html: { id: 'datetimepicker-voting_start_date', readonly: true, value: (f.object.voting_start_date.to_formatted_s(:db_without_seconds) unless f.object.voting_start_date.nil?) }
|
||||
= f.input :voting_end_date, as: :string, input_html: { id: 'datetimepicker-voting_start_date', readonly: true, value: (f.object.voting_end_date.to_formatted_s(:db_without_seconds) unless f.object.voting_end_date.nil?) }
|
||||
= f.input :rating_enabled, label: 'Enable voting', hint: 'To enable voting you need to set voting dates as well'
|
||||
= f.input :blind_voting, hint: 'Enable this feature if you do not want to show voting results and voters prior to user submitting a vote. For the feature to work you need to set the voting dates below as well', input_html: { class: 'voting_fields' }
|
||||
= f.input :voting_start_date, as: :string, input_html: { id: 'datetimepicker-voting_start_date', readonly: true, value: (f.object.voting_start_date.to_formatted_s(:db_without_seconds) unless f.object.voting_start_date.nil?),class: 'voting_fields' }
|
||||
= f.input :voting_end_date, as: :string, input_html: { id: 'datetimepicker-voting_start_date', readonly: true, value: (f.object.voting_end_date.to_formatted_s(:db_without_seconds) unless f.object.voting_end_date.nil?),class: 'voting_fields' }
|
||||
%p.text-right
|
||||
= f.action :submit, as: :button, button_html: {class: 'btn btn-primary'}
|
||||
|
|
|
|||
|
|
@ -57,11 +57,12 @@
|
|||
|
||||
%h3 Voting Options
|
||||
%hr
|
||||
%dt
|
||||
Rating Levels
|
||||
%dd#rating
|
||||
= @program.rating
|
||||
|
||||
%dt Voting enabled?
|
||||
%dd
|
||||
- if @program.rating_enabled
|
||||
Yes
|
||||
- else
|
||||
No
|
||||
%dt Blind Voting
|
||||
%dd#blind_voting
|
||||
= @program.blind_voting
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
%th Title
|
||||
%th State
|
||||
%th Type
|
||||
%th Rating
|
||||
%th Created At
|
||||
%tbody
|
||||
- @user.events.each do |event|
|
||||
|
|
@ -21,12 +20,4 @@
|
|||
%td= link_to event.title, admin_conference_program_event_path(event.program.conference.short_title, event)
|
||||
%td= event.state
|
||||
%td= "#{event.event_type.title} (#{show_time(event.event_type.length)})"
|
||||
%td
|
||||
- if event.program && event.program.rating && event.program.rating > 0
|
||||
- event.program.rating.times do |counter|
|
||||
- if event.average_rating.to_f.round == counter+1
|
||||
= label_tag 'label_rating', '', class: 'avgrating', avgrate: true
|
||||
= javascript_tag "$('label[avgrate=true]').prevAll().andSelf().addClass('bright');"
|
||||
- else
|
||||
= label_tag 'label_rating', '', class: 'avgrating'
|
||||
%td= event.created_at
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
= link_to 'commercial',
|
||||
admin_conference_commercials_path(conference_id: Conference.find(version.conference_id).short_title)
|
||||
|
||||
- when 'EventsRegistration', 'Comment', 'Vote', 'Event'
|
||||
- when 'EventsRegistration', 'Comment', 'Event'
|
||||
= 'event'
|
||||
- object = current_or_last_object_state(version.item_type, version.item_id)
|
||||
- event_id = object.try(:event_id) || object.try(:commentable_id) || object.id
|
||||
|
|
|
|||
|
|
@ -35,9 +35,6 @@
|
|||
- when 'Comment'
|
||||
= comment_change_description(version)
|
||||
|
||||
- when 'Vote'
|
||||
= vote_change_description(version)
|
||||
|
||||
- when 'User'
|
||||
= user_change_description(version)
|
||||
|
||||
|
|
|
|||
16
app/views/admin/votable_fields/_form.html.haml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
.row
|
||||
.col-md-12
|
||||
.page-header
|
||||
%h1
|
||||
- if @votable_field.new_record?
|
||||
New
|
||||
Votable Field
|
||||
.row
|
||||
.col-md-8
|
||||
= semantic_form_for(@votable_field, :url => (@votable_field.new_record? ? admin_conference_votable_fields_path : admin_conference_votable_field_path(@conference.short_title, @votable_field))) do |f|
|
||||
= f.input :title
|
||||
= f.input :votable_type, collection: options_for_select(['Event'])
|
||||
= f.input :stars, label: 'Add the total number of stars for the field'
|
||||
= f.input :for_admin, label: 'Add this field for voting in admin side only'
|
||||
%p.text-right
|
||||
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }
|
||||
42
app/views/admin/votable_fields/index.html.haml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
.row
|
||||
.col-md-12
|
||||
.page-header
|
||||
%h1 Votable Fields
|
||||
%p.text-muted
|
||||
Select the criteria of rating in conference
|
||||
- if @conference.votable_fields.any?
|
||||
.row
|
||||
.col-md-12
|
||||
%table.table.table-striped.table-bordered.table-hover.datatable
|
||||
%thead
|
||||
%th Enabled?
|
||||
%th Title
|
||||
%th Votable Type
|
||||
%th Actions
|
||||
%tbody
|
||||
- @conference.votable_fields.each do |votable_field|
|
||||
%tr
|
||||
%td
|
||||
- if can? :update, votable_field
|
||||
= check_box_tag @conference.short_title, votable_field.id , votable_field.enabled,
|
||||
method: :patch, url: "/admin/conferences/#{@conference.short_title}/votable_fields/#{votable_field.id}?votable_field[enabled]=",
|
||||
class: 'switch-checkbox', readonly: false, data: { size: 'small', on_color: 'success', off_color: 'warning', on_text: 'Yes', off_text: 'No' }
|
||||
- else
|
||||
= check_box_tag @conference.short_title, votable_field.id , votable_field.enabled,
|
||||
method: :patch, url: "/admin/conferences/#{@conference.short_title}/votable_fields/#{votable_field.id}?votable_field[enabled]=",
|
||||
class: 'switch-checkbox', readonly: true,data: { size: 'small', on_color: 'success', off_color: 'warning', on_text: 'Yes', off_text: 'No' }
|
||||
%td
|
||||
= votable_field.title
|
||||
%td
|
||||
= votable_field.votable_type
|
||||
%td
|
||||
.btn-group
|
||||
= link_to 'Edit', edit_admin_conference_votable_field_path(@conference.short_title, votable_field),
|
||||
method: :get, class: 'btn btn-primary', disabled: !(can? :update, votable_field)
|
||||
= link_to 'Delete', admin_conference_votable_field_path(@conference.short_title, votable_field),
|
||||
method: :delete, class: 'btn btn-danger', disabled: !(can? :destroy, votable_field),
|
||||
data: { confirm: "Do you really want to delete #{votable_field.title}?" }
|
||||
.row
|
||||
.col-md-12
|
||||
= link_to 'Add Votable Field', new_admin_conference_votable_field_path, class: 'btn btn-success pull-right', disabled: !(can? :create, @conference.votable_fields.new)
|
||||
|
||||
1
app/views/admin/votable_fields/new.html.haml
Normal file
|
|
@ -0,0 +1 @@
|
|||
= render 'form'
|
||||
|
|
@ -144,3 +144,8 @@
|
|||
= link_to admin_conference_resources_path(@conference.short_title) do
|
||||
%span.fa.fa-pencil-square
|
||||
Resources
|
||||
- if can? :index, @conference.votable_fields.new
|
||||
%li
|
||||
= link_to admin_conference_votable_fields_path(@conference.short_title) do
|
||||
%span.fa.fa-star
|
||||
Votable Fields
|
||||
|
|
|
|||
|
|
@ -45,6 +45,13 @@
|
|||
.row.speakerbio
|
||||
.col-md-12
|
||||
= markdown(speaker.biography)
|
||||
-if @event.scheduled?
|
||||
%dl.col-md-12
|
||||
-if @event.ended?
|
||||
- @votable_fields.each do |field|
|
||||
%dt= field.title
|
||||
%dd= rating_for @event, field.title
|
||||
%br
|
||||
.col-md-9
|
||||
.row
|
||||
.col-md-12
|
||||
|
|
|
|||