Distinguishing click and double-click in D3 Version 4 - d3.js

I have seen http://bl.ocks.org/couchand/6394506 which applies to d3 Version 3, but I have discovered that d3.rebind is no longer supported in Version 4. Can someone please help as I need this functionality?

As the CHANGELOG states, rebind can be replaced by implementing a simple wrapper function.
cc.on = function() {
var value = event.on.apply(event, arguments);
return value === event ? cc : value;
};
return cc;
function clickcancel() {
var event = d3.dispatch('click', 'dblclick');
function cc(selection) {
var down,
tolerance = 5,
last,
wait = null;
// euclidean distance
function dist(a, b) {
return Math.sqrt(Math.pow(a[0] - b[0], 2), Math.pow(a[1] - b[1], 2));
}
selection.on('mousedown', function() {
down = d3.mouse(document.body);
last = +new Date();
});
selection.on('mouseup', function() {
if (dist(down, d3.mouse(document.body)) > tolerance) {
return;
} else {
if (wait) {
window.clearTimeout(wait);
wait = null;
event.call('dblclick', d3.event);
} else {
wait = window.setTimeout((function(e) {
return function() {
event.call('click', e);
wait = null;
};
})(d3.event), 300);
}
}
});
};
cc.on = function() {
var value = event.on.apply(event, arguments);
return value === event ? cc : value;
};
return cc;
}
var cc = clickcancel();
d3.select('#map').call(cc);
cc.on('click', function() {
d3.select('#map').text(d3.select('#map').text() + 'click, ');
});
cc.on('dblclick', function() {
d3.select('#map').text(d3.select('#map').text() + 'dblclick, ');
});
#map {
width: 960px;
height: 500px;
background: cyan;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id='map'></div>
This is simply the source from the original rebind method.

Related

Error while merging 'double click' code with old d3 'rebind' substitute

I try to revive double click event which was presented in previous versions of D3 for 'node.on' method. I need to use it with d3.forceSimulation.
The substitute snippet for double click event which I found works fine on old version of d3. It uses also the old d3.rebind method which is removed from the current d3 and which also has the substitute.
I tried to merge these two snippets but it failed with error 'Cannot read property 'apply' of undefined'.
Here's the merged code:
<div id='map'></div>
<script>
function clickcancel() {
// Copies a variable number of methods from source to target.
d3.rebind = function(target, source) {
var i = 1, n = arguments.length, method;
while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
return target;
};
// Method is assumed to be a standard D3 getter-setter:
// If passed with no arguments, gets the value.
// If passed with arguments, sets the value and returns the target.
function d3_rebind(target, source, method) {
return function() {
var value = method.apply(source, arguments);
return value === source ? target : value;
};
}
// we want to a distinguish single/double click
// details http://bl.ocks.org/couchand/6394506
var event = d3.dispatch('click', 'dblclick');
function cc(selection) {
var down, tolerance = 5, last, wait = null, args;
// euclidean distance
function dist(a, b) {
return Math.sqrt(Math.pow(a[0] - b[0], 2), Math.pow(a[1] - b[1], 2));
}
selection.on('mousedown', function() {
down = d3.mouse(document.body);
last = +new Date();
args = arguments;
});
selection.on('mouseup', function() {
if (dist(down, d3.mouse(document.body)) > tolerance) {
return;
} else {
if (wait) {
window.clearTimeout(wait);
wait = null;
event.dblclick.apply(this, args);
} else {
wait = window.setTimeout((function() {
return function() {
event.click.apply(this, args);
wait = null;
};
})(), 300);
}
}
});
};
return d3.rebind(cc, event, 'on');
}
var cc = clickcancel();
d3.select('#map').call(cc);
cc.on('click', function(d, index) {
d3.select('#map').text(d3.select('#map').text() + 'click, ');
});
cc.on('dblclick', function(d, index) {
d3.select('#map').text(d3.select('#map').text() + 'dblclick, ');
});
Simpler way to get double click functionality:
//Set variables
<script type="text/javascript">
var clickDate = new Date();
var difference_ms;
//Set dragstarted functionality fired on '.call(d3.drag().on("start", dragstarted)'
function dragstarted(d) {
difference_ms = (new Date()).getTime() - clickDate.getTime();
clickDate = new Date();
if(difference_ms > 200) {
d3.select(this).classed("fixed", d.fixed = true);
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
} else {
d3.select(this).classed("fixed", d.fixed = false);
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
};
}
//Set dragended functionality fired on '.call(d3.drag().on("end", dragended)'
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
if(difference_ms < 200) {
d.fx = null;
d.fy = null;
};
}
The way the tricky code that I stumbled upon could be revived is described here in comments of honorable nharrisanalyst. Though I still don't get why one should follow such a sophisticated path:
var cc = clickcancel();
d3.select('#map').call(cc); //#map is the element just for example
cc.on('click', function(d, index) {
d3.select('#map').text(d3.select('#map').text() + 'click, ');
});
cc.on('dblclick', function(d, index) {
d3.select('#map').text(d3.select('#map').text() + 'dblclick, ');
});
function clickcancel() {}

nightwatch assert ALL elements, not ANY

I'm trying to test that, when the Submit button is clicked on an empty form, all the "please fill in this field" labels are displayed.
I'm doing so with this:
page.click('#btn_submit');
page.expect.element('#validation_label_required').to.be.visible;
where #validation_label_required is represented by the CSS selector:
input[required] ~ p.error-message-required
However, this test passes if ANY of the validation labels are visible. The test should only pass if they ALL are.
How can I achieve this?
You will need to create a custom assertion for that where you locate all elements by selenium commands and then loop to verify condition. It should look something like this
var util = require('util');
exports.assertion = function (elementSelector, expectedValue, msg) {
this.message = msg || util.format('Testing if elements located by "%s" are visible', elementSelector);
this.expected = expectedValue;
this.pass = function (value) {
return value === this.expected;
};
this.value = function (result) {
return result;
};
this.command = function (callback) {
var that = this.api;
this.api.elements('css selector',elementSelector, function (elements) {
elements.value.forEach(function(element){
that.elementIdDisplayed(element.ELEMENT,function(result){
if(!result.value){
callback(false);
}
});
});
callback(true);
});
return this;
};
};
I've just ended up with another custom assertion that check how many elements are visible by given css selector.
/**
* Check how many elements are visible by given css selector.
*
*/
var util = require('util');
exports.assertion = function(elementSelector, expectedCount, msg) {
this.message = msg || util.format('Asserting %s elements located by css selector "%s" are visible', expectedCount, elementSelector);
this.expected = expectedCount;
this.count = 0;
this.pass = function(value) {
return value === this.expected;
};
this.value = function(result) {
return this.count;
};
this.command = function(callback) {
var me = this, elcount = 0;
this.count = 0;
this.api.elements('css selector', elementSelector, function(elements) {
if(elements.value && elements.value.length > 0){
elcount = elements.value.length;
}else{
return callback(false);
}
elements.value.forEach(function(element) {
me.api.elementIdDisplayed(element.ELEMENT, function(result) {
if (result.value) {
me.count++;
}
elcount--;
if (elcount === 0) {
callback(me.count);
}
});
});
});
};
};

Vertical and horizontal scrollable ion-content with ion-infinite-scroll only vertically

Inside my ion-content I have a list which when scrolled to the bottom more items are loaded (provided by ion-infinite-scroll which calls 'LoadMore()').
The problem is that if I set direction = xy to ion-content, ion-infinite-scroll calls 'LoadMore()' if I scroll vertically and horizontally.
It is possible to my ion-infinite-scroll only call 'LoadMore()' when scrolled vertically?
Currently ionic version beta.13 does not support this feature.
I already have posted my suggested solution on ionic repository ( https://github.com/driftyco/ionic/issues/1073 )
I've created 'ion-infinite-scroll-fixed' directive wich differs only in last if block:
.directive('ionInfiniteScrollFixed', ['$timeout', function($timeout) {
function calculateMaxValue(distance, maximum, isPercent) {
return isPercent ?
maximum * (1 - parseFloat(distance,10) / 100) :
maximum - parseFloat(distance, 10);
}
return {
restrict: 'E',
require: ['^$ionicScroll', 'ionInfiniteScrollFixed'],
template: '<i class="icon {{icon()}} icon-refreshing"></i>',
scope: true,
controller: ['$scope', '$attrs', function($scope, $attrs) {
this.isLoading = false;
this.scrollView = null; //given by link function
this.getMaxScroll = function() {
var distance = ($attrs.distance || '2.5%').trim();
var isPercent = distance.indexOf('%') !== -1;
var maxValues = this.scrollView.getScrollMax();
return {
left: this.scrollView.options.scrollingX ?
calculateMaxValue(distance, maxValues.left, isPercent) :
-1,
top: this.scrollView.options.scrollingY ?
calculateMaxValue(distance, maxValues.top, isPercent) :
-1
};
};
}],
link: function($scope, $element, $attrs, ctrls) {
var scrollCtrl = ctrls[0];
var infiniteScrollCtrl = ctrls[1];
var scrollView = infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
$scope.icon = function() {
return angular.isDefined($attrs.icon) ? $attrs.icon : 'ion-loading-d';
};
var onInfinite = function() {
$element[0].classList.add('active');
infiniteScrollCtrl.isLoading = true;
$scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');
};
var finishInfiniteScroll = function() {
$element[0].classList.remove('active');
$timeout(function() {
scrollView.resize();
checkBounds();
}, 0, false);
infiniteScrollCtrl.isLoading = false;
};
$scope.$on('scroll.infiniteScrollComplete', function() {
finishInfiniteScroll();
});
$scope.$on('$destroy', function() {
void 0;
if(scrollCtrl && scrollCtrl.$element)scrollCtrl.$element.off('scroll', checkBounds);
});
var checkBounds = ionic.animationFrameThrottle(checkInfiniteBounds);
//Check bounds on start, after scrollView is fully rendered
setTimeout(checkBounds);
scrollCtrl.$element.on('scroll', checkBounds);
function checkInfiniteBounds() {
if (infiniteScrollCtrl.isLoading) return;
var scrollValues = scrollView.getValues();
var maxScroll = infiniteScrollCtrl.getMaxScroll();
if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left && $attrs.notOnHorizontal !=="true") ||
(maxScroll.top !== -1 && scrollValues.top >= maxScroll.top && $attrs.notOnVertical !=="true")) {
onInfinite();
}
}
}
};
}]);
and in my HTML:
<ion-infinite-scroll-fixed
not-on-horizontal="true"
ng-if="infiniteScroll.canLoad"
on-infinite="infiniteScroll.loadMore();"
distance="1%">
</ion-infinite-scroll-fixed>
I hope this can help somebody :)

SlickGrid filter not working

I am fairly new to SlickGrid. I am trying to make SlickGrid filter work but no luck. I am following the example (http://mleibman.github.io/SlickGrid/examples/example4-model.html).
Below is my source code.
$(document).ready(function() {
var tName;
$('#submit').click(function(e) {
tName = $('#source option:selected').text();// name1
tName = tName.trim();
$.ajax({
url : 'someUrl',
type : 'GET',
cache : false,
success : function(d) {
var grid;
var searchString = "";
var data = [];
var columns = new Array();
var cols = d[0].columns;
var pkColNames = d[0].pkColNames;
for (var j=0; j< cols.length; j++) {
var key = {id: cols[j].colName, name: cols[j].colName, field: cols[j].colName, width: 200, sortable:true, editor: Slick.Editors.LongText};
columns[j] = key;
}
var options = {
editable: true,
enableAddRow: false,
enableCellNavigation: true,
asyncEditorLoading: false,
enableColumnReorder:true,
multiColumnSort: false,
autoEdit: false,
autoHeight: false
};
function myFilter(item, args) {
return true;// Let us return true all time ?
}
for (var i = 0; i < d.length; i++) {
var tempData = (data[i] = {});
var title = null;
var val = null;
for (var q = 0; q < d[i].columns.length; q++) {
title = d[i].columns[q].colName;
val = d[i].columns[q].colValue;
tempData[title] = val;
}
}
var dataView = new Slick.Data.DataView({ inlineFilters: true });
grid = new Slick.Grid("#myGrid", dataView, columns, options);
dataView.setPagingOptions({
pageSize: 25
});
var pager = new Slick.Controls.Pager(dataView, grid, $("#myPager"));
var columnpicker = new Slick.Controls.ColumnPicker(columns, grid, options);
grid.setSelectionModel(new Slick.CellSelectionModel());
grid.onAddNewRow.subscribe(function(e, args) {
// Adding a new record is not yet decided.
});
grid.onCellChange.subscribe(function (e) {
var editedCellNo = arguments[1].cell;
var editedColName = grid.getColumns()[editedCellNo].field;
var newUpdatedValue= arguments[1].item[grid.getColumns()[editedCellNo].field];
var editedColType = "";
for (var cnt = 0; cnt < cols.length; cnt++) {
if (editedColName == cols[cnt].colName) {
editedColType = cols[cnt].colType;
break;
}
}
var pkKeyValue="";
for (var v=0; v <pkColNames.length;v++) {
for (var p=0; p<grid.getColumns().length; p++) {
if (pkColNames[v] == grid.getColumns()[p].field) {
var value = arguments[1].item[grid.getColumns()[p].field];
pkKeyValue += "{"+pkColNames[v] + '~' +getColDbType(grid.getColumns()[p].field) + ":"+value+"},";
break;
}
}
}
function getColDbType(colName) {
for (var c = 0; c < cols.length; c++) {
if (colName == cols[c].colName) {
return cols[c].colType;
}
}
}
pkKeyValue = pkKeyValue.substring(0, pkKeyValue.length-1);
$.ajax({
url: 'anotherUrl',
type:'GET',
dataType:'text',
success: function(f) {
bootbox.alert("Data updated successfully");
},
error: function() {
bootbox.alert("Error - updating data. Please ensure you are adding the data in right format.");
grid.invalidateAllRows();
grid.render();
}
});
});
grid.onClick.subscribe(function (e) {
//do-nothing
});
dataView.onRowsChanged.subscribe(function(e, args) {
grid.updateRowCount();
grid.invalidateRows(args.rows);
grid.render();
});
grid.onSort.subscribe(function(e, args) {
// args.multiColumnSort indicates whether or not this is a multi-column sort.
// If it is, args.sortCols will have an array of {sortCol:..., sortAsc:...} objects.
// If not, the sort column and direction will be in args.sortCol & args.sortAsc.
// We'll use a simple comparer function here.
var comparer = function(a, b) {
return a[args.sortCol.field] > b[args.sortCol.field];
}
// Delegate the sorting to DataView.
// This will fire the change events and update the grid.
dataView.sort(comparer, args.sortAsc);
});
// wire up the search textbox to apply the filter to the model
$("#txtSearch").keyup(function (e) {
console.log('Called...txtSearch');
Slick.GlobalEditorLock.cancelCurrentEdit();
// clear on Esc
if (e.which == 27) {
this.value = "";
}
searchString = this.value;
updateFilter();
});
function updateFilter() {
console.log("updateFilter");
dataView.setFilterArgs({
searchString: searchString
});
dataView.refresh();
}
dataView.beginUpdate();
dataView.setItems(data, pkColNames);
dataView.setFilterArgs({
searchString: searchString
});
dataView.setFilter(myFilter);
dataView.endUpdate();
},
error : function() {
bootbox.alert("Invalid user");
}
});
});
});
Your function myFilter() is always returning true so of course it will never work. The example that you looked at, was to filter something specific. I would recommend that you look at the following example to have a generic filter. From the example, simply type the text you are looking on a chosen column and look at the result... see example below (from SlickGrid examples).Using fixed header row for quick filters
In case you want a more in depth conditional filters ( > 10, <> 10, etc...), please take a look at my previous answer on how to make this kind of filtering possible, see my previous answer below:SlickGrid column type
Hope that helps you out

Facebook check in and windows phone

I've got a script that essentially gives the user the ability to check in to a specific location when they click it. Upon clicking it, it checks them into the same location each time.
On desktop this works fine and I suspect it works on Android too. I have a WP and thus obviously I want to get it working on there. This feature is pretty much going to be used exclusively on mobile so getting it working cross-device is important.
Now, to the actual issue. Upon clicking the check in button on WP it loads a blank page and hangs there. Whilst this may be a signal that it perhaps has checked in, when I look it hasn't. So it's hanging there and doing nothing.
This is the URL it's hanging on: https://m.facebook.com/dialog/oauth?display=touch&domain=www.staffscuesociety.com&scope=publish_stream&api_key=API_KEY&app_id=APP_ID&locale=en_US&sdk=joey&access_token=ACCESS_TOKEN&client_id=CLIENT_ID&redirect_uri=http%3A%2F%2Fstatic.ak.facebook.com%2Fconnect%2Fxd_arbiter.php%3Fversion%3D18%23cb%3Df35fef827217048%26origin%3Dhttp%253A%252F%252Fwww.staffscuesociety.com%252Ff166245837c3b6e%26domain%3Dwww.staffscuesociety.com%26relation%3Dopener%26frame%3Df398a7113310e64&origin=2&response_type=token%2Csigned_request
and this is the code:
var button;
(function(d){
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
window.fbAsyncInit = function() {
FB.init({
appId : '260445377421174',
channelUrl : '//www.staffscuesociety.com/checkin/channel.html'
});
button = document.getElementById('checkinWidget');
if (!button) return;
if (!getPost()) {
allowCheckin();
} else {
allowUndo();
}
};
function setPost(value) {
var d = new Date();
d.setDate(d.getTime() + (10 * 60 * 1000));
document.cookie = 'post=' + escape(value) + ';expires=' + d.toUTCString();
}
function getPost() {
return getCookie('post');
}
function getCookie(key) {
currentcookie = document.cookie;
if (currentcookie.length > 0) {
firstidx = currentcookie.indexOf(key + '=');
if (firstidx != -1) {
firstidx = firstidx + key.length + 1;
lastidx = currentcookie.indexOf(';', firstidx);
if (lastidx == -1) {
lastidx = currentcookie.length;
}
return unescape(currentcookie.substring(firstidx, lastidx));
}
}
return '';
}
function checkin() {
allowNothing();
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me/permissions', function (permissions) {
if (permissions.data.length > 0 && permissions.data[0].publish_stream) {
FB.api('/me/feed', 'post', { message: null, place: '117072761683514' }, function(post) {
if (!post || post.error) {
showError();
} else {
setPost(post.id);
allowUndo();
}
});
} else {
allowCheckin();
}
});
} else {
allowCheckin();
}
}, {scope : 'publish_stream'});
}
function undo() {
allowNothing();
FB.api('/' + getPost(), 'delete', function(response) {
setPost('');
allowCheckin();
});
}
function allowNothing() {
button.onclick = function() { };
button.className = 'pressed';
}
function allowCheckin() {
button.onclick = checkin;
button.className = '';
}
function allowUndo() {
button.onclick = undo;
button.className = 'pressed tick';
}
function showError() {
button.className = 'pressed error';
}
}

Resources