html5 mobile boilerplate template. closes #119
|
@ -1,6 +1,7 @@
|
|||
class Middleman::Templates::Html5 < Middleman::Templates::Base
|
||||
class_option :css_dir, :default => "css"
|
||||
class_option :js_dir, :default => "js"
|
||||
class_option :images_dir, :default => "img"
|
||||
|
||||
def self.source_root
|
||||
File.dirname(__FILE__)
|
||||
|
|
17
lib/middleman/templates/mobile.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class Middleman::Templates::Mobile < Middleman::Templates::Base
|
||||
class_option :css_dir, :default => "css"
|
||||
class_option :js_dir, :default => "js"
|
||||
class_option :images_dir, :default => "img"
|
||||
|
||||
def self.source_root
|
||||
File.dirname(__FILE__)
|
||||
end
|
||||
|
||||
def build_scaffold
|
||||
template "shared/config.tt", File.join(location, "config.rb")
|
||||
directory "mobile/source", File.join(location, "source")
|
||||
empty_directory File.join(location, "source")
|
||||
end
|
||||
end
|
||||
|
||||
Middleman::Templates.register(:mobile, Middleman::Templates::Mobile)
|
38
lib/middleman/templates/mobile/source/404.html
Executable file
|
@ -0,0 +1,38 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Page Not Found :(</title>
|
||||
<style>
|
||||
body { text-align: center;}
|
||||
h1 { font-size: 50px; text-align: center }
|
||||
span[frown] { transform: rotate(90deg); display:inline-block; color: #bbb; }
|
||||
body { font: 20px Constantia, 'Hoefler Text', "Adobe Caslon Pro", Baskerville, Georgia, Times, serif; color: #999; text-shadow: 2px 2px 2px rgba(200, 200, 200, 0.5); }
|
||||
::-moz-selection{ background:#FF5E99; color:#fff; }
|
||||
::selection { background:#FF5E99; color:#fff; }
|
||||
article {display:block; text-align: left; width: 500px; margin: 0 auto; }
|
||||
|
||||
a { color: rgb(36, 109, 56); text-decoration:none; }
|
||||
a:hover { color: rgb(96, 73, 141) ; text-shadow: 2px 2px 2px rgba(36, 109, 56, 0.5); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Not found <span frown>:(</span></h1>
|
||||
<div>
|
||||
<p>Sorry, but the page you were trying to view does not exist.</p>
|
||||
<p>It looks like this was the result of either:</p>
|
||||
<ul>
|
||||
<li>a mistyped address</li>
|
||||
<li>an out-of-date link</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),
|
||||
GOOG_FIXURL_SITE = location.host;
|
||||
</script>
|
||||
<script src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
64
lib/middleman/templates/mobile/source/README.markdown
Executable file
|
@ -0,0 +1,64 @@
|
|||
#Mobile Boilerplate http://html5boilerplate.com
|
||||
v1.0 (code named Secret Diary)
|
||||
|
||||
##Summary:
|
||||
|
||||
This is a set of features made specifically for mobile development, with following included:
|
||||
|
||||
###Markup:
|
||||
Home screen icon (Android, iOS, Symbian)
|
||||
CSS class target IE Mobile 7
|
||||
Cross browser viewport optimization (Android, iOS, Mobile IE, Blackberry)
|
||||
Optimized viewport scaling (Android, iOS, Mobile IE, Blackberry)
|
||||
IE Mobile better Font rendering
|
||||
Prevent scaling
|
||||
iPhone full screen mode
|
||||
|
||||
###CSS:
|
||||
Mobile helper class
|
||||
Prevent text resize in ie/webkit browser
|
||||
Prevent callout
|
||||
HTML5 contenteditable attribute on mobile
|
||||
S60 3.x and 5.0 devices which animated gif fix
|
||||
Text overflow with ellipsis
|
||||
Mobile optimized default CSS
|
||||
|
||||
###JavaScript:
|
||||
Cross browser CSS media queries
|
||||
Textarea autogrow
|
||||
Hide webkit chrome
|
||||
Insant button
|
||||
Firebug lite debugger
|
||||
Media queries for low end smartphone
|
||||
|
||||
###Server:
|
||||
Added Blackberry MIME type
|
||||
Added Nokia MIME type
|
||||
Prevent Transcoding
|
||||
Mobile site redirection
|
||||
|
||||
###General:
|
||||
HTML5 offline caching for smartphone
|
||||
Mobile sitemap
|
||||
Mobile bookmark bubble
|
||||
Browser Database Wrapper API
|
||||
User Agent Detection
|
||||
GA for low end mobile devices
|
||||
Mobile build tool
|
||||
|
||||
|
||||
|
||||
##License:
|
||||
|
||||
###Major components:
|
||||
css3-mediaqueries.js: Public Domain<br />
|
||||
Bookmark bubble library: Apache License, Version 2.0<br />
|
||||
Web Storage Portability Layer: Apache License, Version 2.0<br />
|
||||
Modernizr: MIT/BSD license<br />
|
||||
jQuery: MIT/GPL license<br />
|
||||
HTML5Doctor CSS reset: Creative Commons 3.0 <br />
|
||||
CSS Reset Reloaded: Public Domain
|
||||
|
||||
###Everything else:
|
||||
|
||||
The Unlicense (aka: public domain)
|
25
lib/middleman/templates/mobile/source/crossdomain.xml
Executable file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
|
||||
|
||||
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
|
||||
|
||||
<!-- Most restrictive policy: -->
|
||||
<site-control permitted-cross-domain-policies="none"/>
|
||||
|
||||
|
||||
|
||||
<!-- Least restrictive policy: -->
|
||||
<!--
|
||||
<site-control permitted-cross-domain-policies="all"/>
|
||||
<allow-access-from domain="*" to-ports="*" secure="false"/>
|
||||
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
|
||||
-->
|
||||
<!--
|
||||
If you host a crossdomain.xml file with allow-access-from domain=“*”
|
||||
and don’t understand all of the points described here, you probably
|
||||
have a nasty security vulnerability. ~ simon willison
|
||||
-->
|
||||
|
||||
</cross-domain-policy>
|
236
lib/middleman/templates/mobile/source/css/style.css
Executable file
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* HTML5 ✰ Boilerplate
|
||||
*
|
||||
* style.css contains a reset, font normalization and some base styles.
|
||||
*
|
||||
* Credit is left where credit is due.
|
||||
* Much inspiration was taken from these projects:
|
||||
* - yui.yahooapis.com/2.8.1/build/base/base.css
|
||||
* - camendesign.com/design/
|
||||
* - praegnanz.de/weblog/htmlcssjs-kickstart
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
|
||||
* v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
|
||||
* html5doctor.com/html-5-reset-stylesheet/
|
||||
*/
|
||||
|
||||
html, body, div, span, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
|
||||
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
blockquote, q { quotes: none; }
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after { content: ""; content: none; }
|
||||
|
||||
ins { background-color: #ff9; color: #000; text-decoration: none; }
|
||||
|
||||
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
|
||||
|
||||
del { text-decoration: line-through; }
|
||||
|
||||
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
|
||||
|
||||
table { border-collapse: collapse; border-spacing: 0; }
|
||||
|
||||
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
|
||||
|
||||
input, select { vertical-align: middle; }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
|
||||
*/
|
||||
|
||||
|
||||
body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
|
||||
select, input, textarea, button { font:99% sans-serif; }
|
||||
|
||||
/* Normalize monospace sizing:
|
||||
en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
|
||||
pre, code, kbd, samp { font-family: monospace, sans-serif; }
|
||||
|
||||
|
||||
/**
|
||||
* Minimal base styles.
|
||||
*/
|
||||
|
||||
/* Prevent mobile zooming while remain desktop zooming: github.com/shichuan/mobile-html5-boilerplate/issues/closed#issue/14 */
|
||||
html { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
|
||||
|
||||
/* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */
|
||||
a:hover, a:active { outline: none; }
|
||||
|
||||
ul, ol { margin-left: 2em; }
|
||||
ol { list-style-type: decimal; }
|
||||
|
||||
/* Remove margins for navigation lists */
|
||||
nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
|
||||
|
||||
small { font-size: 85%; }
|
||||
strong, th { font-weight: bold; }
|
||||
|
||||
td { vertical-align: top; }
|
||||
|
||||
/* Set sub, sup without affecting line-height: gist.github.com/413930 */
|
||||
sub, sup { font-size: 75%; line-height: 0; position: relative; }
|
||||
sup { top: -0.5em; }
|
||||
sub { bottom: -0.25em; }
|
||||
|
||||
pre {
|
||||
/* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */
|
||||
white-space: pre; white-space: pre-wrap; word-wrap: break-word;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */
|
||||
|
||||
.iem7 legend { margin-left: -7px; }
|
||||
|
||||
/* Align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */
|
||||
input[type="radio"] { vertical-align: text-bottom; }
|
||||
input[type="checkbox"] { vertical-align: bottom; }
|
||||
.iem7 input[type="checkbox"] { vertical-align: baseline; }
|
||||
|
||||
/* Hand cursor on clickable input elements */
|
||||
label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
|
||||
|
||||
/* Webkit browsers add a 2px margin outside the chrome of form elements */
|
||||
button, input, select, textarea { margin: 0; }
|
||||
|
||||
/* Colors for form validity */
|
||||
input:valid, textarea:valid { }
|
||||
input:invalid, textarea:invalid {
|
||||
border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red;
|
||||
}
|
||||
.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
|
||||
|
||||
|
||||
/* These selection declarations have to be separate
|
||||
No text-shadow: twitter.com/miketaylr/status/12228805301
|
||||
Also: hot pink! */
|
||||
::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; }
|
||||
::selection { background:#FF5E99; color:#fff; text-shadow: none; }
|
||||
|
||||
/* j.mp/webkit-tap-highlight-color */
|
||||
a:link { -webkit-tap-highlight-color: #FF5E99; }
|
||||
|
||||
/* Make buttons play nice in IE:
|
||||
www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */
|
||||
button { width: auto; overflow: visible; }
|
||||
|
||||
/* Bicubic resizing for non-native sized IMG:
|
||||
code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */
|
||||
.iem7 img { -ms-interpolation-mode: bicubic; }
|
||||
|
||||
|
||||
/**
|
||||
* You might tweak these..
|
||||
*/
|
||||
|
||||
body, select, input, textarea {
|
||||
/* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */
|
||||
color: #444;
|
||||
/* Set your base font here, to apply evenly */
|
||||
/* font-family: Georgia, serif; */
|
||||
}
|
||||
|
||||
/* Headers (h1, h2, etc) have no default font-size or margin; define those yourself */
|
||||
h1, h2, h3, h4, h5, h6 { font-weight: bold; }
|
||||
|
||||
a, a:active, a:visited { color: #607890; }
|
||||
a:hover { color: #036; }
|
||||
|
||||
|
||||
/*
|
||||
* Helper classes
|
||||
*/
|
||||
|
||||
/* prevent callout */
|
||||
.nocallout {-webkit-touch-callout: none;}
|
||||
|
||||
/* Text overflow with ellipsis */
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* A hack for HTML5 contenteditable attribute on mobile */
|
||||
textarea.contenteditable {-webkit-appearance: none;}
|
||||
|
||||
/* A workaround for S60 3.x and 5.0 devices which do not animated gif images if they have been set as display: none */
|
||||
.gifhidden {position: absolute; left: -100%;}
|
||||
|
||||
/* For image replacement */
|
||||
.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
|
||||
|
||||
/* Hide for both screenreaders and browsers:
|
||||
css-discuss.incutio.com/wiki/Screenreader_Visibility */
|
||||
.hidden { display: none; visibility: hidden; }
|
||||
|
||||
/* Hide only visually, but have it available for screenreaders: by Jon Neal.
|
||||
www.webaim.org/techniques/css/invisiblecontent/ & j.mp/visuallyhidden */
|
||||
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
|
||||
/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: drupal.org/node/897638 */
|
||||
.visuallyhidden.focusable:active,
|
||||
.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
|
||||
|
||||
/* Hide visually and from screenreaders, but maintain layout */
|
||||
.invisible { visibility: hidden; }
|
||||
|
||||
/* The Magnificent Clearfix: Updated to prevent margin-collapsing on child elements.
|
||||
j.mp/bestclearfix */
|
||||
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
|
||||
.clearfix:after { clear: both; }
|
||||
/* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */
|
||||
.clearfix { zoom: 1; }
|
||||
|
||||
|
||||
/* Primary Styles for mobile
|
||||
Author:
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Media queries for responsive design https://github.com/shichuan/mobile-html5-boilerplate/wiki/The-Style
|
||||
*/
|
||||
|
||||
|
||||
/* Styles for desktop and large screen ----------- */
|
||||
|
||||
/*styles for 800px and up!*/
|
||||
@media only screen and (min-width: 800px) {
|
||||
/* Styles */
|
||||
}/*/mediaquery*/
|
||||
|
||||
|
||||
/* iPhone 4, Opera Mobile 11 and other high pixel ratio devices ----------- */
|
||||
@media
|
||||
only screen and (-webkit-min-device-pixel-ratio: 1.5),
|
||||
only screen and (-o-min-device-pixel-ratio: 3/2),
|
||||
only screen and (min-device-pixel-ratio: 1.5) {
|
||||
/* Styles */
|
||||
}
|
17
lib/middleman/templates/mobile/source/default.appcache
Executable file
|
@ -0,0 +1,17 @@
|
|||
CACHE MANIFEST
|
||||
# version 1
|
||||
img/l/apple-touch-icon.png
|
||||
img/l/apple-touch-icon-precomposed.png
|
||||
img/l/splash.png
|
||||
img/m/apple-touch-icon.png
|
||||
img/h/apple-touch-icon.png
|
||||
img/h/splash.png
|
||||
css/style.css
|
||||
js/libs/jquery-1.5.1.min.js
|
||||
js/libs/modernizr-custom.js
|
||||
js/libs/respond.min.js
|
||||
|
||||
NETWORK:
|
||||
#http://example.com/api/
|
||||
|
||||
FALLBACK:
|
43
lib/middleman/templates/mobile/source/humans.txt
Executable file
|
@ -0,0 +1,43 @@
|
|||
/* the humans responsible & colophon */
|
||||
/* humanstxt.org */
|
||||
|
||||
|
||||
/* TEAM */
|
||||
<your title>: <your name>
|
||||
Site:
|
||||
Twitter:
|
||||
Location:
|
||||
|
||||
/* THANKS */
|
||||
Names (& URL):
|
||||
|
||||
/* SITE */
|
||||
Standards: HTML5, CSS3
|
||||
Components: Modernizr, jQuery
|
||||
Software:
|
||||
|
||||
|
||||
|
||||
-o/-
|
||||
+oo//-
|
||||
:ooo+//:
|
||||
-ooooo///-
|
||||
/oooooo//:
|
||||
:ooooooo+//-
|
||||
-+oooooooo///-
|
||||
-://////////////+oooooooooo++////////////::
|
||||
:+ooooooooooooooooooooooooooooooooooooo+:::-
|
||||
-/+ooooooooooooooooooooooooooooooo+/::////:-
|
||||
-:+oooooooooooooooooooooooooooo/::///////:-
|
||||
--/+ooooooooooooooooooooo+::://////:-
|
||||
-:+ooooooooooooooooo+:://////:--
|
||||
/ooooooooooooooooo+//////:-
|
||||
-ooooooooooooooooooo////-
|
||||
/ooooooooo+oooooooooo//:
|
||||
:ooooooo+/::/+oooooooo+//-
|
||||
-oooooo/::///////+oooooo///-
|
||||
/ooo+::://////:---:/+oooo//:
|
||||
-o+/::///////:- -:/+o+//-
|
||||
:-:///////:- -:/://
|
||||
-////:- --//:
|
||||
-- -:
|
BIN
lib/middleman/templates/mobile/source/img/h/apple-touch-icon.png
Executable file
After Width: | Height: | Size: 3 KiB |
BIN
lib/middleman/templates/mobile/source/img/h/splash.png
Executable file
After Width: | Height: | Size: 17 KiB |
BIN
lib/middleman/templates/mobile/source/img/l/apple-touch-icon-precomposed.png
Executable file
After Width: | Height: | Size: 2.4 KiB |
BIN
lib/middleman/templates/mobile/source/img/l/apple-touch-icon.png
Executable file
After Width: | Height: | Size: 2.4 KiB |
BIN
lib/middleman/templates/mobile/source/img/l/splash.png
Executable file
After Width: | Height: | Size: 1.9 KiB |
BIN
lib/middleman/templates/mobile/source/img/m/apple-touch-icon.png
Executable file
After Width: | Height: | Size: 4.4 KiB |
95
lib/middleman/templates/mobile/source/index.html
Executable file
|
@ -0,0 +1,95 @@
|
|||
<!doctype html>
|
||||
<!-- Conditional comment for mobile ie7 http://blogs.msdn.com/b/iemobile/ -->
|
||||
<!-- Appcache Facts http://appcachefacts.info/ -->
|
||||
<!--[if IEMobile 7 ]> <html class="no-js iem7" manifest="default.appcache?v=1"> <![endif]-->
|
||||
<!--[if (gt IEMobile 7)|!(IEMobile)]><!--> <html class="no-js" manifest="default.appcache?v=1"> <!--<![endif]-->
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title></title>
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<!-- Mobile viewport optimization http://goo.gl/b9SaQ -->
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="MobileOptimized" content="320"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Home screen icon Mathias Bynens http://goo.gl/6nVq0 -->
|
||||
<!-- For iPhone 4 with high-resolution Retina display: -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="img/h/apple-touch-icon.png">
|
||||
<!-- For first-generation iPad: -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="img/m/apple-touch-icon.png">
|
||||
<!-- For non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
|
||||
<link rel="apple-touch-icon-precomposed" href="img/l/apple-touch-icon-precomposed.png">
|
||||
<!-- For nokia devices: -->
|
||||
<link rel="shortcut icon" href="img/l/apple-touch-icon.png">
|
||||
|
||||
<!--iOS web app, deletable if not needed -->
|
||||
<!--<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="apple-touch-startup-image" href="img/l/splash.png">-->
|
||||
|
||||
<!-- Mobile IE allows us to activate ClearType technology for smoothing fonts for easy reading -->
|
||||
<meta http-equiv="cleartype" content="on">
|
||||
|
||||
<!-- more tags for your 'head' to consider https://gist.github.com/849231 -->
|
||||
|
||||
<!-- Main Stylesheet -->
|
||||
<link rel="stylesheet" href="css/style.css?v=1">
|
||||
|
||||
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
|
||||
<script src="js/libs/modernizr-custom.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
<header>
|
||||
|
||||
</header>
|
||||
<div id="main" role="main">
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
</div> <!--! end of #container -->
|
||||
|
||||
|
||||
<!-- JavaScript at the bottom for fast page loading -->
|
||||
|
||||
<!-- Grab Google CDN's jQuery, with a protocol relative URL; fall back to local if necessary -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.js"></script>
|
||||
<script>window.jQuery || document.write("<script src='js/libs/jquery-1.5.1.min.js'>\x3C/script>")</script>
|
||||
|
||||
<!-- scripts concatenated and minified via ant build script -->
|
||||
<script src="js/mylibs/helper.js"></script>
|
||||
<!-- end concatenated and minified scripts-->
|
||||
|
||||
<script>
|
||||
// iPhone Scale Bug Fix, read this when using http://www.blog.highub.com/mobile-2/a-fix-for-iphone-viewport-scale-bug/
|
||||
MBP.scaleFix();
|
||||
|
||||
// Media Queries Polyfill https://github.com/shichuan/mobile-html5-boilerplate/wiki/Media-Queries-Polyfill
|
||||
yepnope({
|
||||
test : Modernizr.mq('(min-width)'),
|
||||
nope : ['js/libs/respond.min.js']
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Debugger - remove for production -->
|
||||
<!-- <script src="https://getfirebug.com/firebug-lite.js"></script> -->
|
||||
|
||||
<!-- mathiasbynens.be/notes/async-analytics-snippet Change UA-XXXXX-X to be your site's ID -->
|
||||
<script>
|
||||
var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.async=1;
|
||||
g.src=("https:"==location.protocol?"//ssl":"//www")+".google-analytics.com/ga.js";
|
||||
s.parentNode.insertBefore(g,s)}(document,"script"));
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
8316
lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.js
vendored
Executable file
16
lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.min.js
vendored
Executable file
14
lib/middleman/templates/mobile/source/js/libs/modernizr-custom.js
Executable file
7
lib/middleman/templates/mobile/source/js/libs/respond.min.js
vendored
Executable file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* respond.js - A small and fast polyfill for min/max-width CSS3 Media Queries
|
||||
* Copyright 2011, Scott Jehl, scottjehl.com
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* Usage: Check out the readme file or github.com/scottjehl/respond
|
||||
*/
|
||||
(function(d,g){d.respond={};respond.update=function(){};respond.mediaQueriesSupported=g;if(g){return}var s=d.document,q=s.documentElement,h=[],j=[],o=[],n={},f=30,e=s.getElementsByTagName("head")[0]||q,b=e.getElementsByTagName("link"),a=function(){var y=s.styleSheets,u=y.length;for(var x=0;x<u;x++){var w=y[x],v=w.href;if(!!v&&!n[v]){if(!/^([a-zA-Z]+?:(\/\/)?(www\.)?)/.test(v)||v.replace(RegExp.$1,"").split("/")[0]===d.location.host){var t=v;m(v,function(z){l(z,t);n[t]=true})}else{n[v]=true}}}},l=function(B,t){var z=B.match(/@media ([^\{]+)\{([\S\s]+?)(?=\}\/\*\/mediaquery\*\/)/gmi),C=z&&z.length||0,t=t.substring(0,t.lastIndexOf("/"));if(t.length){t+="/"}for(var w=0;w<C;w++){var x=z[w].match(/@media ([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,u=x.split(","),A=u.length;j.push(RegExp.$2&&RegExp.$2.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+t+"$2$3"));for(var v=0;v<A;v++){var y=u[v];h.push({media:y.match(/(only\s+)?([a-zA-Z]+)(\sand)?/)&&RegExp.$2,rules:j.length-1,minw:y.match(/\(min\-width:\s?(\s?[0-9]+)px\s?\)/)&&parseFloat(RegExp.$1),maxw:y.match(/\(max\-width:\s?(\s?[0-9]+)px\s?\)/)&&parseFloat(RegExp.$1)})}}i()},k,p,i=function(C){var t="clientWidth",v=q[t],B=s.compatMode==="CSS1Compat"&&v||s.body[t]||v,x={},A=s.createDocumentFragment(),z=b[b.length-1],u=(new Date()).getTime();if(C&&k&&u-k<f){clearTimeout(p);p=setTimeout(i,f);return}else{k=u}for(var w in h){var D=h[w];if(!D.minw&&!D.maxw||(!D.minw||D.minw&&B>=D.minw)&&(!D.maxw||D.maxw&&B<=D.maxw)){if(!x[D.media]){x[D.media]=[]}x[D.media].push(j[D.rules])}}for(var w in o){if(o[w]&&o[w].parentNode===e){e.removeChild(o[w])}}for(var w in x){var E=s.createElement("style"),y=x[w].join("\n");E.type="text/css";E.media=w;if(E.styleSheet){E.styleSheet.cssText=y}else{E.appendChild(s.createTextNode(y))}A.appendChild(E);o.push(E)}e.insertBefore(A,z.nextSibling)},m=function(t,v){var u=c();if(!u){return}u.open("GET",t,true);u.onreadystatechange=function(){if(u.readyState!=4||u.status!=200&&u.status!=304){return}v(u.responseText)};if(u.readyState==4){return}u.send()},c=(function(){var t=false,u=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new XMLHttpRequest()}],w=u.length;while(w--){try{t=u[w]()}catch(v){continue}break}return function(){return t}})();a();respond.update=a;function r(){i(true)}if(d.addEventListener){d.addEventListener("resize",r,false)}else{if(d.attachEvent){d.attachEvent("onresize",r)}}})(this,(function(f){var g=(function(k){var i=3,l=document.createElement("div"),j=l.getElementsByTagName("i");while(l.innerHTML="<!--[if gt IE "+(++i)+"]><i></i><![endif]-->",j[0]){}return i>4?i:k}());if(f.matchMedia||g&&g>=9){return true}if(g&&g<=8){return false}var e=f.document,a=e.documentElement,b=e.createElement("body"),h=e.createElement("div"),d=e.createElement("style"),c="@media only all { #qtest { position: absolute; } }";h.setAttribute("id","qtest");d.type="text/css";b.appendChild(h);if(d.styleSheet){d.styleSheet.cssText=c}else{d.appendChild(e.createTextNode(c))}a.insertBefore(b,a.firstChild);a.insertBefore(d,b);support=(f.getComputedStyle?f.getComputedStyle(h,null):h.currentStyle)["position"]=="absolute";a.removeChild(b);a.removeChild(d);return support})(this));
|
147
lib/middleman/templates/mobile/source/js/mylibs/helper.js
Executable file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* MBP - Mobile boilerplate helper functions
|
||||
*/
|
||||
(function(document){
|
||||
|
||||
window.MBP = window.MBP || {};
|
||||
|
||||
// Fix for iPhone viewport scale bug
|
||||
// http://www.blog.highub.com/mobile-2/a-fix-for-iphone-viewport-scale-bug/
|
||||
|
||||
MBP.viewportmeta = document.querySelector && document.querySelector('meta[name="viewport"]');
|
||||
MBP.ua = navigator.userAgent;
|
||||
|
||||
MBP.scaleFix = function () {
|
||||
if (MBP.viewportmeta && /iPhone|iPad/.test(MBP.ua) && !/Opera Mini/.test(MBP.ua)) {
|
||||
MBP.viewportmeta.content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
|
||||
document.addEventListener("gesturestart", MBP.gestureStart, false);
|
||||
}
|
||||
};
|
||||
MBP.gestureStart = function () {
|
||||
MBP.viewportmeta.content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
|
||||
};
|
||||
|
||||
|
||||
// Hide URL Bar for iOS
|
||||
// http://remysharp.com/2010/08/05/doing-it-right-skipping-the-iphone-url-bar/
|
||||
|
||||
MBP.hideUrlBar = function () {
|
||||
/iPhone/.test(MBP.ua) && !pageYOffset && !location.hash && setTimeout(function () {
|
||||
window.scrollTo(0, 1);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
||||
// Fast Buttons - read wiki below before using
|
||||
// https://github.com/shichuan/mobile-html5-boilerplate/wiki/JavaScript-Helper
|
||||
|
||||
MBP.fastButton = function (element, handler) {
|
||||
this.element = element;
|
||||
this.handler = handler;
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener('touchstart', this, false);
|
||||
element.addEventListener('click', this, false);
|
||||
}
|
||||
};
|
||||
|
||||
MBP.fastButton.prototype.handleEvent = function(event) {
|
||||
switch (event.type) {
|
||||
case 'touchstart': this.onTouchStart(event); break;
|
||||
case 'touchmove': this.onTouchMove(event); break;
|
||||
case 'touchend': this.onClick(event); break;
|
||||
case 'click': this.onClick(event); break;
|
||||
}
|
||||
};
|
||||
|
||||
MBP.fastButton.prototype.onTouchStart = function(event) {
|
||||
event.stopPropagation();
|
||||
this.element.addEventListener('touchend', this, false);
|
||||
document.body.addEventListener('touchmove', this, false);
|
||||
this.startX = event.touches[0].clientX;
|
||||
this.startY = event.touches[0].clientY;
|
||||
this.element.style.backgroundColor = "rgba(0,0,0,.7)";
|
||||
};
|
||||
|
||||
MBP.fastButton.prototype.onTouchMove = function(event) {
|
||||
if(Math.abs(event.touches[0].clientX - this.startX) > 10 || Math.abs(event.touches[0].clientY - this.startY) > 10) {
|
||||
this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
MBP.fastButton.prototype.onClick = function(event) {
|
||||
event.stopPropagation();
|
||||
this.reset();
|
||||
this.handler(event);
|
||||
if(event.type == 'touchend') {
|
||||
MBP.preventGhostClick(this.startX, this.startY);
|
||||
}
|
||||
this.element.style.backgroundColor = "";
|
||||
};
|
||||
|
||||
MBP.fastButton.prototype.reset = function() {
|
||||
this.element.removeEventListener('touchend', this, false);
|
||||
document.body.removeEventListener('touchmove', this, false);
|
||||
this.element.style.backgroundColor = "";
|
||||
};
|
||||
|
||||
MBP.preventGhostClick = function (x, y) {
|
||||
MBP.coords.push(x, y);
|
||||
window.setTimeout(function (){
|
||||
MBP.coords.splice(0, 2);
|
||||
}, 2500);
|
||||
};
|
||||
|
||||
MBP.ghostClickHandler = function (event) {
|
||||
for(var i = 0, len = MBP.coords.length; i < len; i += 2) {
|
||||
var x = MBP.coords[i];
|
||||
var y = MBP.coords[i + 1];
|
||||
if(Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (document.addEventListener) {
|
||||
document.addEventListener('click', MBP.ghostClickHandler, true);
|
||||
}
|
||||
|
||||
MBP.coords = [];
|
||||
|
||||
|
||||
// iOS Startup Image
|
||||
// https://github.com/shichuan/mobile-html5-boilerplate/issues#issue/2
|
||||
|
||||
MBP.splash = function () {
|
||||
var filename = navigator.platform === 'iPad' ? 'h/' : 'l/';
|
||||
document.write('<link rel="apple-touch-startup-image" href="/img/' + filename + 'splash.png" />' );
|
||||
};
|
||||
|
||||
|
||||
// Autogrow
|
||||
// http://googlecode.blogspot.com/2009/07/gmail-for-mobile-html5-series.html
|
||||
|
||||
MBP.autogrow = function (element, lh) {
|
||||
|
||||
function handler(e){
|
||||
var newHeight = this.scrollHeight,
|
||||
currentHeight = this.clientHeight;
|
||||
if (newHeight > currentHeight) {
|
||||
this.style.height = newHeight + 3 * textLineHeight + "px";
|
||||
}
|
||||
}
|
||||
|
||||
var setLineHeight = (lh) ? lh : 12,
|
||||
textLineHeight = element.currentStyle ? element.currentStyle.lineHeight :
|
||||
getComputedStyle(element, null).lineHeight;
|
||||
|
||||
textLineHeight = (textLineHeight.indexOf("px") == -1) ? setLineHeight :
|
||||
parseInt(textLineHeight, 10);
|
||||
|
||||
element.style.overflow = "hidden";
|
||||
element.addEventListener ? element.addEventListener('keyup', handler, false) :
|
||||
element.attachEvent('onkeyup', handler);
|
||||
};
|
||||
|
||||
})(document);
|
||||
|
0
lib/middleman/templates/mobile/source/js/script.js
Executable file
5
lib/middleman/templates/mobile/source/robots.txt
Executable file
|
@ -0,0 +1,5 @@
|
|||
# www.robotstxt.org/
|
||||
# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
|
||||
|
||||
User-agent: *
|
||||
|
10
lib/middleman/templates/mobile/source/sitemap.xml
Executable file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0">
|
||||
<!-- http://www.google.com/support/webmasters/bin/answer.py?answer=34648 -->
|
||||
<url>
|
||||
<!--<loc>http://mobile.example.com/index.html</loc>-->
|
||||
|
||||
<mobile:mobile/>
|
||||
</url>
|
||||
</urlset>
|
31
lib/middleman/templates/mobile/source/test/index.html
Executable file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>QUnit Tests</title>
|
||||
<link rel="stylesheet" href="qunit/qunit.css" media="screen">
|
||||
|
||||
<!-- reference your own javascript files here -->
|
||||
|
||||
<script src="../js/modernizr-1.5.min.js"></script>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script src="../js/plugins.js"></script>
|
||||
<script src="../js/script.js"></script>
|
||||
|
||||
|
||||
<!-- test runner files -->
|
||||
<script src="qunit/qunit.js"></script>
|
||||
<script src="tests.js"></script>
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="flora">
|
||||
<h1 id="qunit-header">QUnit Test Suite</h1>
|
||||
<h2 id="qunit-banner"></h2>
|
||||
<div id="qunit-testrunner-toolbar"></div>
|
||||
<h2 id="qunit-userAgent"></h2>
|
||||
<ol id="qunit-tests"></ol>
|
||||
<div id="qunit-fixture">test markup</div>
|
||||
</body>
|
||||
</html>
|
148
lib/middleman/templates/mobile/source/test/qunit/qunit.css
Executable file
|
@ -0,0 +1,148 @@
|
|||
/** Font Family and Sizes */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||
#qunit-tests { font-size: smaller; }
|
||||
|
||||
|
||||
/** Resets */
|
||||
|
||||
#qunit-tests, #qunit-tests li ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/** Header */
|
||||
|
||||
#qunit-header {
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
|
||||
color: #fff;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
|
||||
background-color: #0d3349;
|
||||
|
||||
border-radius: 15px 15px 0 0;
|
||||
-moz-border-radius: 15px 15px 0 0;
|
||||
-webkit-border-top-right-radius: 15px;
|
||||
-webkit-border-top-left-radius: 15px;
|
||||
}
|
||||
|
||||
#qunit-banner {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar {
|
||||
padding: 0em 0 0.5em 2em;
|
||||
}
|
||||
|
||||
#qunit-userAgent {
|
||||
padding: 0.5em 0 0.5em 2.5em;
|
||||
background-color: #2b81af;
|
||||
color: #fff;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
|
||||
|
||||
/** Tests: Pass/Fail */
|
||||
|
||||
#qunit-tests {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li {
|
||||
padding: 0.4em 0.5em 0.4em 2.5em;
|
||||
border-bottom: 1px solid #fff;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#qunit-tests li ol {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
|
||||
box-shadow: inset 0px 2px 13px #999;
|
||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
||||
}
|
||||
|
||||
#qunit-tests li li {
|
||||
margin: 0.5em;
|
||||
padding: 0.4em 0.5em 0.4em 0.5em;
|
||||
background-color: #fff;
|
||||
border-bottom: none;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/*** Passing Styles */
|
||||
|
||||
#qunit-tests li li.pass {
|
||||
color: #5E740B;
|
||||
background-color: #fff;
|
||||
border-left: 26px solid #C6E746;
|
||||
}
|
||||
|
||||
#qunit-tests li.pass { color: #528CE0; background-color: #D2E0E6; }
|
||||
#qunit-tests li.pass span.test-name { color: #366097; }
|
||||
|
||||
#qunit-tests li li.pass span.test-actual,
|
||||
#qunit-tests li li.pass span.test-expected { color: #999999; }
|
||||
|
||||
strong b.pass { color: #5E740B; }
|
||||
|
||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||
|
||||
/*** Failing Styles */
|
||||
|
||||
#qunit-tests li li.fail {
|
||||
color: #710909;
|
||||
background-color: #fff;
|
||||
border-left: 26px solid #EE5757;
|
||||
}
|
||||
|
||||
#qunit-tests li.fail { color: #000000; background-color: #EE5757; }
|
||||
#qunit-tests li.fail span.test-name,
|
||||
#qunit-tests li.fail span.module-name { color: #000000; }
|
||||
|
||||
#qunit-tests li li.fail span.test-actual { color: #EE5757; }
|
||||
#qunit-tests li li.fail span.test-expected { color: green; }
|
||||
|
||||
strong b.fail { color: #710909; }
|
||||
|
||||
#qunit-banner.qunit-fail,
|
||||
#qunit-testrunner-toolbar { background-color: #EE5757; }
|
||||
|
||||
|
||||
/** Footer */
|
||||
|
||||
#qunit-testresult {
|
||||
padding: 0.5em 0.5em 0.5em 2.5em;
|
||||
|
||||
color: #2b81af;
|
||||
background-color: #D2E0E6;
|
||||
|
||||
border-radius: 0 0 15px 15px;
|
||||
-moz-border-radius: 0 0 15px 15px;
|
||||
-webkit-border-bottom-right-radius: 15px;
|
||||
-webkit-border-bottom-left-radius: 15px;
|
||||
}
|
||||
|
||||
/** Fixture */
|
||||
|
||||
#qunit-fixture {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
}
|
1265
lib/middleman/templates/mobile/source/test/qunit/qunit.js
Executable file
26
lib/middleman/templates/mobile/source/test/tests.js
Executable file
|
@ -0,0 +1,26 @@
|
|||
// documentation on writing tests here: http://docs.jquery.com/QUnit
|
||||
// example tests: https://github.com/jquery/qunit/blob/master/test/same.js
|
||||
|
||||
// below are some general tests but feel free to delete them.
|
||||
|
||||
module("example tests");
|
||||
test('HTML5 Boilerplate is sweet',function(){
|
||||
expect(1);
|
||||
equals('boilerplate'.replace('boilerplate','sweet'),'sweet','Yes. HTML5 Boilerplate is, in fact, sweet');
|
||||
|
||||
})
|
||||
|
||||
// these test things from plugins.js
|
||||
test('Environment is good',function(){
|
||||
expect(3);
|
||||
ok( !!window.log, 'log function present');
|
||||
|
||||
var history = log.history && log.history.length || 0;
|
||||
log('logging from the test suite.')
|
||||
equals( log.history.length - history, 1, 'log history keeps track' )
|
||||
|
||||
ok( !!window.Modernizr, 'Modernizr global is present')
|
||||
})
|
||||
|
||||
|
||||
|
BIN
lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/Readme.PDF
Executable file
|
@ -0,0 +1,31 @@
|
|||
<%@ Page Language="C#" %>
|
||||
<script language="C#" runat="server">
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
private const string GaAccount = "ACCOUNT ID GOES HERE";
|
||||
private const string GaPixel = "ga.aspx";
|
||||
|
||||
private string GoogleAnalyticsGetImageUrl() {
|
||||
System.Text.StringBuilder url = new System.Text.StringBuilder();
|
||||
url.Append(GaPixel + "?");
|
||||
url.Append("utmac=").Append(GaAccount);
|
||||
|
||||
Random RandomClass = new Random();
|
||||
url.Append("&utmn=").Append(RandomClass.Next(0x7fffffff));
|
||||
|
||||
string referer = "-";
|
||||
if (Request.UrlReferrer != null
|
||||
&& "" != Request.UrlReferrer.ToString()) {
|
||||
referer = Request.UrlReferrer.ToString();
|
||||
}
|
||||
url.Append("&utmr=").Append(HttpUtility.UrlEncode(referer));
|
||||
|
||||
if (HttpContext.Current.Request.Url != null) {
|
||||
url.Append("&utmp=").Append(HttpUtility.UrlEncode(Request.Url.PathAndQuery));
|
||||
}
|
||||
|
||||
url.Append("&guid=ON");
|
||||
|
||||
return url.ToString();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<% string googleAnalyticsImageUrl = GoogleAnalyticsGetImageUrl(); %>
|
||||
<img src="<%= googleAnalyticsImageUrl %>" />
|
|
@ -0,0 +1,195 @@
|
|||
<% @Page Language="C#" ContentType="image/gif"%><%
|
||||
@Import Namespace="System.Net" %><%
|
||||
@Import Namespace="System.Security.Cryptography" %><%
|
||||
@Import Namespace="System.Text" %><script runat="server" language="c#">
|
||||
/**
|
||||
Copyright 2009 Google Inc. All Rights Reserved.
|
||||
**/
|
||||
|
||||
// Tracker version.
|
||||
private const string Version = "4.4sa";
|
||||
|
||||
private const string CookieName = "__utmmobile";
|
||||
|
||||
// The path the cookie will be available to, edit this to use a different
|
||||
// cookie path.
|
||||
private const string CookiePath = "/";
|
||||
|
||||
// Two years in seconds.
|
||||
private readonly TimeSpan CookieUserPersistence = TimeSpan.FromSeconds(63072000);
|
||||
|
||||
// 1x1 transparent GIF
|
||||
private readonly byte[] GifData = {
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
|
||||
0x01, 0x00, 0x01, 0x00, 0x80, 0xff,
|
||||
0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x01, 0x00, 0x00, 0x02,
|
||||
0x02, 0x44, 0x01, 0x00, 0x3b
|
||||
};
|
||||
|
||||
private static readonly Regex IpAddressMatcher =
|
||||
new Regex(@"^([^.]+\.[^.]+\.[^.]+\.).*");
|
||||
|
||||
// A string is empty in our terms, if it is null, empty or a dash.
|
||||
private static bool IsEmpty(string input) {
|
||||
return input == null || "-" == input || "" == input;
|
||||
}
|
||||
|
||||
// The last octect of the IP address is removed to anonymize the user.
|
||||
private static string GetIP(string remoteAddress) {
|
||||
if (IsEmpty(remoteAddress)) {
|
||||
return "";
|
||||
}
|
||||
// Capture the first three octects of the IP address and replace the forth
|
||||
// with 0, e.g. 124.455.3.123 becomes 124.455.3.0
|
||||
Match m = IpAddressMatcher.Match(remoteAddress);
|
||||
if (m.Success) {
|
||||
return m.Groups[1] + "0";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a visitor id for this hit.
|
||||
// If there is a visitor id in the cookie, use that, otherwise
|
||||
// use the guid if we have one, otherwise use a random number.
|
||||
private static string GetVisitorId(
|
||||
string guid, string account, string userAgent, HttpCookie cookie) {
|
||||
|
||||
// If there is a value in the cookie, don't change it.
|
||||
if (cookie != null && cookie.Value != null) {
|
||||
return cookie.Value;
|
||||
}
|
||||
|
||||
String message;
|
||||
if (!IsEmpty(guid)) {
|
||||
// Create the visitor id using the guid.
|
||||
message = guid + account;
|
||||
} else {
|
||||
// otherwise this is a new user, create a new random id.
|
||||
message = userAgent + GetRandomNumber() + Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
|
||||
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] sum = md5.ComputeHash(messageBytes);
|
||||
|
||||
string md5String = BitConverter.ToString(sum);
|
||||
md5String = md5String.Replace("-","");
|
||||
|
||||
md5String = md5String.PadLeft(32, '0');
|
||||
|
||||
return "0x" + md5String.Substring(0, 16);
|
||||
}
|
||||
|
||||
// Get a random number string.
|
||||
private static String GetRandomNumber() {
|
||||
Random RandomClass = new Random();
|
||||
return RandomClass.Next(0x7fffffff).ToString();
|
||||
}
|
||||
|
||||
// Writes the bytes of a 1x1 transparent gif into the response.
|
||||
private void WriteGifData() {
|
||||
Response.AddHeader(
|
||||
"Cache-Control",
|
||||
"private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
|
||||
Response.AddHeader("Pragma", "no-cache");
|
||||
Response.AddHeader("Expires", "Wed, 17 Sep 1975 21:32:10 GMT");
|
||||
Response.Buffer = false;
|
||||
Response.OutputStream.Write(GifData, 0, GifData.Length);
|
||||
}
|
||||
|
||||
// Make a tracking request to Google Analytics from this server.
|
||||
// Copies the headers from the original request to the new one.
|
||||
// If request containg utmdebug parameter, exceptions encountered
|
||||
// communicating with Google Analytics are thown.
|
||||
private void SendRequestToGoogleAnalytics(string utmUrl) {
|
||||
try {
|
||||
WebRequest connection = WebRequest.Create(utmUrl);
|
||||
|
||||
((HttpWebRequest)connection).UserAgent = Request.UserAgent;
|
||||
connection.Headers.Add("Accepts-Language",
|
||||
Request.Headers.Get("Accepts-Language"));
|
||||
|
||||
using (WebResponse resp = connection.GetResponse()) {
|
||||
// Ignore response
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (Request.QueryString.Get("utmdebug") != null) {
|
||||
throw new Exception("Error contacting Google Analytics", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track a page view, updates all the cookies and campaign tracker,
|
||||
// makes a server side request to Google Analytics and writes the transparent
|
||||
// gif byte data to the response.
|
||||
private void TrackPageView() {
|
||||
TimeSpan timeSpan = (DateTime.Now - new DateTime(1970, 1, 1).ToLocalTime());
|
||||
string timeStamp = timeSpan.TotalSeconds.ToString();
|
||||
string domainName = Request.ServerVariables["SERVER_NAME"];
|
||||
if (IsEmpty(domainName)) {
|
||||
domainName = "";
|
||||
}
|
||||
|
||||
// Get the referrer from the utmr parameter, this is the referrer to the
|
||||
// page that contains the tracking pixel, not the referrer for tracking
|
||||
// pixel.
|
||||
string documentReferer = Request.QueryString.Get("utmr");
|
||||
if (IsEmpty(documentReferer)) {
|
||||
documentReferer = "-";
|
||||
} else {
|
||||
documentReferer = HttpUtility.UrlDecode(documentReferer);
|
||||
}
|
||||
string documentPath = Request.QueryString.Get("utmp");
|
||||
if (IsEmpty(documentPath)) {
|
||||
documentPath = "";
|
||||
} else {
|
||||
documentPath = HttpUtility.UrlDecode(documentPath);
|
||||
}
|
||||
|
||||
string account = Request.QueryString.Get("utmac");
|
||||
string userAgent = Request.UserAgent;
|
||||
if (IsEmpty(userAgent)) {
|
||||
userAgent = "";
|
||||
}
|
||||
|
||||
// Try and get visitor cookie from the request.
|
||||
HttpCookie cookie = Request.Cookies.Get(CookieName);
|
||||
|
||||
string visitorId = GetVisitorId(
|
||||
Request.Headers.Get("X-DCMGUID"), account, userAgent, cookie);
|
||||
|
||||
// Always try and add the cookie to the response.
|
||||
HttpCookie newCookie = new HttpCookie(CookieName);
|
||||
newCookie.Value = visitorId;
|
||||
newCookie.Expires = DateTime.Now + CookieUserPersistence;
|
||||
newCookie.Path = CookiePath;
|
||||
Response.Cookies.Add(newCookie);
|
||||
|
||||
string utmGifLocation = "http://www.google-analytics.com/__utm.gif";
|
||||
|
||||
// Construct the gif hit url.
|
||||
string utmUrl = utmGifLocation + "?" +
|
||||
"utmwv=" + Version +
|
||||
"&utmn=" + GetRandomNumber() +
|
||||
"&utmhn=" + HttpUtility.UrlEncode(domainName) +
|
||||
"&utmr=" + HttpUtility.UrlEncode(documentReferer) +
|
||||
"&utmp=" + HttpUtility.UrlEncode(documentPath) +
|
||||
"&utmac=" + account +
|
||||
"&utmcc=__utma%3D999.999.999.999.999.1%3B" +
|
||||
"&utmvid=" + visitorId +
|
||||
"&utmip=" + GetIP(Request.ServerVariables["REMOTE_ADDR"]);
|
||||
|
||||
SendRequestToGoogleAnalytics(utmUrl);
|
||||
|
||||
// If the debug parameter is on, add a header to the response that contains
|
||||
// the url that was used to contact Google Analytics.
|
||||
if (Request.QueryString.Get("utmdebug") != null) {
|
||||
Response.AddHeader("X-GA-MOBILE-URL", utmUrl);
|
||||
}
|
||||
// Finally write the gif data to the response.
|
||||
WriteGifData();
|
||||
}
|
||||
</script><% TrackPageView(); %>
|
|
@ -0,0 +1,44 @@
|
|||
<%@ Page Language="C#" %>
|
||||
<script language="C#" runat="server">
|
||||
private const string GaAccount = "MO-3845491-5";
|
||||
private const string GaPixel = "ga.aspx";
|
||||
|
||||
private string GoogleAnalyticsGetImageUrl() {
|
||||
System.Text.StringBuilder url = new System.Text.StringBuilder();
|
||||
url.Append(GaPixel + "?");
|
||||
url.Append("utmac=").Append(GaAccount);
|
||||
|
||||
Random RandomClass = new Random();
|
||||
url.Append("&utmn=").Append(RandomClass.Next(0x7fffffff));
|
||||
|
||||
string referer = "-";
|
||||
if (Request.UrlReferrer != null
|
||||
&& "" != Request.UrlReferrer.ToString()) {
|
||||
referer = Request.UrlReferrer.ToString();
|
||||
}
|
||||
url.Append("&utmr=").Append(HttpUtility.UrlEncode(referer));
|
||||
|
||||
if (HttpContext.Current.Request.Url != null) {
|
||||
url.Append("&utmp=").Append(HttpUtility.UrlEncode(Request.Url.PathAndQuery));
|
||||
}
|
||||
|
||||
url.Append("&guid=ON");
|
||||
|
||||
return url.ToString();
|
||||
}
|
||||
</script>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>Sample Mobile Analytics Page</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Publisher's content here.
|
||||
<%
|
||||
string googleAnalyticsImageUrl = GoogleAnalyticsGetImageUrl();
|
||||
%>
|
||||
<img src="<%= googleAnalyticsImageUrl %>" />
|
||||
Testing: <%= googleAnalyticsImageUrl %>
|
||||
</body>
|
||||
</html>
|
225
lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/ga.jsp
Executable file
|
@ -0,0 +1,225 @@
|
|||
<%@ page language="java"
|
||||
contentType="image/gif"
|
||||
import="java.io.ByteArrayOutputStream,
|
||||
java.io.IOException,
|
||||
java.io.OutputStream,
|
||||
java.io.UnsupportedEncodingException,
|
||||
java.math.BigInteger,
|
||||
java.net.HttpURLConnection,
|
||||
java.net.URLConnection,
|
||||
java.net.URL,
|
||||
java.net.URLEncoder,
|
||||
java.net.URLDecoder,
|
||||
java.security.MessageDigest,
|
||||
java.security.NoSuchAlgorithmException,
|
||||
javax.servlet.http.Cookie,
|
||||
java.util.regex.Pattern,
|
||||
java.util.regex.Matcher,
|
||||
java.util.UUID" %><%!
|
||||
|
||||
/**
|
||||
Copyright 2009 Google Inc. All Rights Reserved.
|
||||
**/
|
||||
|
||||
// Tracker version.
|
||||
private static final String version = "4.4sj";
|
||||
|
||||
private static final String COOKIE_NAME = "__utmmobile";
|
||||
|
||||
// The path the cookie will be available to, edit this to use a different
|
||||
// cookie path.
|
||||
private static final String COOKIE_PATH = "/";
|
||||
|
||||
// Two years in seconds.
|
||||
private static final int COOKIE_USER_PERSISTENCE = 63072000;
|
||||
|
||||
// 1x1 transparent GIF
|
||||
private static final byte[] GIF_DATA = new byte[] {
|
||||
(byte)0x47, (byte)0x49, (byte)0x46, (byte)0x38, (byte)0x39, (byte)0x61,
|
||||
(byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x80, (byte)0xff,
|
||||
(byte)0x00, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0x00, (byte)0x00,
|
||||
(byte)0x00, (byte)0x2c, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
|
||||
(byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x02,
|
||||
(byte)0x02, (byte)0x44, (byte)0x01, (byte)0x00, (byte)0x3b
|
||||
};
|
||||
|
||||
// A string is empty in our terms, if it is null, empty or a dash.
|
||||
private static boolean isEmpty(String in) {
|
||||
return in == null || "-".equals(in) || "".equals(in);
|
||||
}
|
||||
|
||||
// The last octect of the IP address is removed to anonymize the user.
|
||||
private static String getIP(String remoteAddress) {
|
||||
if (isEmpty(remoteAddress)) {
|
||||
return "";
|
||||
}
|
||||
// Capture the first three octects of the IP address and replace the forth
|
||||
// with 0, e.g. 124.455.3.123 becomes 124.455.3.0
|
||||
String regex = "^([^.]+\\.[^.]+\\.[^.]+\\.).*";
|
||||
Pattern getFirstBitOfIPAddress = Pattern.compile(regex);
|
||||
Matcher m = getFirstBitOfIPAddress.matcher(remoteAddress);
|
||||
if (m.matches()) {
|
||||
return m.group(1) + "0";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a visitor id for this hit.
|
||||
// If there is a visitor id in the cookie, use that, otherwise
|
||||
// use the guid if we have one, otherwise use a random number.
|
||||
private static String getVisitorId(
|
||||
String guid, String account, String userAgent, Cookie cookie)
|
||||
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
|
||||
// If there is a value in the cookie, don't change it.
|
||||
if (cookie != null && cookie.getValue() != null) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
|
||||
String message;
|
||||
if (!isEmpty(guid)) {
|
||||
// Create the visitor id using the guid.
|
||||
message = guid + account;
|
||||
} else {
|
||||
// otherwise this is a new user, create a new random id.
|
||||
message = userAgent + getRandomNumber() + UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
MessageDigest m = MessageDigest.getInstance("MD5");
|
||||
m.update(message.getBytes("UTF-8"), 0, message.length());
|
||||
byte[] sum = m.digest();
|
||||
BigInteger messageAsNumber = new BigInteger(1, sum);
|
||||
String md5String = messageAsNumber.toString(16);
|
||||
|
||||
// Pad to make sure id is 32 characters long.
|
||||
while (md5String.length() < 32) {
|
||||
md5String = "0" + md5String;
|
||||
}
|
||||
|
||||
return "0x" + md5String.substring(0, 16);
|
||||
}
|
||||
|
||||
// Get a random number string.
|
||||
private static String getRandomNumber() {
|
||||
return Integer.toString((int) (Math.random() * 0x7fffffff));
|
||||
}
|
||||
|
||||
// Writes the bytes of a 1x1 transparent gif into the response.
|
||||
private void writeGifData(HttpServletResponse response) throws IOException {
|
||||
response.addHeader(
|
||||
"Cache-Control",
|
||||
"private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
|
||||
response.addHeader("Pragma", "no-cache");
|
||||
response.addHeader("Expires", "Wed, 17 Sep 1975 21:32:10 GMT");
|
||||
ServletOutputStream output = response.getOutputStream();
|
||||
output.write(GIF_DATA);
|
||||
output.flush();
|
||||
}
|
||||
|
||||
// Make a tracking request to Google Analytics from this server.
|
||||
// Copies the headers from the original request to the new one.
|
||||
// If request containg utmdebug parameter, exceptions encountered
|
||||
// communicating with Google Analytics are thown.
|
||||
private void sendRequestToGoogleAnalytics(
|
||||
String utmUrl, HttpServletRequest request) throws Exception {
|
||||
try {
|
||||
URL url = new URL(utmUrl);
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setUseCaches(false);
|
||||
|
||||
connection.addRequestProperty("User-Agent",
|
||||
request.getHeader("User-Agent"));
|
||||
connection.addRequestProperty("Accepts-Language",
|
||||
request.getHeader("Accepts-Language"));
|
||||
|
||||
connection.getContent();
|
||||
} catch (Exception e) {
|
||||
if (request.getParameter("utmdebug") != null) {
|
||||
throw new Exception(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track a page view, updates all the cookies and campaign tracker,
|
||||
// makes a server side request to Google Analytics and writes the transparent
|
||||
// gif byte data to the response.
|
||||
private void trackPageView(
|
||||
HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
String timeStamp = Long.toString(System.currentTimeMillis() / 1000);
|
||||
String domainName = request.getServerName();
|
||||
if (isEmpty(domainName)) {
|
||||
domainName = "";
|
||||
}
|
||||
|
||||
// Get the referrer from the utmr parameter, this is the referrer to the
|
||||
// page that contains the tracking pixel, not the referrer for tracking
|
||||
// pixel.
|
||||
String documentReferer = request.getParameter("utmr");
|
||||
if (isEmpty(documentReferer)) {
|
||||
documentReferer = "-";
|
||||
} else {
|
||||
documentReferer = URLDecoder.decode(documentReferer, "UTF-8");
|
||||
}
|
||||
String documentPath = request.getParameter("utmp");
|
||||
if (isEmpty(documentPath)) {
|
||||
documentPath = "";
|
||||
} else {
|
||||
documentPath = URLDecoder.decode(documentPath, "UTF-8");
|
||||
}
|
||||
|
||||
String account = request.getParameter("utmac");
|
||||
String userAgent = request.getHeader("User-Agent");
|
||||
if (isEmpty(userAgent)) {
|
||||
userAgent = "";
|
||||
}
|
||||
|
||||
// Try and get visitor cookie from the request.
|
||||
Cookie[] cookies = request.getCookies();
|
||||
Cookie cookie = null;
|
||||
if (cookies != null) {
|
||||
for(int i = 0; i < cookies.length; i++) {
|
||||
if (cookies[i].getName().equals(COOKIE_NAME)) {
|
||||
cookie = cookies[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String visitorId = getVisitorId(
|
||||
request.getHeader("X-DCMGUID"), account, userAgent, cookie);
|
||||
|
||||
// Always try and add the cookie to the response.
|
||||
Cookie newCookie = new Cookie(COOKIE_NAME, visitorId);
|
||||
newCookie.setMaxAge(COOKIE_USER_PERSISTENCE);
|
||||
newCookie.setPath(COOKIE_PATH);
|
||||
response.addCookie(newCookie);
|
||||
|
||||
String utmGifLocation = "http://www.google-analytics.com/__utm.gif";
|
||||
|
||||
// Construct the gif hit url.
|
||||
String utmUrl = utmGifLocation + "?" +
|
||||
"utmwv=" + version +
|
||||
"&utmn=" + getRandomNumber() +
|
||||
"&utmhn=" + URLEncoder.encode(domainName, "UTF-8") +
|
||||
"&utmr=" + URLEncoder.encode(documentReferer, "UTF-8") +
|
||||
"&utmp=" + URLEncoder.encode(documentPath, "UTF-8") +
|
||||
"&utmac=" + account +
|
||||
"&utmcc=__utma%3D999.999.999.999.999.1%3B" +
|
||||
"&utmvid=" + visitorId +
|
||||
"&utmip=" + getIP(request.getRemoteAddr());
|
||||
|
||||
sendRequestToGoogleAnalytics(utmUrl, request);
|
||||
|
||||
// If the debug parameter is on, add a header to the response that contains
|
||||
// the url that was used to contact Google Analytics.
|
||||
if (request.getParameter("utmdebug") != null) {
|
||||
response.setHeader("X-GA-MOBILE-URL", utmUrl);
|
||||
}
|
||||
// Finally write the gif data to the response.
|
||||
writeGifData(response);
|
||||
}
|
||||
%><%
|
||||
// Let exceptions bubble up to container, for better debugging.
|
||||
trackPageView(request, response);
|
||||
%>
|
|
@ -0,0 +1,35 @@
|
|||
<%@ page import="java.io.UnsupportedEncodingException,
|
||||
java.net.URLEncoder" %>
|
||||
<%!
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
private static final String GA_ACCOUNT = "ACCOUNT ID GOES HERE";
|
||||
private static final String GA_PIXEL = "ga.jsp";
|
||||
|
||||
private String googleAnalyticsGetImageUrl(
|
||||
HttpServletRequest request) throws UnsupportedEncodingException {
|
||||
StringBuilder url = new StringBuilder();
|
||||
url.append(GA_PIXEL + "?");
|
||||
url.append("utmac=").append(GA_ACCOUNT);
|
||||
url.append("&utmn=").append(Integer.toString((int) (Math.random() * 0x7fffffff)));
|
||||
|
||||
String referer = request.getHeader("referer");
|
||||
String query = request.getQueryString();
|
||||
String path = request.getRequestURI();
|
||||
|
||||
if (referer == null || "".equals(referer)) {
|
||||
referer = "-";
|
||||
}
|
||||
url.append("&utmr=").append(URLEncoder.encode(referer, "UTF-8"));
|
||||
|
||||
if (path != null) {
|
||||
if (query != null) {
|
||||
path += "?" + query;
|
||||
}
|
||||
url.append("&utmp=").append(URLEncoder.encode(path, "UTF-8"));
|
||||
}
|
||||
|
||||
url.append("&guid=ON");
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
%>
|
|
@ -0,0 +1,2 @@
|
|||
<% String googleAnalyticsImageUrl = googleAnalyticsGetImageUrl(request); %>
|
||||
<img src="<%= googleAnalyticsImageUrl %>" />
|
|
@ -0,0 +1,51 @@
|
|||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1"%>
|
||||
<%@ page import="java.io.UnsupportedEncodingException,
|
||||
java.net.URLEncoder" %>
|
||||
<%!
|
||||
private static final String GA_ACCOUNT = "MO-3845491-5";
|
||||
private static final String GA_PIXEL = "ga.jsp";
|
||||
|
||||
private String googleAnalyticsGetImageUrl(
|
||||
HttpServletRequest request) throws UnsupportedEncodingException {
|
||||
StringBuilder url = new StringBuilder();
|
||||
url.append(GA_PIXEL + "?");
|
||||
url.append("utmac=").append(GA_ACCOUNT);
|
||||
url.append("&utmn=").append(Integer.toString((int) (Math.random() * 0x7fffffff)));
|
||||
|
||||
String referer = request.getHeader("referer");
|
||||
String query = request.getQueryString();
|
||||
String path = request.getRequestURI();
|
||||
|
||||
if (referer == null || "".equals(referer)) {
|
||||
referer = "-";
|
||||
}
|
||||
url.append("&utmr=").append(URLEncoder.encode(referer, "UTF-8"));
|
||||
|
||||
if (path != null) {
|
||||
if (query != null) {
|
||||
path += "?" + query;
|
||||
}
|
||||
url.append("&utmp=").append(URLEncoder.encode(path, "UTF-8"));
|
||||
}
|
||||
|
||||
url.append("&guid=ON");
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>Sample Mobile Analytics Page</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Publishers content here.
|
||||
<%
|
||||
String googleAnalyticsImageUrl = googleAnalyticsGetImageUrl(request);
|
||||
%>
|
||||
<img src="<%= googleAnalyticsImageUrl %>" />
|
||||
Testing: <%= googleAnalyticsImageUrl %>
|
||||
</body>
|
||||
</html>
|
176
lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/ga.php
Executable file
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
Copyright 2009 Google Inc. All Rights Reserved.
|
||||
**/
|
||||
|
||||
// Tracker version.
|
||||
define("VERSION", "4.4sh");
|
||||
|
||||
define("COOKIE_NAME", "__utmmobile");
|
||||
|
||||
// The path the cookie will be available to, edit this to use a different
|
||||
// cookie path.
|
||||
define("COOKIE_PATH", "/");
|
||||
|
||||
// Two years in seconds.
|
||||
define("COOKIE_USER_PERSISTENCE", 63072000);
|
||||
|
||||
// 1x1 transparent GIF
|
||||
$GIF_DATA = array(
|
||||
chr(0x47), chr(0x49), chr(0x46), chr(0x38), chr(0x39), chr(0x61),
|
||||
chr(0x01), chr(0x00), chr(0x01), chr(0x00), chr(0x80), chr(0xff),
|
||||
chr(0x00), chr(0xff), chr(0xff), chr(0xff), chr(0x00), chr(0x00),
|
||||
chr(0x00), chr(0x2c), chr(0x00), chr(0x00), chr(0x00), chr(0x00),
|
||||
chr(0x01), chr(0x00), chr(0x01), chr(0x00), chr(0x00), chr(0x02),
|
||||
chr(0x02), chr(0x44), chr(0x01), chr(0x00), chr(0x3b)
|
||||
);
|
||||
|
||||
// The last octect of the IP address is removed to anonymize the user.
|
||||
function getIP($remoteAddress) {
|
||||
if (empty($remoteAddress)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Capture the first three octects of the IP address and replace the forth
|
||||
// with 0, e.g. 124.455.3.123 becomes 124.455.3.0
|
||||
$regex = "/^([^.]+\.[^.]+\.[^.]+\.).*/";
|
||||
if (preg_match($regex, $remoteAddress, $matches)) {
|
||||
return $matches[1] . "0";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a visitor id for this hit.
|
||||
// If there is a visitor id in the cookie, use that, otherwise
|
||||
// use the guid if we have one, otherwise use a random number.
|
||||
function getVisitorId($guid, $account, $userAgent, $cookie) {
|
||||
|
||||
// If there is a value in the cookie, don't change it.
|
||||
if (!empty($cookie)) {
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
$message = "";
|
||||
if (!empty($guid)) {
|
||||
// Create the visitor id using the guid.
|
||||
$message = $guid . $account;
|
||||
} else {
|
||||
// otherwise this is a new user, create a new random id.
|
||||
$message = $userAgent . uniqid(getRandomNumber(), true);
|
||||
}
|
||||
|
||||
$md5String = md5($message);
|
||||
|
||||
return "0x" . substr($md5String, 0, 16);
|
||||
}
|
||||
|
||||
// Get a random number string.
|
||||
function getRandomNumber() {
|
||||
return rand(0, 0x7fffffff);
|
||||
}
|
||||
|
||||
// Writes the bytes of a 1x1 transparent gif into the response.
|
||||
function writeGifData() {
|
||||
global $GIF_DATA;
|
||||
header("Content-Type: image/gif");
|
||||
header("Cache-Control: " .
|
||||
"private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: Wed, 17 Sep 1975 21:32:10 GMT");
|
||||
echo join($GIF_DATA);
|
||||
}
|
||||
|
||||
// Make a tracking request to Google Analytics from this server.
|
||||
// Copies the headers from the original request to the new one.
|
||||
// If request containg utmdebug parameter, exceptions encountered
|
||||
// communicating with Google Analytics are thown.
|
||||
function sendRequestToGoogleAnalytics($utmUrl) {
|
||||
$options = array(
|
||||
"http" => array(
|
||||
"method" => "GET",
|
||||
"user_agent" => $_SERVER["HTTP_USER_AGENT"],
|
||||
"header" => ("Accepts-Language: " . $_SERVER["HTTP_ACCEPT_LANGUAGE"]))
|
||||
);
|
||||
if (!empty($_GET["utmdebug"])) {
|
||||
$data = file_get_contents(
|
||||
$utmUrl, false, stream_context_create($options));
|
||||
} else {
|
||||
$data = @file_get_contents(
|
||||
$utmUrl, false, stream_context_create($options));
|
||||
}
|
||||
}
|
||||
|
||||
// Track a page view, updates all the cookies and campaign tracker,
|
||||
// makes a server side request to Google Analytics and writes the transparent
|
||||
// gif byte data to the response.
|
||||
function trackPageView() {
|
||||
$timeStamp = time();
|
||||
$domainName = $_SERVER["SERVER_NAME"];
|
||||
if (empty($domainName)) {
|
||||
$domainName = "";
|
||||
}
|
||||
|
||||
// Get the referrer from the utmr parameter, this is the referrer to the
|
||||
// page that contains the tracking pixel, not the referrer for tracking
|
||||
// pixel.
|
||||
$documentReferer = $_GET["utmr"];
|
||||
if (empty($documentReferer) && $documentReferer !== "0") {
|
||||
$documentReferer = "-";
|
||||
} else {
|
||||
$documentReferer = urldecode($documentReferer);
|
||||
}
|
||||
$documentPath = $_GET["utmp"];
|
||||
if (empty($documentPath)) {
|
||||
$documentPath = "";
|
||||
} else {
|
||||
$documentPath = urldecode($documentPath);
|
||||
}
|
||||
|
||||
$account = $_GET["utmac"];
|
||||
$userAgent = $_SERVER["HTTP_USER_AGENT"];
|
||||
if (empty($userAgent)) {
|
||||
$userAgent = "";
|
||||
}
|
||||
|
||||
// Try and get visitor cookie from the request.
|
||||
$cookie = $_COOKIE[COOKIE_NAME];
|
||||
|
||||
$visitorId = getVisitorId(
|
||||
$_SERVER["HTTP_X_DCMGUID"], $account, $userAgent, $cookie);
|
||||
|
||||
// Always try and add the cookie to the response.
|
||||
setrawcookie(
|
||||
COOKIE_NAME,
|
||||
$visitorId,
|
||||
$timeStamp + COOKIE_USER_PERSISTENCE,
|
||||
COOKIE_PATH);
|
||||
|
||||
$utmGifLocation = "http://www.google-analytics.com/__utm.gif";
|
||||
|
||||
// Construct the gif hit url.
|
||||
$utmUrl = $utmGifLocation . "?" .
|
||||
"utmwv=" . VERSION .
|
||||
"&utmn=" . getRandomNumber() .
|
||||
"&utmhn=" . urlencode($domainName) .
|
||||
"&utmr=" . urlencode($documentReferer) .
|
||||
"&utmp=" . urlencode($documentPath) .
|
||||
"&utmac=" . $account .
|
||||
"&utmcc=__utma%3D999.999.999.999.999.1%3B" .
|
||||
"&utmvid=" . $visitorId .
|
||||
"&utmip=" . getIP($_SERVER["REMOTE_ADDR"]);
|
||||
|
||||
sendRequestToGoogleAnalytics($utmUrl);
|
||||
|
||||
// If the debug parameter is on, add a header to the response that contains
|
||||
// the url that was used to contact Google Analytics.
|
||||
if (!empty($_GET["utmdebug"])) {
|
||||
header("X-GA-MOBILE-URL:" . $utmUrl);
|
||||
}
|
||||
// Finally write the gif data to the response.
|
||||
writeGifData();
|
||||
}
|
||||
?><?php
|
||||
trackPageView();
|
||||
?>
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||
$GA_ACCOUNT = "ACCOUNT ID GOES HERE";
|
||||
$GA_PIXEL = "ga.php";
|
||||
|
||||
function googleAnalyticsGetImageUrl() {
|
||||
global $GA_ACCOUNT, $GA_PIXEL;
|
||||
$url = "";
|
||||
$url .= $GA_PIXEL . "?";
|
||||
$url .= "utmac=" . $GA_ACCOUNT;
|
||||
$url .= "&utmn=" . rand(0, 0x7fffffff);
|
||||
|
||||
$referer = $_SERVER["HTTP_REFERER"];
|
||||
$query = $_SERVER["QUERY_STRING"];
|
||||
$path = $_SERVER["REQUEST_URI"];
|
||||
|
||||
if (empty($referer)) {
|
||||
$referer = "-";
|
||||
}
|
||||
$url .= "&utmr=" . urlencode($referer);
|
||||
|
||||
if (!empty($path)) {
|
||||
$url .= "&utmp=" . urlencode($path);
|
||||
}
|
||||
|
||||
$url .= "&guid=ON";
|
||||
|
||||
return $url;
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
$googleAnalyticsImageUrl = googleAnalyticsGetImageUrl();
|
||||
?>
|
||||
<img src="<?= $googleAnalyticsImageUrl ?>" />
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
$GA_ACCOUNT = "MO-3845491-5";
|
||||
$GA_PIXEL = "ga.php";
|
||||
|
||||
function googleAnalyticsGetImageUrl() {
|
||||
global $GA_ACCOUNT, $GA_PIXEL;
|
||||
$url = "";
|
||||
$url .= $GA_PIXEL . "?";
|
||||
$url .= "utmac=" . $GA_ACCOUNT;
|
||||
$url .= "&utmn=" . rand(0, 0x7fffffff);
|
||||
|
||||
$referer = $_SERVER["HTTP_REFERER"];
|
||||
$query = $_SERVER["QUERY_STRING"];
|
||||
$path = $_SERVER["REQUEST_URI"];
|
||||
|
||||
if (empty($referer)) {
|
||||
$referer = "-";
|
||||
}
|
||||
$url .= "&utmr=" . urlencode($referer);
|
||||
|
||||
if (!empty($path)) {
|
||||
$url .= "&utmp=" . urlencode($path);
|
||||
}
|
||||
|
||||
$url .= "&guid=ON";
|
||||
|
||||
return $url;
|
||||
}
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>Sample Mobile Analytics Page</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Publishers content here.
|
||||
<?php
|
||||
$googleAnalyticsImageUrl = googleAnalyticsGetImageUrl();
|
||||
?>
|
||||
<img src="<?= $googleAnalyticsImageUrl ?>" />
|
||||
Testing: <?= $googleAnalyticsImageUrl ?>
|
||||
</body>
|
||||
</html>
|
195
lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/ga.pl
Executable file
|
@ -0,0 +1,195 @@
|
|||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
|
||||
use CGI;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
use LWP::UserAgent;
|
||||
use URI::Escape;
|
||||
use strict;
|
||||
|
||||
# Tracker version.
|
||||
use constant VERSION => '4.4sp';
|
||||
|
||||
use constant COOKIE_NAME => '__utmmobile';
|
||||
|
||||
# The path the cookie will be available to, edit this to use a different
|
||||
# cookie path.
|
||||
use constant COOKIE_PATH => '/';
|
||||
|
||||
# Two years.
|
||||
use constant COOKIE_USER_PERSISTENCE => '+2y';
|
||||
|
||||
# 1x1 transparent GIF
|
||||
my @GIF_DATA = (
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
|
||||
0x01, 0x00, 0x01, 0x00, 0x80, 0xff,
|
||||
0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x01, 0x00, 0x00, 0x02,
|
||||
0x02, 0x44, 0x01, 0x00, 0x3b);
|
||||
|
||||
my $query = new CGI;
|
||||
|
||||
# The last octect of the IP address is removed to anonymize the user.
|
||||
sub get_ip {
|
||||
my ($remote_address) = @_;
|
||||
if ($remote_address eq "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
# Capture the first three octects of the IP address and replace the forth
|
||||
# with 0, e.g. 124.455.3.123 becomes 124.455.3.0
|
||||
if ($remote_address =~ /^((\d{1,3}\.){3})\d{1,3}$/) {
|
||||
return $1 . "0";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
# Generate a visitor id for this hit.
|
||||
# If there is a visitor id in the cookie, use that, otherwise
|
||||
# use the guid if we have one, otherwise use a random number.
|
||||
sub get_visitor_id {
|
||||
my ($guid, $account, $user_agent, $cookie) = @_;
|
||||
|
||||
# If there is a value in the cookie, don't change it.
|
||||
if ($cookie ne "") {
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
my $message = "";
|
||||
if ($guid ne "") {
|
||||
# Create the visitor id using the guid.
|
||||
$message = $guid . $account;
|
||||
} else {
|
||||
# otherwise this is a new user, create a new random id.
|
||||
$message = $user_agent . get_random_number();
|
||||
}
|
||||
|
||||
my $md5_string = md5_hex($message);
|
||||
|
||||
return "0x" . substr($md5_string, 0, 16);
|
||||
}
|
||||
|
||||
# Get a random number string.
|
||||
sub get_random_number {
|
||||
return int(rand(0x7fffffff));
|
||||
}
|
||||
|
||||
# Writes the bytes of a 1x1 transparent gif into the response.
|
||||
sub write_gif_data {
|
||||
my ($cookie, $utm_url) = @_;
|
||||
|
||||
my @header_args = (
|
||||
-type => 'image/gif',
|
||||
-Cache_Control =>
|
||||
'private, no-cache, no-cache=Set-Cookie, proxy-revalidate',
|
||||
-Pragma => 'no-cache',
|
||||
-cookie => $cookie,
|
||||
-expires => '-1d');
|
||||
|
||||
# If the debug parameter is on, add a header to the response that contains
|
||||
# the url that was used to contact Google Analytics.
|
||||
if (defined($query->param('utmdebug'))) {
|
||||
push(@header_args, -X_GA_MOBILE_URL => $utm_url);
|
||||
}
|
||||
print $query->header(@header_args);
|
||||
print pack("C35", @GIF_DATA);
|
||||
}
|
||||
|
||||
# Make a tracking request to Google Analytics from this server.
|
||||
# Copies the headers from the original request to the new one.
|
||||
# If request containg utmdebug parameter, exceptions encountered
|
||||
# communicating with Google Analytics are thown.
|
||||
sub send_request_to_google_analytics {
|
||||
my ($utm_url) = @_;
|
||||
my $ua = LWP::UserAgent->new;
|
||||
|
||||
if (exists($ENV{'HTTP_ACCEPT_LANGUAGE'})) {
|
||||
$ua->default_header('Accepts-Language' => $ENV{'HTTP_ACCEPT_LANGUAGE'});
|
||||
}
|
||||
if (exists($ENV{'HTTP_USER_AGENT'})) {
|
||||
$ua->agent($ENV{'HTTP_USER_AGENT'});
|
||||
}
|
||||
|
||||
my $ga_output = $ua->get($utm_url);
|
||||
|
||||
if (defined($query->param('utmdebug')) && !$ga_output->is_success) {
|
||||
print $ga_output->status_line;
|
||||
}
|
||||
}
|
||||
|
||||
# Track a page view, updates all the cookies and campaign tracker,
|
||||
# makes a server side request to Google Analytics and writes the transparent
|
||||
# gif byte data to the response.
|
||||
sub track_page_view {
|
||||
my $domain_name = "";
|
||||
if (exists($ENV{'SERVER_NAME'})) {
|
||||
$domain_name = $ENV{'SERVER_NAME'};
|
||||
}
|
||||
|
||||
# Get the referrer from the utmr parameter, this is the referrer to the
|
||||
# page that contains the tracking pixel, not the referrer for tracking
|
||||
# pixel.
|
||||
my $document_referer = "-";
|
||||
if (defined($query->param('utmr'))) {
|
||||
$document_referer = uri_unescape($query->param('utmr'));
|
||||
}
|
||||
my $document_path = "";
|
||||
if (defined($query->param('utmp'))) {
|
||||
$document_path = uri_unescape($query->param('utmp'));
|
||||
}
|
||||
|
||||
my $account = $query->param('utmac');
|
||||
my $user_agent = "";
|
||||
if (exists($ENV{'HTTP_USER_AGENT'})) {
|
||||
$user_agent = $ENV{'HTTP_USER_AGENT'};
|
||||
}
|
||||
|
||||
# Try and get visitor cookie from the request.
|
||||
my $cookie = "";
|
||||
if (defined($query->cookie(COOKIE_NAME))) {
|
||||
$cookie = $query->cookie(COOKIE_NAME);
|
||||
}
|
||||
|
||||
my $guid = "";
|
||||
if (exists($ENV{'HTTP_X_DCMGUID'})) {
|
||||
$guid = $ENV{'HTTP_X_DCMGUID'};
|
||||
}
|
||||
|
||||
my $visitor_id = get_visitor_id($guid, $account, $user_agent, $cookie);
|
||||
|
||||
# Always try and add the cookie to the response.
|
||||
my $new_cookie = $query->cookie(
|
||||
-name => COOKIE_NAME,
|
||||
-value => $visitor_id,
|
||||
-path => COOKIE_PATH,
|
||||
-expires => COOKIE_USER_PERSISTENCE);
|
||||
|
||||
my $utm_gif_location = "http://www.google-analytics.com/__utm.gif";
|
||||
|
||||
my $remote_address = "";
|
||||
if (exists($ENV{'REMOTE_ADDR'})) {
|
||||
$remote_address = $ENV{'REMOTE_ADDR'};
|
||||
}
|
||||
|
||||
# Construct the gif hit url.
|
||||
my $utm_url = $utm_gif_location . '?' .
|
||||
'utmwv=' . VERSION .
|
||||
'&utmn=' . get_random_number() .
|
||||
'&utmhn=' . uri_escape($domain_name) .
|
||||
'&utmr=' . uri_escape($document_referer) .
|
||||
'&utmp=' . uri_escape($document_path) .
|
||||
'&utmac=' . $account .
|
||||
'&utmcc=__utma%3D999.999.999.999.999.1%3B' .
|
||||
'&utmvid=' . $visitor_id .
|
||||
'&utmip=' . get_ip($remote_address);
|
||||
|
||||
send_request_to_google_analytics($utm_url);
|
||||
|
||||
# Finally write the gif data to the response.
|
||||
write_gif_data($new_cookie, $utm_url);
|
||||
}
|
||||
|
||||
track_page_view();
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
|
||||
use URI::Escape;
|
||||
|
||||
use constant GA_ACCOUNT => 'ACCOUNT ID GOES HERE';
|
||||
use constant GA_PIXEL => 'ga.pl';
|
||||
|
||||
sub google_analytics_get_image_url {
|
||||
my $url = '';
|
||||
$url .= GA_PIXEL . '?';
|
||||
$url .= 'utmac=' . GA_ACCOUNT;
|
||||
$url .= '&utmn=' . int(rand(0x7fffffff));
|
||||
|
||||
my $referer = $ENV{'HTTP_REFERER'};
|
||||
my $query = $ENV{'QUERY_STRING'};
|
||||
my $path = $ENV{'REQUEST_URI'};
|
||||
|
||||
if ($referer eq "") {
|
||||
$referer = '-';
|
||||
}
|
||||
|
||||
$url .= '&utmr=' . uri_escape($referer);
|
||||
$url .= '&utmp=' . uri_escape($path);
|
||||
$url .= '&guid=ON';
|
||||
|
||||
$url;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
print '<img src="' . google_analytics_get_image_url() . '" />';
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
|
||||
use strict;
|
||||
use URI::Escape;
|
||||
|
||||
use constant GA_ACCOUNT => 'MO-3845491-5';
|
||||
use constant GA_PIXEL => 'ga.pl';
|
||||
|
||||
sub google_analytics_get_image_url {
|
||||
my $url = '';
|
||||
$url .= GA_PIXEL . '?';
|
||||
$url .= 'utmac=' . GA_ACCOUNT;
|
||||
$url .= '&utmn=' . int(rand(0x7fffffff));
|
||||
|
||||
my $referer = $ENV{'HTTP_REFERER'};
|
||||
my $query = $ENV{'QUERY_STRING'};
|
||||
my $path = $ENV{'REQUEST_URI'};
|
||||
|
||||
if ($referer eq "") {
|
||||
$referer = '-';
|
||||
}
|
||||
|
||||
$url .= '&utmr=' . uri_escape($referer);
|
||||
$url .= '&utmp=' . uri_escape($path);
|
||||
$url .= '&guid=ON';
|
||||
|
||||
$url;
|
||||
}
|
||||
|
||||
print "Content-Type: text/html; charset=ISO-8859-1\n\n";
|
||||
print '<html><head></head><body>';
|
||||
print '<img src="' . google_analytics_get_image_url() . '" />';
|
||||
print google_analytics_get_image_url();
|
||||
print '</body></html>';
|
||||
|
||||
exit (0);
|
202
lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/COPYING
Executable file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,559 @@
|
|||
/*
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Bookmark bubble library. This is meant to be included in the
|
||||
* main JavaScript binary of a mobile web application.
|
||||
*
|
||||
* Supported browsers: iPhone / iPod / iPad Safari 3.0+
|
||||
*/
|
||||
|
||||
var google = google || {};
|
||||
google.bookmarkbubble = google.bookmarkbubble || {};
|
||||
|
||||
|
||||
/**
|
||||
* Binds a context object to the function.
|
||||
* @param {Function} fn The function to bind to.
|
||||
* @param {Object} context The "this" object to use when the function is run.
|
||||
* @return {Function} A partially-applied form of fn.
|
||||
*/
|
||||
google.bind = function(fn, context) {
|
||||
return function() {
|
||||
return fn.apply(context, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function used to define an abstract method in a base class. If a subclass
|
||||
* fails to override the abstract method, then an error will be thrown whenever
|
||||
* that method is invoked.
|
||||
*/
|
||||
google.abstractMethod = function() {
|
||||
throw Error('Unimplemented abstract method.');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The bubble constructor. Instantiating an object does not cause anything to
|
||||
* be rendered yet, so if necessary you can set instance properties before
|
||||
* showing the bubble.
|
||||
* @constructor
|
||||
*/
|
||||
google.bookmarkbubble.Bubble = function() {
|
||||
/**
|
||||
* Handler for the scroll event. Keep a reference to it here, so it can be
|
||||
* unregistered when the bubble is destroyed.
|
||||
* @type {function()}
|
||||
* @private
|
||||
*/
|
||||
this.boundScrollHandler_ = google.bind(this.setPosition, this);
|
||||
|
||||
/**
|
||||
* The bubble element.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.element_ = null;
|
||||
|
||||
/**
|
||||
* Whether the bubble has been destroyed.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hasBeenDestroyed_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Shows the bubble if allowed. It is not allowed if:
|
||||
* - The browser is not Mobile Safari, or
|
||||
* - The user has dismissed it too often already, or
|
||||
* - The hash parameter is present in the location hash, or
|
||||
* - The application is in fullscreen mode, which means it was already loaded
|
||||
* from a homescreen bookmark.
|
||||
* @return {boolean} True if the bubble is being shown, false if it is not
|
||||
* allowed to show for one of the aforementioned reasons.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.showIfAllowed = function() {
|
||||
if (!this.isAllowedToShow_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.show_();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Shows the bubble if allowed after loading the icon image. This method creates
|
||||
* an image element to load the image into the browser's cache before showing
|
||||
* the bubble to ensure that the image isn't blank. Use this instead of
|
||||
* showIfAllowed if the image url is http and cacheable.
|
||||
* This hack is necessary because Mobile Safari does not properly render
|
||||
* image elements with border-radius CSS.
|
||||
* @param {function()} opt_callback Closure to be called if and when the bubble
|
||||
* actually shows.
|
||||
* @return {boolean} True if the bubble is allowed to show.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.showIfAllowedWhenLoaded =
|
||||
function(opt_callback) {
|
||||
if (!this.isAllowedToShow_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
// Attach to self to avoid garbage collection.
|
||||
var img = self.loadImg_ = document.createElement('img');
|
||||
img.src = self.getIconUrl_();
|
||||
img.onload = function() {
|
||||
if (img.complete) {
|
||||
delete self.loadImg_;
|
||||
img.onload = null; // Break the circular reference.
|
||||
|
||||
self.show_();
|
||||
opt_callback && opt_callback();
|
||||
}
|
||||
};
|
||||
img.onload();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the parameter in the location hash. As it is
|
||||
* unpredictable what hash scheme is to be used, this method must be
|
||||
* implemented by the host application.
|
||||
*
|
||||
* This gets called automatically when the bubble is shown. The idea is that if
|
||||
* the user then creates a bookmark, we can later recognize on application
|
||||
* startup whether it was from a bookmark suggested with this bubble.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.setHashParameter = google.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the parameter is present in the location hash. As it is
|
||||
* unpredictable what hash scheme is to be used, this method must be
|
||||
* implemented by the host application.
|
||||
*
|
||||
* Call this method during application startup if you want to log whether the
|
||||
* application was loaded from a bookmark with the bookmark bubble promotion
|
||||
* parameter in it.
|
||||
*
|
||||
* @return {boolean} Whether the bookmark bubble parameter is present in the
|
||||
* location hash.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.hasHashParameter = google.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* The number of times the user must dismiss the bubble before we stop showing
|
||||
* it. This is a public property and can be changed by the host application if
|
||||
* necessary.
|
||||
* @type {number}
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.NUMBER_OF_TIMES_TO_DISMISS = 2;
|
||||
|
||||
|
||||
/**
|
||||
* Time in milliseconds. If the user does not dismiss the bubble, it will auto
|
||||
* destruct after this amount of time.
|
||||
* @type {number}
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.TIME_UNTIL_AUTO_DESTRUCT = 15000;
|
||||
|
||||
|
||||
/**
|
||||
* The prefix for keys in local storage. This is a public property and can be
|
||||
* changed by the host application if necessary.
|
||||
* @type {string}
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.LOCAL_STORAGE_PREFIX = 'BOOKMARK_';
|
||||
|
||||
|
||||
/**
|
||||
* The key name for the dismissed state.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.DISMISSED_ = 'DISMISSED_COUNT';
|
||||
|
||||
|
||||
/**
|
||||
* The arrow image in base64 data url format.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.IMAGE_ARROW_DATA_URL_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAATCAMAAABSrFY3AAABKVBMVEUAAAD///8AAAAAAAAAAAAAAAAAAADf398AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD09PQAAAAAAAAAAAC9vb0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD19fUAAAAAAAAAAAAAAADq6uoAAAAAAAAAAAC8vLzU1NTT09MAAADg4OAAAADs7OwAAAAAAAAAAAD///+cueenwerA0vC1y+3a5fb5+/3t8vr4+v3w9PuwyOy3zO3h6vfh6vjq8Pqkv+mat+fE1fHB0/Cduuifu+iuxuuivemrxOvC1PDz9vzJ2fKpwuqmwOrb5vapw+q/0vDf6ffK2vLN3PPprJISAAAAQHRSTlMAAAEGExES7FM+JhUoQSxIRwMbNfkJUgXXBE4kDQIMHSA0Tw4xIToeTSc4Chz4OyIjPfI3QD/X5OZR6zzwLSUPrm1y3gAAAQZJREFUeF5lzsVyw0AURNE3IMsgmZmZgszQZoeZOf//EYlG5Yrhbs+im4Dj7slM5wBJ4OJ+undAUr68gK/Hyb6Bcp5yBR/w8jreNeAr5Eg2XE7g6e2/0z6cGw1JQhpmHP3u5aiPPnTTkIK48Hj9Op7bD3btAXTfgUdwYjwSDCVXMbizO0O4uDY/x4kYC5SWFnfC6N1a9RCO7i2XEmQJj2mHK1Hgp9Vq3QBRl9shuBLGhcNtHexcdQCnDUoUGetxDD+H2DQNG2xh6uAWgG2/17o1EmLqYH0Xej0UjHAaFxZIV6rJ/WK1kg7QZH8HU02zmdJinKZJaDV3TVMjM5Q9yiqYpUwiMwa/1apDXTNESjsAAAAASUVORK5CYII=';
|
||||
|
||||
|
||||
/**
|
||||
* The close image in base64 data url format.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.IMAGE_CLOSE_DATA_URL_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEXM3fm+1Pfb5/rF2fjw9f23z/aavPOhwfTp8PyTt/L3+v7T4vqMs/K7zP////+qRWzhAAAAXElEQVQIW2O4CwUM996BwVskxtOqd++2rwMyPI+ve31GD8h4Madqz2mwms5jZ/aBGS/mHIDoen3m+DowY8/hOVUgxusz+zqPg7SvPA1UxQfSvu/du0YUK2AMmDMA5H1qhVX33T8AAAAASUVORK5CYII=';
|
||||
|
||||
|
||||
/**
|
||||
* The link used to locate the application's home screen icon to display inside
|
||||
* the bubble. The default link used here is for an iPhone home screen icon
|
||||
* without gloss. If your application uses a glossy icon, change this to
|
||||
* 'apple-touch-icon'.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.REL_ICON_ =
|
||||
'apple-touch-icon-precomposed';
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for detecting an iPhone or iPod or iPad.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.MOBILE_SAFARI_USERAGENT_REGEX_ =
|
||||
/iPhone|iPod|iPad/;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for detecting an iPad.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.IPAD_USERAGENT_REGEX_ = /iPad/;
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the bubble should be shown or not.
|
||||
* @return {boolean} Whether the bubble should be shown or not.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.isAllowedToShow_ = function() {
|
||||
return this.isMobileSafari_() &&
|
||||
!this.hasBeenDismissedTooManyTimes_() &&
|
||||
!this.isFullscreen_() &&
|
||||
!this.hasHashParameter();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Builds and shows the bubble.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.show_ = function() {
|
||||
this.element_ = this.build_();
|
||||
|
||||
document.body.appendChild(this.element_);
|
||||
this.element_.style.WebkitTransform =
|
||||
'translateY(' + this.getHiddenYPosition_() + 'px)';
|
||||
|
||||
this.setHashParameter();
|
||||
|
||||
window.setTimeout(this.boundScrollHandler_, 1);
|
||||
window.addEventListener('scroll', this.boundScrollHandler_, false);
|
||||
|
||||
// If the user does not dismiss the bubble, slide out and destroy it after
|
||||
// some time.
|
||||
window.setTimeout(google.bind(this.autoDestruct_, this),
|
||||
this.TIME_UNTIL_AUTO_DESTRUCT);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Destroys the bubble by removing its DOM nodes from the document.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.destroy = function() {
|
||||
if (this.hasBeenDestroyed_) {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener('scroll', this.boundScrollHandler_, false);
|
||||
if (this.element_ && this.element_.parentNode == document.body) {
|
||||
document.body.removeChild(this.element_);
|
||||
this.element_ = null;
|
||||
}
|
||||
this.hasBeenDestroyed_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remember that the user has dismissed the bubble once more.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.rememberDismissal_ = function() {
|
||||
if (window.localStorage) {
|
||||
try {
|
||||
var key = this.LOCAL_STORAGE_PREFIX + this.DISMISSED_;
|
||||
var value = Number(window.localStorage[key]) || 0;
|
||||
window.localStorage[key] = String(value + 1);
|
||||
} catch (ex) {
|
||||
// Looks like we've hit the storage size limit. Currently we have no
|
||||
// fallback for this scenario, but we could use cookie storage instead.
|
||||
// This would increase the code bloat though.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the user has dismissed the bubble often enough that we will not
|
||||
* show it again.
|
||||
* @return {boolean} Whether the user has dismissed the bubble often enough
|
||||
* that we will not show it again.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.hasBeenDismissedTooManyTimes_ =
|
||||
function() {
|
||||
if (!window.localStorage) {
|
||||
// If we can not use localStorage to remember how many times the user has
|
||||
// dismissed the bubble, assume he has dismissed it. Otherwise we might end
|
||||
// up showing it every time the host application loads, into eternity.
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
var key = this.LOCAL_STORAGE_PREFIX + this.DISMISSED_;
|
||||
|
||||
// If the key has never been set, localStorage yields undefined, which
|
||||
// Number() turns into NaN. In that case we'll fall back to zero for
|
||||
// clarity's sake.
|
||||
var value = Number(window.localStorage[key]) || 0;
|
||||
|
||||
return value >= this.NUMBER_OF_TIMES_TO_DISMISS;
|
||||
} catch (ex) {
|
||||
// If we got here, something is wrong with the localStorage. Make the same
|
||||
// assumption as when it does not exist at all. Exceptions should only
|
||||
// occur when setting a value (due to storage limitations) but let's be
|
||||
// extra careful.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the application is running in fullscreen mode.
|
||||
* @return {boolean} Whether the application is running in fullscreen mode.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.isFullscreen_ = function() {
|
||||
return !!window.navigator.standalone;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the application is running inside Mobile Safari.
|
||||
* @return {boolean} True if the current user agent looks like Mobile Safari.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.isMobileSafari_ = function() {
|
||||
return this.MOBILE_SAFARI_USERAGENT_REGEX_.test(window.navigator.userAgent);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the application is running on an iPad.
|
||||
* @return {boolean} True if the current user agent looks like an iPad.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.isIpad_ = function() {
|
||||
return this.IPAD_USERAGENT_REGEX_.test(window.navigator.userAgent);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Positions the bubble at the bottom of the viewport using an animated
|
||||
* transition.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.setPosition = function() {
|
||||
this.element_.style.WebkitTransition = '-webkit-transform 0.7s ease-out';
|
||||
this.element_.style.WebkitTransform =
|
||||
'translateY(' + this.getVisibleYPosition_() + 'px)';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Destroys the bubble by removing its DOM nodes from the document, and
|
||||
* remembers that it was dismissed.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.closeClickHandler_ = function() {
|
||||
this.destroy();
|
||||
this.rememberDismissal_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets called after a while if the user ignores the bubble.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.autoDestruct_ = function() {
|
||||
if (this.hasBeenDestroyed_) {
|
||||
return;
|
||||
}
|
||||
this.element_.style.WebkitTransition = '-webkit-transform 0.7s ease-in';
|
||||
this.element_.style.WebkitTransform =
|
||||
'translateY(' + this.getHiddenYPosition_() + 'px)';
|
||||
window.setTimeout(google.bind(this.destroy, this), 700);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the y offset used to show the bubble (i.e., position it on-screen).
|
||||
* @return {number} The y offset.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.getVisibleYPosition_ = function() {
|
||||
return this.isIpad_() ? window.pageYOffset + 17 :
|
||||
window.pageYOffset - this.element_.offsetHeight + window.innerHeight - 17;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the y offset used to hide the bubble (i.e., position it off-screen).
|
||||
* @return {number} The y offset.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.getHiddenYPosition_ = function() {
|
||||
return this.isIpad_() ? window.pageYOffset - this.element_.offsetHeight :
|
||||
window.pageYOffset + window.innerHeight;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The url of the app's bookmark icon.
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.iconUrl_;
|
||||
|
||||
|
||||
/**
|
||||
* Scrapes the document for a link element that specifies an Apple favicon and
|
||||
* returns the icon url. Returns an empty data url if nothing can be found.
|
||||
* @return {string} A url string.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.getIconUrl_ = function() {
|
||||
if (!this.iconUrl_) {
|
||||
var link = this.getLink(this.REL_ICON_);
|
||||
if (!link || !(this.iconUrl_ = link.href)) {
|
||||
this.iconUrl_ = 'data:image/png;base64,';
|
||||
}
|
||||
}
|
||||
return this.iconUrl_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the requested link tag if it exists.
|
||||
* @param {string} rel The rel attribute of the link tag to get.
|
||||
* @return {Element} The requested link tag or null.
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.getLink = function(rel) {
|
||||
rel = rel.toLowerCase();
|
||||
var links = document.getElementsByTagName('link');
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
var currLink = /** @type {Element} */ (links[i]);
|
||||
if (currLink.getAttribute('rel').toLowerCase() == rel) {
|
||||
return currLink;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the bubble and appends it to the document.
|
||||
* @return {Element} The bubble element.
|
||||
* @private
|
||||
*/
|
||||
google.bookmarkbubble.Bubble.prototype.build_ = function() {
|
||||
var bubble = document.createElement('div');
|
||||
var isIpad = this.isIpad_();
|
||||
|
||||
bubble.style.position = 'absolute';
|
||||
bubble.style.zIndex = 1000;
|
||||
bubble.style.width = '100%';
|
||||
bubble.style.left = '0';
|
||||
bubble.style.top = '0';
|
||||
|
||||
var bubbleInner = document.createElement('div');
|
||||
bubbleInner.style.position = 'relative';
|
||||
bubbleInner.style.width = '214px';
|
||||
bubbleInner.style.margin = isIpad ? '0 0 0 82px' : '0 auto';
|
||||
bubbleInner.style.border = '2px solid #fff';
|
||||
bubbleInner.style.padding = '20px 20px 20px 10px';
|
||||
bubbleInner.style.WebkitBorderRadius = '8px';
|
||||
bubbleInner.style.WebkitBoxShadow = '0 0 8px rgba(0, 0, 0, 0.7)';
|
||||
bubbleInner.style.WebkitBackgroundSize = '100% 8px';
|
||||
bubbleInner.style.backgroundColor = '#b0c8ec';
|
||||
bubbleInner.style.background = '#cddcf3 -webkit-gradient(linear, ' +
|
||||
'left bottom, left top, ' + isIpad ?
|
||||
'from(#cddcf3), to(#b3caed)) no-repeat top' :
|
||||
'from(#b3caed), to(#cddcf3)) no-repeat bottom';
|
||||
bubbleInner.style.font = '13px/17px sans-serif';
|
||||
bubble.appendChild(bubbleInner);
|
||||
|
||||
// The "Add to Home Screen" text is intended to be the exact same size text
|
||||
// that is displayed in the menu of Mobile Safari on iPhone.
|
||||
bubbleInner.innerHTML = 'Install this web app on your phone: tap ' +
|
||||
'<b style="font-size:15px">+</b> and then <b>\'Add to Home Screen\'</b>';
|
||||
|
||||
var icon = document.createElement('div');
|
||||
icon.style['float'] = 'left';
|
||||
icon.style.width = '55px';
|
||||
icon.style.height = '55px';
|
||||
icon.style.margin = '-2px 7px 3px 5px';
|
||||
icon.style.background =
|
||||
'#fff url(' + this.getIconUrl_() + ') no-repeat -1px -1px';
|
||||
icon.style.WebkitBackgroundSize = '57px';
|
||||
icon.style.WebkitBorderRadius = '10px';
|
||||
icon.style.WebkitBoxShadow = '0 2px 5px rgba(0, 0, 0, 0.4)';
|
||||
bubbleInner.insertBefore(icon, bubbleInner.firstChild);
|
||||
|
||||
var arrow = document.createElement('div');
|
||||
arrow.style.backgroundImage = 'url(' + this.IMAGE_ARROW_DATA_URL_ + ')';
|
||||
arrow.style.width = '25px';
|
||||
arrow.style.height = '19px';
|
||||
arrow.style.position = 'absolute';
|
||||
arrow.style.left = '111px';
|
||||
if (isIpad) {
|
||||
arrow.style.WebkitTransform = 'rotate(180deg)';
|
||||
arrow.style.top = '-19px';
|
||||
} else {
|
||||
arrow.style.bottom = '-19px';
|
||||
}
|
||||
bubbleInner.appendChild(arrow);
|
||||
|
||||
var close = document.createElement('a');
|
||||
close.onclick = google.bind(this.closeClickHandler_, this);
|
||||
close.style.position = 'absolute';
|
||||
close.style.display = 'block';
|
||||
close.style.top = '-3px';
|
||||
close.style.right = '-3px';
|
||||
close.style.width = '16px';
|
||||
close.style.height = '16px';
|
||||
close.style.border = '10px solid transparent';
|
||||
close.style.background =
|
||||
'url(' + this.IMAGE_CLOSE_DATA_URL_ + ') no-repeat';
|
||||
bubbleInner.appendChild(close);
|
||||
|
||||
return bubble;
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Sample</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width,minimum-scale=1.0,maximum-scale=1.0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon-precomposed" href="../images/icon_calendar.png" />
|
||||
<script type="text/javascript" src="../bookmark_bubble.js"></script>
|
||||
<script type="text/javascript" src="example.js"></script>
|
||||
</head>
|
||||
<body style="height: 3000px;">
|
||||
<p>The bookmark bubble will show after a second, if:</p>
|
||||
<ul>
|
||||
<li>It has not been previously dismissed too often</li>
|
||||
<li>The application is not running in full screen mode</li>
|
||||
<li>The bookmark bubble hash token is not present</li>
|
||||
</ul>
|
||||
<p>Supported browsers:</p>
|
||||
<ul>
|
||||
<li>iPhone / iPod / iPad Mobile Safari 3.0+</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/** @fileoverview Example of how to use the bookmark bubble. */
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
window.setTimeout(function() {
|
||||
var bubble = new google.bookmarkbubble.Bubble();
|
||||
|
||||
var parameter = 'bmb=1';
|
||||
|
||||
bubble.hasHashParameter = function() {
|
||||
return window.location.hash.indexOf(parameter) != -1;
|
||||
};
|
||||
|
||||
bubble.setHashParameter = function() {
|
||||
if (!this.hasHashParameter()) {
|
||||
window.location.hash += parameter;
|
||||
}
|
||||
};
|
||||
|
||||
bubble.getViewportHeight = function() {
|
||||
window.console.log('Example of how to override getViewportHeight.');
|
||||
return window.innerHeight;
|
||||
};
|
||||
|
||||
bubble.getViewportScrollY = function() {
|
||||
window.console.log('Example of how to override getViewportScrollY.');
|
||||
return window.pageYOffset;
|
||||
};
|
||||
|
||||
bubble.registerScrollHandler = function(handler) {
|
||||
window.console.log('Example of how to override registerScrollHandler.');
|
||||
window.addEventListener('scroll', handler, false);
|
||||
};
|
||||
|
||||
bubble.deregisterScrollHandler = function(handler) {
|
||||
window.console.log('Example of how to override deregisterScrollHandler.');
|
||||
window.removeEventListener('scroll', handler, false);
|
||||
};
|
||||
|
||||
bubble.showIfAllowed();
|
||||
}, 1000);
|
||||
}, false);
|
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 206 B |
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Generates base64 versions of images so they can be inlined using the 'data:'
|
||||
# URI scheme.
|
||||
|
||||
declare -ra IMAGE_FILES=( close.png arrow.png )
|
||||
|
||||
for image in ${IMAGE_FILES[@]}; do
|
||||
OUT="$image.base64"
|
||||
cat "$image" \
|
||||
| uuencode -m ignore-this \
|
||||
| grep -v begin-base64 \
|
||||
| grep -v "====" \
|
||||
| xargs echo \
|
||||
| sed -e 's/ //g' \
|
||||
| xargs echo -n \
|
||||
> $OUT
|
||||
ls -l $OUT
|
||||
done
|
After Width: | Height: | Size: 3.7 KiB |
27
lib/middleman/templates/mobile/source/tools/wspl/README
Executable file
|
@ -0,0 +1,27 @@
|
|||
How to run the tests:
|
||||
|
||||
* The tests themselves assume that jsunit is in a sibling directory
|
||||
to the one containing the distrbution. If this is not so, it is sufficient
|
||||
to edit the paths in the test files. (On UNIX, symlinks may be your
|
||||
friend if this is not convenient.)
|
||||
|
||||
* jsmock.js (available from http://jsmock.sourceforge.net/) should
|
||||
be placed in the distribution directory.
|
||||
|
||||
* Specify the test files via a URL parameter. (This might be an issue
|
||||
with jsunit: http://digitalmihailo.blogspot.com/2008/06/make-jsunit-work-in-firefox-30.html)
|
||||
|
||||
For example, if the root of your downloaded of the distribution is /mypath:
|
||||
|
||||
file:///mypath/jsunit/testRunner.html?testpage=mypath/webstorageportabilitylayer/dbwrapper_gears_test.html
|
||||
file:///mypath/jsunit/testRunner.html?testpage=mypath/webstorageportabilitylayer/dbwrapper_html5_test.html
|
||||
file:///mypath/jsunit/testRunner.html?testpage=mypath/webstorageportabilitylayer/dbwrapperapi_test.html
|
||||
|
||||
NB: the leading / in a UNIX path is not included in mypath so setting
|
||||
it via pwd will not deliver the desired effect.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
45
lib/middleman/templates/mobile/source/tools/wspl/databasefactory.js
Executable file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Namespace.
|
||||
google.wspl.DatabaseFactory = google.wspl.DatabaseFactory || {};
|
||||
|
||||
/**
|
||||
* Factory function to build databases in a cross-API manner.
|
||||
* @param {string} dbName of the database
|
||||
* @param {string} dbworkerUrl the URL for Gears worker.
|
||||
* @return {google.wspl.Database} The database object.
|
||||
*/
|
||||
google.wspl.DatabaseFactory.createDatabase = function(dbName, dbworkerUrl) {
|
||||
var dbms;
|
||||
if (window.openDatabase) {
|
||||
// We have HTML5 functionality.
|
||||
dbms = new google.wspl.html5.Database(dbName);
|
||||
} else {
|
||||
// Try to use Google Gears.
|
||||
var gearsDb = goog.gears.getFactory().create('beta.database');
|
||||
var wp = goog.gears.getFactory().create('beta.workerpool');
|
||||
|
||||
// Note that Gears will not allow file based URLs when creating a worker.
|
||||
dbms = new wireless.db.gears.Database();
|
||||
dbms.openDatabase('', dbName, gearsDb);
|
||||
wp.onmessage = google.bind(dbms.onMessage_, dbms);
|
||||
|
||||
// Comment this line out to use the synchronous database.
|
||||
dbms.startWorker(wp, dbworkerUrl, 0);
|
||||
}
|
||||
return dbms;
|
||||
};
|
324
lib/middleman/templates/mobile/source/tools/wspl/dbworker.js
Executable file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A worker thread that performs synchronous queries against a
|
||||
* Gears database on behalf of an asynchronous calling client.
|
||||
*
|
||||
* The worker replies to the sender with messages to pass results, errors, and
|
||||
* notifications about completed transactions. The type field of the message
|
||||
* body specifies the message type. For each successful statement, a RESULT
|
||||
* message is sent with a result attribute containing the Gears result set. For
|
||||
* the first unsuccessful statement, an ERROR message will be sent with details
|
||||
* stored in the error field. After the transaction has been committed, a COMMIT
|
||||
* message is sent. If the transaction is rolled back, a ROLLBACK message is
|
||||
* sent.
|
||||
*
|
||||
* NB: The worker must be served over http. Further, to operate successfully,
|
||||
* it requires the inclusion of global_functions.js and gearsutils.js.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DbWorker to handle incoming messages, execute queries, and return
|
||||
* results to the main thread.
|
||||
*
|
||||
* @param {GearsWorkerPool} wp The gears worker pool.
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.gears.DbWorker = function(wp) {
|
||||
|
||||
/**
|
||||
* An array of transaction ids representing the transactions that are open on
|
||||
* the database.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.transactions_ = [];
|
||||
|
||||
/**
|
||||
* The gears worker pool.
|
||||
* @type {GearsWorkerPool}
|
||||
* @private
|
||||
*/
|
||||
this.wp_ = wp;
|
||||
|
||||
this.wp_.onmessage = google.bind(this.onMessage_, this);
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.STARTED
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* The gears database that this worker thread will interact with.
|
||||
* @type {GearsDatabase}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.db_;
|
||||
|
||||
/**
|
||||
* A singleton instance of DbWorker.
|
||||
* @type {google.wspl.gears.DbWorker?}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.instance_;
|
||||
|
||||
/**
|
||||
* The sender ID of the incomming messages. Default to 0 for workerpool ID.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.senderId_ = 0;
|
||||
|
||||
/**
|
||||
* Message type constants for worker command messages.
|
||||
* @enum {number}
|
||||
*/
|
||||
google.wspl.gears.DbWorker.CommandTypes = {
|
||||
OPEN: 1,
|
||||
BEGIN: 2,
|
||||
EXECUTE: 3,
|
||||
COMMIT: 4,
|
||||
ROLLBACK: 5
|
||||
};
|
||||
|
||||
/**
|
||||
* Message type constants for worker reply messages.
|
||||
* @enum {number}
|
||||
*/
|
||||
google.wspl.gears.DbWorker.ReplyTypes = {
|
||||
RESULT: 1,
|
||||
FAILURE: 2,
|
||||
COMMIT: 3,
|
||||
ROLLBACK: 4,
|
||||
STARTED: 5,
|
||||
OPEN_SUCCESSFUL: 6,
|
||||
OPEN_FAILED: 7,
|
||||
LOG: 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the DbWorker.
|
||||
*/
|
||||
google.wspl.gears.DbWorker.start = function() {
|
||||
var wp = google.gears.workerPool;
|
||||
google.wspl.gears.DbWorker.instance_ = new google.wspl.gears.DbWorker(wp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an OPEN command from the main thread.
|
||||
*
|
||||
* @param {string} userId The user to which the database belongs.
|
||||
* @param {string} name The database's name.
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleOpen_ = function(userId, name) {
|
||||
this.log_('Attempting to create Gears database: userId=' + userId + ', name='
|
||||
+ name);
|
||||
try {
|
||||
this.db_ = google.gears.factory.create('beta.database', '1.0');
|
||||
google.wspl.GearsUtils.openDatabase(userId, name, this.db_, this.log_);
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.OPEN_SUCCESSFUL
|
||||
});
|
||||
} catch (ex) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED,
|
||||
'error': ex
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a EXECUTE command from the main thread.
|
||||
*
|
||||
* @param {!Array.<google.wspl.Statement>} statements The statements to execute.
|
||||
* @param {number} callbackId The callback to invoke after each execution.
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleExecute_ =
|
||||
function(statements, callbackId, transactionId) {
|
||||
var self = this;
|
||||
try {
|
||||
this.executeAll_(statements, function(results) {
|
||||
self.sendMessageToWorker_(/** @type {string} */({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.RESULT,
|
||||
'results': results,
|
||||
'callbackId': callbackId,
|
||||
'transactionId': transactionId
|
||||
}));
|
||||
});
|
||||
} catch (e) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.FAILURE,
|
||||
'error': e,
|
||||
'callbackId': callbackId,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes all of the statements on the Gears database. The callback is
|
||||
* invoked with the query results after each successful query execution.
|
||||
*
|
||||
* @param {!Array.<Object>} statements The statements to execute.
|
||||
* @param {Function} callback The callback to invoke with query results.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.executeAll_ =
|
||||
function(statements, callback) {
|
||||
var results = [];
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var resultset = this.db_.execute(statements[i]['sql'],
|
||||
statements[i]['params']);
|
||||
var result = google.wspl.GearsUtils.resultSetToObjectArray(resultset);
|
||||
results.push(result);
|
||||
}
|
||||
callback(results);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a BEGIN command from the main thread.
|
||||
*
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleBegin_ = function(transactionId) {
|
||||
this.transactions_.push(transactionId);
|
||||
this.db_.execute('BEGIN IMMEDIATE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a COMMIT command from the main thread.
|
||||
*
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleCommit_ = function(transactionId) {
|
||||
this.db_.execute('COMMIT');
|
||||
this.postCommit_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a ROLLBACK command from the main thread.
|
||||
*
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleRollback_ = function(transactionId) {
|
||||
this.db_.execute('ROLLBACK');
|
||||
this.postRollback_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a COMMIT reply to the main thread for each transaction that was
|
||||
* committed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.postCommit_ = function() {
|
||||
for (var i = this.transactions_.length - 1; i >= 0; i--) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.COMMIT,
|
||||
'transactionId': this.transactions_[i]
|
||||
});
|
||||
}
|
||||
this.transactions_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a ROLLBACK reply to the main thread for each transaction that was
|
||||
* rolled back.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.postRollback_ = function() {
|
||||
for (var i = this.transactions_.length - 1; i >= 0; i --) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK,
|
||||
'transactionId': this.transactions_[i]
|
||||
});
|
||||
}
|
||||
this.transactions_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles incomming messages.
|
||||
* @param {string} a Deprecated.
|
||||
* @param {number} b Deprecated.
|
||||
* @param {Object} messageObject The message object.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.onMessage_ =
|
||||
function(a, b, messageObject) {
|
||||
this.senderId_ = messageObject.sender;
|
||||
var message = messageObject.body;
|
||||
var type = message['type'];
|
||||
var name = message['name'];
|
||||
var statements = message['statements'];
|
||||
var callbackId = message['callbackId'];
|
||||
var transactionId = message['transactionId'];
|
||||
var userId = message['userId'];
|
||||
|
||||
try {
|
||||
switch(type) {
|
||||
case google.wspl.gears.DbWorker.CommandTypes.OPEN:
|
||||
this.handleOpen_(userId, name);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.EXECUTE:
|
||||
this.handleExecute_(statements, callbackId, transactionId);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.BEGIN:
|
||||
this.handleBegin_(transactionId);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.COMMIT:
|
||||
this.handleCommit_(transactionId);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.ROLLBACK:
|
||||
this.handleRollback_(transactionId);
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
this.log_('Database worker failed: ' + ex.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a log message to the main thread to be logged.
|
||||
* @param {string} msg The message to log.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.log_ = function(msg) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.LOG,
|
||||
'msg': msg
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to the main worker thread.
|
||||
* @param {Object} msg The message object to send.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.sendMessageToWorker_ = function(msg) {
|
||||
this.wp_.sendMessage(msg, this.senderId_);
|
||||
};
|
393
lib/middleman/templates/mobile/source/tools/wspl/dbworker_test.html
Executable file
|
@ -0,0 +1,393 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears worker tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="gearsutils.js"></script>
|
||||
<script type="text/javascript" src="dbworker.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var db;
|
||||
var wp;
|
||||
var factory;
|
||||
var callbackId = 10;
|
||||
var transactionId = 15;
|
||||
var name = 'name';
|
||||
var userId = 'userId';
|
||||
var utils;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
|
||||
// Mock the Gears factory.
|
||||
factory = mockControl.createMock();
|
||||
factory.addMockMethod('create');
|
||||
|
||||
// Mock Google Gears.
|
||||
google.gears = {};
|
||||
google.gears.factory = {};
|
||||
google.gears.factory.create = factory.create;
|
||||
|
||||
// Mock the Gears workerpool object.
|
||||
wp = mockControl.createMock({
|
||||
allowCrossOrigin: function(){},
|
||||
sendMessage: function(){}
|
||||
});
|
||||
|
||||
// Mock the Gears database object.
|
||||
db = mockControl.createMock({
|
||||
execute: function(){},
|
||||
open: function(){},
|
||||
close: function(){},
|
||||
remove: function(){}
|
||||
});
|
||||
|
||||
// Mock the Gears utility classes
|
||||
utils = mockControl.createMock({
|
||||
openDatabase: function(){},
|
||||
});
|
||||
|
||||
google.wspl = google.wspl || {};
|
||||
google.wspl.GearsUtils = google.wspl.GearsUtils || {};
|
||||
google.wspl.GearsUtils.openDatabase = utils.openDatabase;
|
||||
google.wspl.GearsUtils.resultSetToObjectArray = function(rs) {
|
||||
return rs;
|
||||
};
|
||||
}
|
||||
|
||||
function buildWorker() {
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), 0).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Wrong message type.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.STARTED, msg.type);
|
||||
});
|
||||
var worker = new google.wspl.gears.DbWorker(wp);
|
||||
worker.db_ = db;
|
||||
worker.log_ = function() {};
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
function testConstruction() {
|
||||
var worker = buildWorker();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleExecute_success() {
|
||||
var worker = buildWorker();
|
||||
var stat1 = {sql: 'sql1', params: [1, 2]};
|
||||
var stat2 = {sql: 'sql2', params: [3, 4]};
|
||||
var statements = [stat1, stat2];
|
||||
var type = google.wspl.gears.DbWorker.ReplyTypes.RESULT;
|
||||
|
||||
db.expects().execute(stat1.sql, stat1.params).andReturn('result1');
|
||||
db.expects().execute(stat2.sql, stat2.params).andReturn('result2');
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Wrong message type.', type, msg.type);
|
||||
assertEquals('Wrong results.length', 2, msg.results.length);
|
||||
assertEquals('Wrong results[0].', 'result1', msg.results[0]);
|
||||
assertEquals('Wrong results[1].', 'result2', msg.results[1]);
|
||||
assertEquals('Wrong callbackId.', callbackId, msg.callbackId);
|
||||
assertEquals('Wrong transactionId.', transactionId, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.handleExecute_(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleExecute_failure() {
|
||||
var worker = buildWorker();
|
||||
var stat1 = {sql: 'sql1', params: [1, 2]};
|
||||
var stat2 = {sql: 'sql2', params: [3, 4]};
|
||||
var stat3 = {sql: 'sql3', params: [5, 6]};
|
||||
var statements = [stat1, stat2, stat3];
|
||||
var type1 = google.wspl.gears.DbWorker.ReplyTypes.RESULT;
|
||||
var type2 = google.wspl.gears.DbWorker.ReplyTypes.FAILURE;
|
||||
var error = 'sql error';
|
||||
|
||||
db.expects().execute(stat1.sql, stat1.params).andReturn('result1');
|
||||
db.expects().execute(stat2.sql, stat2.params).andThrow(error);
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Wrong message type.', type2, msg.type);
|
||||
assertEquals('Wrong result.', error, msg.error.message);
|
||||
assertEquals('Wrong callbackId.', callbackId, msg.callbackId);
|
||||
assertEquals('Wrong transactionId.', transactionId, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.handleExecute_(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleBegin() {
|
||||
var worker = buildWorker();
|
||||
|
||||
// Expecting two transactions to begin.
|
||||
db.expects().execute('BEGIN IMMEDIATE');
|
||||
db.expects().execute('BEGIN IMMEDIATE');
|
||||
|
||||
worker.handleBegin_(transactionId);
|
||||
worker.handleBegin_(22);
|
||||
|
||||
assertEquals('Did not save first transaction id', transactionId,
|
||||
worker.transactions_[0]);
|
||||
assertEquals('Did not save second transaction id', 22,
|
||||
worker.transactions_[1]);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleCommit() {
|
||||
var worker = buildWorker();
|
||||
db.expects().execute('COMMIT');
|
||||
worker.handleCommit_(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleRollback() {
|
||||
var worker = buildWorker();
|
||||
db.expects().execute('ROLLBACK');
|
||||
worker.handleRollback_(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpen_success() {
|
||||
var worker = buildWorker();
|
||||
worker.db_ = null;
|
||||
|
||||
factory.expects().create('beta.database', '1.0').andReturn(db);
|
||||
utils.expects().openDatabase(userId, name, db, worker.log_).andReturn(db);
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function(msg) {
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.OPEN_SUCCESSFUL, msg.type);
|
||||
});
|
||||
|
||||
worker.handleOpen_(userId, name);
|
||||
assertEquals('Database wrongly set', db, worker.db_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpen_failure_gearsfactory() {
|
||||
var worker = buildWorker();
|
||||
worker.db_ = null;
|
||||
|
||||
factory.expects().create('beta.database', '1.0').andThrow('blah!');
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function(msg) {
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED, msg.type);
|
||||
});
|
||||
|
||||
worker.handleOpen_(userId, name);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpen_failure_dbopen() {
|
||||
var worker = buildWorker();
|
||||
worker.db_ = null;
|
||||
|
||||
factory.expects().create('beta.database', '1.0').andReturn(null);
|
||||
utils.expects().openDatabase(userId, name, null, worker.log_).andThrow('blah!');
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function(msg) {
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED, msg.type);
|
||||
});
|
||||
|
||||
worker.handleOpen_(userId, name);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testPostCommit() {
|
||||
var worker = buildWorker();
|
||||
worker.transactions_ = [4, 5];
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.COMMIT, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
5, msg.transactionId);
|
||||
});
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.COMMIT, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
4, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.postCommit_();
|
||||
assertEquals('Did not clear the transactions.', 0,
|
||||
worker.transactions_.length);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testPostRollback() {
|
||||
var worker = buildWorker();
|
||||
worker.transactions_ = [4, 5];
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
5, msg.transactionId);
|
||||
});
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
4, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.postRollback_();
|
||||
assertEquals('Did not clear the transactions.', 0,
|
||||
worker.transactions_.length);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage() {
|
||||
var messageObject = {sender: 123, body: {}};
|
||||
var worker = buildWorker();
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
|
||||
assertEquals('Wrong sender ID.', 123, worker.senderId_);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_open() {
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.OPEN,
|
||||
name: name,
|
||||
userId: userId
|
||||
}};
|
||||
|
||||
var worker = buildWorker();
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('open');
|
||||
worker.handleOpen_ = handler.open;
|
||||
handler.expects().open(userId, name);
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_execute() {
|
||||
var worker = buildWorker();
|
||||
var statements = ['stat1', 'stat2'];
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.EXECUTE,
|
||||
statements: statements,
|
||||
callbackId: callbackId,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleExecute_ = function(stat, call, trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong statements.', statements, stat);
|
||||
assertEquals('Wrong callback id.', callbackId, call);
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleExecute_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_begin() {
|
||||
var worker = buildWorker();
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.BEGIN,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleBegin_ = function(trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleBegin_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_commit() {
|
||||
var worker = buildWorker();
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.COMMIT,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleCommit_ = function(trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleCommit_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_rollback() {
|
||||
var worker = buildWorker();
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.ROLLBACK,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleRollback_ = function(trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleRollback_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
32
lib/middleman/templates/mobile/source/tools/wspl/dbworkerstarter.js
Executable file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Starts the dbworker.
|
||||
*
|
||||
* When constructing the worker for execution, this needs to be the last
|
||||
* file. The worker consists of the following source files combined together.
|
||||
*
|
||||
* globalfunctions.js
|
||||
* gearsutils.js
|
||||
* dbworker.js
|
||||
* dbworkerstarter.js
|
||||
*
|
||||
* and then loaded into a Gears worker process as implemented in
|
||||
* databasefactory.js
|
||||
*/
|
||||
|
||||
google.wspl.gears.DbWorker.start();
|
595
lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears.js
Executable file
|
@ -0,0 +1,595 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A Gears implementation of dbwrapperapi Database.
|
||||
*
|
||||
* This implementation locks database access upon invoking the transaction's
|
||||
* populate callback. Statements are then asynchronously sent to a worker
|
||||
* thread for execution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see google.wspl.Database#Database
|
||||
* @param {boolean} opt_sync Perform all callbacks synchronously.
|
||||
* @constructor
|
||||
* @extends google.wspl.Database
|
||||
*/
|
||||
google.wspl.gears.Database = function(opt_sync) {
|
||||
google.wspl.Database.call(this);
|
||||
|
||||
/**
|
||||
* Begin transactions synchronously.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.synchronous_ = !!opt_sync;
|
||||
};
|
||||
google.inherits(google.wspl.gears.Database, google.wspl.Database);
|
||||
|
||||
/**
|
||||
* The time to wait for the dbworker to reply with STARTED.
|
||||
* @type {number}
|
||||
*/
|
||||
google.wspl.gears.Database.TIMEOUT = 60000;
|
||||
|
||||
/**
|
||||
* Whether the gears worker failed to reply with STARTED before TIMEOUT.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.workerTimeout_ = false;
|
||||
|
||||
/**
|
||||
* Flag set when the worker is ready with an open database connection.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.workerReady_ = false;
|
||||
|
||||
/**
|
||||
* Flag set when this database should use the worker to process transactions.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.useWorker_ = false;
|
||||
|
||||
/**
|
||||
* The user for this database.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.userId_;
|
||||
|
||||
/**
|
||||
* The name for this database.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.name_;
|
||||
|
||||
/**
|
||||
* A map of open transactions and their callbacks.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.transactions_ = {};
|
||||
|
||||
/**
|
||||
* An array of transaction ids that should be executed in order as the lock
|
||||
* becomes available.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.queuedTransactions_ = [];
|
||||
|
||||
/**
|
||||
* The transaction lock for this database.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.locked_ = false;
|
||||
|
||||
/**
|
||||
* The number of transactions to be used as an index.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.transCount_ = 1;
|
||||
|
||||
/**
|
||||
* The id of the transaction being executed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.currentTransactionId_;
|
||||
|
||||
/**
|
||||
* The Gears worker pool.
|
||||
* @type {GearsWorkerPool}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.wp_;
|
||||
|
||||
/**
|
||||
* The worker ID.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.workerId_;
|
||||
|
||||
/**
|
||||
* The Gears database object.
|
||||
* @type {GearsDatabase}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.db_;
|
||||
|
||||
/**
|
||||
* Opens a new Gears database. This operation can only be performed once.
|
||||
* @param {string} userId The user for this database.
|
||||
* @param {string} name The name for this database.
|
||||
* @param {GearsDatabase} gearsDb The gears database.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.openDatabase = function(userId, name,
|
||||
gearsDb) {
|
||||
if (!this.db_) {
|
||||
this.db_ = gearsDb;
|
||||
this.userId_ = userId;
|
||||
this.name_ = name;
|
||||
google.wspl.GearsUtils.openDatabase(userId, name, this.db_,
|
||||
google.logger);
|
||||
} else {
|
||||
google.logger('openDatabase already invoked.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a worker to handle the database interactions. The worker will be
|
||||
* asynchronously started after the specified delay and will not be used until
|
||||
* the completion of any pending transaction.
|
||||
* @param {GearsWorkerPool} wp The Gears worker pool.
|
||||
* @param {string} workerUrl The URL to find the gears database worker.
|
||||
* @return {number} The worker ID.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.startWorker = function(wp, workerUrl) {
|
||||
this.wp_ = wp;
|
||||
|
||||
google.logger('Starting dbworker thread.');
|
||||
|
||||
this.workerId_ = wp.createWorkerFromUrl(workerUrl);
|
||||
|
||||
this.timeoutId_ = window.setTimeout(google.bind(this.handleTimeout_, this),
|
||||
google.wspl.gears.Database.TIMEOUT);
|
||||
|
||||
return this.workerId_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.Transaction#createTransaction
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.createTransaction = function(populate,
|
||||
opt_callback) {
|
||||
var transactionCallback = opt_callback || {
|
||||
onSuccess : function() {},
|
||||
onFailure : function() {}
|
||||
};
|
||||
|
||||
var id = this.transCount_++;
|
||||
var transaction = new google.wspl.gears.Transaction(id, this);
|
||||
|
||||
this.saveTransaction_(transaction, transactionCallback, populate);
|
||||
|
||||
this.queuedTransactions_.push(transaction.id_);
|
||||
this.nextTransaction_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the transaction and transaction callback to be accessed later when a
|
||||
* commit or rollback is performed.
|
||||
*
|
||||
* @param {google.wspl.gears.Transaction} transaction The transaction that the
|
||||
* callback belongs to.
|
||||
* @param {Object} callback A transaction callback with onSuccess and onFailure
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.saveTransaction_ = function(
|
||||
transaction, callback, populate) {
|
||||
this.transactions_[transaction.id_] = {
|
||||
transaction: transaction,
|
||||
callback: callback,
|
||||
populate: populate
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles incomming messages.
|
||||
* @param {string} a Deprecated.
|
||||
* @param {number} b Deprecated.
|
||||
* @param {Object} messageObject The message object.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.onMessage_ =
|
||||
function(a, b, messageObject) {
|
||||
var message = messageObject.body;
|
||||
|
||||
try {
|
||||
switch(message['type']) {
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.RESULT:
|
||||
this.handleResult_(message['results'], message['callbackId'],
|
||||
message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.FAILURE:
|
||||
this.handleFailure_(message['error'], message['callbackId'],
|
||||
message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.COMMIT:
|
||||
this.handleCommit_(message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK:
|
||||
this.handleRollback_(message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.STARTED:
|
||||
this.handleStarted_();
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.OPEN_SUCCESSFUL:
|
||||
this.handleOpenSuccessful_();
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED:
|
||||
this.handleOpenFailed_(message['error']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.LOG:
|
||||
google.logger(message['msg']);
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
google.logger('Gears database failed: ' + ex.message, ex);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a new Gears database.
|
||||
*
|
||||
* @param {string} userId The user to which the database belongs.
|
||||
* @param {string} name The name of the database.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doOpen = function(userId, name) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.OPEN,
|
||||
'name': name,
|
||||
'userId': userId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Begins a new transaction on the Gears database.
|
||||
*
|
||||
* @param {number} transactionId The id of the transaction being committed.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doBegin = function(transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.db_.execute('BEGIN IMMEDIATE');
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.BEGIN,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Commits the current transaction on the Gears database. The transactionId
|
||||
* is used to invoke the callback associated with the transaction.
|
||||
*
|
||||
* @param {number} transactionId The id of the transaction being committed.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doCommit = function(transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.db_.execute('COMMIT');
|
||||
this.postCommit_();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.COMMIT,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Rolls the current transaction back on the Gears database. The transactionId
|
||||
* is used to invoke the callback associated with the transaction.
|
||||
*
|
||||
* @param {number} transactionId The id of the transaction being rolled back.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doRollback = function(transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.db_.execute('ROLLBACK');
|
||||
this.postRollback_();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.ROLLBACK,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the Gears database. The transactionId and
|
||||
* callbackId are used to identify the callback that should be invoked when
|
||||
* handleResult or handleFailure is called.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements The group of statements to
|
||||
* execute
|
||||
* @param {number} callbackId The callback to invoke for each statement
|
||||
* @param {number} transactionId The transaction that the statements belong to
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doExecute = function(statements,
|
||||
callbackId,
|
||||
transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.doExecuteSynchronously_(statements, callbackId, transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
var newStatements = [];
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
newStatements[i] = {
|
||||
'sql': statements[i].sql,
|
||||
'params': statements[i].params
|
||||
};
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.EXECUTE,
|
||||
'statements': newStatements,
|
||||
'callbackId': callbackId,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the synchronous Gears databse.
|
||||
* @param {Array.<google.wspl.Statement>} statements
|
||||
* @param {number} callbackId
|
||||
* @param {number} transactionId
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doExecuteSynchronously_ =
|
||||
function(statements, callbackId, transactionId) {
|
||||
var db = this;
|
||||
var results = [];
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
try {
|
||||
var resultset = this.db_.execute(statements[i].sql, statements[i].params);
|
||||
var result = google.wspl.GearsUtils.resultSetToObjectArray(resultset);
|
||||
results.push(result);
|
||||
} catch (e) {
|
||||
var error = e;
|
||||
function failureCallback() {
|
||||
db.handleFailure_(error, callbackId, transactionId);
|
||||
};
|
||||
this.setTimeout_(failureCallback, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function resultCallback() {
|
||||
db.handleResult_(results, callbackId, transactionId);
|
||||
};
|
||||
this.setTimeout_(resultCallback, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a RESULT message from the worker thread.
|
||||
*
|
||||
* @param {!Array.<!Array.<Object>>} results A Gears result set.
|
||||
* @param {number} callbackId The callback to invoke.
|
||||
* @param {number} transactionId The transaction that the statement is executing
|
||||
* in.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleResult_ = function(results,
|
||||
callbackId, transactionId) {
|
||||
var transInfo = this.transactions_[transactionId];
|
||||
if (transInfo) {
|
||||
for (var i = 0, l = results.length; i < l; i++) {
|
||||
var resultSet = new google.wspl.gears.ResultSet(results[i]);
|
||||
transInfo.transaction.success(resultSet, callbackId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a FAILURE message from the worker thread.
|
||||
*
|
||||
* @param {Error} error An error produced by the Gears database
|
||||
* @param {number} callbackId The callback to invoke
|
||||
* @param {number} transactionId The transaction that the statement is executing
|
||||
* in
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleFailure_ = function(error,
|
||||
callbackId, transactionId) {
|
||||
var transInfo = this.transactions_[transactionId];
|
||||
if (transInfo) {
|
||||
transInfo.error = error;
|
||||
transInfo.transaction.failure(error, callbackId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a COMMIT message from the worker thread.
|
||||
*
|
||||
* @param {number} id The transaction id.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleCommit_ = function(id) {
|
||||
var transaction = this.removeTransaction_(id);
|
||||
if (transaction) {
|
||||
transaction.callback.onSuccess();
|
||||
}
|
||||
|
||||
this.nextTransaction_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the completion of a commit from the synchronous database.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.postCommit_ = function() {
|
||||
this.handleCommit_(this.currentTransactionId_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a ROLLBACK message from the worker thread.
|
||||
*
|
||||
* @param {number} id The transaction id
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleRollback_ = function(id) {
|
||||
var transaction = this.removeTransaction_(id);
|
||||
if (transaction) {
|
||||
transaction.callback.onFailure(transaction.error);
|
||||
}
|
||||
|
||||
this.nextTransaction_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the completion of a rollback from the synchronous database.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.postRollback_ = function() {
|
||||
this.handleRollback_(this.currentTransactionId_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a STARTED message from the worker thread.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleStarted_ = function() {
|
||||
if (!this.workerTimeout_) {
|
||||
google.logger('Dbworker started.');
|
||||
window.clearTimeout(this.timeoutId_);
|
||||
this.timeoutId_ = 0;
|
||||
this.doOpen(this.userId_, this.name_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a timeout of waiting for a STARTED message from the worker thread.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleTimeout_ = function() {
|
||||
this.workerTimeout_ = true;
|
||||
google.logger('Timed out while waiting for the dbworker to start.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a OPEN_SUCCESSFUL message from the worker thread.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleOpenSuccessful_ = function() {
|
||||
this.workerReady_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a OPEN_FAILED message from the worker thread.
|
||||
* @param {string} error
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleOpenFailed_ = function(error) {
|
||||
google.logger('Worker failed to open Gears database.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the next transaction if there is one queued.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.nextTransaction_ = function() {
|
||||
if (this.queuedTransactions_.length && !this.locked_) {
|
||||
this.locked_ = true;
|
||||
|
||||
if (this.workerReady_ && !this.useWorker_) {
|
||||
this.useWorker_ = true;
|
||||
google.logger('Switching to asynchronous database interface.');
|
||||
}
|
||||
|
||||
var id = this.queuedTransactions_.shift();
|
||||
this.currentTransactionId_ = id;
|
||||
var transactionData = this.transactions_[id];
|
||||
|
||||
var db = this;
|
||||
function populate() {
|
||||
transactionData.populate(transactionData.transaction);
|
||||
|
||||
// If populate did not execute statements on the database, invoke the
|
||||
// success callback and process the next transaction.
|
||||
if (!transactionData.transaction.isExecuting()) {
|
||||
db.handleCommit_(id);
|
||||
}
|
||||
};
|
||||
|
||||
this.setTimeout_(populate, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans up the transaction and transaction callback for the id specified.
|
||||
*
|
||||
* @param {number} id The transaction id.
|
||||
* @return {google.wspl.Transaction} The transaction and callback in an object.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.removeTransaction_ = function(id) {
|
||||
this.locked_ = false;
|
||||
var transaction = this.transactions_[id];
|
||||
if (transaction) {
|
||||
delete this.transactions_[id];
|
||||
}
|
||||
return transaction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a function using window's setTimeout.
|
||||
* @param {Function} func The function to execute.
|
||||
* @param {number} time The time delay before invocation.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.setTimeout_ = function(func, time) {
|
||||
if (this.synchronous_) {
|
||||
func();
|
||||
} else {
|
||||
window.setTimeout(func, time);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to the database worker thread.
|
||||
* @param {Object} msg The message object to send.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.sendMessageToWorker_ = function(msg) {
|
||||
this.wp_.sendMessage(msg, this.workerId_);
|
||||
};
|
404
lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears_test.html
Executable file
|
@ -0,0 +1,404 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears database wrapper tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="gearsutils.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="gears_resultset.js"></script>
|
||||
<script type="text/javascript" src="gears_transaction.js"></script>
|
||||
<script type="text/javascript" src="dbworker.js"></script>
|
||||
<script type="text/javascript" src="dbwrapper_gears.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var callbackMock;
|
||||
var dbMock;
|
||||
var wp;
|
||||
var workerId = 123;
|
||||
var transMock;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
|
||||
callbackMock = mockControl.createMock({
|
||||
onSuccess: function(){},
|
||||
onFailure: function(){}
|
||||
});
|
||||
|
||||
transMock = mockControl.createMock({
|
||||
success: function(){},
|
||||
failure: function(){}
|
||||
});
|
||||
|
||||
wp = mockControl.createMock({
|
||||
allowCrossOrigin: function(){},
|
||||
createWorkerFromUrl: function(){},
|
||||
sendMessage: function(){}
|
||||
});
|
||||
|
||||
dbMock = mockControl.createMock({
|
||||
execute: function(){},
|
||||
open: function(){},
|
||||
close: function(){},
|
||||
remove: function(){}
|
||||
});
|
||||
|
||||
google.wspl.GearsUtils.resultSetToObjectArray = function(rs) {
|
||||
return rs;
|
||||
};
|
||||
}
|
||||
|
||||
function buildSyncDatabase() {
|
||||
var database = new google.wspl.gears.Database(true);
|
||||
database.userId_ = 'userId';
|
||||
database.name_ = 'name';
|
||||
database.db_ = dbMock;
|
||||
return database;
|
||||
}
|
||||
|
||||
function buildDatabase() {
|
||||
var database = buildSyncDatabase();
|
||||
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.OPEN;
|
||||
wp.expects().createWorkerFromUrl('workerUrl').andReturn(workerId);
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('Wrong message type.', type, arguments[0].type);
|
||||
assertEquals('Incorrect name.', 'name', arguments[0].name);
|
||||
assertEquals('Incorrect user id.', 'userId', arguments[0].userId);
|
||||
});
|
||||
|
||||
database.startWorker(wp, 'workerUrl', 0);
|
||||
database.handleStarted_();
|
||||
database.handleOpenSuccessful_();
|
||||
database.useWorker_ = true;
|
||||
return database;
|
||||
}
|
||||
|
||||
function testConstructor() {
|
||||
var db = buildSyncDatabase();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testStartWorker() {
|
||||
var db = buildDatabase();
|
||||
assertTrue('Expected worker to be ready.', db.workerReady_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateTransaction() {
|
||||
var db = buildDatabase();
|
||||
var tx;
|
||||
var populateMock = function(txa) {
|
||||
tx = txa;
|
||||
var transactions = db.transactions_;
|
||||
assertEquals('missing transaction', tx, transactions[tx.id_].transaction);
|
||||
assertEquals('missing callback', callbackMock,
|
||||
transactions[tx.id_].callback);
|
||||
assertEquals('database not saved', db, tx.db_);
|
||||
assertTrue('database should be locked', db.locked_);
|
||||
};
|
||||
|
||||
callbackMock.expects().onSuccess();
|
||||
|
||||
var transactions = db.transactions_;
|
||||
db.createTransaction(populateMock, callbackMock);
|
||||
assertEquals('failed to clean up transaction', undefined,
|
||||
transactions[tx.id_]);
|
||||
assertFalse('database should not be locked', db.locked_);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testMultipleTransactions() {
|
||||
var db = buildDatabase();
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('populate');
|
||||
handler.addMockMethod('onSuccess');
|
||||
handler.addMockMethod('onFailure');
|
||||
|
||||
var trans;
|
||||
handler.expects().populate(TypeOf.isA(google.wspl.gears.Transaction)).andStub(
|
||||
function(tx) {
|
||||
trans = tx;
|
||||
tx.numActiveExecutes_ = 1;
|
||||
});
|
||||
|
||||
db.createTransaction(handler.populate, handler);
|
||||
db.createTransaction(handler.populate, handler);
|
||||
mockControl.verify();
|
||||
|
||||
handler.expects().onSuccess();
|
||||
handler.expects().populate(TypeOf.isA(google.wspl.gears.Transaction)).andStub(
|
||||
function(tx) {
|
||||
trans = tx;
|
||||
tx.numActiveExecutes_ = 1;
|
||||
});
|
||||
|
||||
db.handleCommit_(trans.id_);
|
||||
mockControl.verify();
|
||||
|
||||
handler.expects().onFailure(undefined).andStub(function() {
|
||||
db.createTransaction(handler.populate, handler);
|
||||
});
|
||||
handler.expects().populate(TypeOf.isA(google.wspl.gears.Transaction)).andStub(
|
||||
function(tx) {
|
||||
trans = tx;
|
||||
tx.numActiveExecutes_ = 1;
|
||||
});
|
||||
|
||||
db.handleRollback_(trans.id_);
|
||||
mockControl.verify();
|
||||
|
||||
handler.expects().onSuccess();
|
||||
|
||||
db.handleCommit_(trans.id_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoBegin() {
|
||||
var db = buildDatabase();
|
||||
var transactionId = 10;
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.BEGIN;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertNotUndefined('Missing transaction id.', arguments[0].transactionId);
|
||||
});
|
||||
db.doBegin(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousBegin() {
|
||||
var db = buildSyncDatabase();
|
||||
dbMock.expects().execute('BEGIN IMMEDIATE');
|
||||
db.doBegin(1);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoExecute() {
|
||||
var statements = [{sql: 's1', params: 'p1'},
|
||||
{sql: 's2', params: 'p2'},
|
||||
{sql: 's3', params: 'p3'}];
|
||||
var callbackId = 5;
|
||||
var transactionId = 10;
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.EXECUTE;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('statements do not match', statements.length,
|
||||
arguments[0].statements.length);
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
assertEquals('a statement sql does not match', statements[i].sql,
|
||||
arguments[0].statements[i].sql);
|
||||
assertEquals('a statement params does not match', statements[i].params,
|
||||
arguments[0].statements[i].params);
|
||||
}
|
||||
assertEquals('missing callback id', callbackId, arguments[0].callbackId);
|
||||
assertEquals('missing trans id', transactionId, arguments[0].transactionId);
|
||||
});
|
||||
db.doExecute(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousExecute() {
|
||||
var statements = [{sql: 's1', params: 'p1'},
|
||||
{sql: 's2', params: 'p2'},
|
||||
{sql: 's3', params: 'p3'}];
|
||||
var callbackId = 5;
|
||||
var transactionId = 10;
|
||||
var db = buildSyncDatabase();
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var stat = statements[i];
|
||||
dbMock.expects().execute(stat.sql, stat.params).andReturn('result' + i);
|
||||
}
|
||||
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('handleResult_');
|
||||
db.handleResult_ = handler.handleResult_;
|
||||
handler.expects().handleResult_(
|
||||
TypeOf.isA(Array), callbackId, transactionId).andStub(function(results) {
|
||||
var expected = ['result0', 'result1', 'result2'];
|
||||
assertArrayEquals('Wrong results.', expected, results);
|
||||
});
|
||||
|
||||
db.doExecute(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousExecute_failure() {
|
||||
var statements = [{sql: 's1', params: 'p1'},
|
||||
{sql: 's2', params: 'p2'},
|
||||
{sql: 's3', params: 'p3'}];
|
||||
var callbackId = 5;
|
||||
var transactionId = 10;
|
||||
var db = buildSyncDatabase();
|
||||
|
||||
dbMock.expects().execute('s1', 'p1').andReturn('result0');
|
||||
dbMock.expects().execute('s2', 'p2').andThrow(Error('db error'));
|
||||
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('handleFailure_');
|
||||
db.handleFailure_ = handler.handleFailure_;
|
||||
handler.expects().handleFailure_(
|
||||
TypeOf.isA(Error), callbackId, transactionId);
|
||||
|
||||
db.doExecute(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoCommit() {
|
||||
var transactionId = 10;
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.COMMIT;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('missing trans id', transactionId, arguments[0].transactionId);
|
||||
});
|
||||
db.doCommit(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousCommit() {
|
||||
var db = buildSyncDatabase();
|
||||
dbMock.expects().execute('COMMIT');
|
||||
db.doCommit(1);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoRollback() {
|
||||
var transactionId = 10;
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.ROLLBACK;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('missing trans id', transactionId, arguments[0].transactionId);
|
||||
});
|
||||
db.doRollback(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousRollback() {
|
||||
var db = buildSyncDatabase();
|
||||
dbMock.expects().execute('ROLLBACK');
|
||||
db.doRollback(1);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleCommit() {
|
||||
var db = buildDatabase();
|
||||
db.transactions_[5] = {transaction: 'tx', callback: callbackMock};
|
||||
callbackMock.expects().onSuccess();
|
||||
db.handleCommit_(5);
|
||||
assertUndefined('failed to remove transaction', db.transactions_[5]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleRollback() {
|
||||
var db = buildDatabase();
|
||||
db.transactions_[5] = {
|
||||
transaction: 'tx',
|
||||
callback: callbackMock,
|
||||
error: Error('error')
|
||||
};
|
||||
callbackMock.expects().onFailure(TypeOf.isA(Error)).andStub(function() {
|
||||
assertEquals('did not pass error', 'error', arguments[0].message);
|
||||
});
|
||||
db.handleRollback_(5);
|
||||
assertUndefined('failed to remove transaction', db.transactions_[5]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleResult() {
|
||||
var db = buildDatabase();
|
||||
transMock.expects().success(TypeOf.isA(google.wspl.gears.ResultSet),
|
||||
22).andStub(function() {
|
||||
assertEquals('result not set', 'result1', arguments[0].resultArray_);
|
||||
});
|
||||
transMock.expects().success(TypeOf.isA(google.wspl.gears.ResultSet),
|
||||
22).andStub(function() {
|
||||
assertEquals('result not set', 'result2', arguments[0].resultArray_);
|
||||
});
|
||||
db.transactions_[5] = {transaction: transMock, callback: 'cb'};
|
||||
|
||||
db.handleResult_(['result1', 'result2'], 22, 5);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleFailure() {
|
||||
var db = buildDatabase();
|
||||
transMock.expects().failure(TypeOf.isA(Error), 22).andStub(function() {
|
||||
assertEquals('error not set', 'error', arguments[0].message);
|
||||
});
|
||||
db.transactions_[5] = {transaction: transMock, callback: 'cb'};
|
||||
|
||||
db.handleFailure_(Error('error'), 22, 5);
|
||||
assertEquals('failed to save error', 'error',
|
||||
db.transactions_[5].error.message);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleStarted() {
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.OPEN;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('wrong db userId', 'userId', arguments[0].userId);
|
||||
assertEquals('wrong db name', 'name', arguments[0].name);
|
||||
});
|
||||
db.handleStarted_();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleStarted_afterTimeout() {
|
||||
var db = buildDatabase();
|
||||
db.handleTimeout_();
|
||||
|
||||
// This should do nothing.
|
||||
db.handleStarted_();
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleTimeout() {
|
||||
var db = buildDatabase();
|
||||
db.handleTimeout_();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpenSuccessful() {
|
||||
var db = buildDatabase();
|
||||
db.handleOpenSuccessful_();
|
||||
assertTrue('Worker should be ready.', db.workerReady_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpenFailed() {
|
||||
var db = buildDatabase();
|
||||
db.handleOpenFailed_('error');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
203
lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5.js
Executable file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Generic Database API.
|
||||
*
|
||||
* A small set of classes to define how we interact with databases that
|
||||
* can easily be implemented on top of HTML5.
|
||||
*/
|
||||
|
||||
google.wspl.html5 = google.wspl.html5 || {};
|
||||
|
||||
/**
|
||||
* Specification's default largest database size in HTML5 databases.
|
||||
* @type{number}
|
||||
*/
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE = 1024 * 1024 * 4;
|
||||
|
||||
/**
|
||||
* Creates an HTML5 Transaction object.
|
||||
* @see google.wspl.Transaction#Transaction
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.Transaction
|
||||
*
|
||||
* @param {SQLTransaction} html5tx The HTML5 implementation of transactions.
|
||||
*/
|
||||
google.wspl.html5.Transaction = function(html5tx) {
|
||||
this.tx_ = html5tx;
|
||||
};
|
||||
google.inherits(google.wspl.html5.Transaction, google.wspl.Transaction);
|
||||
|
||||
/**
|
||||
* Runs an array of statements in a single database transaction.
|
||||
* Invokes the onSuccess callback once for each succeesfully executed
|
||||
* statement and
|
||||
* once for the first failed statement.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements The statements to
|
||||
* execute.
|
||||
* @param {Object?} opt_callback An object containing onSuccess and onFailure
|
||||
* handlers.
|
||||
*/
|
||||
google.wspl.html5.Transaction.prototype.executeAll = function(statements,
|
||||
opt_callback) {
|
||||
if (statements.length == 0) {
|
||||
throw Error('Possibly silly attempt to execute empty statement list.');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
for (var i = 0; i < statements.length; ++i) {
|
||||
var statement = statements[i];
|
||||
google.logger('SQL: ' + statement.sql + ' PARAMS: ' + statement.params);
|
||||
this.tx_.executeSql(statement.sql, statement.params,
|
||||
function(tx, result) {
|
||||
if (opt_callback && opt_callback.onSuccess) {
|
||||
var resultSet = new google.wspl.html5.ResultSet(result);
|
||||
opt_callback.onSuccess(self, resultSet);
|
||||
}
|
||||
},
|
||||
function(tx, error) {
|
||||
if (opt_callback && opt_callback.onFailure) {
|
||||
opt_callback.onFailure(error);
|
||||
}
|
||||
// fail the whole transaction if any step fails
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.Database#Database
|
||||
* @param {string} name The name for this database.
|
||||
* @param {window} opt_window A window object for dependency injection.
|
||||
* @constructor
|
||||
* @extends google.wspl.Database
|
||||
*/
|
||||
google.wspl.html5.Database = function(name, opt_window) {
|
||||
/**
|
||||
* Sequence number for transactions.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.sequenceNum_ = 1;
|
||||
|
||||
/**
|
||||
* Map of transactionIds -> transaction start time in millis.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.inflightTransactions_ = {};
|
||||
|
||||
var win = opt_window || window;
|
||||
this.db_ = win.openDatabase(name, '',
|
||||
name, google.wspl.LARGEST_SUPPORTED_DATABASE);
|
||||
if (this.db_ == null) {
|
||||
throw Error('The returned database was null.');
|
||||
}
|
||||
};
|
||||
google.inherits(google.wspl.html5.Database, google.wspl.Database);
|
||||
|
||||
/**
|
||||
* @see google.wspl.Database#createTransaction
|
||||
*/
|
||||
google.wspl.html5.Database.prototype.createTransaction = function(populate,
|
||||
opt_callback) {
|
||||
var transactionCallback = opt_callback || {
|
||||
onSuccess: function() {},
|
||||
onFailure: function() {}
|
||||
};
|
||||
|
||||
var transactionId = this.sequenceNum_++;
|
||||
var inflightTransactions = this.inflightTransactions_;
|
||||
inflightTransactions[transactionId] = this.getCurrentTime();
|
||||
this.db_.transaction(
|
||||
function(tx) {
|
||||
// Delete the transaction before the executing it because our
|
||||
// definition of an 'in-flight' transaction is the time between
|
||||
// when the request was made and when the database starts to
|
||||
// execute the transaction.
|
||||
delete inflightTransactions[transactionId];
|
||||
populate(new google.wspl.html5.Transaction(tx));
|
||||
},
|
||||
function(error) {transactionCallback.onFailure(error);},
|
||||
function() {transactionCallback.onSuccess();});
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if there is an in-flight database transaction that's older than
|
||||
* the given time period.
|
||||
* @param {number} olderThanMillis The time period.
|
||||
* @return {boolean} True if the database has an in-flight transaction older
|
||||
* than the given time period, false otherwise.
|
||||
*/
|
||||
google.wspl.html5.Database.prototype.hasInflightTransactions =
|
||||
function(olderThanMillis) {
|
||||
for (var transactionId in this.inflightTransactions_) {
|
||||
var startTime = this.inflightTransactions_[transactionId];
|
||||
if (this.getCurrentTime() - startTime > olderThanMillis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current time.
|
||||
* @return {number} The current time in millis.
|
||||
*/
|
||||
google.wspl.html5.Database.prototype.getCurrentTime = function() {
|
||||
// The iPhone does not support Date.now()
|
||||
var d = new Date();
|
||||
return d.getTime();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an HTML5 ResultSet object.
|
||||
* @see google.wspl.ResultSet#ResultSet
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.ResultSet
|
||||
*
|
||||
* @param {Object} html5_result The HTML5 implementation of result set.
|
||||
*/
|
||||
google.wspl.html5.ResultSet = function(html5_result) {
|
||||
this.result_ = html5_result;
|
||||
this.index_ = 0;
|
||||
};
|
||||
google.inherits(google.wspl.html5.ResultSet, google.wspl.ResultSet);
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#isValidRow
|
||||
*/
|
||||
google.wspl.html5.ResultSet.prototype.isValidRow = function() {
|
||||
return this.index_ >= 0 && this.index_ < this.result_.rows.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#next
|
||||
*/
|
||||
google.wspl.html5.ResultSet.prototype.next = function() {
|
||||
this.index_ ++;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#getRow
|
||||
*/
|
||||
google.wspl.html5.ResultSet.prototype.getRow = function() {
|
||||
return this.result_.rows.item(this.index_);
|
||||
};
|
468
lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5_test.html
Executable file
|
@ -0,0 +1,468 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Database wrapper tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="dbwrapper_html5.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var html5Tx;
|
||||
var callbackMock;
|
||||
var html5Window;
|
||||
var html5Db;
|
||||
var populateMock;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
html5Tx = mockControl.createMock({executeSql: function(){}});
|
||||
callbackMock = mockControl.createMock({onSuccess: function(){},
|
||||
onFailure: function(){}});
|
||||
html5Window = mockControl.createMock({openDatabase: function(){}});
|
||||
html5Db = mockControl.createMock({transaction: function(){}});
|
||||
populateMock = mockControl.createMock({populate: function(){}});
|
||||
|
||||
dbTx = mockControl.createMock({execute: function(){}, executeAll:
|
||||
function(){}});
|
||||
dbRows = mockControl.createMock({item: function(){}});
|
||||
}
|
||||
|
||||
function testConstructTransaction() {
|
||||
var trans = new google.wspl.html5.Transaction('foo');
|
||||
assertEquals('transaction instance not set correctly', 'foo', trans.tx_);
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll() {
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
try {
|
||||
trans.executeAll([], callbackMock);
|
||||
fail('Should never get here');
|
||||
} catch(e) {
|
||||
assertEquals(
|
||||
'did not exception fault on empty statement list',
|
||||
'Error: Possibly silly attempt to execute empty statement list.',
|
||||
e.toString());
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
/*
|
||||
The sequence of digits at the end of the test names indicate as boolean
|
||||
variables the success or failure of each statement.
|
||||
*/
|
||||
function testTransactionExecuteAll_1() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback = null;
|
||||
var failureCallback = null;
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback = arguments[2];
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset', arguments[1].result_)});
|
||||
|
||||
trans.executeAll([stat1], callbackMock);
|
||||
successCallback(html5Tx, 'resultset');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_11() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var failureCallback = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset1', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset2', arguments[1].result_)});
|
||||
|
||||
trans.executeAll([stat1, stat2], callbackMock);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_111() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var successCallback3 = null;
|
||||
var failureCallback = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat3', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback3 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 2, params[0]);
|
||||
assertEquals('incorrect params to sql', 3, params[1]);
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset1', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset2', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset3', arguments[1].result_)});
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3], callbackMock);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
successCallback3(html5Tx, 'resultset3');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_noCallback_111() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var successCallback3 = null;
|
||||
var failureCallback = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(
|
||||
function() { successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat3', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(
|
||||
function() { successCallback3 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 2, params[0]);
|
||||
assertEquals('incorrect params to sql', 3, params[1]);
|
||||
});
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3]);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
successCallback3(html5Tx, 'resultset3');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_110() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var successCallback3 = null;
|
||||
var failureCallback3 = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat3', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback3 = arguments[2];
|
||||
failureCallback3 = arguments[3];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 2, params[0]);
|
||||
assertEquals('incorrect params to sql', 3, params[1]);
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset1', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset2', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onFailure('error3');
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3], callbackMock);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
assertTrue('failure case callback not terminating transaction',
|
||||
failureCallback3(html5Tx, 'error3'));
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecute_nocallback() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback = null;
|
||||
var failureCallback = null;
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback = arguments[2];
|
||||
});
|
||||
|
||||
trans.execute(stat1);
|
||||
successCallback(html5Tx, 'resultset');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function buildDatabase() {
|
||||
html5Window.expects().openDatabase('name', '', 'name',
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE).
|
||||
andReturn(html5Db);
|
||||
return new google.wspl.html5.Database('name', html5Window);
|
||||
}
|
||||
|
||||
function testConstructDatabase() {
|
||||
var db = buildDatabase();
|
||||
mockControl.verify();
|
||||
assertEquals('did not set db_ correctly', db.db_, html5Db);
|
||||
}
|
||||
|
||||
function testConstructDatabase_null() {
|
||||
html5Window.expects().openDatabase('name', '', 'name',
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE).
|
||||
andReturn(null);
|
||||
try {
|
||||
var db = google.wspl.html5.Database('name', html5Window);
|
||||
fail('Should never get here');
|
||||
} catch (e) {
|
||||
if (e.isJsUnitException) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testConstructDatabase_undefined() {
|
||||
html5Window.expects().openDatabase('name', '', 'name',
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE).
|
||||
andReturn(undefined);
|
||||
try {
|
||||
var db = google.wspl.html5.Database('name', html5Window);
|
||||
fail('Should never get here');
|
||||
} catch (e) {
|
||||
if (e.isJsUnitException) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function createTransactionCore() {
|
||||
var db = buildDatabase();
|
||||
var fun1 = null;
|
||||
var failure = null;
|
||||
var success = null;
|
||||
html5Db.expects().transaction(TypeOf.isA(Function), TypeOf.isA(Function),
|
||||
TypeOf.isA(Function)).andStub(function() {
|
||||
fun1 = arguments[0];
|
||||
failure = arguments[1];
|
||||
success = arguments[2];
|
||||
});
|
||||
|
||||
var tx = null;
|
||||
var populateMock = function(txa) {
|
||||
tx = txa;
|
||||
};
|
||||
|
||||
db.createTransaction(populateMock, callbackMock);
|
||||
fun1('transactionTest');
|
||||
|
||||
assertEquals('transaction not saved', 'transactionTest', tx.tx_);
|
||||
assertEquals('did not set db_ correctly', db.db_, html5Db);
|
||||
|
||||
return {failure: failure, success: success};
|
||||
}
|
||||
|
||||
function testCreateTransaction_success() {
|
||||
cbs = createTransactionCore();
|
||||
callbackMock.expects().onSuccess();
|
||||
cbs.success('success');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateTransaction_failure() {
|
||||
cbs = createTransactionCore();
|
||||
callbackMock.expects().onFailure('error');
|
||||
cbs.failure('error');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateTransaction_onlyPopulate() {
|
||||
cbs = createTransactionCore();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDatabaseExecute_nocallback() {
|
||||
var db = buildDatabase();
|
||||
|
||||
dbTx.expects().execute('statementText', null);
|
||||
db.execute('statementText');
|
||||
}
|
||||
|
||||
function testDatabaseExecute_nocallback() {
|
||||
var db = buildDatabase();
|
||||
|
||||
dbTx.expects().execute('statementText', null);
|
||||
db.execute('statementText');
|
||||
}
|
||||
|
||||
function testDatabaseExecuteAll() {
|
||||
var db = buildDatabase();
|
||||
dbTx.expects().executeAll(TypeOf.isA(Array));
|
||||
}
|
||||
|
||||
function testResultSetNext() {
|
||||
var res = {rows: {length: 4}};
|
||||
var result = new google.wspl.html5.ResultSet(res);
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
assertTrue('expected valid row', result.isValidRow());
|
||||
result.next();
|
||||
}
|
||||
assertFalse('expected invalid row', result.isValidRow());
|
||||
}
|
||||
|
||||
function testResultSetIsValidRow() {
|
||||
var res = {rows: {length: 0}};
|
||||
var result = new google.wspl.html5.ResultSet(res);
|
||||
|
||||
assertFalse('expected invalid row', result.isValidRow());
|
||||
res.rows.length = 1;
|
||||
assertTrue('expected valid row', result.isValidRow());
|
||||
result.next();
|
||||
assertFalse('expected invalid row', result.isValidRow());
|
||||
}
|
||||
|
||||
function testResultSetGetRow() {
|
||||
var res = {rows: {
|
||||
length: 3,
|
||||
item: function(index) {
|
||||
assertEquals('expected index of 1', 1, index);
|
||||
return {h0: 'value0', h1: 'value1', h2: 'value2'};
|
||||
}
|
||||
}};
|
||||
|
||||
var result = new google.wspl.html5.ResultSet(res);
|
||||
result.next();
|
||||
var row = result.getRow();
|
||||
assertEquals('first field is not valid', 'value0', row.h0);
|
||||
assertEquals('first field is not valid', 'value1', row.h1);
|
||||
assertEquals('first field is not valid', 'value2', row.h2);
|
||||
}
|
||||
|
||||
function testHasInflightTransactions() {
|
||||
var fakeTime = 1000;
|
||||
var db = buildDatabase();
|
||||
var dummyPopulate = function() {};
|
||||
db.getCurrentTime = function() {return fakeTime;};
|
||||
|
||||
// Create a transaction, make sure it's kept track of.
|
||||
var transId = db.sequenceNum_;
|
||||
db.createTransaction(dummyPopulate);
|
||||
assertEquals('There should be an inflight request.',
|
||||
fakeTime, db.inflightTransactions_[transId]);
|
||||
|
||||
// Haven't advanced time, so hasInflightTransactions should return false.
|
||||
assertFalse('Expected hasInflightTransactions=false.',
|
||||
db.hasInflightTransactions(100));
|
||||
|
||||
// Advance time, now hasInflightTransactions should return true.
|
||||
fakeTime += 10000;
|
||||
var faqs = db.hasInflightTransactions(100);
|
||||
assertTrue('Expected hasInflightTransactions=true.',
|
||||
db.hasInflightTransactions(100));
|
||||
|
||||
// Now now have the transaction completed, hasInflightTransactions
|
||||
// should return false.
|
||||
delete db.inflightTransactions_[transId];
|
||||
assertFalse('Expected hasInflightTransactions=false.',
|
||||
db.hasInflightTransactions(100));
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
202
lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi.js
Executable file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Generic Database API.
|
||||
*
|
||||
* A small set of classes to define how we interact with databases that can
|
||||
* easily be implemented on top of HTML5 and Gears. The classes in this file
|
||||
* should be extended to provide the missing method implementations and more
|
||||
* sophisticated constructors where applicable.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a Statement object. A Statement is an SQL statement paired
|
||||
* with the parameters needed to execute it.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!string} sql The SQL statement.
|
||||
* @param {Array.<Object>?} opt_params The parameters for the SQL statement.
|
||||
*/
|
||||
google.wspl.Statement = function(sql, opt_params) {
|
||||
|
||||
/**
|
||||
* The SQL statement with '?' in place of parameters.
|
||||
* @type {string}
|
||||
*/
|
||||
this.sql = sql;
|
||||
|
||||
/**
|
||||
* The parameters to use with the SQL statement.
|
||||
* @type {!Array}
|
||||
*/
|
||||
this.params = opt_params || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new statement object from the given statement with the parameters
|
||||
* set as specified.
|
||||
* @param {Array.<Object>} params The array of values for ? placeholders.
|
||||
* @return {!google.wspl.Statement} The created Statement.
|
||||
*/
|
||||
google.wspl.Statement.prototype.createStatement = function(params) {
|
||||
return new google.wspl.Statement(this.sql, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a Transaction object. Transaction objects
|
||||
* group together a series of statements into a single atomic
|
||||
* action on the database.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.Transaction = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a statement and an optional callback object and
|
||||
* runs the statement on the database. The callback can be used to
|
||||
* add more statements to the same transaction, or execute can be
|
||||
* called repeatedly and the transactions will later execute in the
|
||||
* order provided.
|
||||
*
|
||||
* @param {google.wspl.Statement} statement The statement to execute.
|
||||
* @param {Object} opt_callback An object containing onSuccess and onFailure
|
||||
* handlers.
|
||||
*/
|
||||
google.wspl.Transaction.prototype.execute = function(statement,
|
||||
opt_callback) {
|
||||
this.executeAll([statement], opt_callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs an array of statements in a single database transaction.
|
||||
* Invokes the onSuccess callback once for each successfully executed
|
||||
* statement and once for the first failed statement. The callback can be
|
||||
* used to add more statements to the same transaction, or executeAll can
|
||||
* be called repeatedly and each block of statements given will execute
|
||||
* in the same order as the sequence of calls to executeAll.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements The statements to
|
||||
* execute.
|
||||
* @param {Object?} opt_callback An object containing onSuccess and onFailure
|
||||
* handlers.
|
||||
*/
|
||||
google.wspl.Transaction.prototype.executeAll = function(statements,
|
||||
opt_callback) {
|
||||
throw Error('executeAll not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a Database object. Database objects are handles that allow
|
||||
* access to a database (and create the corresponding database if it doesn't
|
||||
* already exist). To open the database, pass the name of the needed
|
||||
* database to the constructor, and then execute transactions on it using
|
||||
* the execute method.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.Database = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a transaction object that can execute a series of SQL statements
|
||||
* atomically.
|
||||
*
|
||||
* @param {Function} populate A callback to run execute calls on the
|
||||
* transaction.
|
||||
* @param {Object?} opt_callback An optional success/failure callback that is
|
||||
* called when the entire transaction is finished executing.
|
||||
*/
|
||||
google.wspl.Database.prototype.createTransaction = function(populate,
|
||||
opt_callback) {
|
||||
throw Error('createTransaction not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the database, invoking the optional
|
||||
* callback after statement execution and the optional transactionCallback upon
|
||||
* completion of the transaction.
|
||||
*
|
||||
* @param {google.wspl.Statement} statement the statement to execute
|
||||
* @param {Object?} opt_callback object that defines onSuccess and onFailure
|
||||
* @param {Object?} opt_transactionCallback object that defines onSuccess and
|
||||
* onFailure
|
||||
*/
|
||||
google.wspl.Database.prototype.execute = function(statement,
|
||||
opt_callback,
|
||||
opt_transactionCallback) {
|
||||
this.createTransaction(function(tx) {
|
||||
tx.execute(statement, opt_callback);
|
||||
}, opt_transactionCallback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the database, invoking the optional
|
||||
* callback for each statement in the transaction. In the case of a statement
|
||||
* failure, only the first failed statement will be reported and the transaction
|
||||
* will be rolled back. This method invokes the optional transactionCallback
|
||||
* upon completion of the transaction.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements the statements to execute
|
||||
* @param {Object?} opt_callback object that defines onSuccess and onFailure
|
||||
* @param {Object?} opt_transactionCallback object that defines onSuccess and
|
||||
* onFailure
|
||||
*/
|
||||
google.wspl.Database.prototype.executeAll = function(statements,
|
||||
opt_callback,
|
||||
opt_transactionCallback) {
|
||||
this.createTransaction(function(tx) {
|
||||
tx.executeAll(statements, opt_callback);
|
||||
}, opt_transactionCallback);
|
||||
};
|
||||
|
||||
/**
|
||||
* An immutable set of results that is returned from a single successful query
|
||||
* on the database.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.ResultSet = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if next() will advance to a valid row in the result set.
|
||||
*
|
||||
* @return {boolean} if next() will advance to a valid row in the result set
|
||||
*/
|
||||
google.wspl.ResultSet.prototype.isValidRow = function() {
|
||||
throw Error('isValidRow not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Advances to the next row in the results.
|
||||
*/
|
||||
google.wspl.ResultSet.prototype.next = function() {
|
||||
throw Error('next not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current row as an object with a property for each field returned
|
||||
* by the database. The property will have the name of the column and the value
|
||||
* of the cell.
|
||||
*
|
||||
* @return {Object} The current row
|
||||
*/
|
||||
google.wspl.ResultSet.prototype.getRow = function() {
|
||||
throw Error('getRow not implemented');
|
||||
};
|
51
lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi_test.html
Executable file
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Database wrapper api</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
function subStatementTest(stm) {
|
||||
assertEquals('statement not set correctly', 'bar', stm.sql);
|
||||
assertEquals('first param not set correctly', 1, stm.params[0]);
|
||||
assertEquals('second param not set correctly', 2, stm.params[1]);
|
||||
}
|
||||
|
||||
function testConstructStatement() {
|
||||
var stm = new google.wspl.Statement('foo');
|
||||
assertEquals('statement not set correctly', 'foo', stm.sql);
|
||||
|
||||
var stm = new google.wspl.Statement('bar', [1,2]);
|
||||
subStatementTest(stm);
|
||||
}
|
||||
|
||||
function testConstructStatementFromTemplate() {
|
||||
var temp = new google.wspl.Statement('bar');
|
||||
var stm = temp.createStatement([1,2]);
|
||||
subStatementTest(stm);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
71
lib/middleman/templates/mobile/source/tools/wspl/gears_resultset.js
Executable file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A Gears implementation of dbwrapperapi ResultSet.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a Gears ResultSet object.
|
||||
* @see google.wspl.ResultSet#ResultSet
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.ResultSet
|
||||
* @param {!Array.<Object>} resultArray An array of hash objects where the
|
||||
* column names in the query are used as members of the objects.
|
||||
*/
|
||||
google.wspl.gears.ResultSet = function(resultArray) {
|
||||
google.wspl.ResultSet.call(this);
|
||||
|
||||
/**
|
||||
* The result set as an array of hash objects.
|
||||
* @type {!Array.<Object>}
|
||||
* @private
|
||||
*/
|
||||
this.resultArray_ = resultArray;
|
||||
};
|
||||
google.inherits(google.wspl.gears.ResultSet, google.wspl.ResultSet);
|
||||
|
||||
/**
|
||||
* The current record in the result set.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.current_ = 0;
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#isValidRow
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.isValidRow = function() {
|
||||
return this.current_ < this.resultArray_.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#next
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.next = function() {
|
||||
this.current_++;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#getRow
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.getRow = function() {
|
||||
return this.resultArray_[this.current_];
|
||||
};
|
86
lib/middleman/templates/mobile/source/tools/wspl/gears_resultset_test.html
Executable file
|
@ -0,0 +1,86 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears resultset tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="gears_resultset.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var resultArray;
|
||||
|
||||
function setUp() {
|
||||
var obj1 = {
|
||||
field1: 'a',
|
||||
field2: 2,
|
||||
field3: 'c'
|
||||
};
|
||||
|
||||
var obj2 = {
|
||||
field1: 'd',
|
||||
field2: 4,
|
||||
field3: 'f'
|
||||
};
|
||||
|
||||
var obj3 = {
|
||||
field1: 'g',
|
||||
field2: 6,
|
||||
field3: 'i'
|
||||
};
|
||||
|
||||
resultArray = [obj1, obj2, obj3];
|
||||
}
|
||||
|
||||
function testResultSetNext() {
|
||||
var result = new google.wspl.gears.ResultSet(resultArray);
|
||||
assertEquals('incorrect value for current', 0, result.current_);
|
||||
result.next();
|
||||
assertEquals('incorrect value for current', 1, result.current_);
|
||||
result.next();
|
||||
assertEquals('incorrect value for current', 2, result.current_);
|
||||
}
|
||||
|
||||
function testResultSetIsValidRow() {
|
||||
var result = new google.wspl.gears.ResultSet(resultArray);
|
||||
assertTrue('incorrect return from isValidRow', result.isValidRow());
|
||||
result.next();
|
||||
assertTrue('incorrect return from isValidRow', result.isValidRow());
|
||||
result.next();
|
||||
assertTrue('incorrect return from isValidRow', result.isValidRow());
|
||||
result.next();
|
||||
assertFalse('incorrect return from isValidRow', result.isValidRow());
|
||||
}
|
||||
|
||||
function testResultSetGetRow() {
|
||||
var result = new google.wspl.gears.ResultSet(resultArray);
|
||||
result.next();
|
||||
|
||||
var row = result.getRow();
|
||||
assertEquals('first field is not valid', 'd', row.field1);
|
||||
assertEquals('first field is not valid', 4, row.field2);
|
||||
assertEquals('first field is not valid', 'f', row.field3);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
196
lib/middleman/templates/mobile/source/tools/wspl/gears_transaction.js
Executable file
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A Gears implementation of dbwrapperapi Transaction.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a Gears Transaction object.
|
||||
* @see google.wspl.ResultSet#ResultSet
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.Transaction
|
||||
*
|
||||
* @param {number} id The unique id for this transaction
|
||||
* @param {google.wspl.gears.Database} db The Gears implementation of the
|
||||
* dbwrapperapi database
|
||||
*/
|
||||
google.wspl.gears.Transaction = function(id, db) {
|
||||
google.wspl.Transaction.call(this);
|
||||
|
||||
/**
|
||||
* The unique id for this transaction.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.id_ = id;
|
||||
|
||||
/**
|
||||
* The Gears implementation of the dbwrapperapi database.
|
||||
* @type {google.wspl.gears.Database}
|
||||
* @private
|
||||
*/
|
||||
this.db_ = db;
|
||||
|
||||
/**
|
||||
* A map of statements, callback, and current statement.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.activeExecutes_ = {};
|
||||
};
|
||||
google.inherits(google.wspl.gears.Transaction, google.wspl.Transaction);
|
||||
|
||||
/**
|
||||
* The number of active executes.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.numActiveExecutes_ = 0;
|
||||
|
||||
/**
|
||||
* The id for the next call to execute. Incremented after use.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.nextCallbackId_ = 1;
|
||||
|
||||
/**
|
||||
* Whether the transaction should be rolled back or not. This property is set
|
||||
* to true when a statement fails.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.needsRollback_ = false;
|
||||
|
||||
/**
|
||||
* Begins a new transaction with the Gears database. Commits the transaction if
|
||||
* all calls to executeAll for this transaction have finished receiving
|
||||
* callbacks. Rolls the transaction back if a statement failed.
|
||||
*
|
||||
* @see google.wspl.Transaction#executeAll
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.executeAll = function(statements,
|
||||
opt_callback) {
|
||||
if (statements.length == 0) {
|
||||
throw Error('Possibly silly attempt to execute empty statement list.');
|
||||
}
|
||||
if (this.numActiveExecutes_ == 0) {
|
||||
this.db_.doBegin(this.id_);
|
||||
}
|
||||
|
||||
this.numActiveExecutes_++;
|
||||
var callbackId = this.nextCallbackId_++;
|
||||
|
||||
var callback = opt_callback || {
|
||||
onSuccess : function() {},
|
||||
onFailure : function() {}
|
||||
};
|
||||
|
||||
this.activeExecutes_[callbackId] = {
|
||||
statements: statements,
|
||||
currentStatement: 0,
|
||||
callback: callback
|
||||
};
|
||||
|
||||
this.db_.doExecute(statements, callbackId, this.id_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes onSuccess on the specified callback.
|
||||
*
|
||||
* @param {google.wspl.ResultSet} result The result of a successful statement
|
||||
* @param {number} callbackId The callback to invoke
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.success = function(result,
|
||||
callbackId) {
|
||||
if (!this.needsRollback_) {
|
||||
var activeExecute = this.activeExecutes_[callbackId];
|
||||
activeExecute.callback.onSuccess(this, result);
|
||||
}
|
||||
this.endStatement_(callbackId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes onFailure on the specified callback.
|
||||
*
|
||||
* @param {Error} error The error of an unsuccessful statement
|
||||
* @param {number} callbackId The callback to invoke
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.failure = function(error,
|
||||
callbackId) {
|
||||
if (!this.needsRollback_) {
|
||||
this.needsRollback_ = true;
|
||||
var activeExecute = this.activeExecutes_[callbackId];
|
||||
activeExecute.callback.onFailure(error);
|
||||
}
|
||||
this.endStatement_(callbackId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles clean up for the end of a single execution.
|
||||
*
|
||||
* @param {number} callbackId The callback to clean up.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.endStatement_ = function(callbackId) {
|
||||
var activeExecute = this.activeExecutes_[callbackId];
|
||||
var statements = activeExecute.statements;
|
||||
var currentStatement = ++activeExecute.currentStatement;
|
||||
|
||||
if (currentStatement == statements.length) {
|
||||
this.endExecute_(callbackId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles clean up for the end of a call to executeAll. Performs a commit or
|
||||
* rollback if this is the last active execute to clean up.
|
||||
*
|
||||
* @param {number} callbackId The callback to clean up
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.endExecute_ = function(callbackId) {
|
||||
delete this.activeExecutes_[callbackId];
|
||||
this.numActiveExecutes_--;
|
||||
if (!this.isExecuting()) {
|
||||
this.endTransaction_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instructs the worker to commit the transaction or roll it back if a failure
|
||||
* occurred and a rollback is required.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.endTransaction_ = function() {
|
||||
if (this.needsRollback_) {
|
||||
this.db_.doRollback(this.id_);
|
||||
} else {
|
||||
this.db_.doCommit(this.id_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the transaction has statements executing, false
|
||||
* otherwise.
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.isExecuting = function() {
|
||||
return this.numActiveExecutes_ > 0;
|
||||
};
|
221
lib/middleman/templates/mobile/source/tools/wspl/gears_transaction_test.html
Executable file
|
@ -0,0 +1,221 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears transaction tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="gears_resultset.js"></script>
|
||||
<script type="text/javascript" src="gears_transaction.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var callbackMock;
|
||||
var dbMock;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
callbackMock = mockControl.createMock({
|
||||
onSuccess: function(){},
|
||||
onFailure: function(){}
|
||||
});
|
||||
dbMock = mockControl.createMock({
|
||||
doBegin: function(){},
|
||||
doCommit: function(){},
|
||||
doRollback: function(){},
|
||||
doExecute: function(){}
|
||||
});
|
||||
}
|
||||
|
||||
function testConstructTransaction() {
|
||||
var trans = new google.wspl.gears.Transaction(10, 'foo');
|
||||
assertEquals('foo', trans.db_);
|
||||
assertEquals(10, trans.id_);
|
||||
}
|
||||
|
||||
function testExecuteAll_noStatements() {
|
||||
var trans = new google.wspl.gears.Transaction(27, dbMock);
|
||||
|
||||
try {
|
||||
trans.executeAll([], callbackMock);
|
||||
fail('Should never get here');
|
||||
} catch(e) {
|
||||
if (e.isJsUnitException) {
|
||||
throw e;
|
||||
}
|
||||
assertEquals('did not exception fault on empty statement list',
|
||||
'Error: Possibly silly attempt to execute empty statement list.',
|
||||
e.toString());
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testExecuteAll_noCallback() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var transactionId = 27;
|
||||
var callbackId = 1;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2, stat3], callbackId, transactionId);
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3]);
|
||||
trans.success('resultset1', callbackId);
|
||||
trans.success('resultset2', callbackId);
|
||||
trans.success('resultset3', callbackId);
|
||||
}
|
||||
|
||||
function testExecuteAll_success() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var transactionId = 27;
|
||||
var callbackId = 1;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2, stat3], callbackId, transactionId);
|
||||
callbackMock.expect().onSuccess(trans, 'resultset1');
|
||||
callbackMock.expect().onSuccess(trans, 'resultset2');
|
||||
callbackMock.expect().onSuccess(trans, 'resultset3');
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3], callbackMock);
|
||||
trans.success('resultset1', callbackId);
|
||||
trans.success('resultset2', callbackId);
|
||||
trans.success('resultset3', callbackId);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testExecuteAll_failure() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var stat4 = new google.wspl.Statement('stat4', [4, 5]);
|
||||
var transactionId = 27;
|
||||
var callbackId = 1;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2, stat3, stat4], callbackId,
|
||||
transactionId);
|
||||
|
||||
callbackMock.expect().onSuccess(trans, 'resultset1');
|
||||
callbackMock.expect().onFailure('sql error');
|
||||
dbMock.expects().doRollback(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3, stat4], callbackMock);
|
||||
trans.success('resultset1', callbackId);
|
||||
trans.failure('sql error', callbackId);
|
||||
|
||||
// These should do nothing.
|
||||
trans.success('resultset3', callbackId);
|
||||
trans.failure('sql error', callbackId);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testExecuteAll_multipleCalls() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var stat4 = new google.wspl.Statement('stat4', [4, 5]);
|
||||
var transactionId = 27;
|
||||
var callbackId_1 = 1;
|
||||
var callbackId_2 = 2;
|
||||
var callbackId_3 = 3;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2], callbackId_1, transactionId);
|
||||
dbMock.expects().doExecute([stat3], callbackId_2, transactionId);
|
||||
callbackMock.expects().onSuccess(trans, 'resultset1').andStub(function() {
|
||||
trans.executeAll([stat4], callbackMock);
|
||||
});
|
||||
dbMock.expects().doExecute([stat4], callbackId_3, transactionId);
|
||||
callbackMock.expects().onSuccess(trans, 'resultset2').andStub(function() {});
|
||||
callbackMock.expects().onSuccess(trans, 'resultset3');
|
||||
callbackMock.expects().onSuccess(trans, 'resultset4');
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2], callbackMock);
|
||||
trans.executeAll([stat3], callbackMock);
|
||||
trans.success('resultset1', callbackId_1);
|
||||
trans.success('resultset2', callbackId_1);
|
||||
trans.success('resultset3', callbackId_2);
|
||||
trans.success('resultset4', callbackId_3);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnSuccess() {
|
||||
var transactionId = 27;
|
||||
var callbackId = 3;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
callbackMock.expects().onSuccess(trans, 'result2');
|
||||
callbackMock.expects().onSuccess(trans, 'result3');
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.numActiveExecutes_ = 1;
|
||||
trans.activeExecutes_[callbackId] = {
|
||||
statements: ['s1', 's2', 's3'],
|
||||
currentStatement: 1,
|
||||
callback: callbackMock
|
||||
};
|
||||
trans.success('result2', callbackId);
|
||||
trans.success('result3', callbackId);
|
||||
assertUndefined('activeExecute not removed',
|
||||
trans.activeExecutes_[callbackId]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnFailure() {
|
||||
var transactionId = 27;
|
||||
var callbackId = 5;
|
||||
callbackMock.expects().onFailure(TypeOf.isA(Error)).andStub(function() {
|
||||
assertEquals('error not returned', 'error', arguments[0].message);
|
||||
});
|
||||
dbMock.expects().doRollback(transactionId);
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
trans.numActiveExecutes_ = 1;
|
||||
trans.activeExecutes_[callbackId] = {
|
||||
statements: ['s1', 's2', 's3'],
|
||||
currentStatement: 1,
|
||||
callback: callbackMock
|
||||
};
|
||||
|
||||
trans.failure(Error('error'), callbackId);
|
||||
trans.success('result3', callbackId);
|
||||
assertUndefined('activeExecute not removed',
|
||||
trans.activeExecutes_[callbackId]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
94
lib/middleman/templates/mobile/source/tools/wspl/gearsutils.js
Executable file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Some simple utilities for supporting Gears usage.
|
||||
*/
|
||||
google.wspl.GearsUtils = google.wspl.GearsUtils || {};
|
||||
|
||||
/**
|
||||
* Returns an array of hash objects, one per row in the result set,
|
||||
* where the column names in the query are used as the members of
|
||||
* the object.
|
||||
*
|
||||
* @param {GearsResultSet} rs the result set returned by execute.
|
||||
* @return {Array.<Object>} An array containing hashes. Returns an empty
|
||||
* array if there are no matching rows.
|
||||
*/
|
||||
google.wspl.GearsUtils.resultSetToObjectArray = function(rs) {
|
||||
var rv = [];
|
||||
if (rs) {
|
||||
var cols = rs['fieldCount']();
|
||||
var colNames = [];
|
||||
for (var i = 0; i < cols; i++) {
|
||||
colNames.push(rs['fieldName'](i));
|
||||
}
|
||||
|
||||
while (rs['isValidRow']()) {
|
||||
var h = {};
|
||||
for (var i = 0; i < cols; i++) {
|
||||
h[colNames[i]] = rs['field'](i);
|
||||
}
|
||||
rv.push(h);
|
||||
rs['next']();
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum file name length.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_ = 64;
|
||||
|
||||
/**
|
||||
* Ensures that the given dbName is safe to use as a Gears database name.
|
||||
* @type {!string} dbName
|
||||
* @return {!string} The sanitized name.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.GearsUtils.makeSafeFileName_ = function(dbName) {
|
||||
var sanitizedFileName = dbName.replace(/[^a-zA-Z0-9\.\-@_]/g, '');
|
||||
if (sanitizedFileName.length <=
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_) {
|
||||
return sanitizedFileName;
|
||||
} else {
|
||||
return sanitizedFileName.substring(0,
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a Gears Database using the provided userid and name.
|
||||
* @param {string} userId The user to which the database belongs.
|
||||
* @param {string} name The database's name.
|
||||
* @param {GearsDatabase} db The Gears database to open.
|
||||
* @param {function(string)} opt_logger A logger function for writing
|
||||
* messages.
|
||||
* @return {GearsDatabase} The open GearsDatabase object.
|
||||
*/
|
||||
google.wspl.GearsUtils.openDatabase = function(userId, name, db,
|
||||
opt_logger) {
|
||||
var dbId = userId + '-' + name;
|
||||
var safeDbId = google.wspl.GearsUtils.makeSafeFileName_(dbId);
|
||||
if (opt_logger && dbId != safeDbId) {
|
||||
opt_logger('database name ' + dbId + '->' + safeDbId);
|
||||
}
|
||||
db.open(safeDbId);
|
||||
return db;
|
||||
};
|
84
lib/middleman/templates/mobile/source/tools/wspl/gearsutils_test.html
Executable file
|
@ -0,0 +1,84 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears Utilities Tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="gearsutils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var mockGearsDb;
|
||||
var mockLogger;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
mockGearsDb = mockControl.createMock({
|
||||
execute: function(){},
|
||||
open: function(){},
|
||||
close: function(){},
|
||||
remove: function(){}
|
||||
});
|
||||
|
||||
mockLogger = mockControl.createMock({
|
||||
info: function(){}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function testMakeSafeFileName() {
|
||||
assertEquals('simple name wrongly modified', 'ABCDEFGHIJKLMNOPQRSTUVXYZ',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('ABCDEFGHIJKLMNOPQRSTUVXYZ'));
|
||||
assertEquals('simple name wrongly modified', 'abcdefghijklmnopqrstuvxyz',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('abcdefghijklmnopqrstuvxyz'));
|
||||
assertEquals('simple name wrongly modified', '.-@_',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('.-@_'));
|
||||
|
||||
var longName = 'abcdefghijklmnopqrstuvxyz' + 'abcdefghijklmnopqrstuvxyz' +
|
||||
'abcdefghijklmnopqrstuvxyz';
|
||||
assertEquals('long name not truncated',
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_,
|
||||
google.wspl.GearsUtils.makeSafeFileName_(longName).length);
|
||||
|
||||
assertEquals('forbidden character not stripped', 'abc',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('a#b$()//c'));
|
||||
}
|
||||
|
||||
function testCreateDatabase_success() {
|
||||
mockGearsDb.expects().open('hello-there');
|
||||
google.wspl.GearsUtils.openDatabase('hello', 'there', mockGearsDb,
|
||||
mockLogger);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateDatabase_invalidname() {
|
||||
mockLogger.expects().info('database name foo/bar-ranch->foobar-ranch');
|
||||
mockGearsDb.expects().open('foobar-ranch');
|
||||
assertEquals('bad return value', mockGearsDb,
|
||||
google.wspl.GearsUtils.openDatabase('foo/bar', 'ranch',
|
||||
mockGearsDb, mockLogger.info));
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
72
lib/middleman/templates/mobile/source/tools/wspl/global_functions.js
Executable file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Global function implementations used for running the
|
||||
* web storage portability layer code outside of the Google internal
|
||||
* development environment.
|
||||
*
|
||||
* Include this file only once.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Namespace object.
|
||||
* @type {Object}
|
||||
*/
|
||||
var google = google || {};
|
||||
google.wspl = google.wspl || {};
|
||||
google.wspl.gears = google.wspl.gears || {};
|
||||
|
||||
/**
|
||||
* Inherit the prototype methods from one constructor into another.
|
||||
* @param {Function} childConstructor Child class.
|
||||
* @param {Function} parentConstructor Parent class.
|
||||
*/
|
||||
google.inherits = function(childConstructor, parentConstructor) {
|
||||
function tempConstructor() {};
|
||||
tempConstructor.prototype = parentConstructor.prototype;
|
||||
childConstructor.prototype = new tempConstructor();
|
||||
childConstructor.prototype.constructor = childConstructor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a context object to the function.
|
||||
* @param {Function} fn The function to bind to.
|
||||
* @param {Object} context The "this" object to use when the function is run.
|
||||
* @return {Function} A partially-applied form of fn.
|
||||
*/
|
||||
google.bind = function(fn, context) {
|
||||
return function() {
|
||||
return fn.apply(context, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Null function used for callbacks.
|
||||
* @type {Function}
|
||||
*/
|
||||
google.nullFunction = function() {};
|
||||
|
||||
/**
|
||||
* Simple logging facility.
|
||||
* @param {string} msg A message to write to the console.
|
||||
*/
|
||||
google.logger = function(msg) {
|
||||
// Uncomment the below to get log messages
|
||||
// May require firebug enabled to work in FireFox.
|
||||
// window.console.info(msg);
|
||||
};
|
347
lib/middleman/templates/mobile/source/tools/wspl/simplenotes/index.html
Executable file
|
@ -0,0 +1,347 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
A simple application showing the use of the web storage portability layer
|
||||
code and cache pattern for offline web application.
|
||||
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>SimpleNotes Demo</title>
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
<script src="../global_functions.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="../dbwrapperapi.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="../dbwrapper_html5.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="../databasefactory.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="template.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="simplenotes.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="list-note" class="screen list-note-screen" >
|
||||
<div class="title-bar">Note List</div>
|
||||
<div class="top command-bar">
|
||||
<button id="view-note-new">New</button>
|
||||
</div>
|
||||
<div class="status-bar">status bar</div>
|
||||
<div class="list-note-container-empty" style="display:none;"></div>
|
||||
<div class="list-note-container" style="display:none;">
|
||||
<div class="list-note-item">
|
||||
<a href="javascript:showNote(%noteKey%);">%subject%</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top command-bar">
|
||||
<button id="view-note-new">New</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-note" class="screen view-note-screen" style="display:none;">
|
||||
<div class="title-bar">View Note</div>
|
||||
<div class="top command-bar">
|
||||
<button id="view-note-back">Back</button>
|
||||
<button id="view-note-edit">Edit Note</button>
|
||||
</div>
|
||||
<div class="status-bar">status bar</div>
|
||||
<div class="view-note-container" style="display:none;">
|
||||
<div class="subject">%subject%</div>
|
||||
<div class="body">%body%</div>
|
||||
</div>
|
||||
<div class="bottom command-bar">
|
||||
<button id="view-note-back">Back</button>
|
||||
<button id="view-note-edit">Edit Note</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-note" class="screen edit-note-screen" style="display:none;">
|
||||
<div class="title-bar">Edit Note</div>
|
||||
<div class="top command-bar">
|
||||
<button id="edit-back-save">Save Note</button>
|
||||
<button id="edit-back-revert">Revert Note</button>
|
||||
</div>
|
||||
<div class="status-bar">status bar</div>
|
||||
<div>Subject:</div>
|
||||
<textarea rows="1" style="width:100%;" id="edit-note-subject"></textarea>
|
||||
<br>
|
||||
<div>Note:</div>
|
||||
<textarea rows="20" style="width:100%;" id="edit-note-body"></textarea>
|
||||
<div class="bottom command-bar">
|
||||
<button id="edit-back-save">Save Note</button>
|
||||
<button id="edit-back-revert">Revert Note</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
|
||||
/**
|
||||
* Tests the given DOM element for a specific CSS class.
|
||||
* @param {Object} e A DOM element.
|
||||
* @param {string} css A CSS class identifier to match.
|
||||
* @return {boolean} true if the element has the CSS class.
|
||||
*/
|
||||
function hasCssClass(e, css) {
|
||||
return e && e.className && e.className.search(css + '( |$)') > -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the parent element of a given element that has
|
||||
* the specified CSS class.
|
||||
* @param {Object} e A DOM element.
|
||||
* @param {string} css A CSS class identifier to match.
|
||||
* @param {Object} opt_stop A parent DOM element to stop at.
|
||||
* @return {Object} A DOM element parent of e or null.
|
||||
*/
|
||||
function findParentOfClass(e, css, opt_stop) {
|
||||
var stop = opt_stop || document;
|
||||
while (e && e != stop && !hasCssClass(e, css)) {
|
||||
e = e.parentElement;
|
||||
}
|
||||
return hasCssClass(e, css) ? e : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to the note creation screen.
|
||||
*/
|
||||
function editNote(event) {
|
||||
google.logger('editNote: <' + currentNote.subject + '>');
|
||||
hideBlock('#edit-note .status-bar');
|
||||
|
||||
var subject = document.getElementById('edit-note-subject');
|
||||
var body = document.getElementById('edit-note-body');
|
||||
subject.value = currentNote.subject;
|
||||
body.value = currentNote.body;
|
||||
|
||||
google.logger('end of editNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a note.
|
||||
*/
|
||||
function saveNote(event) {
|
||||
google.logger('saveNote: do database or other activity here');
|
||||
// Copy contents from fields and save.
|
||||
var subject = document.getElementById('edit-note-subject');
|
||||
var body = document.getElementById('edit-note-body');
|
||||
|
||||
cache.applyUiChange(currentNote.noteKey, subject.value,
|
||||
body.value, showNote);
|
||||
google.logger('saveNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Abandons the note.
|
||||
*/
|
||||
function abandonNote(event) {
|
||||
google.logger('abandonNote: do database or other activity here');
|
||||
showNote(currentNote.noteKey);
|
||||
google.logger('abandonNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to edit a new note.
|
||||
*/
|
||||
function newNote(event) {
|
||||
google.logger('newNote: do database or other activity here');
|
||||
alert('not implemented');
|
||||
// grab the contents and inject into the database.
|
||||
var subject = document.getElementById('edit-note-subject');
|
||||
var body = document.getElementById('edit-note-body');
|
||||
subject.value = '';
|
||||
body.value = '';
|
||||
google.logger('newNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps button names to customizer functions.
|
||||
*/
|
||||
var buttonHandlerSpecification = {
|
||||
'view-note-new': ['edit-note', newNote],
|
||||
'view-note-back': ['list-note', showNoteList],
|
||||
'view-note-edit': ['edit-note', editNote],
|
||||
'edit-back-save': ['view-note', saveNote],
|
||||
'edit-back-revert': ['view-note', abandonNote]
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides a block specified by a CSS selector and parent node.
|
||||
* @param {string} selector A CSS selector string.
|
||||
* @param {Object} opt_el A DOM element.
|
||||
*/
|
||||
function hideBlock(selector, opt_el) {
|
||||
var el = opt_el || document;
|
||||
var targetEl = el.querySelector(selector);
|
||||
targetEl.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a block specified by a CSS selector and parent node, inserting
|
||||
* the specified string as the innerHTML property of the selected block.
|
||||
* @param {string} selector A CSS selector
|
||||
* @param {string} html String to assign to the innerHTML property.
|
||||
* @param {Object} opt_el A DOM element.
|
||||
*/
|
||||
function showBlock(selector, html, opt_el) {
|
||||
var el = opt_el || document;
|
||||
var targetEl = el.querySelector(selector);
|
||||
if (html) targetEl.innerHTML = html;
|
||||
targetEl.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all button events.
|
||||
*/
|
||||
function buttonHandler(event) {
|
||||
google.logger('clicked on: ' + event.target.id);
|
||||
|
||||
var currentScreen = findParentOfClass(event.target, 'screen');
|
||||
var nextScreen = document.getElementById(
|
||||
buttonHandlerSpecification[event.target.id][0]);
|
||||
var helperFunction = buttonHandlerSpecification[event.target.id][1];
|
||||
|
||||
// All button handlers have a common implementation.
|
||||
// 1. hide current screen
|
||||
currentScreen.style.display = 'none';
|
||||
|
||||
// 2. show new screen
|
||||
nextScreen.style.display = 'block';
|
||||
|
||||
// 3. Activate status
|
||||
showBlock('.status-bar', 'Working...', nextScreen);
|
||||
|
||||
// 4. Call customizer function (which will probably query the model
|
||||
// stored in the cache.
|
||||
helperFunction(event);
|
||||
|
||||
// 5. A (possibly asynchronous) callback from the customizer will
|
||||
// finish the action by running the appropriate view function.
|
||||
google.logger('end of button Handler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Call back from a specific JavaScript url to show a specific
|
||||
* note.
|
||||
* @param {number} noteKey
|
||||
* @return {boolean}
|
||||
*/
|
||||
function showNote(noteKey) {
|
||||
hideBlock('#list-note');
|
||||
showBlock('#view-note .status-bar', 'Working...');
|
||||
showBlock('#view-note');
|
||||
cache.getValues('fullnote', [noteKey], renderFullNote);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for a note and display it.
|
||||
* @param {Object} oneNote
|
||||
* @param {boolean} complete True if the note has been provided or
|
||||
* if an updated note is arriving. (Necessary for future support for
|
||||
* sub-note updates.)
|
||||
*/
|
||||
function renderFullNote(oneNote, complete) {
|
||||
google.logger('renderFullNote');
|
||||
|
||||
showBlock('.view-note-container', noteViewTemplate.process(oneNote));
|
||||
if (complete) {
|
||||
hideBlock('#view-note .status-bar');
|
||||
}
|
||||
currentNote = oneNote;
|
||||
google.logger('done renderFullNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for a note list and display it.
|
||||
* @param {Array.<Object>} noteHeaders
|
||||
* @param {boolean} complete True if all in-existence notes that lie
|
||||
* in the query range have been provided even if less than what was
|
||||
* requested.
|
||||
*/
|
||||
function renderNoteList(noteHeaders, complete) {
|
||||
google.logger('renderNoteList: ' + noteHeaders.length);
|
||||
|
||||
if (noteHeaders.length == 0) {
|
||||
showBlock('.list-note-container-empty', 'No notes');
|
||||
} else {
|
||||
var notes = noteHeaders.map(function(n) {
|
||||
return noteListTemplate.process(n);
|
||||
});
|
||||
showBlock('.list-note-container', notes.join());
|
||||
}
|
||||
if (complete) {
|
||||
hideBlock('#list-note .status-bar');
|
||||
}
|
||||
google.logger('done renderNoteList');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for an updated note list.
|
||||
*/
|
||||
function showNoteList() {
|
||||
google.logger('showNoteList');
|
||||
cache.getValues('list', [0,20], renderNoteList);
|
||||
currentNote = undefined;
|
||||
google.logger('end of showNoteList');
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor for the controller. Sets up the UI and
|
||||
* queries the cache for initial notelist contents.
|
||||
*/
|
||||
function createController() {
|
||||
google.logger('createController:');
|
||||
var buttons = document.querySelectorAll('.command-bar button');
|
||||
Array.prototype.forEach.call(buttons, function(b) {
|
||||
b.addEventListener('click', buttonHandler);
|
||||
});
|
||||
google.logger('controller is live');
|
||||
showNoteList();
|
||||
}
|
||||
|
||||
google.logger('about to fetch the template');
|
||||
var currentNote = undefined;
|
||||
|
||||
var noteListTemplate = new google.wspl.simplenotes.Template(
|
||||
document.querySelector('.list-note-container').innerHTML);
|
||||
|
||||
var noteViewTemplate = new google.wspl.simplenotes.Template(
|
||||
document.querySelector('.view-note-container').innerHTML);
|
||||
|
||||
// var noteEditTemplate = new google.wspl.simplenotes.Template();
|
||||
|
||||
google.logger('created the template');
|
||||
|
||||
// debugging code... tear out...
|
||||
// test the template construction...
|
||||
// showBlock('.list-note-container', noteListTemplate.process(
|
||||
// {noteKey: 123, subject: 'hi rob'}));
|
||||
|
||||
|
||||
google.logger('loaded database');
|
||||
var cache = new google.wspl.simplenotes.Cache();
|
||||
cache.startCache(createController);
|
||||
|
||||
google.logger('finished loading page');
|
||||
</script>
|
||||
</html>
|
503
lib/middleman/templates/mobile/source/tools/wspl/simplenotes/simplenotes.js
Executable file
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A concrete example of the cache pattern for building an offline
|
||||
* webapplication: a cache for SimpleNotes.
|
||||
*/
|
||||
|
||||
google.wspl.simplenotes = google.wspl.simplenotes || {};
|
||||
google.logger('start simplenotes.js');
|
||||
|
||||
/**
|
||||
* Status keys for the write buffer.
|
||||
* @enum {number}
|
||||
*/
|
||||
google.wspl.simplenotes.WriteBufferStates = {
|
||||
/**
|
||||
* The update is in flight to the server.
|
||||
*/
|
||||
INFLIGHT: 1,
|
||||
|
||||
/**
|
||||
* The update needs to be (re)sent to the server but is not in flight.
|
||||
*/
|
||||
SEND: 2,
|
||||
|
||||
/**
|
||||
* The update needs to be applied to the cached notes.
|
||||
*/
|
||||
REAPPLY: 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SimpleNotes cache wrapping a backing database.
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.simplenotes.Cache = function() {
|
||||
this.dbms_ = google.wspl.DatabaseFactory.createDatabase(
|
||||
'simple-notes', 'http://yourdomain/dbworker.js');
|
||||
|
||||
/**
|
||||
* Cache directory is a two-tuple over a range. (Holes
|
||||
* must be allowed to support delection.)
|
||||
* This is the lower (inclusive) bound of the cached range.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.start_ = -1;
|
||||
|
||||
/**
|
||||
*
|
||||
* This is the upper (inclusive) bound of the cached range.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.end_ = -1;
|
||||
|
||||
/**
|
||||
* Start of range of notes known to exist on server at time of last
|
||||
* response.
|
||||
* @param {number}
|
||||
*/
|
||||
this.serverStart_ = -1;
|
||||
|
||||
/**
|
||||
* End of range of notes known to exist on server at time of last
|
||||
* response.
|
||||
* @param {number}
|
||||
*/
|
||||
this.serverEnd_ = -1;
|
||||
|
||||
/**
|
||||
* Time of last refresh.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastRefresh_ = -1;
|
||||
|
||||
/**
|
||||
* Last missing query.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.lastMiss_ = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interval between refreshes in milliseconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_ = 2000;
|
||||
|
||||
google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TABLE IF NOT EXISTS cached_notes (' +
|
||||
'noteKey INTEGER UNIQUE PRIMARY KEY,' +
|
||||
'subject TEXT,' +
|
||||
'body TEXT' +
|
||||
');'
|
||||
);
|
||||
|
||||
google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TABLE IF NOT EXISTS write_buffer (' +
|
||||
'sequence INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT,' +
|
||||
'noteKey INTEGER,' +
|
||||
'status INTEGER,' +
|
||||
'subject TEXT,' +
|
||||
'body TEXT' +
|
||||
');'
|
||||
);
|
||||
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT MIN(noteKey) as minNoteKey FROM cached_notes;');
|
||||
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT MAX(noteKey) as maxNoteKey FROM cached_notes;');
|
||||
|
||||
/**
|
||||
* Builds a cache and writebuffer combination for notes and then
|
||||
* invokes the given callback.
|
||||
* @param {function) callback.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.startCache = function(callback) {
|
||||
google.logger('startCache');
|
||||
var statc = 0;
|
||||
var self = this;
|
||||
|
||||
var perStatCallback = function(tx, result) {
|
||||
google.logger('perStatCallback');
|
||||
if (statc == 4) {
|
||||
self.start_ = (result.isValidRow()) ? result.getRow().minNoteKey : -1;
|
||||
self.serverStart_ = self.start_; // Temporary. Remove when server exists.
|
||||
} else if (statc == 5) {
|
||||
self.end_ = (result.isValidRow()) ? result.getRow().maxNoteKey : -1;
|
||||
self.serverEnd_ = self.end_; // Temporary. Remove when server exists.
|
||||
}
|
||||
statc++;
|
||||
};
|
||||
|
||||
this.dbms_.executeAll([
|
||||
google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_,
|
||||
google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_,
|
||||
google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_,
|
||||
google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_,
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_,
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_],
|
||||
{onSuccess: perStatCallback, onFailure: this.logError_},
|
||||
{onSuccess: callback, onFailure: this.logError_});
|
||||
google.logger('finished startCache');
|
||||
};
|
||||
|
||||
/**
|
||||
* Stub function to be replaced with a server communication.
|
||||
* @param {Array.<Object>} updates Payload to send to server.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.sendXhrPost = function(updates) {
|
||||
google.logger('Should dispatch XHR to server now.');
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT noteKey, subject from cached_notes WHERE ' +
|
||||
'noteKey >= ? AND ' +
|
||||
'noteKey <= ? ' +
|
||||
';'
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests if the given range is stored in the cache.
|
||||
* Note that this mechanism requires extension to handle the
|
||||
* creation of new notes.
|
||||
* @param {number} start Lower bound (inclusive) on range.
|
||||
* @param {number} end Uppder bound (inclusive) on range.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.isCacheHit_ = function(start, end) {
|
||||
return start >= this.start_ && end <= this.end_
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a possibly useful error message.
|
||||
* @param {Object} error An error descriptor.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.logError_ = function(error) {
|
||||
google.logger('Simple Notes Cache is sad: ' + error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queries the cache for a list of note headers.
|
||||
* @param {number} start The lower id in a range of notes.
|
||||
* @param {number} end The higher id in a range of notes.
|
||||
* @param {function(Array.<Object>)} valuesCallback A function to call
|
||||
* with the result set after the transaction has completed.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.getNoteList_ = function(start, end,
|
||||
valuesCallback) {
|
||||
var notes = [];
|
||||
|
||||
var accumulateResults = function(tx, result) {
|
||||
for(; result.isValidRow(); result.next()) {
|
||||
notes.push(result.getRow());
|
||||
google.logger('pushed...');
|
||||
}
|
||||
};
|
||||
|
||||
var inTransactionGetNotes = function(tx) {
|
||||
tx.execute(google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_.
|
||||
createStatement([start, end]), {
|
||||
onSuccess: accumulateResults,
|
||||
onFailure: this.logError_});
|
||||
};
|
||||
|
||||
var hit = this.isCacheHit_(start, end);
|
||||
this.dbms_.createTransaction(inTransactionGetNotes, {onSuccess: function() {
|
||||
valuesCallback(notes, hit);
|
||||
}, onFailure: this.logError_});
|
||||
|
||||
if (hit) {
|
||||
this.fetchFromServer(this.start_, this.end_); // Refresh
|
||||
} else {
|
||||
this.fetchFromServer(Math.min(this.start_, start), Math.max(this.end_, end));
|
||||
this.lastMiss_ = {callback: valuesCallback, start: start, end: end};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.GET_ONE_NOTE_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT noteKey, subject, body from cached_notes WHERE ' +
|
||||
'noteKey = ? ' +
|
||||
';'
|
||||
);
|
||||
|
||||
/**
|
||||
* Queries the cache for a list of note headers.
|
||||
* @param {number} noteId The note to get from the cache.
|
||||
* @param {function(Array.<Object>)} valuesCallback A function to call
|
||||
* with the result set after the transaction has completed.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.getOneNote_ = function(noteId,
|
||||
callback) {
|
||||
var note;
|
||||
|
||||
this.dbms_.execute(google.wspl.simplenotes.Cache.GET_ONE_NOTE_.
|
||||
createStatement([noteId]),
|
||||
{onSuccess: function(tx, result) { note = result.getRow(); },
|
||||
onFailure: this.logError_},
|
||||
{onSuccess: function() { callback(note, true); },
|
||||
onFailure: this.logError_});
|
||||
};
|
||||
|
||||
/**
|
||||
* Queries the cache for either a list of notes or a single note including
|
||||
* its body.
|
||||
* @param {string} type The kind of values desired: 'list' or 'fullnote'.
|
||||
* @param {Array.<number>} query The query for the values.
|
||||
* @param {function(Array.<Object>)} valuesCallback A function to call
|
||||
* with the result set after the transaction has completed.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.getValues = function(type,
|
||||
query, valuesCallback) {
|
||||
|
||||
// Reduce any query to what would be available from the server
|
||||
query[0] = Math.max(this.serverStart_, query[0]);
|
||||
query[1] = Math.min(this.serverEnd_, query[1]);
|
||||
|
||||
if (type == 'list') {
|
||||
this.getNoteList_(query[0], query[1], valuesCallback);
|
||||
} else if (type == 'fullnote') {
|
||||
this.getOneNote_(query[0], valuesCallback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* SQL trigger to insert a new change from write buffer to
|
||||
* cache.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TRIGGER IF NOT EXISTS updateTrigger ' +
|
||||
'AFTER INSERT ON write_buffer ' +
|
||||
'BEGIN ' +
|
||||
' REPLACE INTO cached_notes ' +
|
||||
' SELECT noteKey, subject, body ' +
|
||||
' FROM write_buffer WHERE status & 8 = 8; ' +
|
||||
' UPDATE write_buffer SET status = status & ~ 8; ' +
|
||||
'END;'
|
||||
);
|
||||
|
||||
/**
|
||||
* SQL trigger to replay changes from the write buffer to
|
||||
* the cache.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TRIGGER IF NOT EXISTS replayTrigger ' +
|
||||
'AFTER UPDATE ON write_buffer WHEN NEW.status & 8 = 8 ' +
|
||||
'BEGIN ' +
|
||||
' REPLACE INTO cached_notes ' +
|
||||
' SELECT noteKey, subject, body ' +
|
||||
' FROM write_buffer ' +
|
||||
' WHERE noteKey = NEW.noteKey ' +
|
||||
' ORDER BY sequence ASC;' +
|
||||
' UPDATE write_buffer SET status = status & ~ 8; ' +
|
||||
'END;'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* SQL statement to mark actions for replay.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.MARK_FOR_REPLAY =
|
||||
new google.wspl.Statement(
|
||||
'UPDATE write_buffer SET status = status | 8;');
|
||||
|
||||
/**
|
||||
* SQL statement to insert notes updates.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_ =
|
||||
new google.wspl.Statement(
|
||||
'INSERT INTO write_buffer (' +
|
||||
'noteKey, status, subject, body' +
|
||||
') ' + 'VALUES(?,?,?,?);');
|
||||
|
||||
/**
|
||||
* Updates the given entry and write a new write buffer entry.
|
||||
* @param {number} noteKey
|
||||
* @param {string} subject
|
||||
* @param {string} body
|
||||
* @param {function(number)} ackCallback
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.applyUiChange = function(noteKey,
|
||||
subject, body, ackCallback) {
|
||||
var self = this;
|
||||
var update = [noteKey, 2 | 8, subject, body];
|
||||
var stat = google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_.createStatement(
|
||||
update);
|
||||
|
||||
this.dbms_.execute(stat, null, {onSuccess: function() {
|
||||
google.logger('applyUiChange cb');
|
||||
ackCallback(noteKey);
|
||||
}, onFailure: function (error) {
|
||||
self.logError_(error);
|
||||
ackCallback(-1);
|
||||
}});
|
||||
};
|
||||
|
||||
/**
|
||||
* SQL statement to insert notes updates.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.INSERT_NOTE_ =
|
||||
new google.wspl.Statement(
|
||||
'REPLACE INTO cached_notes (noteKey, subject, body) ' +
|
||||
'VALUES(?,?,?);' );
|
||||
|
||||
/**
|
||||
* SQL statement to force replay of pending actions by setting a bit
|
||||
* flag on each write-buffer row indicating that it should be reapplied
|
||||
* to the contents of the cache.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.FORCE_REPLAY_ =
|
||||
new google.wspl.Statement(
|
||||
'UPDATE write_buffer SET status = status | 8;' );
|
||||
|
||||
/**
|
||||
* SQL statement to delete notes no longer to be cached.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.EVICT_ =
|
||||
new google.wspl.Statement(
|
||||
'DELETE FROM cached_notes WHERE noteKey < ? OR noteKey > ?;');
|
||||
|
||||
/**
|
||||
* Applies the changes delivered from the server by first inserting
|
||||
* them into the cache and reapplying the write-buffer to the cache.
|
||||
* @param {!Array.<Object>} notes An array of arrays.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.insertUpdate = function(notes) {
|
||||
var self = this; var stats = [];
|
||||
var start = notes[0].noteKey;
|
||||
var end = notes[0].noteKey;
|
||||
|
||||
for (var i = 0; i < notes.length; i++) {
|
||||
stats.push(google.wspl.simplenotes.Cache.INSERT_NOTE_.
|
||||
createStatement([notes[i].noteKey, notes[i].subject, notes[i].body]));
|
||||
start = Math.min(start, notes[0].noteKey);
|
||||
end = Math.max(end, notes[0].noteKey);
|
||||
}
|
||||
stats.push(google.wspl.simplenotes.Cache.EVICT_.createStatement([start, end]));
|
||||
stats.push(google.wspl.simplenotes.Cache.FORCE_REPLAY_);
|
||||
|
||||
var inTrans = function(tx) {
|
||||
self.start_ = start;
|
||||
self.end_ = end;
|
||||
tx.executeAll(stats);
|
||||
};
|
||||
|
||||
var afterInsert = function(tx) {
|
||||
if (this.lastMiss_ &&
|
||||
this.isCacheHit_(this.lastMiss_.start, this.lastMiss_.end)) {
|
||||
this.lastMiss_.callback(notes);
|
||||
this.lastMiss_ = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
this.dbms_.createTransaction(inTrans, {onSuccess: afterInsert,
|
||||
onError: this.logError_});
|
||||
};
|
||||
|
||||
/**
|
||||
* SQL statement to force replay of pending actions by setting a bit
|
||||
* flag on each write-buffer row indicating that it should be reapplied
|
||||
* to the contents of the cache.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT noteKey, subject, body FROM write_buffer WHERE status & 2 = 2;');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SQL statement to mark write buffer statements as inflight.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_ =
|
||||
new google.wspl.Statement(
|
||||
'UPDATE write_buffer SET status = status & ~2 | 1 WHERE status & 2 = 2;');
|
||||
|
||||
/**
|
||||
* Fetches new material from the server as required.
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {function} opt_valueCallBack
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.fetchFromServer = function(start,
|
||||
end) {
|
||||
google.logger('fetchFromServer');
|
||||
var now = this.dbms_.getCurrentTime();
|
||||
if (start >= this.start_ && end <= this.end_ &&
|
||||
now - this.lastRefresh_ <
|
||||
google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_) {
|
||||
return;
|
||||
}
|
||||
|
||||
var updates = []; var self = this; var flag = 1; var sql = []
|
||||
sql.push(google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_);
|
||||
sql.push(google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_);
|
||||
|
||||
var accumulateUpdates = function(tx, rs) {
|
||||
if (flag == 1) {
|
||||
for(; rs.isValidRow(); rs.next()) { updates.push(['u', rs.getRow()]); }
|
||||
flag++;
|
||||
}
|
||||
};
|
||||
|
||||
var ackAndPost = function() {
|
||||
updates.push(['q', {start: start, end: end}]);
|
||||
self.sendXhrPost(updates);
|
||||
};
|
||||
|
||||
this.dbms_.executeAll(sql,
|
||||
{onSuccess: accumulateUpdates, onFailure: this.logError_},
|
||||
{onSuccess: ackAndPost, onFailure: this.logError_});
|
||||
};
|
66
lib/middleman/templates/mobile/source/tools/wspl/simplenotes/styles.css
Executable file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #efefef;
|
||||
font: medium "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background-color: #ffd21d;
|
||||
display: none;
|
||||
-webkit-border-radius: 6.0px;
|
||||
-moz-border-radius: 6.0px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.screen {
|
||||
border: thin solid #939393;
|
||||
padding: 2px 2px 2px 2px;
|
||||
}
|
||||
|
||||
.command-bar {
|
||||
background-color: #275390;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
font-weight: bold;
|
||||
background-color: #4b4b4b;
|
||||
text-align: center;
|
||||
padding: 3px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
button {
|
||||
text-align:center;
|
||||
font-family: Helvetica;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
text-decoration: none;
|
||||
text-shadow: #fff 0px 2px 2px;
|
||||
padding: 3px;
|
||||
-webkit-border-radius: 6.0px;
|
||||
-moz-border-radius: 6.0px;
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
}
|
75
lib/middleman/templates/mobile/source/tools/wspl/simplenotes/template.js
Executable file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview template.js contains the implementation for a simple template
|
||||
* scheme. Templates are fragments of HTML containing patterns into which
|
||||
* arguments will be substituted.
|
||||
*/
|
||||
|
||||
google.wspl.simplenotes = google.wspl.simplenotes || {};
|
||||
|
||||
/**
|
||||
* Template class constructor. A template is an object which will
|
||||
* substitute provided parameters into a marked-up string.
|
||||
* @param {string} template
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.simplenotes.Template = function(template) {
|
||||
this.template_ = template;
|
||||
this.res_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the template expanded with the given args where args
|
||||
* is an object (acting as an associative array) binding keys (found
|
||||
* in the template wrapped with % symbols) to the associated
|
||||
* values.
|
||||
*
|
||||
* Template substitution symbols without corresponding arguments
|
||||
* will be passed through unchanged to the output.
|
||||
*
|
||||
* We assume that in typical use, the same template will be expanded
|
||||
* repeatedly with different values. In this case, storing and re-using
|
||||
* previously generated regular expressions will provide a performance
|
||||
* improvement.
|
||||
* @param {Object} args associates names with values
|
||||
* @param {boolean} opt_rebuild set to true to force re-building the
|
||||
* the regular epxression.
|
||||
*/
|
||||
google.wspl.simplenotes.Template.prototype.process = function(args,
|
||||
opt_rebuild) {
|
||||
var rebuild = opt_rebuild || false;
|
||||
|
||||
if (rebuild || this.res_ == null) {
|
||||
var accumulatedRe = [];
|
||||
this.res_ = null;
|
||||
for (var a in args) {
|
||||
accumulatedRe.push('%' + String(a) + '%');
|
||||
}
|
||||
if (accumulatedRe.length > 0) {
|
||||
this.res_ = new RegExp(accumulatedRe.join('|'), 'g');
|
||||
}
|
||||
}
|
||||
if (this.res_ != null) {
|
||||
return this.template_.replace(this.res_, function(match) {
|
||||
var keyName = match.slice(1,-1);
|
||||
return args[keyName];
|
||||
});
|
||||
} else {
|
||||
return this.template_;
|
||||
}
|
||||
};
|