I am using the Fivestar 2.2 module. Everything works fine, but voting on a touch screen doesn't: It is impossible to give 5 stars on a 5 star widget, even though it works perfectly on the desktop. Unfortunately I'm not allowed to provide a link.
Is there someone who already solved this? Drupal.org is not a help.
/**
* #file
*
* Fivestar JavaScript behaviors integration.
*/
/**
* Create a degradeable star rating interface out of a simple form structure.
*
* Originally based on the Star Rating jQuery plugin by Wil Stuckey:
* http://sandbox.wilstuckey.com/jquery-ratings/
*/
(function($){ // Create local scope.
Drupal.behaviors.fivestar = {
attach: function (context) {
$(context).find('div.fivestar-form-item').once('fivestar', function() {
var $this = $(this);
var $container = $('<div class="fivestar-widget clearfix"></div>');
var $select = $('select', $this);
// Setup the cancel button
var $cancel = $('option[value="0"]', $this);
if ($cancel.length) {
$('<div class="cancel">' + $cancel.text() + '</div>')
.appendTo($container);
}
// Setup the rating buttons
var $options = $('option', $this).not('[value="-"], [value="0"]');
var index = -1;
$options.each(function(i, element) {
var classes = 'star-' + (i+1);
classes += (i + 1) % 2 == 0 ? ' even' : ' odd';
classes += i == 0 ? ' star-first' : '';
classes += i + 1 == $options.length ? ' star-last' : '';
$('<div class="star">' + element.text + '</div>')
.addClass(classes)
.appendTo($container);
if (element.value == $select.val()) {
index = i + 1;
}
});
if (index != -1) {
$container.find('.star').slice(0, index).addClass('on');
}
$container.addClass('fivestar-widget-' + ($options.length));
$container.find('a')
.bind('click', $this, Drupal.behaviors.fivestar.rate)
.bind('mouseover', $this, Drupal.behaviors.fivestar.hover);
$container.bind('mouseover mouseout', $this, Drupal.behaviors.fivestar.hover);
// Attach the new widget and hide the existing widget.
$select.after($container).css('display', 'none');
// Allow other modules to modify the widget.
Drupal.attachBehaviors($this);
});
},
rate: function(event) {
var $this = $(this);
var $widget = event.data;
var value = this.hash.replace('#', '');
$('select', $widget).val(value).change();
var $this_star = (value == 0) ? $this.parent().parent().find('.star') :
$this.closest('.star');
$this_star.prevAll('.star').andSelf().addClass('on');
$this_star.nextAll('.star').removeClass('on');
if(value==0){
$this_star.removeClass('on');
}
event.preventDefault();
},
hover: function(event) {
var $this = $(this);
var $widget = event.data;
var $target = $(event.target);
var $stars = $('.star', $this);
if (event.type == 'mouseover') {
var index = $stars.index($target.parent());
$stars.each(function(i, element) {
if (i <= index) {
$(element).addClass('hover');
} else {
$(element).removeClass('hover');
}
});
} else {
$stars.removeClass('hover');
}
}
};
})(jQuery);
Sorry if it's not what you asked but I'll try to help. Don't use fivestar, I've been in the same situation. I suggest you to try the rate module (https://www.drupal.org/project/rate) which is in my opinion a more complete solution.
I have a responsive website which of course works with mobile devices and you can vote without problem, although I can't assure it was mobile ready.
Related
I'm trying to combine the Isotope multiple checkbox filtering with a searchbox.
I used the example with the checkbox filters from here and tried to implement the searchbox but with no luck.
Just the checkbox filtering works well. I think i'm close to the solution but my javascript skills are at a very beginner level.
I commented out the section of what i've tried to implement.
Thank you for some hints
// quick search regex
var qsRegex;
var $grid;
var filters = {};
var $grid = $('.grid');
//set initial options
$grid.isotope({
layoutMode: 'fitRows'
});
$(function() {
$grid = $('#grid');
$grid.isotope();
// do stuff when checkbox change
$('#options').on('change', function(jQEvent) {
var $checkbox = $(jQEvent.target);
manageCheckbox($checkbox);
var comboFilter = getComboFilter(filters);
/*var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
var filterResult = function() {
return comboFilter && searchResult;
}*/
$grid.isotope({
filter: comboFilter //or filterResult
});
});
});
function getComboFilter(filters) {
var i = 0;
var comboFilters = [];
var message = [];
for (var prop in filters) {
message.push(filters[prop].join(' '));
var filterGroup = filters[prop];
// skip to next filter group if it doesn't have any values
if (!filterGroup.length) {
continue;
}
if (i === 0) {
// copy to new array
comboFilters = filterGroup.slice(0);
} else {
var filterSelectors = [];
// copy to fresh array
var groupCombo = comboFilters.slice(0); // [ A, B ]
// merge filter Groups
for (var k = 0, len3 = filterGroup.length; k < len3; k++) {
for (var j = 0, len2 = groupCombo.length; j < len2; j++) {
filterSelectors.push(groupCombo[j] + filterGroup[k]); // [ 1, 2 ]
}
}
// apply filter selectors to combo filters for next group
comboFilters = filterSelectors;
}
i++;
}
var comboFilter = comboFilters.join(', ');
return comboFilter;
}
// use value of search field to filter
var $quicksearch = $('.quicksearch').keyup(debounce(function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, ));
// debounce so filtering doesn't happen every millisecond
function debounce(fn, threshold) {
var timeout;
threshold = threshold || 100;
return function debounced() {
clearTimeout(timeout);
var args = arguments;
var _this = this;
function delayed() {
fn.apply(_this, args);
}
timeout = setTimeout(delayed, threshold);
}
}
function manageCheckbox($checkbox) {
var checkbox = $checkbox[0];
var group = $checkbox.parents('.option-set').attr('data-group');
// create array for filter group, if not there yet
var filterGroup = filters[group];
if (!filterGroup) {
filterGroup = filters[group] = [];
}
var isAll = $checkbox.hasClass('all');
// reset filter group if the all box was checked
if (isAll) {
delete filters[group];
if (!checkbox.checked) {
checkbox.checked = 'checked';
}
}
// index of
var index = $.inArray(checkbox.value, filterGroup);
if (checkbox.checked) {
var selector = isAll ? 'input' : 'input.all';
$checkbox.siblings(selector).prop('checked', false);
if (!isAll && index === -1) {
// add filter to group
filters[group].push(checkbox.value);
}
} else if (!isAll) {
// remove filter from group
filters[group].splice(index, 1);
// if unchecked the last box, check the all
if (!$checkbox.siblings('[checked]').length) {
$checkbox.parents('.option-set').find(selector).prop('checked', false);
}
}
I found the solution by myself, but i had to add a second function for returning the searchresult. Otherwise the search function is triggered only after using a checkbox or leaving the search box input field.
How could i avoid this redundand code?
JS:
// use value of search field to filter
var $quicksearch = $('.quicksearch').keyup(debounce(function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, 200));
$(function() {
$grid = $('#grid');
$grid.isotope({
filter: function() {
var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
return searchResult;
}
});
// do stuff when checkbox change
$('#options').on('change', function(jQEvent) {
var $checkbox = $(jQEvent.target);
manageCheckbox($checkbox);
var comboFilter = getComboFilter(filters);
$grid.isotope({
filter: function() {
var buttonResult = comboFilter ? $(this).is(comboFilter) : true;
var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
return buttonResult && searchResult;
}
});
});
});
I have been using ngMap with my angularjs code for displaying markers. However, with more than 100 markers I have noticed that there is a considerable decrease in performance mainly related to ng-repeat and two way binding. I would like to add markers with custom HTML elements similar to CustomMarker but using ordinary Markers and modified from controller when required.
Challenges faced :
I have SVG images which need to be dynamically coloured based on the conditions (These SVGs are not single path ones, hence doesn't seem to work well when I used it with Symbol)
These are vehicle markers and hence should support rotation
I have solved this by creating CustomMarker with Overlay and then adding the markers that are only present in the current map bounds to the map so that map doesn't lag.
Below is the code snippet with which I achieved it.
createCustomMarkerComponent();
/**
* options : [Object] : options to be passed on
* - position : [Object] : Google maps latLng object
* - map : [Object] : Google maps instance
* - markerId : [String] : Marker id
* - innerHTML : [String] : innerHTML string for the marker
**/
function CustomMarker(options) {
var self = this;
self.options = options || {};
self.el = document.createElement('div');
self.el.style.display = 'block';
self.el.style.visibility = 'hidden';
self.visible = true;
self.display = false;
for (var key in options) {
self[key] = options[key];
}
self.setContent();
google.maps.event.addListener(self.options.map, "idle", function (event) {
//This is the current user-viewable region of the map
var bounds = self.options.map.getBounds();
checkElementVisibility(self, bounds);
});
if (this.options.onClick) {
google.maps.event.addDomListener(this.el, "click", this.options.onClick);
}
}
function checkElementVisibility(item, bounds) {
//checks if marker is within viewport and displays the marker accordingly - triggered by google.maps.event "idle" on the map Object
if (bounds.contains(item.position)) {
//If the item isn't already being displayed
if (item.display != true) {
item.display = true;
item.setMap(item.options.map);
}
} else {
item.display = false;
item.setMap(null);
}
}
var supportedTransform = (function getSupportedTransform() {
var prefixes = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' ');
var div = document.createElement('div');
for (var i = 0; i < prefixes.length; i++) {
if (div && div.style[prefixes[i]] !== undefined) {
return prefixes[i];
}
}
return false;
})();
function createCustomMarkerComponent() {
if (window.google) {
CustomMarker.prototype = new google.maps.OverlayView();
CustomMarker.prototype.setContent = function () {
this.el.innerHTML = this.innerHTML;
this.el.style.position = 'absolute';
this.el.style.cursor = 'pointer';
this.el.style.top = 0;
this.el.style.left = 0;
};
CustomMarker.prototype.getPosition = function () {
return this.position;
};
CustomMarker.prototype.getDraggable = function () {
return this.draggable;
};
CustomMarker.prototype.setDraggable = function (draggable) {
this.draggable = draggable;
};
CustomMarker.prototype.setPosition = function (position) {
var self = this;
return new Promise(function () {
position && (self.position = position); /* jshint ignore:line */
if (self.getProjection() && typeof self.position.lng == 'function') {
var setPosition = function () {
if (!self.getProjection()) {
return;
}
var posPixel = self.getProjection().fromLatLngToDivPixel(self.position);
var x = Math.round(posPixel.x - (self.el.offsetWidth / 2));
var y = Math.round(posPixel.y - self.el.offsetHeight + 10); // 10px for anchor; 18px for label if not position-absolute
if (supportedTransform) {
self.el.style[supportedTransform] = "translate(" + x + "px, " + y + "px)";
} else {
self.el.style.left = x + "px";
self.el.style.top = y + "px";
}
self.el.style.visibility = "visible";
};
if (self.el.offsetWidth && self.el.offsetHeight) {
setPosition();
} else {
//delayed left/top calculation when width/height are not set instantly
setTimeout(setPosition, 300);
}
}
});
};
CustomMarker.prototype.setZIndex = function (zIndex) {
if (zIndex === undefined) return;
(this.zIndex !== zIndex) && (this.zIndex = zIndex); /* jshint ignore:line */
(this.el.style.zIndex !== this.zIndex) && (this.el.style.zIndex = this.zIndex);
};
CustomMarker.prototype.getVisible = function () {
return this.visible;
};
CustomMarker.prototype.setVisible = function (visible) {
if (this.el.style.display === 'none' && visible) {
this.el.style.display = 'block';
} else if (this.el.style.display !== 'none' && !visible) {
this.el.style.display = 'none';
}
this.visible = visible;
};
CustomMarker.prototype.addClass = function (className) {
var classNames = this.el.className.trim().split(' ');
(classNames.indexOf(className) == -1) && classNames.push(className); /* jshint ignore:line */
this.el.className = classNames.join(' ');
};
CustomMarker.prototype.removeClass = function (className) {
var classNames = this.el.className.split(' ');
var index = classNames.indexOf(className);
(index > -1) && classNames.splice(index, 1); /* jshint ignore:line */
this.el.className = classNames.join(' ');
};
CustomMarker.prototype.onAdd = function () {
this.getPanes().overlayMouseTarget.appendChild(this.el);
// this.getPanes().markerLayer.appendChild(label-div); // ??
};
CustomMarker.prototype.draw = function () {
this.setPosition();
this.setZIndex(this.zIndex);
this.setVisible(this.visible);
};
CustomMarker.prototype.onRemove = function () {
this.el.parentNode.removeChild(this.el);
// this.el = null;
};
} else {
setTimeout(createCustomMarkerComponent, 200);
}
}
The checkElementVisibility function helps in identifying whether a marker should appear or not.
In case there are better solutions please add it here.Thanks!
What I'm trying to do:
Update the status to "TAKEN" when the chat is closed.
Issue:
Can't get $scope.currentChat.$set() or $scope.currentChat.$update() to work when trying to update the status. (See the $scope.close() function.)
What I've tried:
Various methods including $set, $update; I don't know. A lot of things. Been researching this for several hours, and can't find a solution that works.
NOTES:
$scope.currentChat.$set({status:"TAKEN"}); Doesn't work.
$scope.currentChat.$getRecord('status'); Works. Returns:
Object {$value: "OPEN", $id: "status", $priority: null}
So what exactly is going on here? Why can't I seem to set the status to TAKEN?
The issue is currently in the $scope.close() function, when trying to update the status:
// $SCOPE.CLOSE
// - Closes the current ticket.
$scope.close = function() {
// $scope.ticketObject.status = "TAKEN";
// $scope.currentChat.$set({status:"TAKEN"});
console.log("===========================");
console.log("STATUS:");
console.log($scope.currentChat.$getRecord('status'));
console.log($scope.currentChat['status']);
console.log("===========================");
$scope.ticketObject = {};
$scope.ticket = false;
$scope.toggle();
}
Here's my code:
bloop.controller('HomeCtrl', ['$scope', '$firebase', function($scope, $firebase) {
console.log("HomeController!");
var url = 'https://**********.firebaseio.com/tickets/';
var ref = new Firebase(url);
// $SCOPE.CREATETICKET
// - This function makes a connection to Firebase and creates the ticket.
$scope.createTicket = function() {
$scope.tickets = $firebase(ref).$asArray();
$scope.tickets.$add($scope.ticketObject).then(function(r) {
var id = r.name();
$scope.currentFBID = id;
$scope.syncTickets();
console.log("===========================");
console.log("CREATED TICKET: " + $scope.currentFBID);
console.log("URL: " + url + $scope.currentFBID);
console.log("===========================");
});
}
// $SCOPE.SYNCTICKETS
// - This function makes a connection to Firebase and syncs the ticket with the $scope to easily update the tickets.
$scope.syncTickets = function() {
var ticketRefURL = new Firebase(url + $scope.currentFBID);
$scope.currentChat = $firebase(ticketRefURL).$asArray();
$scope.currentChat.$save($scope.ticketObject);
var archiveRefURL = new Firebase(url + $scope.currentFBID + "/archive");
$scope.currentChat.archive = $firebase(archiveRefURL).$asArray();
console.log("===========================");
console.log("SAVED TICKET: " + $scope.currentFBID);
console.log("URL: " + ticketRefURL);
console.log("ARCHIVE URL: " + archiveRefURL);
console.log("===========================");
}
// $SCOPE.POST
// - This function pushes whatever is typed into the chat into the chat archive.
// - $scope.ticketObject.archive (is an array of objects)
$scope.post = function(name) {
// Push to ticketObject.archive array...
$scope.ticketObject.archive.push({
"name" : name,
"text" : $scope.chatText
});
// Logging the array to make sure it exists...
console.log("===========================");
console.log("CHAT ARCHIVE:");
console.log($scope.ticketObject.archive);
console.log("===========================");
$scope.currentChat.archive.$add({
"name" : name,
"text" : $scope.chatText
});
// This resets the text area so it's empty...
$scope.chatText = "";
} // WORKS
// $SCOPE.CLOSE
// - Closes the current ticket.
$scope.close = function() {
// $scope.ticketObject.status = "TAKEN";
// $scope.currentChat.$set({status:"TAKEN"});
console.log("===========================");
console.log("STATUS:");
console.log($scope.currentChat.$getRecord('status'));
console.log($scope.currentChat['status']);
console.log("===========================");
$scope.ticketObject = {};
$scope.ticket = false;
$scope.toggle();
}
// $SCOPE.TOGGLE
// - This function toggles the chat to be either open or closed.
$scope.toggle = function() {
if($scope.toggleState === false) {
$scope.toggleState = true;
$scope.checkTicket();
} else if($scope.toggleState === true) {
$scope.toggleState = false;
}
}
// $SCOPE.CHECKTICKET
// - This function checks to see if there's an existing ticket.
// - If there's not an existing ticket, it creates one.
$scope.checkTicket = function() {
if($scope.ticket === false) {
// Generate New Ticket Data
$scope.ticketObject = newTicket();
// Create the Ticket
$scope.createTicket();
// Ticket now exists.
$scope.ticket = true;
}
}
function newTicket() {
var ticketID = generateTicketID();
var newTicket = {
id: ticketID,
status: "OPEN",
name: "N/A",
email: "N/A",
date: generateDate(),
opID: "Unassigned",
opName: "Unassigned",
archive: [],
notes: []
}
return newTicket;
}
function generateTicketID() {
var chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ";
var result = '';
for(var i=12; i>0; --i) {
result += chars[Math.round(Math.random() * (chars.length - 1))];
}
return result;
}
function generateDate() {
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
if(dd < 10) {
dd = '0' + dd;
}
if(mm < 10) {
dd = '0' + mm;
}
var date = mm + "/" + dd + "/" + yyyy;
return date;
}
}]);
$update and $set are part of the $firebase API. You are attempting to call them on the synchronized array returned by $asArray(), which is a $FirebaseArray instance. That has its own API, which includes neither update nor set.
I have a Jquery for Password-correction and it works fine, if I use the 2 Lines
// $('#minimum span').show();
// $('#minimum').addClass('boldgreen');
But I´ve tryed to put that out in a seperat function, and the return is what I want, if I alert(ret[0]);
But it doesn´t change the css in the html code.
I´ve tryed many ways, but without any result.
Any suggestions?
Thanks in advance.
var cssPassword = function(cssId, cssClass, hideShow, removeAddClass)
{
var IDKlasse = "$('" + cssId + " span')." + hideShow +"();";
var cssKlasse = "$('" + cssId + "')." + removeAddClass + "('" + cssClass + "');";
var cssArray = new Array();
cssArray[0] = IDKlasse;
cssArray[1] = cssKlasse;
return cssArray;
}
function()
...
if(passwordNew.length > 7) {
var ret = cssPassword('#minimum', 'green', 'show', 'addClass');
//eval(ret[0]);
//eval(ret[1]);
strength += 1;
alert(ret[1]);
// $('#minimum span').show();
// $('#minimum').addClass('boldgreen');
}else{
...
Using ExtJs4.1 on Sencha Architect.
I have following code in my onDeleteButton code
onDeleteButtonClick: function(button, e, options) {
var active = this.activeRecord;
var myGrid = Ext.getCmp('publisherResultsGridView'),
sm = myGrid.getSelectionModel(),
selection = sm.getSelection(); // gives you a array of records(models)
if (selection.length > 0){
for( var i = 0; i < selection.length; i++) {
this.application.log('OnDeleteItemID is ' + selection);
}
this.remove(selection);
}
Code for Remove Function
remove: function(record) {
var store = Ext.getStore('PublisherProperties');
store.proxy.url = MasterDataManager.globals.url + "Publishers/";
store.remove(record);
store.sync();
When I run it, I could see an array of objects in my log, also I dont get any errors after the remove function is executed. But the store doesnt update, I mean it doesnt remove the selected items.
Can somebody please help me.
Thanks
I solved my problem by making the following changes.
To onDeleteButtonClick
if (selection.length > 0){
for( var i = 0; i < selection.length; i++) {
this.application.log('OnDeleteItemID is ' + selection[i].data.id);
this.remove(selection[i]);
}
}
To Remove function
remove: function(record) {
var store = Ext.getStore('PublisherProperties');
this.application.log('Remove Function is ' + record);
store.proxy.url = MasterDataManager.globals.url + "Publishers/" + record.data.id;
store.load({
scope : this,
callback : function(records, operation, success){
if (records.length > 0){
var store2 = Ext.getStore('PublisherProperties');
store2.proxy.url = MasterDataManager.globals.url + "Publishers/";
store2.remove(records[0]);
store2.sync();
}
}
});
//store.remove(record);
//store.sync();