Using a script to sync google sheets with google calendar. It was originally posted on GitHub by DavePar - the original can be seen here - GitHub project
Changed a couple of titles and it works fine.
However, added a column title Stand and getting the following error message. When tried to run the script now
TypeError: Cannot find function getStand in object CalendarEvent.
Have looked through several times and can't see where the problem lies. Can anyone have a look though and point me in the right direction, please?
Here is the slightly modified version:
// Script to synchronize a calendar to a spreadsheet and vice versa.
//
// See https://github.com/Davepar/gcalendarsync for instructions on setting this up.
//
// Set this value to match your calendar!!!
// Calendar ID can be found in the "Calendar Address" section of the Calendar Settings.
var calendarId = 'dtsmike#googlemail.com';
// Set the beginning and end dates that should be synced. beginDate can be set to Date() to use
// today. The numbers are year, month, date, where month is 0 for Jan through 11 for Dec.
var beginDate = new Date(1970, 0, 1); // Default to Jan 1, 1970
var endDate = new Date(2500, 0, 1); // Default to Jan 1, 2500
// Date format to use in the spreadsheet.
var dateFormat = 'd/m/yyyy H:mm';
var titleRowMap = {
'title': 'Title',
'stand': 'Stand',
'description': 'Kit Prep',
'location': 'Location',
'starttime': 'Install',
'endtime': 'Show Finish',
'guests': 'Guests',
'color': 'Color',
'id': 'Id'
};
var titleRowKeys = ['title', 'stand', 'description', 'location', 'starttime', 'endtime', 'guests', 'color', 'id'];
var requiredFields = ['id', 'title', 'starttime', 'endtime'];
// This controls whether email invites are sent to guests when the event is created in the
// calendar. Note that any changes to the event will cause email invites to be resent.
var SEND_EMAIL_INVITES = false;
// Setting this to true will silently skip rows that have a blank start and end time
// instead of popping up an error dialog.
var SKIP_BLANK_ROWS = true;
// Updating too many events in a short time period triggers an error. These values
// were tested for updating 40 events. Modify these values if you're still seeing errors.
var THROTTLE_THRESHOLD = 10;
var THROTTLE_SLEEP_TIME = 75;
// Adds the custom menu to the active spreadsheet.
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{
name: "Update from Calendar",
functionName: "syncFromCalendar"
}, {
name: "Update to Calendar",
functionName: "syncToCalendar"
}
];
spreadsheet.addMenu('Calendar Sync', menuEntries);
}
// Creates a mapping array between spreadsheet column and event field name
function createIdxMap(row) {
var idxMap = [];
for (var idx = 0; idx < row.length; idx++) {
var fieldFromHdr = row[idx];
for (var titleKey in titleRowMap) {
if (titleRowMap[titleKey] == fieldFromHdr) {
idxMap.push(titleKey);
break;
}
}
if (idxMap.length <= idx) {
// Header field not in map, so add null
idxMap.push(null);
}
}
return idxMap;
}
// Converts a spreadsheet row into an object containing event-related fields
function reformatEvent(row, idxMap, keysToAdd) {
var reformatted = row.reduce(function(event, value, idx) {
if (idxMap[idx] != null) {
event[idxMap[idx]] = value;
}
return event;
}, {});
for (var k in keysToAdd) {
reformatted[keysToAdd[k]] = '';
}
return reformatted;
}
// Converts a calendar event to a psuedo-sheet event.
function convertCalEvent(calEvent) {
convertedEvent = {
'id': calEvent.getId(),
'title': calEvent.getTitle(),
'stand': calEvent.getStand(),
'description': calEvent.getDescription(),
'location': calEvent.getLocation(),
'guests': calEvent.getGuestList().map(function(x) {return x.getEmail();}).join(','),
'color': calEvent.getColor()
};
if (calEvent.isAllDayEvent()) {
convertedEvent.starttime = calEvent.getAllDayStartDate();
var endtime = calEvent.getAllDayEndDate();
if (endtime - convertedEvent.starttime === 24 * 3600 * 1000) {
convertedEvent.endtime = '';
} else {
convertedEvent.endtime = endtime;
if (endtime.getHours() === 0 && endtime.getMinutes() == 0) {
convertedEvent.endtime.setSeconds(endtime.getSeconds() - 1);
}
}
} else {
convertedEvent.starttime = calEvent.getStartTime();
convertedEvent.endtime = calEvent.getEndTime();
}
return convertedEvent;
}
// Converts calendar event into spreadsheet data row
function calEventToSheet(calEvent, idxMap, dataRow) {
convertedEvent = convertCalEvent(calEvent);
for (var idx = 0; idx < idxMap.length; idx++) {
if (idxMap[idx] !== null) {
dataRow[idx] = convertedEvent[idxMap[idx]];
}
}
}
// Returns empty string or time in milliseconds for Date object
function getEndTime(ev) {
return ev.endtime === '' ? '' : ev.endtime.getTime();
}
// Tests whether calendar event matches spreadsheet event
function eventMatches(cev, sev) {
var convertedCalEvent = convertCalEvent(cev);
return convertedCalEvent.title == sev.title &&
convertedCalEvent.description == sev.description &&
convertedCalEvent.stand == sev.stand &&
convertedCalEvent.location == sev.location &&
convertedCalEvent.starttime.toString() == sev.starttime.toString() &&
getEndTime(convertedCalEvent) === getEndTime(sev) &&
convertedCalEvent.guests == sev.guests &&
convertedCalEvent.color == ('' + sev.color);
}
// Determine whether required fields are missing
function areRequiredFieldsMissing(idxMap) {
return requiredFields.some(function(val) {
return idxMap.indexOf(val) < 0;
});
}
// Returns list of fields that aren't in spreadsheet
function missingFields(idxMap) {
return titleRowKeys.filter(function(val) {
return idxMap.indexOf(val) < 0;
});
}
// Set up formats and hide ID column for empty spreadsheet
function setUpSheet(sheet, fieldKeys) {
sheet.getRange(1, fieldKeys.indexOf('starttime') + 1, 999).setNumberFormat(dateFormat);
sheet.getRange(1, fieldKeys.indexOf('endtime') + 1, 999).setNumberFormat(dateFormat);
sheet.hideColumns(fieldKeys.indexOf('id') + 1);
}
// Display error alert
function errorAlert(msg, evt, ridx) {
var ui = SpreadsheetApp.getUi();
if (evt) {
ui.alert('Skipping row: ' + msg + ' in event "' + evt.title + '", row ' + (ridx + 1));
} else {
ui.alert(msg);
}
}
// Updates a calendar event from a sheet event.
function updateEvent(calEvent, sheetEvent){
sheetEvent.sendInvites = SEND_EMAIL_INVITES;
if (sheetEvent.endtime === '') {
calEvent.setAllDayDate(sheetEvent.starttime);
} else {
calEvent.setTime(sheetEvent.starttime, sheetEvent.endtime);
}
calEvent.setTitle(sheetEvent.title);
calEvent.setDescription(sheetEvent.description);
calEvent.setStand(sheetEvent.stand);
calEvent.setLocation(sheetEvent.location);
// Set event color
if (sheetEvent.color > 0 && sheetEvent.color < 12) {
calEvent.setColor('' + sheetEvent.color);
}
var guestCal = calEvent.getGuestList().map(function (x) {
return {
email: x.getEmail(),
added: false
};
});
var sheetGuests = sheetEvent.guests || '';
var guests = sheetGuests.split(',').map(function (x) {
return x ? x.trim() : '';
});
// Check guests that are already invited.
for (var gIx = 0; gIx < guestCal.length; gIx++) {
var index = guests.indexOf(guestCal[gIx].email);
if (index >= 0) {
guestCal[gIx].added = true;
guests.splice(index, 1);
}
}
guests.forEach(function (x) {
if (x) calEvent.addGuest(x);
});
guestCal.forEach(function (x) {
if (!x.added) {
calEvent.removeGuest(x.email);
}
});
}
// Synchronize from calendar to spreadsheet.
function syncFromCalendar() {
// Get calendar and events
var calendar = CalendarApp.getCalendarById(calendarId);
var calEvents = calendar.getEvents(beginDate, endDate);
// Get spreadsheet and data
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var range = sheet.getDataRange();
var data = range.getValues();
var eventFound = new Array(data.length);
// Check if spreadsheet is empty and add a title row
var titleRow = [];
for (var idx = 0; idx < titleRowKeys.length; idx++) {
titleRow.push(titleRowMap[titleRowKeys[idx]]);
}
if (data.length < 1) {
data.push(titleRow);
range = sheet.getRange(1, 1, data.length, data[0].length);
range.setValues(data);
setUpSheet(sheet, titleRowKeys);
}
if (data.length == 1 && data[0].length == 1 && data[0][0] === '') {
data[0] = titleRow;
range = sheet.getRange(1, 1, data.length, data[0].length);
range.setValues(data);
setUpSheet(sheet, titleRowKeys);
}
// Map spreadsheet headers to indices
var idxMap = createIdxMap(data[0]);
var idIdx = idxMap.indexOf('id');
// Verify header has all required fields
if (areRequiredFieldsMissing(idxMap)) {
var reqFieldNames = requiredFields.map(function(x) {return titleRowMap[x];}).join(', ');
errorAlert('Spreadsheet must have ' + reqFieldNames + ' columns');
return;
}
// Array of IDs in the spreadsheet
var sheetEventIds = data.slice(1).map(function(row) {return row[idIdx];});
// Loop through calendar events
for (var cidx = 0; cidx < calEvents.length; cidx++) {
var calEvent = calEvents[cidx];
var calEventId = calEvent.getId();
var ridx = sheetEventIds.indexOf(calEventId) + 1;
if (ridx < 1) {
// Event not found, create it
ridx = data.length;
var newRow = [];
var rowSize = idxMap.length;
while (rowSize--) newRow.push('');
data.push(newRow);
} else {
eventFound[ridx] = true;
}
// Update event in spreadsheet data
calEventToSheet(calEvent, idxMap, data[ridx]);
}
// Remove any data rows not found in the calendar
var rowsDeleted = 0;
for (var idx = eventFound.length - 1; idx > 0; idx--) {
//event doesn't exists and has an event id
if (!eventFound[idx] && sheetEventIds[idx - 1]) {
data.splice(idx, 1);
rowsDeleted++;
}
}
// Save spreadsheet changes
range = sheet.getRange(1, 1, data.length, data[0].length);
range.setValues(data);
if (rowsDeleted > 0) {
sheet.deleteRows(data.length + 1, rowsDeleted);
}
}
// Synchronize from spreadsheet to calendar.
function syncToCalendar() {
// Get calendar and events
var calendar = CalendarApp.getCalendarById(calendarId);
if (!calendar) {
errorAlert('Cannot find calendar. Check instructions for set up.');
}
var calEvents = calendar.getEvents(beginDate, endDate);
var calEventIds = calEvents.map(function(val) {return val.getId();});
// Get spreadsheet and data
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var range = sheet.getDataRange();
var data = range.getValues();
if (data.length < 2) {
errorAlert('Spreadsheet must have a title row and at least one data row');
return;
}
// Map headers to indices
var idxMap = createIdxMap(data[0]);
var idIdx = idxMap.indexOf('id');
var idRange = range.offset(0, idIdx, data.length, 1);
var idData = idRange.getValues()
// Verify header has all required fields
if (areRequiredFieldsMissing(idxMap)) {
var reqFieldNames = requiredFields.map(function(x) {return titleRowMap[x];}).join(', ');
errorAlert('Spreadsheet must have ' + reqFieldNames + ' columns');
return;
}
var keysToAdd = missingFields(idxMap);
// Loop through spreadsheet rows
var numChanges = 0;
var numUpdated = 0;
var changesMade = false;
for (var ridx = 1; ridx < data.length; ridx++) {
var sheetEvent = reformatEvent(data[ridx], idxMap, keysToAdd);
// If enabled, skip rows with blank/invalid start and end times
if (SKIP_BLANK_ROWS && !(sheetEvent.starttime instanceof Date) &&
!(sheetEvent.endtime instanceof Date)) {
continue;
}
// Do some error checking first
if (!sheetEvent.title) {
errorAlert('must have title', sheetEvent, ridx);
continue;
}
if (!(sheetEvent.starttime instanceof Date)) {
errorAlert('start time must be a date/time', sheetEvent, ridx);
continue;
}
if (sheetEvent.endtime !== '') {
if (!(sheetEvent.endtime instanceof Date)) {
errorAlert('end time must be empty or a date/time', sheetEvent, ridx);
continue;
}
if (sheetEvent.endtime < sheetEvent.starttime) {
errorAlert('end time must be after start time for event', sheetEvent, ridx);
continue;
}
}
// Ignore events outside of the begin/end range desired.
if (sheetEvent.starttime > endDate) {
continue;
}
if (sheetEvent.endtime === '') {
if (sheetEvent.starttime < beginDate) {
continue;
}
} else {
if (sheetEvent.endtime < beginDate) {
continue;
}
}
// Determine if spreadsheet event is already in calendar and matches
var addEvent = true;
if (sheetEvent.id) {
var eventIdx = calEventIds.indexOf(sheetEvent.id);
if (eventIdx >= 0) {
calEventIds[eventIdx] = null; // Prevents removing event below
addEvent = false;
var calEvent = calEvents[eventIdx];
if (!eventMatches(calEvent, sheetEvent)) {
// Update the event
updateEvent(calEvent, sheetEvent);
// Maybe throttle updates.
numChanges++;
if (numChanges > THROTTLE_THRESHOLD) {
Utilities.sleep(THROTTLE_SLEEP_TIME);
}
}
}
}
if (addEvent) {
var newEvent;
sheetEvent.sendInvites = SEND_EMAIL_INVITES;
if (sheetEvent.endtime === '') {
newEvent = calendar.createAllDayEvent(sheetEvent.title, sheetEvent.starttime, sheetEvent);
} else {
newEvent = calendar.createEvent(sheetEvent.title, sheetEvent.starttime, sheetEvent.endtime, sheetEvent);
}
// Put event ID back into spreadsheet
idData[ridx][0] = newEvent.getId();
changesMade = true;
// Set event color
if (sheetEvent.color > 0 && sheetEvent.color < 12) {
newEvent.setColor('' + sheetEvent.color);
}
// Maybe throttle updates.
numChanges++;
if (numChanges > THROTTLE_THRESHOLD) {
Utilities.sleep(THROTTLE_SLEEP_TIME);
}
}
}
// Save spreadsheet changes
if (changesMade) {
idRange.setValues(idData);
}
// Remove any calendar events not found in the spreadsheet
var numToRemove = calEventIds.reduce(function(prevVal, curVal) {
if (curVal !== null) {
prevVal++;
}
return prevVal;
}, 0);
if (numToRemove > 0) {
var ui = SpreadsheetApp.getUi();
var response = ui.Button.YES;
if (numToRemove > numUpdated) {
response = ui.alert('Delete ' + numToRemove + ' calendar event(s) not found in spreadsheet?',
ui.ButtonSet.YES_NO);
}
if (response == ui.Button.YES) {
calEventIds.forEach(function(id, idx) {
if (id != null) {
calEvents[idx].deleteEvent();
Utilities.sleep(20);
}
});
}
}
Logger.log('Updated %s calendar events', numChanges);
}
// Set up a trigger to automatically update the calendar when the spreadsheet is
// modified. See the instructions for how to use this.
function createSpreadsheetEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('syncToCalendar')
.forSpreadsheet(ss)
.onEdit()
.create();
}
// Delete the trigger. Use this to stop automatically updating the calendar.
function deleteTrigger() {
// Loop over all triggers.
var allTriggers = ScriptApp.getProjectTriggers();
for (var idx = 0; idx < allTriggers.length; idx++) {
if (allTriggers[idx].getHandlerFunction() === 'syncToCalendar') {
ScriptApp.deleteTrigger(allTriggers[idx]);
}
}
}
Related
I have a code that generates a string of 6 characters, this code generates 3 different characters and 3-digital number in the column
I would like this code to check in this column cell by cell whether the values generated by this generator are unique (they are not repeated). If he finds repetitive value in the cell, I want to generate the value for the cell again.
Thank you in advance for your help
// index.gs
function onOpen() {
SpreadsheetApp.getUi().createMenu("Indexe Master Babe").addItem('Insert that SHIT', 'insertData').addToUi(); // Or DocumentApp or SlidesApp or FormApp.
}
indexService.gs
function insertData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var coordinates = getCoordinatesToStart(sheet);
if (coordinates) {
var indexArray = prepareIndexArray(); //geting two demension array of indexes
sheet.getRange(coordinates.row, coordinates.column, indexArray.length, 1).setValues(indexArray); // get range from marked cell to array.length cells
} else {
showAlert();
}
}
function getCoordinatesToStart(sheet) {
var activeCell = sheet.getActiveCell();
var row = activeCell.getRow();
var column = activeCell.getColumn();
if (isZeroSafe(row) || isZeroSafe(column)) {
return false;
} else {
return {
row: row,
column: column
};
}
}
function prepareIndexArray() {
var array = [];
var takenStrings = [];
var index = "";
var blankSpace = 0;
for (var i = 1; i <= 1000; i++) {
var indexString = getRandomIndexString();
while (takenStrings.indexOf(indexString) !== -1) {
var indexString = getRandomIndexString();
}
takenStrings.push(indexString);
index = indexString + getIndexNumber(i);
array.push([index]);
};
return array;
}
function getRandomIndexString() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (var i = 0; i < 3; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
};
function getIndexNumber(i) {
var indexNumber = "";
var blankSpace = 3 - getIntegerLength(i);
for (var zeroCount = 0; zeroCount < blankSpace; zeroCount++) {
indexNumber += "0";
};
indexNumber += i;
return indexNumber;
}
function showAlert() {
var ui = SpreadsheetApp.getUi();
var result = ui.alert(
'Please click on any Cell',
ui.ButtonSet.OK);
}
// utilities.gs
function isNull(input) {
return input === null;
}
function isUndefinded(input) {
return input === undefined;
}
function isZero(input) {
return input === 0;
}
function isZeroSafe(input) {
return isUndefinded(input) || isNull(input) || isZero(input)
}
function getIntegerLength(number) {
var stringHelper = number + "";
return stringHelper.length;
}
I am developing an extension, which is about fetching the list of topics from the server and find if those topics match with the currently opened Gmail messages or not, if found then highlight that topic otherwise don't.
But if already 6 topics are matched, then it should not check or highlight other topics. This one is working but now I have a problem like if I go back from the current message and again come to that message then highlight won't be shown. Also if I open another message, the highlight is not done.
If I remove the code of counter check from the following snippet it works but this will highlight all the topics that are matched instead of just max 6 topics.
var count = 1;
var highlightAllWords = function(topics) {
Object.keys(topics.topics).forEach(function(topic) {
if (count <= 6) {
highlightTopic(topic);
if (topic !== null || !topic.length) {
count += 1;
}
}
});
};
// init highlight CSS
var ruleExistenceDict = {};
var sheet = (function() {
var style = document.createElement('style');
style.appendChild(document.createTextNode('')); // WebKit hack ##
document.head.appendChild(style);
return style.sheet;
})();
var topicData = {
topics: {
hostname: 4,
cto: 19,
aws: 382,
its: 26,
repo: 15,
unsubscribe: 65,
bitbucket: 313,
having: 28,
devops: 414,
frontend: 25,
stepin: 105,
username: 121,
deployed: 24,
vimeo: 460,
gmail: 156,
rds: 486,
clicked: 9,
lai: 850
}
};
function fetchTopics() {
// api call will be done here but for now its done with dummy object
searchPage(topicData);
}
function searchPage(topics) {
highlightAllWords(topics);
}
var count = 1;
var highlightAllWords = function(topics) {
Object.keys(topics.topics).forEach(function(topic) {
if (count <= 6) {
highlightTopic(topic);
if (topic !== null || !topic.length) {
count += 1;
}
}
});
};
function highlightTopic(topic) {
// let found = 0;
let isCompleted = false;
if (topic == null || topic.length === 0) return;
var topicRegex = new RegExp(topic, 'gi');
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
var result = NodeFilter.FILTER_SKIP;
if (topicRegex.test(node.nodeValue)) {
// found += 1;
// if (found <= 6) {
result = NodeFilter.FILTER_ACCEPT;
return result;
// }
}
}
},
false
);
var skipTagName = {
NOSCRIPT: true,
SCRIPT: true,
STYLE: true
};
var nodeList = [];
// let count = 1;
console.log('count near nextNode', count);
while (treeWalker.nextNode()) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
// count = count + 1;
// console.log('count:' + count);
}
}
nodeList.forEach(function(n) {
var rangeList = [];
// find sub-string ranges
var startingIndex = 0;
do {
// console.log(word, startingIndex, n.parentNode, n.textContent);
startingIndex = n.textContent.indexOf(topic, startingIndex + 1);
if (startingIndex !== -1) {
var topicRange = document.createRange();
topicRange.setStart(n, startingIndex);
topicRange.setEnd(n, startingIndex + topic.length);
rangeList.push(topicRange);
}
} while (startingIndex !== -1);
// highlight all ranges
rangeList.forEach(function(r) {
highlightRange(r);
});
});
}
var highlightRange = function(range) {
const bgColorCode = '#000000';
var anchor = document.createElement('A');
var selectorName = (anchor.className = 'highlighted_text');
anchor.classList.add('highlighted_text');
if (!ruleExistenceDict[bgColorCode]) {
sheet.insertRule(
[
'.',
selectorName,
' { background: #',
bgColorCode,
' !important; }'
].join(''),
0
);
ruleExistenceDict[bgColorCode] = true;
console.log(sheet);
}
anchor.appendChild(range.extractContents());
anchor.href = `https://app.com/profile/topics/${range.extractContents()}`;
range.insertNode(anchor);
};
Here is the full code:
https://gist.github.com/MilanRgm/5d6b9861be1326ba8b049ccfb6c3b376
You should declare the count variable inside the function, so that every time you refresh the page, the count will start from 1 again. Please update your code as follows:
var highlightAllWords = function(topics) {
var count = 1;
Object.keys(topics.topics).forEach(function(topic) {
if (count <= 6) {
highlightTopic(topic);
if (topic !== null || !topic.length) {
count += 1;
}
}
});
};
I am developing an extension which is about fetching the list of topics from the server and find if those topics match with the currently opened gmail messages or not, if found then highlight that topic otherwise don't. But if already 6 topics are matched then it should not check or highlight other topics.
I have used the treewalker for crawling the gmail contents so the matched content will get highlighted as follow
function searchPage(topics) {
highlightAllWords(topics);
}
var highlightAllWords = function(topics) {
Object.keys(topics.topics).forEach(function(topic) {
highlightTopic(topic);
})
}
function highlightTopic(topic) {
let found = 0;
if (topic == null || topic.length === 0) return;
var topicRegex = new RegExp(topic, 'gi');
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
var result = NodeFilter.FILTER_SKIP;
if (topicRegex.test(node.nodeValue)) {
found += 1
console.log('found', found);
if (found <= 6) {
console.log('foound less than 6', found)
result = NodeFilter.FILTER_ACCEPT
return result;
}
};
}
}, false
)
var skipTagName = {
"NOSCRIPT": true,
"SCRIPT": true,
"STYLE": true
}
var nodeList = [];
while (treeWalker.nextNode()) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
}
}
nodeList.forEach(function (n) {
var rangeList = [];
// find sub-string ranges
var startingIndex = 0;
do {
// console.log(word, startingIndex, n.parentNode, n.textContent);
startingIndex = n.textContent.indexOf(topic, startingIndex + 1);
if (startingIndex !== -1) {
var topicRange = document.createRange();
topicRange.setStart(n, startingIndex);
topicRange.setEnd(n, startingIndex + topic.length);
rangeList.push(topicRange);
}
} while (startingIndex !== -1);
// highlight all ranges
rangeList.forEach(function (r) {
highlightRange(r);
});
});
}
// highlight the keyword by surround it by `i`
var highlightRange = function (range) {
const bgColorCode = '#000000';
var anchor = document.createElement("A");
var selectorName = anchor.className = "highlighted_text";
anchor.classList.add("highlighted_text");
// range.surroundContents(iNode) will throw exception if word across multi tag
if (!ruleExistenceDict[bgColorCode]) {
sheet.insertRule([".", selectorName, " { background: #", bgColorCode, " !important; }"].join(""), 0);
ruleExistenceDict[bgColorCode] = true;
console.log(sheet);
}
anchor.appendChild(range.extractContents());
anchor.href = `https://google.com/?search=${
range.extractContents()
}`;
range.insertNode(anchor);
};
It highlights the matched content in gmail messages but does highlights more than 6 contents. I have taken the screenshot and it is something like this
update after counter increased and checked in treewalker.nextnode()
There are two syntax errors in your code. You are missing semicolon at end of this statement
found += 1;
Secondly, there is also one extra ";" at the end of function(node).
And You can add the counter check in the following code snippet as
var count=1;
while (treeWalker.nextNode() && count<=6) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
count=count+1;
}
}
So, the final script for the function highlightTopic(topic) will look like
function highlightTopic(topic) {
let found = 0;
if (topic == null || topic.length === 0) return;
var topicRegex = new RegExp(topic, 'gi');
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
var result = NodeFilter.FILTER_SKIP;
if (topicRegex.test(node.nodeValue)) {
found += 1;
console.log('found', found);
if (found <= 6) {
console.log('foound less than 6', found)
result = NodeFilter.FILTER_ACCEPT
return result;
}
}
}
}, false
)
var skipTagName = {
"NOSCRIPT": true,
"SCRIPT": true,
"STYLE": true
}
var nodeList = [];
var count=1;
while (treeWalker.nextNode() && count<=6) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
count=count+1;
console.log('count:'+count);
}
}
nodeList.forEach(function (n) {
var rangeList = [];
// find sub-string ranges
var startingIndex = 0;
do {
// console.log(word, startingIndex, n.parentNode, n.textContent);
startingIndex = n.textContent.indexOf(topic, startingIndex + 1);
if (startingIndex !== -1) {
var topicRange = document.createRange();
topicRange.setStart(n, startingIndex);
topicRange.setEnd(n, startingIndex + topic.length);
rangeList.push(topicRange);
}
} while (startingIndex !== -1);
// highlight all ranges
rangeList.forEach(function (r) {
highlightRange(r);
});
});
}
Please update me if it worked.
Thanks
Edit:
And also update the function highlightAllWords as follow:
var highlightAllWords = function(topics) {
var count=1;
Object.keys(topics.topics).forEach(function(topic) {
if(count<=6){
highlightTopic(topic);
console.log('counter:'+count);
if (topic != null && topic.length != 0)
count=count+1;
}
})
}
I've two tables which has exactly same columns. These tables are placed besides each other. Currently they are sorted separately. I want them to be sorted together. i.e. when I click on first column header of table 1 both tables should be sorted as if both are a single table.
This is the .js which I am using
function SortableTable (tableEl) {
this.tbody = tableEl.getElementsByTagName('tbody');
this.thead = tableEl.getElementsByTagName('thead');
this.tfoot = tableEl.getElementsByTagName('tfoot');
this.getInnerText = function (el) {
if (typeof(el.textContent) != 'undefined') return el.textContent;
if (typeof(el.innerText) != 'undefined') return el.innerText;
if (typeof(el.innerHTML) == 'string') return el.innerHTML.replace(/<[^<>]+>/g,'');
}
this.getParent = function (el, pTagName) {
if (el == null) return null;
else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())
return el;
else
return this.getParent(el.parentNode, pTagName);
}
this.sort = function (cell) {
var column = cell.cellIndex;
var itm = this.getInnerText(this.tbody[0].rows[1].cells[column]);
var sortfn = this.sortCaseInsensitive;
if (itm.match(/\d\d[-]+\d\d[-]+\d\d\d\d/)) sortfn = this.sortDate; // date format mm-dd-yyyy
if (itm.replace(/^\s+|\s+$/g,"").match(/^[\d\.]+$/)) sortfn = this.sortNumeric;
this.sortColumnIndex = column;
var newRows = new Array();
for (j = 0; j < this.tbody[0].rows.length; j++) {
newRows[j] = this.tbody[0].rows[j];
}
newRows.sort(sortfn);
if (cell.getAttribute("sortdir") == 'down') {
newRows.reverse();
cell.setAttribute('sortdir','up');
} else {
cell.setAttribute('sortdir','down');
}
for (i=0;i<newRows.length;i++) {
this.tbody[0].appendChild(newRows[i]);
}
}
this.sortCaseInsensitive = function(a,b) {
aa = thisObject.getInnerText(a.cells[thisObject.sortColumnIndex]).toLowerCase();
bb = thisObject.getInnerText(b.cells[thisObject.sortColumnIndex]).toLowerCase();
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
}
this.sortDate = function(a,b) {
aa = thisObject.getInnerText(a.cells[thisObject.sortColumnIndex]);
bb = thisObject.getInnerText(b.cells[thisObject.sortColumnIndex]);
date1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
date2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
if (date1==date2) return 0;
if (date1<date2) return -1;
return 1;
}
this.sortNumeric = function(a,b) {
aa = parseFloat(thisObject.getInnerText(a.cells[thisObject.sortColumnIndex]));
if (isNaN(aa)) aa = 0;
bb = parseFloat(thisObject.getInnerText(b.cells[thisObject.sortColumnIndex]));
if (isNaN(bb)) bb = 0;
return aa-bb;
}
// define variables
var thisObject = this;
var sortSection = this.thead;
// constructor actions
if (!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length > 0)) return;
if (sortSection && sortSection[0].rows && sortSection[0].rows.length > 0) {
var sortRow = sortSection[0].rows[0];
} else {
return;
}
for (var i=0; i<sortRow.cells.length; i++) {
sortRow.cells[i].sTable = this;
sortRow.cells[i].onclick = function () {
this.sTable.sort(this);
return false;
}
}
}
There are just two columns and are likely to remain the same. Some ideas would be really appreciated.
I don't quite follow the HTML you have (since you didn't include any HTML), so I created a simple version myself here and implemented a sort across tables. The basic idea is that it collects all the data out of the tables into a single javascript data structure, sorts the javascript data structure and then puts all the data back into the tables.
This makes the sorting much simpler and the only places you have to deal with the complexities of multiple tables is when retrieving the data and then putting it back into the tables. You can see it work here: http://jsfiddle.net/jfriend00/Z5ywA/. Just click on a column header to see both tables sorted by that column.
The code is this:
function sortTables(colNum) {
var t1 = document.getElementById("table1");
var t2 = document.getElementById("table2");
var data = [], sortFn;
function sortAlpha(a, b) {
// deal with empty strings
var aa = a[colNum].data, bb = b[colNum].data;
if (!aa) {
return(!bb ? 0 : -1);
} else if (!bb){
return(1);
} else {
return(aa.localeCompare(bb));
}
}
function sortNumeric(a, b) {
// deal with possibly empty strings
var aa = a[colNum].data, bb = b[colNum].data;
if (typeof aa == "string" || typeof bb == "string") {
return(sortAlpha(a, b));
} else {
return(aa - bb);
}
}
// get the data
function getData(table) {
var cells, rowData, matches, item, cellData;
var rows = table.getElementsByTagName("tbody")[0].getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
rowData = [];
cells = rows[i].getElementsByTagName("td");
for (var j = 0; j < cells.length; j++) {
// add each cell in the row to the rowData data structure
item = {};
item.origStr = cells[j].textContent || cells[j].innerText;
cellData = item.origStr.replace(/^\s*|\s*$/g, "").toLowerCase();
if (cellData.match(/^[\d\.]+$/)) {
cellData= parseFloat(cellData);
} else if (matches = cellData.match(/^(\d+)-(\d+)-(\d+)$/)) {
cellData= new Date(
parseInt(matches[3], 10),
parseInt(matches[1], 10) - 1,
parseInt(matches[2], 10)
);
}
item.data = cellData;
// determine the type of sort based on the first cell we find
// with data in the column that we're sorting
if (!sortFn && item.data !== "" && j == colNum) {
if (typeof item.data == "number" || typeof item.data == "object") {
sortFn = sortNumeric;
} else {
sortFn = sortAlpha;
}
}
rowData.push(item);
}
// add each row to the overall data structure
data.push(rowData);
}
}
// put data back into the tables
function putData(table) {
var cells, rowData, item;
var rows = table.getElementsByTagName("tbody")[0].getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
cells = rows[i].getElementsByTagName("td");
for (var j = 0; j < cells.length; j++) {
// add each cell in the row to the rowData data structure
cells[j].innerHTML = data[0][j].origStr;
}
// remove this row
data.shift();
}
}
getData(t1);
getData(t2);
data.sort(sortFn);
putData(t1);
putData(t2);
return(false);
}
I need to perform a pagination where I need to display 5 rows every time. First 5 rows are displayed. On the second click, the next 5, the third rows no. 11-15, and so on. I need to display even if at least one row is there (after n clicks where totalRows mod 5 is less than 5). I do not get this.
function rightArrow()
{
pageCount = document.getElementById('pgCnt').value; //totalRows / 5
totCount = document.getElementById('totCnt').value; //totalRows
currPage = tempRows - pageCount + 2; //current set of rows
document.getElementById('tempPage').value = tempPage;
var m = totCount%5;
if(pageCount != tempRows)
m = 5;
else
{ m = (totCount % 5) - 1;
document.getElementById('rightArr').disabled = true;
}
document.getElementById('pgCnt').value = document.getElementById('pgCnt').value - 1;
for(i = 0; i < m;i++)
{
$.ajax({
type: "POST",
url: "getEODRow.php",
data: "page=" + currPage + "&row=" + i,
success: function(html)
{
var row = document.getElementById('chRecommend').insertRow(0);
temp = html.split(",");
for(j = 0; j < 9; j++)
{
str = temp[j].replace("\"","");
str = temp[j].replace("\"",'');
str = temp[j].replace("[",'');
col = row.insertCell(j);
col.innerHTML = str;
}
}
});
}
currPage++;
}
I realize this question is pretty much dead, but I came across it looking for something else, and I figured I'd toss an answer at it. Since it's not a critical issue, I went ahead and reworked everything you've got here plus a lot of code that you didn't choose to include.
// App namespace
var MyApp = {Paging: {}};
// Paging Controller
MyApp.Paging.Controller = function(){ this.init.apply(this,arguments);};
MyApp.Paging.Controller.prototype =
{
// Initializer gets everything hooked up
init: function()
{
$.log("Initializing Paging Controller.")
// Get all the nodes we'll need
this.totCntNode = document.getElementById("totCnt");
this.pgCntNode = document.getElementById("pgCnt");
this.currMinNode = document.getElementById("currMin");
this.currMaxNode = document.getElementById("currMax");
this.prevPageButton = document.getElementById("prevPageButton");
this.nextPageButton = document.getElementById("nextPageButton");
this.pageContainer = document.getElementById("pageOfItems");
// Mimic table's .insertRow to make row adding easy
this.pageContainer.insertRow = function() {
var row = document.createElement("div");
row.className = "row clearfix";
for (var i = 0; i < MyApp.Paging.Model.itemsPerRow; i++)
{
var cell = document.createElement("span");
row.appendChild(cell);
}
this.appendChild(row);
return row;
};
// Attach listeners to the next and previous buttons
this.prevPageButton.onclick = this.showPrevPage.bind(this);
this.nextPageButton.onclick = this.showNextPage.bind(this);
// Update the display for the first time
this.updatePageInfo();
},
// Run this whenever the model has changed and needs to update the display
updatePageInfo: function()
{
// Get info about what page we're on
var currentPage = MyApp.Paging.Model.currentPage,
totalPages = MyApp.Paging.Model.getTotalPages(),
itemCount = MyApp.Paging.Model.itemCount,
pageSize = MyApp.Paging.Model.getPageSize(),
rowsPerPage = MyApp.Paging.Model.rowsPerPage,
rowsOnThisPage = Math.ceil(MyApp.Paging.Model.getItemsOnPage(currentPage)/MyApp.Paging.Model.itemsPerRow);
// Clear out previous page data
while (this.pageContainer.children.length > 0)
{
this.pageContainer.removeChild(this.pageContainer.children[0]);
}
// Add space for the new page data
for (var rowInd = 0; rowInd < rowsOnThisPage ; rowInd++)
{
this.pageContainer.insertRow();
}
$.log("Loading Page " + currentPage + ".");
// Request the data via ajax for each row
for(var i = 0; i < rowsOnThisPage ; i++)
{
$.ajax({
type: MyApp.Paging.Model.queryType,
url: MyApp.Paging.Model.queryURI,
data: MyApp.Paging.Model.getQueryData(currentPage, i),
success: function(pageNum, rowNum, result)
{
// Don't serve data from the wrong page
if (pageNum !== MyApp.Paging.Model.currentPage) return;
// When we get the data back, put it into the correct container
// regardless of when it was received
var row = this.pageContainer.children[rowNum],
temp = result.replace(/[\["]/g,"").split(","),
str = "";
for(var j = 0; j < temp.length; j++)
{
row.children[j].innerHTML = temp[j];
}
}.bind(this, currentPage, i)
});
}
// Update the informational bits under the items
this.totCntNode.textContent = itemCount;
this.pgCntNode.textContent = totalPages;
var min = currentPage * (pageSize ) + 1;
this.currMinNode.textContent = min;
this.currMaxNode.textContent = Math.min(min + pageSize - 1, itemCount);
// Disable the prev page button if there are no previous pages
if (currentPage <= 0)
{
if (this.prevPageButton.className.indexOf("disabled") < 0)
{
this.prevPageButton.className += " disabled";
}
}
// Enable the prev page button if there are previous pages
else
{
if (this.prevPageButton.className.indexOf("disabled") > -1)
{
this.prevPageButton.className = this.prevPageButton.className.replace(/(?:^|\s+)disabled(?!\S)/g, "");
}
}
// Disable the next page button if there are next pages
if (currentPage + 1 >= totalPages)
{
if (this.nextPageButton.className.indexOf("disabled") < 0)
{
this.nextPageButton.className += " disabled";
}
}
// Enable the next page button if there are next pages
else
{
if (this.nextPageButton.className.indexOf("disabled") > -1)
{
this.nextPageButton.className = this.nextPageButton.className.replace(/(?:^|\s+)disabled(?!\S)/g, "");
}
}
},
// This is called when the next page button is clicked.
showNextPage: function()
{
if (MyApp.Paging.Model.currentPage + 1 >= MyApp.Paging.Model.getTotalPages())
{
// Shouldn't have been able to activate this anyway
}
else
{
MyApp.Paging.Model.currentPage++;
this.updatePageInfo();
}
return false;
},
// This is called when the prev page button is clicked
showPrevPage: function()
{
if (MyApp.Paging.Model.currentPage <= 0)
{
// Shouldn't have been able to activate this anyway
}
else
{
MyApp.Paging.Model.currentPage--;
this.updatePageInfo();
}
return false;
}
};
// I typically expect an object like this to be created by the server and dropped dynamically onto the page
MyApp.Paging.Model = {
itemCount: 140,
itemsPerRow: 9,
rowsPerPage: 5,
currentPage: 0,
queryType: "POST",
queryURI: "getEODRow.php",
queryDataFormat: "page={itemPage}&row={itemRow}",
getTotalPages: function() {
with(MyApp.Paging.Model) {
return Math.ceil(itemCount/(itemsPerRow*rowsPerPage));
}
},
getPageSize: function() {
with(MyApp.Paging.Model) {
return itemsPerRow * rowsPerPage;
}
},
getItemsOnPage: function(pageNum) {
with(MyApp.Paging.Model) {
return Math.min(((pageNum+1) * getPageSize()), itemCount) - pageNum*getPageSize();
}
},
getItemsInRow: function(pageNum, rowNum) {
with(MyApp.Paging.Model) {
var onPage = getItemsOnPage(pageNum);
return Math.min((rowNum+1)*itemsPerRow, onPage) - rowNum*itemsPerRow;
}
},
getQueryData: function(itemPage, itemRow) {
with(MyApp.Paging.Model) {
var data = queryDataFormat;
data = data.replace(/{itemPage}/gi, itemPage);
data = data.replace(/{itemRow}/gi, itemRow);
return data;
}
}
};
So, whenever the page is loaded, with an onload handler or whatever, you would create and hang onto a singleton instance of the Paging Controller.
MyApp.Paging.Controller.instance = new MyApp.Paging.Controller();
This should handle issues with Ajax calls returning out of order as well as partial pages of data and partial rows within those pages.
Demo: http://jsfiddle.net/2UJH8/8/