How to autofill Google form with random answears using JS - javascript

I want to randomly fill a 100 google forms using JS. Is any way to do it?
There is example google form.
I couldn't find anything on stackoverflow or web, only python or java solutions. But I want to do it in javascript if it is possible of course.

Here is a dirty script, which could be a starting point. It only works with the specific form you provided as an example. It uses document.querySelector to target the form elements.
As soon as you'll open the form, it will fill it, submit it, go back to it, submit it, over and over.
To use it:
Install the TamperMonkey extension in Google Chrome
Click on the icon that appeared in your browser, select "Dashboard"
Create a new script, and replace all the content with the code below
Ctrl + S to save
Open the form in a tab and watch it do the work
Code:
// ==UserScript==
// #name GoogleForm Spammer
// #namespace http://tampermonkey.net/
// #version 0.1
// #description Spam a Google Form
// #author You
// #match https://docs.google.com/forms/*
// #grant unsafeWindow
// ==/UserScript==
(function() {
window.addEventListener('load', function() {
if (window.location.pathname.indexOf('/forms/d') === 0) { // If we're on the form page
submitRandomForm();
} else if (window.location.pathname.indexOf('/forms/u') === 0) { // If we're on the "submitted" page
goBackToForm();
}
function submitRandomForm() {
// Size
var radios = document.querySelectorAll(".appsMaterialWizToggleRadiogroupRadioButtonContainer"),
radioIndex = Math.floor(Math.random() * radios.length);
radios[radioIndex].click();
// Print
var checkboxes = document.querySelectorAll(".appsMaterialWizTogglePapercheckboxCheckbox"),
checkboxIndex = Math.floor(Math.random() * checkboxes.length);
checkboxes[checkboxIndex].click();
// Age (between 16 and 45)
var age = Math.floor(Math.random() * 30) + 16;
document.querySelector(".quantumWizTextinputPaperinputInput").value = age;
// Submit
document.querySelector(".freebirdFormviewerViewCenteredContent .appsMaterialWizButtonPaperbuttonLabel").click();
}
function goBackToForm() {
window.location.href = 'https://docs.google.com/forms/d/e/1FAIpQLSd7GueJGytOiQpkhQzo_dCU0oWwbk3L1htKblBO1m14VHSpHw/viewform';
}
});
})();
And here is a little cleaner way. You declare the form URL at the top, the form fields, and for some of them, a function which will return a random value according to your needs.
To try this one out, save that script, and try accessing this form:
// ==UserScript==
// #name GoogleForm Spammer
// #namespace http://tampermonkey.net/
// #version 0.1
// #description Spam a Google Form
// #author You
// #match https://docs.google.com/forms/*
// #grant none
// ==/UserScript==
var formUrl = 'https://docs.google.com/forms/d/e/1FAIpQLSdQ9iT7isDU8IIbyg-wowB-9HGzyq-xu2NyzsOeG0j8fhytmA/viewform';
var formSchema = [
{type: 'radio'}, // A
{type: 'radio'}, // B
{type: 'checkbox'}, // C
{type: 'checkbox'}, // D
{type: 'short_text', func: generateAnswerE }, // E
{type: 'paragraph', func: generateParagraph }, // F
];
function generateAnswerE() {
// Let's say we want a random number
return Math.floor(Math.random() * 30) + 16;
}
function generateParagraph() {
// Just for the example
return "Hello world";
}
(function() {
window.addEventListener('load', function() {
if (window.location.pathname.indexOf('/forms/d') === 0) { // If we're on the form page
submitRandomForm();
} else if (window.location.pathname.indexOf('/forms/u') === 0) { // If we're on the "submitted" page
window.location.href = formUrl;
}
function submitRandomForm() {
var formItems = document.querySelectorAll('.freebirdFormviewerViewItemsItemItem');
for (var i = 0; i < formSchema.length; i++) {
var field = formSchema[i],
item = formItems[i];
switch(field.type) {
case 'radio':
var radios = item.querySelectorAll(".appsMaterialWizToggleRadiogroupRadioButtonContainer"),
radioIndex = Math.floor(Math.random() * radios.length);
radios[radioIndex].click();
break;
case 'checkbox':
var checkboxes = item.querySelectorAll(".appsMaterialWizTogglePapercheckboxCheckbox"),
checkboxIndex = Math.floor(Math.random() * checkboxes.length);
checkboxes[checkboxIndex].click();
break;
case 'short_text':
item.querySelector(".quantumWizTextinputPaperinputInput").value = field.func();
break;
case 'paragraph':
item.querySelector(".quantumWizTextinputPapertextareaInput").value = field.func();
break;
}
}
// Submit
document.querySelector(".freebirdFormviewerViewCenteredContent .appsMaterialWizButtonPaperbuttonLabel").click();
}
});
})();

Related

How to get the first revision without having a delete button?

<div class="right modal-footer">
<a class="modal-action waves-effect btn-flat left" ng-switch-when="true" ng-click="delete()">Delete</a>
<a class="modal-action waves-effect btn-flat" ng-click="close()">Cancel</a>
<a class="modal-action waves-effect btn-flat" ng-click="save()">Save</a>
</div>
</div>
The first revision or revision A should not have a delete button. Every other revision needs to have one, so that's revision B, C, etc. Does anyone have any ideas on how i can do this?
The above part is the footer for the summary revision dialog. It includes the action buttons of save, cancel, and delete. Delete button is the main focus here.
The below part is the JavaScript controller code that tells what the button to do. When the revision is opened it will display the original revisions details along with which revision this is. the close function is simple, just simply closing the dialog without saving the entered and changed information. The save function is also pretty simple, saves the entered data and will show the changes when the save button is clicked on. The Delete function will delete the current revision and move back the the revision before it, so for example, deleting revision C will display revision B. But what I am trying to do is hide the delete button on Revision A(the first revision) so it will not delete the initial revision and keep the delete button display for any other revision.
Thank you developers for your help.
angular.module('Comet').controller('RevisionEditController', function ($scope, $rootScope, $objectExtensions, $odataModel, $validation, $toast, ProposalsService, ErrorsService) {
const DIALOG_SEL = '#revisionEditDialog';
$scope.originalRevision = null;
$scope.revision = null;
/**
* Opens the dialog.
* #param {number} proposal - The proposal that the revision is for.
* #param {object} [revision] - The existing revision to edit. Null when creating a new revision.
*/
$scope.open = function (proposal, revision) {
$scope.originalRevision = new $odataModel.ProposalDetail(revision);
$scope.revision = new $odataModel.ProposalDetail(revision);
$scope.revision.rev = revision ? revision.rev : getNextRevision(proposal.proposalDetails);
$scope.revision.proposal = {
id: proposal.id
};
$(DIALOG_SEL).modal('open');
};
/**
* Closes the dialog.
*/
$scope.close = function () {
$(DIALOG_SEL).modal('close');
};
/**
* Saves the revision.
*/
$scope.save = function () {
if ($validation.validate($scope.revisionEditForm)) {
$rootScope.dataReady = false;
if ($scope.revision.id) {
ProposalsService.editProposalDetail($scope.revision.proposal.id, $scope.revision.id, $scope.originalRevision, $scope.revision)
.then(onSaveSuccess, onSaveFailure);
} else {
ProposalsService.addProposalDetail($scope.revision.proposal.id, $scope.revision)
.then(onSaveSuccess, onSaveFailure);
}
}
};
/**
* Deletes the revision.
*/
$scope.delete = function () {
ProposalsService.deleteProposalDetail($scope.revision.proposal.id, $scope.revision.id)
.then(onDeleteSuccess, onDeleteFailure);
};
/**
* Calls the revision updated callback and closes the dialog.
* #param {object} updatedRevision - The updated revision.
*/
function onSaveSuccess(updatedRevision) {
$scope.$ctrl.onRevisionUpdated({ revision: updatedRevision });
$scope.close();
$rootScope.dataReady = true;
}
/**
* Displays an error message and logs the exception.
* #param {object} ex - The exception to log.
*/
function onSaveFailure(ex) {
$toast.error('There was an error saving the revision. Please try again.');
ErrorsService.log(ex);
$rootScope.dataReady = true;
}
/**
* Calls the revision deleted callback and closes the dialog.
* #param {number} id - The ID of the revision that was deleted.
*/
function onDeleteSuccess(id) {
$scope.$ctrl.onRevisionDeleted({ id: id });
$scope.close();
$rootScope.dataReady = true;
}
/**
* Displays an error message and logs the exception.
* #param {object} ex - The exception to log.
*/
function onDeleteFailure(ex) {
$toast.error('There was an error deleting the revision. Please try again.');
ErrorsService.log(ex);
$rootScope.dataReady = true;
}
/**
* Gets the next revision number.
* #param {object[]} revisions - The previous revisions.
* #returns {string} The next revision number.
*/
function getNextRevision(revisions) {
var nextRevision = '';
var latestRevision = revisions
.sort(function (a, b) {
var order = 0;
if (a.id > b.id)
order = -1;
else if (a.id < b.id)
order = 1;
return order;
})[0];
if (latestRevision) {
nextRevision = latestRevision.rev ? '' : 'A';
var increment = true;
for (var idx = latestRevision.rev.length - 1; idx >= 0 && increment; idx--) {
var currLetter = latestRevision.rev[idx].charCodeAt(0);
if (currLetter == 90) {
nextRevision = nextRevision + 'A';
} else {
increment = false;
nextRevision = String.fromCharCode(currLetter + 1) + nextRevision;
}
}
if (nextRevision.length < latestRevision.rev.length) {
nextRevision = latestRevision.substring(0, nextRevision.length - 1) + nextRevision;
}
}
return nextRevision;
}
I assume that there is a loop where you iterate the revisions, use the loop's index to check with ngIf if the button should be shown.

getting the images that load in a page dynamically

I'd like to get all the images in a facebook newsfeed as it is loaded. I'm running a tampermonkey script. I'm having a few problems:
the end result is including in the images with urls that I'm excluding (with facebook static urls).
it only includes some of the images in the newsfeed, and if i scroll down it does not re-evaluate its outputs. This is probably because of the load function, but how can I make it a dynamic load instead? Where could I add a function like .scroll for example?
I'm using jquery to run the functions only when the page is loaded. Should I do something else instead?
Below is some part of the code:
// ==UserScript==
// #name Accountability
// #namespace http://tampermonkey.net/
// #include https://www.facebook.com/*
// #include http*://*.facebook.com/*
// #exclude htt*://*static*.facebook.com*
// #version 0.1
// #description
// #author You
// #match http://tampermonkey.net/scripts.php
// #grant none
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// ==/UserScript==
/* jshint -W097 */
'use strict';
window.addEventListener('load', function() {
var all_images = document.evaluate('//img[#src]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var newsfeed = document.evaluate('//*[contains(#id, topnews_main_stream_408239535924329)]', document, null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var imgSrcs = [];
for (var i=0; i < all_images.snapshotLength; i++){
var this_image = all_images.snapshotItem(i);
var src = this_image.src;
if(src.indexOf('static') > -1){
continue;
}
if(src.indexOf('external') > -1){
continue;
}
imgSrcs.push(src);
console.log(this_image.src);
this_image.addEventListener("click", my_func, false);
}
for (var i=0; i < newsfeed.snapshotLength; i++){
var this_news = newsfeed.snapshotItem(i);
var src = this_news.src;
if(this_news.children.length>0){
}
if(Object.getOwnPropertyNames(this_news)[0]== '_startTime'){
var x = this_news.onreadystatechange();
}
this_news.addEventListener("click", my_func, false);
this_news.addEventListener("mouseover", my_func, false);
}
var my_func = function(){
console.log("the list", imgSrcs);
}
}, false);
You can bind load on all img tags like
$("img").on("load", function() {
console.log($(this)[0].src)
});
If you really want to use pure javascript to mimic on, you can reference Emulate jQuery "on" with selector in pure javascript

Insert inline image to automatic email response via google forms

I am trying to insert a company logo into an automatic email that is sent out to customers who fill in a form via our website (which is linked to google forms). I am using the script to generate the automatic email and I can't seem to find out how to insert an inline image. I'm currently linking to an online image we have and it doesn't work well because the aspect ratio seems to vary depending on which device you are using.
Could someone please amend my code below so that it works with an inline image. Many thanks.
/**
* #OnlyCurrentDoc Limits the script to only accessing the current form.
*/
var DIALOG_TITLE = 'Example Dialog';
var SIDEBAR_TITLE = 'Example Sidebar';
/**
* Adds a custom menu with items to show the sidebar and dialog.
*
* #param {Object} e The event parameter for a simple onOpen trigger.
*/
function onOpen(e) {
FormApp.getUi()
.createAddonMenu()
.addItem('Show sidebar', 'showSidebar')
.addItem('Show dialog', 'showDialog')
.addToUi();
}
/**
* Runs when the add-on is installed; calls onOpen() to ensure menu creation and
* any other initializion work is done immediately.
*
* #param {Object} e The event parameter for a simple onInstall trigger.
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar. The sidebar structure is described in the Sidebar.html
* project file.
*/
function showSidebar() {
var ui = HtmlService.createTemplateFromFile('Sidebar')
.evaluate()
.setTitle(SIDEBAR_TITLE);
FormApp.getUi().showSidebar(ui);
}
/**
* Opens a dialog. The dialog structure is described in the Dialog.html
* project file.
*/
function showDialog() {
var ui = HtmlService.createTemplateFromFile('Dialog')
.evaluate()
.setWidth(350)
.setHeight(170);
FormApp.getUi().showModalDialog(ui, DIALOG_TITLE);
}
/**
* Appends a new form item to the current form.
*
* #param {Object} itemData a collection of String data used to
* determine the exact form item created.
*/
function addFormItem(itemData) {
// Use data collected from sidebar to manipulate the form.
var form = FormApp.getActiveForm();
switch (itemData.type) {
case 'Date':
form.addDateItem().setTitle(itemData.name);
break;
case 'Scale':
form.addScaleItem().setTitle(itemData.name);
break;
case 'Text':
form.addTextItem().setTitle(itemData.name);
break;
}
}
/**
* Queries the form DocumentProperties to determine whether the formResponse
* trigger is enabled or not.
*
* #return {Boolean} True if the form submit trigger is enabled; false
* otherwise.
*/
function getTriggerState() {
// Retrieve and return the information requested by the dialog.
var properties = PropertiesService.getDocumentProperties();
return properties.getProperty('triggerId') != null;
}
/**
* Turns the form submit trigger on or off based on the given argument.
*
* #param {Boolean} enableTrigger whether to turn on the form submit
* trigger or not
*/
function adjustFormSubmitTrigger(enableTrigger) {
// Use data collected from dialog to manipulate form.
// Determine existing state of trigger on the server.
var form = FormApp.getActiveForm();
var properties = PropertiesService.getDocumentProperties();
var triggerId = properties.getProperty('triggerId');
if (!enableTrigger && triggerId != null) {
// Delete the existing trigger.
var triggers = ScriptApp.getUserTriggers(form);
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getUniqueId() == triggerId) {
ScriptApp.deleteTrigger(triggers[i]);
break;
}
}
properties.deleteProperty('triggerId');
} else if (enableTrigger && triggerId == null) {
// Create a new trigger.
var trigger = ScriptApp.newTrigger('respondToFormSubmit')
.forForm(form)
.onFormSubmit()
.create();
properties.setProperty('triggerId', trigger.getUniqueId());
}
}
/**
* Responds to form submit events if a form summit trigger is enabled.
* Collects some form information and sends it as an email to the form creator.
*
* #param {Object} e The event parameter created by a form
* submission; see
* https://developers.google.com/apps-script/understanding_events
*/
function respondToFormSubmit(e) {
if (MailApp.getRemainingDailyQuota() > 0) {
var form = FormApp.getActiveForm();
var message = 'There have been ' + form.getResponses().length +
' response(s) so far. Latest Response:\n';
var itemResponses = e.response.getItemResponses();
for (var i = 0; i < itemResponses.length; i++) {
var itemTitle = itemResponses[i].getItem().getTitle();
var itemResponse = JSON.stringify(itemResponses[i].getResponse());
message += itemTitle + ': ' + itemResponse + '\n';
}
MailApp.sendEmail(
Session.getEffectiveUser().getEmail(),
'Form response received for form ' + form.getTitle(),
message,
{name: 'Forms Add-on Template'});
}
}
/* Send Confirmation Email with Google Forms */
function Initialize() {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendConfirmationMail")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendConfirmationMail(e) {
try {
var ss, bcc, sendername, subject, columns, submitter;
var message, value, textbody, sender, aliases;
// Log the aliases for this Gmail account and send an email as the first one.
var aliases = GmailApp.getAliases();
// This is your email address and you will be in the CC
bcc = aliases[0];
// This will show up as the sender's name
sendername = "The Flatser Team";
// This is the submitter's name
submitter = e.namedValues["Full Name"].toString();
// This is the submitter's email address
sender = e.namedValues["Email Address"].toString();
// Optional but change the following variable
// to have a custom subject for Google Docs emails
subject = "Please select a call time";
// This is the body of the auto-reply
message = "Dear "+ submitter + ", <br/><br/>Thank you for completing our form. Please fill in your availability for a telephone or skype call using the link below.<br/><br/>https://calendly.com/bcwolf/flatser-skype-call/06-23-2015<br/><br/><br/>Kind regards,<br/><br/>--<br/><br/><b>The Flatser Team</b><br/><a href='www.flatser.com'>www.flatser.com</a><br/><br/><img width='10%' height='10%' src='http://flatser.com/img/logo1.png'/>";
ss = SpreadsheetApp.getActiveSheet();
columns = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
textbody = message.replace("<br>", "\n");
GmailApp.sendEmail(sender, subject, textbody,
{bcc: bcc, name: sendername, htmlBody: message, from: aliases[0] });
} catch (e) {
Logger.log(e.toString());
}
}
You can set different widths according to different devices in your email using media queries.
In your case, i would prepend the <style> in your message variable for function SendConfirmationMail and add class to img tag inside the message variable which will then look as follows:
BODY OF MESSAGE ( assuming you need 100px width logo on mobile and 200px width on bigger screens. Also note do not set the height as it will ruin the aspect ratio if you calculate it incorrectly)
// This is the body of the auto-reply
// This is what i would add
message = "<style> #media (max-width:499px) { .logo__flaster { width: 100px; } #media (min-width:500px) { .logo__flaster { width: 200px; } </style>"
// This is same, i have only removed width from img and added class logo__flaster which i used in the style above.
message += "Dear "+ submitter + ", <br/><br/>Thank you for completing our form. Please fill in your availability for a telephone or skype call using the link below.<br/><br/>https://calendly.com/bcwolf/flatser-skype-call/06-23-2015<br/><br/><br/>Kind regards,<br/><br/>--<br/><br/><b>The Flatser Team</b><br/><a href='www.flatser.com'>www.flatser.com</a><br/><br/><img class='logo__flaster' src='http://flatser.com/img/logo1.png'/>";
Please read Media Queries in HTML Email article for more information, Also see more media queries templates if you want to customise more to a specific screen.

How to poll a Google Doc from an add-on

A documented restriction with document and sheet add-ons is that Apps Script cannot tell what a user does outside of the add-on. This tantalizing tip is given:
It is possible to poll for changes in a file's contents from a
sidebar's client-side code, although you'll always have a slight
delay. That technique can also alert your script to changes in the
user's selected cells (in Sheets) and cursor or selection (in Docs).
Sadly, this isn't shown in any of the demo code. How can I do it?
The polling is done from the html code in your add-on's User Interface, calling across to server-side Apps Script functions using google.script.run.
Using jQuery simplifies this, and we can even start with the answers from jQuery, simple polling example.
function doPoll(){
$.post('ajax/test.html', function(data) {
alert(data); // process results here
setTimeout(doPoll,5000);
});
}
The basic idea can work for Google Apps Script, if we replace the ajax calls with the GAS equivalents.
Here's the skeleton of the poll function that you would use in your html file:
/**
* On document load, assign click handlers to button(s), add
* elements that should start hidden (avoids "flashing"), and
* start polling for document updates.
*/
$(function() {
// assign click handler(s)
// Add elements that should start hidden
// Start polling for updates
poll();
});
/**
* Poll a server-side function 'serverFunction' at the given interval
* and update DOM elements with results.
*
* #param {Number} interval (optional) Time in ms between polls.
* Default is 2s (2000ms)
*/
function poll(interval){
interval = interval || 2000;
setTimeout(function(){
google.script.run
.withSuccessHandler(
function(results) {
$('#some-element').updateWith(results);
//Setup the next poll recursively
poll(interval);
})
.withFailureHandler(
function(msg, element) {
showError(msg, $('#button-bar'));
element.disabled = false;
})
.serverFunction();
}, interval);
};
Add-on Example, Document Poller
This is a demonstration of the jQuery polling technique calling server-side Google Apps Script functions to detect user behavior in a Google Document. It does nothing useful, but it showcases a few things that would typically require knowledge of the user's activity and state of the document, for instance context-sensitve control over a button.
The same principle could apply to a spreadsheet, or a stand-alone GAS Web Application.
Like the UI App example in this question, this technique could be used to get around execution time limits, for operations with a User Interface at least.
The code builds upon the example add-on from Google's 5-minute quickstart. Follow the instructions from that guide, using the code below instead of that in the quickstart.
Code.gs
/**
* Creates a menu entry in the Google Docs UI when the document is opened.
*
* #param {object} e The event parameter for a simple onOpen trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode.
*/
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'showSidebar')
.addToUi();
}
/**
* Runs when the add-on is installed.
*
* #param {object} e The event parameter for a simple onInstall trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode. (In practice, onInstall triggers always
* run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
* AuthMode.NONE.)
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar in the document containing the add-on's user interface.
*/
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('Document Poller');
DocumentApp.getUi().showSidebar(ui);
}
/**
* Check if there is a current text selection.
*
* #return {boolean} 'true' if any document text is selected
*/
function checkSelection() {
return {isSelection : !!(DocumentApp.getActiveDocument().getSelection()),
cursorWord : getCursorWord()};
}
/**
* Gets the text the user has selected. If there is no selection,
* this function displays an error message.
*
* #return {Array.<string>} The selected text.
*/
function getSelectedText() {
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var text = [];
var elements = selection.getSelectedElements();
for (var i = 0; i < elements.length; i++) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
text.push(element.getText().substring(startIndex, endIndex + 1));
} else {
var element = elements[i].getElement();
// Only translate elements that can be edited as text; skip images and
// other non-text elements.
if (element.editAsText) {
var elementText = element.asText().getText();
// This check is necessary to exclude images, which return a blank
// text element.
if (elementText != '') {
text.push(elementText);
}
}
}
}
if (text.length == 0) {
throw 'Please select some text.';
}
return text;
} else {
throw 'Please select some text.';
}
}
/**
* Returns the word at the current cursor location in the document.
*
* #return {string} The word at cursor location.
*/
function getCursorWord() {
var cursor = DocumentApp.getActiveDocument().getCursor();
var word = "<selection>";
if (cursor) {
var offset = cursor.getSurroundingTextOffset();
var text = cursor.getSurroundingText().getText();
word = getWordAt(text,offset);
if (word == "") word = "<whitespace>";
}
return word;
}
/**
* Returns the word at the index 'pos' in 'str'.
* From https://stackoverflow.com/questions/5173316/finding-the-word-at-a-position-in-javascript/5174867#5174867
*/
function getWordAt(str, pos) {
// Perform type conversions.
str = String(str);
pos = Number(pos) >>> 0;
// Search for the word's beginning and end.
var left = str.slice(0, pos + 1).search(/\S+$/),
right = str.slice(pos).search(/\s/);
// The last word in the string is a special case.
if (right < 0) {
return str.slice(left);
}
// Return the word, using the located bounds to extract it from the string.
return str.slice(left, right + pos);
}
Sidebar.html
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<div class="sidebar branding-below">
<form>
<div class="block" id="button-bar">
<button class="blue" id="get-selection" disabled="disable">Get selection</button>
</div>
</form>
</div>
<div class="sidebar bottom">
<img alt="Add-on logo" class="logo" height="27"
id="logo"
src="https://www.gravatar.com/avatar/adad1d8ad010a76a83574b1fff4caa46?s=128&d=identicon&r=PG">
<span class="gray branding-text">by Mogsdad, D.Bingham</span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
/**
* On document load, assign click handlers to button(s), add
* elements that should start hidden (avoids "flashing"), and
* start polling for document selections.
*/
$(function() {
// assign click handler(s)
$('#get-selection').click(getSelection);
// Add elements that should start hidden
var newdiv1 = $( "<div class='block' id='cursor-word'/>" ).hide(),
newdiv2 = $( "<div class='block' id='selected-text'/>" ).hide();
$('#button-bar').after( newdiv1, newdiv2 );
$('#cursor-word').html('<H2>Word at cursor:</H2><p id="cursor-word-content"></p>');
$('#selected-text').html('<H2>Selected text:</H2><p id="selected-text-content"></p>');
// Start polling for updates
poll();
});
/**
* Poll the server-side 'checkSelection' function at the given
* interval for document selection, and enable or disable the
* '#get-selection' button.
*
* #param {Number} interval (optional) Time in ms between polls.
* Default is 2s (2000ms)
*/
function poll(interval){
interval = interval || 2000;
setTimeout(function(){
google.script.run
.withSuccessHandler(
function(cursor) {
if (cursor.isSelection) {
// Text has been selected: enable button, hide cursor word.
$('#get-selection').attr('disabled', false);
$('#cursor-word').hide();
// $('#selected-text').show(); // Not so fast - wait until button is clicked.
}
else {
$('#get-selection').attr('disabled', true);
$('#cursor-word').show();
$('#selected-text').hide();
}
$('#cursor-word-content').text(cursor.cursorWord);
//Setup the next poll recursively
poll(interval);
})
.withFailureHandler(
function(msg, element) {
showError(msg, $('#button-bar'));
element.disabled = false;
})
.checkSelection();
}, interval);
};
/**
* Runs a server-side function to retrieve the currently
* selected text.
*/
function getSelection() {
this.disabled = true;
$('#error').remove();
google.script.run
.withSuccessHandler(
function(selectedText, element) {
// Show selected text
$('#selected-text-content').text(selectedText);
$('#selected-text').show();
element.disabled = false;
})
.withFailureHandler(
function(msg, element) {
showError(msg, $('#button-bar'));
element.disabled = false;
})
.withUserObject(this)
.getSelectedText();
}
/**
* Inserts a div that contains an error message after a given element.
*
* #param msg The error message to display.
* #param element The element after which to display the error.
*/
function showError(msg, element) {
var div = $('<div id="error" class="error">' + msg + '</div>');
$(element).after(div);
}
</script>
Polling Interval
The setTimeout() function accepts a time interval expressed in milliseconds, but I found through experimentation that a two-second response was the best that could be expected. Therefore, the skeleton poll() has a 2000ms interval as its default. If your situation can tolerate a longer delay between poll cycles, then provide a larger value with the onLoad call to poll(), e.g. poll(10000) for a 10-second poll cycle.
Sheets
For a sheet example see How do I make a Sidebar display values from cells?

Youtube QR Code Generation UserScript not functioning

I'm trying to create a userscript that automatically adds a QR code image of the current url to the 'Share' menu on a youtube video page.
I know next to nothing of JavaScript, UserScript, HTML, etc.
But, this is what I have so far:
// ==UserScript==
// #name Youtube QR ShareLink
// #description Displays QR of youtube URL
// #version 0.1
// #match http://www.youtube.com/watch*
// #match https://www.youtube.com/?*
// #match http://www.youtube.com/?*
// #match https://www.youtube.com/watch*
// #include http://www.youtube.com/?*
// #include http://www.youtube.com/watch*
// #include https://www.youtube.com/?*
// #include https://www.youtube.com/watch*
// ==/UserScript==
(function () {
var shareDiv = document.getElementById('share-option-container ytg-box');
var qrIMG = 'http://chart.googleapis.com/chart?chl=' + window.location.href + '&chld=M%7C0&cht=qr&chs=125x125';
var img = document.createElement('qrcode');
img.src=qrIMG;
img.width=125;
img.height=125;
shareDiv.appendChild(img);
}());
Unsurprisingly, it doesn't work.
Could anyone please tell me what it is I'm doing wrong?
Thank you!
You're using document.getElementById with a value that isn't the id of the box - it's a list of the classes for that element. To use a selector like that, you could do it with a couple of calls to document.getElementsByClassName, or you could use document.querySelector('.share-option-container .ytg-box'), or you could use jQuery to perform that selection.
Your second problem is that you're creating an element called 'qrcode', but you should be creating an img element.
You're revised code should look like this:
// ==UserScript==
// #name Youtube QR ShareLink
// #description Displays QR of youtube URL
// #version 0.1
// #match http://www.youtube.com/watch*
// #match https://www.youtube.com/?*
// #match http://www.youtube.com/?*
// #match https://www.youtube.com/watch*
// #include http://www.youtube.com/?*
// #include http://www.youtube.com/watch*
// #include https://www.youtube.com/?*
// #include https://www.youtube.com/watch*
// ==/UserScript==
(function () {
var shareDiv = document.querySelector('.share-option-container .ytg-box');
var qrIMG = 'http://chart.googleapis.com/chart?chl=' + window.location.href + '&chld=M%7C0&cht=qr&chs=125x125';
var img = document.createElement('img');
img.src=qrIMG;
img.width=125;
img.height=125;
shareDiv.appendChild(img);
}());
Note that on YouTube, the element that you're grabbing doesn't exist until the share box is actually opened, so you'll need to actually handle opening the share box first before the rest of your code runs. I've tested this in my browser and the above code works well once the share box has been opened, but not before.
You can account for that by using a timer. Change the code to:
var shareBoxCheckInterval = setInterval (AddQR_Code, 200);
function AddQR_Code () {
var shareDiv = document.querySelector ('.share-option-container .ytg-box');
if (shareDiv) {
var qrIMG = 'http://chart.googleapis.com/chart?chl='
+ window.location.href + '&chld=M%7C0&cht=qr&chs=125x125';
var img = document.createElement ('img');
img.src = qrIMG;
img.width = 125;
img.height = 125;
shareDiv.appendChild (img);
/*-- If you want to continually check for new share boxes, on the
same page, comment out this next line.
*/
clearInterval (shareBoxCheckInterval);
}
}

Categories