Skip to content

Commit

Permalink
Fixed issue #11788: Reworked the Emoji-Slider with HTML 5 and unicode…
Browse files Browse the repository at this point in the history
… EMojis.
  • Loading branch information
lacrioque committed Oct 21, 2016
1 parent 782e816 commit 69b08e5
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 147 deletions.
3 changes: 2 additions & 1 deletion application/config/third_party.php
Expand Up @@ -330,7 +330,8 @@
'emoji' => array(
'basePath' => 'third_party.emojifont',
'css' => array(
'css/emoji.css'
'css/emoji.css',
'css/ss-emoji.css'
)
)

Expand Down
3 changes: 2 additions & 1 deletion application/views/survey/questions/5pointchoice/answer.php
Expand Up @@ -39,7 +39,8 @@
<?php elseif($slider_rating==2):?>
<script type='text/javascript'>
<!--
doRatingSlider( <?php echo $sliderId; ?> );
var doRatingSlider_<?php echo $sliderId; ?> = new getRatingSlider( <?php echo $sliderId; ?> );
doRatingSlider_<?php echo $sliderId; ?>();
-->
</script>
<?php endif;?>
Expand Down
268 changes: 158 additions & 110 deletions scripts/slider-rating.js
Expand Up @@ -5,123 +5,171 @@
* Update answers part for rating slider
*
* @author Markus Flür (lacrioque)
* @TODO: Make switches for mandatory-question-taking
* @TODO: Make sizes responsive
* @param {number} qId The qid of the question where apply.
*/
function doRatingSlider(qID) {
//Find the basic php-generated part of the question
var answersList=$('#question'+qID+' .answers-list.radio-list:not(.slidered-list)');
if(!answersList){return;} //if the generated part is not available => jump out of this method
function getRatingSlider(qID){
//Find the basic php-generated part of the question
var answersList=$('#question'+qID+' .answers-list.radio-list:not(.slidered-list)'),
basicSettings = {
},
package = {
sliderHtmlElement : $("<div id='emoji_slider_container_"+qID+"' class='slider-wrapper'></div>"), //wrapper for the slider parts
sliderInnerHtmlElement : $("<div class='slider-labels'></div>"), //the labels of the wrapper
sliderGrabContainer : $("<div id='emoji_slider_grab_container_"+qID+"' class='slider-grab-container'></div>"), //the container for the handle and the colorline
sliderLine : $("<div id='slider_line_item_"+qID+"' class='slider-line'></div>"), //The colored baseline
sliderHandle : $("<div id='slider_handle_item_"+qID+"' class='slider-handle'></div>"), //the handle
sliderLabelEmoji : $("<div class='slider-label'><i class='emoji emoji-enormous'></i></div>"),
sliderLabelNoAnswer : $("<div class='slider-label slider-label-6' data-position='6'><i class='fa fa-ban' style='font-size:28px;'></i></div>")
},
mapEmojiToValue = function(position){
var imageMap = {1: "emoji-sad",2: "emoji-mildlyunamused",3: "emoji-whatever",4: "emoji-smile",5: "emoji-grin-eyes",6: "emoji-grin",7: "emoji-bigsmile"};
return imageMap[position];
},
calculateMapObjects = function(){
var maps = {};
//the map to the relative position based on the container for screens >640px
var mapToStickToSelection = { 1 : 31 , 2 : 123, 3 : 214, 4 : 304, 5 : 397, 6 : 476};
//the map to the relative position based on the container for screens <640px
var mapToStickToSelectionSmallScreen = { 1 : 16 , 2 : 72, 3 : 127, 4 : 182, 5 : 236, 6 : 286};
if( $(window).width() < 640){
maps.selection = mapToStickToSelectionSmallScreen;
} else {
maps.selection = mapToStickToSelection;
}

var openValue=answersList.find("input:radio:checked").val() || 6, //Select either noAnswer, or the preselected
sliderHtmlElement = $("<div class='slider-wrapper'></div>"), //wrapper for the slider parts
sliderInnerHtmlElement = $("<div class='slider-labels'></div>"), //the labels of the wrapper
sliderGrabContainer = $("<div class='slider-grab-container'></div>"), //the container for the handle and the colorline
sliderHandle = $("<div class='slider-handle'></div>"), //the handle
sliderPosition = 23, //the basic position is 23 pixels left @TODO make that responsive
//map to emojiclasses based on position
mapEmojiToValue = {1: "emoji-sad",2: "emoji-mildlyunamused",3: "emoji-whatever",4: "emoji-smile",5: "emoji-grin-eyes",6: "emoji-grin",7: "emoji-bigsmile"},
//the map to the relative position based on the container
mapToStickToSelection = { 1 : 23 , 2 : 114, 3 : 205, 4 : 296, 5 : 387, 6 : 478};
//Method to set the colored emoji based on the index, also it triggers the change in the base elemenst which triggers the EM
setValueAndColorize = function(index){
answersList.find('input[type=radio][value='+index+']').prop('checked',true).trigger('click');
sliderHtmlElement.find(".slider-label").find('i').removeClass('emoji-color'); //remove all other color-classes
if(index==6){ //if it is the "no Answer" set, add text-danger instead of emoji-color
sliderHtmlElement.find(".slider-label-"+index).find('i').addClass('text-danger');
} else {
sliderHtmlElement.find(".slider-label-6").find('i').removeClass('text-danger');
sliderHtmlElement.find(".slider-label-"+index).find('i').addClass('emoji-color');
}
},
//this methods writes the slider position to the preregistered variable and triggers the colorchange in the emojis
//@TODO: Make this responsive
setSliderPosition = function(rawOffsetX,containerOffset,containerWidth){

var offsetX = (rawOffsetX)-containerOffset.left;
if(offsetX > 0 && offsetX <= 68) {
sliderPosition = 23;
setValueAndColorize(1);
} else if(offsetX > 68 && offsetX <= 159) {
setValueAndColorize(2);
sliderPosition = 114;
} else if(offsetX > 159 && offsetX <= 250) {
setValueAndColorize(3);
sliderPosition = 205;
} else if(offsetX > 250 && offsetX <= 341) {
setValueAndColorize(4);
sliderPosition = 296;
} else if(offsetX > 341 && offsetX <= 432) {
setValueAndColorize(5);
sliderPosition = 387;
} else if(offsetX > 432 && offsetX <= 523) {
setValueAndColorize(6);
sliderPosition = 478;
}
//the map to the field with attributes min and max for screens >640px
var mapToStickToSelection = { 1 : {min: 0, max:78} , 2 : {min: 78, max:171}, 3 : {min: 171, max:264}, 4 : {min: 264, max:357}, 5 : {min: 357, max:450}, 6 : {min: 450, max:543}};
//the map to the field with attributes min and max for screens <640px
var mapToStickToSelectionSmallScreen = { 1 : {min: 0, max:44} , 2 : {min: 44, max:100} , 3 : {min: 100, max:156} , 4 : {min: 156, max:212} , 5 : {min: 212, max:268}, 6 : {min: 268, max:324} };
if( $(window).width() < 640){
maps.borders = mapToStickToSelectionSmallScreen;
} else {
maps.borders = mapToStickToSelection;
}

return sliderPosition;
},
//the Method to be called when either the mousebutton is freed, or the cursor leaves the wrapper
onEndDrag = function(e){
sliderHandle.css('left', sliderPosition+"px");
sliderGrabContainer.off("mousemove.drag");
sliderGrabContainer.off("mouseup.drag");
sliderHtmlElement.off("mouseleave.drag");
},
//method to set the handle and therefore register a change event
onSetHandlePosition = function(e){
setSliderPosition(e.screenX, sliderGrabContainer.offset(),sliderGrabContainer.width());
sliderHandle.css('left', sliderPosition+"px");
};
return maps
},
//Method setting the value on the EM-calculateable part
setValueAndColorize = function(index){
answersList.find('input[type=radio][value='+index+']').prop('checked',true).trigger('click');
$("#emoji_slider_container_"+qID).find(".slider-label").find('i').removeClass('emoji-color'); //remove all other color-classes
if(index==6){ //if it is the "no Answer" set, add text-danger instead of emoji-color
$("#emoji_slider_container_"+qID).find(".slider-label-"+index).find('i').addClass('text-danger');
} else {
$("#emoji_slider_container_"+qID).find(".slider-label-6").find('i').removeClass('text-danger');
$("#emoji_slider_container_"+qID).find(".slider-label-"+index).find('i').addClass('emoji-color');
}
},
//Method to unset all events on dragend
onEndDrag = function(e){
var element = this;
onSetHandlePosition(e,element);
$("#emoji_slider_container_"+qID).find('.slider-grab-container').off("mousemove.dragOn");
$("#emoji_slider_container_"+qID).find('.slider-grab-container').off("mouseup.dragOn");
},
//method to set the handle and therefore register a change event
onSetHandlePosition = function(e,element){
var rawPosition = e.pageX,
element = element || this;
var position = calculateSliderPositionFromOffset(rawPosition);
var index = getIndexFromPosition(position);
setSliderPosition(position,element);
setValueAndColorize(index);
},
checkHasNoAnswerOption = function(){
return (answersList.find('.noanswer-item').length > 0);
},
checkOpenValue = function(){
var openValue = answersList.find("input:radio:checked").val() || (checkHasNoAnswerOption() ? 6 : 1); //Select either noAnswer, or the preselected
return openValue || false;
},
relativeOffset = function(rawOffset){
var baseZero = package.sliderHtmlElement.offset();
var relativeZero = baseZero.left;
var offset = rawOffset-relativeZero;
return offset;
},
getIndexFromPosition = function(position){
var maps = calculateMapObjects();
for(var i in maps.borders){
if(maps.selection[i] == position) {
return i;
}
}
},
calculateSliderPositionFromOffset = function(rawOffsetX){
var offsetX = relativeOffset(rawOffsetX);
var maps = calculateMapObjects();
for(var i in maps.borders){
if(offsetX >= maps.borders[i]['min'] && offsetX < maps.borders[i]['max']) {
return maps.selection[i];
}
}
},
getSliderPositionFromIndex = function(index){
var maps = calculateMapObjects();
return maps.selection[index];
},
setSliderPosition = function(position,element){
$(element).closest('.slider-wrapper').find('.slider-handle').css('left', position+"px");
},
//Register the events
bindEventsToContainer = function(){
//Register the click event on the slider container
//this event will trigger when the slider area is clicked,
//to move the slider to where the click was generated
$("#emoji_slider_grab_container_"+qID).on('click', onSetHandlePosition );
//Registers to the event, when the handle is dragged.
//Emulate a dragging behaviour, that is ignoring the Y-axis and skipst through the predefined stoppoints
$("#emoji_slider_grab_container_"+qID).find('.slider-handle').on("mousedown.drag", function(e){
//set events on dragging, on leaving the container and on dropping the handle
$("#emoji_slider_grab_container_"+qID).on("mousemove.dragOn", onSetHandlePosition );
$("#emoji_slider_grab_container_"+qID).on("mouseup.dragOn", onEndDrag);
});
//Bind events for clicking on the labels
$("#emoji_slider_container_"+qID).find('.slider-label').on('click',onSetHandlePosition);
},
pinUpHtml = function(){
//Fill the inner Element with the labels
//create 5 Emojis and append them as labels to the label-row
for (i=1; i<=5; i++) {
package.sliderLabelEmoji.clone().addClass("slider-label-"+i).data('position', i).find('i').addClass(mapEmojiToValue(i)).end().appendTo(package.sliderInnerHtmlElement);
}
//Add the "no answer" label, if the question need one, also add the mandatory class to the sliderline
if(!checkHasNoAnswerOption()){
package.sliderLine.addClass('mandatory');
package.sliderLine.addClass('mandatory');
} else {
package.sliderInnerHtmlElement.append(package.sliderLabelNoAnswer);
}
//append the colored line on the bottom
package.sliderGrabContainer.append(package.sliderLine);
//append the handle
package.sliderGrabContainer.append(package.sliderHandle);

//Append things to the main elemen
//append the labels
package.sliderHtmlElement.append(package.sliderInnerHtmlElement);
//append the grabContainer (handle + colored baseline)
package.sliderHtmlElement.append(package.sliderGrabContainer);
//put everything on the screen
answersList.after(package.sliderHtmlElement);
},

//create 5 Emojis and append them as labels to the label-row
for (i=1; i<=5; i++) {
sliderInnerHtmlElement.append("<div class='slider-label slider-label-"+i+"' data-position='"+i+"'><i class='emoji emoji-enormous "+mapEmojiToValue[i]+"'></i></div>");
doRatingSlider = function () {
if(!answersList){return;} //if the generated part is not available => jump out of this method
//hide the basic radioes
pinUpHtml();
bindEventsToContainer();
answersList.addClass("slidered-list hide read");
//if a value is set, set it in the emojis
var openValue = checkOpenValue();
if(openValue){
setValueAndColorize(openValue);
setSliderPosition(getSliderPositionFromIndex(openValue), package.sliderGrabContainer);
}
}
//Add the "no answer" label
//@TODO: create a method for mandatories
sliderInnerHtmlElement.append("<div class='slider-label slider-label-6' data-position='6'><i class='fa fa-ban' style='font-size:28px;'></i></div>");

//Append things to the grab-container
//append the colored line on the bottom
sliderGrabContainer.append("<div class='slider-line'></div>");
//append the handle
sliderGrabContainer.append(sliderHandle);

//append the labels
sliderHtmlElement.append(sliderInnerHtmlElement);
//append the grabContainer (handle + colored baseline)
sliderHtmlElement.append(sliderGrabContainer);

sliderGrabContainer.on('click',function(e){
setSliderPosition(e.screenX, sliderGrabContainer.offset(),sliderGrabContainer.width());
sliderHandle.css('left', sliderPosition+"px");
});

sliderHandle.on("mousedown.drag", function(e){

sliderGrabContainer.on("mousemove.drag", function(e){
setSliderPosition(e.screenX, sliderGrabContainer.offset(),sliderGrabContainer.width());
sliderHandle.css('left', sliderPosition+"px");
});

sliderHtmlElement.on("mouseleave.drag", onEndDrag);
sliderGrabContainer.on("mouseup.drag", onEndDrag);
});

answersList.after(sliderHtmlElement);

$('.slider-label').on('click',function(){
setValueAndColorize($(this).data('position'));
sliderHandle.css("left",mapToStickToSelection[$(this).data('position')]);
});

//answersList.addClass("slidered-list hide read");
if(openValue){
setValueAndColorize(openValue);
sliderHandle.css("left",mapToStickToSelection[openValue]);
}

return doRatingSlider;
}

0 comments on commit 69b08e5

Please sign in to comment.