Wednesday, September 10, 2014
Thursday, August 21, 2014
Count LOC recursively
Count lines of code in a directory recursively:
find . -name '*.java' | xargs wc -l
See [1].
[1] How to count all the lines of code in a directory recursively?
find . -name '*.java' | xargs wc -l
See [1].
[1] How to count all the lines of code in a directory recursively?
Monday, August 18, 2014
Avoid text/image/video width overrun with css
Say you have a html table or div, or in the case of mobile device browser. A long text segment, or image, or video (e.g., from youtube, using iframe), whose width is too long to fit in the parent container, this will break the layout. Since I'm working on a forum compatible with both desktop computer and mobile devices, here are my findings on the solutions.
== text ==
Use this in display:
<pre class="forum">content</pre>
Of course, it does not have to be pre tag. It can be a div too. In css [1]:
pre.forum {
/* Wrap long text with space in the middle. */
white-space: pre-wrap; /* Wrap text as needed. CSS 3. */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+, Non standard for webkit */
/* Wrap long text without space in between. */
-ms-word-break: break-all;
word-break: break-all;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
}
This css works for both desktop browser and mobile browser.
== Image ==
Use this in display:
<img src="..." class="media_img">
In css:
img.media_img {
max-width: 100%;
height: auto;
}
Note here use max-width instead of width, because if the image width is less than device width or parent container width (in desktop browser), we want to keep the image width and not expand it. This css works for both desktop browser and mobile browser.
This will always work in Safari. However, this may fail in firefox, if the "width" value is specified in the img tag, e.g.,
<img src="..." width="1500" class="media_img">
The way to fix this, is to wrap the img tag with another div tag, whose width is specified as an absolute value (the max design value):
<div class="media_img_container"> <img src="..." width="1500" class="media_img"> </div>
In css:
div.media_img_container {
width: 1000px;
}
Then the image will be at most 1000px in width, despite the specification of width="1500" in img tag. This also means that for different design width, you need to specify different div container width.
== Video ==
For desktop browser, this usually is not a problem, since video width is often around 500px. For mobile device, this can be a problem since mobile browser width can be only 320px, so the css here can be used. This css will resize the video iframe to always match device width.
In the case the iframe width is less than device width, this will expand the iframe width. If you don't want to do it, maybe you can check the width parameter in the iframe tag and don't apply this css.
Use this in display:
<div class="aspect-ratio"><iframe ...></iframe></div>
In css [2]:
.aspect-ratio {
position: relative;
width: 100%;
height: 0;
padding-bottom: 75%; /* height/width, usually 3/4 on youtube */
}
.aspect-ratio iframe {
position: absolute;
width: 99%;
height: 99%;
left: 0; top: 0;
}
References:
[1] http://kenneth.io/blog/2012/03/04/word-wrapping-hypernation-using-css/
[2] http://fettblog.eu/blog/2013/06/16/preserving-aspect-ratio-for-embedded-iframes/
== text ==
Use this in display:
<pre class="forum">content</pre>
Of course, it does not have to be pre tag. It can be a div too. In css [1]:
pre.forum {
/* Wrap long text with space in the middle. */
white-space: pre-wrap; /* Wrap text as needed. CSS 3. */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+, Non standard for webkit */
/* Wrap long text without space in between. */
-ms-word-break: break-all;
word-break: break-all;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
}
This css works for both desktop browser and mobile browser.
== Image ==
Use this in display:
<img src="..." class="media_img">
In css:
img.media_img {
max-width: 100%;
height: auto;
}
Note here use max-width instead of width, because if the image width is less than device width or parent container width (in desktop browser), we want to keep the image width and not expand it. This css works for both desktop browser and mobile browser.
This will always work in Safari. However, this may fail in firefox, if the "width" value is specified in the img tag, e.g.,
<img src="..." width="1500" class="media_img">
The way to fix this, is to wrap the img tag with another div tag, whose width is specified as an absolute value (the max design value):
<div class="media_img_container"> <img src="..." width="1500" class="media_img"> </div>
In css:
div.media_img_container {
width: 1000px;
}
Then the image will be at most 1000px in width, despite the specification of width="1500" in img tag. This also means that for different design width, you need to specify different div container width.
== Video ==
For desktop browser, this usually is not a problem, since video width is often around 500px. For mobile device, this can be a problem since mobile browser width can be only 320px, so the css here can be used. This css will resize the video iframe to always match device width.
In the case the iframe width is less than device width, this will expand the iframe width. If you don't want to do it, maybe you can check the width parameter in the iframe tag and don't apply this css.
Use this in display:
<div class="aspect-ratio"><iframe ...></iframe></div>
In css [2]:
.aspect-ratio {
position: relative;
width: 100%;
height: 0;
padding-bottom: 75%; /* height/width, usually 3/4 on youtube */
}
.aspect-ratio iframe {
position: absolute;
width: 99%;
height: 99%;
left: 0; top: 0;
}
References:
[1] http://kenneth.io/blog/2012/03/04/word-wrapping-hypernation-using-css/
[2] http://fettblog.eu/blog/2013/06/16/preserving-aspect-ratio-for-embedded-iframes/
Thursday, August 7, 2014
Create a website compatible with Mobile device
A template of a html page compatible with mobile device layout is attached at the end of this post.
In the header: <meta name="viewport" content="width=device-width"> means if the agent is a mobile device, then set the page width to the device's width.
In the resizePage() function, if the navigator useragent is detected as a mobile device, then set the image's width and the div's width. Function effectiveDeviceWidth() [1] will return the device screen width for the current orientation (horizontal or vertical). The code in [1] was like this but somehow it does not work with my Android:
function effectiveDeviceWidth() {
var deviceWidth = window.orientation == 0 ? window.screen.width : window.screen.height;
// iOS returns available pixels, Android returns pixels / pixel ratio
// http://www.quirksmode.org/blog/archives/2012/07/more_about_devi.html
if (navigator.userAgent.indexOf('Android') >= 0 && window.devicePixelRatio) {
deviceWidth = deviceWidth / window.devicePixelRatio;
}
return deviceWidth;
}
Note you can see relevant parameters with this javascript:
document.write("width: " + window.screen.width + ", height:" + window.screen.height + ", ratio:" + window.devicePixelRatio + ", orientation:" + window.orientation);
For example, for iPhone:
Vertical: width: 320, height: 480, ratio: 2, orientation: 0
Horizontal: width: 320, height: 480, ratio: 2, orientation:90 or -90
For Android:
Vertical: width: 480, height: 800, ratio: 1.5, orientation: 0
Horizontal: width: 800, height: 480, ratio: 1.5, orientation:90 or -90
Note in general it is hard to get the device size in a way compatible with all the brands. I am using iPhone 7.1 and Android 4.1.2.
The code after this [2] are useful to mobile devices that do not automatically update layout when orientation is changed. This is not an issue for iPhone, but is one for Android.
== HTML Page Template Compatible With Mobile Devices ==
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>Test Site</title>
</head>
<body style="margin: 0px 0px 0px 0px;" onload="resizePage();">
<center>
<img id="img1" src="https://www.google.com/images/srpr/logo11w.png">
<div id="div1">
<h2>About</h2>
<p>This is a site compatible to mobile device layout.</p>
</div>
</center>
<script type="text/javascript">
//
// Reisze page elements according to device width.
// Applies for mobile devices only.
//
function resizePage() {
var uagent = navigator.userAgent.toLowerCase();
if (uagent.search("iphone") > -1 ||
uagent.search("ipod") > -1 ||
uagent.search("ipad") > -1 ||
uagent.search("appletv") > -1 ||
uagent.search("android") > -1 ||
uagent.search("blackberry") > -1 ||
uagent.search("webos") > -1
) {
var w = effectiveDeviceWidth();
document.getElementById("img1").style.width = w + "px";
document.getElementById("div1").style.width = (w - 20) + "px";
}
}
function effectiveDeviceWidth() {
// document.write("width: " + window.screen.width + ", height:" + window.screen.height + ", ratio:" +
// window.devicePixelRatio + ", orientation:" + window.orientation);
var deviceWidth = window.screen.width;
if (navigator.userAgent.toLowerCase().indexOf('android') > -1 && window.devicePixelRatio) {
deviceWidth = deviceWidth / window.devicePixelRatio;
}
return deviceWidth;
}
//
// Resize page if orientation is changed. useful for Android.
//
var previousOrientation = window.orientation;
var checkOrientation = function(){
if(window.orientation !== previousOrientation){
previousOrientation = window.orientation;
resizePage();
}
};
window.addEventListener("resize", checkOrientation, false);
window.addEventListener("orientationchange", checkOrientation, false);
</script>
</body>
</html>
References:
[1] Detect effective horizontal pixel width on a mobile device with Javascript
[2] Detect rotation of Android phone in the browser with javascript
In the header: <meta name="viewport" content="width=device-width"> means if the agent is a mobile device, then set the page width to the device's width.
In the resizePage() function, if the navigator useragent is detected as a mobile device, then set the image's width and the div's width. Function effectiveDeviceWidth() [1] will return the device screen width for the current orientation (horizontal or vertical). The code in [1] was like this but somehow it does not work with my Android:
function effectiveDeviceWidth() {
var deviceWidth = window.orientation == 0 ? window.screen.width : window.screen.height;
// iOS returns available pixels, Android returns pixels / pixel ratio
// http://www.quirksmode.org/blog/archives/2012/07/more_about_devi.html
if (navigator.userAgent.indexOf('Android') >= 0 && window.devicePixelRatio) {
deviceWidth = deviceWidth / window.devicePixelRatio;
}
return deviceWidth;
}
Note you can see relevant parameters with this javascript:
document.write("width: " + window.screen.width + ", height:" + window.screen.height + ", ratio:" + window.devicePixelRatio + ", orientation:" + window.orientation);
For example, for iPhone:
Vertical: width: 320, height: 480, ratio: 2, orientation: 0
Horizontal: width: 320, height: 480, ratio: 2, orientation:90 or -90
For Android:
Vertical: width: 480, height: 800, ratio: 1.5, orientation: 0
Horizontal: width: 800, height: 480, ratio: 1.5, orientation:90 or -90
Note in general it is hard to get the device size in a way compatible with all the brands. I am using iPhone 7.1 and Android 4.1.2.
The code after this [2] are useful to mobile devices that do not automatically update layout when orientation is changed. This is not an issue for iPhone, but is one for Android.
== HTML Page Template Compatible With Mobile Devices ==
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>Test Site</title>
</head>
<body style="margin: 0px 0px 0px 0px;" onload="resizePage();">
<center>
<img id="img1" src="https://www.google.com/images/srpr/logo11w.png">
<div id="div1">
<h2>About</h2>
<p>This is a site compatible to mobile device layout.</p>
</div>
</center>
<script type="text/javascript">
//
// Reisze page elements according to device width.
// Applies for mobile devices only.
//
function resizePage() {
var uagent = navigator.userAgent.toLowerCase();
if (uagent.search("iphone") > -1 ||
uagent.search("ipod") > -1 ||
uagent.search("ipad") > -1 ||
uagent.search("appletv") > -1 ||
uagent.search("android") > -1 ||
uagent.search("blackberry") > -1 ||
uagent.search("webos") > -1
) {
var w = effectiveDeviceWidth();
document.getElementById("img1").style.width = w + "px";
document.getElementById("div1").style.width = (w - 20) + "px";
}
}
function effectiveDeviceWidth() {
// document.write("width: " + window.screen.width + ", height:" + window.screen.height + ", ratio:" +
// window.devicePixelRatio + ", orientation:" + window.orientation);
var deviceWidth = window.screen.width;
if (navigator.userAgent.toLowerCase().indexOf('android') > -1 && window.devicePixelRatio) {
deviceWidth = deviceWidth / window.devicePixelRatio;
}
return deviceWidth;
}
//
// Resize page if orientation is changed. useful for Android.
//
var previousOrientation = window.orientation;
var checkOrientation = function(){
if(window.orientation !== previousOrientation){
previousOrientation = window.orientation;
resizePage();
}
};
window.addEventListener("resize", checkOrientation, false);
window.addEventListener("orientationchange", checkOrientation, false);
</script>
</body>
</html>
References:
[1] Detect effective horizontal pixel width on a mobile device with Javascript
[2] Detect rotation of Android phone in the browser with javascript
WordPress note
== Move WordPress to another folder ==
There is no need to backup entire site and move things. A simple, light-weight procedure is [1]:
1. Go to admin panel: Settings > General
2. Change the two URI addresses, click on "Save Changes". This will show an error page, but it's fine.
3. Open your SSH/FTP client (or other site admin tool) and move the WP folder
4. Now open your blog at the new location, go to Settings > General, click on "Save Changes".
5. Go to Settings > Permalinks, click on "Save Changes", this is supposed to recreate all .htaccess rules.
== Add social media share function ==
The simple script to insert for the sharing panel can be obtained from [2]. It just needs to insert this script anywhere between the body tags in your site.
Say you are using the twenty twelve theme, just edit this file: wp-content/themes/twentytwelve/footer.php
Add the script before the close body tag. That's all.
[1] http://wordpress.org/support/topic/moving-wordpress-to-another-folder
[2] http://jiathis.com/
There is no need to backup entire site and move things. A simple, light-weight procedure is [1]:
1. Go to admin panel: Settings > General
2. Change the two URI addresses, click on "Save Changes". This will show an error page, but it's fine.
3. Open your SSH/FTP client (or other site admin tool) and move the WP folder
4. Now open your blog at the new location, go to Settings > General, click on "Save Changes".
5. Go to Settings > Permalinks, click on "Save Changes", this is supposed to recreate all .htaccess rules.
== Add social media share function ==
The simple script to insert for the sharing panel can be obtained from [2]. It just needs to insert this script anywhere between the body tags in your site.
Say you are using the twenty twelve theme, just edit this file: wp-content/themes/twentytwelve/footer.php
Add the script before the close body tag. That's all.
[1] http://wordpress.org/support/topic/moving-wordpress-to-another-folder
[2] http://jiathis.com/
Wednesday, July 30, 2014
Flash an Android Phone
A friend got an old Android phone to use for Android development testing. It was a Nexus S 4G, Android build version was GWK74 (2.7.3 Gingerbread). So I spent some time to investigate how to do this. It worked and here is the note.
In this case, when boot into fastboot mode, it complains "Fastboot Mode No Boot or Recovery IMG". So need to download the images.
Also during the process, windows needs 2 device drivers to detect the phone: 1) when android is on, need google device driver for Nexus S 4G, which comes with ADT (in my case, it's in D:\Android\android-sdk\extras\google\usb_drive). 2) when android is in fastboot mode, need another driver called "Android Bootloader Interface driver". After a lot of search, this is available with pdaNet [1].
Next, need to download the images. This can be downloaded from Factory Images for Nexus Devices [2]. For Nexus S 4G, the corresponding section is at the end of the page: "Factory Images sojus for Nexus S 4G (d720)". 3 versions are available: 2.3.7 (GWK74), 4.0.4 (IMM76D) and 4.1.1 (JRO03R). The phone previously had 2.3.7 (GWK74), now we want a more modern version, so choose 4.1.1 (JRO03R).
Now, for actual steps to flash the phone, see [3]. Note in the last step 10, the commands are the same as in flash-all.sh and flash-all.bat of the package in [2]. The steps in [3] are copied below. In an established environment (no need to setup any drivers), the steps involved are 5, 6, 7, 9, 10, 11.
1. Install the Android SDK and Eclipse.
Eclipse probably isn’t necessary but it is nice to have.
2. Launch the Android SDK Manager.
3. In the Android SDK manager verify that the Google USB Driver is installed and up to date.
4. Connect the target phone to the PC using USB. Make sure USB Debugging is enabled on the phone.
5. From the command line, display all adb devices
Use the command: adb devices
This command is in the Android SDK platform-tools folder. I have this added to my Path. ADB should list out all attached Android devices.
If no devices are listed, make sure you have the Google Android USB driver installed.
Windows Device Manager should show a device of “Android Composite ADB Interface”.
6. Use ADB to reboot the phone into Fastboot mode
Use the command: adb reboot bootloader
This command is in the Android SDK platform-tools folder.
7. Verify that fastboot can access the phone
Use the command: fastboot devices
This should list out all attached Android devices in Fastboot mode. Notice that the device is no longer visible to ADB. “adb devices” no longer lists the device. However, “fastboot devices” should.
8. Install the Android Bootloader Interface driver if needed
If Fastboot Devices lists the phone, this step is not necessary. If Fastboot does not list the phone or shows
You may need to install the Android Bootloader Interface driver. This can be from the pdaNet.
9. Download and expand the device images to use.
For this phone, Google publishes the standard images at https://developers.google.com/android/nexus/images#sojusgwk74
10. Execute the commands from flash-all.sh to flash the device.
Change to the folder with the expanded images and execute the fastboot commands from the flash-all.sh file.
fastboot flash bootloader bootloader-crespo4g-d720sprlc1.img
fastboot reboot-bootloader
fastboot flash radio radio-crespo4g-d720sprlf2.img
fastboot reboot-bootloader
fastboot -w update image-sojus-jro03r.zip
Note the last step "fastboot -w update image-sojus-jro03r.zip" may fail, possible because it cannot decode the zip file for the images, so do it manually: first uncompress the zip file, which contains the images, then in DOS, use these commands (refer to the end of [4]), which I call step 11:
11. Flash the images.
fastboot flash recovery recovery.img
fastboot flash boot boot.img
fastboot flash userdata userdata.img
fastboot flash system system.img
12. Recovery
In the Android screen, use sound dial to choose "Recovery", then press power button to confirm.
It takes a few minutes to recover from the flashed images, then automatically boot into the new Android 4.1.1 system.
The entire process can be summarized into 3 steps:
1) setup device drivers so computer can detect the phone in both normal and fastboot modes.
2) download image package for your device.
3) use Android SDK tools adb and fastboot to do the flash.
References:
[1] pdaNet
[2] Factory Images for Nexus Devices
[3] Flash Nexus S 4G
[4] http://wiki.cyanogenmod.org/w/Doc:_fastboot_intro
In this case, when boot into fastboot mode, it complains "Fastboot Mode No Boot or Recovery IMG". So need to download the images.
Also during the process, windows needs 2 device drivers to detect the phone: 1) when android is on, need google device driver for Nexus S 4G, which comes with ADT (in my case, it's in D:\Android\android-sdk\extras\google\usb_drive). 2) when android is in fastboot mode, need another driver called "Android Bootloader Interface driver". After a lot of search, this is available with pdaNet [1].
Next, need to download the images. This can be downloaded from Factory Images for Nexus Devices [2]. For Nexus S 4G, the corresponding section is at the end of the page: "Factory Images sojus for Nexus S 4G (d720)". 3 versions are available: 2.3.7 (GWK74), 4.0.4 (IMM76D) and 4.1.1 (JRO03R). The phone previously had 2.3.7 (GWK74), now we want a more modern version, so choose 4.1.1 (JRO03R).
Now, for actual steps to flash the phone, see [3]. Note in the last step 10, the commands are the same as in flash-all.sh and flash-all.bat of the package in [2]. The steps in [3] are copied below. In an established environment (no need to setup any drivers), the steps involved are 5, 6, 7, 9, 10, 11.
1. Install the Android SDK and Eclipse.
Eclipse probably isn’t necessary but it is nice to have.
2. Launch the Android SDK Manager.
3. In the Android SDK manager verify that the Google USB Driver is installed and up to date.
4. Connect the target phone to the PC using USB. Make sure USB Debugging is enabled on the phone.
5. From the command line, display all adb devices
Use the command: adb devices
This command is in the Android SDK platform-tools folder. I have this added to my Path. ADB should list out all attached Android devices.
If no devices are listed, make sure you have the Google Android USB driver installed.
Windows Device Manager should show a device of “Android Composite ADB Interface”.
6. Use ADB to reboot the phone into Fastboot mode
Use the command: adb reboot bootloader
This command is in the Android SDK platform-tools folder.
7. Verify that fastboot can access the phone
Use the command: fastboot devices
This should list out all attached Android devices in Fastboot mode. Notice that the device is no longer visible to ADB. “adb devices” no longer lists the device. However, “fastboot devices” should.
8. Install the Android Bootloader Interface driver if needed
If Fastboot Devices lists the phone, this step is not necessary. If Fastboot does not list the phone or shows
9. Download and expand the device images to use.
For this phone, Google publishes the standard images at https://developers.google.com/android/nexus/images#sojusgwk74
10. Execute the commands from flash-all.sh to flash the device.
Change to the folder with the expanded images and execute the fastboot commands from the flash-all.sh file.
fastboot flash bootloader bootloader-crespo4g-d720sprlc1.img
fastboot reboot-bootloader
fastboot flash radio radio-crespo4g-d720sprlf2.img
fastboot reboot-bootloader
fastboot -w update image-sojus-jro03r.zip
Note the last step "fastboot -w update image-sojus-jro03r.zip" may fail, possible because it cannot decode the zip file for the images, so do it manually: first uncompress the zip file, which contains the images, then in DOS, use these commands (refer to the end of [4]), which I call step 11:
11. Flash the images.
fastboot flash recovery recovery.img
fastboot flash boot boot.img
fastboot flash userdata userdata.img
fastboot flash system system.img
12. Recovery
In the Android screen, use sound dial to choose "Recovery", then press power button to confirm.
It takes a few minutes to recover from the flashed images, then automatically boot into the new Android 4.1.1 system.
The entire process can be summarized into 3 steps:
1) setup device drivers so computer can detect the phone in both normal and fastboot modes.
2) download image package for your device.
3) use Android SDK tools adb and fastboot to do the flash.
References:
[1] pdaNet
[2] Factory Images for Nexus Devices
[3] Flash Nexus S 4G
[4] http://wiki.cyanogenmod.org/w/Doc:_fastboot_intro
Monday, July 28, 2014
Google form and Sign in with linkedin
== Google form ==
Now working with some friends on a registration function.
I just learned Google forms is a convenient tool to create simple registration forms. You can do it from either Google Forms or Google Drive. This form will be linked to a spreadsheet online, such that all records are stored there.
The submission of a form is much better if it 1) sends a confirmation email, and also 2) include a link to edit the submission. It's also good to 3) have a dashboard that displays submitted information, which you often want an interface independent from the spreadsheet. These can all be done with Google Forms API.
1) and 2) need writing a javascript function.
See Email confirmations from Google Forms for how to set up a script triggered by form submission action.
This is my code to include edit link:
function myFunction(e) {
if (typeof e == 'undefined') {
Logger.log("e is undefined");
return;
}
//var userName = e.values[1];
//var userEmail = e.values[2];
var userName = e.namedValues["Name"][0]; // From a field whose name is "Name".
var userEmail = e.namedValues["Email"][0]; // From a field whose name is "Email".
if (userEmail == '') return;
var subject = "Form Submitted";
var form = FormApp.openById('[form id]');
var formResponses = form.getResponses(); // All responses/rows in spreadsheet.
var formResponse = formResponses[formResponses.length-1]; // Get the just submitted item - last row.
//Logger.log("formResponses.length = " + formResponses.length);
var message = "Thank you, " + userName + " for finishing the survey.\n\n";
message += "You can see the current list at [dash board page link]\n\n";
message += "You can edit your information at: " + formResponse.getEditResponseUrl() + "\n\n";
message += "Have a good day.";
MailApp.sendEmail (userEmail, subject, message);
}
Note in the code above, the "form id" must be the id of the form, and not the spreadsheet. The code itself it a code of the spreadsheet.
Here is another piece of code that works equally well, but should be embedded in the form, and not the spreadsheet. This code is better in that it does not need to specify any form id. I prefer this one.
function onFormSubmit(e) {
if (typeof e == 'undefined') {
Logger.log("e is undefined");
return;
}
var itemResponses = e.response.getItemResponses();
/*
for (var i = 0; i < itemResponses.length; i++) {
var itemResponse = itemResponses[i];
Logger.log('Response #%s to the question "%s" was "%s"',
(i + 1).toString(),
itemResponse.getItem().getTitle(),
itemResponse.getResponse());
}
*/
var subject = "Form Submitted";
var userName = itemResponses[0].getResponse();
var userEmail = itemResponses[4].getResponse();
var message = "Thank you, " + userName + " for finishing the survey.\n\n";
message += "You can edit your information at: " + e.response.getEditResponseUrl() + "\n\n";
message += "Have a good day.";
MailApp.sendEmail (userEmail, subject, message);
}
3) Displays submitted information not using the spreadsheet.
See Query a Google Spreadsheet like a Database with Google Visualization API Query Language. This shows how to display a table containing selected spreadsheet columns. The link will be below (replace group id and group id with your values):
https://docs.google.com/spreadsheets/d/[form id]/gviz/tq?tqx=out:html&tq&gid=[group id]
If you want to display only selected columns, e.g., columns A and B, you can specify this with the tq parameter: tq=SELECT+A,B, so the links becomes:
https://docs.google.com/spreadsheets/d/[form id]/gviz/tq?tqx=out:html&tq=SELECT+A,B&gid=[group id]
One concern of giving people this link is security: they can modify the value of tq to see all fields. To overcome this is easy: set up a php page that read in the contents and display, this way the url is hidden. It is also really easy to set up, just 1 line of php code is needed:
<?php
echo file_get_contents("https://docs.google.com/spreadsheets/d/[form id]/gviz/tq?tqx=out:html&tq=SELECT+A,B&gid=[group id]");
?>
== Reliability issue ==
Well, it seems the script in google forms are not always reliably triggered. The above code stops to function without any reason. Searched on line for "google form script trigger not reliable", it seems many other people had similar experience. Free lunch is not always tasty.
== Sign in with Linkedin ==
Say you want people to fill the above form, but not everyone, only those who registered with linked in. So what you do is to set up a page that requires linkedin authentication, then forward people to the above link. For details, see [1][2][3][4].
Following example code in [4]. The code to set up such a page is in appendix.
Note that the Google form itself is not protected by session. So if anyone knows the url of the form, he will be able to register the form. I have not studied about ways to do this. There may not be a way of doing it, since it's not a full-fledged website anyway. One can change the setting of make a Google form private/public or accessible to only some people, that's what you can do if you don't want it public.
References:
[1] Sign In With LinkedIn
[2] Linkedin authentication documentation - Important. [3] below is linked from here.
[3] Linkedin developer network - Register here to get a linkedin application account. Important.
[4] Linkedin authentication code sample in PHP - Useful
Appendix. Authentication with Linkedin.
<?php
// Change these 5 fields.
define('API_KEY', '...');
define('API_SECRET', '...');
define('REDIRECT_URI', 'http://...');
define('SCOPE', ''); //r_fullprofile r_emailaddress rw_nus');
$reg_url = "https://docs.google.com/forms/d/[form id]/viewform?c=0&w=1&usp=mail_form_link";
// You'll probably use a database
session_name('linkedin');
session_start();
$user = fetch('GET', '/v1/people/~:(firstName,lastName)');
// OAuth 2 Control Flow
if (isset($_GET['error'])) {
// LinkedIn returned an error
//print $_GET['error'] . ': ' . $_GET['error_description'];
//exit;
} elseif (isset($_GET['code'])) {
// User authorized your application
if ($_SESSION['state'] == $_GET['state']) {
// Get token so you can make API calls
getAccessToken();
} else {
// CSRF attack? Or did you mix up your states?
exit;
}
} elseif (isset($_GET['logout'])) {
$_SESSION = array();
} elseif (isset($_GET['login'])) {
if ((empty($_SESSION['expires_at'])) || (time() > $_SESSION['expires_at'])) {
// Token has expired, clear the state
$_SESSION = array();
}
if (empty($_SESSION['access_token'])) {
// Start authorization process
getAuthorizationCode();
}
else {
print "?";
}
}
$user = fetch('GET', '/v1/people/~:(firstName,lastName)');
if ($user->firstName == '' && $user->lastName == '') {
print "Please <a href='" . $_SERVER['PHP_SELF'] . "?login=1'>log into linkedin</a> before registration.<br/>";
exit;
} else {
header("Location: $reg_url");
//echo file_get_contents($reg_url);
//exit;
//print "Hello $user->firstName $user->lastName. Click here to go to <a href='$reg_url'>registration form</a>.";
//print "<br/><a href='" . $_SERVER['PHP_SELF'] . "?logout=1'>logout</a>";
exit;
}
function getAuthorizationCode() {
$_SESSION['state'] = uniqid('', true); // unique long string.
$params = array('response_type' => 'code',
'client_id' => API_KEY,
'scope' => SCOPE,
'state' => $_SESSION['state'],
'redirect_uri' => REDIRECT_URI,
);
// Authentication request
$url = 'https://www.linkedin.com/uas/oauth2/authorization?' . http_build_query($params);
// Needed to identify request when it returns to us
$_SESSION['state'] = $params['state'];
// Redirect user to authenticate
header("Location: $url");
exit;
}
function getAccessToken() {
$params = array('grant_type' => 'authorization_code',
'client_id' => API_KEY,
'client_secret' => API_SECRET,
'code' => $_GET['code'],
'redirect_uri' => REDIRECT_URI,
);
// Access Token request
$url = 'https://www.linkedin.com/uas/oauth2/accessToken?' . http_build_query($params);
// Tell streams to make a POST request
$context = stream_context_create(
array('http' =>
array('method' => 'POST',
)
)
);
// Retrieve access token information
$response = file_get_contents($url, false, $context);
// Native PHP object, please
$token = json_decode($response);
// Store access token and expiration time
$_SESSION['access_token'] = $token->access_token; // guard this!
$_SESSION['expires_in'] = $token->expires_in; // relative time (in seconds)
$_SESSION['expires_at'] = time() + $_SESSION['expires_in']; // absolute time
return true;
}
function fetch($method, $resource, $body = '') {
$params = array('oauth2_access_token' => $_SESSION['access_token'],
'format' => 'json',
);
// Need to use HTTPS
$url = 'https://api.linkedin.com' . $resource . '?' . http_build_query($params);
// Tell streams to make a (GET, POST, PUT, or DELETE) request
$context = stream_context_create(
array('http' =>
array('method' => $method,
)
)
);
// Hocus Pocus
$response = file_get_contents($url, false, $context);
// Native PHP object, please
return json_decode($response);
}
?>
Now working with some friends on a registration function.
I just learned Google forms is a convenient tool to create simple registration forms. You can do it from either Google Forms or Google Drive. This form will be linked to a spreadsheet online, such that all records are stored there.
The submission of a form is much better if it 1) sends a confirmation email, and also 2) include a link to edit the submission. It's also good to 3) have a dashboard that displays submitted information, which you often want an interface independent from the spreadsheet. These can all be done with Google Forms API.
1) and 2) need writing a javascript function.
See Email confirmations from Google Forms for how to set up a script triggered by form submission action.
This is my code to include edit link:
function myFunction(e) {
if (typeof e == 'undefined') {
Logger.log("e is undefined");
return;
}
//var userName = e.values[1];
//var userEmail = e.values[2];
var userName = e.namedValues["Name"][0]; // From a field whose name is "Name".
var userEmail = e.namedValues["Email"][0]; // From a field whose name is "Email".
if (userEmail == '') return;
var subject = "Form Submitted";
var form = FormApp.openById('[form id]');
var formResponses = form.getResponses(); // All responses/rows in spreadsheet.
var formResponse = formResponses[formResponses.length-1]; // Get the just submitted item - last row.
//Logger.log("formResponses.length = " + formResponses.length);
var message = "Thank you, " + userName + " for finishing the survey.\n\n";
message += "You can see the current list at [dash board page link]\n\n";
message += "You can edit your information at: " + formResponse.getEditResponseUrl() + "\n\n";
message += "Have a good day.";
MailApp.sendEmail (userEmail, subject, message);
}
Note in the code above, the "form id" must be the id of the form, and not the spreadsheet. The code itself it a code of the spreadsheet.
Here is another piece of code that works equally well, but should be embedded in the form, and not the spreadsheet. This code is better in that it does not need to specify any form id. I prefer this one.
function onFormSubmit(e) {
if (typeof e == 'undefined') {
Logger.log("e is undefined");
return;
}
var itemResponses = e.response.getItemResponses();
/*
for (var i = 0; i < itemResponses.length; i++) {
var itemResponse = itemResponses[i];
Logger.log('Response #%s to the question "%s" was "%s"',
(i + 1).toString(),
itemResponse.getItem().getTitle(),
itemResponse.getResponse());
}
*/
var subject = "Form Submitted";
var userName = itemResponses[0].getResponse();
var userEmail = itemResponses[4].getResponse();
var message = "Thank you, " + userName + " for finishing the survey.\n\n";
message += "You can edit your information at: " + e.response.getEditResponseUrl() + "\n\n";
message += "Have a good day.";
MailApp.sendEmail (userEmail, subject, message);
}
3) Displays submitted information not using the spreadsheet.
See Query a Google Spreadsheet like a Database with Google Visualization API Query Language. This shows how to display a table containing selected spreadsheet columns. The link will be below (replace group id and group id with your values):
https://docs.google.com/spreadsheets/d/[form id]/gviz/tq?tqx=out:html&tq&gid=[group id]
If you want to display only selected columns, e.g., columns A and B, you can specify this with the tq parameter: tq=SELECT+A,B, so the links becomes:
https://docs.google.com/spreadsheets/d/[form id]/gviz/tq?tqx=out:html&tq=SELECT+A,B&gid=[group id]
One concern of giving people this link is security: they can modify the value of tq to see all fields. To overcome this is easy: set up a php page that read in the contents and display, this way the url is hidden. It is also really easy to set up, just 1 line of php code is needed:
<?php
echo file_get_contents("https://docs.google.com/spreadsheets/d/[form id]/gviz/tq?tqx=out:html&tq=SELECT+A,B&gid=[group id]");
?>
== Reliability issue ==
Well, it seems the script in google forms are not always reliably triggered. The above code stops to function without any reason. Searched on line for "google form script trigger not reliable", it seems many other people had similar experience. Free lunch is not always tasty.
== Sign in with Linkedin ==
Say you want people to fill the above form, but not everyone, only those who registered with linked in. So what you do is to set up a page that requires linkedin authentication, then forward people to the above link. For details, see [1][2][3][4].
Following example code in [4]. The code to set up such a page is in appendix.
Note that the Google form itself is not protected by session. So if anyone knows the url of the form, he will be able to register the form. I have not studied about ways to do this. There may not be a way of doing it, since it's not a full-fledged website anyway. One can change the setting of make a Google form private/public or accessible to only some people, that's what you can do if you don't want it public.
References:
[1] Sign In With LinkedIn
[2] Linkedin authentication documentation - Important. [3] below is linked from here.
[3] Linkedin developer network - Register here to get a linkedin application account. Important.
[4] Linkedin authentication code sample in PHP - Useful
Appendix. Authentication with Linkedin.
<?php
// Change these 5 fields.
define('API_KEY', '...');
define('API_SECRET', '...');
define('REDIRECT_URI', 'http://...');
define('SCOPE', ''); //r_fullprofile r_emailaddress rw_nus');
$reg_url = "https://docs.google.com/forms/d/[form id]/viewform?c=0&w=1&usp=mail_form_link";
// You'll probably use a database
session_name('linkedin');
session_start();
$user = fetch('GET', '/v1/people/~:(firstName,lastName)');
// OAuth 2 Control Flow
if (isset($_GET['error'])) {
// LinkedIn returned an error
//print $_GET['error'] . ': ' . $_GET['error_description'];
//exit;
} elseif (isset($_GET['code'])) {
// User authorized your application
if ($_SESSION['state'] == $_GET['state']) {
// Get token so you can make API calls
getAccessToken();
} else {
// CSRF attack? Or did you mix up your states?
exit;
}
} elseif (isset($_GET['logout'])) {
$_SESSION = array();
} elseif (isset($_GET['login'])) {
if ((empty($_SESSION['expires_at'])) || (time() > $_SESSION['expires_at'])) {
// Token has expired, clear the state
$_SESSION = array();
}
if (empty($_SESSION['access_token'])) {
// Start authorization process
getAuthorizationCode();
}
else {
print "?";
}
}
$user = fetch('GET', '/v1/people/~:(firstName,lastName)');
if ($user->firstName == '' && $user->lastName == '') {
print "Please <a href='" . $_SERVER['PHP_SELF'] . "?login=1'>log into linkedin</a> before registration.<br/>";
exit;
} else {
header("Location: $reg_url");
//echo file_get_contents($reg_url);
//exit;
//print "Hello $user->firstName $user->lastName. Click here to go to <a href='$reg_url'>registration form</a>.";
//print "<br/><a href='" . $_SERVER['PHP_SELF'] . "?logout=1'>logout</a>";
exit;
}
function getAuthorizationCode() {
$_SESSION['state'] = uniqid('', true); // unique long string.
$params = array('response_type' => 'code',
'client_id' => API_KEY,
'scope' => SCOPE,
'state' => $_SESSION['state'],
'redirect_uri' => REDIRECT_URI,
);
// Authentication request
$url = 'https://www.linkedin.com/uas/oauth2/authorization?' . http_build_query($params);
// Needed to identify request when it returns to us
$_SESSION['state'] = $params['state'];
// Redirect user to authenticate
header("Location: $url");
exit;
}
function getAccessToken() {
$params = array('grant_type' => 'authorization_code',
'client_id' => API_KEY,
'client_secret' => API_SECRET,
'code' => $_GET['code'],
'redirect_uri' => REDIRECT_URI,
);
// Access Token request
$url = 'https://www.linkedin.com/uas/oauth2/accessToken?' . http_build_query($params);
// Tell streams to make a POST request
$context = stream_context_create(
array('http' =>
array('method' => 'POST',
)
)
);
// Retrieve access token information
$response = file_get_contents($url, false, $context);
// Native PHP object, please
$token = json_decode($response);
// Store access token and expiration time
$_SESSION['access_token'] = $token->access_token; // guard this!
$_SESSION['expires_in'] = $token->expires_in; // relative time (in seconds)
$_SESSION['expires_at'] = time() + $_SESSION['expires_in']; // absolute time
return true;
}
function fetch($method, $resource, $body = '') {
$params = array('oauth2_access_token' => $_SESSION['access_token'],
'format' => 'json',
);
// Need to use HTTPS
$url = 'https://api.linkedin.com' . $resource . '?' . http_build_query($params);
// Tell streams to make a (GET, POST, PUT, or DELETE) request
$context = stream_context_create(
array('http' =>
array('method' => $method,
)
)
);
// Hocus Pocus
$response = file_get_contents($url, false, $context);
// Native PHP object, please
return json_decode($response);
}
?>
Subscribe to:
Posts (Atom)
Blog Archive
-
▼
2026
(36)
-
▼
June
(19)
- C10K to C10M: from thread-per-connection model to ...
- Benchmark server performance
- Application server for php, python, java, node.js,...
- Application server for C++, Go and Rust
- Nginx as reverse proxy and load balancer
- Infrastructure running: nginx, apache, php, python...
- Flow chart of nginx+apache+uvcorn infrastructure
- Flow chart of apache+uvcorn infrastructure
- Uvicorn and Gunicorn
- What's deadsnakes PPA
- What's the optional lsb-core package
- Codex known logging bug
- Daemonsize a service
- Train text to image model, to generate images of c...
- Train a model based on OpenAI API
- Open port 8080 for WebSocket
- Add websocket support on Bluehost Ubuntu VPS for D...
- Install Claude Code on ubuntu VPS of Bluehost
- Install PostgreSQL on Mac
-
▼
June
(19)
