Read the statement by Michael Teeuw here.
Peek-a-boo...
-
@darrene you are a SAINT!..I’m going to follow these instructions now and will let you know how it goes
-
@darrene I went through all the steps and I can see in the console that the modules are receiving the notification broadcast as expected but for whatever reason the Calendar isn’t actually responding (i.e. HIDE_CALENDAR doesn’t make the calendar go away).
Any ideas? I’m wondering if it has to do with where I put the code into calendar.js?
-
It sounds as though you’re almost there @carteblanche. That’s great!
It’s either a case then of a mismatch between what you’re expecting to be broadcast and what is being, or as you say, the calendar not reacting to the notification it receives.
So to be clear, in the console you see HIDE_CALENDAR as the notification that’s broadcast? if so, that rules out the first issue.
I’m using the default calendar module rather than a 3rd-party one. I’m going to assume you are too but if not, you’ll just need to ensure it’s the right module you’re working on. (I don’t mean it to sound condescending and sorry if it’s obvious but personally speaking, I’m so busy looking closely at the issue that I usually find out afterwards it was something like that! :) )
It could be the location in the calendar.js but it’s just as likely there’s a small typo which means the calendar is ignoring it.
If we assume the notification is making it as far as the calendar module, I’ll post my entire calendar.js so you can compare the syntax and see where I’ve put it (in all of my modules I’ve tried to put it in the same place relatively - just before the Override Dom Generator section)
Good luck!
/* global Module */ /* Magic Mirror * Module: Calendar * * By Michael Teeuw http://michaelteeuw.nl * MIT Licensed. */ Module.register("calendar",{ // Define module defaults defaults: { maximumEntries: 10, // Total Maximum Entries maximumNumberOfDays: 365, displaySymbol: true, defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/ displayRepeatingCountTitle: false, defaultRepeatingCountTitle: '', maxTitleLength: 25, fetchInterval: 5 * 60 * 1000, // Update every 5 minutes. animationSpeed: 2000, fade: true, urgency: 7, timeFormat: "relative", fadePoint: 0.25, // Start on 1/4th of the list. calendars: [ { symbol: "calendar", url: "http://www.calendarlabs.com/templates/ical/US-Holidays.ics", }, ], titleReplace: { "De verjaardag van ": "", "'s birthday": "" }, }, // Define required scripts. getStyles: function() { return ["calendar.css", "font-awesome.css"]; }, // Define required scripts. getScripts: function() { return ["moment.js"]; }, // Define required translations. getTranslations: function() { // The translations for the defaut modules are defined in the core translation files. // Therefor we can just return false. Otherwise we should have returned a dictionairy. // If you're trying to build your own module including translations, check out the documentation. return false; }, // Override start method. start: function() { Log.log("Starting module: " + this.name); // Set locale. moment.locale(config.language); for (var c in this.config.calendars) { var calendar = this.config.calendars[c]; calendar.url = calendar.url.replace("webcal://", "http://"); this.addCalendar(calendar.url, calendar.user, calendar.pass); } this.calendarData = {}; this.loaded = false; }, // Override socket notification handler. socketNotificationReceived: function(notification, payload) { if (notification === "CALENDAR_EVENTS") { if (this.hasCalendarURL(payload.url)) { this.calendarData[payload.url] = payload.events; this.loaded = true; } } else if (notification === "FETCH_ERROR") { Log.error("Calendar Error. Could not fetch calendar: " + payload.url); } else if (notification === "INCORRECT_URL") { Log.error("Calendar Error. Incorrect url: " + payload.url); } else { Log.log("Calendar received an unknown socket notification: " + notification); } this.updateDom(this.config.animationSpeed); }, // added by DGE to respond to voice notifications notificationReceived: function(notification, payload, sender) { if (notification === "HIDE_CALENDAR") { this.hide(); } else if (notification === "SHOW_CALENDAR") { this.show(); } else if (notification === "HIDE_ALL") { this.hide(); } else if (notification === "SHOW_ALL") { this.show(); } }, // Override dom generator. getDom: function() { var events = this.createEventList(); var wrapper = document.createElement("table"); wrapper.className = "small"; if (events.length === 0) { wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING"); wrapper.className = "small dimmed"; return wrapper; } for (var e in events) { var event = events[e]; var eventWrapper = document.createElement("tr"); eventWrapper.className = "normal"; if (this.config.displaySymbol) { var symbolWrapper = document.createElement("td"); symbolWrapper.className = "symbol"; var symbol = document.createElement("span"); symbol.className = "fa fa-" + this.symbolForUrl(event.url); symbolWrapper.appendChild(symbol); eventWrapper.appendChild(symbolWrapper); } var titleWrapper = document.createElement("td"), repeatingCountTitle = ''; if (this.config.displayRepeatingCountTitle) { repeatingCountTitle = this.countTitleForUrl(event.url); if(repeatingCountTitle !== '') { var thisYear = new Date().getFullYear(), yearDiff = thisYear - event.firstYear; repeatingCountTitle = ', '+ yearDiff + '. ' + repeatingCountTitle; } } titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle; titleWrapper.className = "title bright"; eventWrapper.appendChild(titleWrapper); var timeWrapper = document.createElement("td"); //console.log(event.today); var now = new Date(); // Define second, minute, hour, and day variables var one_second = 1000; // 1,000 milliseconds var one_minute = one_second * 60; var one_hour = one_minute * 60; var one_day = one_hour * 24; if (event.fullDayEvent) { if (event.today) { timeWrapper.innerHTML = this.translate("TODAY"); } else if (event.startDate - now < one_day && event.startDate - now > 0) { timeWrapper.innerHTML = this.translate("TOMORROW"); } else if (event.startDate - now < 2*one_day && event.startDate - now > 0) { /*Provide ability to show "the day after tomorrow" instead of "in a day" *if "DAYAFTERTOMORROW" is configured in a language's translation .json file, *,which can be found in MagicMirror/translations/ */ if (this.translate('DAYAFTERTOMORROW') !== 'DAYAFTERTOMORROW') { timeWrapper.innerHTML = this.translate("DAYAFTERTOMORROW"); } else { timeWrapper.innerHTML = moment(event.startDate, "x").fromNow(); } } else { /* Check to see if the user displays absolute or relative dates with their events * Also check to see if an event is happening within an 'urgency' time frameElement * For example, if the user set an .urgency of 7 days, those events that fall within that * time frame will be displayed with 'in xxx' time format or moment.fromNow() * * Note: this needs to be put in its own function, as the whole thing repeats again verbatim */ if (this.config.timeFormat === "absolute") { if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * one_day))) { // This event falls within the config.urgency period that the user has set timeWrapper.innerHTML = moment(event.startDate, "x").fromNow(); } else { timeWrapper.innerHTML = moment(event.startDate, "x").format("MMM Do"); } } else { timeWrapper.innerHTML = moment(event.startDate, "x").fromNow(); } } } else { if (event.startDate >= new Date()) { if (event.startDate - now < 2 * one_day) { // This event is within the next 48 hours (2 days) if (event.startDate - now < 6 * one_hour) { // If event is within 6 hour, display 'in xxx' time format or moment.fromNow() timeWrapper.innerHTML = moment(event.startDate, "x").fromNow(); } else { // Otherwise just say 'Today/Tomorrow at such-n-such time' timeWrapper.innerHTML = moment(event.startDate, "x").calendar(); } } else { /* Check to see if the user displays absolute or relative dates with their events * Also check to see if an event is happening within an 'urgency' time frameElement * For example, if the user set an .urgency of 7 days, those events that fall within that * time frame will be displayed with 'in xxx' time format or moment.fromNow() * * Note: this needs to be put in its own function, as the whole thing repeats again verbatim */ if (this.config.timeFormat === "absolute") { if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * one_day))) { // This event falls within the config.urgency period that the user has set timeWrapper.innerHTML = moment(event.startDate, "x").fromNow(); } else { timeWrapper.innerHTML = moment(event.startDate, "x").format("MMM Do"); } } else { timeWrapper.innerHTML = moment(event.startDate, "x").fromNow(); } } } else { timeWrapper.innerHTML = this.translate("RUNNING") + ' ' + moment(event.endDate,"x").fromNow(true); } } //timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll'); //console.log(event); timeWrapper.className = "time light"; eventWrapper.appendChild(timeWrapper); wrapper.appendChild(eventWrapper); // Create fade effect. if (this.config.fade && this.config.fadePoint < 1) { if (this.config.fadePoint < 0) { this.config.fadePoint = 0; } var startingPoint = events.length * this.config.fadePoint; var steps = events.length - startingPoint; if (e >= startingPoint) { var currentStep = e - startingPoint; eventWrapper.style.opacity = 1 - (1 / steps * currentStep); } } } return wrapper; }, /* hasCalendarURL(url) * Check if this config contains the calendar url. * * argument url sting - Url to look for. * * return bool - Has calendar url */ hasCalendarURL: function(url) { for (var c in this.config.calendars) { var calendar = this.config.calendars[c]; if (calendar.url === url) { return true; } } return false; }, /* createEventList() * Creates the sorted list of all events. * * return array - Array with events. */ createEventList: function() { var events = []; var today = moment().startOf("day"); for (var c in this.calendarData) { var calendar = this.calendarData[c]; for (var e in calendar) { var event = calendar[e]; event.url = c; event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000); events.push(event); } } events.sort(function(a, b) { return a.startDate - b.startDate; }); return events.slice(0, this.config.maximumEntries); }, /* createEventList(url) * Requests node helper to add calendar url. * * argument url sting - Url to add. */ addCalendar: function(url, user, pass) { this.sendSocketNotification("ADD_CALENDAR", { url: url, maximumEntries: this.config.maximumEntries, maximumNumberOfDays: this.config.maximumNumberOfDays, fetchInterval: this.config.fetchInterval, user: user, pass: pass }); }, /* symbolForUrl(url) * Retrieves the symbol for a specific url. * * argument url sting - Url to look for. * * return string - The Symbol */ symbolForUrl: function(url) { for (var c in this.config.calendars) { var calendar = this.config.calendars[c]; if (calendar.url === url && typeof calendar.symbol === "string") { return calendar.symbol; } } return this.config.defaultSymbol; }, /* countTitleForUrl(url) * Retrieves the name for a specific url. * * argument url sting - Url to look for. * * return string - The Symbol */ countTitleForUrl: function(url) { for (var c in this.config.calendars) { var calendar = this.config.calendars[c]; if (calendar.url === url && typeof calendar.repeatingCountTitle === "string") { return calendar.repeatingCountTitle; } } return this.config.defaultRepeatingCountTitle; }, /* shorten(string, maxLength) * Shortens a sting if it's longer than maxLenthg. * Adds an ellipsis to the end. * * argument string string - The string to shorten. * argument maxLength number - The max lenth of the string. * * return string - The shortened string. */ shorten: function(string, maxLength) { if (string.length > maxLength) { return string.slice(0,maxLength) + "…"; } return string; }, /* titleTransform(title) * Transforms the title of an event for usage. * Replaces parts of the text as defined in config.titleReplace. * Shortens title based on config.maxTitleLength * * argument title string - The title to transform. * * return string - The transformed title. */ titleTransform: function(title) { for (var needle in this.config.titleReplace) { var replacement = this.config.titleReplace[needle]; title = title.replace(needle, replacement); } title = this.shorten(title, this.config.maxTitleLength); return title; }
});
-
As I mentioned earlier, the approach I’ve taken isn’t exactly elegant. It’s the way to go for a HIDE_ALL or SHOW_ALL because obviously all the modules would need to know then, but a more efficient approach would really be to target modules as you need them - that involved reworking the voicecontrol.js or node-helper though and passing two variables - the notification and the target module. I just wasn’t confident enough to do this yet.
-
@darrene thank you for sharing your calendar.js file because that is where I found where the issue was…it was the name for the function notificationReceived: – needed to have a lower case “n”.
Now it works perfectly!! And as you predicted I experienced a massive grin.
update:
I’ve been testing this out with other modules and it works like a charm. I also discovered (after trial and error) that its possible to combine the hide/show with the hide all/show all command simply by revising the if statement on the notificationReceived function with an “or” condition specifying both commands . See below is an example of the revised code:notificationReceived: function(notification, payload, sender) {
if (notification === “HIDE_CALENDAR” || “HIDE_ALL”) {
this.hide();
} else if (notification === “SHOW_CALENDAR” || “SHOW_ALL”) {
this.show();
}
}, -
@carteblanche said in Peek-a-boo...:
@darrene thank you for sharing your calendar.js file because that is where I found where the issue was…it was the name for the function notificationReceived: – needed to have a lower case “n”.
Now it works perfectly!! And as you predicted I experienced a massive grin.
update:
I’ve been testing this out with other modules and it works like a charm. I also discovered (after trial and error) that its possible to combine the hide/show with the hide all/show all command simply by revising the if statement on the notificationReceived function with an “or” condition specifying both commands . See below is an example of the revised code:notificationReceived: function(notification, payload, sender) {
if (notification === “HIDE_CALENDAR” || “HIDE_ALL”) {
this.hide();
} else if (notification === “SHOW_CALENDAR” || “SHOW_ALL”) {
this.show();
}
},update: while this worked for a short period, upon further testing this code seems to cause some issues with HIDE_ALL auto suspending all modules on launch. So the or condition if statement doesn’t appear to be a viable solution, the only way to include hide/show all is the way you have the code now @darrene with the multiple else if statements.
-
if (notification === "HIDE_CALENDAR" || "HIDE_ALL") this will be always true: notification === "HIDE_CALENDAR" can be true or false, but "HIDE_ALL" will be always true
that’s why it always hide the module
it must be like that
if (notification === "HIDE_CALENDAR" || notification === "HIDE_ALL") { this.hide(); } else if (notification === "SHOW_CALENDAR" || notification === "SHOW_ALL") { this.show(); } },
-
@strawberry-3.141 Thank you for the tip! Now it makes perfect sense.
It does still work with the multiple else if statements but for the sake of cleaner code I like the idea of using the or condition.
-
Apologies for the typo in notification @carteblanche - I should have copied and pasted the code originally, rather than typing it off the the top of my head!
Glad to read that you managed to sort it though. Nice work :)
-
@darrene hey no worries at all on the typo. I can’t thank you enough for helping out and providing the comprehensive step-by-step guide in plain english to noobs like me. I don’t know how I would have got the voice module working without your help so thanks again!