An approach for tracking and using sequential clicks - javascript

I'd like to define a relationship between two dom elements (in my particular case, draw lines between to nodes) by clicking on the first followed by a click on the second. The following is my approach. I don't think it's particularly elegant or clean. Is there a better way to do this?
$(function() {
var State = {
initialClick: false, // tracks clicks status for drawing lines
initialID: undefined // remember data gotten from the first click
};
...
$map.on('click', '.node', function() {
var $this = $(this),
currentID = $this.attr('data-id');
if (State.initialClick) {
if (State.initialID === currentID) {
// process first click
} else {
// process second click
}
resetClickTracking();
} else {
setState(currentID);
}
});
...
function setState(id) {
State.initialClick = true;
State.initialID = id;
}
function resetState() {
State.initialClick = false;
State.initialID = undefined;
}
});

I would probably go for a delegate pattern:
var lastNodeClicked = null;
$map.on('click', '.node', function() {
if (lastNodeClicked && lastNodeClicked !== this) {
// draw line
lastNodeClicked = null; // reset
} else {
lastNodeClicked = this;
}
});
Instead of resetting the last clicked node, you can also unconditionally update it with the clicked node; that way you can draw lines with less clicks ;-)

Related

How to get the event text of a single calendar in Google calendar in javascript

I would like to create a Chrome extension to change the color of the text for all events in a particular calendar. I am modifying the code in an extension called gcalcolor. Here is the code in the content.js file of the extension:
// Content script for the extension. Does all the work.
"use strict";
// Colorizes an event element. Finds the colored dot, then sets the
// overall color to that dot's color (which is its borderColor; the
// dot is just an empty 0x0 element with a circular border). Also
// hides the dot, since it is no longer needed to show the color, and
// reduces padding to help line up events and let you see more of
// their names.
function colorizeEvent(eventEl) {
let success = true;
// First try layout for month and multi-week (custom) views.
let dotEl = eventEl;
for (let i=0; i<3; i++) {
dotEl = dotEl.firstChild;
if (!dotEl) {
success = false;
break;
}
}
if (success) {
let color = dotEl.style.borderColor;
if (!color) {
success = false; // Probably not a timed event
}
else {
eventEl.firstChild.style.color = color;
eventEl.firstChild.style.padding = '0';
dotEl.style.display = 'none';
}
}
// if the above failed, try the Schedule (Agenda) layout
if (!success) {
let timeEl = eventEl.firstChild;
if (!timeEl) {
return;
}
let detailsEl = timeEl.nextSibling;
if (!detailsEl) {
return;
}
let dotContainer1El = detailsEl.nextSibling;
if (!dotContainer1El) {
return;
}
let dotContainer2El = dotContainer1El.firstChild;
if (!dotContainer2El) {
return;
}
let dotEl = dotContainer2El.firstChild;
if (!dotEl) {
return;
}
let color = dotEl.style.borderColor;
if (!color) {
return;
}
else {
detailsEl.style.color = color;
eventEl.style.height = '28px';
dotContainer1El.style.display = 'none';
}
}
}
// Colorizes all visible events.
function colorizeAll() {
let eventElements = document.querySelectorAll('[data-eventid]');
for (let eventElement of eventElements) {
colorizeEvent(eventElement);
}
}
// We don't have a precise way to know when Google Calendar has drawn
// some new events on the screen. Instead we use a MutationObserver to
// watch for DOM changes anywhere on the page. It would be really
// inefficient to run colorizeAll every time we got an observer
// callback, so instead we wait for a short delay to see if any more
// callbacks happen. If so, we reset the timer and wait again. We call
// colorizeAll only when the timer completes without another callback.
//
// Because there are a lot of irregularly timed screen updates when
// the page is first being loaded, we set the delay to a quarter second
// at first. After five seconds, we set it to 20 milliseconds for a
// faster response to small updates.
let timeoutId = null;
let observerDelay = 250;
setTimeout(() => { observerDelay = 20; }, 5000);
function postObserverCallbacks() {
timeoutId = null;
colorizeAll();
}
function observerCallback(mutationList) {
if (timeoutId)
clearTimeout(timeoutId);
timeoutId = setTimeout(postObserverCallbacks, observerDelay);
}
let observer = new MutationObserver(observerCallback);
observer.observe(
document.body,
{
childList: true,
attributes: true,
subtree: true
}
);
I have already modified the following line as follows:
Original line
let color = dotEl.style.borderColor;
My new line
let color = '#FA8072';
This modification causes the text of all events to change to the same color.
Now, I want to change the color of events only in a particular calendar. I know the id of the calendar. For sake of illustration, let's say the calendar id is abcdefg1234567. I think the line of code I need to modify to select only events from this calendar is this line:
let eventElements = document.querySelectorAll('[data-eventid]');
I have searched for a few hours to try and figure out how to modify this line, but I haven't been able to figure it out.
Simple answer:
Delete "use strict"; at top of page :)

Tiles dont turn right

I am making a website, http://phaidonasgialis.xyz. I want to make the tiles to turn and every other tile back. Until now it works except one small bug, the tile that you pressed does not turn back until you click second time.Any help would be thankful.
Here is the code:
$(document).ready(function() {
var previous = [];
$('.flip-card').each(function(i, obj) {
$(this).click(function() {
if ($(this).find('.flip-card-inner').hasClass('flip-card-transform')) {
$(this).find('.flip-card-inner').removeClass('flip-card-transform');
} else {
$(this).find('.flip-card-inner').addClass('flip-card-transform');
previous.push(this);
if (previous.length >= 2 && previous[previous.length - 2] != previous[previous.length - 1]) {
for (var i = 0; i < previous.length; i++) {
if ($(this).find('.flip-card-inner').hasClass('flip-card-transform')) {
$(previous[i - 1]).find('.flip-card-inner').removeClass('flip-card-transform');
console.log("2")
} else {
$(this).find('.flip-card-inner').addClass('flip-card-transform');
console.log("3")
}
}
}
}
});
});
If I understand your question correctly, you would like a card to flip back automatically after a certain period of time? If so then you just should set a Timeout and remove the class flip-card-transform from .flip-card-inner.
Aside note: you should definitely optimize your code by using a variable instead of doing $(this).find('.flip-card-inner')– converting this to jQuery object and searching trough its children every time when you need your flip-card-inner. Also instead of $('.flip-card').each(...) you could directly use $('.flip-card').click(...). And also as Harun Yilmaz suggested in his comment you don't need previous as an array...
So something like this should work:
$(document).ready(function () {
var previous
var timeout
$('.flip-card').click(function () {
var cardInner = $(this).find('.flip-card-inner')
if (cardInner.hasClass('flip-card-transform')) {
cardInner.removeClass('flip-card-transform')
clearTimeout(timeout)
} else {
cardInner.addClass('flip-card-transform')
// Set a Timeout
timeout = setTimeout(function () {
cardInner.removeClass('flip-card-transform')
}, 2000) // set whatever time sutable for you
// Also as Harun Yilmaz suggested in his comment you don't need previous as an array
if (previous && previous.hasClass('flip-card-transform')) {
previous.removeClass('flip-card-transform')
}
previous = cardInner
}
})
})

Issues with event delegation on same container

I have a function, simplified like this:
var fooFunction = function($container, data) {
$container.data('foobarData', data);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
if (typeof data.validateFunction === 'function' && !data.validateFunction(e)) {
return;
}
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(data.returnText);
});
};
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-companies').length ? true : false;
},
returnText: 'Hello Company!',
});
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-humans').length ? true : false;
},
returnText: 'Hello Human!',
})
I am using event delegation on the same container (.same-container) with a custom validateFunction() to validate if the code in // Do stuff should run.
For each fooFunction() initiation, I have some different logic that will get called on // Do stuff. The issue is that those two event delegations conflict. It seems that only one of them is called and overwrites the other one.
How can I have multiple event delegations with the option to define via a custom validateFunction which one should be called. I use preventDefault() + stopPropagation() so on click on a <a>, nothing happens as long as validateFunction() returns true.
The problem is that you're overwriting $(e.delegateTarget).data('foobarData') every time.
Instead, you could add the options to an array, which you loop over until a match is found.
var fooFunction = function($container, data) {
var oldData = $container.data('foobarData', data);
if (oldData) { // Already delegated, add the new data
oldData.push(data);
$container.data('foobarData', oldData);
} else { // First time, create initial data and delegate handler
$container.data('foobarData', [data]);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
var index = data.find(data => typeof data.validateFunction === 'function' && !data.validateFunction(e));
if (index > -1) {
var foundData = data[index]
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(foundData.returnText);
}
});
}
}

Adding a condition to function so that it runs only in specific situations

I'm working on a text game in javascript right now and have a function to pick up a sword.
var takeSword = function() {
if (currentRoom.hasSword) {
$("<p>You picked up a sword.</p>").properDisplay();
}
else {
$("<p>The sword is not here.</p>").properDisplay();
}
};
My problem is, as long as you're in the same room as the sword, you can pick it up over and over. How can you set the function so that once you pick up the sword you can't pick it up again?
My original thought was setting a variable like var sword = false; and then when the function runs to set sword = true; but that didn't work.
This isn't my entire code, there's an object further up that sets `hasSword = true;' so that the sword can be picked up in the first place and can NOT be picked up in different rooms of the game.
Something like this perhaps?
var GameState = {
worldHasSword: true,
playerHasSword: false,
takeSword: function() {
if( this.worldHasSword ) {
this.worldHasSword = false;
this.playerHasSword = true;
$("<p>You picked up a sword.</p>").properDisplay();
}
else {
$("<p>The sword is not here.</p>").properDisplay();
}
}
}
GameState.takeSword();
The best solution here is not to touch your original function at all, but simply wrap it in a general function that will prevent its being called more than once. This general function can be used anywhere else in your code that you need to "once-ify" something:
function once(fn, context) {
var result;
return function() {
if(fn) {
result = fn.apply(context || this, arguments);
fn = null;
}
return result;
};
}
Now you simply do:
var takeSwordOnce = once(takeSword);
and use takeSwordOnce in your code. Or, you can do:
takeSword = once(takeSword);
Please see this article for further explanation of the once function.
#Kenney got me thinking in the right direction. Here's how I fixed it without totally rewriting the function:
var takeSword = function() {
if (currentRoom.hasSword) {
$("<p>You picked up a sword.</p>").properDisplay();
currentRoom.hasSword = false;
}
else {
$("<p>The sword is not here.</p>").properDisplay();
}
};
The first mistake I made was thinking "hasSword" was the variable by itself. I needed to add the currentRoom to it then it worked fine. I've tested it and also tried it in rooms where the sword does not exist and it seems to be working fine.

How can I make this javascript easier to read, maintain, and understand from an OO background?

I come from the land of Java, C#, etc. I am working on a javascript report engine for a web application I have. I am using jQuery, AJAX, etc. I am having difficulty making things work the way I feel they should - for instance, I have gone to what seems like too much trouble to make sure that when I make an AJAX call, my callback has access to the object's members. Those callback functions don't need to be that complicated, do they? I know I must be doing something wrong. Please point out what I could be doing better - let me know if the provided snippet is too much/too little/too terrible to look at.
What I'm trying to do:
On page load, I have a select full of users.
I create the reports (1 for now) and add them to a select box.
When both a user and report are selected, I run the report.
The report involves making a series of calls - getting practice serieses, leagues, and tournaments - for each league and tournament, it gets all of those serieses, and then for each series it grabs all games.
It maintains a counter of the calls that are active, and when they have all completed the report is run and displayed to the user.
Code:
//Initializes the handlers and reports
function loadUI() {
loadReports();
$("#userSelect").change(updateRunButton);
$("#runReport").click(runReport);
updateRunButton();
return;
$("#userSelect").change(loadUserGames);
var user = $("#userSelect").val();
if(user) {
getUserGames(user);
}
}
//Creates reports and adds them to the select
function loadReports() {
var reportSelect = $("#reportSelect");
var report = new SpareReport();
engine.reports[report.name] = report;
reportSelect.append($("<option/>").text(report.name));
reportSelect.change(updateRunButton);
}
//The class that represents the 1 report we can run right now.
function SpareReport() {
this.name = "Spare Percentages";
this.activate = function() {
};
this.canRun = function() {
return true;
};
//Collects the data for the report. Initializes/resets the class variables,
//and initiates calls to retrieve all user practices, leagues, and tournaments.
this.run = function() {
var rC = $("#rC");
var user = engine.currentUser();
rC.html("<img src='/img/loading.gif' alt='Loading...'/> <span id='reportProgress'>Loading games...</span>");
this.pendingOperations = 3;
this.games = [];
$("#runReport").enabled = false;
$.ajaxSetup({"error":(function(report) {
return function(event, XMLHttpRequest, ajaxOptions, thrownError) {
report.ajaxError(event, XMLHttpRequest, ajaxOptions, thrownError);
};
})(this)});
$.getJSON("/api/leagues", {"user":user}, (function(report) {
return function(leagues) {
report.addSeriesGroup(leagues);
};
})(this));
$.getJSON("/api/tournaments", {"user":user}, (function(report) {
return function(tournaments) {
report.addSeriesGroup(tournaments);
};
})(this));
$.getJSON("/api/practices", {"user":user}, (function(report) {
return function(practices) {
report.addSerieses(practices);
};
})(this));
};
// Retrieves the serieses (group of IDs) for a series group, such as a league or
// tournament.
this.addSeriesGroup = function(seriesGroups) {
var report = this;
if(seriesGroups) {
$.each(seriesGroups, function(index, seriesGroup) {
report.pendingOperations += 1;
$.getJSON("/api/seriesgroup", {"group":seriesGroup.key}, (function(report) {
return function(serieses) {
report.addSerieses(serieses);
};
})(report));
});
}
this.pendingOperations -= 1;
this.tryFinishReport();
};
// Retrieves the actual serieses for a series group. Takes a set of
// series IDs and retrieves each series.
this.addSerieses = function(serieses) {
var report = this;
if(serieses) {
$.each(serieses, function(index, series) {
report.pendingOperations += 1;
$.getJSON("/api/series", {"series":series.key}, (function(report) {
return function(series) {
report.addSeries(series);
};
})(report));
});
}
this.pendingOperations -= 1;
this.tryFinishReport();
};
// Adds the games for the series to the list of games
this.addSeries = function(series) {
var report = this;
if(series && series.games) {
$.each(series.games, function(index, game) {
report.games.push(game);
});
}
this.pendingOperations -= 1;
this.tryFinishReport();
};
// Checks to see if all pending requests have completed - if so, runs the
// report.
this.tryFinishReport = function() {
if(this.pendingOperations > 0) {
return;
}
var progress = $("#reportProgress");
progress.text("Performing calculations...");
setTimeout((function(report) {
return function() {
report.finishReport();
};
})(this), 1);
}
// Performs report calculations and displays them to the user.
this.finishReport = function() {
var rC = $("#rC");
//snip a page of calculations/table generation
rC.html(html);
$("#rC table").addClass("tablesorter").attr("cellspacing", "1").tablesorter({"sortList":[[3,1]]});
};
// Handles errors (by ignoring them)
this.ajaxError = function(event, XMLHttpRequest, ajaxOptions, thrownError) {
this.pendingOperations -= 1;
};
return true;
}
// A class to track the state of the various controls. The "series set" stuff
// is for future functionality.
function ReportingEngine() {
this.seriesSet = [];
this.reports = {};
this.getSeriesSet = function() {
return this.seriesSet;
};
this.clearSeriesSet = function() {
this.seriesSet = [];
};
this.addGame = function(series) {
this.seriesSet.push(series);
};
this.currentUser = function() {
return $("#userSelect").val();
};
this.currentReport = function() {
reportName = $("#reportSelect").val();
if(reportName) {
return this.reports[reportName];
}
return null;
};
}
// Sets the enablement of the run button based on the selections to the inputs
function updateRunButton() {
var report = engine.currentReport();
var user = engine.currentUser();
setRunButtonEnablement(report != null && user != null);
}
function setRunButtonEnablement(enabled) {
if(enabled) {
$("#runReport").removeAttr("disabled");
} else {
$("#runReport").attr("disabled", "disabled");
}
}
var engine = new ReportingEngine();
$(document).ready( function() {
loadUI();
});
function runReport() {
var report = engine.currentReport();
if(report == null) {
updateRunButton();
return;
}
report.run();
}
I am about to start adding new reports, some of which will operate on only a subset of user's games. I am going to be trying to use subclasses (prototype?), but if I can't figure out how to simplify some of this... I don't know how to finish that sentence. Help!
$.getJSON("/api/leagues", {"user":user}, (function(report) {
return function(leagues) {
report.addSeriesGroup(leagues);
};
})(this));
Can be written as:
var self = this;
$.getJSON("/api/leagues", {"user":user}, (function(leagues) {
self.addSeriesGroup(leagues);
});
The function-returning-function is more useful when you're inside a loop and want to bind to a variable that changes each time around the loop.
Provide "some" comments where necessary.
I'm going to be honest with you and say that I didn't read the whole thing. However, I think there is something about JavaScript you should know and that is that it has closures.
var x = 1;
$.ajax({
success: function () {
alert(x);
}
});
No matter how long time it takes for the AJAX request to complete, it will have access to x and will alert "1" once it succeeds.
Understand Closures. This takes some getting used to. (which, many will use, and is certainly the typical way of going about things, so it's good if you understand how that's happening)
This is a good thread to read to get a simple explanation of how to use them effectively.
You should use prototypes to define methods and do inheritance:
function Parent(x) {
this.x = x; /* Set an instance variable. Methods come later. */
}
/* Make Parent inherit from Object by assigning an
* instance of Object to Parent.prototype. This is
* very different from how you do inheritance in
* Java or C# !
*/
Parent.prototype = { /* Define a method in the parent class. */
foo: function () {
return 'parent ' + this.x; /* Use an instance variable. */
}
}
function Child(x) {
Parent.call(this, x) /* Call the parent implementation. */
}
/* Similar to how Parent inherits from Object; you
* assign an instance of the parent class (Parent) to
* the prototype attribute of the child constructor
* (Child).
*/
Child.prototype = new Parent();
/* Specialize the parent implementation. */
Child.prototype.foo = function() {
return Parent.prototype.foo.call(this) + ' child ' + this.x;
}
/* Define a method in Child that does not override
* something in Parent.
*/
Child.prototype.bar = function() {
return 'bar';
}
var p = new Parent(1);
alert(p.foo());
var ch = new Child(2);
alert(ch.foo());
alert(ch.bar());
I'm not familiar with jQuery, but I know the Prototype library (worst name choice ever) has some functionality that make it easier to work with inheritance.
Also, while coming up with the answer to this question, I found a nice page that goes into more detail on how to do OO right in JS, which you may want to look at.

Categories