0
1
Fork 0

Initial commit

This commit is contained in:
Juan Ferrer Toribio 2014-05-09 14:19:53 +02:00
commit c30dbce493
358 changed files with 16676 additions and 0 deletions

9
DEBIAN/control Normal file
View File

@ -0,0 +1,9 @@
Package: hedera-web
Version: 1.0-1
Architecture: all
Maintainer: Juan Ferrer Toribio <juan@verdnatura.es>
Depends: php5-mysql, php5-imap
Section: misc
Priority: optional
Description: Verdnatura's web page
Verdnatura's web page.

View File

@ -0,0 +1,9 @@
Alias /hedera-web /usr/share/hedera-web/
<Directory /usr/share/hedera-web/>
Options Indexes
Options +FollowSymLinks
AllowOverride None
Order Allow,Deny
Allow From All
</Directory>

14
usr/share/hedera-web/.htaccess Executable file
View File

@ -0,0 +1,14 @@
<Files *.css>
SetOutputFilter DEFLATE
</Files>
<Files *.js>
SetOutputFilter DEFLATE
</Files>
<Files *.php>
SetOutputFilter DEFLATE
</Files>
<FilesMatch "\.(ttf|otf|eot|woff)$">
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>

54
usr/share/hedera-web/ajax.php Executable file
View File

@ -0,0 +1,54 @@
<?php
require_once ('php/web/hedera.php');
require_once ('php/web/json.php');
Hedera::init ();
JsonLib::init ();
if (Hedera::login ())
{
// Checking the client version
if (isset ($_COOKIE['hedera_version']))
{
$clientVersion = $_COOKIE['hedera_version'];
set_type ($clientVersion, TYPE_DOUBLE);
if ($clientVersion < $_SESSION['version'])
{
$row = Hedera::$sysConn->getRow ('SELECT critical, changelog FROM version LIMIT 1');
if (!$row || $row['critical'])
Error::trigger ('Version', 'criticalVersion', $row['changelog']);
else
Error::trigger ('Version', 'newVersion', $row['changelog']);
}
}
// Getting the action
$action = NULL;
if (isset ($_REQUEST['action']))
$action = $_REQUEST['action'];
if ($action)
{
$actionFile = 'ajax/'. $action .'.php';
if (file_exists ($actionFile))
{
Vn\Locale::loadFile ('ajax/'. $action);
require_once ($actionFile);
actionActivate (Hedera::$conn);
}
else
Error::trigger ('Ajax', 'invalidAction', s('InvalidAction'));
}
}
JsonLib::sendReply ();
Hedera::deinit ();
?>

View File

@ -0,0 +1,179 @@
<?php
const IMG_MAX_SIZE = 3;
const IMG_URL_BASE = '/var/www/image/cache';
const IMG_URL_FULL = 'full/';
const IMG_URL_ICON = 'icon/';
const IMG_FULL_HEIGHT = 650;
const IMG_FULL_WIDTH = 750;
const IMG_ICON_SIZE = 30;
function actionActivate ($conn)
{
$data = FALSE;
$query = "SELECT r.name = 'employee' "
.'FROM role r JOIN user_role u ON r.id = u.role_id '
.'WHERE u.user_id = account.user_get_id ()';
if (!$conn->getValue ($query))
JsonLib::setError ('Image', 'permissionDenied', s('PermissionDenied'));
else if (!isset ($_REQUEST['name']) || !isset ($_REQUEST['directory']))
JsonLib::setError ('Image', 'comError', s('ComError'));
else if (!(isset ($_FILES['image']['name']) && $_FILES['image']['name'] != ''))
JsonLib::setError ('Image', 'fileNotChoosed', s('FileNotChoosed'));
else if ($_FILES['image']['error'] != 0)
JsonLib::setError ('Image', 'uploadError', s('FileUploadError'));
else if ($_FILES['image']['size'] > IMG_MAX_SIZE * 1048576)
JsonLib::setError ('Image', 'fileSizeError', sprintf (s('FileSizeError'), IMG_MAX_SIZE));
else
{
$name = $_REQUEST['name'];
$directory = $_REQUEST['directory'];
$exp = '/[^a-z0-9_]/';
if (preg_match ($exp, $name) === 0 && preg_match ($exp, $directory) === 0)
{
$img = & $_FILES['image'];
$data = TRUE;
switch ($img['type'])
{
case 'image/png':
$image = imagecreatefrompng ($img['tmp_name']);
break;
case 'image/jpeg':
$image = imagecreatefromjpeg ($img['tmp_name']);
break;
case 'image/gif':
$image = imagecreatefromgif ($img['tmp_name']);
break;
default:
JsonLib::setError ('Image', 'badFileFormat', s('BadFileFormat'));
$data = FALSE;
}
}
else
JsonLib::setError ('Image', 'badFileName', s('BadFileName'));
if ($data)
{
$data = FALSE;
if (!$image)
JsonLib::setError ('Image', 'openError', s('ImageOpenError'));
else
{
list ($width, $height) = getimagesize ($img['tmp_name']);
$n = -1;
$max_height = array ();
$max_width = array ();
$url = array ();
++$n;
$max_height[$n] = IMG_ICON_SIZE;
$max_width[$n] = IMG_ICON_SIZE;
$url[$n] = IMG_URL_ICON;
++$n;
$max_height[$n] = IMG_FULL_HEIGHT;
$max_width[$n] = IMG_FULL_WIDTH;
$url[$n] = IMG_URL_FULL;
for ($n = 0; $n < count ($url); $n++)
{
$reduce = FALSE;
$w = $width;
$h = $height;
if ($h > $max_height[$n])
{
$reduce = $h / $max_height[$n];
$h = $max_height[$n];
$w = (int) ($w / $reduce);
}
if ($w > $max_width[$n])
{
$reduce = $w / $max_width[$n];
$w = $max_width[$n];
$h = (int) ($h / $reduce);
}
if ($reduce !== FALSE)
{
$image_new = imagecreatetruecolor ($w, $h);
imagecopyresized ($image_new, $image, 0, 0, 0, 0, $w, $h, $width, $height);
}
else
$image_new = $image;
imagealphablending ($image_new, FALSE);
imagesavealpha ($image_new, TRUE);
$dest_url = IMG_URL_BASE .'/'. $directory . '/' . $url[$n] . $name . '.png';
$data = imagepng ($image_new, $dest_url);
if (!$data)
JsonLib::setError ('Image', 'saveError', sprintf (s('FileSaveError'), $dest_url));
}
}
}
unlink ($img['tmp_name']);
}
JsonLib::$encoding = JsonLib::ENCODING_HTML;
JsonLib::setData ($data);
}
/*
// Script to reduce and change format for all images
define ('IMG_DIR_BASE', '/var/www/test/image/');
define ('IMG_DIR_OLD', IMG_DIR_BASE.'gallery/');
define ('IMG_DIR_NEW', IMG_DIR_BASE.'icon/');
define ('IMG_SIZE', 85);
if (TRUE)
{
$directorio = opendir (IMG_DIR_OLD);
$n = 1;
while ($filename = readdir ($directorio))
{
switch (strtolower(substr ($filename, -3, 3)))
{
case 'jpg':
$image = imagecreatefromjpeg (IMG_DIR_OLD.$filename);
break;
case 'png':
$image = imagecreatefrompng (IMG_DIR_OLD.$filename);
break;
}
if (isset ($image))
{
$n++;
list ($width, $height) = getimagesize (IMG_DIR_OLD.$filename);
$siz_max = ($width > $height) ? $width : $height;
$percent = ($siz_max > IMG_SIZE) ? IMG_SIZE / $siz_max : 1;
$width_new = $width * $percent;
$height_new = $height * $percent;
$image_new = imagecreatetruecolor ($width_new, $height_new);
imagecopyresized ($image_new, $image, 0, 0, 0, 0, $width_new, $height_new, $width, $height);
imagesavealpha ($image_new, TRUE);
imagepng ($image_new, IMG_DIR_NEW.substr ($filename, 0, -4).'.png');
}
}
JsonLib::setError ('Image', 'imagesResized', s('ImagesResized'));
}
*/
?>

View File

@ -0,0 +1,8 @@
<?php
function actionActivate ($conn)
{
JsonLib::setData (TRUE);
}
?>

View File

@ -0,0 +1,9 @@
<?php
function actionActivate ($conn)
{
Auth::logout ($conn);
JsonLib::setData (TRUE);
}
?>

View File

@ -0,0 +1,88 @@
<?php
class JsonResult
{
var $field;
var $data;
}
function actionActivate ($conn)
{
if (isset ($_REQUEST['sql']) && $_REQUEST['sql'] != '')
{
$results = array ();
if ($conn->multiQuery ($_REQUEST['sql']))
do {
$result = $conn->storeResult ();
if ($result !== FALSE)
{
$jsonResult = new JsonResult ();
$columns = $result->fetch_fields ();
$jsonResult->data = array ();
$jsonResult->field = $columns;
for ($i = 0; $row = $result->fetch_row (); $i++)
{
for ($j = 0; $j < $result->field_count; $j++)
{
$cell = & $row[$j];
if ($cell != NULL)
{
switch ($columns[$j]->type)
{
case MYSQLI_TYPE_DATE:
case MYSQLI_TYPE_DATETIME:
case MYSQLI_TYPE_TIMESTAMP:
$cell = mktime
(
substr ($cell, 11 , 2)
,substr ($cell, 14 , 2)
,substr ($cell, 17 , 2)
,substr ($cell, 5 , 2)
,substr ($cell, 8 , 2)
,substr ($cell, 0 , 4)
);
break;
case MYSQLI_TYPE_BIT:
set_type ($cell, TYPE_BOOLEAN);
break;
case MYSQLI_TYPE_TINY:
case MYSQLI_TYPE_SHORT:
case MYSQLI_TYPE_LONG:
case MYSQLI_TYPE_LONGLONG:
case MYSQLI_TYPE_INT24:
case MYSQLI_TYPE_YEAR:
set_type ($cell, TYPE_INTEGER);
break;
case MYSQLI_TYPE_FLOAT:
case MYSQLI_TYPE_DOUBLE:
case MYSQLI_TYPE_DECIMAL:
case MYSQLI_TYPE_NEWDECIMAL:
set_type ($cell, TYPE_DOUBLE);
break;
}
}
}
$jsonResult->data[$i] = $row;
}
$results[] = $jsonResult;
$result->free ();
}
else
$results[] = TRUE;
}
while ($conn->moreResults () && $conn->nextResult ());
JsonLib::setData ($results);
}
else
JsonLib::setError ('Query', 'emptyQuery', s('EmptyQuery'));
}
?>

66
usr/share/hedera-web/config.php Executable file
View File

@ -0,0 +1,66 @@
<?php
/**
* Configuration file. Be careful to respect the PHP syntax.
**/
$conf = array (
/**
* The default language.
**/
'defaultLang' => 'es'
/**
* Compatible browsers.
**/
,'browser' => array
(
'Firefox' => 4.0
,'Iceweasel' => 4.0
,'IE' => 10.0
,'Chrome' => 7.0
,'Opera' => 11.6
)
/**
* Database parameters.
**/
,'db' => array
(
'host' => 'db.verdnatura.es'
,'name' => 'hedera'
,'user' => 'root'
,'pass' => 'base64pass'
)
/**
* The guest user.
**/
,'guest' => array
(
'user' => 'visitor'
,'pass' => 'base64pass'
)
/**
* Whether to force the use of https on the client.
**/
,'https' => FALSE
/**
* The lifetime of the session cookie, in days.
**/
,'cookieLife' => 15
/**
* The module to load if none is specified.
**/
,'defaultModule' => 'home'
/**
* The directory where images are located.
**/
,'imageDir' => '/image/cache'
);
?>

View File

@ -0,0 +1,16 @@
Copyright (C) 2013 - Juan Ferrer Toribio
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

View File

@ -0,0 +1,9 @@
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-49049601-1', 'verdnatura.es');
ga('send', 'pageview');
</script>

View File

@ -0,0 +1,13 @@
<?php
if ($result = Hedera::$sysConn->query ('SELECT name, content FROM metatag'))
{
echo '<meta name="content-language" content="'.$_SESSION['lang'].'"/>'."\n\t";
while ($row = $result->fetch_assoc ())
echo '<meta name="'.$row['name'].'" content="'.$row['content'].'"/>'."\n\t";
$result->free ();
}
?>

Binary file not shown.

View File

@ -0,0 +1,12 @@
/* Global */
@font-face {
font-family: 'Open Sans';
src: url(opensans.ttf) format('truetype');
}
*
{
font-size: 13px;
font-family: 'Open Sans', 'Verdana', 'Sans';
}

View File

@ -0,0 +1,321 @@
/* Global */
@font-face {
font-family: 'Open Sans';
src: url(opensans.ttf) format('truetype');
}
body
{
margin: 0px;
}
body,
label,
button,
input,
textarea
{
font-size: 13px;
font-family: 'Open Sans', 'Verdana', 'Sans';
}
iframe
{
border: 0px;
}
fieldset,
div
{
margin: 0px;
}
form
{
padding: 0px;
margin: 0px;
}
table
{
width: 100%;
}
a:link,
a:visited,
a:active
{
color: #000;
text-decoration: none;
}
a:hover
{
text-decoration: none;
}
a img
{
padding: 1px;
border: 1px solid #EEE;
border-color: transparent;
}
a img:hover
{
border-color: #999;
}
h1, h2, h3, h4, h5, h6
{
margin: 0px;
padding: 2px;
}
h1
{
font-size: 22px;
}
h2
{
font-size: 20px;
}
p
{
margin: 0px;
margin-top: 16px;
margin-bottom: 16px;
}
/* Inputs */
input,
textarea,
button
{
border: 1px solid #CCD;
margin: 2px;
padding: 4px;
border-radius: 2px;
/* box-shadow: 0px 2px 2px #AAA; */
}
textarea
{
height: 40px;
width: 200px;
}
input[type=submit],
input[type=button],
button
{
background-color: #EEF;
margin: 6px;
padding: 4px;
}
input[type=text]:focus,
input[type=password]:focus,
textarea:focus
{
background-color: #EEF;
border-color: #BBC;
}
input[type=submit]:hover,
input[type=button]:hover,
button:hover
{
cursor: pointer;
background-color: #DDE;
}
/* Grid */
table.grid
{
width: 95%;
margin: auto;
border-collapse: collapse;
text-align: center;
margin-bottom: 15px;
}
table.grid thead tr,
table.grid tfoot tr
{
/* background-color: #C0CCB3;
background: url(image/gradient.png) repeat-x scroll 0;
*/
background-color: #EEF;
border: 1px solid #CCD;
color: black;
font-weight: bold;
vertical-align: middle;
text-align: center;
height: 30px;
}
table.grid thead th
{
cursor: pointer;
}
table.grid thead th:hover
{
background-color: #DDE;
}
table.grid tr
{
height: 35px;
}
table.grid tfoot a,
table.grid thead a
{
color: black;
}
table.grid tr.pair-row
{
background-color: transparent;
}
td.grid-message
{
height: 80px;
}
td.grid-message img
{
vertical-align: middle;
padding: 10px;
}
table.grid tbody td
{
padding-right: 8px;
padding-left: 8px;
}
input.cell-spin
{
width: 25px;
text-align: right;
}
/* Calendar */
.calendar
{
width: 220px;
background-color: white;
border: 1px solid #CCD;
border-radius: 2px;
box-shadow: 0px 2px 2px #AAA;
}
.calendar table
{
border-collapse: collapse;
}
.calendar thead tr,
.calendar tfoot tr
{
background-color: #EEF;
color: black;
font-weight: normal;
vertical-align: middle;
text-align: center;
height: 30px;
}
.calendar thead tr
{
border-bottom: 1px solid #CCD;
}
.calendar tfoot tr
{
border-top: 1px solid #CCD;
}
.calendar col
{
width: 14.2%;
}
.calendar tr
{
height: 22px;
}
.calendar tbody td
{
text-align: right;
padding-right: 6px;
}
.calendar td.highlight,
.calendar td:hover,
.button:hover
{
cursor: pointer;
background-color: #DDE;
}
/* Date chooser */
.date-chooser button
{
margin: 0px;
margin-right: 8px;
}
/* Full image */
div.full-image
{
position: fixed;
background-color: #FFF;
text-align: center;
border: 1px solid #999;
border-radius: 2px;
z-index: 2;
}
div.image-loader
{
position: fixed;
background-color: #FFF;
border: 1px solid #999;
border-radius: 2px;
z-index: 3;
}
div.image-loader img
{
padding: 10px;
}
/* Image editor */
img.editable
{
cursor: pointer;
}
div.image-editor
{
background-color: #FFF;
border: 1px solid #999;
border-radius: 2px;
padding: 12px;
width: 400px;
}
div.image-editor h3
{
text-align: center;
padding-bottom: 10px;
}
div.image-editor iframe
{
display: none;
}
div.image-editor button
{
margin-left: 5px;
margin-right: 5px;
}
div.image-editor img
{
visibility: hidden;
vertical-align: middle;
padding-right: 10px;
}
/* Form */
table.form
{
border-collapse: separate;
border-spacing: 4px;
}
table.form td.label
{
text-align: right;
}
table.form tr
{
height: 35px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

BIN
usr/share/hedera-web/image/ok.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

73
usr/share/hedera-web/index.php Executable file
View File

@ -0,0 +1,73 @@
<?php
require_once ('php/web/hedera.php');
require_once ('php/web/js.php');
Hedera::init ();
if ($conf['https'] && (!isset ($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on'))
{
header ('Location: https://'.$_SERVER['SERVER_NAME']);
exit (0);
}
if (!Hedera::$sysConn->isOpen ())
{
header ('Location: unavailable.html');
exit (0);
}
// Getting the section
if (isset ($_GET['section']))
$section = $_GET['section'];
else
$section = 'login';
// Checking the browser version
if (!isset ($_SESSION['skipBrowser']) && $section != 'update-browser')
{
$updateBrowser = FALSE;
if (!isset ($_GET['skipBrowser'])
&& ($browser = get_browser ($_SERVER['HTTP_USER_AGENT'])))
{
$browserVersion = $browser->version;
set_type ($browserVersion, TYPE_DOUBLE);
$updateBrowser = isset ($conf['browser'][$browser->browser])
&& $browserVersion < $conf['browser'][$browser->browser];
}
if ($updateBrowser)
{
header ('Location: ?section=update-browser');
exit (0);
}
else
$_SESSION['skipBrowser'] = TRUE;
}
// Loading the section
$basePath = 'sections/'. $section;
setcookie ('hedera_version', $_SESSION['version']);
header ('Content-Type: text/html;charset=utf-8');
if (file_exists ($basePath))
{
Vn\Locale::loadFile ($basePath);
$phpFile = $basePath .'/'. $section .'.php';
if (file_exists ($phpFile))
require ($phpFile);
require ($basePath .'/html.php');
}
Hedera::deinit ();
?>

View File

@ -0,0 +1,59 @@
/**
* Computes a sum of data in the model.
**/
Db.CalcSum = new Class
({
Extends: Db.Calc
,Tag: 'db-calc-sum'
,getRowValue: function (row)
{
var value;
if (this._func)
{
this.form.row = row;
value = this._func (this.form);
}
else
value = this._model.getByIndex (row, this.columnIndex);
return value;
}
,before: function (row)
{
var value = this.getRowValue (row)
if (value !== null)
{
this.sum -= value;
this.numbersCount--;
}
}
,after: function (row)
{
var value = this.getRowValue (row);
if (value !== null)
{
this.sum += value;
this.numbersCount++;
}
}
,init: function ()
{
this.sum = 0;
this.numbersCount = 0;
}
,done: function ()
{
if (this.numbersCount > 0)
this.value = this.sum;
else
this.value = null;
}
});

View File

@ -0,0 +1,149 @@
/**
* Interface for handle foreach operations on the model.
**/
Db.Calc = new Class
({
Extends: Vn.Param
,Tag: 'db-calc'
,Properties:
{
model:
{
type: Db.Model
,set: function (x)
{
this.link ({_model: x},
{
'status-changed': this.onModelChange
,'row-deleted-before': this.onRowDeleteBefore
,'row-updated': this.onRowUpdate
,'row-updated-before': this.onRowUpdateBefore
,'row-inserted': this.onRowInsert
});
this.form = new Db.Form ({model: x});
}
,get: function ()
{
return this._model;
}
},
columnIndex:
{
type: Number
,set: function (x)
{
this.reset ();
this._columnIndex = x;
this.onModelChange ();
}
,get: function ()
{
return this._columnIndex;
}
},
columnName:
{
type: String
,set: function (x)
{
this.reset ();
this._columnName = x;
this.onModelChange ();
}
,get: function ()
{
return this._columnName;
}
},
func:
{
type: Function
,set: function (x)
{
this.reset ();
this._func = x;
this.onModelChange ();
}
,get: function ()
{
return this._func;
}
}
}
,_model: null
,reset: function ()
{
delete this._columnIndex;
delete this._columnName;
delete this._func;
}
,onModelChange: function ()
{
this.init ();
if (this._model)
{
if (this._model.ready && this._columnName)
this._columnIndex = this._model.getColumnIndex (this._columnName);
var rows = this._model.numRows;
for (var i = 0; i < rows; i++)
this.after (i);
}
this.done ();
}
,onRowInsert: function (model, row)
{
this.after (row);
this.done ();
}
,onRowUpdateBefore: function (model, row)
{
this.before (row);
}
,onRowUpdate: function (model, row)
{
this.after (row);
this.done ();
}
,onRowDeleteBefore: function (model, row)
{
this.before (row);
this.done ();
}
/**
* Called before each update or delete row operation.
* You don't need to define it if model isn't updatable.
*
* @param {integer} row The row number
**/
,before: function (row) {}
/**
* Called after each update or insert row operation.
*
* @param {integer} row The row number
**/
,after: function (row) {}
/**
* Called before each model refresh.
**/
,init: function () {}
/**
* Called when an operation in the model is complete.
**/
,done: function () {}
});

View File

@ -0,0 +1,241 @@
/**
* Simulates a connection to a database by making asynchronous http requests to
* a remote PHP script that returns the results in JSON format.
* Using this class can perform any operation that can be done with a database,
* like open/close a connection or selecion/updating queries.
**/
Db.Conn = new Class ().extend
({
Flag:
{
NOT_NULL : 1
,PRI_KEY : 2
,AI : 512 | 2 | 1
}
,Type:
{
TIMESTAMP : 7
,DATE : 10
,DATE_TIME : 12
}
});
Db.Conn.implement
({
Extends: Vn.Object
,connected: false
,requestsCount: 0
/**
* Initilizes the connection object.
**/
,initialize: function ()
{
this.parent ();
}
/**
* Opens the connection to the database.
*
* @param {string} user The user name
* @param {String} password The user password
* @param {Boolean} remember Specifies if the user should be remembered
* @param {Function} openCallback The function to call when operation is done
**/
,open: function (user, pass, remember, openCallback)
{
this.signalEmit ('loading-changed', true);
var request = new Db.HttpRequest ();
request.add ({'action': 'login'});
if (user != null)
{
request.add
({
'user': user
,'password': pass
,'remember': remember
});
}
request.send ('ajax.php',
this.opened.bind (this, request, openCallback));
}
/*
* Called when open operation is done.
*/
,opened: function (request, openCallback, success)
{
var openSuccess = false;
if (success)
try {
var json = request.getJson ();
openSuccess = true /* json.data */;
}
catch (e) {}
if (openSuccess)
this.connected = true;
this.signalEmit ('loading-changed', false);
if (openCallback)
openCallback (this, openSuccess);
}
/**
* Closes the connection to the database.
*
* @param {Function} closeCallback The function to call when operation is done
**/
,close: function (closeCallback)
{
this.signalEmit ('loading-changed', true);
var request = new Db.HttpRequest ();
request.addValue ({'action': 'close'});
request.send ('ajax.php',
this.closed.bind (this, closeCallback));
}
/*
* Called when close operation is done.
*/
,closed: function (closeCallback)
{
this.connected = false;
this.signalEmit ('closed');
this.signalEmit ('loading-changed', false);
if (closeCallback)
closeCallback (this);
}
/**
* Runs a SQL query on the database.
*
* @param {String} sql The SQL statement
* @param {Function} callback The function to call when operation is done
**/
,execSql: function (sql, callback)
{
this.requestsCount++;
if (this.requestsCount == 1)
this.signalEmit ('loading-changed', true);
var httpRequest = new Db.HttpRequest ()
httpRequest.add
({
'action': 'query'
,'sql': sql
});
httpRequest.send ('ajax.php',
this.execDone.bind (this, httpRequest, callback));
}
/**
* Runs a stmt on the database.
*
* @param {Sql.Stmt} stmt The statement
* @param {Function} callback The function to call when operation is done
* @param {Sql.Batch} batch The batch used to set the parameters
**/
,execStmt: function (stmt, callback, batch)
{
this.execSql (stmt.render (batch), callback);
}
/**
* Runs a query on the database.
*
* @param {String} query The SQL statement
* @param {Function} callback The function to call when operation is done
* @param {Sql.Batch} batch The batch used to set the parameters
**/
,execQuery: function (query, callback, batch)
{
this.execStmt (new Sql.String ({query: query}), callback, batch);
}
/*
* Parses a value to date.
*/
,valueToDate: function (value)
{
return new Date (value * 1000);
}
/*
* Called when a query is executed.
*/
,execDone: function (httpRequest, callback, success)
{
var error = null;
var results = null;
this.requestsCount--;
if (this.requestsCount == 0)
this.signalEmit ('loading-changed', false);
if (!success)
error = new Vn.Error ('Conn', 'connError', _('ConnError'));
else
try {
var json = httpRequest.getJson ();
results = json.data;
if (json.error !== null)
error = new Vn.Error (json.error.domain,
json.error.code, json.error.message);
if (results instanceof Array)
for (var i = 0; i < results.length; i++)
if (results[i] !== true)
{
var data = results[i].data;
var columns = results[i].field;
for (var j = 0; j < columns.length; j++)
{
var castFunc = null;
switch (columns[j].type)
{
case Db.Conn.Type.DATE:
case Db.Conn.Type.DATE_TIME:
case Db.Conn.Type.TIMESTAMP:
castFunc = this.valueToDate;
break;
}
if (castFunc !== null)
{
if (columns[j].def != null)
columns[j].def = castFunc (columns[j].def);
for (var k = 0; k < data.length; k++)
if (data[k][j] != null)
data[k][j] = castFunc (data[k][j]);
}
}
}
}
catch (e) {
error = new Vn.Error ('Conn', 'badReply', e.message /*_('BadServerReply')*/);
}
if (callback)
callback (new Db.ResultSet (results, error));
if (error)
this.signalEmit ('error', error);
}
});

View File

@ -0,0 +1,212 @@
Db.Form = new Class
({
Extends: Vn.Object
,Tag: 'db-form'
,Child: 'model'
,Properties:
{
/**
* The model associated to this form.
**/
model:
{
type: Db.Model
,set: function (x)
{
this.link ({_model: x},
{
'status-changed': this.onModelChange
,'row-updated': this.onRowUpdate
});
}
,get: function ()
{
return this._model;
}
},
/**
* The row where the form positioned, has -1 if the row is unselected.
**/
row:
{
type: Number
,set: function (x)
{
if (!this._model || this._model.numRows <= x || x < -1)
x = -1;
if (x == this._row)
return;
this._row = x;
this.iterChanged ();
}
,get: function ()
{
return this._row;
}
},
/**
* The number of rows in the form.
**/
numRows:
{
type: Number
,get: function ()
{
if (this._model)
return this._model.numRows;
return 0;
}
},
/**
* Checks if the form data is ready.
**/
ready:
{
type: Boolean
,get: function ()
{
return this._ready;
}
}
}
,lastRow: 0
,_model: null
,_row: -1
,_ready: false
,onModelChange: function ()
{
var ready = this._model && this._model.ready;
if (ready != this._ready)
{
if (this._row != -1)
this.lastRow = this._row;
this._ready = ready;
this.signalEmit ('status-changed');
if (this._row == -1)
this.row = this.lastRow;
}
}
,onRowUpdate: function (model, row, column)
{
if (row == this._row)
this.iterChanged ();
}
,refresh: function ()
{
if (this._model)
this._model.refresh ();
}
/**
* Emits the 'iter-changed' signal on the form.
**/
,iterChanged: function ()
{
this.signalEmit ('iter-changed');
}
/**
* Get the index of the column from its name.
*
* @param {String} columnName The column name
* @return {integer} The column index or -1 if column not exists
**/
,getColumnIndex: function (columnName)
{
if (this._model)
return this._model.getColumnIndex (columnName);
return -1;
}
,insertRow: function ()
{
if (this._model)
this.row = this._model.insertRow ();
}
,performOperations: function ()
{
if (this._model)
this._model.performOperations ();
}
/**
* Removes the current row.
**/
,deleteRow: function ()
{
if (this._row >= 0)
this._model.deleteRow (this._row);
}
/**
* Gets a value from the form.
*
* @param {String} columnName The column name
* @return {Object} The value
**/
,get: function (columnName)
{
return this._model.get (this._row, columnName);
}
/**
* Sets a value on the form.
*
* @param {String} columnName The column name
* @param {Object} value The new value
**/
,set: function (columnName, value)
{
return this._model.set (this._row, columnName, value);
}
/**
* Gets a value from the form using the column index.
*
* @param {String} columnName The column index
* @return {Object} The value
**/
,getByIndex: function (column)
{
return this._model.getByIndex (this._row, column);
}
/**
* Sets a value on the form using the column index.
*
* @param {String} columnName The column index
* @param {Object} value The new value
**/
,setByIndex: function (column, value)
{
return this._model.setByIndex (this._row, column, value);
}
/**
* Gets a param from the form.
*
* @param {String} columnName The column name
* @return {Db.Param} The new parameter
**/
,getParam: function (columnName)
{
return new Db.Param
({
form: this
,column: columnName
});
}
});

View File

@ -0,0 +1,30 @@
Db.HttpRequest = new Class
({
kvPairs: {}
,add: function (map)
{
for (var key in map)
this.kvPairs[key] = map[key];
}
,send: function (file, callback)
{
this.request = new XMLHttpRequest ();
this.request.open ('post', file, true);
this.request.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
this.request.onreadystatechange = this.requestStateChanged.bind (this, callback);
this.request.send (Vn.Url.makeUri (this.kvPairs));
}
,requestStateChanged: function (callback)
{
if (this.request.readyState == 4)
callback (this, this.request.status == 200);
}
,getJson: function ()
{
return eval (this.request.responseText);
}
});

View File

@ -0,0 +1,5 @@
/**
* The namespace.
**/
var Db = {};

View File

@ -0,0 +1,18 @@
<?php
require_once ('js/sql/main.php');
Js::includeLib ('db'
,'main'
,'conn'
,'result'
,'result-set'
,'model'
,'form'
,'param'
,'calc'
,'calc-sum'
,'http-request'
);
?>

View File

@ -0,0 +1,809 @@
Db.Model = new Class ().extend
({
Status:
{
CLEAN : 1
,LOADING : 2
,READY : 3
,ERROR : 4
}
});
Db.Model.implement
({
Extends: Vn.Object
,Tag: 'db-model'
,Child: 'query'
,Properties:
{
/**
* The connection used to execute the statement.
**/
conn:
{
type: Db.Conn
,set: function (x)
{
this._conn = x;
this.refresh ();
}
,get: function ()
{
return this._conn;
}
},
/**
* The result index.
**/
resultIndex:
{
type: Number
,set: function (x)
{
this._resultIndex = x;
}
,get: function ()
{
return this._resultIndex;
}
},
/**
* The batch used to execute the statement.
**/
batch:
{
type: Sql.Batch
,set: function (x)
{
this.link ({_batch: x}, {'changed': this.refresh});
this.refresh ();
}
,get: function ()
{
return this._batch;
}
},
/**
* The model select statement.
**/
stmt:
{
type: Sql.Stmt
,set: function (x)
{
this._stmt = x;
this.refresh ();
}
,get: function ()
{
return this._stmt;
}
},
/**
* The model query.
**/
query:
{
type: String
,set: function (x)
{
this._stmt = new Sql.String ({query: x});
}
,get: function ()
{
return this._stmt.render (null);
}
},
/**
* The main table.
**/
mainTable:
{
type: String
,set: function (x)
{
this._mainTable = null;
this.requestedMainTable = x;
this.refreshMainTable ();
}
,get: function ()
{
return this._mainTable;
}
},
/**
* Determines if the model is updatable.
**/
updatable:
{
type: Boolean
,set: function (x)
{
this._updatable = false;
this.requestedUpdatable = x;
this.refreshUpdatable ();
}
,get: function ()
{
return this._updatable;
}
},
/**
* The number of rows in the model.
**/
numRows:
{
type: Number
,get: function ()
{
if (this.data)
return this.data.length;
return 0;
}
},
/**
* The current status of the model.
**/
status:
{
type: Number
,get: function ()
{
return this._status;
}
},
/**
* Checks if the model data is ready.
**/
ready:
{
type: Boolean
,get: function ()
{
return this._status == Db.Model.Status.READY;
}
}
}
,_resultIndex: 0
,_batch: null
,_stmt: null
,_status: Db.Model.Status.CLEAN
,requestedMainTable: null
,requestedUpdatable: true
,data: null
,columns: null
,columnMap: null
,insertedRow: -1
,defaults: []
,columnDefaults: []
,sortColumn: -1
,initialize: function (props)
{
this.parent (props);
this.cleanData (false);
}
,loadXml: function (builder, node)
{
this.parent (builder, node);
var query = node.firstChild.nodeValue;
if (query)
this.query = query;
}
,refresh: function ()
{
if (this._stmt && this._batch)
this._stmt.findHolders (this._batch);
if (this._conn && this._stmt
&& (!this._batch || this._batch.isReady ()))
{
this.setStatus (Db.Model.Status.LOADING);
this._conn.execStmt (this._stmt, this.selectDone.bind (this), this._batch);
}
else
this.cleanData (false);
}
,cleanData: function (error)
{
this.data = null;
this.columns = null;
this.columnMap = null;
if (error)
this.setStatus (Db.Model.Status.ERROR);
else
this.setStatus (Db.Model.Status.CLEAN);
}
,refreshUpdatable: function ()
{
var oldValue = this._updatable;
this._updatable = this._mainTable != null && this.requestedUpdatable;
if (oldValue != this._updatable)
this.signalEmit ('updatable-changed');
}
,refreshMainTable: function ()
{
var newMainTable = null;
var newMainSchema = null;
var x = this.columns;
if (x)
for (var i = 0; i < x.length; i++)
if (x[i].flags & Db.Conn.Flag.PRI_KEY)
if (!this.requestedMainTable
|| x[i].table === this.requestedMainTable)
{
newMainTable = x[i].table;
break;
}
this._mainTable = newMainTable;
this.refreshUpdatable ();
}
/**
* Sets the default value for inserted fields.
*
* @param {String} field The destination field name
* @param {String} table The destination table name
* @param {Object} value The default value
**/
,setDefaultFromValue: function (field, table, value)
{
this.defaults.push
({
field: field
,table: table
,value: value
});
}
/**
* Sets the default value for inserted fields from another column in the
* model.
*
* @param {String} field The destination field name
* @param {String} table The destination table name
* @param {String} srcColumn The source column
**/
,setDefaultFromColumn: function (field, table, srcColumn)
{
this.columnDefaults.push
({
field: field
,table: table
,srcColumn: srcColumn
});
}
/**
* Checks if the column exists.
*
* @param {integer} column The column index
* @return {Boolean} %true if column exists, %false otherwise
**/
,checkColExists: function (column)
{
return this.columns && column >= 0 && column < this.columns.length;
}
/**
* Checks if the row exists.
*
* @param {integer} row The row index
* @return {Boolean} %true if row exists, %false otherwise
**/
,checkRowExists: function (row)
{
return this.data && row >= 0 && row < this.data.length;
}
,checkRowUpdatable: function (row)
{
return this.checkRowExists (row);
}
/**
* Get the index of the column from its name.
*
* @param {string} columnName The column name
* @return {number} The column index or -1 if column not exists
**/
,getColumnIndex: function (columnName)
{
var index;
if (this.columnMap
&& (index = this.columnMap[columnName]) !== undefined)
return index;
return -1;
}
/**
* Gets a value from the model.
*
* @param {number} row The row index
* @param {string} columnName The column name
* @return {mixed} The value
**/
,get: function (row, columnName)
{
var index = this.getColumnIndex (columnName);
if (index != -1)
return this.getByIndex (row, index);
return undefined;
}
/**
* Updates a value on the model.
*
* @param {number} row The row index
* @param {string} columnName The column name
* @param {mixed} value The new value
**/
,set: function (row, columnName, value)
{
var index = this.getColumnIndex (columnName);
if (index != -1)
this.setByIndex (row, index, value);
}
/**
* Gets a value from the model using the column index.
*
* @param {number} row The row index
* @param {number} column The column index
* @return {mixed} The value
**/
,getByIndex: function (row, column)
{
if (this.checkRowExists (row) && this.checkColExists (column))
return this.data[row][column];
return undefined;
}
/**
* Updates a value on the model using the column index.
*
* @param {number} row The row index
* @param {number} col The column index
* @param {mixed} value The new value
**/
,setByIndex: function (row, col, value)
{
if (!(this.checkRowUpdatable (row) && this.checkColExists (col)))
return;
if (row == this.insertedRow)
{
this.performUpdate (row, [col], [value]);
return;
}
var column = this.columns[col];
var where = this.getWhere (column.table, row, false);
if (where)
{
var multiStmt = new Sql.MultiStmt ();
var table = new Sql.Table
({
name: column.orgtable
,schema: column.db
});
var update = new Sql.Update ({where: where});
update.addTarget (table);
update.addSet (column.orgname, value);
multiStmt.addStmt (update);
var select = new Sql.Select ({where: where});
select.addTarget (table);
select.addField (column.orgname);
multiStmt.addStmt (select);
var updateData = {
row: row
,columns: [col]
};
this._conn.execStmt (multiStmt,
this.updateDone.bind (this, updateData));
}
else
this.sendInsert (column.table, row, col, value);
}
/**
* Deletes a row from the model.
*
* @param {number} row The row index
**/
,deleteRow: function (row)
{
if (!this.checkRowUpdatable (row))
return;
if (row != this.insertedRow)
{
var where = this.getWhere (this._mainTable, row, false);
if (where)
{
var deleteQuery = new Sql.Delete ({where: where});
var table = this.getTarget (this._mainTable);
deleteQuery.addTarget (table);
this._conn.execStmt (deleteQuery,
this.deleteDone.bind (this, row));
}
}
else
{
this.performDelete (row);
this.insertedRow = -1;
}
}
/**
* Inserts a new row on the model.
*
* @return The index of the inserted row
**/
,insertRow: function ()
{
if (!this._updatable || this.insertedRow != -1)
return -1;
var x = this.columns;
var newRow = new Array (x.length);
for (var i = 0; i < x.length; i++)
if (x[i].table === this._mainTable)
newRow[i] = x[i].def;
else
newRow[i] = null;
this.insertedRow = this.data.push (newRow) - 1;
this.signalEmit ('row-inserted', this.insertedRow);
return this.insertedRow;
}
,performOperations: function ()
{
if (this.insertedRow == -1)
return;
this.sendInsert (this._mainTable, this.insertedRow, -1, null);
}
/*
* Function used to sort the model.
*/
,sortFunction: function (column, a, b)
{
if (a[column] < b[column])
return -1;
else if (a[column] > b[column])
return 1;
return 0;
}
/**
* Orders the model by the specified column.
*
* @param {integer} column the column index
**/
,sort: function (column)
{
if (!this.checkColExists (column))
return;
this.setStatus (Db.Model.Status.LOADING);
if (column != this.sortColumn)
{
this.data.sort (this.sortFunction.bind (this, column));
this.sortColumn = column;
}
else
this.data.reverse ();
this.setStatus (Db.Model.Status.READY);
}
/**
* Searchs a value on the model and returns the row index of the first
* ocurrence.
*
* @param {String} column The column name
* @param {Object} value The value to search
* @return {integer} The column index
**/
,search: function (column, value)
{
var index = this.getColumnIndex (column);
return this.searchByIndex (index, value);
}
/**
* Searchs a value on the model and returns the row index of the first
* ocurrence.
*
* @param {integer} col The column index
* @param {Object} value The value to search
* @return {integer} The column index
**/
,searchByIndex: function (col, value)
{
if (!this.checkColExists (col))
return -1;
var data = this.data;
switch (this.columns[col].type)
{
case Db.Conn.Type.TIMESTAMP:
case Db.Conn.Type.DATE_TIME:
case Db.Conn.Type.DATE:
{
value = value.toString ();
for (var i = 0; i < data.length; i++)
if (value === data[i][col].toString ());
return i;
break;
}
default:
for (var i = 0; i < data.length; i++)
if (value === data[i][col])
return i;
}
return -1;
}
// private:
,setStatus: function (status)
{
this._status = status;
this.signalEmit ('status-changed', status);
}
,getTarget: function (table)
{
var x = this.columns;
for (var i = 0; i < x.length; i++)
if (x[i].table == table)
return new Sql.Table
({
name: x[i].orgtable
,schema: x[i].db
});
return null;
}
,getWhere: function (table, rowIndex, forInsert)
{
var keyFound = false;
var row = this.data[rowIndex];
var andOp = new Sql.Operation ({type: Sql.Operation.Type.AND});
var x = this.columns;
for (var i = 0; i < x.length; i++)
if (x[i].flags & Db.Conn.Flag.PRI_KEY
&& x[i].table === table)
{
var equalOp = new Sql.Operation ({type: Sql.Operation.Type.EQUAL});
equalOp.exprs.add (new Sql.Field ({name: x[i].orgname}));
andOp.exprs.add (equalOp);
if (row[i])
equalOp.exprs.add (new Sql.Value ({value: row[i]}));
else if (x[i].flags & Db.Conn.Flag.AI && forInsert)
equalOp.exprs.add (new Sql.Func ({name: 'LAST_INSERT_ID'}));
else
break;
keyFound = true;
}
return (keyFound) ? andOp : null;
}
,sendInsert: function (table, rowIndex, singleColumn, newValue)
{
var where = this.getWhere (table, rowIndex, true);
if (!where)
return;
var multiStmt = new Sql.MultiStmt ();
var target = this.getTarget (table);
var insert = new Sql.Insert ();
insert.addTarget (target);
multiStmt.addStmt (insert);
var x = this.defaults;
for (var i = 0; i < x.length; i++)
if (x[i].table === table)
insert.addSet (x[i].field, x[i].value);
var x = this.columnDefaults;
for (var i = 0; i < x.length; i++)
if (x[i].table === table)
insert.addSet (x[i].field, this.get (rowIndex, x[i].srcColumn));
var select = new Sql.Select ({where: where});
select.addTarget (target);
multiStmt.addStmt (select);
var columns = [];
var row = this.data[rowIndex];
var x = this.columns;
for (var i = 0; i < x.length; i++)
if (x[i].table === table)
{
var setValue = singleColumn != i ? row[i] : newValue;
if (setValue)
insert.addSet (x[i].orgname, setValue);
select.addField (x[i].orgname);
columns.push (i);
}
var updateData = {
row: rowIndex
,columns: columns
};
this._conn.execStmt (multiStmt,
this.updateDone.bind (this, updateData));
}
,selectDone: function (resultSet)
{
var result;
var dataResult;
for (var i = 0; result = resultSet.fetchResult (); i++)
if (i == this._resultIndex)
dataResult = result;
if (dataResult && typeof dataResult === 'object')
{
this.sortColumn = -1;
this.data = dataResult.data;
this.columns = dataResult.columns;
this.columnMap = dataResult.columnMap;
this.repairColumns (this.columns);
this.refreshMainTable ();
this.setStatus (Db.Model.Status.READY);
}
else
this.cleanData (true);
}
,updateDone: function (updateData, resultSet)
{
var newValues;
if (resultSet.fetchResult () && (newValues = resultSet.fetchRow ()))
this.performUpdate (updateData.row, updateData.columns, newValues);
}
,performUpdate: function (rowIndex, columns, newValues)
{
this.signalEmit ('row-updated-before', rowIndex);
var row = this.data[rowIndex];
for (var i = 0; i < columns.length; i++)
row[columns[i]] = newValues[i];
this.signalEmit ('row-updated', rowIndex, columns);
}
,deleteDone: function (rowIndex, resultSet)
{
if (resultSet.fetchResult())
this.performDelete (rowIndex);
}
,performDelete: function (rowIndex)
{
var row = this.data[rowIndex];
if (!this.requestedMainTable)
{
this.signalEmit ('row-deleted-before', rowIndex);
this.data.splice (rowIndex, 1);
this.signalEmit ('row-deleted', rowIndex);
}
else
{
this.signalEmit ('row-updated-before', rowIndex);
var columns = [];
for (var i = 0; i < this.columns.length; i++)
if (this.columns[i].table == this._mainTable)
{
row[i] = null;
columns.push (i);
}
this.signalEmit ('row-updated', rowIndex, columns);
}
}
// Delete when MySQL FLAG and view orgname "bugs" are repaired:
,tableInfo: {}
,fieldFlags: {}
,setTableInfo: function (table, orgtable, db)
{
this.tableInfo[table] =
({
orgtable: orgtable
,db: db
});
}
,setFieldFlags: function (field, flags)
{
this.fieldFlags[field] = flags;
}
,repairColumns: function (columns)
{
for (var i = 0; i < columns.length; i++)
{
var newFlags = this.fieldFlags[columns[i].name];
if (newFlags)
columns[i].flags |= newFlags;
var tableInfo = this.tableInfo[columns[i].table];
if (tableInfo)
{
columns[i].orgtable = tableInfo.orgtable;
columns[i].db = tableInfo.db;
}
}
}
});

View File

@ -0,0 +1,91 @@
Db.Param = new Class
({
Extends: Vn.Param
,Tag: 'db-param'
,Parent: 'form'
,Properties:
{
/**
* The form field referenced by this param.
**/
column:
{
type: String
,set: function (x)
{
this._columnName = x;
this.refresh ();
}
,get: function ()
{
this._columnName;
}
},
/**
* The form referenced by this param.
**/
form:
{
type: Db.Form
,set: function (x)
{
this.link ({_form: x},
{
'status-changed': this.onFormChange
,'iter-changed': this.onIterChange
});
this.refresh ();
}
,get: function ()
{
return this._form;
}
}
}
,_columnName: null
,_form: null
,formLock: false
,columnIndex: -1
,initialize: function (props)
{
this.parent (props);
this.on ('changed', this.onChange, this);
}
,refresh: function ()
{
if (this._form)
{
this.onFormChange ();
this.onIterChange ();
}
}
,onFormChange: function ()
{
if (this._columnName != null)
this.columnIndex = this._form.getColumnIndex (this._columnName);
}
,onIterChange: function ()
{
this.formLock = true;
if (this.columnIndex != -1)
this.value = this._form.getByIndex (this.columnIndex);
else
this.value = undefined;
this.formLock = false;
}
,onChange: function ()
{
if (!this.formLock && this.columnIndex != -1)
this._form.setByIndex (this.columnIndex, this._value);
}
});

View File

@ -0,0 +1,89 @@
/**
* This class stores the database results.
**/
Db.ResultSet = new Class
({
results: null
,error: null
/**
* Initilizes the resultset object.
**/
,initialize: function (results, error)
{
this.results = results;
this.error = error;
}
/**
* Gets the query error.
*
* @return {Db.Err} the error or null if no errors hapened
**/
,getError: function ()
{
return this.error;
}
,fetch: function ()
{
if (this.results !== null
&& this.results.length > 0)
return this.results.shift ();
return null;
}
/**
* Fetchs the next result from the resultset.
*
* @return {Db.Result} the result or %null if error or there are no more results
**/
,fetchResult: function ()
{
var result = this.fetch ();
if (result !== null)
{
if (result.data instanceof Array)
return new Db.Result (result.data, result.field);
else
return true;
}
return null;
}
/**
* Fetchs the first row from the next resultset.
*
* @return {Array} the row if success, %null otherwise
**/
,fetchRow: function ()
{
var result = this.fetch ();
if (result !== null
&& result.data instanceof Array
&& result.data.length > 0)
return result.data[0];
return null;
}
/**
* Fetchs the first row and column value from the next resultset.
*
* @return {Object} the value if success, %null otherwise
**/
,fetchValue: function ()
{
var row = this.fetchRow ();
if (row instanceof Array && row.length > 0)
return row[0];
return null;
}
});

View File

@ -0,0 +1,58 @@
/**
* This class stores a database result.
**/
Db.Result = new Class
({
/**
* Initilizes the result object.
**/
initialize: function (data, columns)
{
this.data = data;
this.columns = columns;
this.columnMap = null;
this.row = -1;
if (columns)
{
this.columnMap = {};
for (var i = 0; i < columns.length; i++)
this.columnMap[columns[i].name] = i;
}
}
/**
* Gets a value from de result.
*
* @param {String} columnName The column name
* @return {Object} The cell value
**/
,get: function (columnName)
{
var columnIndex = this.columnMap[columnName];
return this.data[this.row][columnIndex];
}
/**
* Resets the result iterator.
**/
,reset: function ()
{
this.row = -1;
}
/**
* Moves the internal iterator to the next row.
**/
,next: function ()
{
this.row++;
if (this.row >= this.data.length)
return false;
return true;
}
});

View File

@ -0,0 +1,84 @@
/**
* Represents a grid column. This is an abstract class and should not be
* instantiated directly.
**/
Htk.Column = new Class
({
Extends: Vn.Object
,Tag: 'htk-column'
,Parent: 'grid'
,Properties:
{
value:
{
type: Object
,value: null
},
column:
{
type: String
,value: null
},
columnIndex:
{
type: Number
,value: -1
},
title:
{
type: String
,value: null
},
editable:
{
type: Boolean
,value: false
},
renderer:
{
type: Function
,value: false
},
grid:
{
type: Htk.Grid
,set: function (x)
{
if (x)
x.appendColumn (this);
}
}
}
/**
* Initializes the column.
**/
,initialize: function (props)
{
this.parent (props);
this.td = document.createElement ('td');
}
/**
* Draws the cell and returns its associated td.
*
* @param {HTMLTableRow} tr the table row
* @return {HTMLTableData} the rendered cell
**/
,render: function (tr)
{
return this.td.cloneNode (true);
}
,updateColumnIndex: function (model)
{
if (this.column)
this.columnIndex = model.getColumnIndex (this.column);
}
,changed: function (tr, newValue)
{
this.signalEmit ('changed', tr.rowIndex - 1, newValue);
}
});

View File

@ -0,0 +1,46 @@
Htk.ColumnButton = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-button'
,Properties:
{
image:
{
type: String
,value: null
},
tip:
{
type: String
,value: null
}
}
,render: function (tr)
{
var td = this.parent (tr);
var button = document.createElement ('button');
button.className = 'cell-button';
button.addEventListener ('click',
this.buttonClicked.bind (this, this.value, tr));
td.appendChild (button);
var img = document.createElement ('img');
img.src = this.image;
button.appendChild (img);
if (this.tip)
{
button.title = _(this.tip);
img.title = _(this.tip);
}
return td;
}
,buttonClicked: function (value, tr)
{
this.signalEmit ('clicked', value, tr.rowIndex - 1);
}
});

View File

@ -0,0 +1,28 @@
Htk.ColumnCheck = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-check'
,render: function (tr)
{
var checkButton = document.createElement ('input');
checkButton.type = 'checkbox';
checkButton.checked = this.value;
if (this.editable)
checkButton.addEventListener ('changed',
this.inputChanged.bind (this, tr, node));
else
checkButton.disabled = true;
var td = this.parent (tr);
td.style.textAlign = 'center';
td.appendChild (checkButton);
return td;
}
,inputChanged: function (tr, node)
{
this.changed (tr, node.value);
}
});

View File

@ -0,0 +1,36 @@
Htk.ColumnDate = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-date'
,Properties:
{
/**
* Format that applies to the value.
**/
format:
{
type: String
,set: function (x)
{
this._format = _(x);
}
,get: function ()
{
return this._format;
}
}
}
,_format: _('%a, %e %b %Y')
,render: function (tr)
{
var text = Vn.Date.strftime (this.value, this._format);
var td = this.parent (tr);
td.style.textAlign = 'left';
td.appendChild (document.createTextNode (text));
return td;
}
});

View File

@ -0,0 +1,159 @@
Htk.ColumnImage = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-image'
,Properties:
{
/**
* The directory where the images are allocated.
**/
directory:
{
type: String
,set: function (x)
{
this._directory = x;
this.basedir = Vn.Config['imageDir'] +'/'+ x;
}
,get: function ()
{
return this._directory;
}
},
/**
* The directory where the images are allocated.
**/
subdir:
{
type: String
,value: null
},
/**
* Whether to show the full image when mouse hover.
**/
showFull:
{
type: Boolean
,value: false
}
}
,_directory: null
,basedir: null
,initialize: function (props)
{
this.parent (props);
this.fullImage = new Htk.FullImage ();
}
,render: function (tr)
{
var td = this.parent (tr);
var img = document.createElement ('img');
img.alt = ''
td.appendChild (img);
var cell =
{
img: img
,value: this.value
,tr: tr
,error: false
};
this.setSrc (cell, true);
if (this.showFull)
{
img.addEventListener ('mouseover', this.onMouseOver.bind (this, cell));
img.addEventListener ('mouseout', this.onMouseOut.bind (this));
}
if (this.editable)
{
img.className = 'editable';
img.addEventListener ('dblclick', this.onDoubleClick.bind (this, cell));
img.addEventListener ('error', this.onImageError.bind (this, cell));
}
return td;
}
,setSrc: function (cell, useCache)
{
if (cell.value)
{
var url = this.basedir +'/';
if (this.subdir)
url += this.subdir +'/';
url += cell.value;
if (!useCache)
query = '?'+ new Date ().getTime ();
cell.img.src = url;
}
else
this.onImageError (cell);
}
,onImageError: function (cell)
{
if (!cell.error)
{
cell.error = true;
cell.img.src = 'image/empty.png';
}
}
,onMouseOver: function (cell)
{
if (!cell.error)
this.fullImage.show (this.basedir, cell.value);
}
,onMouseOut: function ()
{
this.fullImage.hide ();
}
,onDoubleClick: function (cell)
{
var editor = new Htk.ImageEditor ();
editor.setData (cell.value, this._directory);
this.nameChangedHandler = this.onNameChange.bind (this, cell);
editor.on ('name-changed', this.nameChangedHandler);
this.fileUploadedHandler = this.onFileUpload.bind (this, cell);
editor.on ('file-uploaded', this.fileUploadedHandler);
this.editorClosedHandler = this.onEditorClose.bind (this);
editor.on ('closed', this.editorClosedHandler);
editor.showPopup (cell.img);
}
,onNameChange: function (cell, editor, value)
{
cell.value = value;
this.setSrc (cell, true);
this.changed (cell.tr, value);
}
,onFileUpload: function (cell, editor)
{
this.setSrc (cell, false);
editor.hidePopup ();
}
,onEditorClose: function (editor)
{
editor.disconnect ('name-changed', this.nameChangedHandler);
editor.disconnect ('file-uploaded', this.fileUploadedHandler);
editor.disconnect ('closed', this.editorClosedHandler);
}
});

View File

@ -0,0 +1,41 @@
Htk.ColumnLink = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-link'
,Properties:
{
/**
* The link url.
**/
href:
{
type: String
,value: null
},
/**
* the target where the link is opened.
**/
target:
{
type: String
,value: null
}
}
,render: function (tr)
{
var link = document.createElement ('a');
link.href = this.href;
link.appendChild (document.createTextNode (this.value));
if (this.target)
link.target = this.target;
var td = this.parent (tr);
td.style.textAlign = 'left';
td.appendChild (link);
return td;
}
});

View File

@ -0,0 +1,37 @@
Htk.ColumnRadio = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-radio'
,Properties:
{
param:
{
type: Vn.Param
,set: function (x)
{
this.radioGroup.master = x;
}
}
}
,initialize: function (props)
{
this.parent (props);
this.radioGroup = new Htk.RadioGroup ();
}
,render: function (tr)
{
var td = this.parent (tr);
var radio = this.radioGroup.createButton (this.value);
td.appendChild (radio);
return td;
}
,clear: function ()
{
this.radioGroup.clear ();
}
});

View File

@ -0,0 +1,61 @@
Htk.ColumnSpin = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-spin'
,Properties:
{
/**
* The text to append to the number.
**/
unit:
{
type: String
,value: null
},
/**
* The number of decimal places to display.
**/
digits: {
type: Number
,value: 0
}
}
,render: function (tr)
{
var td = this.parent (tr);
td.style.textAlign = 'right';
var valueString = null;
if (this.value !== null && this.value !== undefined)
valueString = new Number (this.value).toFixed (this.digits);
if (this.editable)
{
var entry = document.createElement ('input');
entry.type = 'text';
entry.className = 'cell-spin';
entry.addEventListener ('change', this.inputChanged.bind (this, tr, entry));
td.appendChild (entry);
if (valueString)
entry.value = valueString;
}
else if (valueString)
{
if (this.unit)
valueString = valueString +' '+ this.unit;
var text = document.createTextNode (valueString);
td.appendChild (text);
}
return td;
}
,inputChanged: function (tr, entry)
{
this.changed (tr, parseInt (entry.value));
}
});

View File

@ -0,0 +1,55 @@
Htk.ColumnText = new Class
({
Extends: Htk.Column
,Tag: 'htk-column-text'
,Properties:
{
/**
* Format that applies to the value.
**/
format:
{
type: String
,set: function (x)
{
this._format = _(x);
}
,get: function ()
{
return this._format;
}
}
}
,_format: null
,render: function (tr)
{
var node;
if (this.editable)
{
var value = this.value ? this.value : '';
node = document.createElement ('input');
node.type = 'text';
node.className = 'cell-text';
node.value = value;
node.addEventListener ('changed',
this.inputChanged.bind (this, tr, node));
}
else
node = document.createTextNode (
Vn.Value.format (this.value, this._format));
var td = this.parent (tr);
td.style.textAlign = 'left';
td.appendChild (node);
return td;
}
,inputChanged: function (tr, node)
{
this.changed (tr, node.value);
}
});

View File

@ -0,0 +1,131 @@
Htk.Field = new Class
({
Extends: Htk.Widget
,Tag: 'htk-field'
,Child: 'param'
,Properties:
{
value:
{
type: Object
,set: function (x)
{
if (Vn.Value.compare (x, this._value))
return;
if (x instanceof Date)
x = x.clone ();
this.valueChanged (x);
this.putValue (x);
}
,get: function (x)
{
return this._value;
}
},
param:
{
type: Vn.Param
,set: function (x)
{
this.link ({_param: x}, {'changed': this.onParamChange});
this.onParamChange ();
}
,get: function ()
{
return this._param;
}
},
editable:
{
type: Boolean
,set: function (x)
{
if (x != this._editable)
{
this._editable = x;
this.setEditable (x);
}
}
,get: function ()
{
return this._editable;
}
},
form:
{
type: Db.Form
,set: function (x)
{
this._form = x;
this.bindToForm ();
}
},
column:
{
type: String
,set: function (x)
{
this._paramName = x;
this.bindToForm ();
}
}
}
,_value: undefined
,_param: null
,_editable: true
,ignoreParamChange: false
,onParamChange: function ()
{
this.ignoreParamChange = true;
this.value = this._param.value;
this.ignoreParamChange = false;
}
,bindToForm: function ()
{
if (this._form && this._paramName)
this.param = this._form.getParam (this._paramName);
}
/**
* Virtual method that must be implemented by class childs to set the entry
* editable.
*
* @param {Boolean} editable Whether the user is allowed to edit the entry
**/
,setEditable: function (editable) {}
/**
* Virtual method that must be implemented by class childs to put the value
* on the associated entry.
*
* @param {Object} value The new value for the entry
**/
,putValue: function (value) {}
/**
* Protected method that should be called from class childs when the value
* on the associated entry changes.
*
* @param {Object} value The new entry value
**/
,valueChanged: function (value)
{
this._value = value;
if (this._param && !this.ignoreParamChange)
{
this._param.blockSignal ('changed', this.onParamChange, true);
this._param.value = value;
this._param.blockSignal ('changed', this.onParamChange, false);
}
this.signalEmit ('changed');
}
});

View File

@ -0,0 +1,265 @@
Htk.Calendar = new Class
({
Extends: Htk.Field
,Implements: Htk.Popup
,Tag: 'htk-calendar'
,tds: []
,selectedTd: null
,todayTd: null
,year: null
,month: null
,initialize: function (props)
{
this.parent (props);
var len = Vn.Date.WDays.length;
this.createElement ('div');
this.node.className = 'calendar';
var table = document.createElement ('table');
this.node.appendChild (table);
var colgroup = document.createElement ('colgroup');
table.appendChild (colgroup);
for (var i = 0; i < len; i++)
colgroup.appendChild (document.createElement ('col'));
var thead = document.createElement ('thead');
table.appendChild (thead);
var tr = document.createElement ('tr');
thead.appendChild (tr);
var th = document.createElement ('th');
th.appendChild (document.createTextNode ('<'));
th.className = 'button';
th.addEventListener ('click', this.prevMonthClicked.bind (this));
tr.appendChild (th);
var monthNode = document.createElement ('th');
monthNode.colSpan = 5;
tr.appendChild (monthNode);
var th = document.createElement ('th');
th.appendChild (document.createTextNode ('>'));
th.className = 'button';
th.addEventListener ('click', this.nextMonthClicked.bind (this));
tr.appendChild (th);
var tr = document.createElement ('tr');
thead.appendChild (tr);
for (i = 1; i <= len; i++)
{
var th = document.createElement ('th');
tr.appendChild (th);
var weekday = Vn.Date.AbrWDays [i%len];
th.appendChild (document.createTextNode (weekday));
}
var tfoot = document.createElement ('tfoot');
table.appendChild (tfoot);
var tr = document.createElement ('tr');
tfoot.appendChild (tr);
var th = document.createElement ('th');
th.appendChild (document.createTextNode ('<'));
th.className = 'button';
th.addEventListener ('click', this.prevYearClicked.bind (this));
tr.appendChild (th);
var yearNode = document.createElement ('th');
yearNode.colSpan = 5;
tr.appendChild (yearNode);
var th = document.createElement ('th');
th.appendChild (document.createTextNode ('>'));
th.className = 'button';
th.addEventListener ('click', this.nextYearClicked.bind (this));
tr.appendChild (th);
var tbody = document.createElement ('tbody');
table.appendChild (tbody);
for (i = 0; i < 6; i++)
{
tr = document.createElement ('tr');
tbody.appendChild (tr);
for (j = 0; j < len; j++)
{
td = document.createElement ('td');
td.addEventListener ('click', this.dayClicked.bind (this, td, i*len+j));
tr.appendChild (td);
this.tds.push (td);
}
}
this.monthNode = monthNode;
this.yearNode = yearNode;
this.goToCurrentMonth ();
}
,getFirstWeekDay: function ()
{
var weekDay = new Date (this.year, this.month, 1).getDay ();
return (weekDay != 0) ? weekDay - 1 : 6;
}
,getMonthDays: function ()
{
if (this.month > 6)
return (this.month % 2 != 0) ? 31 : 30;
else if (this.month != 1)
return (this.month % 2 != 1) ? 31 : 30;
else
return (this.year % 4 != 0) ? 28 : 29;
}
,goToMonth: function (year, month)
{
if (year)
this.year = year;
if (month)
this.month = month;
this.refresh ();
}
,goToSelectedMonth: function ()
{
var date = this._value;
if (date instanceof Date)
this.goToMonth (date.getFullYear (), date.getMonth ());
else
this.goToCurrentMonth ();
}
,goToCurrentMonth: function ()
{
var date = new Date ();
this.goToMonth (date.getFullYear (), date.getMonth ());
}
,refresh: function ()
{
Vn.Node.setText (this.yearNode, this.year);
Vn.Node.setText (this.monthNode, Vn.Date.Months[this.month]);
var firstWeekDay = this.getFirstWeekDay ();
var monthDays = this.getMonthDays ();
var day = 1;
for (i = 0; i < this.tds.length; i++)
{
if (firstWeekDay <= i && day <= monthDays)
Vn.Node.setText (this.tds[i], day++);
else
Vn.Node.removeChilds (this.tds[i]);
}
// Marks the current day
var today = new Date ();
if (this.year == today.getFullYear ()
&& this.month == today.getMonth ())
{
var tdIndex = (firstWeekDay + today.getDate ()) - 1;
this.tds[tdIndex].style.fontWeight = 'bold';
this.todayTd = this.tds[tdIndex];
}
else if (this.todayTd)
{
this.todayTd.style.fontWeight = '';
this.todayTd = null;
}
// Marks the selected day
var date = this._value;
if (date instanceof Date
&& this.year == date.getFullYear ()
&& this.month == date.getMonth ())
{
var tdIndex = (firstWeekDay + date.getDate ()) - 1;
this.selectTd (this.tds[tdIndex]);
}
else
this.selectTd (null);
}
,selectTd: function (td)
{
if (this.selectedTd)
this.selectedTd.className = undefined;
if (td)
td.className = 'highlight';
this.selectedTd = td;
}
,putValue: function (value)
{
this.goToSelectedMonth ();
}
,dayClicked: function (td, tdIndex)
{
var monthDay = (tdIndex - this.getFirstWeekDay ()) + 1;
if (monthDay >= 1 && monthDay <= this.getMonthDays ())
{
this.selectTd (td);
var newDate = new Date (this.year, this.month, monthDay);
this.valueChanged (newDate);
}
}
,prevMonthClicked: function ()
{
if (this.month > 0)
this.month--;
else
{
this.month = 11;
this.year--;
}
this.refresh ();
}
,nextMonthClicked: function ()
{
if (this.month < 11)
this.month++;
else
{
this.month = 0;
this.year++;
}
this.refresh ();
}
,prevYearClicked: function ()
{
this.year--;
this.refresh ();
}
,nextYearClicked: function ()
{
this.year++;
this.refresh ();
}
});

View File

@ -0,0 +1,31 @@
Htk.Check = new Class
({
Extends: Htk.Field
,Tag: 'htk-check'
,initialize: function (props)
{
this.parent (props);
this.createElement ('input');
this.node.type = 'checkbox';
this.node.addEventListener ('change', this.changed.bind (this));
}
,changed: function ()
{
this.valueChanged (this.node.checked);
}
,putValue: function (value)
{
if (value)
this.node.checked = true;
else
this.node.checked = false;
}
,setEditable: function (editable)
{
this.node.disabled = !editable;
}
});

View File

@ -0,0 +1,82 @@
Htk.DateChooser = new Class
({
Extends: Htk.Field
,Tag: 'htk-date-chooser'
,format: _('%a, %e %b %Y')
,calendar: null
,ignoreCalendarChange: false
,initialize: function (props)
{
this.parent (props);
this.createElement ('div');
this.node.className = 'date-chooser';
this.label = document.createElement ('label');
this.node.appendChild (this.label);
this.setEditable (this._editable);
}
,putValue: function (value)
{
var dateString;
if (value instanceof Date)
dateString = Vn.Date.strftime (value, this.format);
else
dateString = '';
Vn.Node.setText (this.label, dateString);
}
,setEditable: function (editable)
{
if (editable && !this.calendar)
{
this.button = document.createElement ('button');
this.button.title = _('ChangeDate');
this.button.addEventListener ('click', this.showCalendar.bind (this));
this.node.insertBefore (this.button, this.label);
var img = document.createElement ('img');
img.alt = _('ChangeDate');
img.src = 'image/calendar.png';
this.button.appendChild (img);
var calendar = new Htk.Calendar ();
calendar.on ('changed', this.calendarChanged.bind (this));
this.calendar = calendar;
}
else if (!editable)
{
this.calendar = null;
this.node.removeChild (this.button);
}
}
,calendarChanged: function (calendar)
{
if (this.ignoreCalendarChange)
return;
this.calendar.hidePopup ();
var newDate = calendar.value;
this.putValue (newDate);
this.valueChanged (newDate);
}
,showCalendar: function (event)
{
this.ignoreCalendarChange = true;
this.calendar.value = this._value;
this.calendar.goToSelectedMonth ();
this.ignoreCalendarChange = false;
this.calendar.showPopup (this.button);
}
});

View File

@ -0,0 +1,38 @@
Htk.Entry = new Class
({
Extends: Htk.Field
,Tag: 'htk-entry'
,initialize: function (props)
{
this.parent (props);
this.createElement ('input');
this.node.type = 'text';
this.node.addEventListener ('change', this.changed.bind (this));
}
,changed: function (event)
{
var newValue;
if (this.node.value == '')
newValue = null;
else
newValue = this.node.value;
this.valueChanged (newValue);
}
,putValue: function (value)
{
if (!value)
this.node.value = '';
else
this.node.value = value;
}
,setEditable: function (editable)
{
this.node.readOnly = !editable;
}
});

View File

@ -0,0 +1,139 @@
Htk.FullImage = new Class
({
Extends: Vn.Object
,img: null
,timeout: 0
,loading: false
,visible: false
,hideCalled: false
,initialize: function (props)
{
this.parent (props);
var div = document.createElement ('div');
div.className = 'full-image';
var loadingBox = document.createElement ('div');
loadingBox.className = 'image-loader';
var loadingImg = document.createElement ('img');
loadingImg.src = 'image/loader-black.gif';
loadingImg.alt = _('Loading');
loadingBox.appendChild (loadingImg);
this.div = div;
this.loadingBox = loadingBox;
this.loadingImg = loadingImg;
}
,getLeft: function (width)
{
return parseInt (getPageXOffset () + (getInnerWidth () - width) / 2) +'px';
}
,getTop: function (height)
{
return parseInt (getPageYOffset () + (getInnerHeight () - height) / 2) +'px';
}
,show: function (basedir, file)
{
if (this.timeout)
{
clearTimeout (this.timeout);
this.timeout = 0;
}
this.hideCalled = false;
this.loadingBox.style.left = this.getLeft (40);
this.loadingBox.style.top = this.getTop (40);
this.img = document.createElement ('img');
this.img.src = basedir +'/full/'+ file;
this.img.addEventListener ('load', this.imageLoaded.bind (this, this.img));
this.img.addEventListener ('error', this.hideLoading.bind (this));
if (!this.img.complete && !this.loading)
{
document.body.appendChild (this.loadingBox);
this.loading = true;
}
}
,imageLoaded: function (img)
{
if (img != this.img || this.hideCalled)
return;
var scale = 1.0;
var width = img.width;
var height = img.height;
var innerWidth = getInnerWidth () - 350;
var innerHeight = getInnerHeight () - 120;
if (width > innerWidth)
{
scale = width / innerWidth;
height = parseInt (height / scale);
width = innerWidth;
}
if (height > innerHeight)
{
scale = height / innerHeight;
width = parseInt (width / scale);
height = innerHeight;
}
this.hideLoading ();
this.div.style.left = this.getLeft (width + 2);
this.div.style.top = this.getTop (height + 2);
this.div.style.width = width +'px';
this.div.style.height = height +'px';
if (scale !== 1.0)
{
img.style.width = width +'px';
img.style.height = height +'px';
}
if (this.div.firstChild != null)
this.div.replaceChild (img, this.div.firstChild);
else
this.div.appendChild (img);
if (!this.visible)
{
document.body.appendChild (this.div);
this.visible = true;
}
}
,hide: function ()
{
this.hideCalled = true;
this.hideLoading ();
if (this.visible)
this.timeout = setTimeout (this.hideTimeout.bind (this), 450);
}
,hideTimeout: function ()
{
document.body.removeChild (this.div);
this.visible = false;
this.timeout = 0;
}
,hideLoading: function ()
{
if (this.loading)
{
document.body.removeChild (this.loadingBox);
this.loading = false;
}
}
});

View File

@ -0,0 +1,141 @@
Htk.ImageEditor = new Class
({
Extends: Htk.Widget
,Implements: Htk.Popup
,maxFileSize: 4 * 1024 * 1024 // 4 MegaBytes
,initialize: function ()
{
this.createElement ('div');
this.node.className = 'image-editor';
var h3 = document.createElement ('h3');
h3.appendChild (document.createTextNode (_('UpdateImage')));
this.node.appendChild (h3);
var iframe = document.createElement ('iframe');
iframe.name = 'image-editor';
iframe.addEventListener ('load', this.imageUploaded.bind (this, iframe));
this.node.appendChild (iframe);
var form = document.createElement ('form');
form.method = 'post';
form.action = 'ajax.php?action=image';
form.target = 'image-editor';
form.enctype = 'multipart/form-data';
form.addEventListener ('submit', this.formSubmit.bind (this));
this.node.appendChild (form);
var directoryInput = document.createElement ('input');
directoryInput.type = 'hidden';
directoryInput.name = 'directory';
form.appendChild (directoryInput);
var input = document.createElement ('input');
input.type = 'hidden';
input.name = 'MAX_FILE_SIZE';
input.value = this.maxFileSize;
form.appendChild (input);
var table = document.createElement ('table');
table.cellSpacing = 5;
form.appendChild (table);
var tbody = document.createElement ('tbody');
table.appendChild (tbody);
var tr = document.createElement ('tr');
tbody.appendChild (tr);
var td = document.createElement ('td');
td.style.textAlign = 'right';
td.appendChild (document.createTextNode (_('FileName') + ':'));
tr.appendChild (td);
var td = document.createElement ('td');
tr.appendChild (td);
var nameInput = document.createElement ('input');
nameInput.type = 'text';
nameInput.name = 'name';
nameInput.addEventListener ('change', this.nameChanged.bind (this));
td.appendChild (nameInput);
var tr = document.createElement ('tr');
tbody.appendChild (tr);
var td = document.createElement ('td');
td.style.textAlign = 'right';
td.appendChild (document.createTextNode (_('File') + ':'));
tr.appendChild (td);
var td = document.createElement ('td');
tr.appendChild (td);
var fileInput = document.createElement ('input');
fileInput.type = 'file';
fileInput.name = 'image';
td.appendChild (fileInput);
var tr = document.createElement ('tr');
tbody.appendChild (tr);
var td = document.createElement ('td');
td.style.textAlign = 'center';
td.colSpan = 2;
tr.appendChild (td);
var loader = document.createElement ('img');
loader.alt = _('Loading');
loader.src = 'image/loader-black.gif';
td.appendChild (loader);
var submitButton = document.createElement ('input');
submitButton.type = 'submit';
submitButton.appendChild (document.createTextNode (_('UploadFile')));
td.appendChild (submitButton);
this.directoryInput = directoryInput;
this.fileInput = fileInput;
this.nameInput = nameInput;
this.submitButton = submitButton;
this.loader = loader;
}
,setData: function (image, directory)
{
this.nameInput.value = image;
this.directoryInput.value = directory;
}
,formSubmit: function ()
{
this.submitButton.disabled = true;
this.loader.style.visibility = 'visible';
}
,imageUploaded: function (iframe)
{
this.submitButton.disabled = false;
this.loader.style.visibility = 'hidden';
var text = iframe.contentDocument.body.textContent;
if (!text)
return;
if (text != 1)
{
alert (text);
return;
}
this.signalEmit ('file-uploaded', this.nameInput.value);
}
,nameChanged: function ()
{
this.signalEmit ('name-changed', this.nameInput.value);
}
});

View File

@ -0,0 +1,93 @@
Htk.Image = new Class
({
Extends: Htk.Entry
,Tag: 'htk-image'
,empty: false
,file: null
,initialize: function (props)
{
this.parent (props);
this.node = document.getElementById (nodeId);
this.node.addEventListener ('error', this.error.bind (this));
}
,error: function ()
{
if (!this.empty)
{
this.empty = true;
this.node.src = 'image/empty.png';
}
}
,render: function (force)
{
if (this.realValue)
{
this.file = this.realValue + '.png';
if (force)
this.file += '?' + (new Date()).getTime ();
this.empty = false;
this.node.src = this.url + '/' + this.file;
}
else
{
this.file = null;
this.error ();
}
}
,setRealValue: function (value)
{
this.render (false);
}
,setShowFull: function (show)
{
if (show)
{
var obj = this;
this.node.addEventListener ('mouseover',
function () { obj.mouseOver () }, false);
this.node.addEventListener ('mouseout',
function () { obj.mouseOut () }, false);
}
}
,setEditable: function (editable)
{
if (editable)
{
var obj = this;
this.style.cursor = 'pointer';
this.node.addEventListener ('dblclick',
function (e) { obj.dblClicked (e) }, false);
}
}
,dblClicked: function (event)
{
var form = htkImageForm.node;
form.style.top = getPageYOffset () + (event.clientY - 80) + 'px';
form.style.left = (event.clientX + 30) + 'px';
document.body.appendChild (form);
htkImageForm.load (this);
}
,mouseOver: function ()
{
if (!this.empty)
htkImageFull.show (this.url + '/../full', this.file);
}
,mouseOut: function ()
{
if (!this.empty)
htkImageFull.hide ();
}
});

View File

@ -0,0 +1,37 @@
Htk.Label = new Class
({
Extends: Htk.Field
,Tag: 'htk-label'
,Properties:
{
/**
* Format that applies to the value.
**/
format:
{
type: String
,set: function (x)
{
this._format = _(x);
}
,get: function ()
{
return this._format;
}
}
}
,_format: null
,initialize: function (props)
{
this.parent (props);
this.createElement ('label');
}
,putValue: function (value)
{
Vn.Node.setText (this.node,
Vn.Value.format (value, this._format));
}
});

View File

@ -0,0 +1,57 @@
Htk.Radio = new Class
({
Extends: Vn.Object,
Implements: Vn.Param
,Tag: 'htk-radio'
,initialize: function (props)
{
this.parent (props);
this.rButton = new Array ();
this.uid = ++htkRadioUid;
}
,newRadio: function (value)
{
var radio;
var obj = this;
radio = createRadio (this.uid);
radio.value = value;
radio.checked = value == this.realValue;
radio.addEventListener ('change',
function () { obj.radioChanged (this._value); }, false);
this.rButton.push (radio);
return radio;
}
,radioChanged: function (value)
{
this.realValue = value;
this.signalEmit ('changed');
}
,setRealValue: function (value)
{
var rButton = this.rButton;
for (var n = 0; n < rButton.length; n++)
{
if (rButton[n].value == value)
{
rButton[n].checked = true;
break;
}
}
}
,setEditable: function (editable)
{
var rButton = this.rButton;
for (var n = 0; n < rButton.length; n++)
rButton[n].disabled = !editable;
}
});

View File

@ -0,0 +1,107 @@
Htk.Select = new Class
({
Extends: Htk.Field
,Tag: 'htk-combo'
,Properties:
{
/**
* The model associated to this form.
**/
model:
{
type: Db.Model
,set: function (x)
{
this.link ({_model: x}, {'status-changed': this.onModelChange});
this.onModelChange ();
}
,get: function ()
{
return this._model;
}
}
}
,_model: null
,valueColumnIndex: 0
,valueColumnName: null
,showColumnIndex: 1
,showColumnName: null
,initialize: function (props)
{
this.parent (props);
this.createElement ('select');
this.node.addEventListener ('change', this.changed.bind (this));
}
,changed: function (event)
{
var value;
var row = this.node.selectedIndex - 1;
if (row >= 0)
value = this._model.getByIndex (row, this.valueColumnIndex);
else
value = null;
this.valueChanged (value);
}
,addOption: function (value, text)
{
var option = document.createElement ('option');
option.value = value;
option.appendChild (document.createTextNode (text));
this.node.appendChild (option);
}
,onModelChange: function ()
{
var model = this._model;
if (model.status == Db.Model.Status.LOADING)
return;
Vn.Node.removeChilds (this.node);
switch (model.status)
{
case Db.Model.Status.READY:
{
var data = model.data;
this.addOption (null, '');
for (var i = 0; i < data.length; i++)
this.addOption (data[i][this.showColumnIndex], data[i][1]);
this.selectOption ();
break;
}
case Db.Model.Status.ERROR:
this.addOption (null, _('Error'));
break;
}
}
,setEditable: function (editable)
{
this.node.disabled = !editable;
}
,selectOption: function ()
{
if (!this._model || this._model.status != Db.Model.Status.READY)
return;
var row = this._model.searchByIndex (this.valueColumnIndex, this._value);
if (row != -1)
this.node.selectedIndex = row + 1;
}
,putValue: function (value)
{
this.selectOption ();
}
});

View File

@ -0,0 +1,63 @@
Htk.Spin = new Class
({
Extends: Htk.Entry
,Tag: 'htk-spin'
,initialize: function (props)
{
this.parent (props);
this.unit = null;
this.digits = 0;
}
,changed: function ()
{
var value = (this.entry.value == '') ? null : parseFloat (this.entry.value);
this.entry.value = value;
this.realValue = value;
this.signalEmit ('changed');
}
,setEditable: function (editable)
{
if (editable)
{
var input;
var obj = this;
input = document.createElement ('input');
input.style.textAlign = 'right';
input.style.width = '100%';
setInputTypeNumber (input);
input.addEventListener ('change',
function () { obj.changed (); }, false);
this.node.appendChild (input);
this.entry = input;
}
else
{
removeChilds (this.node);
this.entry = null;
}
}
,setRealValue: function (value)
{
var text;
if (value != null)
{
text = (new Number (value)).toFixed (this.digits);
if (this.unit != null)
text += ' ' + this.unit;
}
else
text = '';
if (!this.editable)
setText (this.node, text);
else
this.entry.value = text;
}
});

View File

@ -0,0 +1,56 @@
Htk.Table = new Class
({
Extends: Htk.Entry
,Tag: 'htk-table'
,initialize: function (props)
{
this.parent (props);
var tv = new Htk.TreeView ();
this.node.appendChild (tv.getNode ());
var renderer = new Htk.CellRendererRadio ();
tv.appendColumn (0, renderer, '');
var rbGroup = renderer.rbGroup;
rbGroup.addSignal ('changed', this.changed, this);
this.treeview = tv;
this.rbGroup = rbGroup;
}
,setModel: function (model)
{
this.treeview.setModel (model);
model.addSignal ('status-changed', this.modelRefresh, this);
this.selectValue ();
}
,changed: function (rbGroup)
{
this.realValue = this.rbGroup.getValue ();
this.signalEmit ('changed');
}
,selectValue: function ()
{
this.rbGroup.setValue (this.realValue);
}
,setRealValue: function ()
{
this.selectValue ();
}
,modelRefresh: function (model, status)
{
if (status == DB_MODEL_STATUS_READY)
this.selectValue ();
}
,setEditable: function (editable)
{
this.rbGroup.setEditable (editable);
}
});

View File

@ -0,0 +1,37 @@
Htk.TextArea = new Class
({
Extends: Htk.Field
,Tag: 'htk-textarea'
,initialize: function (props)
{
this.parent (props);
this.createElement ('textarea');
this.node.addEventListener ('change', this.changed.bind (this));
}
,changed: function (event)
{
var value;
if (this.node.value == '')
value = null;
else
value = this.node.value;
this.valueChanged (value);
}
,setEditable: function (editable)
{
this.node.readOnly = !editable;
}
,putValue: function (value)
{
if (!value)
this.node.value = '';
else
this.node.value = value;
}
});

View File

@ -0,0 +1,352 @@
Htk.Grid = new Class
({
Extends: Htk.Widget
,Tag: 'htk-grid'
,Child: 'model'
,Properties:
{
model:
{
type: Db.Model
,set: function (x)
{
this.link ({_model: x},
{
'status-changed': this.onModelChange
,'row-deleted': this.onRowDelete
,'row-updated': this.onRowUpdate
,'row-inserted': this.onRowInsert
,'updatable-changed': this.onUpdatableChange
});
this.form = new Db.Form ({model: x});
this.onUpdatableChange ();
this.onModelChange ();
}
,get: function ()
{
this._model;
}
},
emptyMessage:
{
type: String
,value: 'NoData'
}
}
,_model: null
,form: null
,columns: new Array ()
,internalColumn: null
,internalColumns: 0
,initialize: function ()
{
this.parent ();
this.table = this.createElement ('table');
this.table.className = 'grid';
var thead = document.createElement ('thead');
this.table.appendChild (thead);
this.thead = document.createElement ('tr')
thead.appendChild (this.thead);
this.tbody = document.createElement ('tbody');
this.table.appendChild (this.tbody);
}
,removeClicked: function (column, value, row)
{
if (confirm (_('ReallyDelete')))
this._model.deleteRow (row);
}
,onRowDelete: function (model, row)
{
var tableRows = this.tbody.childNodes;
this.tbody.removeChild (tableRows[row]);
for (var i = row; i < tableRows.length; i++)
tableRows[i].className = (i % 2) ? 'pair-row' : '';
this.showNoRecordsFound ();
}
,onRowInsert: function (model, row)
{
this.buildRow (1);
}
,renderCell: function (row, column, tr)
{
if (column.columnIndex != -1)
column.value = this._model.data[row][column.columnIndex];
if (column.renderer)
{
this.form.row = row;
column.renderer (column, this.form);
}
return column.render (tr);
}
,refreshRow: function (row, columns)
{
var x = this.columns;
var tr = this.tbody.childNodes[row];
for (var i = 0; i < x.length; i++)
if (x[i].renderer || columns.indexOf (x[i].columnIndex) != -1)
{
var cell = this.renderCell (row, x[i], tr);
tr.replaceChild (cell, tr.childNodes[i]);
}
}
,onRowUpdate: function (model, row, columns)
{
this.refreshRow (row, columns);
}
,buildRow: function (count)
{
for (var i = 0; i < count; i++)
{
var tr = document.createElement ('tr');
if (i % 2)
tr.className = 'pair-row';
for (var j = 0; j < this.columns.length; j++)
{
var cell = this.renderCell (i, this.columns[j], tr);
tr.appendChild (cell);
}
this.tbody.appendChild (tr);
}
}
,onUpdatableChange: function ()
{
if (this._model.updatable)
{
if (!this.internalColumn)
{
this.internalColumn = new Htk.ColumnButton
({
image: 'image/remove.png'
,tip: _('Remove')
});
this.internalColumn.on ('clicked', this.removeClicked, this);
this.insertInternalColumn (0, this.internalColumn);
}
}
else if (this.internalColumn)
{
this.internalColumn = null;
this.removeInternalColumn (0);
}
}
,onModelChange: function ()
{
if (!this._model)
{
this.showMessage (this.emptyMessage, 'refresh.png');
return;
}
this.table.removeChild (this.tbody);
this.tbody = document.createElement ('tbody');
switch (this._model.status)
{
case Db.Model.Status.READY:
{
for (var i = 0; i < this.columns.length; i++)
this.columns[i].updateColumnIndex (this._model);
this.buildRow (this._model.numRows);
this.showNoRecordsFound ();
break;
}
case Db.Model.Status.LOADING:
this.showMessage (_('Loading'), 'loader-black.gif');
break;
case Db.Model.Status.CLEAN:
this.showMessage (_(this.emptyMessage), 'refresh.png');
break;
case Db.Model.Status.ERROR:
this.showMessage (_('ErrorLoadingData'), 'error.png');
break;
}
this.table.appendChild (this.tbody);
}
,showNoRecordsFound: function (count)
{
if (this._model.numRows == 0)
this.showMessage (_('EmptyList'), 'clean.png');
}
,showMessage: function (message, src)
{
if (this.columns.length == 0)
return;
var tr = document.createElement ('tr');
this.tbody.appendChild (tr);
var td = document.createElement ('td');
td.className = 'grid-message';
td.colSpan = this.columns.length;
tr.appendChild (td);
var img = document.createElement ('img');
img.alt = '';
img.src = 'image/'+ src;
td.appendChild (img);
var message = document.createTextNode (message);
td.appendChild (message);
}
,scrollToRow: function (row)
{
if (row >= 0)
{
var height = parseInt (this.tr.style.height);
this.node.scrollTop = (row - 2) * height;
if (this.selectedRow)
this.selectedRow.style.backgroundColor = null;
this.selectedRow = this.tbody.childNodes[row]
this.selectedRow.style.backgroundColor = '#AAD';
}
}
,sortModel: function (column)
{
var columnIndex = column.columnIndex;
if (this._model && columnIndex != -1)
this._model.sort (columnIndex);
}
,columnChanged: function (column, row, newValue)
{
var columnIndex = column.columnIndex;
if (columnIndex != -1)
this._model.setByIndex (row, columnIndex, newValue);
}
,addColumn: function (pos, column)
{
var header = document.createElement ('th');
if (pos == -1 || pos >= this.columns.length)
{
pos = this.columns.push (column) - 1;
this.thead.appendChild (header);
}
else
{
this.columns.splice (pos, 0, column);
this.thead.insertBefore (header, this.thead.childNodes[pos]);
}
header.addEventListener ('click',
this.sortModel.bind (this, column));
header.title = _('Sort');
if (column.title)
{
var title = document.createTextNode (_(column.title));
header.appendChild (title);
}
column.on ('changed', this.columnChanged, this);
var rows = this.tbody.childNodes;
if (this._model && this._model.numRows > 0)
for (var i = 0; i < rows.length; i++)
{
var cell = this.renderCell (i, column, rows[i]);
rows[i].insertBefore (cell, rows[i].childNodes[pos+1]);
}
return pos;
}
,insertInternalColumn: function (pos, column)
{
if (pos < 0 || pos > this.internalColumns)
pos = this.internalColumns;
this.internalColumns++;
return this.addColumn (pos, column);
}
,insertColumn: function (pos, column)
{
if (pos > 0)
pos += this.internalColumns;
return this.addColumn (pos, column) - this.internalColumns;
}
,appendColumn: function (column)
{
return this.insertColumn (-1, column);
}
,deleteColumn: function (pos)
{
if (pos < 0 || pos >= this.columns.length)
return;
this.columns.splice (pos, 1);
this.thead.removeChild (this.thead.childNodes[pos]);
var rows = this.tbody.childNodes;
if (this._model && this._model.numRows > 0)
for (var i = 0; i < rows.length; i++)
rows[i].removeChild (rows[i].childNodes[pos]);
}
,removeInternalColumn: function (pos)
{
if (this.internalColumns == 0)
return;
if (pos < 0 || pos > this.internalColumns)
pos = this.internalColumns;
this.deleteColumn (pos);
this.internalColumns--;
}
,removeColumn: function (pos)
{
if (pos > 0)
pos += this.internalColumns;
this.deleteColumn (pos);
}
,reloadModel: function ()
{
if (this._model != null)
this.onModelChange ();
}
});

View File

@ -0,0 +1,4 @@
/**
* The namespace.
**/
var Htk = {};

View File

@ -0,0 +1,36 @@
<?php
require_once ('js/db/main.php');
Js::includeLib ('htk'
,'main'
,'widget'
,'popup'
,'grid'
,'radio-group'
,'field'
,'field/entry'
,'field/radio'
,'field/label'
,'field/text-area'
,'field/spin'
,'field/check'
,'field/select'
,'field/calendar'
,'field/date-chooser'
,'field/full-image'
,'field/image'
,'field/image-editor'
,'field/table'
,'column'
,'column/button'
,'column/link'
,'column/date'
,'column/image'
,'column/radio'
,'column/spin'
,'column/text'
,'column/check'
);
?>

View File

@ -0,0 +1,46 @@
/**
* Interface for use a widget as a popup.
**/
Htk.Popup = new Class
({
showPopup: function (parent)
{
document.body.appendChild (this.node);
this.node.addEventListener ('mousedown', this.stopEvent);
this.hidePopupHandler = this.hidePopup.bind (this);
document.addEventListener ('mousedown', this.hidePopupHandler);
var spacing = 5;
var rect = parent.getBoundingClientRect ();
var left = rect.left;
var top = rect.top + spacing + parent.offsetHeight;
var width = this.node.offsetWidth;
var height = this.node.offsetHeight;
if (left + width > getInnerWidth ())
left -= width - parent.offsetWidth;
if (top + height > getInnerHeight ())
top -= height + parent.offsetHeight + spacing * 2;
this.node.style.top = (top) + 'px';
this.node.style.left = (left) + 'px';
this.node.style.position = 'fixed';
this.node.style.zIndex = 4;
}
,hidePopup: function ()
{
this.node.removeEventListener ('mousedown', this.stopEvent)
document.removeEventListener ('mousedown', this.hidePopupHandler);
document.body.removeChild (this.node);
this.signalEmit ('closed');
}
,stopEvent: function (event)
{
event.stopPropagation ();
}
});

View File

@ -0,0 +1,48 @@
Htk.RadioGroup = new Class
({
Extends: Vn.Param
,radioLock: false
,initialize: function (props)
{
this.parent (props);
this.clear ();
this.on ('changed', this.onRadioGroupChange, this);
}
,clear: function ()
{
this.name = Math.random ().toString ();
this.buttons = [];
}
,onRadioGroupChange: function ()
{
for (var i = 0; i < this.buttons.length; i++)
if (this.buttons[i].value == this._value)
this.buttons[i].checked = true;
}
,onRadioChange: function (value)
{
if (this.radioLock)
return;
this.radioLock = true;
this.value = value;
this.radioLock = false;
}
,createButton: function (value)
{
var radio = createRadio (this.name);
radio.value = value;
radio.checked = value == this.value;
radio.addEventListener ('change', this.onRadioChange.bind (this, value));
this.buttons.push (radio);
return radio;
}
});

View File

@ -0,0 +1,18 @@
Htk.Widget = new Class
({
Extends: Vn.Object
/** Main HTML node that represents the widget **/
,node: null
,createElement: function (tagName)
{
this.node = document.createElement (tagName);
return this.node;
}
,getNode: function ()
{
return this.node;
}
});

View File

@ -0,0 +1,34 @@
function getPageYOffset ()
{
return window.pageYOffset;
}
function getPageXOffset ()
{
return window.pageXOffset;
}
function getInnerHeight ()
{
return window.innerHeight;
}
function getInnerWidth ()
{
return window.innerWidth;
}
function createRadio (uid)
{
var radio = document.createElement ('input');
radio.type = 'radio';
radio.name = uid;
return radio;
}
function setInputTypeNumber (input)
{
input.type = 'number';
}

Some files were not shown because too many files have changed in this diff Show More