Isotope JS Range Slider with grid-item attr min-max - wordpress

I'm using the Isotope.js jquery library. I can't modify it to my needs, I need help.
https://codepen.io/lifeonlars/pen/yaaOey
// External js: jquery, isotope.pkgd.js, bootstrap.min.js, bootstrap-slider.js
$(document).ready( function() {
// Create object to store filter for each group
var buttonFilters = {};
var buttonFilter = '*';
// Create new object for the range filters and set default values,
// The default values should correspond to the default values from the slider
var rangeFilters = {
'height': {'min':150, 'max': 185},
'weight': {'min':50, 'max': 90}
};
// Initialise Isotope
// Set the item selector
var $grid = $('.grid').isotope({
itemSelector: '.item',
layout: 'masonry',
// use filter function
filter: function() {
var $this = $(this);
var height = $this.attr('data-height');
var weight = $this.attr('data-weight');
var isInHeightRange = (rangeFilters['height'].min <= height && rangeFilters['height'].max >= height);
var isInWeightRange = (rangeFilters['weight'].min <= weight && rangeFilters['weight'].max >= weight);
//console.log(rangeFilters['height']);
//console.log(rangeFilters['weight']);
// Debug to check whether an item is within the height weight range
//console.log('isInHeightRange:' +isInHeightRange + '\nisInWeightRange: ' + isInWeightRange );
return $this.is( buttonFilter ) && (isInHeightRange && isInWeightRange);
}
});
// Initialise Sliders
// Set min/max range on sliders as well as default values
var $heightSlider = $('#filter-height').slider({ tooltip_split: true, min: 130, max: 220, range: true, value: [150, 180] });
var $weightSlider = $('#filter-weight').slider({ tooltip_split: true, min: 40, max: 150, range: true, value: [50, 90] });
function updateRangeSlider(slider, slideEvt) {
console.log('Current slider:' + slider);
var sldmin = +slideEvt.value[0],
sldmax = +slideEvt.value[1],
// Find which filter group this slider is in (in this case it will be either height or weight)
// This can be changed by modifying the data-filter-group="age" attribute on the slider HTML
filterGroup = slider.attr('data-filter-group'),
// Set current selection in variable that can be pass to the label
currentSelection = sldmin + ' - ' + sldmax;
// Update filter label with new range selection
slider.siblings('.filter-label').find('.filter-selection').text(currentSelection);
// Set min and max values for current selection to current selection
// If no values are found set min to 0 and max to 100000
// Store min/max values in rangeFilters array in the relevant filter group
// E.g. rangeFilters['height'].min and rangeFilters['height'].max
console.log('Filtergroup: '+ filterGroup);
rangeFilters[filterGroup] = {
min: sldmin || 0,
max: sldmax || 100000
};
// Trigger isotope again to refresh layout
$grid.isotope();
}
// Trigger Isotope Filter when slider drag has stopped
$heightSlider.on('slideStop', function(slideEvt){
var $this =$(this);
updateRangeSlider($this, slideEvt);
});
$weightSlider.on('slideStop', function(slideEvt){
var $this =$(this);
updateRangeSlider($this, slideEvt);
});
// Look inside element with .filters class for any clicks on elements with .btn
$('.filters').on( 'click', '.btn', function() {
var $this = $(this);
// Get group key from parent btn-group (e.g. data-filter-group="color")
var $buttonGroup = $this.parents('.btn-group');
var filterGroup = $buttonGroup.attr('data-filter-group');
// set filter for group
buttonFilters[ filterGroup ] = $this.attr('data-filter');
// Combine filters or set the value to * if buttonFilters
buttonFilter = concatValues( buttonFilters ) || '*';
// Log out current filter to check that it's working when clicked
console.log( buttonFilter )
// Trigger isotope again to refresh layout
$grid.isotope();
});
// change is-checked class on btn-filter to toggle which one is active
$('.btn-group').each( function( i, buttonGroup ) {
var $buttonGroup = $( buttonGroup );
$buttonGroup.on( 'click', '.btn-filter', function() {
$buttonGroup.find('.is-checked').removeClass('is-checked');
$(this).addClass('is-checked');
});
});
});
// Flatten object by concatting values
function concatValues( obj ) {
var value = '';
for ( var prop in obj ) {
value += obj[ prop ];
}
return value;
}
Here is good code written for filtering with Bootstrap Slider.
Let's take for example attr data-weight="" .
There is only one value specified here. And range filters by it. I need to write min-data-weight or max-data-weight to each item and then filter between those values.
Or maybe there is another library that can do this?
This is needed for Wordpress Woocommece price.
I tried to add conditions that would count between min and max. But it did not work. That's why I'm writing here.

Related

In between the calendar div and the external events fullcalendar

Can anybody tell me which fullcalendar event callback should I use
To handle the case where : the current external event stopped dragging
and it's localised not over the external events Box neither over The Calendar?
knowing that I have both methods isEventOverDiv
//return true/false if we are (not) over the external events and isEventOverDivCal//return true/false if we are (not) over the calendar
I tried in eventDragStop: function (event, jsEvent, ui, view)
eventDragStop: function (event, jsEvent, ui, view){
// if the event is not over the external events box and neither over the calendar
if(!isEventOverDiv(jsEvent.clientX, jsEvent.clientY) && !isEventOverDivCal(jsEvent.clientX, jsEvent.clientY) ) {
// reset
var reccupuredIndexForTitle=$(this).attr('id');
$scope.ctrlendDragging(reccupuredIndexForTitle);
}
}
//return true if we are over the external events
var isEventOverDiv = function (x, y) {
var external_events = $('#external-events');
var offset = external_events.offset();
offset.right = external_events.width() + offset.left;
offset.bottom = external_events.height() + offset.top;
// Compare
if (x >= offset.left &&
y >= offset.top &&
x <= offset.right &&
y <= offset.bottom) {return true;}
return false;
};
//return true if we are over the calendar
var isEventOverDivCal = function(x, y) {
var external_events = $( '#calendar' );
var offset = external_events.offset();
offset.right = external_events.width() + offset.left;
offset.bottom = external_events.height() + offset.top;
// Compare
if (x >= offset.left
&& y >= offset.top
&& x <= offset.right
&& y <= offset .bottom) { return true;}
return false;
}
but it's not working.
update 2
in order to overcome the obstacle of putting events above the virtual scroll bar during their trip into the calendar
1- I apply from mycontroller $scope.ctrlstartDragging
(which triggers from the HTML view on ondragstart="angular.element(this).scope().ctrlstartDragging(id)" callback).
$scope.ctrlstartDragging = function(id) {
var book = document.getElementById(id);
var domRect = absolutePosition(book);
book.style.position = 'fixed';
book.style.top = domRect.top + 'px';
book.style.left = domRect.left + 'px';
book.style.width=(domRect.width) + 'px';
};
and to be able to unset css styles (position top left width)
(N.B: ondragend="angular.element(this).scope().ctrlendDragging(id)" callback)
is not fired and I don't know why but it's not a problematic in my case)
so the purpose is that I should call manually
$scope.ctrlendDragging = function(id) {
var book = document.getElementById(id);
book.style.position = 'relative';
book.style.top = 'unset';
book.style.left = 'unset';
book.style.width='unset';
};
and to do as I said, in case the user aborts to put the event on the calendar during the dragging and this event is positioned both outside the external event box and outside the calendar.
but in my case see MyPlunk I need when the revert is applied , I will need during the revert of this event to the external event box to do make a call to it with the folloiwing
// reset
var reccupuredIndexForTitle=$(this).attr('id');
$scope.ctrlendDragging(reccupuredIndexForTitle);
so I need when the revert is applied I sould apply those two lines
because if they are not applied when an user abort a dragging into the calendar
the event revert but without the unset css styles applied to .style.position top left width
I would get an one isolated external event on the top of the external events box like shown below:
Many thanks.
added the following to my controller:
var x =-1;
var y=-1;
$('#external-events').on('dragstop', function(evt)
{
isDragging = false;
x = evt.originalEvent.pageX;
y = evt.originalEvent.pageY;
if(!isDragging && x !=-1 && y!=-1 && !isEventOverDiv(x, y) && !isEventOverDivCal(x, y) )
{
// reset
var reccupuredIndexForTitle=$('.fc-event').attr('id');
$scope.ctrlendDragging(reccupuredIndexForTitle);
}
});
As you can see from the code, I used jquery on('dragstop') because I don't know why
on ondragend event is not fired
so I removed it from my view HTML ondragend="angular.element(this).scope().ctrlendDragging(id)"
and called manualy from my controller $scope.ctrlendDragging(id) to reset the current dragged event when stoped via $('#external-events').on('dragstop', function(evt)
and handled the case the current external event stopped dragging and it's localised not over the external events Box neither over The Calendar
via
if(!isDragging && x !=-1 && y!=-1 && !isEventOverDiv(x, y) && !isEventOverDivCal(x, y) )
Working codePen
update2:
Because The first solution is basic and work in hazardous conditions
It works only for the first draggable li
and it's not exact nor precise. I made an update to the following:
var domRect;
var isDragging = false;
var x =-1;
var y=-1;
$scope.positionX =-1;
$scope.positionY=-1;
var myId=-1;
$(document).ready(function() {
$scope.ctrlstartDragging = function(id) {
myId = id;
};
$scope.ctrlendDragging = function(id) {
book.style.zIndex = "9999";
};
$('#external-events').on('dragstop', function(evt)
{
$scope.$watchGroup(['positionX','positionY'],function () {
x = $scope.positionX;
y = $scope.positionY;
});
if(!isDragging && x !=-1 && y!=-1 && !isEventOverDiv(x, y) && !isEventOverDivCal(x, y) ) {
// reset
var reccupuredIndexForTitle=myId;
$scope.ctrlendDragging(reccupuredIndexForTitle);
}
});
});//end of $(document).ready(function()
Enclosed in $(document).ready(function() {
all functions that shoul be appplied in document ready:
1- $scope.ctrlstartDragging from which we get myId equal (current li) id passed
via html view ondragstart="angular.element(this).scope().ctrlstartDragging(id)"
2- $scope.ctrlendDragging n.b : I set the z-index via
book.style.zIndex = "9999"; so we could work in a modal context
3- $('#external-events').on('dragstop', function(evt) that should work in
$(document).ready(function() { or $window.load
where added a watchgroup on changes made on positionX positionY of an li
$scope.$watchGroup(['positionX','positionY'],function () {
x = $scope.positionX;
y = $scope.positionY;
});
also added
if(!isDragging && x !=-1 && y!=-1 && !isEventOverDiv(x, y) && !isEventOverDivCal(x, y) ) {
// reset
var reccupuredIndexForTitle=myId;
$scope.ctrlendDragging(reccupuredIndexForTitle);
}
which work with myId this time gotten from $scope.ctrlstartDragging
On the other Hand, I added when initialising external events
$('#external-events .fc-event').each(function() {
drag: function(){ to get the exact positionX positionY for the current dragged li element
$(this).draggable({
zIndex: 999,
revert: true, // will cause the event to go back to its
revertDuration: 0, // original position after the drag
drag: function(){
var offset = $(this).offset();
var xPos = offset.left;
var yPos = offset.top;
$scope.$apply(function() {
$scope.positionX =xPos;
$scope.positionY = yPos;
});
}
});
Working codePen for li
Hope it may help somebody ;).

Visjs timeline edit content item template

I'd like to be able to edit the content attribute of visjs timeline items in the timeline itself. However, when I use an input as part of a template, it doesn't appear to receive any mouse events; I can't click in it and type anything, and clicking buttons doesn't work, either. Buttons appear to get the mouseover event, though:
function test(item) {
alert('clicked');
}
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
min: new Date('2015-01-01'),
max: new Date('2015-12-31'),
zoomMin: 1000 * 4 * 60 * 24 * 7,
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div onClick="test"><input value="click in the middle"></input><button onClick="test">test</button></div>'
}
};
/* create timeline */
timeline.on('click', function (properties) {
var target = properties.event.target;
if(properties.item) properties.event.target.focus();
});
https://codepen.io/barticula/pen/EpWJKd
Edit: Code above CodePen example have been updated to use the click event to focus on the input, but all other normal mouse behavior is missing. Keyboard events appear to function normally.
To get a reaction with a click on a timeline element, you can use the library's own events (see events on doc and this exemple on website).
On your example, you could do something like this among other possible solutions in pure javascript including...
// Configuration for the Timeline
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
min: new Date('2015-01-01'),
max: new Date('2015-12-31'),
zoomMin: 1000 * 4 * 60 * 24 * 7,
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div id="test-div"><input placeholder="hey" type="text" id="inputTest" ><button id="test-button">test</button></div>'
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, null, options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', function (properties) {
var target = properties.event.target;
if(properties.item) alert('click on' + target.id);
});
UPDATED
It is difficult to know exactly what you want to do because there are several possible solutions anyway.
Eventually, I propose another snippet below and a codepen updated.... but will it meet your need, not sure ?
2nd UPDATE (for another work track, see comments)
// Configuration for the Timeline
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div><input placeholder="edit me..." type="text"></input><button>send value</button></div>'
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, null, options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', function(properties) {
var target = properties.event.target;
var item = items.get(properties.item);
console.log(properties.event);
// if (properties.item && target.tagName === "DIV") focusMethod(target);
if (properties.item && target.tagName === "INPUT") target.focus();
if (properties.item && target.tagName === "BUTTON") getInputValue(item, target);
});
focusMethod = function getFocus(target) {
// target.insertAfter("BUTTON");
target.firstChild.focus();
}
getInputValue = function getValue(item, target) {
target.focus();
var inputValue = (target.parentNode.firstChild.value) ? target.parentNode.firstChild.value : "no value entered ";
alert("Input value : " + inputValue + " => send by: " + item.content)
}

FullCalendar RefetchEvents very slow

I call calendar.refetchEvents(); inside an autorun Block to ensure the reactivity of the Scheduler (i'm using resource view) , when tested with big datasets , although i make sure to subscript to only a portion of 2 weeks worth of events , it's extremly slow .
my events arent Json based i'm using meteor and i loop over the events inside the Events function of the calendar .
are there some good fullcalendar practices i'm missing ?
calendar = $('#calendar').fullCalendar({
now: new Date(),
editable: true, // enable draggable events
droppable: true, // this allows things to be dropped onto the calendar
aspectRatio: 1.8,
disableDragging: true,
displayEventTime: false,
selectable:true,
allDaySlot:true,
slotDuration:'24:00',
lazyFetching:true,
scrollTime: '00:00', // undo default 6am scrollTime
header: {
left: 'today prev,next',
center: 'title',
right: 'timelineThreeDays'
},
defaultView: 'timelineThreeDays',
views: {
timelineThreeDays: {
type: 'timeline',
duration: { days: 14 }
}
},
eventAfterAllRender: function(){
Session.set("loading",false);
},
resourceLabelText: 'Employees',
eventRender: function (event, element) {
var originalClass = element[0].className;
if (event.id && event.id.indexOf("temp")>-1){
element[0].className = originalClass + ' dragEvent';
}
else if (event.id && event.id.indexOf("oloc")>-1){
element[0].className = originalClass + ' oloc';
}
else{
element[0].className = originalClass + ' hasmenu';
}
$(element[0]).attr('shift-id', event._id);
element.find('.fc-title').html((event.title?event.title+"<br/>":"")+(event.locationName?event.locationName+"<br/>":"")+moment(event.start).format("HH:mm")+ " "+moment(event.end).format("HH:mm"));
element.bind('mousedown', function (e) {
if (e.which == 3) {
Session.set("selectedShift",event._id);
}
});
},eventAfterRender: function(event, element, view) {
},
resourceRender: function(resourceObj, labelTds, bodyTds) {
var originalClass = labelTds[0].className;
var uid=resourceObj.id;
var resource = Meteor.users.findOne({_id:uid});
if(resource){
var img = Images.findOne({_id: resource.picture});
var imgUrl = img ? img.url() : "/images/default-avatar.png";
var styleString = "<img class='img-profil small' src='"+imgUrl+"'>" + resource.profile.firstname + " " + resource.profile.lastname+" <small style='font-size:0.6em;color:#D24D57;'>"+resource.profile.registeredTelephony+"</small>";
labelTds.find('.fc-cell-text').html("");
labelTds.find('.fc-cell-text').prepend(styleString);
labelTds[0].className = originalClass + ' hasResourceMenu';
}else{
var imgUrl = "/images/default-avatar.png";
var styleString = "<img class='img-profil small' src='"+imgUrl+"'>" + "Unassigned" + " " +" <small style='font-size:0.6em;color:#D24D57;'>"+"</small>";
labelTds.find('.fc-cell-text').html("");
labelTds.find('.fc-cell-text').prepend(styleString);
}
},
resources: function(callback) {
var users = [];
var data = Meteor.users.find({
$or:[
{"profile.showInScheduler":{$exists:false}},
{"profile.showInScheduler":true}
],
assignedTo:{$in:[Session.get("locationId")]},
'locations._id':Session.get("locationId"),
"profile.companyId":Session.get("companyId")
});
var arr = data.map(function(c) {
var employeeType = c.userSettings.employeeType;
var type = EmployeeType.findOne({_id:employeeType});
var img = Images.findOne({_id: c.picture});
var imgUrl = img ? img.url() : "/images/default-avatar.png";
c.name = c.name || "";
var totalHoursAllLocation = 0;
var totalHoursCurrentLocation = 0;
return {
id: c._id,
title: "t"
};
});
arr.push({id:"temp"+Session.get("companyId")+Session.get("locationId"),title:"<div class='img-profil small' style='background: url(/images/default-avatar.png);'></div> UnAssigned"});
callback(arr);
},
events: function(start, end, timezone, callback) {
},
drop: function(date, jsEvent, ui, resourceId) {
// retrieve the dropped element's stored Event Object
var locationId=Session.get("locationId");
var originalEventObject = $(this).data('eventObject');
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
//copiedEventObject.allDay = allDay;
shift = ShiftTypes.findOne({_id:copiedEventObject.id});
if(shift){
startDate = moment(date);
hour = shift.dayDuration.Start.split(":");
startDate.hours(hour[0]).minutes(hour[1]);
endDate = moment(date);
hour = shift.dayDuration.End.split(":");
endDate.hours(hour[0]).minutes(hour[1]);
if(moment(startDate).isAfter(endDate)){
endDate=endDate.add("1","days");
}
var data = {
shiftId:shift._id,
name:shift.name,
uid:resourceId,
locationId:Session.get("locationId"),
companyId:Session.get("companyId"),
day:date,start:startDate.utc().toDate(),
end:endDate.utc().toDate(),
type:"active"
};
if (SchedulesBeforeInsert(data,"dropActive")){
Schedules.insert(data,function (err,result) {});
}
}
},
eventResize: function( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ) {
endDate = moment.utc(new Date(event.start));
schedule = Schedules.findOne({_id:event.id});
var delta = dayDelta._days;
for(i=1;i<delta+1;i++){
Schedules.insert({start:moment.utc(event.start).add(i,"days").toDate(),end:moment.utc(schedule.end).add(i,"days").toDate(),uid:schedule.uid,locationId:Session.get("locationId"),companyId:schedule.companyId,name:schedule.name,shiftId:schedule.shiftId,attendanceCode:schedule.attendanceCode,type:schedule.type});
}
},
dayClick: function(date, jsEvent, view,res,res2) {
Session.set("selectedDay",moment(date).toDate());
Session.set("selectedRessource",res.id);
},
eventClick: function ( event, jsEvent, view ) {
toastr.success(moment(event.start).format('HH:mm') +" TO "+moment(event.endDate).format('HH:mm'))
},
eventReceive: function(event) { // called when a proper external event is dropped
console.log('eventReceive', event);
}
}).data().fullCalendar;
/********************* reactive calendar *****************/
this.autorun(function() {
console.log("autoRun")
Meteor.subscribe("schedules",Session.get("companyId"),moment($('#calendar').fullCalendar('getDate')).startOf('week').toDate(),moment($('#calendar').fullCalendar('getDate')).add(4,"weeks").endOf('week').toDate());
var events = [];
var usersInLocation = Meteor.users.find({$or:[{"profile.showInScheduler":{$exists:false}},{"profile.showInScheduler":true}],assignedTo:{$in:[Session.get("locationId")]},'locations._id':Session.get("locationId"),"profile.companyId":Session.get("companyId")}).fetch();
var userIds = _.pluck(usersInLocation,"_id");
userIds.push("temp"+Session.get("companyId")+Session.get("locationId"));
var data;
if(Session.get("displayAllLocations")===true){
reqEvents = Schedules.find({uid:{$in:userIds},companyId:Session.get("companyId"),type:{$in:["active","activeAttendance"]},start:{$gte:moment(Session.get("currentDate")).startOf('week').toDate()},end:{$lte:moment(Session.get("currentDate")).add(1,"week").endOf('week').toDate()}});
}else{
reqEvents = Schedules.find({uid:{$in:userIds},locationId:Session.get("locationId"),companyId:Session.get("companyId"),type:{$in:["active","activeAttendance"]},start:{$gte:moment(Session.get("currentDate")).startOf('week').toDate()},end:{$lte:moment(Session.get("currentDate")).add(1,"week").endOf('week').toDate()}});
}
reqEvents.forEach(function(evt){
var event = null;
color="";
if(evt.attendanceCode){
attendance =AttendanceCodes.findOne({_id:evt.attendanceCode});
color=attendance.color;
}
attendance = null;
color="";
id="";
locationName="";
if(evt.attendanceCode){
attendance =AttendanceCodes.findOne({_id:evt.attendanceCode})
if(attendance){
color=attendance.color;
}
}
else if(evt.employeeAttendanceCode){
attendance =AttendanceCodesPortal.findOne({_id:evt.employeeAttendanceCode})
if(attendance){
color=attendance.color;
}
}
id=evt._id;
if(evt.locationId!==Session.get("locationId")){
color="#EEEEEE";
id="oloc";
location =Locations.findOne({_id:evt.locationId});
if(location){
locationName=location.name;
}
}
if(evt.name != null){
event = {id:id,title:evt.name,start:evt.start,end:evt.end,color:color,resourceId:evt.uid,locationName:locationName};
}else{
event = {id:id,title:" ",start:evt.start,end:evt.end,color:color,resourceId:evt.uid,locationName:locationName};
}
events.push(event);
});
allUsersCursor =Meteor.users.find({
$or:[
{"profile.showInScheduler":{$exists:false}},
{"profile.showInScheduler":true}
],
assignedTo:{$in:[Session.get("locationId")]},
'locations._id':Session.get("locationId"),
"profile.companyId":Session.get("companyId")
}).fetch();
if(calendar){
calendar.removeEvents();
calendar.addEventSource(events);
calendar.refetchResources();
//SetUp the actions context menu
setUpContextMenu();
// Initialize the external events
$('#external-events div.external-event').each(function() {
var eventObject = {
title: $.trim($(this).text()), // use the element's text as the event title
id:$(this).attr("id")
};
// 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({
helper: 'clone',
revert: 'invalid',
appendTo: 'body'// original position after the drag
});
});
}
});
I can see a number of non-Meteoric patterns in there that you should optimize out:
Your this.autorun will rerun anytime anything changes in your Schedules collection as well as for various user object changes (not just to the logged-in users but other users as well). Your autorun rebuilds your client-side data from scratch every time, including numerous joins. You could use a local collection to cache the joined data and then only add/change those documents when the underlying persisted document changes. This can be done with an observeChanges pattern. Or you could denormalize your data model a bit to avoid all those joins.
You're modifying the DOM by attaching events to a number of selectors using jQuery instead of using Meteor event handlers. This is also happening in your autorun which means you're attaching the same events to the same DOM objects repeatedly.
You use many Session variables and you use them repeatedly. These can be slow since they use browser local storage. You should .get() such data into a local variable once only and then refer to the local variable from then on.
Make sure that you only include fields in your Schedules publication that your code actually refers to on the client. The rest are overhead.

Filtering events with multiple filters (Checkboxes)

I am using the jQuery's full calendar with my sharepoint calendar list. I want to filter the events with multiple checkbox selection. I want to filter the events based on the Rooms Selected in the checkboxes.
For this I am using the following script.
Script :
<link rel="stylesheet" type="text/css" href="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/fullcalendar.css" />
<link rel="stylesheet" type="text/css" href="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/jquery-ui-1.8.16.custom.css" />
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/jquery-2.1.4.js"></script>
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/jquery-ui-1.8.16.custom.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/0.7.1a/jquery.SPServices-0.7.1a.min.js"></script>
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/moment.min.js"></script>
<script type="text/javascript" src="http://bisp2013-04:1000/sites/Exercise2/Style%20Library/FullCalendar/fullcalendar.js"></script>
<script type=text/javascript">
</script>
<script type="text/javascript">
// Format UTC dates as local date/time strings.
var rooms = [];
var selectedRooms = [];
var events = [];
var filteredSource = [];
var RoomSelection = [];
var date = new Date();
function closeDialog(result) {
SP.UI.ModalDialog.RefreshPage(result);
}
function formatDateToLocal( date ) {
var dateUTC;
if ( typeof date === "string" ) {
// Convert UTC string to date object
var d = new Date();
var year = date.split('-')[0];
var month = date.split('-')[1] - 1;
var day;
var hour;
var minute;
var second;
day = date.split('-')[2].split('T')[0];
hour = date.split('T')[1].split(':')[0];
minute = date.split('T')[1].split(':')[1].split(':')[0];
second = date.split('T')[1].split(':')[2].split('Z')[0];
dateUTC = new Date( Date.UTC( year, month, day, hour, minute, second ) );
}
else if ( typeof date === "object" ) {
dateUTC = date;
}
else {
alert( "Date is not a valid string or date object." );
}
// Create local date strings from UTC date object
var year = "" + dateUTC.getFullYear();
var month = "" + ( dateUTC.getMonth() + 1 ); // Add 1 to month because months are zero-indexed.
var day = "" + dateUTC.getDate();
var hour = "" + dateUTC.getHours();
var minute = "" + dateUTC.getMinutes();
var second = "" + dateUTC.getSeconds();
// Add leading zeros to single-digit months, days, hours, minutes, and seconds
if ( month.length < 2 ) {
month = "0" + month;
}
if ( day.length < 2 ) {
day = "0" + day;
}
if ( hour.length < 2 ) {
hour = "0" + hour;
}
if ( minute.length < 2 ) {
minute = "0" + minute;
}
if ( second.length < 2 ) {
second = "0" + second;
}
var localDateString = year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second;
return localDateString;
}
$(document).ready( function() {
$().SPServices({
operation: "GetListItems",
async: false,
listName: "Training Rooms",
completefunc: function (xData, Status) {
if(Status == 'success' )
{
$(xData.responseXML).SPFilterNode("z:row").each(function()
{
var value= $(this).attr("ows_ID");
var text = $(this).attr("ows_Title");
var roomColor = $(this).attr("ows_bifctpEventColor")
rooms.push({
title: text,
id: value,
Color: roomColor
});
$("#filterBox").append ( "<input id='chk_" + value + "' type='checkbox' value='" + value + "' style='background:"+roomColor+"' onchange='FilterCalendar(" + value + ",this)'/><label for='chk_" + value + "'>" + text + "</label>" );
});
}
}
});
$( '#calendar' ).fullCalendar({
// Assign buttons to the header of the calendar. See FullCalendar documentation for details.
header: {
left:'prev,next today',
center: 'title',
right: 'month, agendaWeek, agendaDay'
},
dayClick: function (date, jsEvent, view) {
var now = new Date();
if (date.setHours(0,0,0,0) < now.setHours(0,0,0,0))
{
alert('You Can Not add Event on this date');
}
else
{
var siteUrl = window.location.protocol + "//" + window.location.host + _spPageContextInfo.siteServerRelativeUrl;
var myLinksUrl = siteUrl + "/Lists/RoomReservation/NewForm.aspx";
var options = {
url: myLinksUrl,
title: "Room Reservation",
dialogReturnValueCallback: closeDialog
};
SP.SOD.execute('sp.ui.dialog.js', 'SP.UI.ModalDialog.showModalDialog', options);
}
},
defaultView: "month", // Set the default view to month
/*
when user select timeslot this option code will execute.
It has three arguments. Start,end and allDay.
Start means starting time of event.
End means ending time of event.
allDay means if events is for entire day or not.
*/
selectable: true,
selectHelper: true,
select: function(start, end, allDay)
{
if(start < date)
{
// Do whatever you want here.
alert('Cannot select this dates.');
return;
}
var title = prompt('Event Title:');
if (title) {
$( '#calendar' ).fullCalendar('renderEvent', {
title: title,
start: start,
end: end,
allDay: allDay
},
true // make the event "stick"
);
}
$( '#calendar' ).fullCalendar('unselect');
},
editable: true,
// Add events to the calendar.
events: function( start, end, callback ) {
// Create an array to hold the events.
events = [];
// Set the date from which to pull events based on the first visible day in the current calendar view. For a month view, this will usually be several days into the previous month. We can use FullCalendar's built-in getView method along with the formatDate utility function to create a date string in the format that SharePoint requires. It must be in the format YYYY-MM-DDTHH:MM:SSZ. Due to time zone differences, we will omit everything after the day.
var startDate = moment().format($('#calendar').fullCalendar('getView').intervalstart, "u").split("T")[0];
// Get the current view of the calendar (agendaWeek, agendaDay, month, etc.). Then set the camlView to the appropriate value to pass to the web service. This way we will only retrieve events needed by the current view (e.g. the agendaWeek view will only retrieve events during the current week rather than getting all events for the current month).
var calView = $( '#calendar' ).fullCalendar( 'getView' ).title;
var camlView = "";
switch( calView ) {
case "agendaWeek":
camlView = "<Week />";
break;
case "agendaDay":
camlView = "<Week />";
break;
default: // Default to month view
camlView = "<Month />";
}
// Set the camlFields, camlQuery, and camlOptions to the appropriate values to pass to the web service. You can add additional <ViewFields /> or adjust the CAML query if you have some custom columns that you want to filter by or display data from. The values below are the pretty much the minimum you'll want to start from to get it working.
var camlFields = "<ViewFields><FieldRef Name='Title' /><FieldRef Name='EventDate' /><FieldRef Name='EndDate' /><FieldRef Name='Location' /><FieldRef Name='Description' /><FieldRef Name='fRecurrence' /><FieldRef Name='RecurrenceData' /><FieldRef Name='RecurrenceID' /><FieldRef Name='fAllDayEvent' /></ViewFields>";
var camlQuery = "<Query><Where><And><Geq><FieldRef Name='EventDate' /><Value IncludeTimeValue='TRUE' Type='DateTime'>"+moment(start).toISOString()+"</Value></Geq><Leq><FieldRef Name='EndDate' /><Value IncludeTimeValue='TRUE' Type='DateTime'>"+moment(end).toISOString()+"</Value></Leq></And></Where></Query>";
var camlOptions = "<QueryOptions><CalendarDate>" + startDate + "</CalendarDate><RecurrencePatternXMLVersion>v3</RecurrencePatternXMLVersion><ExpandRecurrence>TRUE</ExpandRecurrence><DateInUtc>TRUE</DateInUtc></QueryOptions>";
// Make the web service call to retrieve events.
$().SPServices({
operation: "GetListItems",
async: false,
listName: "Room Reservation", // Change this to the GUID or display name of your calendar. If the calendar is on a different site, you can use the display name with the webURL option (see SPServices.CodePlex.com for more information).
CAMLViewFields: camlFields,
CAMLQuery: camlQuery,
CAMLQueryOptions: camlOptions,
completefunc: function( xData, Status ) {
$( xData.responseXML ).find( "z\\:row, row" ).each( function() {
// Check for all day events
var fADE = $( this ).attr( 'ows_fAllDayEvent' );
var thisADE = false;
var thisStart;
var thisEnd;
if ( typeof fADE !== "undefined" && fADE !== "0" ) {
thisADE = true;
// Get the start and end date/time of the event. FullCalendar will parse date strings in local time automagically, and we don't need to do any local time conversions for all day events, so we can use the UTC date strings from SharePoint without converting them to local time.
var thisStart = $( this ).attr( 'ows_EventDate' );
var thisEnd = $( this ).attr( 'ows_EndDate' );
}
else {
// Get the start and end date/time of the event. FullCalendar will parse date strings in local time automagically, so we need to convert the UTC date strings from SharePoint into local time. The formatDateToLocal() function above will take care of this. See comments in that function for more information.
var thisStart = formatDateToLocal( $( this ).attr( 'ows_EventDate' ) );
var thisEnd = formatDateToLocal( $( this ).attr( 'ows_EndDate' ) );
}
// Get the list item ID and recurrence date if present. This will be used to generate the ID query string parameter to link to the event (or the specific instance of a recurring event). The ID query string parameter must be in the format "ID.0.yyyy-MM-ddTHH:mm:ssZ" for recurring events (where "ID" is the list item ID for the event). Event ID's are returned as just a number (for non-recurring events) or several numbers separated by ";#" in 2007 or "." in 2010 to indicate individual instances of recurring events. By splitting and joining the ID this way, thisID will be set to a valid query string parameter whether an event is recurring or not for both versions of SharePoint.
var thisID = $( this ).attr( 'ows_ID' ).split( ';#' ).join( '.' );
var thisRoom = $(this).attr('ows_bifctpTrainingRoom').split(';#')[0];
// FullCalendar documentation specifies that recurring events should all have the same id value when building the events array (the id is optional, but I'm including it for completeness). We can get the list item ID (which is the same for all instances of recurring events) without the recurrence information by simply splitting thisID.
var eventID = thisID.split( '.' )[0];
// Get the event title. This is displayed on the calendar along with the start time of the event.
var thisTitle = $( this ).attr( 'ows_Title' );
// Get the event description. I don't use it in this example, but you could use it for something, perhaps as a tooltip when hovering over the event.
var thisDesc = $( this ).attr( 'ows_Description' );
// Add the event information to the events array so FullCalendar can display it.
events.push({
title: thisTitle,
id: eventID,
start: thisStart,
end: thisEnd,
allDay: thisADE,
room:thisRoom,
// Adjust this URL to link to the display form for your calendar events. You can include a Source parameter to allow users to easily return to the FullCalendar page.
url: _spPageContextInfo.siteAbsoluteUrl + '/Lists/RoomReservation/DispForm.aspx?ID=' + thisID + '&Source=' + window.location,
description: 'Check Calendar Events'
});
});
callback( events );
}
});
},
eventRender: function eventRender( event, element, view ) {
var isFound = false;
var backgroundColor = "";
for (var roomID in rooms) {
var currentRoom = rooms[roomID];
if (currentRoom.id == event.room) {
backgroundColor = currentRoom.Color;
element.find(".fc-event-time").html(event.start.format("HH:mm") + "-" + event.end.format("HH:mm"));
element.find(".fc-title").parent().attr("title", event.title);
element.css('color','black');
element.find('.fc-event-skin').css('background-color', backgroundColor);
element.css('background-color', backgroundColor);
isFound = true;
break;
}
}
return isFound;
}
});
})
</script>
<style type="text/css">
#calendar
{
width: 900px;
margin: 0 auto;
}
</style>
<div id="filterBox">
</div>
<div id="calendar">
<!-- Calendar will be added here -->
</div><!-- #calendar -->
This is how my screen looks at present:
What i want to do is on change event of the each of the checkbox. I want to filter the calendar events and show events only for the selected rooms.
You need to use event source, so you can split your events in different sources (rooms in your case).
Then, you can easily show / hide your events with add Event Source and remove Event Source.
So your code will be something like:
$('#calendar').fullCalendar({
// put your options and callbacks here
eventSources: [source1, source2]
})
And bound to your checkbox:
$('#calendar').fullCalendar( 'addEventSource', source1 ); //Show it
$('#calendar').fullCalendar( 'removeEventSource', source1 ); //Hide it
I did a plunker where you can reproduce it.

Changing Google Maps v3 MarkerClustererPlus Title

I am trying to dynamically set the cluster title, rollover text, of clustered icons. I want the cluster count/total to be used in the rollover text.
Through console.log I am able to see that the title has been changed to that set for var txt. It also works with alert( txt ). The default title for a cluster is "" and does not seem to be getting updated and stays at the default value.
Currently I am setting the title in google.maps.event.addListener( markerClusterer, 'mouseover', function( cluster ) {}).
I'm thinking that my code continues to execute and that might be the reason I don't see the change but I haven't been able to narrow it down.
var latlng = new google.maps.LatLng( lat, lng );
var qs = location.search;
var options = {
zoom: 17,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
}
};
map = new google.maps.Map( mapId[0], options );
google.maps.event.addListener( map, 'idle', function() {
var bounds = map.getBounds();
downloadXML( ABSPATH + 'xml/maps/markers.php' + qs + '&bounds=' + bounds, function( data ) {
var xml = parseXml( data );
var markers = xml.documentElement.getElementsByTagName( "marker" );
var markerArray = [];
for ( var i = 0; i < markers.length; i++ ) {
var attributes = getMarkerAttributes( markers[i] );
var marker = createMarker( attributes );
// Add marker to marker array
markerArray.push(marker);
}
// Define the marker clusterer
var clusterOptions = {
zoomOnClick: false,
gridSize: 1
}
var markerClusterer = new MarkerClusterer( map, markerArray, clusterOptions );
// Listen for a cluster to be clicked
google.maps.event.addListener( markerClusterer, 'clusterclick', function( cluster ) {
combineInfoWindows( cluster );
});
// Listen for a cluster to be hovered and set title
google.maps.event.addListener( markerClusterer, 'mouseover', function( cluster ) {
var txt = 'There are ' + cluster.getSize() + ' properties at this location.';
//alert( txt );
console.log( cluster );
markerClusterer.setTitle( txt );
});
}); // downloadXML
}); // google.maps.event.addListener( map, 'idle', ... )
Any help would be greatly appreciated. Thanks!
EDIT: 1
I have a solution based on the suggested solution by Rick.
I have modified the onAdd method.
/**
* Adds the icon to the DOM.
*/
ClusterIcon.prototype.onAdd = function () {
var cClusterIcon = this;
// MY CHANGES - START
this.cluster_.markerClusterer_.title_ = 'There are ' + this.cluster_.getSize() + ' properties at this location.';
// MY CHANGES - END
this.div_ = document.createElement("div");
if (this.visible_) {
this.show();
}
...
};
EDIT: 2 - FINAL SOLUTION
Moved changes to show method versus previous onAdd method as Rick had suggested. Change is made in a file outside of the original source file for MarkerClustererPlus.
/**
* Positions and shows the icon.
*/
ClusterIcon.prototype.show = function () {
if (this.div_) {
var pos = this.getPosFromLatLng_(this.center_);
this.div_.style.cssText = this.createCss(pos);
if (this.cluster_.printable_) {
// (Would like to use "width: inherit;" below, but doesn't work with MSIE)
this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_ + "px;'>" + this.sums_.text + "</div>";
} else {
this.div_.innerHTML = this.sums_.text;
}
//this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
// MY SOLUTION BELOW
this.div_.title = 'There are ' + this.cluster_.getSize() + ' properties at this location.';
this.div_.style.display = "";
}
this.visible_ = true;
};
Are you using this for clustering markers? Did you extend it to make your own setTitle function? If not, You'll have to make your own label. It's not actually a marker per se.
Edit: Didn't know this existed.
The cluster icons just pull the title from the MCOptions. I don't see where ClusterIcon or Cluster has a setTitle function, so I'd think the best bet would be overriding the ClusterIcon show prototype yourself and setting it there.
> ClusterIcon.prototype.show =
> function () { if (this.div_) {
> var pos = this.getPosFromLatLng_(this.center_);
> this.div_.style.cssText = this.createCss(pos);
> if (this.cluster_.printable_) {
> // (Would like to use "width: inherit;" below, but doesn't work with MSIE)
> this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_
> + "px;'>" + this.sums_.text + "</div>";
> } else {
> this.div_.innerHTML = this.sums_.text;
> }
> this.div_.title = **** Your stuff here ***
> this.div_.style.display = ""; } this.visible_ = true; };
Your problem is that you are trying to assign the mouseover event listener (where you set the title) to a MarkerClusterer, but to define a mouseover listener, you have to pass a Cluster.
There is a MarkerClusterer.getClusters() function that will return an Array of the Cluster instances. You want to loop over that Array and pass an instance of Cluster to your mouseover event listeners. If you check the reference doc and scroll down to the MarkerClusterer Events section of the doc, the row for mouseover defines the Argument to be:
c:Cluster
Which is in contrast to events like clusteringbegin and clusteringend, which define the Argument to be:
mc:MarkerClusterer
Having said all that, I'm not sure there is an easy way to set the title for each Cluster. That class does not have a setTitle function. The setTitle on the MarkerClusterer just applies the title to all of the Cluster instances. And I've double checked in JavaScript; there is no setTitle function on the Cluster class. Your best option right now seems to be to dynamically create the content you want to display within the mouseover handler for each Cluster. You could create an InfoBox and then close it on a Cluster mouseoet event. Not the simplest solution ever, but it will get you where you want to be.
I know this is an old question, but it ranked high on Google for my search. Anyway, here is what I did thanks to tips from this page:
google.maps.event.addListener(markerCluster, 'mouseover', function (c) {
if(c.clusterIcon_.div_){
c.clusterIcon_.div_.title = c.getSize() + ' businesses in this area';
}
});
I can't guarantee it will be compatible with future versions of MarkerClusterer Plus since I'm using "private" properties clusterIcon_ and div_.

Resources