Commit 5128d77a authored by GI's avatar GI

Merge branch 'master' into bootstrap4

parents cfef5065 ff6e78a4
Pipeline #50662180 failed with stages
in 1 minute and 11 seconds
...@@ -2,3 +2,4 @@ ...@@ -2,3 +2,4 @@
/src/research/papers/ /src/research/papers/
*.webm *.webm
*.mp4 *.mp4
grades.csv
...@@ -58,7 +58,7 @@ def cleanup_outpath( site ): ...@@ -58,7 +58,7 @@ def cleanup_outpath( site ):
# Allow .git in root of local out, to compare changes in # Allow .git in root of local out, to compare changes in
# generated output # generated output
elif dev_env != 'local' or name != '.git': elif dev_env != 'local' or not name.startswith('.git'):
# See if was generated from .md # See if was generated from .md
if not p.exists( p.join( site.searchpath, if not p.exists( p.join( site.searchpath,
p.splitext(name)[0] + '.md' ) ): p.splitext(name)[0] + '.md' ) ):
......
...@@ -97,8 +97,8 @@ class mdconverter: ...@@ -97,8 +97,8 @@ class mdconverter:
label = os.path.basename(text) label = os.path.basename(text)
# Check if the path exists # Check if the path exists
(f, cdir) = self.site.get_cdir( self.current_context, link ) f = self.path_exists( self.current_context, link )
if not os.path.exists( os.path.join( cdir, f ) ): if f is None:
self.site.logger.warn( '%s WARNING: Broken link "%s"' self.site.logger.warn( '%s WARNING: Broken link "%s"'
% (self.current_context['name'], link) ) % (self.current_context['name'], link) )
elif sep < 0: elif sep < 0:
......
...@@ -16,7 +16,8 @@ author_office_phone: 412 268 8419 ...@@ -16,7 +16,8 @@ author_office_phone: 412 268 8419
[production] [production]
# Options only set in the production environment. # Options only set in the production environment.
site_host: www.math.cmu.edu site_host: www.math.cmu.edu
site_prefix: /~gautam/sj path_prefix: sj
site_prefix: /~gautam/%(path_prefix)s
site_rurl: //%(site_host)s%(site_prefix)s site_rurl: //%(site_host)s%(site_prefix)s
site_url: http:%(site_rurl)s site_url: http:%(site_rurl)s
site_surl: https:%(site_rurl)s site_surl: https:%(site_rurl)s
...@@ -25,6 +26,7 @@ upload: rsync --checksum --delete -av out-prod/ qwe1:WWW/sj/ ...@@ -25,6 +26,7 @@ upload: rsync --checksum --delete -av out-prod/ qwe1:WWW/sj/
[local] [local]
# Options only set in the local testing environment. # Options only set in the local testing environment.
site_host: localhost:8000 site_host: localhost:8000
path_prefix:
site_prefix: site_prefix:
site_rurl: //%(site_host)s site_rurl: //%(site_host)s
site_url: http:%(site_rurl)s%(site_prefix)s site_url: http:%(site_rurl)s%(site_prefix)s
......
teaching/2018-19/269-vector-analysis
\ No newline at end of file
# Shibboleth header
AuthType Shibboleth
ShibRequireSession On
ShibApplicationId default
ShibExportAssertion On
# For any authenticated user. (Need not be a CMU member)
#Require Shibboleth
# Specific user id
#require user gi1242@andrew.cmu.edu
# Groups.
#require shib-attr affiliation Faculty@andrew.cmu.edu
#require shib-attr affiliation Staff@andrew.cmu.edu
#require shib-attr affiliation Member@andrew.cmu.edu
require shib-user ~ ^.+@andrew\.cmu\.edu$
title: test
## CGI Output
<pre id='form-output'></pre>
<script>
window.addEventListener('DOMContentLoaded', function() {
$.get( "{{get_link('/cgi-bin/auth/test.py')}}",
'{{dirname}}/grades.csv',
function(data) {
$('#form-output').text(data);
}
);
});
</script>
...@@ -213,6 +213,7 @@ The status message should tell you what files are conflicted, and what you shoul ...@@ -213,6 +213,7 @@ The status message should tell you what files are conflicted, and what you shoul
You can fix these conflicts by opening these files in your editor, and searching for the conflict markers `<<<<<<<`, `=======`, `>>>>>>>`. You can fix these conflicts by opening these files in your editor, and searching for the conflict markers `<<<<<<<`, `=======`, `>>>>>>>`.
Here's what it will typically look like: Here's what it will typically look like:
<pre class='codehilite'> <pre class='codehilite'>
In 1875, Galton and Watson~\cite{WatsonGalton75} took up an investigation into the phenomenon In 1875, Galton and Watson~\cite{WatsonGalton75} took up an investigation into the phenomenon
of ``the decay of the families of men who occupied conspicuous positions in past times.'' of ``the decay of the families of men who occupied conspicuous positions in past times.''
......
...@@ -25,7 +25,7 @@ LaTeX can be used between `$` signs: ...@@ -25,7 +25,7 @@ LaTeX can be used between `$` signs:
## WikiLinks ## WikiLinks
* `[[file]]` generates a link to `file`. * `[[file]]` generates a link to `file`.
* To specify the link text use `[[/index.html|Test]]`, which links to `/index.html` and says `Test`. * To specify the link text use `[[/index.md|Test]]`, which links to `/index.html` and says `Test`.
This renders like this: [[/index.html|Test]] This renders like this: [[/index.html|Test]]
* For `markdown` files, an extension of `.md` is replaced with `.html`. * For `markdown` files, an extension of `.md` is replaced with `.html`.
Also, if the link text is omitted the title of the linked file is used instead. Also, if the link text is omitted the title of the linked file is used instead.
......
...@@ -4,7 +4,7 @@ breadcrumb: /{{dirname}}.md ...@@ -4,7 +4,7 @@ breadcrumb: /{{dirname}}.md
<div class='alert alert-info' role='alert' markdown="1"> <div class='alert alert-info' role='alert' markdown="1">
This markdown cheat cheat sheet is taken from [gist jonschlinkert/5854601](https://gist.github.com/jonschlinkert/5854601), and is used as a test of markdown rendering on this site. This markdown cheat cheat sheet is taken from [gist jonschlinkert/5854601](https://gist.github.com/jonschlinkert/5854601), and is used as a test of markdown rendering on this site.
Site specific extensions are [[README.md|here]]. Site specific extensions are [[local-extensions.md|here]].
</div> </div>
......
page: 20140129-wordpress.md
subject: Good post
name: Venkat
email: venkannababugadde@gmail.com
avatar: http://cdn.libravatar.org/avatar/083d67aa888df13b1c6e1e6c0003d943
date: 2019-01-28 06:11:12 EST
ip: 49.207.228.182
referer: http://www.math.cmu.edu/~gautam/sj/blog/20140129-wordpress.html
Thanks for sharing your valuable information. This is a very useful and informative post.
...@@ -8,4 +8,4 @@ As an alternative to the SOCKS proxy, you could set up an [SSH tunnel](http://ma ...@@ -8,4 +8,4 @@ As an alternative to the SOCKS proxy, you could set up an [SSH tunnel](http://ma
`ssh you@campus.machine.edu -L <port>:paywalledjournal.com:<port>` `ssh you@campus.machine.edu -L <port>:paywalledjournal.com:<port>`
(With <port> for HTTP access usually being 80.) (With `<port>` for HTTP access usually being 80.)
page: 20150324-bibtex-reverse.md
subject: Location: where to save
name: Ankit
email: bholu9837@zoho.com
avatar: http://cdn.libravatar.org/avatar/6125e9f7ab603b02c94c46ff9c09922d
date: 2019-02-25 22:07:07 EST
ip: 174.99.9.115
referer: http://www.math.cmu.edu/~gautam/sj/blog/20150324-bibtex-reverse.html
Just put the .bst file in the folder where .tex file is located.
page: 20160406-dropbox-git.md
subject: Dropbox exclude
name: SSJ
email: unknown@email.id
avatar: http://cdn.libravatar.org/avatar/99daad327e93a79b9b1a785c70649146
date: 2019-02-25 15:56:00 EST
ip: 128.61.138.109
referer: http://www.math.cmu.edu/~gautam/sj/blog/20160406-dropbox-git.html
Hi,
Any idea how to get $ dropbox exclude add .git to work with windows? I have the windows dropbox app installed but cant seem to get the dropbox command to work in command line or gitbash
Thanks
page: 20160406-dropbox-git.md
subject: Re: Dropbox exclude
name: Gautam Iyer
email: gautam@math.cmu.edu
avatar: http://cdn.libravatar.org/avatar/0ec534a31b72b69338d0897211ec9925
date: 2019-02-27 15:11:59 EST
ip: 198.181.231.244
referer: http://www.math.cmu.edu/~gautam/sj/blog/20160406-dropbox-git.html
> Any idea how to get `$ dropbox exclude add .git` to work with windows?
I don't know how to do this on windows. But I'm guessing the windows Dropbox
client has a way to exclude files. You just need to get it to exclude the file
`.git` from being synced. But if you don't, it's not such a big deal -- the
file will be a plain text file which Dropbox can happily sync. Just ask your
collaborators to avoid modifying / deleting this file.
Options -Indexes
# Shibboleth header
AuthType Shibboleth
ShibRequireSession On
ShibApplicationId default
ShibExportAssertion On
# For any authenticated user. (Need not be a CMU member)
#Require Shibboleth
# Specific user id
#require user gi1242@andrew.cmu.edu
# Groups.
#require shib-attr affiliation Faculty@andrew.cmu.edu
#require shib-attr affiliation Staff@andrew.cmu.edu
#require shib-attr affiliation Member@andrew.cmu.edu
require shib-user ~ ^.+@andrew\.cmu\.edu$
#! /usr/bin/python3
#import cgitb; cgitb.enable()
import cgi, os, sys, csv, json, statistics
def tofloat(x):
try: return float(x)
except: return 0
print( "Content-type: application/json;\n" )
docroot = os.environ.get( 'CONTEXT_DOCUMENT_ROOT',
os.environ.get('PWD') )
fname = os.path.join( docroot, '{{path_prefix}}',
os.environ.get('QUERY_STRING') )
user = os.environ.get( 'eppn', '' )[:-len('@andrew.cmu.edu')]
{% if dev_env == 'local' %}
# No authentication; just impersonate someone for testing
user = 'afogelso'
{% endif %}
data = {}
try:
with open(fname) as f:
reader = csv.reader( f )
header = next( reader )
lname = header.index('Last Name')
fname = header.index('Preferred/First Name')
mi = header.index('MI')
andrewid = header.index('Andrew ID')
scores_start = max( lname, fname, mi, andrewid ) + 1
scores_end = len( header )
scores = [ [] for s in range(scores_start, scores_end) ]
# Put assignment titles in rows
data['rows'] = header[ scores_start:scores_end ]
# Put assignment scores, and statistics in columns
data['cols'] = []
for row in reader:
# Check for users score
if user == row[andrewid]:
data['cols'].append( ['Your Score']
+ row[scores_start:] + ['']*(scores_end - len(row)) )
data['name'] = row[fname] + ' ' + row[lname]
# Get non-zero HW scores in scores to compute statistics
if row[lname] and row[fname] and row[andrewid]:
for i, s in enumerate( row[scores_start:] ):
sf = tofloat(s)
if sf > 0: scores[i].append(sf)
# Check for total points
if row[lname] == '' and row[fname] == '':
# print( 'getgrades.py:', row, file=sys.stderr )
rf = map( tofloat, row[scores_start:scores_end] )
if all( s != 0 for s in rf ):
data['cols'].append( ['Out of']
+ row[scores_start:scores_end] )
# This is the last row we care about
break
data['cols'].append( ['# Submissions']
+ [len(s) for s in scores] )
data['cols'].append( ['Max']
+ [max(s) for s in scores] )
#data['cols'].append( ['Min']
# + [min(s) for s in scores] )
data['cols'].append( ['Median']
+ [round(statistics.median(s), 1) for s in scores] )
data['cols'].append( ['Mean']
+ [round(statistics.mean(s), 1) for s in scores] )
#data['cols'].append( ['Std. Dev']
# + [round(statistics.stdev(s), 2) for s in scores] )
#data['rows'] = [row for row in reader]
#data['scores'] = scores
except Exception as e:
data['error'] = str(e)
print( json.JSONEncoder().encode( data ) )
#! /usr/bin/python3
import cgitb; cgitb.enable()
import cgi, os, sys, csv
print( "Content-type: text/plain;\n" )
# Print values
print( 'Query: {}\n\n'.format( os.environ.get( 'QUERY_STRING', 'None' ) ) )
print( 'CGI Fields:' )
form = cgi.FieldStorage()
for k in form.keys():
print( '{}: {}'.format( k, form.getvalue(k) ) )
# Print environment (text)
print( '\n\nEnvironment:' )
for k, v in os.environ.items():
print( '{}: {}'.format(k, v) )
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
{%- macro course_info_table(c) -%} {%- macro course_info_table(c) -%}
{#- Import this using "with context", so all the variables are inherited #} {#- Import this using "with context", so all the variables are inherited #}
<table class='table' summary='Logistical Information'> <table class='table'>
<thead><tr><td colspan='2'></td></tr></thead> <thead><tr><td colspan='2'></td></tr></thead>
<tbody>{# Instructor, lectures {{{ #} <tbody>{# Instructor, lectures {{{ #}
<tr> <tr>
...@@ -153,6 +153,26 @@ ...@@ -153,6 +153,26 @@
{%- endif -%} {%- endif -%}
</td> </td>
</tr> </tr>
{%- if ta.name2 %}
<tr>
<th scope='row'>TA</th>
<td>
{%- if ta.url2 -%}
<a href='{{ta.url2}}'>{{ta.name2}}</a>.
{%- else -%}
{{ta.name2 | default('TBA')}}.
{%- endif -%}
{%- if ta.office2 -%}
&emsp;
{{ room( ta.office2.bldg, ta.office2.room ) }}
{%- endif -%}
{%- if ta.email2 -%}
&emsp;
{{ email( ta.email2 ) }}
{%- endif -%}
</td>
</tr>
{%- endif %}
{%- if ta.office_hours -%} {%- if ta.office_hours -%}
<tr> <tr>
<th scope='row'>Office Hours (TA)</th> <th scope='row'>Office Hours (TA)</th>
...@@ -215,11 +235,13 @@ ...@@ -215,11 +235,13 @@
{%- endfor -%} {%- endfor -%}
</tbody> </tbody>
{%- endif -%}{# }}} #} {%- endif -%}{# }}} #}
{%- if c.mailing_list or c.anonymous_list or c.blackboard or c.mediasite or c.links or c.canvas -%}{# Mailing list {{{ #} {#- Mailing list {{{ #}
{%- if c.mailing_list or c.anonymous_list or c.blackboard
or c.mediasite or c.links or c.discourse or c.canvas %}
<tbody> <tbody>
{%- if c.mailing_list -%} {%- if c.mailing_list -%}
<tr> <tr>
<th scope='row'>Mailing list</td> <th scope='row'>Mailing list</th>
<td> <td>
<a href='{{c.mailing_list.url}}'>{{c.mailing_list.name}}</a> <a href='{{c.mailing_list.url}}'>{{c.mailing_list.name}}</a>
<span class='small'> <span class='small'>
...@@ -236,6 +258,14 @@ ...@@ -236,6 +258,14 @@
</td> </td>
</tr> </tr>
{%- endif -%} {%- endif -%}
{%- if c.discourse -%}
<tr>
<th scope='row'>Discourse</th>
<td>
Class discussion board <a href="{{c.discourse}}">here</a>.
</td>
</tr>
{%- endif -%}
{%- if c.blackboard -%} {%- if c.blackboard -%}
<tr> <tr>
<th scope='row'>Blackboard</th> <th scope='row'>Blackboard</th>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
{% extends "layouts/md-nav-right-affix.j2" -%} {% extends "layouts/md-nav-right-affix.j2" -%}
{% block comments_block -%} {% block comments_block -%}
{% if comments != 'disabled' -%} {% if comments != 'disabled' -%}
<br /> <br>
<h3> <h3>
{{ glyph( 'comment' ) }}&emsp;Comments {{ glyph( 'comment' ) }}&emsp;Comments
</h3> </h3>
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<div class='media-body'> <div class='media-body'>
<h4 class='media-heading'> <h4 class='media-heading'>
{{meta.subject}} {{meta.subject}}
<br /> <br>
<small>{{meta.name|default('Anonymous')}} ({{meta.date}})</small> <small>{{meta.name|default('Anonymous')}} ({{meta.date}})</small>
</h4> </h4>
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
</div> </div>
</li> </li>
</ul> </ul>
<br /> <br>
{% endfor -%} {% endfor -%}
<h4> <h4>
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
{% block nav_after_toc -%} {% block nav_after_toc -%}
{{ super() }} {{ super() }}
<br /> <br>
<h4>{{glyph( 'bullhorn' )}}&emsp;Recent posts</h4> <h4>{{glyph( 'bullhorn' )}}&emsp;Recent posts</h4>
{% set files = glob( '/blog/[0-9]*.md' ) | sort( reverse=True ) -%} {% set files = glob( '/blog/[0-9]*.md' ) | sort( reverse=True ) -%}
<ol class='list-unstyled'> <ol class='list-unstyled'>
......
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
{% set active = 'teaching' -%} {% set active = 'teaching' -%}
{%- if path_exists( 'course_info.j2' ) -%} {%- if path_exists( 'course_info.j2' ) -%}
{%- from get_file('course_info.j2') import course -%} {%- from get_file('course_info.j2') import course -%}
{%- if not title %}{% set title = course.title %}{% endif -%} {%- if not title %}
{%- set title = course.title %}
{%- if course.semester %}{% set subtitle = course.semester %}{% endif %}
{%- endif -%}
{% set page_email = course.email -%} {% set page_email = course.email -%}
{# Set breadcrumb for level 1 pages -#} {# Set breadcrumb for level 1 pages -#}
{% if not breadcrumb and basename != 'index.md' -%} {% if not breadcrumb and basename != 'index.md' -%}
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
{% if title -%} {% if title -%}
<h1> <h1>
{{title}} {{title}}
{% if post_date(basename) -%} {% if subtitle or post_date(basename) -%}
<br /> <br>
<small>{{post_date(basename)}}</small> <small>{{subtitle | default(post_date(basename))}}</small>
{% endif -%} {% endif -%}
</h1> </h1>
{% endif -%} {% endif -%}
......
'use strict';
$(document).ready( function() { $(document).ready( function() {
/* Easy Bootstrap affix. Simply do: /* Easy Bootstrap affix. Simply do:
* *
...@@ -12,65 +14,64 @@ $(document).ready( function() { ...@@ -12,65 +14,64 @@ $(document).ready( function() {
* CSS to disable: .affix, .affix-bottom { position: static; } * CSS to disable: .affix, .affix-bottom { position: static; }
*/ */
$('[data-affix-after]').each( function() { $('[data-affix-after]').each( function() {
var elem = $(this); var elem = $(this);
var parent_panel = elem.parent(); var parent_panel = elem.parent();
var prev = $( elem.data('affix-after') ); var prev = $( elem.data('affix-after') );
if( prev.length != 1 ) { function resizeFn() {
/* Create a new element immediately before. */ /* Set the width to it's natural width in the parent. */
prev = elem.before( '<div></div>' ).prev(); var sideBarNavWidth = parent_panel.width()
} - parseInt(elem.css('paddingLeft'))
- parseInt(elem.css('paddingRight'))
- parseInt(elem.css('marginLeft'))
- parseInt(elem.css('marginRight'))
- parseInt(elem.css('borderLeftWidth'))
- parseInt(elem.css('borderRightWidth'));
elem.css('width', sideBarNavWidth);
var resizeFn = function() { elem.affix({
/* Set the width to it's natural width in the parent. */ offset: {
var sideBarNavWidth = parent_panel.width() top: prev.offset().top + prev.outerHeight(true),
- parseInt(elem.css('paddingLeft')) bottom: $('body>footer').outerHeight(true)
- parseInt(elem.css('paddingRight')) }
- parseInt(elem.css('marginLeft')) });
- parseInt(elem.css('marginRight'))
- parseInt(elem.css('borderLeftWidth'))
- parseInt(elem.css('borderRightWidth'));
elem.css('width', sideBarNavWidth);
elem.affix({
offset: {
top: prev.offset().top + prev.outerHeight(true),
bottom: $('body>footer').outerHeight(true)
}
});
elem.affix( 'checkPosition' ); elem.affix( 'checkPosition' );
}; }
resizeFn(); if( prev.length != 1 )
$(window).resize(resizeFn); /* Create a new element immediately before. */
prev = elem.before( '<div></div>' ).prev();
$('[data-spy~="scroll"]').each( function() { resizeFn();
$(this).on('activate.bs.scrollspy', function() { $(window).resize(resizeFn);
elem.affix( 'checkPosition' );
});
});
});
$('[data-spy~="scroll"]').each( function() {
$(this).on('activate.bs.scrollspy', function() {
elem.affix( 'checkPosition' );
});
});
}); /* end $('[data-affix-after]') */
}); /* end ready */ }); /* end ready */
function submit_comment() { function submit_comment() {
$('#comment-failed,#comment-form,#comment-success').addClass('hidden'); function failfn(data) {
$('#comment-sending').removeClass( 'hidden' );
var failfn = function(data) {
$('#comment-form,#comment-sending').addClass('hidden'); $('#comment-form,#comment-sending').addClass('hidden');
$('#comment-failed,#comment-form').removeClass( 'hidden' ); $('#comment-failed,#comment-form').removeClass( 'hidden' );
{% if dev_env == 'local' %} {% if dev_env == 'local' %}
$('#comment-failed').append('<pre>' + data + '</pre>'); $('#comment-failed').append('<pre>' + data + '</pre>');
{% endif %} {% endif %}
}; }
$('#comment-failed,#comment-form,#comment-success').addClass('hidden');
$('#comment-sending').removeClass( 'hidden' );
$.post( "{{get_link('/cgi-bin/comment.py')}}", $.post( "{{get_link('/cgi-bin/comment.py')}}",
$('#comment-form > form').serializeArray() ) $('#comment-form > form').serializeArray() )
.done( function( data ) { .done( function( data ) {
if( data.trim() == "OK" ) { if( data.trim() == "OK" ) {
$('#comment-failed,#comment-form,#comment-sending').addClass('hidden'); $('#comment-failed,#comment-form,#comment-sending')
.addClass('hidden');
$('#comment-success').removeClass( 'hidden' ); $('#comment-success').removeClass( 'hidden' );
} }
else failfn(data); else failfn(data);
...@@ -78,3 +79,62 @@ function submit_comment() { ...@@ -78,3 +79,62 @@ function submit_comment() {
) )
.fail( failfn ); .fail( failfn );
} }
function get_grades(fname) {
function failfn( msg ) {
$('#status')
.attr( 'class', 'alert alert-danger' )
.text( 'Sorry, an error occurred.' );
{%- if dev_env == 'local' %}
$('#status').append( ' Error: ' +
(typeof(msg) === 'object' && msg.hasOwnProperty('statusText') ?
msg.statusText : msg ));
/* For debugging */
get_grades.errMsg = msg;
{%- endif -%}
}
$.getJSON( "{{get_link('/cgi-bin/auth/getgrades.py')}}",
fname, function(data) {
var table = $('#scores');
var thead = table.children('thead').children();
var tbody = table.children('tbody');
{% if dev_env == 'local' -%}
/* Save for debugging */
get_grades.data = data;
{% endif -%}
if( data.hasOwnProperty('error') )
failfn( data.error );
else {
$('#status').addClass( 'hidden' );
if( data.name )
$('#student-name').text(data.name).wrap('<strong></strong>');
else
$('#status')
.attr( 'class', 'alert alert-warning' )
.text( 'Your scores were not found!' );
// for( let c of data.cols ) throws an error in IE
for ( let i=0; i < data.cols.length; i++ ) {
thead.append( '<th>' + data.cols[i][0] + '</th>' );
/* console.log( '<td>' + c[0] + '</td>' ); */
};
for ( let i=0; i < data.rows.length; i++ ) {
let tr = $('<tr></tr>').appendTo(tbody);
tr.append('<th>' + data.rows[i] + '</th>' );
for( let j=0; j < data.cols.length; j++ )
tr.append( '<td>' + data.cols[j][i+1] + '</td>' );
}
/* $('#form-output').text(JSON.stringify(data, null, 2)); */
}
}
) /* getJSON */
.fail(failfn)
;
}