From 0c3d2fc5c6d653adb5c4a62d1ff89f27fc946ec7 Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Wed, 23 Feb 2011 10:22:28 +0000 Subject: [PATCH] new client side Ajax admin user interface --- MANIFEST | 26 + site/cgi-bin/cfg/admin-ui.cfg | 6 + site/cgi-bin/modules/BSE/ChangePW.pm | 12 +- site/cgi-bin/modules/BSE/UI/API.pm | 19 +- site/htdocs/admin/admin.html | 29 + site/htdocs/css/admin-ui/admin.css | 680 ++++++++++++ site/htdocs/css/admin-ui/extra.css | 119 +++ site/htdocs/css/admin-ui/reset.css | 1 + site/htdocs/css/bse_adminui.css | 191 ++++ site/htdocs/images/admin/ui/changepw.png | Bin 0 -> 249 bytes site/htdocs/images/admin/ui/debug.png | Bin 0 -> 525 bytes site/htdocs/images/admin/ui/delete_dk.png | Bin 0 -> 627 bytes site/htdocs/images/admin/ui/menu.png | Bin 0 -> 202 bytes site/htdocs/images/admin/ui/move_dk.png | Bin 0 -> 230 bytes site/htdocs/images/admin/ui/spinner.gif | Bin 0 -> 2545 bytes site/htdocs/js/admin-ui/debug.js | 34 + site/htdocs/js/admin-ui/menu.js | 17 + site/htdocs/js/bse_adminui.js | 509 +++++++++ site/htdocs/js/bse_api.js | 145 ++- site/htdocs/js/bse_dialog.js | 1170 +++++++++++++++++++++ site/htdocs/js/bse_loader.js | 41 + site/htdocs/js/bse_menu.js | 342 ++++++ site/htdocs/js/bse_validate.js | 204 ++++ t-js/01validate.html | 13 + t-js/01validate.js | 241 +++++ t-js/10menu.html | 15 + t-js/10menu.js | 145 +++ t-js/menu.css | 228 ++++ t-js/test.js | 134 +++ t-js/tests.css | 24 + 30 files changed, 4290 insertions(+), 55 deletions(-) create mode 100644 site/cgi-bin/cfg/admin-ui.cfg create mode 100644 site/htdocs/admin/admin.html create mode 100644 site/htdocs/css/admin-ui/admin.css create mode 100644 site/htdocs/css/admin-ui/extra.css create mode 100644 site/htdocs/css/admin-ui/reset.css create mode 100644 site/htdocs/css/bse_adminui.css create mode 100644 site/htdocs/images/admin/ui/changepw.png create mode 100644 site/htdocs/images/admin/ui/debug.png create mode 100644 site/htdocs/images/admin/ui/delete_dk.png create mode 100644 site/htdocs/images/admin/ui/menu.png create mode 100644 site/htdocs/images/admin/ui/move_dk.png create mode 100644 site/htdocs/images/admin/ui/spinner.gif create mode 100644 site/htdocs/js/admin-ui/debug.js create mode 100644 site/htdocs/js/admin-ui/menu.js create mode 100644 site/htdocs/js/bse_adminui.js create mode 100644 site/htdocs/js/bse_dialog.js create mode 100644 site/htdocs/js/bse_loader.js create mode 100644 site/htdocs/js/bse_menu.js create mode 100644 site/htdocs/js/bse_validate.js create mode 100644 t-js/01validate.html create mode 100644 t-js/01validate.js create mode 100644 t-js/10menu.html create mode 100644 t-js/10menu.js create mode 100644 t-js/menu.css create mode 100644 t-js/test.js create mode 100644 t-js/tests.css diff --git a/MANIFEST b/MANIFEST index 646fa427..100ca121 100644 --- a/MANIFEST +++ b/MANIFEST @@ -42,6 +42,7 @@ site/cgi-bin/admin/userupdate.pl site/cgi-bin/affiliate.pl site/cgi-bin/api.pl site/cgi-bin/bse.cfg +site/cgi-bin/cfg/admin-ui.cfg site/cgi-bin/fileprogress.fcgi site/cgi-bin/fileprogress.pl site/cgi-bin/fmail.fcgi @@ -381,6 +382,7 @@ site/docs/upgrade_mysql.html site/docs/userupdate.html site/docs/userupdate.pod site/htdocs/a/.htaccess +site/htdocs/admin/admin.html site/htdocs/admin/advanced.html site/htdocs/admin/help/access.html site/htdocs/admin/help/addgroup.html @@ -396,9 +398,13 @@ site/htdocs/admin/help/subs.html site/htdocs/admin/help/subssend.html site/htdocs/admin/index.html site/htdocs/admin/sadmin.html +site/htdocs/css/admin-ui/admin.css +site/htdocs/css/admin-ui/extra.css +site/htdocs/css/admin-ui/reset.css site/htdocs/css/admin.css site/htdocs/css/admin.css_natural site/htdocs/css/admin_messages.css +site/htdocs/css/bse_adminui.css site/htdocs/css/sadmin.css site/htdocs/css/style-main.css site/htdocs/favicon.ico @@ -410,6 +416,12 @@ site/htdocs/images/admin/help.gif site/htdocs/images/admin/move_down.gif site/htdocs/images/admin/move_up.gif site/htdocs/images/admin/nothumb.png +site/htdocs/images/admin/ui/changepw.png +site/htdocs/images/admin/ui/debug.png +site/htdocs/images/admin/ui/delete_dk.png +site/htdocs/images/admin/ui/menu.png +site/htdocs/images/admin/ui/move_dk.png +site/htdocs/images/admin/ui/spinner.gif site/htdocs/images/admin/unchecked.gif site/htdocs/images/filestatus/download.gif site/htdocs/images/filestatus/forSale.gif @@ -431,12 +443,19 @@ site/htdocs/images/titles/the_shop.gif site/htdocs/images/titles/your_site.gif site/htdocs/images/trans_pixel.gif site/htdocs/images/videoclose.png +site/htdocs/js/admin-ui/debug.js +site/htdocs/js/admin-ui/menu.js site/htdocs/js/admin_messages.js site/htdocs/js/admin_prodopts.js site/htdocs/js/admin_tools.js site/htdocs/js/bse.js +site/htdocs/js/bse_adminui.js site/htdocs/js/bse_api.js +site/htdocs/js/bse_dialog.js site/htdocs/js/bse_flowplayer.js +site/htdocs/js/bse_loader.js +site/htdocs/js/bse_menu.js +site/htdocs/js/bse_validate.js site/htdocs/js/builder.js site/htdocs/js/controls.js site/htdocs/js/date.js @@ -737,6 +756,13 @@ site/util/make_versions.pl site/util/mysql.str site/util/update_title_summary.pl site/util/upgrade_mysql.pl +t-js/01validate.html +t-js/01validate.js +t-js/10menu.html +t-js/10menu.js +t-js/menu.css +t-js/test.js +t-js/tests.css t/BSE/Test.pm t/cfg/bse.cfg t/cfg/cfg/00start.cfg diff --git a/site/cgi-bin/cfg/admin-ui.cfg b/site/cgi-bin/cfg/admin-ui.cfg new file mode 100644 index 00000000..46861621 --- /dev/null +++ b/site/cgi-bin/cfg/admin-ui.cfg @@ -0,0 +1,6 @@ +[extra a_config] +admin_ui=admin ui scripts + +[admin ui scripts] +menu=Main Menu;/js/admin-ui/menu.js;am +debug=Debug Log;/js/admin-ui/debug.js;zm diff --git a/site/cgi-bin/modules/BSE/ChangePW.pm b/site/cgi-bin/modules/BSE/ChangePW.pm index c747e5ff..5aa3e711 100644 --- a/site/cgi-bin/modules/BSE/ChangePW.pm +++ b/site/cgi-bin/modules/BSE/ChangePW.pm @@ -4,7 +4,7 @@ use BSE::Util::Tags qw(tag_error_img); use BSE::Util::HTML; use base 'BSE::UI::AdminDispatch'; -our $VERSION = "1.000"; +our $VERSION = "1.001"; my %actions = ( @@ -78,12 +78,18 @@ sub req_change { && $newpw ne $confirm) { $errors{confirm} = "Confirmation password does not match new password"; } - keys %errors - and return $class->req_form($req, undef, \%errors); + if (keys %errors) { + $req->is_ajax + and return $class->_field_error($req, \%errors); + return $class->req_form($req, undef, \%errors); + } $user->changepw($newpw); $user->save; + $req->is_ajax + and return $req->json_content(success => 1); + my $r = $cgi->param('r'); unless ($r) { $r = $req->url('menu', { m => "New password saved" }); diff --git a/site/cgi-bin/modules/BSE/UI/API.pm b/site/cgi-bin/modules/BSE/UI/API.pm index 4d2979a6..ed083e07 100644 --- a/site/cgi-bin/modules/BSE/UI/API.pm +++ b/site/cgi-bin/modules/BSE/UI/API.pm @@ -2,7 +2,7 @@ package BSE::UI::API; use strict; use base "BSE::UI::Dispatch"; -our $VERSION = "1.000"; +our $VERSION = "1.001"; my %actions = ( @@ -22,13 +22,28 @@ sub req_config { my ($self, $req) = @_; my $cfg = $req->cfg; - return $req->json_content + my %result = ( success => 1, perlbal => $cfg->entry("basic", "perlbal", 0), access_control => $cfg->entry("basic", "access_control", 0), tracking_uploads => $req->_tracking_uploads, ); + + my %custom = $cfg->entries("extra a_config"); + for my $key (keys %custom) { + exists $result{$key} and next; + + my $section = $custom{$key}; + $section =~ /\{(level|generator|parentid|template)\}/ + and next; + + $section eq "db" and die; + + $result{$key} = { $cfg->entries($section) }; + } + + return $req->json_content(\%result); } sub req_fail { diff --git a/site/htdocs/admin/admin.html b/site/htdocs/admin/admin.html new file mode 100644 index 00000000..78391b5c --- /dev/null +++ b/site/htdocs/admin/admin.html @@ -0,0 +1,29 @@ + + + + + BSE Administration + + + + + + + + + + + + + + + + +
+ + + diff --git a/site/htdocs/css/admin-ui/admin.css b/site/htdocs/css/admin-ui/admin.css new file mode 100644 index 00000000..12632f5d --- /dev/null +++ b/site/htdocs/css/admin-ui/admin.css @@ -0,0 +1,680 @@ +/* @override + http://www.spacepark.com/admin/sp/css/admin.css + file:///Users/adriann/Work/Spacepark/spadmin/admin.css +*/ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + text-align: center; + width: 100%; +} +body { + width: 100%; + text-align: left; + margin: 0 auto; + padding: 0; + background-color: #666; + font: normal normal 0.75em/1.5 "Lucida Sans Unicode", "Lucida Grande", "Helvetica Neue", Helvetica, Arial, Helvetica, sans-serif; + text-shadow: 0 0.083em 0 #fff; + color: #585858; + position: relative; +} +select, input, textarea, button { + font-size: 1em; + margin: 0; +} +.hidden { + display: none !important; +} +.left { + float: left; +} +.right { + float: right; +} +.preview { + position: absolute; + top: 4em; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + position: fixed; +} +iframe { + width: 100%; + height: 100%; + border: 0; /* ie requires HTML iframe attribute frameborder="0" */ + z-index: 1; + position: absolute; +} +.lightbox { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; + padding: 0; + background-color: rgba(0,0,0,0.75); + z-index: 3; + overflow: visible; + height: 100%; + width: 100%; +} +.half { + width: 50%; +} +.full { + width: 100%; +} +fieldset.error, label.error, .error>label, .error>fieldset, .error>legend { + color: #c03 !important; + border-color: #c03 !important; +} + +/* @group window */ + +div.window { + position: relative; + z-index: 100; + margin: 4em auto; + margin-top: 8em; + width: 95%; + max-width: 80em; + min-width: 34em; + padding: 1em 2em 1.5em; + background-color: #e8e8e8; + border-top: 0.2em solid #fff; + + overflow: hidden; /* clear contained floats */ + + -webkit-border-radius: 1em; + -moz-border-radius: 1em; + -o-border-radius: 1em; + border-radius: 1em; + + -moz-box-shadow: 0.25em 0.25em 0.5em rgba(0,0,0,0.75); + -webkit-box-shadow: 0.25em 0.25em 0.5em rgba(0,0,0,0.75); + -o-box-shadow: 0.25em 0.25em 0.5em rgba(0,0,0,0.75); + box-shadow: 0.25em 0.25em 0.5em rgba(0,0,0,0.75); +} +div.window.dialog { + max-width: 40em; +} +div.window fieldset fieldset { + /*display: table; opera encloses the legend if set as table */ + border: 0.1em solid #ddd; + border-color: #b5b5b5 #ddd #fff #ddd; + margin-bottom: 1em; + padding: 1em; + padding-bottom: 0.75em; + + -webkit-border-radius: 0.5em; + -moz-border-radius: 0.5em; + -o-border-radius: 0.5em; + border-radius: 0.5em; +} +div.window>form>fieldset>legend { + font-size: 2em !important; + color: #999; +} +div.window fieldset fieldset legend { + font-size: 1.25em; + color: #333; + margin: 0; + padding: 0 0.25em; +} +div.window fieldset>div { + display: table-row; +} +div.window fieldset>div>label, div.window fieldset>div>span { + display: table-cell; + vertical-align: top; + /*padding: 0.25em 1em 0.25em 0;*/ + padding: 0.25em 0; +} +div.window fieldset>div>label { + padding-right: 0.5em; + text-align: right; + width: 10%; + min-width: 9em; + white-space: nowrap; +} +div.window fieldset>div>span { + width: 100%; +} +div.window fieldset>div>span>button { + display: none; + /*background-color: #ccc; + width: 1.75em; + height: 1.75em; + text-indent: -9999em; + overflow: hidden;*/ +} +div.window fieldset>div:hover>span>button { + display: inline; +} +div.window fieldset input[type=text], div.window fieldset input[type=password], +div.window fieldset input[type=file], div.window fieldset select, div.window fieldset textarea { + border: 0.1em solid #ddd; + border-top-color: #aaa; + line-height: 1.2; + padding: 0.25em; + background-color: #eee; + float: left; /* because some odd margins persist + display: inline-block;*/ + min-width: 20em; + width: 40%; +} +div.window fieldset input[type=file] { + padding: 0.1em 0.25em; + overflow: hidden; + position: relative; +} +div.window fieldset div.full input[type=text], div.window fieldset div.full input[type=password], +div.window fieldset div.full input[type=file], div.window fieldset div.full select, div.window fieldset div.full textarea, +div.dialog fieldset input[type=text], div.dialog fieldset input[type=password], +div.dialog fieldset input[type=file], div.dialog fieldset select, div.dialog fieldset textarea { + width: 100%; +} +div.window fieldset textarea { + height: 20em; +} +div.window.dialog fieldset textarea { + height: 10em; +} +div.window fieldset input:focus, div.window fieldset textarea:focus, div.window fieldset select:focus { + border-color: rgba(3,153,212,0.75); + -moz-box-shadow: 0 0 0.25em rgba(3,153,212,0.75); + -webkit-box-shadow: 0 0 0.25em rgba(3,153,212,0.75); + -o-box-shadow: 0 0 0.25em rgba(3,153,212,0.75); + box-shadow: 0 0 0.25em rgba(3,153,212,0.75); +} +div.window fieldset>label { + text-align: left; + display: block; +} + +/* @end */ + +/* @group menu */ + +#menu p.message { + position: absolute; + top: 4em; + width: 100%; + padding: 1em 2em; + color: #fff; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.3); + font-weight: bold; + + -webkit-box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + -moz-box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); +} +#menu { + margin: 0; + padding: 0; + line-height: 1; + width: 100%; + height: 4em; + + -webkit-box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + -moz-box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + + background: #8b8b8b; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#a9a9a9', endColorstr='#7a7a7a'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#a9a9a9), to(#7a7a7a)); /* for webkit browsers */ + background: -moz-linear-gradient(top, #a9a9a9, #7a7a7a); /* for firefox 3.6+ */ + + border-bottom: solid 0.1em #6d6d6d; + + z-index: 2; + position: fixed; + top: 0; + overflow: visible; +} +#nav { + position: relative; + z-index: 1001; + overflow: visible; +} +#nav li { + margin: 0 0 0 1em; + padding: 0.75em 0; + float: left; + position: relative; + list-style: none; +} +#nav li li.separate { + border-bottom: 0.1em solid #b4b4b4; +} +#nav li li.separate+li { + border-top: 0.1em solid #fff; +} +/* main level link */ +#nav a, #nav li > span { + position: relative; + line-height: 1.5; + font-weight: bold; + color: #e7e5e5; + text-decoration: none; + display: block; + margin: -0.1em 0; + padding: 0.5em 1.5em; + -webkit-border-radius: 1.5em; + -moz-border-radius: 1.5em; + border-radius: 1.5em; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.5); +} +/* main level link hover */ +#nav .current > a, #nav li:hover > a, +#nav .current > span, #nav li:hover > span { + background: #d1d1d1; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb', endColorstr='#a1a1a1'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#a1a1a1)); /* for webkit browsers */ + background: -moz-linear-gradient(top, #ebebeb, #a1a1a1); /* for firefox 3.6+ */ + + color: #444; + border-top: solid 0.1em #f8f8f8; + -webkit-box-shadow: 0 0.1em 0.1em rgba(0,0,0,0.2); + -moz-box-shadow: 0 0.1em 0.1em rgba(0,0,0,0.2); + box-shadow: 0 0.1em 0.1em rgba(0,0,0,0.2); + text-shadow: 0 0.1em 0.1em rgba(255,255,255,1); +} +/* sub levels link hover */ +#nav ul li:hover a, #nav li:hover li a, +#nav ul li:hover>span, #nav li:hover li>span { + background: none; + border: none; + color: #666; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + margin: 0; +} +#nav ul a:hover, #nav ul li>span:hover { + background: #0399d4 !important; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#04acec', endColorstr='#0186ba'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#04acec), to(#0186ba)) !important; /* for webkit browsers */ + background: -moz-linear-gradient(top, #04acec, #0186ba) !important; /* for firefox 3.6+ */ + + color: #fff !important; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.1); +} +/* level 2 list */ +#nav ul { + background: #ddd; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#cfcfcf'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#cfcfcf)); /* for webkit browsers */ + background: -moz-linear-gradient(top, #fff, #cfcfcf); /* for firefox 3.6+ */ + + z-index: 100; + display: none; + margin: 0; + padding: 0; + width: 18em; + position: absolute; + top: 3.75em; + left: 0; + border: solid 0.1em #b4b4b4; + -webkit-border-radius: 0.5em; + -moz-border-radius: 0.5em; + -o-border-radius: 0.5em; + border-radius: 0.5em; + -webkit-box-shadow: 0 0.16em 0.5em rgba(0,0,0,0.3); + -moz-box-shadow: 0 0.16em 0.5em rgba(0,0,0,0.3); + box-shadow: 0 0.16em 0.5em rgba(0,0,0,0.3); +} +#nav ul ul { + max-height: 30.16em; + overflow-y: auto; + overflow-x: visible; +} +#nav ul.full { + width: 27em; +} + +/* @group widgets */ + +#nav li>a>span, #nav li>span>span { + position: absolute; + top: 0; + left: 0; + width: 1.2em; + height: 100%; + text-indent: -9999em; + background: transparent url(../images/move_dk.png) no-repeat 50% 50%; + background-size: 1.2em; + display: none; +} +#nav li>a>span.delete, #nav li>span>span.delete { + background-image: url(../images/delete_dk.png); + background-size: 1.5em; + width: 1.5em; + left: auto; + right: 0.5em; +} +#nav li:hover>a>span, #nav li:hover>span>span { + display: block; +} + +/* @end */ + +/* @group dropdown */ + +#nav li:hover > ul { + display: block; +} +#nav ul li { + float: none; + margin: 0; + padding: 0; +} +#nav ul a, #nav ul li>span { + font-weight: normal; + text-shadow: 0 0.1em 0.1em rgba(255,255,255,0.9); +} + +/* @end */ + +/* @group level 3+ list */ + +#nav ul ul { + left: 17.5em; + top: -0.1em; +} +/*#nav ul ul.full { + left: 35.5em; +}*/ + +/* @end */ + +/* @group rounded corners for first and last child */ + +#nav ul li:first-child > a { + -webkit-border-top-left-radius: 0.5em; + -moz-border-radius-topleft: 0.5em; + border-top-left-radius: 0.5em; + -webkit-border-top-right-radius: 0.5em; + -moz-border-radius-topright: 0.5em; + border-top-right-radius: 0.5em; +} +#nav ul li:last-child > a { + -webkit-border-bottom-left-radius: 0.5em; + -moz-border-radius-bottomleft: 0.5em; + border-bottom-left-radius: 0.5em; + -webkit-border-bottom-right-radius: 0.5em; + -moz-border-radius-bottomright: 0.5em; + border-bottom-right-radius: 0.5em; +} + +/* @end */ + +/* @end */ + +/* @group button */ + + +/* button +---------------------------------------------- */ +button[disabled], button[disabled]:hover, button[disabled]:active { + opacity: 0.25; + top: 0; + color: #e9e9e9; + border: solid 0.1em #555; + background: #6e6e6e; + background: -webkit-gradient(linear, left top, left bottom, from(#888), to(#575757)); + background: -moz-linear-gradient(top, #888, #575757); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#888888', endColorstr='#575757'); +} +.button { + display: inline-block; + zoom: 1; /* zoom and *display = ie7 hack for display:inline-block */ + *display: inline; + vertical-align: top; + margin: 0 0 0 0.5em; + padding: 0.5em 1.5em; + + cursor: pointer; + text-align: center; + text-decoration: none; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.3); + -webkit-border-radius: 0.5em; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + -webkit-box-shadow: 0 0.1em 0.2em rgba(0,0,0,0.5); + -moz-box-shadow: 0 0.1em 0.2em rgba(0,0,0,0.5); + box-shadow: 0 0.1em 0.2em rgba(0,0,0,0.5); + border-width: 0.1em !important; + + line-height: 1.25; /* Cannot modify because FF doesn't allow override on button element */ + color: #fff !important; +} +.button:hover { + text-decoration: none; +} +.button:active { + position: relative; + top: 0.1em; +} +.bigrounded { + -webkit-border-radius: 2em; + -moz-border-radius: 2em; + border-radius: 2em; +} +.small { + font-size: 0.85em; + padding: 0.3em 1em; +} + +/* color styles +---------------------------------------------- */ + +/* black */ +.black { + color: #d7d7d7; + border: solid 0.1em #333; + background: #333; + background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#000)); + background: -moz-linear-gradient(top, #666, #000); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666', endColorstr='#000000'); +} +.black:hover { + background: #000; + background: -webkit-gradient(linear, left top, left bottom, from(#444), to(#000)); + background: -moz-linear-gradient(top, #444, #000); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#444444', endColorstr='#000000'); +} +.black:active { + color: #666; + background: -webkit-gradient(linear, left top, left bottom, from(#000), to(#444)); + background: -moz-linear-gradient(top, #000, #444); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#000000', endColorstr='#666666'); +} + +/* gray */ +.gray { + color: #e9e9e9; + border: solid 0.1em #555; + background: #6e6e6e; + background: -webkit-gradient(linear, left top, left bottom, from(#888), to(#575757)); + background: -moz-linear-gradient(top, #888, #575757); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#888888', endColorstr='#575757'); +} +.gray:hover { + background: #616161; + background: -webkit-gradient(linear, left top, left bottom, from(#757575), to(#4b4b4b)); + background: -moz-linear-gradient(top, #757575, #4b4b4b); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#757575', endColorstr='#4b4b4b'); +} +.gray:active { + color: #afafaf; + background: -webkit-gradient(linear, left top, left bottom, from(#575757), to(#888)); + background: -moz-linear-gradient(top, #575757, #888); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#575757', endColorstr='#888888'); +} + +/* orange */ +.orange { + color: #fef4e9; + border: solid 0.1em #da7c0c; + background: #f78d1d; + background: -webkit-gradient(linear, left top, left bottom, from(#faa51a), to(#f47a20)); + background: -moz-linear-gradient(top, #faa51a, #f47a20); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#faa51a', endColorstr='#f47a20'); +} +.orange:hover { + background: #f47c20; + background: -webkit-gradient(linear, left top, left bottom, from(#f88e11), to(#f06015)); + background: -moz-linear-gradient(top, #f88e11, #f06015); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f88e11', endColorstr='#f06015'); +} +.orange:active { + color: #fcd3a5; + background: -webkit-gradient(linear, left top, left bottom, from(#f47a20), to(#faa51a)); + background: -moz-linear-gradient(top, #f47a20, #faa51a); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f47a20', endColorstr='#faa51a'); +} + +/* red */ +.red { + color: #faddde; + border: solid 0.1em #980c10; + background: #d81b21; + background: -webkit-gradient(linear, left top, left bottom, from(#ed1c24), to(#aa1317)); + background: -moz-linear-gradient(top, #ed1c24, #aa1317); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ed1c24', endColorstr='#aa1317'); +} +.red:hover { + background: #b61318; + background: -webkit-gradient(linear, left top, left bottom, from(#c9151b), to(#a11115)); + background: -moz-linear-gradient(top, #c9151b, #a11115); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c9151b', endColorstr='#a11115'); +} +.red:active { + color: #de898c; + background: -webkit-gradient(linear, left top, left bottom, from(#aa1317), to(#ed1c24)); + background: -moz-linear-gradient(top, #aa1317, #ed1c24); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#aa1317', endColorstr='#ed1c24'); +} + +/* blue */ +.blue { + color: #d9eef7; + border: solid 0.1em #0076a3; + background: #0095cd; + background: -webkit-gradient(linear, left top, left bottom, from(#00adee), to(#0078a5)); + background: -moz-linear-gradient(top, #00adee, #0078a5); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00adee', endColorstr='#0078a5'); +} +.blue:hover { + background: #007ead; + background: -webkit-gradient(linear, left top, left bottom, from(#0095cc), to(#00678e)); + background: -moz-linear-gradient(top, #0095cc, #00678e); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0095cc', endColorstr='#00678e'); +} +.blue:active { + color: #80bed6; + background: -webkit-gradient(linear, left top, left bottom, from(#0078a5), to(#00adee)); + background: -moz-linear-gradient(top, #0078a5, #00adee); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0078a5', endColorstr='#00adee'); +} + +/* rosy */ +.rosy { + color: #fae7e9; + border: solid 0.1em #b73948; + background: #da5867; + background: -webkit-gradient(linear, left top, left bottom, from(#f16c7c), to(#bf404f)); + background: -moz-linear-gradient(top, #f16c7c, #bf404f); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f16c7c', endColorstr='#bf404f'); +} +.rosy:hover { + background: #ba4b58; + background: -webkit-gradient(linear, left top, left bottom, from(#cf5d6a), to(#a53845)); + background: -moz-linear-gradient(top, #cf5d6a, #a53845); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cf5d6a', endColorstr='#a53845'); +} +.rosy:active { + color: #dca4ab; + background: -webkit-gradient(linear, left top, left bottom, from(#bf404f), to(#f16c7c)); + background: -moz-linear-gradient(top, #bf404f, #f16c7c); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#bf404f', endColorstr='#f16c7c'); +} + +/* green */ +.green { + color: #e8f0de; + border: solid 0.1em #538312; + background: #64991e; + background: -webkit-gradient(linear, left top, left bottom, from(#7db72f), to(#4e7d0e)); + background: -moz-linear-gradient(top, #7db72f, #4e7d0e); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#7db72f', endColorstr='#4e7d0e'); +} +.green:hover { + background: #538018; + background: -webkit-gradient(linear, left top, left bottom, from(#6b9d28), to(#436b0c)); + background: -moz-linear-gradient(top, #6b9d28, #436b0c); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#6b9d28', endColorstr='#436b0c'); +} +.green:active { + color: #a9c08c; + background: -webkit-gradient(linear, left top, left bottom, from(#4e7d0e), to(#7db72f)); + background: -moz-linear-gradient(top, #4e7d0e, #7db72f); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4e7d0e', endColorstr='#7db72f'); +} + +/* @end */ + +/* @group buttons */ + +.buttons { + display: table; + width: 100%; + text-align: right; + clear: both; + margin-top: 1em; +} +.buttons>span { + display: table-cell; + white-space: nowrap; + vertical-align: top; +} +.buttons>span span { + text-align: left; + text-indent: -9999em; + height: 2.5em; + border-width: 0.1em !important; +} +.buttons span.progress { + background-color: #bbb; + width: 100%; +} +.buttons>span.progress span { + display: block; +} +.buttons>span span.status { + text-indent: 0.5em; + line-height: 2.5; + margin-bottom: -2.5em; + color: #fff; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.3); +} +.buttons>span span.spinner { + display: inline-block; + width: 2.5em; + background: transparent url(../images/spinner.gif) no-repeat 100% 50%; + background-size: 2em; +} + +/* @end */ diff --git a/site/htdocs/css/admin-ui/extra.css b/site/htdocs/css/admin-ui/extra.css new file mode 100644 index 00000000..1740e167 --- /dev/null +++ b/site/htdocs/css/admin-ui/extra.css @@ -0,0 +1,119 @@ +/* functions not covered by Adrian's base classes. + I don't want to modify the original, since he'll make refinements. +*/ +.bse_modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; + padding: 0; + background-color: rgba(0,0,0,0.75); + z-index: 3; + overflow: visible; + height: 100%; + width: 100%; +} + +#base_work { + color: #FFF; +} + +.buttons>span span.spinner { + background: transparent url(/images/admin/ui/spinner.gif) no-repeat 100% 50%; +} + +#nav li>span>span.item { + display: block; + text-indent: 0em; + position: static; + width: auto; +} + +#nav li>span>span.move { + background: transparent url(/images/admin/ui/move_dk.png) no-repeat 50% 50%; + +} + +#nav li>span>span.delete { + background-image: url(/images/admin/ui/delete_dk.png); +} + +/* undo the CSS popup styling */ +#nav ul, #nav li>a>span, #nav li>span>span { + display: block; +} + +.bse_field_error { + margin-top: 2em; + margin-left: -25em; + color: #F00; +} + +.bse_image_field img.display { + width: 80px; + height: 80px; + border: 1px solid #CCC; + float: right; +} + +div.window fieldset.bse_image_field > input[type=file] { + width: auto; +} + +div.window fieldset.bse_image_field > div.more { + clear: both; + display: block; +} + +div.window fieldset.bse_image_gallery div.bse_gallery_imagelist { + border: 1px solid #808080; + display: block; + height: 102px; +} + +div.window fieldset.bse_image_gallery div.bse_gallery_imagelist .bse_gallery_image, +div.window fieldset.bse_image_gallery div.bse_gallery_imagelist .bse_drop_target { + width: 81px; + height: 100px; + float: left; + border-right: 1px solid #808080; + position: relative; +} + +.bse_gallery_image { + background-color: #FFF; +} + +.bse_drop_target { + background-color: #CFC; + font-weight: bold; + padding-top: 40px; + text-align: center; +} + +div.window fieldset.bse_image_gallery div.bse_gallery_imagelist > .bse_gallery_image .name { + width: 80px; + position: absolute; + top: 80px; + display: block; + text-align: center; + overflow: hidden; + text-size: 70%; +} + +div.window fieldset.bse_image_gallery div.bse_gallery_imagelist > .bse_gallery_image .delete { + top: 0px; + right: 0px; + position: absolute; + background-color: #CCC; +} + +div.window fieldset.bse_image_gallery div.bse_gallery_imagelist > .bse_gallery_image .edit { + top: 1.5em; + right: 0px; + position: absolute; + background-color: #CCC; +} + diff --git a/site/htdocs/css/admin-ui/reset.css b/site/htdocs/css/admin-ui/reset.css new file mode 100644 index 00000000..99a02116 --- /dev/null +++ b/site/htdocs/css/admin-ui/reset.css @@ -0,0 +1 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0} \ No newline at end of file diff --git a/site/htdocs/css/bse_adminui.css b/site/htdocs/css/bse_adminui.css new file mode 100644 index 00000000..d018abb5 --- /dev/null +++ b/site/htdocs/css/bse_adminui.css @@ -0,0 +1,191 @@ +#debug_log { +} + +body { + font: 12px Arial; + margin: 0px; + background-color: #E0E0E0; +} + +.bse_modal { + position: fixed; + top: 0px; + background-image: url(/images/50.png); + width: 100%; + height: 20em; +} + +.bse_dialog .bse_title { + text-align: center; + font-weight: bold; + border-bottom: 1px solid #8080FF; +} + +.bse_dialog .bse_alert { + background-color: #F00; + color: #FFF; +} + +.bse_dialog { + position: absolute; + background-color: #F0F0FF; + padding: 5px; + border: 2px solid #FFF; +} + +.bse_dialog .bse_required label:after { + content: "*"; +} + +.bse_dialog .bse_invalid { + background-color: #FF8080; +} + +.bse_error { + padding: 0px 3px; + font-weight: bold; + color: red; +} + +.bse_dialog input, +.bse_dialog textarea, +.bse_dialog select { + +} + +.bse_field_wrapper{ + margin-left: 200px; + margin-top: 10px; +} + +.bse_dialog label { + margin-left: -190px; + float: left; +} + +#base_wrapper { + width: 1010px; + border-left: 5px solid white; + border-right: 5px solid white; + margin-left: auto; + margin-right: auto; + background-color: #F0F0FF; +} + +#base_menubar { + background-color: #E0E0FF; + border-bottom: 1px solid #8080FF; + /*position: fixed; + width: 1000px; */ +} + +#base_menu_wrapper { + position: relative; + background-color: #C0C0FF; + width: 150px; + float: left; +} + +#base_menu { + display: none; + background-color: #C0C0FF; + position: absolute; + top: 2em; + left: 1px; + /*border-top: 1px solid #000;*/ +} + +#base_menu_wrapper:hover #base_menu { + display: block; +} + +#base_menu_current { + padding: 0.5em 1em; + white-space: nowrap; + overflow: hidden; + width: 150px; + font-weight: bold; + cursor: pointer; +} + +#base_menu a, +#base_logon_menu a { + display: block; + padding: 5px 1em; + /*border-bottom: 1px solid #000; + border-left: 1 px solid #000; + border-right: 1px solid #000;*/ + text-decoration: none; + font-weight: bold; + white-space: nowrap; + width: 150px; + background-position: 150px; + background-repeat: no-repeat; +} + +#base_menu a:hover, +#base_logon_menu a:hover { + background-color: #E0E0FF; +} + +#base_menu_item_debug { + background-image: url(/images/admin/ui/debug.png); +} + +#base_menu_item_menu { + background-image: url(/images/admin/ui/menu.png); +} + +#base_change_password { + background-image: url(/images/admin/ui/changepw.png); +} + +#base_logon_wrapper { + position: relative; + background-color: #C0C0FF; + float: right; +} + +#base_logon { + display: none; + font-weight: bold; + background-color: #C00000; + color: #FFFFFF; + float: right; + padding: 0.5em 1em; + cursor: pointer; +} + +#base_logon_menu { + display: none; + background-color: #C0C0FF; + position: absolute; + right: 0px; + top: 100%; +} + +#base_logon_wrapper:hover #base_logon_menu { + display: block; +} + +.clearer { clear: both; } + +/* hide the various page components until initialized */ +#base_wrapper.hide #base_menubar, +#base_wrapper.hide #base_work { + display: none; +} + +#base_loading { + text-align: center; + font: bold 24px Arial; + padding-top: 10em; + padding-bottom: 10em; + color: #8080FF; +} + +#base_messages div.message { + background-color: #C0C0C0; + padding: 2px 5px; +} + diff --git a/site/htdocs/images/admin/ui/changepw.png b/site/htdocs/images/admin/ui/changepw.png new file mode 100644 index 0000000000000000000000000000000000000000..09bc58d14d371c6524d64fb4aca284e21c6e44f9 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^LO?9Y!3HERME&^*q!^2X+?^QKos)S9l0&L?o))$}OhQ`UN`wqJRjvi> z%IBW2)1cPx#24YJ`L;(K){{a7>y{D4^000McNliru+X5UA9tVOU^alU{0isDnK~y-)t<=wp zjbRiA@Xv8?3Dcn1%*Fx}t5M31Mr3T1(Ja)gEZB^lWaBT8g`{loH~29d8+Nh~rNod7 zvr%Nm%t*tXdu*ONy}jcP8ksQhood3?uH?86nz75`LMA{@sT%;PymaSomj^1+SVqFN5nWr@EI2}^W)n1OL<2ygT>O|RwbKu`7Yolrf?k}FjnF< zaH)_?7UGS#Q7bJ=2G#OU;voi_h3yT-GIO?+(HVJtE!Hmc-H4dZ%vLiqTew#PtVYDQ zj$016!7~^s*oo5U%iV~s;Xd{j z863c~3elZf#@mXl8dZq|_G739K3tUl4m&w^F34Ca{c2lxUmL0?`> z%_zJhsnCY1s%zu%*o19?d*+`48e%$P=djQIRmg+eHh2Yo0xI+XMr^%@O`wAr@C)oy zoNB0IMz1$cZv@}K0Q_c!Y{mog4cMA#a!=27_Lq zQ8JnIaod?dnm9F+J4z%Hn$W1*?SAz6d|P~WH%(;CB#qv++wC24P1=e?B4_!0{(Z4n zyr$Z`gIq3m$zRRvUAdb=p|Bi_#f~Qr{1d?u_>j$J^+u!d-6^`urDQUh`C6^^$1se8 zN1q5rJ3_!Hm&Q@;@kEaFB-{|)dn_bng|FR=E;`9JzAzyRi_D>+?E&w~H} N002ovPDHLkV1mcfBANgI literal 0 HcmV?d00001 diff --git a/site/htdocs/images/admin/ui/menu.png b/site/htdocs/images/admin/ui/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..386e09c7d759421fa1cdfd5028355917bd286a5c GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^LO?9Y!3HERME&^*q!^2X+?^QKos)S9l>Eak-aeC`yN3H_~94zLwKmXNKZ4y_^ zy~^Qe#u%x-OJ(6gh6%Y0M}ICp%u^V7)_>uu$rmO?Ebg(Y3CgzS+qV90by7&F+4+n^ v7uDVJEqPO(Uq5H?%IDjkb*0u6{1-oD!M^ literal 0 HcmV?d00001 diff --git a/site/htdocs/images/admin/ui/move_dk.png b/site/htdocs/images/admin/ui/move_dk.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2aab8c50598a750e06cbb9d082418ae6deadd3 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM6!3HE{=IMI@DajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MID|ljv*Dd?o2t$dq9Dswb6p@@RrSG6Z9_IzFDf4!B{`Z z>{$8aJ?|N892ssbi1#sA;qM^FXnCxn!oX6X!XPqW66@roD$mvIKOK(zHJq3Fc6zA~ z8{66p-jtmdKI;Vst0B}H51poj5 literal 0 HcmV?d00001 diff --git a/site/htdocs/images/admin/ui/spinner.gif b/site/htdocs/images/admin/ui/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..46025beae75de68d45d89907485b8187877ea283 GIT binary patch literal 2545 zcma*pX;2jD83*u}zPqQp=@E66m}KZ_mSFGF6B^^GwTd9 zz`z_FatMMBhdNj&f(k@YLBPfJVs(vL6>Hbk59;P9ll`!!D%tdCQk4&+YTwWOR=>Y~ z|IgD;j9wSEEOilB1bzVE<;#}rhXm+Znam-8Rq0z;>g|db&PMa3Eg@v7 zd4<&zU)AKr5R=C(&tgF;fV;ZF8(H?XK3X-UGf#S?ZYz#4sSS^Q;WI^kvpzg<@xtng z4cYar-W%6cn3NDok|<1!OIc zjX0lsDj{S04kbcc0d;o6pqi$Q&_s+rlA-V3y5Kq}OkO#PWvhMWBAjy*##JH#DOu91 za`hC*dY;?jduMw#Nt|B({BkEz@R=p~9uXa?s-IU~MLx8wh*e8{2006c<^)P}IU>~{ zYfwyORgJWy8H9LE309P1UQ=z6Q?z*0$ZDnp!5FK(&eE9ITyrd=>KFsjyNyj|#4oJ+ za2de-qCkF1Hpsom4*)87w5%zm7^=szOU6PVXd)`$d`Qnoo;x6Q;W6w&aF z;o&15G6(TVuR{ID3ls8rC5<$VDm0`N&-k=h$>hX2{1JktiO4txLqto%;pVy{?HzR| z1i1Dj&rnc{vJLCQAjlHehS-^lo50YitDQ)({aU0g<|^oucW%OFFV(f@wr4(y3$Q-{ zop*k7=+?KbHHkNXRm9}9Byv4I=id^$WhygDmjCkuwwl+@VU@F5OBJ`@8SS&(H^~6dIn&T12vl8 zYfVEY%}`DM=!E~}@$M@#a|15A0Zv7FySFe|Kjm~VQ8sN5MJ~6Vty1^}3k$DFjXvNi zsEz}^WIF550wV|r-@<$g;yI#m9bWPrru`DSpV8$0tQ8O9(>LT~& zuTs-E%i=U6R)(^^vB`4mcxD?9*BboA3o8h*p7e)B9Vdsa8JG$5$~jh=cv-0J;%x$T zsXqd{jdc53Q>;B-@4L6Aj0vi~bVvBoQaN|7F1RHcF1>xJP*H!w>9D8RXti>Br4{m! z)O#(aD!-YL#ap)Vx{1EVKO;k4De<#`XG{-f(-wqsNfW#=_;`6_F7KmYXk@gu!SJ%# zA5J%zLyAjwMG_>AlLS+Z)u_eVsBCLvn&d?#Yb5L*Unb80i^SxuJ9ntaB-)eRxK*{c z129rg0WpO*0TzKvIkR8V7C=c^7qaUv;G`pJ(+%OX_OVXiIWu*}4M|y)-p=-R*So^m zXf<^`Ry7+&CYZSWH3(g?RuLS#&gB7nc-_yI!wY6-YUWq2w$a99p$F2tbT&{*bvvt37Z0+kt$A+qVBi@=6|I_u;)+4SK5ie{M{OF4+#6~ zKYvyD>c@m0`=4&kcQ+@hZ1?9%Lxs7-ZckLgdo%oLl50xOJs>tkBt>lV&1-x6L^#Fr^2n2;ljbZISudP^j|}N zIEyIfO+DrY>2HHNEG>VpIVc*^TsKR<{O(oDio5wseGB753FOHyyIv=j-vobo9Lp(& zbPs{|z^{+)8nqcxR9t8=R;n!j+;Ffa#;huoQ@E7W5h$I>iRJB3i9$*aNm(vRlE%~_0+w0KU&`TsDs zuu#?_aR6C^*pVRj0OGK$76*{pNU5-oyV8Mdd>vaF9q-5Qc>ybycvb}shPISkkxo7f zE6k|snBSTo&{!~ev}xTxep0^gONW>O@kdu-cF!M%CjNKLa*QK)MwXO8;%HLlU%MY6 zC4m4Y_MZuFSr!4O1G*Pz1ERL6DYTQQPKHL*D7uyIWT5_I=+)6qfI;UHN2bb;Tpo(_ L0n6P3)VuX>B&!xt literal 0 HcmV?d00001 diff --git a/site/htdocs/js/admin-ui/debug.js b/site/htdocs/js/admin-ui/debug.js new file mode 100644 index 00000000..e285d5d9 --- /dev/null +++ b/site/htdocs/js/admin-ui/debug.js @@ -0,0 +1,34 @@ +var BSEDebugUI = Class.create +(BSEUIBase, +{ + start: function(ui, div, args) { + div.innerHTML = ""; + this._log = new Element("div", { id: "debug_log" }); + div.appendChild(this._log); + this._load_log(ui); + this.display(ui, div); + }, + display: function(ui, div) { + this._timer = setInterval(this._load_log.bind(this, ui), 1000); + }, + undisplay: function(ui, div) { + clearInterval(this._timer); + }, + needed_content: function(ui, args) { + return { }; + }, + _load_log: function(ui) { + this._log.innerHTML = ""; + for (var i = 0; i < ui._log.length; ++i) { + var entry = new Element("div"); + entry.appendChild(document.createTextNode(ui._log[i])); + this._log.appendChild(entry); + } + } +}); + +ui.register({ + name: "debug", + object: new BSEDebugUI(), + logon: false +}); \ No newline at end of file diff --git a/site/htdocs/js/admin-ui/menu.js b/site/htdocs/js/admin-ui/menu.js new file mode 100644 index 00000000..65fa8e80 --- /dev/null +++ b/site/htdocs/js/admin-ui/menu.js @@ -0,0 +1,17 @@ +var BSEMenuUI = Class.create +(BSEUIBase, +{ + start: function(ui, div, args) { + div.innerHTML = "There will be a menu here"; + }, + display: function(ui, div) { + }, + needed_content: function(ui, args) { + return { menu: "/admin/ui/menu.html" }; + } +}); + +ui.register({ + name: "menu", + object: new BSEMenuUI +}); \ No newline at end of file diff --git a/site/htdocs/js/bse_adminui.js b/site/htdocs/js/bse_adminui.js new file mode 100644 index 00000000..3275ef9a --- /dev/null +++ b/site/htdocs/js/bse_adminui.js @@ -0,0 +1,509 @@ + +var base_logon_fields = + [ + { + name: "logon", + label: "Logon", + required: true + }, + { + name: "password", + label: "Password", + required: true, + type: "password" + } + ]; + +var BSEAdminUI = Class.create({ + initialize: function() { + this._log = []; + this.api = new BSEAPI({onConfig: this._post_start.bind(this)}); + this._messages = new BSEAdminUI.Messages("message"); + this._modules = new Hash(); + this._loaded_scripts = new Hash(); + this._menubar = new BSEMenuBar({}); + }, + register: function (options) { + var mod = this._modules.get(options.name); + if (mod) { + mod.object = options.object; + mod.options = Object.extend( + Object.extend({}, this.module_defaults()), + options); + } + else { + this._log_entry("Attempt to register unknown module " + options.name); + } + }, + module_defaults: function() { + return { + logon: true + }; + }, + add_menu: function(name, menu) { + var mod = this._modules.get(name); + if (mod) { + this._menubar.add_menu(menu); + mod.menus.push(menu); + if (this.current === mod) { + $("nav").appendChild(menu.element()); + menu.inDocument(); + } + } + else { + this._log_entry("Attempt to register menu with unknown module " + name); + } + }, + // menu_item: function(options) { + // options = Object.extend(Object.extend({}, BSEAdminUI.MenuDefaults), options); + // this.handlers.set( + // options.name, + // { + // options: options, + // key: options.name, + // value: options.object, + // started: false, + // div: null, + // suboptions: new Hash + // }); + // }, + // submenu_item: function(options) { + // options = Object.extend(Object.extend({}, BSEAdminUI.MenuDefaults), options); + // this.handlers.get(options.parent). + // set(options.name, + // { + // options: options, + // key: options.name, + // value: options.object, + // started: false, + // div: null + // }); + // }, + //start: function() {}, + //_bind_static_events: function() { + // $("base_logon").observe("click", this._do_logoff.bindAsEventListener(this)); + // $("base_change_password").observe("click", this._do_changepw.bindAsEventListener(this)); + //}, + _post_start: function() { + if (this.api.conf.access_control != 0) + this._make_logon_menu(); + // each line is + // text;script;sortorder + var ui_conf = this.api.conf.admin_ui; + var menu_items = []; + for (var i in ui_conf) { + var entry = ui_conf[i].split(/;/); + menu_items.push({ + id: "base_menu_item_" + i, + text: entry[0], + _order: entry[2], + onClick: this._select.bind(this, { select: i, rest: ""}) + }); + this._modules.set(i, { + title: entry[0], + script: entry[1], + name: i, + loaded: false, + div: null, + object: null, + menus: [] + }); + } + menu_items.sort(function(a, b) { + return a._order < b._order ? -1 : a._order > b._order ? 1 : 0; + }); + + this._menu_items = menu_items; + this._main_menu = new BSEMenu({ + title: menu_items[0].text, + current: true, + items: menu_items + }); + $("nav").appendChild(this._main_menu.element()); + this._menubar.add_menu(this._main_menu); + + var sel = this._parse_frag(window.location.hash); + this._select(sel); + }, + _make_logon_menu: function() { + this._logon_menu = new BSEMenu({ + title: "(none)", + current: true, + items: [ + { + id: "base_menu_user_logout", + text: "Logoff", + onClick: this._do_logoff.bind(this) + }, + { + id: "base_menu_user_changepw", + text: "Change password", + onClick: this._do_changepw.bind(this) + } + ] + }); + $("nav").appendChild(this._logon_menu.element()); + this._menubar.add_menu(this._logon_menu); + }, + // _finish_load: function() { + // this._log_entry("Scripts loaded, proceeding"); + // this._order_handlers(); + // this._load_menu(); + // var sel = this._parse_frag(window.location.hash); + // this._select(sel); + // $("base_wrapper").removeClassName("hide"); + // $("base_loading").style.display = "none"; + // }, + // _load_scripts: function() { + // var ui_conf = this.api.conf.admin_ui; + // var to_load = new Array; + // for (var i in ui_conf) { + // to_load.push(ui_conf[i]); + // } + // this._log_entry("Loading configured scripts " + to_load.join(" ")); + // new BSELoader({ scripts: to_load, + // onLoaded: this._finish_load.bind(this) }); + // }, + load_css: function(css) { + css.each(function (e) { + var sty = new Element("link", { rel: "stylesheet", type: "text/css", href: e }); + var head = $$("head")[0]; + head.appendChild(sty); + }); + }, + // _order_handlers: function() { + // // make an ordered list of the registered handlers + // // first a name / object list + // var list = this.handlers.values(); + // list.sort( + // function (a, b) { + // var aord = a.options.order; + // var bord = b.options.order; + // return aord < bord ? -1 : aord > bord ? 1 : 0; + // }); + // this.ordered = list; + // }, + // _load_menu: function() { + // var menu = $("base_menu"); + // menu.innerHTML = ""; + // this.ordered.each( + // function(menu, e) { + // var a = new Element("a", { href: "#" + e.key, id: "base_menu_item_"+ e.key }); + // a.update(e.options.text); + // a.observe("click", function(e, event) { + // this._select({ select: e, rest: ""}); + // event.stop(); + // return false; + // }.bind(this, e)); + // menu.appendChild(a); + // }.bind(this, menu) + // ); + // }, + // parse location (or the default "menu") to find something to display + _parse_frag: function(frag) { + if (!frag) frag = "#menu"; + frag = frag.replace(/^\#/, ''); + var m = /^([a-z0-9]+)(?:\/(.*))?$/.exec(frag); + if (m && + this._modules.get(m[1]) != null) { + var rest = m[2] == null ? "" : m[2]; + + return { select: m[1], rest: rest }; + } + else { + return { select: "menu", rest: "" }; + } + }, + // make something active, requiring a logon if the view + // requires it, which most do + _select: function(what) { + var mod = this._modules.get(what.select); + if (mod == null) { + this._log_entry("attempt to select unknown " + what.select); + return; + } + if (!mod.loaded) { + var loader = new BSELoader({ + scripts: [ mod.script ], + onLoaded: function(what, mod) { + mod.loaded = true; + if (mod.object) { + this._select(what); + } + else { + this._log_entry("Loaded " + what.select + " but no object registered"); + } + }.bind(this, what, mod) + }); + } + if (!mod.object) + return; + if (mod.options.logon + && this.api.conf.access_control != 0) { + if (this._userinfo) { + if (this._userinfo.user) { + this._do_select(what); + } + else { + this._do_logon_and_select(what); + } + } + else { + // get the user info + this.api.userinfo( + { + onSuccess: function(what, result) { + this._userinfo = result; + if (this._userinfo.user) + this._show_current_logon(); + // try again + this._select(what); + }.bind(this, what) + } + ); + } + } + else { + this._do_select(what); + } + }, + _select_none: function() { + if (this.current) { + this.current.menus.each(function(menu) { + menu.element().remove(); + }); + this.current.object.undisplay(this, this.current.div); + this.current.div.style.display = "none"; + this.current = null; + } + }, + _do_logon_and_select: function(what) { + new BSEDialog({ + onSubmit: this._on_logon_submit.bind(this, what), + fields: [ + { + type: "fieldset", + legend: "Logon", + fields: base_logon_fields, + }, + ], + modal: true, + title: "Administration", + submit: "Logon", + submit_class: "blue" + }); + }, + _on_logon_submit: function(what, dlg) { + this.api.logon({ + logon: dlg.field("logon").value(), + password: dlg.field("password").value(), + onSuccess: function(what, dlg, user) { + this._userinfo.user = user; + this._show_current_logon(); + dlg.close(); + this._select(what); + this.message(user.logon + " successfully logged on"); + }.bind(this, what, dlg), + onFailure: function(dlg, result) { + dlg.bse_error(result); + }.bind(this, dlg) + }); + }, + // inner make something active + _do_select: function(what) { + if (this.current) + this._select_none(); + var mod = this._modules.get(what.select); + if (mod.started) { + mod.object.display(this, what.select.div, what.rest); + mod.div.style.display = "block"; + } + else { + var id = mod.name.replace(/\W+/g, "-"); + mod.div = new Element("div", { id: "app_"+id }); + mod.object.start(this, mod.div, what.rest); + $("base_work").appendChild(mod.div); + mod.started = true; + this._log_entry("Started "+mod.title); + } + mod.menus.each(function(menu) { + $("nav").appendChild(menu.element()); + menu.inDocument(); + }); + this.current = mod; + this._main_menu.setText(mod.title); + }, + _log_entry: function(text) { + var now = new Date; + this._log.push(now.toISOString() + " " + text); + if (this._log.length > 1000) + this._log.shift(); + }, + _show_current_logon: function() { + if (this._userinfo.user) { + var user = this._userinfo.user; + if (/\S/.test(user.name)) + this._logon_menu.setText(user.name); + else + this._logon_menu.setText(user.logon); + } + else { + this._logon_menu.setText("(none)"); + } + }, + _do_logoff: function(event) { + //event.stop(); + //$("base_logon").update("Logging off..."); + this._select_none(); + this.api.logoff({ + onSuccess: function() { + this._userinfo.user = null; + this._show_current_logon(); + this._select(this._parse_frag("#menu")); + }.bind(this), + onFailure: function(result) { + this.alert(result.msg); + }.bind(this) + }); + }, + _do_changepw: function(event) { + //event.stop(); + if (!this._userinfo.user) + return; + new BSEDialog({ + fields: [ + { + name: "oldpassword", + label: "Old Password", + type: "password", + required: true + }, + { + name: "newpassword", + label: "New Password", + type: "password", + required: true + }, + { + name: "confirm", + label: "Confirm New Password", + type: "password", + rules: "confirm:newpassword", + required: true + } + ], + modal: true, + submit: "Change Password", + title: "Change Password", + cancel: true, + onSubmit: function(dlg) { + this._log_entry("Sending change password"); + this.api.change_password({ + oldpassword: dlg.field("oldpassword").value(), + newpassword: dlg.field("newpassword").value(), + onSuccess: function(dlg) { + this._log_entry("Successfully changed password"); + dlg.close(); + this.message("Password for " + this._userinfo.user.logon + " successfully changed"); + }.bind(this, dlg), + onFailure: function(dlg, result) { + dlg.bse_error(result); + }.bind(this, dlg) + }); + }.bind(this) + }); + }, + alert: function(message) { + new BSEDialog({ + fields: [ + { + type: "help", + helptext: message + } + ], + title: "Alert!", + modal: true, + submit: "Dismiss", + //submit_class: "dismiss", + onSubmit: function(dlg) { dlg.close(); } + }); + }, + message: function(text) { + this._messages.message(text); + }, + busy: function() { + }, + unbusy: function() { + } +}); + +var ui; + +var BSEUIBase = Class.create({ + undisplay: function(ui, div) {} +}); + +BSEAdminUI.MenuDefaults = + { + logon: true + }; + +document.observe( + "dom:loaded", + function() { + ui = new BSEAdminUI(); + } +); + +BSEAdminUI.Messages = Class.create({ + initialize: function(div) { + this.div = $(div); + this.div.style.display = "none"; + }, + message: function(text) { + this.div.update(text); + Effect.Appear(this.div); + setTimeout(this._msg_done.bind(this), + 5000); + }, + _msg_done: function() { + Effect.Fade(this.div); + } +}); + +// var BSEContentUI = Class.create +// (BSEUIBase, +// { +// start: function(ui, div, args) { +// div.innerHTML = "One day I'll do something"; +// }, +// display: function(ui, div) { +// }, +// needed_content: function(ui, args) { +// return { menu: "/admin/ui/menu.html" }; +// } +// }); + +// document.observe("dom:loaded", function() { +// var handler = new BSEContentUI; +// ui.menu_item({ +// name: "content", +// object: handler, +// text: "Content", +// order: "b" +// }); +// ui.menu_item({ +// name: "users", +// object: handler, +// text: "Users", +// order: "c" +// }); +// ui.menu_item({ +// name: "system", +// object: handler, +// text: "System", +// order: "d" +// }); + +// }); + diff --git a/site/htdocs/js/bse_api.js b/site/htdocs/js/bse_api.js index a493b377..736cddc8 100644 --- a/site/htdocs/js/bse_api.js +++ b/site/htdocs/js/bse_api.js @@ -15,14 +15,18 @@ var BSEAPI = Class.create this.onException = function(e) { alert(e); }; - this.onFailure = function(error) { alert(error.message); }; - this._load_csrfp(); + this.onFailure = function(error) { + alert(error.message); + }; + this._load_csrfp(parameters); this.onConfig = parameters.onConfig; - this._load_config(); + delete parameters.onConfig; + this._load_config(parameters); }, - _load_csrfp: function () { + _load_csrfp: function (param) { this.get_csrfp - ({ + (Object.extend + ({ id: -1, name: this._csrfp_names, onSuccess: function(csrfp) { @@ -33,11 +37,12 @@ var BSEAPI = Class.create // ignore this this._csrfp = null; } - }); + }, param)); }, - _load_config: function() { + _load_config: function(param) { this.get_base_config - ({ + (Object.extend + ({ onSuccess:function(conf) { this.conf = conf; if (this.onConfig) @@ -45,7 +50,7 @@ var BSEAPI = Class.create }.bind(this), onFailure: function(err) { } - }); + }, param)); }, // logon to the server // logon - logon name of user @@ -146,6 +151,38 @@ var BSEAPI = Class.create onException: this.onException }); }, + change_password: function(parameters) { + var success = parameters.onSuccess; + if (!success) this._badparm("change_password() missing onSuccess parameter"); + var failure = parameters.onFailure; + if (!failure) failure = this.onFailure; + new Ajax.Request('/cgi-bin/admin/changepw.pl', + { + parameters: { + a_change: 1, + oldpassword: parameters.oldpassword, + newpassword: parameters.newpassword, + confirm: parameters.newpassword + }, + onSuccess: function (success, failure, resp) { + if (resp.responseJSON) { + if(resp.responseJSON.success != 0) { + success(); + } + else { + failure(this._wrap_json_failure(resp), resp); + } + } + else { + failure(this._wrap_nojson_failure(resp), resp); + } + }.bind(this, success, failure), + onFailure: function (failure, resp) { + failure(this._wrap_req_failure(resp), resp); + }.bind(this, failure), + onException: this.onException + }); + }, // fetch a tree of articles; // id - parent of tree to fetch // depth - optional depth of tree to fetch (default is large) @@ -303,47 +340,7 @@ var BSEAPI = Class.create thumb_link: function(im, geoid) { return "/cgi-bin/thumb.pl?image="+im.id+"&g="+geoid+"&page="+im.articleId+"&f="+encodeURIComponent(im.image); }, - can_drag_and_drop: function() { - // hopefully they're implemented at the same time - if (window.FormData != null) - return true; - - if (bse_use_file_api && window.FileReader != null) - return true; - - return false; - }, - make_drop_zone: function(options) { - options.element.addEventListener - ( - "dragenter", - function(options, e) { - e.stopPropagation(); - e.preventDefault(); - }.bind(this, options), - false - ); - options.element.addEventListener - ( - "dragover", - function(options, e) { - e.stopPropagation(); - e.preventDefault(); - }.bind(this, options), - false - ); - options.element.addEventListener - ( - "drop", - function(options, e) { - e.stopPropagation(); - e.preventDefault(); - - options.onDrop(e.dataTransfer.files); - }.bind(this, options), - false - ); - }, + // parameters: // image - file input element (required) // id - owner article of the new image (required) @@ -964,8 +961,14 @@ var BSEAPI = Class.create _do_request: function(url, action, other_parms, success, failure) { if (action != null) other_parms[action] = 1; + var async = true; + if (other_parms.hasOwnProperty("_async")) { + async = other_parms._async; + delete other_parms._async; + } new Ajax.Request(url, { + asynchronous: async, parameters: other_parms, onSuccess: function (success, failure, resp) { if (resp.responseJSON) { @@ -1003,3 +1006,45 @@ var BSEAPI = Class.create _upload_id: 0 }); +BSEAPI.can_drag_and_drop = function() { + // hopefully they're implemented at the same time + if (window.FormData != null) + return true; + + if (bse_use_file_api && window.FileReader != null) + return true; + + return false; +}; + +BSEAPI.make_drop_zone = function(options) { + options.element.addEventListener + ( + "dragenter", + function(options, e) { + e.stopPropagation(); + e.preventDefault(); + }.bind(this, options), + false + ); + options.element.addEventListener + ( + "dragover", + function(options, e) { + e.stopPropagation(); + e.preventDefault(); + }.bind(this, options), + false + ); + options.element.addEventListener + ( + "drop", + function(options, e) { + e.stopPropagation(); + e.preventDefault(); + + options.onDrop(e.dataTransfer.files); + }.bind(this, options), + false + ); +}; \ No newline at end of file diff --git a/site/htdocs/js/bse_dialog.js b/site/htdocs/js/bse_dialog.js new file mode 100644 index 00000000..b8e08203 --- /dev/null +++ b/site/htdocs/js/bse_dialog.js @@ -0,0 +1,1170 @@ +var BSEDialog = Class.create({ + initialize: function(options) { + this.options = Object.extend( + Object.extend({}, this.defaults()), options); + this._build(); + if (this.options.dynamic_validation) + this._start_validation(); + this._show(); + }, + _reset_errors: function() { + this._fields.clear_error(); + }, + error: function(msg) { + this._reset_errors(); + this._error.update(msg); + this._error.show(); + this._error_animate(); + }, + field_errors: function(errors) { + this._reset_errors(); + if (errors.constructor == Hash) { + errors.each(function(entry) { + this._fields.set_error(entry.key, entry.value); + }.bind(this)); + } + else { + for (var i in errors) { + this._fields.set_error(i, errors[i]); + } + } + this._error_animate(); + }, + _error_animate: function() { + this.options.error_animate(this, this.div); + }, + field: function(name) { + return this._values.get(name); + }, + bse_error: function(error) { + if (error.error_code == "FIELD") { + this.field_errors(error.errors); + } + else if (error.message) { + this.error(error.message); + } + else { + this.error(error_code); + } + }, + close: function() { + this.div.remove(); + if (this.wrapper) + this.wrapper.remove(); + if (this._interval) + window.clearInterval(this._interval); + }, + busy: function() { + this._spinner.show(); + }, + unbusy: function() { + this._spinner.hide(); + }, + _build: function() { + var top; + this.div = new Element("div", { className: this.options.top_class }); + if (this.options.modal) { + this.wrapper = new Element("div", { className: this.options.modal_class }); + top = this.wrapper; + this.wrapper.appendChild(this.div); + } + else { + top = this.div; + } + this.top = top; + this.form = new Element("form", { action: "#" }); + this.form.observe("submit", this._on_submit.bind(this)); + this.div.appendChild(this.form); + + var parent; + if (this.options.fieldset_wrapper) { + var fs = new Element("fieldset"); + this.title = new Element("legend"); + this.title.update(this.options.title); + fs.appendChild(this.title); + this.form.appendChild(fs); + parent = fs; + } + else { + parent = this.form; + this.title = new Element("div", { className: this.options.title_class }); + this.title.update(this.options.title); + this.div.appendChild(this.title); + } + this._error = new Element("div", { className: this.options.error_class }); + this._error.hide(); + parent.appendChild(this._error); + this.field_error_divs = {}; + this.field_wrapper_divs = {}; + this.fields = {}; + this._add_fields(parent, this.options.fields); + var button_p = new Element("p", { className: this.options.submit_wrapper_class }); + var sub_wrapper = new Element("span"); + this._progress = new BSEDialog.ProgressBar(); + button_p.appendChild(this._progress.element()); + + this._spinner = new Element("span", {className: this.options.spinner_class }); + this._spinner.hide(); + this._spinner.update(this.options.spinner_text); + sub_wrapper.appendChild(this._spinner); + if (this.options.cancel) { + this.cancel = this._make_cancel(); + this.cancel.observe("click", this._on_cancel.bindAsEventListener(this)); + sub_wrapper.appendChild(this.cancel); + } + this.submit = this._make_submit(); + sub_wrapper.appendChild(this.submit); + button_p.appendChild(sub_wrapper); + this.form.appendChild(button_p); + }, + _make_cancel: function() { + var cancel = new Element("button", { + type: "button", + className: this.options.cancel_base_class + }); + if (this.options.cancel_class) + cancel.addClassName(this.options.cancel_class); + cancel.update(this.options.cancel_text); + + return cancel; + }, + _make_submit: function() { + var submit = new Element("button", { + type: "submit", + className: this.options.submit_base_class + }); + if (this.options.submit_class) + submit.addClassName(this.options.submit_class); + submit.update(this.options.submit); + + return submit; + }, + _show: function() { + var body = $$("body")[0]; + body.insertBefore(this.div, body.firstChild); + if (this.wrapper) + body.insertBefore(this.wrapper, body.firstChild); + if (this.options.position) { + var top_px = (document.viewport.getHeight() - this.div.getHeight()) / 2; + if (top_px < 20) { + this.div.style.overflowX = "scroll"; + this.div.style.top = "10px"; + this.div.style.height = (this.viewport.getHeight()-20) + "px"; + } + else { + this.div.style.top = top_px + "px"; + } + this.div.style.left = (document.viewport.getWidth() - this.div.getWidth()) / 2 + "px"; + } + this._fields.inDocument(); + //if (this.wrapper) { + // this.wrapper.style.height = "100%"; + //} + }, + _add_fields: function(parent, fields) { + this._fields = new BSEDialog.Fields(this.options); + this._value_fields = this._fields.value_fields(); + this._values = new Hash(); + this._value_fields.each(function(field) { + this._values.set(field.name(), field); + }.bind(this)); + this._elements = this._fields.elements(); + this._elements.each(function(parent, ele) { + parent.appendChild(ele); + }.bind(this, parent)); + }, + _start_validation: function() { + if (this.options.validator) { + this._update_submit(); + this._interval = window.setInterval(this._update_submit.bind(this), this.options.dynamic_interval); + } + }, + _update_submit: function() { + var errors = new Hash(); + this.submit.disabled = this.options.validator.validate(this._values, errors) ? "" : "disabled"; + }, + _on_submit: function(event) { + event.stop(); + if (this.options.validator) { + var errors = new Hash(); + if (!this.options.validator.validate(this._values, errors)) { + this.field_errors(errors); + return; + } + } + this.options.onSubmit(this); + }, + _on_cancel: function(event) { + event.stop(); + this.options.onCancel(this); + }, + defaults: function() { + return BSEDialog.defaults; + }, + progress_start: function(note) { + this._progress.start(note); + }, + progress: function(frac, note) { + this._progress.progress(frac, note); + }, + progress_end: function() { + this._progress.end(); + }, + progress_note: function(note) { + this._progress.note(note); + }, + enable: function() { + this.form.enable(); + }, + disable: function() { + this.form.disable(); + } +}); + +BSEDialog.defaults = { + modal: false, + title: "Missing title", + validator: new BSEValidator, + top_class: "window dialog", + modal_class: "bse_modal", + title_class: "bse_title", + error_class: "bse_error", + submit_wrapper_class: "buttons", + submit: "Submit", + cancel: false, + cancel_text: "Cancel", + onCancel: function(dlg) { dlg.close(); }, + dynamic_validation: true, + dynamic_interval: 1000, + position: false, + fieldset_wrapper: true, + cancel_base_class: "button bigrounded cancel", + cancel_class: "gray", + submit_base_class: "button bigrounded", + submit_class: "green", + error_animate: function(dlg, div) { + Effect.Shake(div); + }, + spinner_class: "spinner", + spinner_text: "Busy" +}; + + +// wraps one or more fields +// +BSEDialog.Fields = Class.create({ + initialize: function(options) { + this.options = Object.extend({}, options); + + this._fields = {}; + this._value_fields = []; + this._elements = []; + this._fieldobjs = []; + options.fields.each(function(field) { + var cls = BSEDialog.FieldTypes[field.type || "text"]; + var fieldobj = new cls(field); + this._fieldobjs.push(fieldobj); + fieldobj.value_fields().each(function(field) { + this._fields[field.name()] = field; + }.bind(this)); + this._value_fields = this._value_fields.concat(fieldobj.value_fields()); + this._elements = this._elements.concat(fieldobj.elements()); + }.bind(this)); + }, + set_error: function(name, message) { + this._fields[name].set_error(name, message); + }, + clear_error: function() { + for (var i in this._fields) { + this._fields[i].clear_error(); + } + }, + value_fields: function() { + return this._value_fields; + }, + elements: function() { + return this._elements; + }, + inDocument: function() { + this._fieldobjs.each(function(f) { + f.inDocument(); + }); + } +}); + +BSEDialog.FieldTypes = {}; + +// field objects provide the following methods: +// +// clear_error() - clear all error indicators +// set_error(name, message) - set the error indicator for the named field +// input() - return the underlying input tag (intended for use with file +// fields, many field types will not have a single input) +// value_fields() return the fields that actually have a value. For a field +// this is the child fields of the fieldset. For value fields just return +// [ this ] +// elements() returns the top-level elements for each field, for an input +// this is the wrapper div, for a field set, the fieldset itself +// +// Fields returned by value_fields() must also provide: +// value() - return the value of the given field +// description() - text description of the field +// name() - name of the value +// rules() - validation rules either as a ; separated string or an array +// has_value() - returns true if the field has a non-empty value +// required() - returns true if the field is marked required +// +BSEDialog.FieldTypes.Base = Class.create({ + _make_wrapper: function() { + var wrapper = new Element("div", { className: this.options.field_wrapper_class }); + if (this.options.required) + wrapper.addClassName(this.options.field_required_class); + + return wrapper; + }, + _make_label: function() { + return new Element("label", { htmlFor: this._input.identify() }); + }, + _make_error: function() { + var err_div = new Element("div", { className: this.options.field_error_class }); + err_div.hide(); + return err_div; + }, + name: function() { + return this.options.name; + }, + clear_error: function() { + this._error.update(""); + this._error.hide(); + this.elements().each(function(ele) { + ele.removeClassName(this.options.field_invalid_class); + }.bind(this)); + }, + set_error: function(name, message) { + this._error.update(message); + this._error.show(); + this.elements().each(function(ele) { + ele.addClassName(this.options.field_invalid_class); + }.bind(this)); + }, + input: function() { + return null; + }, + description: function() { + return null; + }, + required: function() { + return this.options.required; + }, + value_fields: function() { + return [ this ]; + }, + defaults: function() { + return BSEDialog.FieldTypes.Base.defaults; + }, + set_object: function(object) { + this._object = object; + }, + object: function() { + return this._object; + }, + // called when the field becomes part of the document + inDocument: function() { + }, + default_options: function() { + return BSEDialog.FieldTypes.Base.defaults; + } +}); + +BSEDialog.FieldTypes.Base.defaults = { + field_wrapper_class: "bse_field_wrapper", + field_error_class: "bse_field_error", + field_required_class: "bse_required", + field_invalid_class: "bse_invalid", + rules: [], + required: false +}; + +BSEDialog.FieldTypes.input = Class.create(BSEDialog.FieldTypes.Base, { + initialize: function(options) { + this.options = Object.extend(Object.extend({}, this.defaults()), options); + this._div = this._make_wrapper(); + var span = new Element("span"); + this._input = this._make_input(); + this._label = this._make_label(); + this._label.update(this.description()); + this._error = this._make_error(); + + span.appendChild(this._input); + this._div.appendChild(this._label); + this._div.appendChild(span); + this._div.appendChild(this._error); + }, + _make_input: function() { + return new Element( + "input", + { + name: this.options.name, + type: this.options.type, + value: this.options.value + }); + }, + value: function() { + return this._input.value; + }, + elements: function() { + return [ this._div ]; + }, + name: function() { + return this.options.name; + }, + description: function() { + return this.options.label || this.options.name; + }, + rules: function() { + return this.options.rules; + }, + has_value: function() { + return /\S/.test(this.value()); + }, + defaults: function() { + return Object.extend( + Object.extend( + {}, BSEDialog.FieldTypes.Base.defaults + ), BSEDialog.FieldTypes.input.defaults); + } +}); + +BSEDialog.FieldTypes.input.defaults = { + type: "text", + value: "" +}; + +BSEDialog.FieldTypes.text = BSEDialog.FieldTypes.input; + +BSEDialog.FieldTypes.password = BSEDialog.FieldTypes.input; + +BSEDialog.FieldTypes.textarea = Class.create(BSEDialog.FieldTypes.input, { + _make_input: function() { + var ta = new Element("textarea", { + name: this.options.name, + value: this.options.value, + cols: this.options.cols, + rows: this.options.rows + }); + ta.update(this.options.value); + + return ta; + }, + defaults: function($super) { + return Object.extend({}, Object.extend($super(), { + rows: 4, + cols: 60 + })); + } +}); + +BSEDialog.FieldTypes.select = Class.create(BSEDialog.FieldTypes.input, { + _make_input: function() { + var input = new Element("select", { name: this.options.name }); + var values = this.options.values; + for (var i = 0; i < values.length; ++i) { + var val = values[i]; + var def = this.option.value != null && this.option.value == val.key; + input.options[input.options.length] = + new Option(val.label, val.value, def); + } + return input; + } +}); + +BSEDialog.FieldTypes.radio = Class.create(BSEDialog.FieldTypes.Base, { + initialize: function() { + }, + value: function() { + }, + input: function() { + } +}); + +BSEDialog.FieldTypes.fieldset = Class.create(BSEDialog.FieldTypes.Base, { + initialize: function(options) { + this.options = Object.extend(Object.extend({}, BSEDialog.FieldTypes.fieldset.defaults), options); + this._element = new Element("fieldset"); + if (this.options.legend) { + var legend = new Element("legend") + legend.update(this.options.legend); + this._element.appendChild(legend); + } + + this._fields = new BSEDialog.Fields(options); + this._fields.elements().each(function(ele) { + this._element.appendChild(ele); + }.bind(this)); + }, + value_fields: function() { + return this._fields.value_fields(); + }, + clear_error: function() { + this._fields.clear_error(); + }, + set_error: function(name, message) { + this._fields.set_error(name, message); + }, + elements: function() { + return [ this._element ]; + }, + inDocument: function() { + this._fields.inDocument(); + } +}); + +BSEDialog.FieldTypes.fieldset.defaults = { +}; + +BSEDialog.FieldTypes.help = Class.create(BSEDialog.FieldTypes.Base, { + initialize: function(options) { + this._element = new Element("div"); + this._element.update(options.helptext); + }, + value_fields: function() { + // nothing to see here, move along + return []; + }, + elements: function() { + return [ this._element ]; + } +}); + +BSEDialog.FieldTypes.image = Class.create(BSEDialog.FieldTypes.Base, { + initialize: function(options) { + this.options = Object.extend(Object.extend({}, this.default_options()), options); + // general container + var wrapper = new Element("fieldset", { + className: "bse_image_field" + }); + this._element = wrapper; + var legend = new Element("legend"); + legend.update(this.options.label); + wrapper.appendChild(legend); + + var disp = new Element("img", { + className: "display" + }); + this._image_display = disp; + this.options.value = Object.extend({ + src: "", + alt: "", + name: "", + description: "", + display_name: "" + }, this.options.value || {}); + if (this.options.value) { + if (this.options.value.src) + disp.src = this.options.value.src; + else if (this.options.value.file) { + new BSEDialog.ImagePlaceholder({ + file: this.options.value.file, + onLoad: function(disp, ph) { + disp.src = ph.src(); + }.bind(this, disp) + }); + } + + disp.alt = this.options.value.alt; + } + + wrapper.appendChild(disp); + var file = new Element("input", { + type: "file" + }); + this._file_input = file; + wrapper.appendChild(file); + + if (BSEAPI.can_drag_and_drop()) { + BSEAPI.make_drop_zone({ + element: disp, + onDrop: function (files) { + this.clear_error(); + var file = files[0]; + if (!/\.(jpe?g|png|gif)$/i.test(file.fileName)) { + this.set_error("Only image files accepted"); + return; + } + this._dropped_file = file; + this.value.display_name = file.fileName; + if (window.URL && window.URL.createObjectURL) { + this._update_thumb_dropped(window.URL.createObjectURL(file)); + } + else if (window.FileReader) { + var fr = new FileReader; + fr.onload = function(fr) { + this._update_thumb_dropped(fr.result); + }.bind(this, fr); + fr.readAsDataURL(file); + } + + this._file_input.hide(); + this._dropped_name.update(this.value.display_name); + this._dropped_name.show(); + }.bind(this) + }); + this._dropped_name = new Element("span", { + className: "dropped_name" + }); + this._dropped_name.hide(); + wrapper.appendChild(this._dropped_name); + } + + var fields = new Array(); + if (!this.options.hide_alt) { + fields.push({ + label: "Alt", + type: "text", + name: "alt", + value: this.options.value.alt + }); + } + if (!this.options.hide_name) { + fields.push({ + label: "Name", + type: "text", + name: "name", + value: this.options.value.name + }); + } + if (!this.options.hide_description) { + fields.push({ + label: "Description", + type: "text", + name: "description", + value: this.options.value.description + }); + } + + if (fields.length != 0) { + var more = new Element("div", { + className: "more" + }); + more.update("more"); + more.observe("click", function () { + if (this._extras_shown) + this._extras.hide(); + else + this._extras.show(); + this._extras_shown = !this._extras_shown; + }.bind(this)); + wrapper.appendChild(more); + + // extra image info + var extras = new Element("div", { + className: "extras" + }); + this._extras = extras; + this._extras_shown = false; + this._extra_fields = new BSEDialog.Fields({ + fields: fields + }); + this._extra_fields.elements().each(function(ele) { + this._extras.appendChild(ele); + }.bind(this)); + extras.hide(); + wrapper.appendChild(extras); + } + this._error = this._make_error(); + wrapper.appendChild(this._error); + }, + _update_thumb_dropped: function(url) { + // a bit hacky + var img = new Element("img"); + img.onload = function(img) { + var canvas = new Element("canvas", { + width: 80, + height: 80 + }); + + var ctx = canvas.getContext("2d"); + var max_dim = img.width > img.height ? img.width : img.height; + var scale = 80 / max_dim; + var sc_width = img.width * scale; + var sc_height = img.height * scale; + var off_x = (80-sc_width)/2; + var off_y = (80-sc_height)/2; + ctx.drawImage(img, off_x, off_y, 80-off_x*2, 80-off_y*2); + this._image_display.src = canvas.toDataURL(); + }.bind(this, img); + img.src = url + }, + default_options: function() { + return {}; + }, + elements: function() { + return [ this._element ]; + }, + rules: function() { + return []; + }, + has_value: function() { + if (this._dropped_file) + return true; + if (this._file_input.value.length) + return true; + + return false; + }, + value: function() { + if (this._dropped_file) + return this._dropped_file.fileName; + + return this._file_input.value; + }, + object: function() { + var obj = {}; + if (this._dropped_file) { + obj.file = this._dropped_file; + obj.display_name = obj.file.fileName; + } + else if (this._file_input.value != "") { + obj.file = this._file_input; + obj.display_name = obj.file.value; + } + if (this._extra_fields) { + this._extra_fields.value_fields().each(function(obj, field) { + obj[field.name()] = field.value(); + }.bind(this, obj)); + } + + return obj; + } +}); + +BSEDialog.FieldTypes.gallery = Class.create(BSEDialog.FieldTypes.Base, { + initialize: function(options) { + this.options = Object.extend( + Object.extend({}, this.default_options()), + options); + this._element = new Element("fieldset", { + className: "bse_image_gallery" + }); + this._undo_history = []; + var legend = new Element("legend"); + legend.update(this.options.label); + this._element.appendChild(legend); + this._images_element = new Element("div", { + className: "bse_gallery_imagelist" + }); + // original images that have been removed + this._deleted = []; + this._element.appendChild(this._images_element); + this._element.appendChild(this._make_input_div()); + this._images = this.options.value.map(function(im) { + return { + type: "old", + image: im, + display_name: im.display_name, + id: im.id, + changed: false, + alt: im.alt, + description: im.description, + name: im.name, + src: im.src + }; + }); + this._autoid = 1; + this._populate_images(); + }, + _make_input_div: function() { + var div = new Element("div"); + this._file_input = this._make_file_input(); + var label = new Element("label", { + "for": this._file_input.identify() + }); + label.update(this.options.file_input_label); + var add = new Element("span", { + className: "widget add" + }); + add.update("Add"); + add.observe("click", this._add_file_input.bind(this)); + div.appendChild(label); + div.appendChild(this._file_input); + div.appendChild(add); + + return div; + }, + _make_file_input: function() { + var file = new Element("input", { + type: "file" + }); + + if (this._file_input) + file.id = this._file_input.identify(); + + if (BSEAPI.can_drag_and_drop()) + file.multiple = true; + + return file; + }, + _add_file_input: function() { + var file = this._file_input; + if (file.value.length > 0) { + if (BSEAPI.can_drag_and_drop()) { + for (var i = 0; i < file.files.length; ++i) { + this._images.push({ + type: "new", + file: file.files[i], + display_name: file.files[i].fileName, + id: "new" + this._autoid++, + alt: "", + description: "", + name: "" + }); + } + } + else { + this._images.push({ + type: "new", + file: file, + display_name: file.value, + id: "new" + this._autoid, + alt: "", + description: "", + name: "" + }); + ++this._autoid; + } + this._populate_images(); + this._make_sortable(); + + this._file_input = this._make_file_input(); + file.replace(this._file_input); + } + }, + _undo_save: function() { + this._undo_history.push({ + images: this._images.clone(), + deleted: this._deleted.clone() + }); + }, + _undo: function() { + if (this._undo_history.length > 0) { + var entry = this._undo_history.pop(); + this._images = entry.images; + this._deleted = entry.deleted; + this._populate_images(); + this._make_sortable(); + } + }, + _populate_images: function() { + this._images_element.update(); + this._images.each(function(im) { + this._images_element.appendChild(this._make_image_element(im)); + }.bind(this)); + + if (BSEAPI.can_drag_and_drop()) { + var targ = new Element("div", { + className: this.options.drop_target_class + }); + targ.update("Drop here"); + BSEAPI.make_drop_zone({ + element: targ, + onDrop: function(targ, files) { + for (var i = 0; i < files.length; ++i) { + this._images.push({ + type: "new", + file: files[i], + display_name: files[i].fileName, + id: "new" + this._autoid, + alt: "", + description: "", + name: "" + }); + ++this._autoid; + this._populate_images(); + this._make_sortable(); + } + }.bind(this, targ) + }); + this._images_element.appendChild(targ); + } + }, + _make_sortable: function() { + Sortable.create(this._images_element.identify(), { + tag: "div", + only: this.options.image_entry_class, + constraint: "horizontal", + overlap: "horizontal", + onUpdate: function() { + }.bind(this) + }); + }, + _make_image_element: function(im) { + var p = new Element("div", { + id: "bse_image_"+im.id, + className: this.options.image_entry_class + }); + p.appendChild(this._make_thumb_img(im)); + var del = new Element("span", { + className: "widget delete" + }); + del.update("Delete"); + del.observe("click", this._delete_image.bind(this, im)); + p.appendChild(del); + var edit = new Element("span", { + className: "widget edit" + }); + edit.update("Edit"); + edit.observe("click", this._edit_image.bind(this, im)); + p.appendChild(edit); + var namep = new Element("span", { + className: "name" + }); + + namep.update(im.display_name); + p.appendChild(namep); + + return p; + }, + _make_thumb_img: function(im) { + if (im.thumb_img) + return im.thumb_img; + + if (im.type == "old") { + if (this.options.getThumbURL) { + var img = new Element("img"); + img.src = this.options.getThumbURL(im.image); + im.thumb_img = img; + } + else { + var thumb = new BSEDialog.ImagePlaceholder({ + url: im.image.src + }); + im.thumb_img = thumb.element(); + } + } + else { + var thumb = new BSEDialog.ImagePlaceholder({ + file: im.file + }); + im.thumb_img = thumb.element(); + } + + return im.thumb_img; + }, + _delete_image: function(im) { + this._undo_save(); + if (im.type == "old") + this._deleted.push(im); + this._images = this._images.without(im); + this._populate_images(); + this._make_sortable(); + }, + _edit_image: function(im) { + new BSEDialog({ + title: "Edit Gallery Image", + modal: true, + submit: "Update", + cancel: true, + fields: [ + { + name: "image", + type: "image", + value: im, + label: "Image" + } + ], + onSubmit: function(im, dlg) { + var result = dlg.field("image").object(); + im.changed = true; + im.name = result.name; + im.alt = result.alt; + im.description = result.description; + if (result.file) { + im.file = result.file; + var thumb = new BSEDialog.ImagePlaceholder({ + file: im.file + }); + im.thumb_img = thumb.element(); + } + this._populate_images(); + this._make_sortable(); + dlg.close(); + }.bind(this, im) + }); + }, + default_options: function($super) { + return Object.extend( + Object.extend({}, $super()), { + value: [], + image_list_class: "bse_gallery_imagelist", + image_entry_class: "bse_gallery_image", + drop_target_class: "bse_drop_target", + file_input_label: "Add image" + }); + }, + elements: function () { + return [ this._element ]; + }, + value: function() { + return this._images.length > 0 ? "1" : ""; + }, + has_value: function() { + return this._value.length != 0; + }, + object: function() { + return { + images: this._images, + deleted: this._deleted + }; + }, + rules: function() { + return []; + }, + inDocument: function() { + this._make_sortable(); + } +}); + +BSEDialog.AskYN = Class.create({ + initialize: function(options) { + this.options = Object.extend({ + submit: "Yes", + cancel: true, + cancel_text: "No", + cancel_class: "rosy", + modal: true + }, options); + this.options.fields = [ + { + type: "help", + helptext: options.text + } + ]; + if (this.options.onYes) + this.options.onSubmit = this.options.onYes; + if (this.options.onNo) + this.options.onCancel = this.options.onNo; + var dlg = new BSEDialog(this.options); + } +}); + +BSEDialog.ProgressBar = Class.create({ + initialize: function() { + this._progress = new Element("span", { + className: "progress" + }); + this._progress.hide(); + this._progress_status = new Element("span", { + className: "status" + }); + this._progress.appendChild(this._progress_status); + this._progress_bar = new Element("span", { + className: "bar blue" + }); + this._progress.appendChild(this._progress_bar); + }, + element: function() { + return this._progress; + }, + start: function(note) { + if (note != null) + this.note(note); + else + this._progress_status.update(); + this._progress.show(); + this._progress_width = this._progress.getWidth(); + this._progress_bar.style.width = "0px"; + }, + progress: function(frac, note) { + if (frac != null) { + this._progress_bar.style.width = Math.floor(this._progress_width * frac) + "px"; + } + if (note != null) + this.note(note); + + }, + end: function() { + this._progress.hide(); + }, + note: function(note) { + if (note != null) + this._progress_status.update(note); + else + this._progress_status.update(); + } +}); + +BSEDialog.ImagePlaceholder = Class.create({ + initialize:function(options) { + this.options = Object.extend(this.default_options(), options); + + this._element = new Element("img", { + width: this.options.width, + height: this.options.height + }); + + if (this.options.url) { + this._update(this.options.url); + return; + } + + var file = options.file.files ? options.file.files[0] : options.file; + if (window.URL && window.URL.createObjectURL) { + this._update(window.URL.createObjectURL(file)); + } + else if (window.URL && window.webkitURL.createObjectURL) { + this._update(window.webkitURL.createObjectURL(file)); + } + else if (window.FileReader) { + var fr = new FileReader; + fr.onload = function(fr) { + this._update(fr.result); + }.bind(this, fr); + fr.readAsDataURL(file); + } + else { + this._src = this.options.noapisrc; + this._element.src = this._src; + this._onload(); + } + }, + _update: function(url) { + var img = new Element("img"); + img.onload = function(img) { + var canvas = new Element("canvas", { + width: this.options.width, + height: this.options.height + }); + + var ctx = canvas.getContext("2d"); + var max_dim = img.width > img.height ? img.width : img.height; + var scale = this.options.width / max_dim; + var sc_width = img.width * scale; + var sc_height = img.height * scale; + var off_x = (this.options.width - sc_width)/2; + var off_y = (this.options.height - sc_height)/2; + ctx.drawImage(img, off_x, off_y, this.options.width-off_x*2, this.options.height-off_y*2); + this._src = canvas.toDataURL(); + this._element.src = this._src; + this._onload(); + }.bind(this, img); + img.src = url + }, + _onload: function() { + if (this.options.onLoad) + this.options.onLoad(this); + }, + element: function() { + return this._element; + }, + // only valid once the image is loaded + src: function() { + return this._src; + }, + default_options: function() { + return { + width: 80, + height: 80, + noapisrc: "/images/ph.gif" + }; + } +}); diff --git a/site/htdocs/js/bse_loader.js b/site/htdocs/js/bse_loader.js new file mode 100644 index 00000000..ef4f4993 --- /dev/null +++ b/site/htdocs/js/bse_loader.js @@ -0,0 +1,41 @@ +var BSELoader = Class.create({ + initialize: function(options) { + this.options = Object.extend({}, options); + this._scripts = options.scripts.clone(); + this._load_next_script(); + }, + _load_next_script: function() { + var uri = this._scripts.shift(); + if (BSELoader.cache_buster) { + uri = uri + "?" + Math.random(); + } + var scr = new Element("script", { src: uri, type: "text/javascript" }); + scr.loadDone = false; + scr.onload = function(scr) { + if (!this.loadDone) { + scr.loadDone = true; + this._script_loaded(); + } + }.bind(this, scr); + scr.onreadystatechange = function(scr) { + if ((scr.readyState === "loaded" || scr.readyState === "complete") + && !scr.loadDone) { + scr.loadDone = true; + this._script_loaded(); + } + }.bind(this, scr); + var head = $$("head")[0]; + head.appendChild(scr); + }, + _script_loaded: function() { + if (this._scripts.length) { + this._load_next_script(); + } + else { + if (this.options.onLoaded != null) + this.options.onLoaded(); + } + } +}); + +BSELoader.cache_buster = false; diff --git a/site/htdocs/js/bse_menu.js b/site/htdocs/js/bse_menu.js new file mode 100644 index 00000000..6b99b597 --- /dev/null +++ b/site/htdocs/js/bse_menu.js @@ -0,0 +1,342 @@ +var BSEMenuBar = Class.create({ + initialize: function(options) { + this.options = Object.extend({ + hide_timeout: 750 + }, options); + this.menus = []; + }, + add_menu: function(menu) { + this.menus.push(menu); + menu.element().observe("mouseover", function(ev, menu) { + if (this._hide_timer) + window.clearTimeout(this._hide_timer); + if (this._current_menu) { + if (this._current_menu == menu) + return; + + this._current_menu.submenu().hide(); + delete this._current_menu; + } + + this._current_menu = menu; + this._current_menu.submenu().show(); + }.bindAsEventListener(this, menu)); + menu.element().observe("mouseleave", function(ev, menu) { + if (this._current_menu) { + if (this._hide_timer) + window.clearTimeout(this._hide_timer); + this._hide_timer = window.setTimeout(function() { + this._current_menu.submenu().hide(); + delete this._current_menu; + delete this._hide_timer; + }.bind(this, menu), this.options.hide_timeout); + } + }.bindAsEventListener(this, menu)); + } +}); + +// represents a top-level menu item +// +var BSEMenu = Class.create({ + initialize: function(options) { + this.options = Object.extend( + Object.extend({}, this.defaults()), options); + this._make_element(); + }, + // return an element containing the menu + // this must be an li + element: function() { + return this._element; + }, + _make_element: function() { + this._element = new Element("li"); + if (this.options.current) + this._element.addClassName(this.options.current_class); + this._title_element = new Element("span", { className: this.options.title_class }); + this._title_element.observe("click", function(ev) { ev.stop(); }); + this._title_element.update(this.options.title); + this._element.appendChild(this._title_element); + + // caller provided their own content for the menu + this._submenu = new this.options.submenu_class(this.options); + this._element.appendChild(this._submenu.element()); + + // this._element.observe("mouseover", function(ev) { + // ev.stop(); + // this._submenu.show(); + // if (this._hide_timer) { + // window.clearTimeout(this._hide_timer); + // delete this._hide_timer; + // } + // }.bind(this)); + // this._element.observe("mouseout", function(ev) { + // ev.stop(); + // if (this._hide_timer) + // window.clearTimeout(this._hide_timer); + // this._hide_timer = window.setTimeout(function() { + // this._submenu.hide(); + // delete this._hide_timer; + // }.bind(this), this.options.hide_timeout); + // }.bind(this)); + }, + setText: function(text) { + this._title_element.update(text); + }, + defaults: function() { + return BSEMenu.defaults; + }, + inDocument: function() { + this._submenu.inDocument(); + }, + submenu: function() { + return this._submenu; + }, + hide: function() { + return this._element.hide(); + }, + show: function() { + return this._element.show(); + } +}); + +// an item in a drop-down menu +BSEMenu.Item = Class.create({ + initialize: function(options) { + this.original_options = options; + this.options = Object.extend( + Object.extend({}, this.defaults()), options); + this._make_element(); + this.original_options.object = this; + }, + element: function() { + return this._element; + }, + submenu: function() { + return this._submenu; + }, + setSubmenu: function(submenu) { + if (this._submenu) { + this._element.replaceChild(submenu.element(), this._submenu.element()); + } + else { + this._element.appendChild(submenu.element()); + } + this._submenu = submenu; + }, + defaults: function() { + return BSEMenu.Item.defaults; + }, + setChecked: function(checked) { + if (checked) { + this._element.removeClassName(this.options.unchecked_class); + this._element.addClassName(this.options.checked_class); + } + else { + this._element.removeClassName(this.options.checked_class); + this._element.addClassName(this.options.unchecked_class); + } + this._checked = checked; + }, + checked: function() { + return this._checked; + }, + setDisabled: function(disabled) { + if (disabled) { + this._element.addClassName(this.options.disabled_class); + } + else { + this._element.removeClassName(this.options.disabled_class); + } + this._disabled = disabled; + }, + disabled: function() { + return this._disabled; + }, + setText: function(text) { + this._link.update(text); + }, + _make_element: function() { + this._element = new Element("li"); + this._wrapper = new Element("span"); + this._element.appendChild(this._wrapper); + this._link = new Element("span", { className: this.options.item_class }); + this._link.update(this.options.text); + this._link.observe("click", this._onclick.bind(this)); + this._wrapper.appendChild(this._link); + if (this.options.item_class) + this._element.addClassName(this.options.item_class); + if (this.options.id) + this._element.id = this.options.id; + + this.options.widgets.each(function(w) { + var ele = new Element("span", { className: w.className }); + ele.observe("click", function(event, w) { + event.stop(); + w.onClick(); + }.bindAsEventListener(this, w)); + this._wrapper.appendChild(ele); + }.bind(this)); + + if (this.options.check) + this.setChecked(this.options.checked); + if (this.options.separate) + this._element.addClassName(this.options.separate_class); + this.setDisabled(this.options.disabled); + + if (this.options.submenu) { + this._submenu = new this.options.submenu_class(this.options.submenu); + this._element.appendChild(this._submenu.element()); + } + }, + _onclick: function(ev) { + ev.stop(); + if (!this._disabled && this.options.onClick) + this.options.onClick(this.original_options); + }, + inDocument: function() { + if (this._submenu) + this._submenu.inDocument(); + } +}); + +// a drop-down menu, either from the menu bar, or as a submenu of an item +BSEMenu.SubMenu = Class.create({ + initialize: function(options) { + this.options = Object.extend( + Object.extend({}, this.defaults()), options); + this._make_element(); + }, + items: function() { + return this._items; + }, + element: function() { + return this._element; + }, + _make_element: function() { + if (this.options.element) { + this._element = this.options.element; + } + else { + var ele = new Element("ul"); + this._items = []; + var items = this.options.items; + for (i = 0; i < items.length; ++i) { + var item = this._make_item(items[i]); + ele.appendChild(item.element()); + this._items.push(item); + } + this._element = ele; + } + this._element.hide(); + }, + _make_item: function(options) { + var item = new this.options.item_class(options); + + item._element.observe("mouseover", function(ev, item) { + if (this._shown_submenu) { + if (item.submenu() && item.submenu() == this._shown_submenu) + return; + this._shown_submenu.hide(); + delete this._shown_submenu; + } + if (item.submenu()) { + item.submenu().show(); + this._shown_submenu = item.submenu(); + } + }.bindAsEventListener(this, item)); + + return item; + }, + defaults: function() { + return BSEMenu.SubMenu.defaults; + }, + inDocument: function() { + this._items.each(function(item) { item.inDocument() }); + }, + show: function() { + this._element.show(); + }, + hide: function() { + this._element.hide(); + if (this._shown_submenu) { + this._shown_submenu.hide(); + delete this._shown_submenu; + } + } +}); + +BSEMenu.OrderedSubMenu = Class.create(BSEMenu.SubMenu, { + //_make_element: function($super) { + // $super(); + + //}, + defaults: function($super) { + return Object.extend( + Object.extend({}, $super()), + { + item_class: BSEMenu.OrderedItem + }); + }, + inDocument: function($super) { + $super(); + + var item_eles = []; + for (var i = 0; i < this._items.length; ++i) { + item_eles.push(this._items[i].move_handle()); + } + Sortable.create(this._element.identify(), { + handles: item_eles + }); + } +}); + +BSEMenu.OrderedItem = Class.create(BSEMenu.Item, { + defaults: function($super) { + return Object.extend( + Object.extend({}, $super()), + { + move_handle_class: "move", + move_handle_text: "Move" + }); + }, + _make_element: function($super) { + $super(); + + var handle = new Element("span", { className: this.options.move_handle_class }); + handle.update(this.options.move_handle_text); + // avoid sending a click through + handle.observe("click", function(ev) { ev.stop; }); + this._wrapper.appendChild(handle); + this._handle = handle; + }, + move_handle: function() { + return this._handle; + } +}); + +BSEMenu.defaults = { + current: false, + current_class: "current", + submenu_class: BSEMenu.SubMenu, + title_class: "item", + hide_timeout: 750 +}; + +BSEMenu.SubMenu.defaults = { + items: [], + item_class: BSEMenu.Item +}; + +BSEMenu.Item.defaults = { + check: false, + separate: false, + disabled: false, + name: "", + checked_class: "checked", + unchecked_class: "unchecked", + separate_class: "separate", + disabled_class: "disabled", + submenu_class: BSEMenu.SubMenu, + item_class: "item", + widgets: [] +}; \ No newline at end of file diff --git a/site/htdocs/js/bse_validate.js b/site/htdocs/js/bse_validate.js new file mode 100644 index 00000000..6caff25c --- /dev/null +++ b/site/htdocs/js/bse_validate.js @@ -0,0 +1,204 @@ +var BSEValidator = Class.create({ + initialize: function(options) { + this.options = Object.extend( + Object.extend({}, this.defaults())); + }, + _validator: function(rule) { + return new BSEValidator.Rules[rule]; + }, + _format: function(message, field) { + return message.replace(/\$n/, field.description()); + }, + validate_one: function(field, fields, errors) { + if (field.required() && !field.has_value()) { + errors.set(field.name(), this._format(this.options.required_error, field)); + return false; + } + var rules = field.rules(); + var value = field.value(); + if (typeof(rules) == "string") { + rules = rules.split(/;/); + } + for (var i = 0; i < rules.length; ++i) { + var rule = rules[i]; + if (rule == "") + continue; + var rest = ""; + var m = /^([0-9a-z_]+):(.*)$/.exec(rule); + if (m) { + rule = m[1]; + rest = m[2]; + } + var cls = this._validator(rule); + try { + var result = cls.test(value, this.options, rest, fields); + try { + field.set_object(result); + } + catch(e) { + // ignore + } + } + catch (e) { + errors.set(field.name(), e.message.replace(/\$n/, field.description())); + return false; + } + } + return true; + }, + validate: function(fields, errors) { + fields.each(function(fields, errors, entry) { + this.validate_one(entry.value, fields, errors); + }.bind(this, fields, errors)); + + return errors.values().length == 0; + }, + defaults: function() { + return BSEValidator.defaults; + } +}); + +BSEValidator.defaults = { + required_error: "$n is required" +}; + +BSEValidator.Rules = {}; + +BSEValidator.Rules.Base = Class.create({ +}); + +BSEValidator.Rules["date"] = Class.create(BSEValidator.Rules.Base, { + _days: [ 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ], + _days_in_month: function(year, month) { + if (month == 2) { + if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) + return 29; + else + return 28; + } + else { + return this._days[month-1]; + } + }, + _parse_limit: function(limit) { + var m = /^([0-9]+)[^0-9]([0-9]+)[^0-9]([0-9]+)$/.exec(limit); + if (m) { + // yyyy-mm-dd + return new Date(parseInt(m[1]), parseInt(m[2])-1, parseInt(m[3])); + } + + var base = new Date(); + m = /^([+-][0-9]+)y/.match(limit); + if (m) { + base.setFullYear(base.getFullYear() + parseInt(m[1])); + return base; + } + + m = /^([+-][0-9]+)y/.match(limit); + if (m) { + // adjust by ms/day + base.setMilliseconds(base.valueOf() + parseInt(m[1]) & 86400000); + return base; + } + + throw new Error("Cannot parse date limit " + limit); + }, + default_options: function() { + return BSEValidator.Rules["date"].defaults; + }, + min_date: function() { + return null; + }, + max_date: function() { + return null; + }, + test: function(value, options, rest) { + options = Object.extend(Object.extend({}, this.default_options()), options); + var m = options.date_re.exec(value); + if (!m) + throw new Error(options.date_format_error); + + var day, month, year; + var format = options.date_order.split(""); + for (var i = 0; i < format.length; ++i) { + switch (format[i]) { + case "d": + day = parseInt(m[1+i]); + break; + case "m": + month = parseInt(m[1+i]); + break; + case "y": + year = parseInt(m[1+i]); + break; + } + } + if (month < 1 || month > 12) + throw new Error(options.date_month_range_error); + if (day < 1 || day > this._days_in_month(year, month)) + throw new Error(options.date_day_range_error); + + var min_date = this.min_date(); + if (min_date != null) { + var min = this._parse_limit(min_date); + if (min.getTime() > result.getTime()) + throw new Error(options.date_too_low_error); + } + var max_date = this.max_date(); + if (options.max_date != null) { + var max = this._parse_limit(max_date); + if (max.getTime() < result.getTime()) + throw new Error(options.date_too_high_error); + } + + var result = new Date(year, month-1, day); + + return result; + } +}); + +BSEValidator.Rules["date"].defaults = { + date_re: /^\s*([0-9]+)[\/-]([0-9]+)[\/-]([0-9]+)\s*/, + date_order: "dmy", + date_format_error: "$n must be dd/mm/yyyy", + date_month_range_error: "Month out of range for $n", + date_day_range_error: "Day out of range for $n", + date_too_high_error: "$n too late", + date_too_low_error: "$n too early" +}; + +BSEValidator.Rules["future"] = Class.create(BSEValidator.Rules["date" ], { + min_date: function() { + var date = new Date(); + date.setHours(0, 0, 0, 0); + return date; + }, + default_options: function() { + return BSEValidator.Rules["future"].defaults; + } +}); + +BSEValidator.Rules["future"].defaults = + Object.extend({ + date_too_low_error: "$n must be be in the future" + }, BSEValidator.Rules["date"].defaults); + +BSEValidator.Rules["confirm"] = Class.create(BSEValidator.Rules.Base, { + test: function(value, options, rest, fields) { + options = Object.extend(Object.extend({}, BSEValidator.Rules["confirm"].defaults), options); + var other = fields.get(rest).value(); + + if (value != other) { + var msg = options.confirm_error; + msg = msg.replace(/\$o/, fields.get(rest).description()); + throw Error(msg); + } + + return value; + } +}); + +BSEValidator.Rules["confirm"].defaults = { + confirm_error: "$n must be the same as $o" +}; + diff --git a/t-js/01validate.html b/t-js/01validate.html new file mode 100644 index 00000000..41e08f23 --- /dev/null +++ b/t-js/01validate.html @@ -0,0 +1,13 @@ + + +BSEValidate tests + + + + + + + +
+ + diff --git a/t-js/01validate.js b/t-js/01validate.js new file mode 100644 index 00000000..8de45e1f --- /dev/null +++ b/t-js/01validate.js @@ -0,0 +1,241 @@ +function validation_check(test, validator, checker) { + if (test.exception) { + var msg = ok_exception(function(validator, value) { + validator.test(value); + }.bind(this, validator, test.value), "parse "+test.name, test.message); + if (msg) { + like(msg, test.message, "check message " + test.name); + } + else { + ok(false, test.name + " no message"); + } + } + else { + var a_parsed = ok_noexception(function(validator, value){ + return validator.test(value, {}); + }.bind(this, validator, test.value), "parse "+test.name); + checker(a_parsed, test); + } +} + +document.observe("dom:loaded", function() { + plan(55); + diag("Date validation"); + var date_val = new BSEValidator.Rules["date"](); + ok(date_val, "make date validator"); + var date_tests = + [ + { + name: "birthday", + value: "30/12/1967", + exception: false, + year: 1967, + month: 12, + day: 30 + }, + { + name: "birthday with spaces", + value: " 30/12/1967 ", + exception: false, + year: 1967, + month: 12, + day: 30 + }, + { + name: "low month", + value: "30/0/1967", + exception: true, + message: /Month out of range/ + }, + { + name: "low month in range", + value: "30/1/1967", + exception: false, + year: 1967, + month: 1, + day: 30 + }, + { + name: "high month", + value: "30/13/1967", + exception: true, + message: /Month out of range/ + }, + { + name: "high month in range", + value: "30/12/1967", + exception: false, + year: 1967, + month: 12, + day: 30 + }, + { + name: "high day normal", + value: "32/12/1967", + exception: true, + message: /Day out of range/ + }, + { + name: "high day normal in range", + value: "31/12/1967", + exception: false, + year: 1967, + month: 12, + day: 31 + }, + { + name: "high day non-leap", + value: "29/2/1970", + exception: true, + message: /Day out of range/ + }, + { + name: "high day non-leap in range", + value: "28/2/1970", + exception: false, + year: 1970, + month: 2, + day: 28 + }, + { + name: "high day leap", + value: "30/2/1980", + exception: true, + message: /Day out of range/ + }, + { + name: "high day leap in range", + value: "29/2/1980", + exception: false, + year: 1980, + month: 2, + day: 29 + }, + { + name: "low day", + value: "0/1/1967", + exception: true, + message: /Day out of range/ + }, + { + name: "low day in range", + value: "1/1/1967", + exception: false, + year: 1967, + month: 1, + day: 1 + }, + { + name: "bad format", + value: "1/1/", + exception: true, + message: /must be dd\/mm\/yyyy$/ + } + ]; + for (var i = 0; i < date_tests.length; ++i) { + var test = date_tests[i]; + validation_check(test, date_val, function(a_date, test) { + is(a_date.getFullYear(), test.year, test.name + " year"); + is(a_date.getMonth(), test.month-1, test.name + " month"); + is(a_date.getDate(), test.day, test.name + " day"); + }); + } + + var TestField = Class.create({ + initialize: function(options) { + this.options = Object.extend({ required: false, rules: [] }, options); + }, + value: function() { + return this.options.value; + }, + description: function() { + return this.options.description + }, + name: function() { + return this.options.name; + }, + rules: function() { + return this.options.rules; + }, + has_value: function() { + return /\S/.test(this.value()); + }, + required: function() { + return this.options.required; + } + }); + + { // required + var fields = new Hash({ + nr1: new TestField({ + name: "nr1", + value: "", + description: "NotRequired1" + }), + nr2: new TestField({ + name: "nr2", + value: " ", + description: "NotRequired2" + }), + r1: new TestField({ + name: "r1", + value: "", + required: true, + description: "Required1" + }), + r2: new TestField({ + name: "r2", + value: " ", + required: true, + description: "Required2" + }), + r3: new TestField({ + name: "r3", + value: "x", + required: true, + description: "Required3" + }) + }); + var val = new BSEValidator(); + var errors = new Hash(); + ok(!val.validate(fields, errors), "should fail validation"); + is(errors.get("nr1"), null, "no error for nr1"); + is(errors.get("nr2"), null, "no error for nr2"); + is(errors.get("r1"), "Required1 is required", "check error for r1"); + is(errors.get("r2"), "Required2 is required", "check error for r2"); + is(errors.get("r3"), null, "no error for r3"); + } + + { + // confirm + var fields = new Hash({ + password: new TestField({ + name: "password", + value: "abc", + description: "Password", + rules: "", + required: true + }), + confirm: new TestField({ + name: "confirm", + value: "abc", + description: "Confirm", + rules: "confirm:password", + }), + confirm2: new TestField({ + name: "confirm2", + value: "abcd", + description: "Confirm2", + rules: "confirm:password", + }) + }); + var val = new BSEValidator(); + var errors = new Hash(); + val.validate(fields, errors); + is(errors.get("confirm"), null, "should be no error for confirm"); + is(errors.get("confirm2"), "Confirm2 must be the same as Password", + "confirm2 should have an error"); + } + + tests_done(); +}); \ No newline at end of file diff --git a/t-js/10menu.html b/t-js/10menu.html new file mode 100644 index 00000000..3a5f078a --- /dev/null +++ b/t-js/10menu.html @@ -0,0 +1,15 @@ + + +BSEMenu tests + + + + + + + + + +
+ + diff --git a/t-js/10menu.js b/t-js/10menu.js new file mode 100644 index 00000000..2280b8a3 --- /dev/null +++ b/t-js/10menu.js @@ -0,0 +1,145 @@ +document.observe("dom:loaded", function() { + plan(7); + + ok(BSEMenu, "have a BSEMenu class"); + ok(BSEMenu.Item, "have a BSEMenu.Item class"); + ok(BSEMenu.SubMenu, "have a BSEMenu.SubMenu class"); + + var clicked = function(item) { diag("Item " + item.text + " clicked") }; + + var bar = $("nav"); + { + var m1 = new BSEMenu({ + title: "Test 1", + current: true, + items: [ + { + text: "Item A", + separator: true, + onClick: clicked + }, + { + text: "Item B", + onClick: clicked + } + ] + }); + ok(m1, "made a menu"); + bar.appendChild(m1.element()); + } + { + var itemfb = { + text: "Item F b", + check: true, + checked: true + }; + itemfb.onClick = function() { + var check = !this.object.checked(); + this.object.setChecked(check); + diag("Item F b clicked, now " + (check ? "" : "not ") + "checked"); + }.bind(itemfb); + var items = [ + { + text: "Item E", + separate: true, + onClick: clicked + }, + { + text: "Item F", + submenu: { + items: [ + { + text: "Item F a", + disabled: true, + onClick: function() { diag("item F a shouldn't be clickable") } + }, + itemfb + ] + } + } + ]; + var m2 = new BSEMenu({ + title: "Test 2", + items: items + }); + bar.appendChild(m2.element()); + ok(m2, "made second menu, with submenu"); + } + { + var itemM = { + text: "item M", + onClick: clicked + }; + var itemN = { + text: "item N", + onClick: clicked + }; + var m3 = new BSEMenu({ + title: "Test 3", + items: [ + itemM, + itemN + ] + }); + ok(m3, "made third menu"); + bar.appendChild(m3.element()); + m3.setText("Test 3 modified"); + itemN.object.setText("Item N modified"); + itemN.object.setSubmenu(new BSEMenu.SubMenu({ + items: [ + { + text: "item Na added", + onClick: clicked + }, + { + text: "item Nb added", + onClick: clicked + }, + { + text: "item Nc added", + onClick: clicked + }, + { + text: "item Nd added", + onClick: clicked + }, + { + text: "item Ne added", + onClick: clicked + } + ] + })); + } + { + var subT = new Element("ul"); + var li1 = new Element("li"); + li1.appendChild(new Element("input", { type: "text" })); + subT.appendChild(li1); + var li2 = new Element("li"); + var a2 = new Element("a", { href: "#" }); + a2.update("Foo"); + li2.appendChild(a2); + subT.appendChild(li2); + + var itemT = { + text: "Articles", + submenu: { + element: subT + } + }; + var m4 = new BSEMenu({ + title: "Articles", + items: [ + { + text: "New article", + onClick: clicked + }, + itemT + ] + }); + bar.appendChild(m4.element()); + ok(m4, "made m4"); + } + tests_done(); +}); + diff --git a/t-js/menu.css b/t-js/menu.css new file mode 100644 index 00000000..4001e690 --- /dev/null +++ b/t-js/menu.css @@ -0,0 +1,228 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0} + +html { + text-align: center; + width: 100%; +} +body { + width: 100%; + text-align: left; + margin: 0 auto; + padding: 0; + background-color: #666; + font: normal normal 0.75em/1.5 "Lucida Sans Unicode", "Lucida Grande", "Helvetica Neue", Helvetica, Arial, Helvetica, sans-serif; + text-shadow: 0 0.083em 0 #fff; + color: #585858; + position: relative; +} + + +#menu { + margin: 0; + padding: 0; + line-height: 1; + width: 100%; + height: 4em; + + -webkit-box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + -moz-box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + box-shadow: 0 0.1em 0.5em rgba(0,0,0,0.4); + + background: #8b8b8b; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#a9a9a9', endColorstr='#7a7a7a'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#a9a9a9), to(#7a7a7a)); /* for webkit browsers */ + background: -moz-linear-gradient(top, #a9a9a9, #7a7a7a); /* for firefox 3.6+ */ + + border-bottom: solid 0.1em #6d6d6d; + + z-index: 1000; + position: fixed; + top: 0; + overflow: visible; +} +#nav { + position: relative; + z-index: 1001; + overflow: visible; +} +#nav li { + margin: 0 0 0 1em; + padding: 0.75em 0; + float: left; + position: relative; + list-style: none; +} +#nav li li.separate { + border-bottom: 0.1em solid #b4b4b4; +} +#nav li li.separate+li { + border-top: 0.1em solid #fff; +} +/* main level link */ +#nav a { + position: relative; + line-height: 1.5; + font-weight: bold; + color: #e7e5e5; + text-decoration: none; + display: block; + margin: -0.1em 0; + padding: 0.5em 1.5em; + -webkit-border-radius: 1.5em; + -moz-border-radius: 1.5em; + border-radius: 1.5em; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.5); +} +/* main level link hover */ +#nav .current > a, #nav li:hover > a { + background: #d1d1d1; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb', endColorstr='#a1a1a1'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#a1a1a1)); /* for webkit browsers */ + background: -moz-linear-gradient(top, #ebebeb, #a1a1a1); /* for firefox 3.6+ */ + + color: #444; + border-top: solid 0.1em #f8f8f8; + -webkit-box-shadow: 0 0.1em 0.1em rgba(0,0,0,0.2); + -moz-box-shadow: 0 0.1em 0.1em rgba(0,0,0,0.2); + box-shadow: 0 0.1em 0.1em rgba(0,0,0,0.2); + text-shadow: 0 0.1em 0.1em rgba(255,255,255,1); +} +/* sub levels link hover */ +#nav ul li:hover a, #nav li:hover li a { + background: none; + border: none; + color: #666; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + margin: 0; +} +#nav ul a:hover { + background: #0399d4 !important; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#04acec', endColorstr='#0186ba'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#04acec), to(#0186ba)) !important; /* for webkit browsers */ + background: -moz-linear-gradient(top, #04acec, #0186ba) !important; /* for firefox 3.6+ */ + + color: #fff !important; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + text-shadow: 0 0.1em 0.1em rgba(0,0,0,0.1); +} +/* level 2 list */ +#nav ul { + background: #ddd; /* for non-css3 browsers */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#cfcfcf'); /* for IE */ + background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#cfcfcf)); /* for webkit browsers */ + background: -moz-linear-gradient(top, #fff, #cfcfcf); /* for firefox 3.6+ */ + + z-index: 100; + display: none; + margin: 0; + padding: 0; + width: 18em; + position: absolute; + top: 3.75em; + left: 0; + border: solid 0.1em #b4b4b4; + -webkit-border-radius: 0.5em; + -moz-border-radius: 0.5em; + -o-border-radius: 0.5em; + border-radius: 0.5em; + -webkit-box-shadow: 0 0.16em 0.5em rgba(0,0,0,0.3); + -moz-box-shadow: 0 0.16em 0.5em rgba(0,0,0,0.3); + box-shadow: 0 0.16em 0.5em rgba(0,0,0,0.3); +} +#nav ul ul { + max-height: 30.16em; + overflow-y: auto; + overflow-x: visible; +} +#nav ul.full { + width: 27em; +} + +/* @group widgets */ + +#nav li>a>span { + position: absolute; + top: 0; + left: 0; + width: 1.2em; + height: 100%; + text-indent: -9999em; + background: transparent url(../images/move_dk.png) no-repeat 50% 50%; + background-size: 1.2em; + display: none; +} +#nav li>a>span+span { + background-image: url(../images/delete_dk.png); + background-size: 1.5em; + width: 1.5em; + left: auto; + right: 0.5em; +} +#nav li:hover>a>span { + display: block; +} + +/* @end */ + +/* @group dropdown */ + +#nav li:hover > ul { + display: block; +} +#nav ul li { + float: none; + margin: 0; + padding: 0; +} +#nav ul a { + font-weight: normal; + text-shadow: 0 0.1em 0.1em rgba(255,255,255,0.9); +} + +/* @end */ + +/* @group level 3+ list */ + +#nav ul ul { + left: 17.5em; + top: -0.1em; +} +/*#nav ul ul.full { + left: 35.5em; +}*/ + +/* @end */ + +/* @group rounded corners for first and last child */ + +#nav ul li:first-child > a { + -webkit-border-top-left-radius: 0.5em; + -moz-border-radius-topleft: 0.5em; + border-top-left-radius: 0.5em; + -webkit-border-top-right-radius: 0.5em; + -moz-border-radius-topright: 0.5em; + border-top-right-radius: 0.5em; +} +#nav ul li:last-child > a { + -webkit-border-bottom-left-radius: 0.5em; + -moz-border-radius-bottomleft: 0.5em; + border-bottom-left-radius: 0.5em; + -webkit-border-bottom-right-radius: 0.5em; + -moz-border-radius-bottomright: 0.5em; + border-bottom-right-radius: 0.5em; +} + +/* @end */ + +/* @end */ + +#nav input { + border: none; + padding: 0px; +} + +#tests { margin-top: 10em; } \ No newline at end of file diff --git a/t-js/test.js b/t-js/test.js new file mode 100644 index 00000000..fa7f82ee --- /dev/null +++ b/t-js/test.js @@ -0,0 +1,134 @@ +function _test_out(text, cls) { + var test_ele = document.getElementById('tests'); + if (test_ele) { + var div_tag = document.createElement("div"); + if (cls != null) + div_tag.className = cls; + var t = document.createTextNode(text); + div_tag.appendChild(t); + test_ele.appendChild(div_tag); + //var open_tag; + //if (cls == null) + //open_tag = "
"; + //else + //open_tag = '
'; + //test_ele.innerHTML = test_ele.innerHTML + open_tag + escape_html(text) + "
"; + } +} + +var test_ok = 0; +var test_fails = 0; +var test_skips = 0; +var test_num = 1; +var test_count; + +function plan(count) { + test_count = count; + _test_out("1.." + count, "plan"); +} + +function ok(test, comment) { + if (test) { + ++test_ok; + _test_out("ok " + test_num + " # " + comment, "ok"); + } + else { + _test_out("not ok " + test_num + " # " + comment, "fail"); + ++test_fails; + } + ++test_num; + return test; +} + +function skip(text, count) { + if (count == null) + count = 1; + for (var i = 0; i < 1; ++i) { + _test_out("ok " + test_num + " SKIP text", "skip"); + ++test_skips; + ++test_num; + } +} + +function is(left, right, comment) { + var test_ok = ok(left == right, comment); + if (!test_ok) { + _test_out("# should match", "fail"); + _test_out("# left :'"+encodeURI(left)+"'", "fail"); + _test_out("# right:'"+encodeURI(right)+"'", "fail"); + } + return test_ok; +} + +function isnt(left, right, comment) { + var test_ok = ok(left != right, comment); + if (!test_ok) { + _test_out("# shouldn't match", "fail"); + _test_out("# left :"+encodeURI(left), "fail"); + _test_out("# right:"+encodeURI(right), "fail"); + } + return test_ok; +} + +function like(value, re, comment) { + var test_ok = ok(value != null && value.match(re), comment); + if (!test_ok) { + _test_out("# should match", "fail"); + _test_out("# value:"+value, "fail"); + _test_out("# regexp:"+re, "fail"); + } + return test_ok; +} + +function diag(text) { + _test_out("# " + text, "diag"); +} + +function tests_done() { + var cls = "ok"; + var top_class = "passed"; + if (test_count != null) { + if (test_count != test_num-1) { + _test_out("Expected "+test_count+" tests but saw "+(test_num-1), "fail"); + cls = "fail"; + top_class = "failed"; + } + } + if (test_fails != 0) { + cls = "fail"; + top_class = "failed"; + } + _test_out("Summary: "+(test_num-1)+" tests, "+test_ok+" Ok, "+test_fails+" failures " + test_skips + " skips", cls); + var test_ele = document.getElementById('tests'); + test_ele.className = top_class; +} + +function ok_noexception(f, text) { + var result; + try { + result = f(); + } + catch (e) { + ok(false, text); + diag(e.message); + return false; + } + + ok(true, text); + return result; +} + +function ok_exception(f, text, match) { + var result; + try { + f(); + } + catch (e) { + ok(true, text); + diag(e.message); + return e.message; + } + + ok(false, text); + return false; +} diff --git a/t-js/tests.css b/t-js/tests.css new file mode 100644 index 00000000..98723d2a --- /dev/null +++ b/t-js/tests.css @@ -0,0 +1,24 @@ +#tests { + font: 10px Arial; +} + +#tests.passed { + background-color: #CFC; +} + +#tests.failed { + background-color: #FCC; +} + +.ok { + color: #080; +} + +.fail { + color: #F00; + font-weight: bold; +} + +.skip, .plan, .diag { + color: #444; +} -- 2.30.2