Remove Elements from fullcalendar (by dragging to trash can) - javascript

I have a fullcalendar with external elements being dragged onto it. I'm relatively new to jquery. I don't know quite how to get the ID of the object being dragged to a "trash can" icon. I simply want to drag items off of the calendar to a image and when I let go of the mouse the item is removed.
This is my code.....
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<link rel='stylesheet' type='text/css' href='../fullcalendar.css' />
<script type='text/javascript' src='../jquery/jquery.js'></script>
<script type='text/javascript' src='../jquery/jquery-ui-custom.js'></script>
<script type='text/javascript' src='../fullcalendar.min.js'></script>
<script type='text/javascript'>
$(document).ready(function() {
/* initialize the external events
-----------------------------------------------------------------*/
$('#external-events div.external-event').each(function() {
// create an Event Object (http://arshaw.com/fullcalendar/docs/event_data/Event_Object/)
// it doesn't need to have a start or end
var eventObject = {
title: $.trim($(this).text()) // use the element's text as the event title
};
// store the Event Object in the DOM element so we can get to it later
$(this).data('eventObject', eventObject);
// make the event draggable using jQuery UI
$(this).draggable({
zIndex: 999,
revert: true, // will cause the event to go back to its
revertDuration: 0 // original position after the drag
});
});
/* initialize the calendar
-----------------------------------------------------------------*/
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar !!!
drop: function(date, allDay) { // this function is called when something is dropped
// retrieve the dropped element's stored Event Object
var originalEventObject = $(this).data('eventObject');
// we need to copy it, so that multiple events don't have a reference to the same object
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
copiedEventObject.allDay = allDay;
// render the event on the calendar
// the last `true` argument determines if the event "sticks" (http://arshaw.com/fullcalendar/docs/event_rendering/renderEvent/)
$('#calendar').fullCalendar('renderEvent', copiedEventObject, true);
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
}
});
});
</script>
<style type='text/css'>
body {
margin-top: 40px;
text-align: center;
font-size: 14px;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
}
#wrap {
width: 1100px;
margin: 0 auto;
}
#external-events {
float: left;
width: 150px;
padding: 0 10px;
border: 1px solid #ccc;
background: #eee;
text-align: left;
}
#external-events h4 {
font-size: 16px;
margin-top: 0;
padding-top: 1em;
}
.external-event { /* try to mimick the look of a real event */
margin: 10px 0;
padding: 2px 4px;
background: #3366CC;
color: #fff;
font-size: .85em;
cursor: pointer;
}
#external-events p {
margin: 1.5em 0;
font-size: 11px;
color: #666;
}
#external-events p input {
margin: 0;
vertical-align: middle;
}
#calendar {
float: right;
width: 900px;
}
</style>
</head>
<body>
<div id='wrap'>
<div id='external-events'>
<h4>Draggable Events</h4>
<div class='external-event'>even1</div>
<div class='external-event'>even2</div>
<p>
<input type='checkbox' id='drop-remove' /> <label for='drop-remove'>remove after drop</label>
</p>
</div>
<div id='calendar'></div>
<img src="redmond/images/trash.png" id="trash">
<div style='clear:both'></div>
</div>
</body>
</html>

Complete tutorial, how to add "move to trash" function to fullcalendar
HERE IS DEMO
if you do not want to use droppable:
from fullcalendar.css delete this lines
.fc-view
{
/* prevents dragging outside of widget */
width: 100%;
overflow: hidden;
}
find in fullcalenar.js (line cca 6086)
function eachEventElement(event, exceptElement, funcName) {
var elements = eventElementsByID[event._id],
i, len = elements.length;
for (i=0; i<len; i++) {
if (!exceptElement || elements[i][0] != exceptElement[0]) {
elements[i][funcName]();
}
}
}
and change to:
function eachEventElement(event, exceptElement, funcName) {
var elements = eventElementsByID[event._id],
i;
if (elements != null) {
var len = elements.length;
for (i = 0; i < len; i++) {
if (!exceptElement || elements[i][0] != exceptElement[0]) {
elements[i][funcName]();
}
}
}
}
in js:
//actually cursor position
var currentMousePos = {
x: -1,
y: -1
};
//set actually cursor pos
jQuery(document).ready(function () {
jQuery(document).on("mousemove", function (event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
});
//check if cursor is in trash
function isElemOverDiv() {
var trashEl = jQuery('#calendarTrash');
var ofs = trashEl.offset();
var x1 = ofs.left;
var x2 = ofs.left + trashEl.outerWidth(true);
var y1 = ofs.top;
var y2 = ofs.top + trashEl.outerHeight(true);
if (currentMousePos.x >= x1 && currentMousePos.x <= x2 &&
currentMousePos.y >= y1 && currentMousePos.y <= y2) {
return true;
}
return false;
}
//fullcalendar mouseover callback
eventMouseover: function (event, jsEvent) {
$(this).mousemove(function (e) {
var trashEl = jQuery('#calendarTrash');
if (isElemOverDiv()) {
if (!trashEl.hasClass("to-trash")) {
trashEl.addClass("to-trash");
}
} else {
if (trashEl.hasClass("to-trash")) {
trashEl.removeClass("to-trash");
}
}
});
},
//fullcalendar eventdragstop callback
eventDragStop: function (event, jsEvent, ui, view) {
if (isElemOverDiv()) {
jQuery('#fr-calendar').fullCalendar('removeEvents', event.id);
var trashEl = jQuery('#calendarTrash');
if (trashEl.hasClass("to-trash")) {
trashEl.removeClass("to-trash");
}
}
},
in fullcalendar set option dragRevertDuration: 0,
in fullcalendar declaration add loading callback function for append a trashcalendar:
loading: function (bool) {
if (!bool) {
jQuery('.fc-header-left').append('<div id="calendarTrash" class="calendar-trash"><img src="' + imagePath + '/cal-trash.png"></img></div>');
}
},
css for trash:
div.calendar-trash{
float: left;
padding-right: 8px;
margin-right:5px;
padding-left:8px;
padding-top: 3px;
cursor: pointer;
}
.to-trash{
background-color:#EAEAEA;
-webkit-border-radius: 5em;
border-radius: 5em;
}
If loading callback not working, add trash on end of jquery document ready function.
foo.JFS('.fc-header-left').append('<div id="calendarTrash" class="calendar-trash"><img src="/images/cal-trash.png"></img></div>');
trash icon:

first of all you're gonna need to remove overflow statement from css:
.fc-view
{
/* prevents dragging outside of widget */
width: 100%;
overflow: hidden;
}
then you can use eventDragStop

here is something I just did hope it helps. I dont know if you are using php/mysql but if your not just remove the ajax call and keep what is in the success function.
$('#calendar').children('.fc-content').children().append('<div id="calendarTrash" style="float: right; padding-top: 5px; padding-right: 5px; padding-left: 5px;"><span class="ui-icon ui-icon-trash"></span></div>');
//listens for drop event
$("#calendarTrash").droppable({
tolerance: 'pointer',
drop: function(event, ui) {
if ( dragged && ui.helper && ui.helper[0] === dragged[0] ) {
var event = dragged[1];
var answer = confirm("Delete Event?")
if (answer)
{
$.ajax({
url:'/employees/removeevent?id='+event.id,
dataType: 'json',
async: false,
error: function(xhr, ajaxOptions, thrownError)
{
//console.log(xhr.status);
//console.log(thrownError);
},
success: function()
{
calendar.fullCalendar( 'removeEvents' , event.id );
}
});
}
}
}
});
eventDragStart: function( event, jsEvent, ui, view ) {
dragged = [ ui.helper[0], event ];
},

i tell us drop: function(event, ui) ui :: ui holds an empty object. Before version 2.1, the jQuery UI object. :) and work then jsEvent :) sn :
eventDragStop: function(event, jsEvent, ui, view) {
if (isElemOverDiv(jsEvent, $('div#external-events'))) {
console.log(isElemOverDiv(jsEvent, $('div#external-events')));
$('.calendario').fullCalendar('removeEvents', event.id);
}
}
var isElemOverDiv = function(draggedItem, dropArea) {
var p = dropArea;
var position = p.position();
console.log("EL DROP AREA left: " + position.left + ", top: " + position.top);
console.log('draggedItem.pageY ', draggedItem.clientX, draggedItem.pageY);
if (draggedItem.clientX >= position.left && draggedItem.pageY >= position.top) {
return true;
}
return false;
}

<div id="calendarTrash" class="calendar-trash"><img src="images\trash.png" alt="image"/></div>
` eventDragStop: function(event,jsEvent) {
var trashEl = jQuery('#calendarTrash');
var ofs = trashEl.offset();
var x1 = ofs.left;
var x2 = ofs.left + trashEl.outerWidth(true);
var y1 = ofs.top;
var y2 = ofs.top + trashEl.outerHeight(true);
if (jsEvent.pageX >= x1 && jsEvent.pageX<= x2 &&
jsEvent.pageY >= y1 && jsEvent.pageY <= y2) {
if (confirm("Are you sure to detete " + event.title +" ?")) {
//pour annuker les informations
$('#external-calendar').fullCalendar('removeEvents', event._id);
}
}
}

Related

Javascript pan and zoom

I have a bunch of CSS and SVG elements that get placed into a div and I would like the user to be able to pan and zoom around to all of them. I am using the library from Jquery Panzoom. Is there a way to contain it so that if the user has an element outside of the viewport they will be able to pan to the edge of the container with all of the elements inside of it?
Below I will provide an example of what it's doing now. When it is all zoomed in and there is still an element not seen, I would like to be able to pan to the other element.
When Everything is seen, I would like it to not be able to pan but still zoom.
When it is zoomed all of the ways out, I would like the container to be the width of the viewport. The problem I am having is that if I set the container to be the width of the viewport at its most zoomed out level. It decides to hide below the viewport.
Example
Example using Panzoom
Code from codepen
Html
<div id="chart_container">
<div class="flowchart-example-container" id="example"></div>
</div>
<div class="draggable_operators">
<div class="draggable_operators_label">
Operators (drag and drop them in the flowchart):
</div>
<div class="draggable_operators_divs">
<div class="draggable_operator" data-nb-inputs="1" data-nb-outputs="0">1 input</div>
<div class="draggable_operator" data-nb-inputs="0" data-nb-outputs="1">1 output</div>
<div class="draggable_operator" data-nb-inputs="1" data-nb-outputs="1">1 input & 1 output</div>
<div class="draggable_operator" data-nb-inputs="1" data-nb-outputs="2">1 in & 2 out</div>
<div class="draggable_operator" data-nb-inputs="2" data-nb-outputs="1">2 in & 1 out</div>
<div class="draggable_operator" data-nb-inputs="2" data-nb-outputs="2">2 in & 2 out</div>
</div>
</div>
<button class="delete_selected_button">Delete selected operator / link</button>
CSS
body {
font-family: 'Helvetica Neue', Helvetica, Arial, serif;
font-size: 15px;
font-weight: 400;
line-height: 1.5;
color: #666;
}
#chart_container {
width: 100%;
height: 500px;
overflow: hidden;
background: repeating-linear-gradient( 45deg, #eee, #eee 10px, #e5e5e5 10px, #e5e5e5 20px);
border: 1px solid black;
}
.flowchart-example-container {
height: 200px;
border: 1px solid #BBB;
margin-bottom: 10px;
}
#example {
width: 2000px;
height: 2000px;
background: white;
}
.draggable_operators_label {
margin-bottom: 5px;
}
.draggable_operators_divs {
margin-bottom: 20px;
}
.draggable_operator {
display: inline-block;
padding: 2px 5px 2px 5px;
border: 1px solid #ccc;
cursor: grab;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
Javascript
$(document).ready(function() {
var $flowchart = $('#example');
var $container = $flowchart.parent();
var cx = $flowchart.width() / 2;
var cy = $flowchart.height() / 2;
// Panzoom initialization...
$flowchart.panzoom();
// Centering panzoom
$flowchart.panzoom('pan', -cx + $container.width() / 2, -cy + $container.height() / 2);
// Panzoom zoom handling...
var possibleZooms = [0.5, 0.75, 1, 2, 3];
var currentZoom = 2;
$container.on('mousewheel.focal', function(e) {
e.preventDefault();
var delta = (e.delta || e.originalEvent.wheelDelta) || e.originalEvent.detail;
var zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
currentZoom = Math.max(0, Math.min(possibleZooms.length - 1, (currentZoom + (zoomOut * 2 - 1))));
$flowchart.flowchart('setPositionRatio', possibleZooms[currentZoom]);
$flowchart.panzoom('zoom', possibleZooms[currentZoom], {
animate: false,
focal: e
});
});
var data = {
operators: {
operator1: {
top: cy - 100,
left: cx - 200,
properties: {
title: 'Operator 1',
inputs: {},
outputs: {
output_1: {
label: 'Output',
}
}
}
},
operator2: {
top: cy,
left: cx + 140,
properties: {
title: 'Operator 2',
inputs: {
input_1: {
label: 'Input 1',
},
input_2: {
label: 'Input 2',
},
},
outputs: {}
}
},
},
links: {
link_1: {
fromOperator: 'operator1',
fromConnector: 'output_1',
toOperator: 'operator2',
toConnector: 'input_2',
},
}
};
// Apply the plugin on a standard, empty div...
$flowchart.flowchart({
data: data,
linkWidth: 5
});
$flowchart.parent().siblings('.delete_selected_button').click(function() {
$flowchart.flowchart('deleteSelected');
});
var $draggableOperators = $('.draggable_operator');
function getOperatorData($element) {
var nbInputs = parseInt($element.data('nb-inputs'));
var nbOutputs = parseInt($element.data('nb-outputs'));
var data = {
properties: {
title: $element.text(),
inputs: {},
outputs: {}
}
};
var i = 0;
for (i = 0; i < nbInputs; i++) {
data.properties.inputs['input_' + i] = {
label: 'Input ' + (i + 1)
};
}
for (i = 0; i < nbOutputs; i++) {
data.properties.outputs['output_' + i] = {
label: 'Output ' + (i + 1)
};
}
return data;
}
var operatorId = 0;
$draggableOperators.draggable({
cursor: "move",
opacity: 0.7,
helper: 'clone',
appendTo: 'body',
zIndex: 1000,
helper: function(e) {
var $this = $(this);
var data = getOperatorData($this);
return $flowchart.flowchart('getOperatorElement', data);
},
stop: function(e, ui) {
var $this = $(this);
var elOffset = ui.offset;
var containerOffset = $container.offset();
if (elOffset.left > containerOffset.left &&
elOffset.top > containerOffset.top &&
elOffset.left < containerOffset.left + $container.width() &&
elOffset.top < containerOffset.top + $container.height()) {
var flowchartOffset = $flowchart.offset();
var relativeLeft = elOffset.left - flowchartOffset.left;
var relativeTop = elOffset.top - flowchartOffset.top;
var positionRatio = $flowchart.flowchart('getPositionRatio');
relativeLeft /= positionRatio;
relativeTop /= positionRatio;
var data = getOperatorData($this);
data.left = relativeLeft;
data.top = relativeTop;
$flowchart.flowchart('addOperator', data);
}
}
});
});

Javascript getBoundingClientRect() - apply to multiple instances of class

I've been trying to use a function to detect if an element is in the viewport:
function isElementInViewport (el) {
var rect = el[0].getBoundingClientRect();
return (rect.top>-1 && rect.bottom <= $(window).height());
}
var s= $('.special'),
y = $('.status');
$(window).on('scroll resize', function(){
if(isElementInViewport(s))
{
setTimeout(function(){
if(isElementInViewport(s))
{
var offer_id = s.data("offer-id");
alert(offer_id);
y.text('Yes');
}
}, 3000);
}
else
{
y.text('No');
}
});
Unfortunately this only seems to work for the first instance of the class 'special'. How do I get it to apply to all instances of that class?
Note that I've added a 3 second delay, to prevent fast scrolling from triggering it.
Here's the jsfiddle of my progress: http://jsfiddle.net/azjbrork/6/
using jquery each we can run the function on each instance of the .special class and report back accordingly (snippet below) :
function isElementInViewport(el) {
var rect = el[0].getBoundingClientRect();
return (rect.top > -1 && rect.bottom <= $(window).height());
}
var s = $('.special'),
y = $('.status');
$(window).on('scroll resize', function() {
s.each(function() {
var $this = $(this);
if (isElementInViewport($this)) {
setTimeout(function() {
if (isElementInViewport($this)) {
var offer_id = $this.data("offer_id");
// advise using an underscore instead of a hyphen in data attributes
// alert(offer_id); // reported in text below
y.text('Yes : ' + offer_id);
}
}, 200);
} else {
// y.text('No'); // omit this line otherwise it will always report no (subsequent out of screen divs will overwrite the response)
}
});
});
.special {
width: 80px;
height: 20px;
border: 1px solid #f90;
margin-top: 200px;
}
.status {
position: fixed;
right: 2em;
top: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='special' data-offer_id='a'></div>
<div class='special' data-offer_id='b'></div>
<div class='special' data-offer_id='c'></div>
<div class='special' data-offer_id='d'></div>
<div class='special' data-offer_id='e'></div>
<div class='special' data-offer_id='f'></div>
<div class='status'></div>

How to change the cursor icon when dragging in HTML5?

I need to set the icon for cursor when a user is dragging DIV (red div in the following example).
I have tried several attempt, including using CSS cursor:move and event.dataTransfer.dropEffect with no success, as the icon always show up a "crossed circle".
Any ideas how to solve this issue using HTML5 drag-and-drop API?
http://jsbin.com/hifidunuqa/1/
<script>
window.app = {
config: {
canDrag: false,
cursorOffsetX: null,
cursorOffsetY: null
},
reset: function () {
this.config.cursorOffsetX = null;
this.config.cursorOffsetY = null;
},
start: function () {
document.getElementById('target').addEventListener('dragstart', function (event) {
console.log('+++++++++++++ dragstart')
this.config.cursorOffsetX = event.offsetX;
this.config.cursorOffsetY = event.offsetY;
this.adjustPostion(event);
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.dropEffect = 'move';
}.bind(this));
document.getElementById('target').addEventListener('drag', function (event) {
console.log('+++++++++++++ drag')
this.adjustPostion(event);
}.bind(this));
document.getElementById('target').addEventListener('dragend', function (event) {
console.log('+++++++++++++ dragend')
this.reset();
}.bind(this));;
},
adjustPostion: function (event) {
if (event.pageX <= 0 || event.pageY <= 0) {
console.log('skipped');
return;
}
var elm = document.getElementById('target');
elm.style.left = (event.pageX - this.config.cursorOffsetX) + 'px';
elm.style.top = (event.pageY - this.config.cursorOffsetY) + 'px';
console.log(event.pageX);
console.log(event.pageY);
}
};
</script>
use mousedown and mousemove
window.app = {
dragging: false,
config: {
canDrag: false,
cursorOffsetX: null,
cursorOffsetY: null
},
reset: function () {
this.config.cursorOffsetX = null;
this.config.cursorOffsetY = null;
},
start: function () {
document.getElementById('target').addEventListener('mousedown', function (event) {
console.log('+++++++++++++ dragstart');
this.dragging = true;
this.config.cursorOffsetX = event.offsetX;
this.config.cursorOffsetY = event.offsetY;
this.adjustPostion(event);
}.bind(this));
document.getElementById('target').addEventListener('mousemove', function (event) {
if (this.dragging) {
console.log('+++++++++++++ drag');
event.target.style.cursor = 'move';
this.adjustPostion(event);
}
}.bind(this));
document.getElementById('target').addEventListener('mouseup', function (event) {
console.log('+++++++++++++ dragend');
this.dragging = false;
event.target.style.cursor = 'pointer';
this.reset();
}.bind(this));
},
adjustPostion: function (event) {
if (event.clientX <= 0 || event.clientY <= 0) {
console.log('skipped');
return;
}
var elm = document.getElementById('target');
elm.style.left = (event.clientX - this.config.cursorOffsetX) + 'px';
elm.style.top = (event.clientY - this.config.cursorOffsetY) + 'px';
console.log(event.pageX);
console.log(event.pageY);
}
};
#target {
position: absolute;
top: 100px;
left: 100px;
width: 400px;
height: 400px;
background-color: red;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
}
#ui1 {
position: absolute;
top: 50px;
left: 50px;
width: 100px;
height: 400px;
background-color: blue;
z-index: 100;
}
#ui2 {
position: absolute;
top: 50px;
left: 550px;
width: 100px;
height: 400px;
background-color: green;
z-index: 100;
}
<!-- simulate -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
</head>
<body onload="window.app.start();">
<div id="ui1"></div>
<div id="ui2"></div>
<div id="target"></div>
</body>
</html>
Do you actually need the Drag API? I found that I was using the Drag API because I was having trouble with the reliability of mouse events (mouseups not being captured, for example).
The Drag API is only for drag-and-drop functionality, and, if you're simply fiddling with the reliability of your clicking and pointing events, a new API, .setPointerCapture is made to handle these cases more effectively. Here's the minimal viable way to achieve this:
el.onpointerdown = ev => {
el.onpointermove = pointerMove
el.setPointerCapture(ev.pointerId)
}
pointerMove = ev => {
console.log('Dragged!')
}
el.onpointerUp = ev => {
el.onpointermove = null
el.releasePointerCapture(ev.pointerId)
}
Beautifully, you will maintain full control over your cursor's display style.
I didn't care about a particular cursor, I just wanted to get rid of the "crossed circle" one. My solution was to include dragover event (with following function) to all elements, that already had dragenter, dragstart and dragend events.
function dragover(event)
{
event.dataTransfer.dropEffect = "move";
event.preventDefault();
}
Adding event.dataTransfer.setData(); should solve the problem. Once the element is recognized as draggable the browser will add a move cursor automatically once you drag. Of course, you will have to remove all other cursor: move declarations to see the cursor changing while dragging.
Minimal example:
document.getElementById('target').addEventListener('dragstart', function (event) {
event.dataTransfer.setData( 'text/plain', '' );
}.bind(this));
If you still want to change the icon (e.g. to use a custom drag icon), you could access the element style using event.target.style.cursor.
For more info see MDN Drag & Drop and MDN Recommended Drag Types

Interact with the tooltip in jQuery

How can I interact with the tooltip in jQuery?
You know, the little pop-up appearing when you hover an <a> element or an <img>.
I wanted to make that one follow my cursor when I move onto that tag. Exactly like this.
You might wanna look at jQuery UI's tooltip or the QTip plugin.
A part for mouse tracking tooltip: Mouse tracking
I didn't not tried it but it seems nice: one more
Here is simple jquery plugin for custom tooltip. jsFiddle
You can specify mouseFollow: true to achieve movable tooltip that follows cursor.
JS
(function ($) {
$.fn.tooltip = function (options) {
var defaults = {
background: '#fff',
border: '1px solid #999',
color: 'black',
rounded: false,
mouseFollow: false,
textChangeOnClick: false
},
settings = $.extend({}, defaults, options);
this.each(function () {
var $this = $(this),
title = null,
hovering = null;
//set class
if (!settings.textChangeOnClick) {
$this.addClass('_tooltip');
}
$(this).data('title', $this.attr('title'))
$(this).attr('title', '');
$this.hover(
// mouse over
function (e) {
//check change
if ($this.attr('title') != '') {
if ($this.attr('title') != $this.data('title')) {
$this.data('title', $this.attr('title'));
$this.attr('title','');
}
} else {
$this.removeAttr('title');
}
$this.attr('title', '');
hovering = true;
$('#tooltip').remove();
//create box
if ($this.data('title') != '') {
$('<div id="tooltip" />')
.appendTo('body')
.text($this.data('title'))
.hide()
.css({
backgroundColor: settings.background,
color: settings.color,
border: settings.border,
top: e.pageY + 10,
left: e.pageX + 20
})
.fadeIn(500);
}
if (settings.rounded) {
$('#tooltip').addClass('rounded');
}
},
//mouse out
function () {
hovering = false;
$('#tooltip').remove();
});
//text change
if (settings.textChangeOnClick) {
//on click
$this.on('click', function () {
if (hovering) {
//set new
$this.data('title',$(this).attr('title'));
$(this).attr('title', '');
$('#tooltip').text($this.data('title'));
}
});
}
//mouse follow
if (settings.mouseFollow) {
$this.mousemove(function (e) {
$('#tooltip').css({
top: e.pageY + 10,
left: e.pageX + 20
});
});
}
});
return this;
}
})(jQuery);
SET PLUGIN FOR ELEMENT
$('a').tooltip({
mouseFollow: true
});
HTML
CSS
#tooltip
{
border: 1px solid #BFBFBF;
float: left;
font-size: 11px;
max-width: 250px;
padding: 5px;
position: absolute;
color: #464646;
z-index: 999999;
}
.rounded
{
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}

Hook jQuery validation message changes

I want to display my jQuery validation messages in a tooltip. In order to accomplish this, I started out by adding the following CSS rules to my stylesheet:
fieldset .field-validation-error {
display: none;
}
fieldset .field-validation-error.tooltip-icon {
background-image: url('/content/images/icons.png');
background-position: -32px -192px;
width: 16px;
height: 16px;
display: inline-block;
}
and a very small piece of JS code:
; (function ($) {
$(function() {
var fields = $("fieldset .field-validation-valid, fieldset .field-validation-error");
fields.each(function() {
var self = $(this);
self.addClass("tooltip-icon");
self.attr("rel", "tooltip");
self.attr("title", self.text());
self.text("");
self.tooltip();
});
});
})(jQuery);
The issue is that I now need to capture any event when the validation message changes, I've been looking at the source for jquery.validate.unobtrusive.js, and the method I'd need to hook to is the function onError(error, inputElement) method.
My tooltip plugin works as long as I've an updated title attribute, the issue comes when the field is revalidated, and the validation message is regenerated, I would need to hook into that and prevent the message from being put out there and place it in the title attribute instead.
I want to figure out a way to do this without modifying the actual jquery.validate.unobtrusive.js file.
On a second note, how could I improve this in order to leave the functionality unaltered in case javascript is disabled?
Ok I went with this, just in case anyone runs into this again:
; (function ($) {
$(function() {
function convertValidationMessagesToTooltips(form) {
var fields = $("fieldset .field-validation-valid, fieldset .field-validation-error", form);
fields.each(function() {
var self = $(this);
self.addClass("tooltip-icon");
self.attr("rel", "tooltip");
self.attr("title", self.text());
var span = self.find("span");
if (span.length) {
span.text("");
} else {
self.text("");
}
self.tooltip();
});
}
$("form").each(function() {
var form = $(this);
var settings = form.data("validator").settings;
var old_error_placement = settings.errorPlacement;
var new_error_placement = function() {
old_error_placement.apply(settings, arguments);
convertValidationMessagesToTooltips(form);
};
settings.errorPlacement = new_error_placement;
convertValidationMessagesToTooltips(form); // initialize in case of model-drawn validation messages at page render time.
});
});
})(jQuery);
and styles:
fieldset .field-validation-error { /* noscript */
display: block;
margin-bottom: 20px;
}
fieldset .field-validation-error.tooltip-icon { /* javascript enabled */
display: inline-block;
margin-bottom: 0px;
background-image: url('/content/images/icons.png');
background-position: -32px -192px;
width: 16px;
height: 16px;
vertical-align: middle;
}
I'll just include the tooltip script I have, since it's kind of custom-made (though I based it off someone else's).
; (function ($, window) {
$.fn.tooltip = function (){
var classes = {
tooltip: "tooltip",
top: "tooltip-top",
left: "tooltip-left",
right: "tooltip-right"
};
function init(self, tooltip) {
if ($(window).width() < tooltip.outerWidth() * 1.5) {
tooltip.css("max-width", $(window).width() / 2);
} else {
tooltip.css("max-width", 340);
}
var pos = {
x: self.offset().left + (self.outerWidth() / 2) - (tooltip.outerWidth() / 2),
y: self.offset().top - tooltip.outerHeight() - 20
};
if (pos.x < 0) {
pos.x = self.offset().left + self.outerWidth() / 2 - 20;
tooltip.addClass(classes.left);
} else {
tooltip.removeClass(classes.left);
}
if (pos.x + tooltip.outerWidth() > $(window).width()) {
pos.x = self.offset().left - tooltip.outerWidth() + self.outerWidth() / 2 + 20;
tooltip.addClass(classes.right);
} else {
tooltip.removeClass(classes.right);
}
if (pos.y < 0) {
pos.y = self.offset().top + self.outerHeight();
tooltip.addClass(classes.top);
} else {
tooltip.removeClass(classes.top);
}
tooltip.css({
left: pos.x,
top: pos.y
}).animate({
top: "+=10",
opacity: 1
}, 50);
};
function activate() {
var self = $(this);
var message = self.attr("title");
var tooltip = $("<div class='{0}'></div>".format(classes.tooltip));
if (!message) {
return;
}
self.removeAttr("title");
tooltip.css("opacity", 0).html(message).appendTo("body");
var reload = function() { // respec tooltip's size and position.
init(self, tooltip);
};
reload();
$(window).resize(reload);
var remove = function () {
tooltip.animate({
top: "-=10",
opacity: 0
}, 50, function() {
$(this).remove();
});
self.attr("title", message);
};
self.bind("mouseleave", remove);
tooltip.bind("click", remove);
};
return this.each(function () {
var self = $(this);
self.bind("mouseenter", activate);
});
};
$.tooltip = function() {
return $("[rel~=tooltip]").tooltip();
};
})(jQuery, window);

Categories