"""
catwalk.py - simple model browser for TurboGears
version 0.9
-----------------------------------------------------------
License: http://www.opensource.org/licenses/mit-license.php
Copyright (c) 2005 Ronald Jaramillo
mail: ronald@checkandshare.com
blog: http://www.checkandshare.com/blog

ChangeLog:
19.10.05
Add support for host based access restriction
19.10.05
Added support for displaying MultipleJoins values
18.10.05
Add dropwdown support for EnumCol
18.10.05
Add support for defining wich column to use as label for ForeignKeys
17.10.05
Remove dependency on mx.DateTime. Thanks to Lauris Buksis-Haberkorns <lauris@nix.lv> for contributing this!
17.10.05
Add support for IE6 (hunt and fix a couple of showstoppers for IE)
16.10.05
Updated the display of foreignKey columns with a link to their own structure definition
15.10.05
Refactor the names of the javascripts methods to gain more consistency (and clarity)
15.10.05
Refactor the javascript functions into method of the 'catwalk' object in order to avoid namespace clashes
13.10.05
Implement support for the following SQLObject column types:
- BoolCol
- DateCol
- DecimalCol
Enforce the maxlength of the StringCol type (char and varchar)
Implement validation the input of IntCols and FloatCols
-----------------------------------------------------------
"""
import cherrypy
import turbogears
import model
import sqlobject
import decimal
import datetime
import re

date_parser = re.compile(r"""^
  (?P<year>\d{4,4})
  (?:
    -
    (?P<month>\d{1,2})
    (?:
      -
      (?P<day>\d{1,2})
      (?:
        T
        (?P<hour>\d{1,2})
        :
        (?P<minute>\d{1,2})
        (?:
          :
          (?P<second>\d{1,2})
          (?:
            \.
            (?P<dec_second>\d+)?
          )?
        )?                    
        (?:
          Z
          |
          (?:
            (?P<tz_sign>[+-])
            (?P<tz_hour>\d{1,2})
            :
            (?P<tz_min>\d{2,2})
          )
        )
      )?
    )?
  )?
$""", re.VERBOSE)

def parse_datetime(s):
  """ parse a string and return a datetime object. """
  assert isinstance(s, basestring)
  r = date_parser.search(s)
  try:
    a = r.groupdict('0')
  except:
    raise ValueError, 'invalid date string format'

  dt = datetime.datetime(int(a['year']),
                         int(a['month']) or 1,
                         int(a['day']) or 1,
                         # If not given these will default to 00:00:00.0
                         int(a['hour']),
                         int(a['minute']),
                         int(a['second']),
                         # Convert into microseconds
                         int(a['dec_second'])*100000,
                         )
  tz_hours_offset = int(a['tz_hour'])
  tz_mins_offset = int(a['tz_min'])
  if a.get('tz_sign', '+') == "-":
    return dt + datetime.timedelta(hours = tz_hours_offset,
                                   minutes = tz_mins_offset)
  else:
    return dt - datetime.timedelta(hours = tz_hours_offset,
                                   minutes = tz_mins_offset)


class CatWalkState(sqlobject.SQLObject):
  _connection = model.hub
  class sqlmeta:
    table = 'catwalk_state_table'
  state = sqlobject.PickleCol()

CatWalkState.createTable(ifNotExists=True)

class CatWalk:
  def __init__(self,allowedHosts=['127.0.0.1']):
    self.allowedHosts=allowedHosts

  def checkAccess(self):
    if cherrypy.request.remoteAddr in self.allowedHosts: return
    raise cherrypy.HTTPRedirect(turbogears.url('noacces'))

  @turbogears.expose()
  def noacces(self):
    return """<h3>No access for %s</h3>
              <p>By default only localhost (127.0.0.1) has access to CatWalk.</p>
              <p>You can provide access to your client by passing your host address to CatWalk as a paramater. Ex:</p>
              <pre>
                from catwalk import CatWalk
                catwalk = CatWalk(allowedHosts=['127.0.0.1','%s'])
              </pre>
           """% (cherrypy.request.remoteAddr,cherrypy.request.remoteAddr)

  def getCols(self,obj):
    myCols = []
    for col in obj.sqlmeta.columns:
      colType = '%r'% obj.sqlmeta.columns[col]
      colType = colType.split()[0][1:]
      label = col
      foreignKey = ''
      foreignName =''
      length =None;
      varchar=False;
      column_label ='' 

      options = []
      if colType == 'SOForeignKey':
        foreignKey = obj.sqlmeta.columns[col].foreignKey
        foreignName = obj.sqlmeta.columns[col].foreignName
        label = '%s:%s'% (foreignName, foreignKey)
        column_label = self.columnToUseAsLabelForObject(foreignKey) 
        options = self.getForeignKeyAlternatives(foreignKey,column_label)
      if colType == 'SOEnumCol': options = obj.sqlmeta.columns[col].enumValues
      if colType == 'SOStringCol':
        length= obj.sqlmeta.columns[col].length
        varchar = obj.sqlmeta.columns[col].varchar
      myCols.append({'name':col,
                     'label':label,
                     'type':colType,
                     'foreignKey':foreignKey,
                     'foreignName':foreignName,
                     'column_label':column_label,
                     'options':options,
                     'length':length,
                     'varchar':varchar
                    })
    return myCols

  def getJoins(self,obj):
    joins = []
    for col in obj.sqlmeta.joins:
      colType = '%r'% col
      colType = colType.split()[0][1:]
      name = '%s:%s'% (col.joinMethodName,col.otherClassName)
      label = name
      
      joins.append({'joinMethodName':col.joinMethodName,'name':name,'label':label,'type':colType})
    return joins

  def getObjectField(self,row,col):
    if col.get('type','') == 'SOForeignKey':
      fkobj = row.__getattribute__(col['foreignName'])
      return '%s'% fkobj.__getattribute__(col['column_label'])
    return '%s'%  row.__getattribute__(col['name'])

  @turbogears.expose()
  def update(self,*k,**v):
    self.checkAccess()
    objectName = v['objectName']
    try: obj = model.__getattribute__(objectName)
    except: return dic()
    ob = obj.get(v['id'])
    cols = self.getCols(obj)
    params = self.extractParameters(cols,v)
    pStr = ','.join(['%s=params[\'%s\']'% (k,k) for k in params.keys()])
    eval('ob.set(%s)'% pStr)
    return self.instances(objectName)

  def extractParameters(self,cols,values):
    params = {}
    for col in cols:
      if values.has_key(col['name']): 
        if col['type'] == 'SODateTimeCol': 
          dt = values[col['name']]
          values[col['name']] = parse_datetime('%sZ'% dt.replace(' ','T'))

        if col['type'] == 'SOBoolCol':
          try:b = bool(int(values[col['name']]))
          except:b = False
          values[col['name']] = b

        if col['type'] == 'SOFloatCol':
          try:b = float(values[col['name']])
          except:b = 0.0 
          values[col['name']] = b

        if col['type'] in ('SOIntCol','SOForeignKey'):
          try:b = int(values[col['name']])
          except:b = 0 
          values[col['name']] = b

        if col['type'] in ('SODecimalCol','SOCurrencyCol'):
          try:b = decimal.Decimal(values[col['name']])
          except:b = decimal.Decimal('0.0') 
          values[col['name']] = b

        params[col['name']] = values[col['name']]
    return params

  @turbogears.expose()
  def add(self,*k,**v):
    self.checkAccess()
    objectName = v['objectName']
    try: obj = model.__getattribute__(objectName)
    except: return dic()

    cols = self.getCols(obj)
    params = self.extractParameters(cols,v)
    pStr = ','.join(['%s=params[\'%s\']'% (k,k) for k in params.keys()])
    eval('obj(%s)'% pStr)
    return self.instances(objectName)

  @turbogears.expose()
  def remove(self,objectName,id):
    self.checkAccess()
    try: obj = model.__getattribute__(objectName)
    except: return dic()
    obj.delete(id)
    return self.instances(objectName)

  @turbogears.expose()
  def instances(self,objectName):
    self.checkAccess()
    try: obj = model.__getattribute__(objectName)
    except: return dic()
    
    rows =list(obj.select())
    cols = self.getCols(obj)
    labels = [col['label'] for col in cols]
    labels.insert(0,'id')
    myRows = []
    for row in rows:
      tmp = []
      tmp.append( self.getObjectField(row,{'name':'id'}))
      for col in cols:
        tmp.append( self.getObjectField(row,col))
      myRows.append(list(tmp))

    return dict(objectName=objectName,rows=myRows,columns=labels)


  def getForeignKeyAlternatives(self,foreignKey,column_label):
    try: obj = model.__getattribute__(foreignKey)
    except: return []
    return [{'id':int(x.id),'label':str(x.__getattribute__(column_label))} for x in list(obj.select())]

  @turbogears.expose()
  def joins(self,objectName,id,join):
    self.checkAccess()
    try: obj = model.__getattribute__(objectName)
    except: return dic()
    inst = obj.get(id)
    joins= inst.__getattribute__(join)
    if not joins: return dict()
    cols = self.getCols(joins[0]) 
    c = '%s'% joins[0].sqlmeta.soClass
    objectName= c.split('.')[-1].replace("'>",'')
    labels = [col['label'] for col in cols]
    labels.insert(0,'id')
    myRows = []

    for row in joins:
      tmp = []
      tmp.append( self.getObjectField(row,{'name':'id'}))
      for col in cols:
        tmp.append( self.getObjectField(row,col))
      myRows.append(list(tmp))

    return dict(objectName=objectName,rows=myRows,columns=labels,join=join)

  @turbogears.expose()
  def instance(self,objectName,id):
    self.checkAccess()
    try: obj = model.__getattribute__(objectName)
    except: return dict()
    inst = obj.get(id)

    cols = self.getCols(obj)
    colsNames = [col['name'] for col in cols]
    values = []
    for col in cols:
      options = []
      colForID=col.copy()
      colForID['type']=''
      values.append({'label':col['label'],
                     'name':col['name'],
                     'id':self.getObjectField(inst,colForID),
                     'value':self.getObjectField(inst,col),
                     'options':col['options'],
                     'type':col['type'],
                     'length':col['length'],
                     'varchar':col['varchar']
                     })
    joins = []
    for join in self.getJoins(obj):
      joins.append( {'label':join['label'],
                     'value':len(inst.__getattribute__(join['joinMethodName'])),
                     'joinMethodName':join['joinMethodName'],
                     'objectName':objectName,
                     'id':id
                     } )
    return dict(objectName=objectName,id=id,values=values,joins=joins)

  @turbogears.expose()
  def columnsForLabel(self,objectName,foreignObjectName,foreignKeyName):
    self.checkAccess()
    try: obj = model.__getattribute__(foreignObjectName)
    except: return dic()
    return dict(columns=self.getCols(obj),
                foreignKeyName=foreignKeyName,
                foreignKeyColumnForLabel= self.columnToUseAsLabelForObject(foreignObjectName), 
                foreignObjectName=foreignObjectName,
                objectName=objectName)

  def getState(self):
    s = CatWalkState.select()[-1:]
    if s:return s[0].state
    return {}

  def setState(self,state):
    s = CatWalkState.select()[-1:]
    if s:
      s[0].state = state
    else:
      CatWalkState(state=state)

  def columnToUseAsLabelForObject(self,objectName):
    state = self.getState()
    lables = state.get('columns',{})  
    return lables.get(objectName,'id')

  def setColumnToUseAsLabelForObject(self,objectName,columnName):
    state = self.getState()
    cl= state.get('columns',{})  
    cl[objectName] = columnName
    state['columns'] = cl
    self.setState(state)

  @turbogears.expose()
  def setColumnForLabel(self,objectName,foreignObjectName,foreignKeyName,columnName):
    self.checkAccess()
    self.setColumnToUseAsLabelForObject(foreignObjectName,columnName)
    return self.show(objectName)

  @turbogears.expose()
  def show(self,objectName):
    self.checkAccess()
    try: obj = model.__getattribute__(objectName)
    except: return dic()
    d = dict(objectName=objectName,columns=self.getCols(obj),joins=self.getJoins(obj))
    return d

  @turbogears.expose()
  def list(self):
    self.checkAccess()
    objs = []
    for m in dir(model):
      if m == 'SQLObject':continue
      try:
        c = model.__getattribute__(m)
        if issubclass(c,sqlobject.SQLObject):objs.append(m)
      except:
        continue
    return dict(SQLObjects=objs)

  @turbogears.expose()
  def index(self):
    self.checkAccess()
    cherrypy.config.update({'sessionFilter.on':True})
    return  """
              <html>
              <head>
                <meta content="text/html; charset=utf-8" http-equiv="content-type">
                <title>CatWalk</title>
                <script src="/tg_js/MochiKit.js"></script>
                <script>
                  var catwalk = {}
                  catwalk.dateOption = function(opt)
                  {
                    if(opt[1] < 10) opt[1] = '0'+ opt[1];
                    if(opt[0]) return createDOM('OPTION',{'value':opt[1],'selected':'selected'},opt[1]);
                    return createDOM('OPTION',{'value':opt[1]},opt[1]);
                  }
                  catwalk.renderBoolField = function(column)
                  {
                    if(typeof(column.value) == 'undefined' || column.value =='False' )
                    {
                      return createDOM('SELECT',{'name':column.name},
                                        createDOM('OPTION',{'value':1},'True'),
                                        createDOM('OPTION',{'value':0,'selected':'selected'},'False')
                                      );
                    }
                    else
                    {
                      return createDOM('SELECT',{'name':column.name},
                                        createDOM('OPTION',{'value':1,'selected':'selected'},'True'),
                                        createDOM('OPTION',{'value':0},'False')
                                      );
                    }
                  }
                  catwalk.renderDateChooser = function(fieldName,year,month,day)
                  {
                    var myDate = new Date();
                    if(typeof(year) == 'undefined') year = myDate.getFullYear();
                    if(typeof(month) == 'undefined') month = (myDate.getMonth() + 1);
                    if(typeof(day) == 'undefined') day = myDate.getDate();

                    var years = [];
                    var months = [];
                    var days = [];

                    var YEAR_SPAN = 60;
                    for(var i=0;i< YEAR_SPAN;i++)
                    {
                      var y = year - (YEAR_SPAN/2) + i;
                      years[years.length] = [(y==year),y]; 
                    }
                    for(var i=1;i< 13;i++) months[months.length] = [(i==month),i];
                    for(var i=1;i< 32;i++) days[days.length] = [(i==day),i];

                    return SPAN({'class':'date_widget'},
                                createDOM('SELECT',{'name':'year_'+ fieldName}, map(catwalk.dateOption,years) ),
                                createDOM('SELECT',{'name':'month_'+ fieldName}, map(catwalk.dateOption,months) ),
                                createDOM('SELECT',{'name':'day_'+ fieldName}, map(catwalk.dateOption,days) )
                               );
                  }
                  catwalk.renderTimeChooser= function(fieldName,hour,minute,second)
                  {
                    var myDate = new Date();
                    if(typeof(hour) == 'undefined') hour = myDate.getHours();
                    if(typeof(minute) == 'undefined') minute = myDate.getMinutes();
                    if(typeof(second) == 'undefined') second = myDate.getSeconds();

                    var hours = [];
                    var minutes = [];
                    var seconds = [];

                    for(var i=0;i< 25;i++) hours[hours.length] = [(i==hour),i];
                    for(var i=0;i< 61;i++) minutes[minutes.length] = [(i==minute),i];
                    for(var i=0;i< 61;i++) seconds[seconds.length] = [(i==second),i];

                    return SPAN({'class':'time_widget'},
                                createDOM('SELECT',{'name':'hour_'+ fieldName}, map(catwalk.dateOption,hours) ),
                                createDOM('SELECT',{'name':'minute_'+ fieldName}, map(catwalk.dateOption,minutes) ),
                                createDOM('SELECT',{'name':'second_'+ fieldName}, map(catwalk.dateOption,seconds) )
                               );
                  }
                  catwalk.renderDateField = function(fieldName,year,month,day)
                  {
                    return SPAN({'class':'date_time_widget'},
                                createDOM('INPUT',{'type':'hidden','name':fieldName,'value':'SODateCol'}),
                                catwalk.renderDateChooser(fieldName,year,month,day)
                               );
                  }
                  catwalk.renderDateTimeField = function(fieldName,year,month,day,hour,minute,second)
                  {
                    return SPAN({'class':'date_time_widget'},
                                createDOM('INPUT',{'type':'hidden','name':fieldName,'value':'SODateTimeCol'}),
                                catwalk.renderDateChooser(fieldName,year,month,day), 
                                SPAN(null,' '),
                                catwalk.renderTimeChooser(fieldName,hour,minute,second)
                               );
                  }

                  catwalk.retrieveModels = function()
                  {
                    var d = loadJSONDoc('list?tg_format=json');
                    d.addCallback(catwalk.renderModels);
                  }
                  catwalk.renderModels = function(result)
                  {
                    var models = UL(null,map(catwalk.renderModel,result['SQLObjects']));
                    replaceChildNodes('models',models);
                  }
                  catwalk.renderModel = function(objectName)
                  {
                    return LI(null,A({'href':"javascript:catwalk.retrieveStructure('"+ objectName +"')"},objectName));
                  }
                  catwalk.retrieveStructure = function(objectName)
                  {
                    var d = loadJSONDoc('show?tg_format=json&objectName='+objectName);
                    d.addCallback(catwalk.renderStructure);
                  }
                  catwalk.renderStructure = function(result)
                  {
                    for(var i=0;i<result['columns'].length;i++) { result['columns'][i].objectName = result['objectName']; }
                    var s = DIV(null,
                                  catwalk.renderObjectMenu(result['objectName']),
                                  TABLE({'class':'detail_grid','cellpadding':'5'},
                                    TBODY(null,
                                     map(catwalk.renderColumn, result['columns']),
                                     map(catwalk.renderJoin, result['joins'])
                                    )
                                  )
                                );
                    replaceChildNodes('content',s);
                  }
                  catwalk.renderObjectMenu = function(objectName)
                  {
                    return  DIV
                            (
                              null,
                              H1(null,objectName),
                              TABLE({'class':'detail_menu','cellpadding':'5'},
                                  TBODY(null,
                                        TR(null,
                                          TD(null,A({'href':"javascript:catwalk.retrieveStructure('"+ objectName +"')",
                                                     'class':'menu_link'},'Structure')),
                                          TD(null,A({'href':"javascript:catwalk.retrieveValues('"+ objectName +"')",
                                                     'class':'menu_link'},'Browse')),
                                          TD(null,A({'href':"javascript:catwalk.retrieveFormAdd('"+ objectName +"')",
                                                     'class':'menu_link'},'Add '+ objectName))
                                        )
                                  )
                               )
                            );
                  }
                  catwalk.renderJoin = function(col)
                  {
                    var objectName = col.label.split(':')[1]; 
                    return TR(null,
                              TD({'style':'background-color:#e3e3e3'}, 
                                  A( {'href':"javascript:catwalk.retrieveStructure('"+ objectName +"')"},col.label )
                                ),
                              TD({'style':'background-color:#e3e3e3'}, SPAN({'style':'color:#999'},col.type) ) 
                            );
                  }
                  catwalk.renderColumn = function(col)
                  {
                    var label = SPAN(null,col.label); 
                    var fieldType = SPAN({'style':'color:#999999'},col.type);

                    if(col.type =='SOForeignKey')
                    {
                      var labelArray = col.label.split(':'); 
                      var foreignKeyName = labelArray[0];
                      var foreignObjectName = labelArray[1];

                      label = SPAN(null,foreignKeyName);
                      fieldType = SPAN({'style':'color:#999999'},
                                    SPAN(null,col.type +':'),
                                    A( {'href':"javascript:catwalk.retrieveStructure('"+ foreignObjectName +"')"},foreignObjectName),
                                    SPAN(null,' , label column:'),
                                    SPAN({'id':'spanid_'+ foreignKeyName},
                                      A( {'href':"javascript:catwalk.retrieveColumnsForLabel('"+ col.objectName +"','"+ foreignObjectName +"','"+ foreignKeyName +"')"},col.column_label)
                                    )
                                  );
                    }
                    return TR(null,
                              TD(null, label),
                              TD(null, fieldType) 
                            );
                  }
                  catwalk.retrieveColumnsForLabel = function(objectName,foreignObjectName,foreignKeyName)
                  {
                    var url='columnsForLabel?tg_format=json&objectName='+objectName;
                    url+='&foreignObjectName='+ foreignObjectName +'&foreignKeyName='+ foreignKeyName;
                    var e = loadJSONDoc(url);
                    e.addCallback(catwalk.renderColumnsForLabel);
                  }
                  catwalk.renderColumnsForLabel = function(result)
                  {
                    var options = [{'selected':true,'value':'id','label':'id'}];
                    for(var i=0;i<result['columns'].length;i++)
                    {
                      selected = ( result['foreignKeyColumnForLabel'] == result['columns'][i].label);
                      options[options.length] = {'selected':selected,
                                                 'value':result['columns'][i].label,
                                                 'label':result['columns'][i].label
                                                 };
                    }
                    var action = "catwalk.selectColumnForLabel(this,"; 
                    action += "'"+ result['objectName'] +"','"+ result['foreignObjectName'] +"','"+ result['foreignKeyName'] +"'";
                    action += ")"; 
                    var labelChooser = createDOM('SELECT',
                                                 {'onchange':action},
                                                  map(catwalk.renderOptions, options) 
                                                );
                    replaceChildNodes('spanid_'+ result['foreignKeyName'],labelChooser); 
                  }
                  catwalk.selectColumnForLabel = function(columnList,objectName,foreignObjectName,foreignKeyName)
                  {
                    var columnName = columnList.options[columnList.selectedIndex].value;
                    var url= 'setColumnForLabel?tg_format=json&objectName='+objectName;
                    url+='&foreignObjectName='+ foreignObjectName;
                    url+='&foreignKeyName='+ foreignKeyName;
                    url+='&columnName='+ columnName;
                    var e = loadJSONDoc(url);
                    e.addCallback(catwalk.renderStructure);
                  }
                  catwalk.retrieveValues = function(objectName)
                  {
                    var e = loadJSONDoc('instances?tg_format=json&objectName='+objectName);
                    e.addCallback(catwalk.renderValues);
                  }
                  catwalk.renderValues = function(result)
                  {
                    replaceChildNodes('content', DIV(null, 
                                                     catwalk.renderObjectMenu(result['objectName']),
                                                     catwalk.renderObjectGrid(result)
                                                    )
                                      );
                  }

                  catwalk.retrieveFormAdd = function(objectName)
                  {
                  var d = loadJSONDoc('show?tg_format=json&objectName='+objectName);
                    d.addCallback(catwalk.renderFormAdd);
                  }
                  catwalk.renderFormAdd = function(result)
                  {
                    var formAdd = createDOM('DIV',null,
                                             catwalk.renderObjectMenu(result['objectName']),
                                             catwalk.renderObjectColumns(result['columns'],catwalk.renderFormAddColumn),
                                             catwalk.renderButton('Cancel',"catwalk.retrieveValues('"+ result['objectName'] +"')"),
                                             catwalk.renderButton('Create',"catwalk.retrieveObjectCreate('"+ result['objectName'] +"')")
                                            );
                    replaceChildNodes('content',formAdd);
                  }
                  catwalk.renderButton = function(label,action)
                  {
                    return createDOM('INPUT',{'type':'button',
                                              'class':'buts',
                                              'value':label,
                                              'onclick':action 
                                             }
                                     );
                  }
                  catwalk.renderFormAddColumn = function(column)
                  {
                    var columnValue;
                    switch(column.type)
                    {
                      case 'SOStringCol':
                        columnValue = catwalk.renderStringColumn(column);
                        break;
                      case 'SOForeignKey':
                        columnValue = catwalk.renderForeignKeyColumn(column);
                        break;
                      case 'SOIntCol':
                        columnValue = catwalk.renderNumericCol(column,'int');
                        break;
                      case 'SOFloatCol':
                        columnValue = catwalk.renderNumericCol(column,'float');
                        break;
                      case 'SOBoolCol':
                        columnValue = TD(null, catwalk.renderBoolField(column));
                        break;
                      case 'SOEnumCol':
                        columnValue =  TD(null,catwalk.renderEnumCol(column));
                        break;
                      case 'SODateCol':
                        columnValue =  TD(null,catwalk.renderDateField(column.name));
                        break;
                      case 'SODateTimeCol':
                        columnValue = TD(null,catwalk.renderDateTimeField(column.name));
                        break;
                      default:
                        columnValue = TD(null, createDOM('INPUT',{'class':'txtfield','type':'text','name':column.name}) );
                    }
                    return TR(null, TD(null, SPAN(null, column.label) ), columnValue);
                  }
                  catwalk.renderForeignKeyColumn = function(column)
                  {
                    for(var i=0;i<column.options.length;i++)
                    {
                      column.options[i] = (column.options[i].id ==column.id)? {
                                                                               'selected':true,
                                                                               'value':column.options[i].id,
                                                                               'label':column.options[i].label
                                                                               }:{
                                                                                 'selected':false,
                                                                                 'value':column.options[i].id,
                                                                                 'label':column.options[i].label
                                                                               };
                    }
                    return TD(null, 
                              createDOM('SELECT',
                                        {'name':column.name},
                                        map(catwalk.renderOptions, column.options) 
                                       ) 
                             );
                  }

                  catwalk.renderEnumCol = function(column)
                  {
                    for(var i=0;i<column.options.length;i++)
                    {
                      column.options[i] = {
                                           'selected':(column.options[i] ==column.value),
                                           'value':column.options[i],
                                           'label':column.options[i]
                                          }
                    }
                    return TD(null, 
                              createDOM('SELECT',
                                        {'name':column.name},
                                        map(catwalk.renderOptions, column.options) 
                                       ) 
                             );
                  }
                  catwalk.getValueForField = function(form,fieldName)
                  {
                    for(var i=0; i<form.elements.length;i++)
                    {
                      if(form.elements[i].name==fieldName) return form.elements[i].value;
                    }
                    return '';
                  }
                  catwalk.isDate = function(field)
                  {
                    if(field.indexOf('year_') > -1) return true;
                    if(field.indexOf('month_') > -1) return true;
                    if(field.indexOf('day_') > -1) return true;
                    if(field.indexOf('hour_') > -1) return true;
                    if(field.indexOf('minute_') > -1) return true;
                    if(field.indexOf('second_') > -1) return true;
                    return false;
                  }
                  catwalk.getDateString = function(form,fieldName)
                  {
                    var y = catwalk.getValueForField(form,'year_'+ fieldName);
                    var m = catwalk.getValueForField(form,'month_'+ fieldName);
                    var d = catwalk.getValueForField(form,'day_'+ fieldName);
                    return y +'-'+ m +'-'+ d;
                  }
                  catwalk.getTimeString = function(form,fieldName)
                  {
                    var h = catwalk.getValueForField(form,'hour_'+ fieldName);
                    var m = catwalk.getValueForField(form,'minute_'+ fieldName);
                    var s = catwalk.getValueForField(form,'second_'+ fieldName);
                    return h+':'+m+':'+s;
                  }
                  catwalk.buildDate = function(form,fieldName)
                  {
                    var fieldType = catwalk.getValueForField(form,fieldName);
                    if(fieldType=='SODateTimeCol')
                    {
                      return catwalk.getDateString(form,fieldName) +' '+ catwalk.getTimeString(form,fieldName);
                    }
                    else
                    {
                      return catwalk.getDateString(form,fieldName); 
                    }
                  }
                  catwalk.getFieldNameForDate = function(field)
                  {
                    fieldName = field.split('_');
                    return fieldName[1];
                  }
                  catwalk.collectPostVars = function(f)
                  {
                    var dates = {};
                    var postVars='';
                    for(var i=0; i<f.elements.length;i++)
                    {
                      var t = f.elements[i].type;
                      if(t.indexOf('text') > -1 )
                      {
                        if(postVars!='') postVars+='&';
                        postVars+= f.elements[i].name +'='+ f.elements[i].value;
                      }
                      if(t.indexOf('select') > -1)
                      {
                        if(catwalk.isDate(f.elements[i].name))
                        {
                          fieldName = catwalk.getFieldNameForDate(f.elements[i].name);
                          if(dates[fieldName]) continue;
                          date = catwalk.buildDate(f,fieldName);
                          dates[fieldName] = date;
                          if(postVars!='') postVars+='&';
                          postVars+= fieldName +'='+ date;
                        }
                        else
                        {
                          if(postVars!='') postVars+='&';
                          postVars+= f.elements[i].name +'='+ f.elements[i].options[f.elements[i].selectedIndex].value;
                        }
                      }
                    }
                    return postVars;
                  }
                  catwalk.retrieveObjectCreate = function(objectName)
                  {
                    var postVars = catwalk.collectPostVars(document.myform);
                    postVars+= '&objectName='+ objectName;
                    var d = postJSONDoc('add',postVars); 
                    d.addCallback(catwalk.renderValues);
                  }
                  catwalk.renderObjectGrid = function(result)
                  {
                      for(var i=0;i<result['rows'].length;i++)
                      {
                        result['rows'][i][0] = {'id':result['rows'][i][0],'name':result['objectName']};
                      }
                    
                      return TABLE({'class':'detail_grid','cellpadding':'5'},
                                    TBODY(null,
                                          TR(null, TD(null,''), map(catwalk.renderGridHeader,result['columns']) ),
                                          map(catwalk.renderGridRow, result['rows'])
                                    )
                                  );
                  }
                  catwalk.renderGridHeader= function(colName)
                  {
                    return TD({'class':'table_head'},colName);
                  }
                  catwalk.renderGridRow = function(row)
                  {
                    return TR(null, catwalk.renderGridActionCell(row[0]), map(catwalk.renderGridCell,row) );
                  }
                  catwalk.renderGridActionCell = function(dict)
                  {
                    return TD({'nowrap':'nowrap'},
                              A({'class':'action','href':"javascript:catwalk.retrieveDisplayObject("+ dict.id +",'"+ dict.name +"')"},' preview '),
                              ' ',
                              A({'class':'action','href':"javascript:catwalk.retrieveRemove("+ dict.id +",'"+ dict.name +"')"},' delete ')
                             );
                  }
                  catwalk.renderGridCell = function (cell)
                  {
                    if(typeof(cell) == 'object') cell = cell.id; 
                    return TD(null,cell);
                  }
                  catwalk.retrieveDisplayObject = function(id,objectName)
                  {
                    var e = loadJSONDoc('instance?tg_format=json&objectName='+objectName+'&id='+id);
                    e.addCallback(catwalk.renderDisplayObject);
                  }
                  catwalk.renderDisplayObject= function(result)
                  {
                    var formEdit = createDOM('DIV',null,
                                              catwalk.renderObjectMenu(result['objectName']) ,
                                              catwalk.renderObjectColumns(result['values'],catwalk.renderDisplayColumn),
                                              BR(null),
                                              catwalk.renderObjectColumns(result['joins'],catwalk.renderDisplayColumnJoins),
                                              catwalk.renderButton('Cancel',"catwalk.retrieveValues('"+ result['objectName'] +"')"),
                                              catwalk.renderButton('Edit',"catwalk.retrieveFormEdit("+ result['id'] +",'"+ result['objectName'] +"')")
                                            );
                    replaceChildNodes('content',formEdit);
                  }
                  catwalk.renderDisplayColumnJoins = function(column)
                  {
                    return TR(null, TD(null,
                                        column.label,' ',
                                        A({'href':"javascript:catwalk.retrieveJoins('"+ column.objectName +"',"+ column.id +",'"+ column.joinMethodName +"')",
                                           'title':'Display join values'},'Show rows ('+ column.value +')'),
                                        DIV({'id':'grid_'+ column.joinMethodName})
                                       )
                              );
                  }

                  catwalk.retrieveJoins= function(objectName,id,joinName)
                  {
                    var e = loadJSONDoc('joins?tg_format=json&objectName='+ objectName +'&id='+ id +'&join='+ joinName);
                    e.addCallback(catwalk.renderJoins);
                  }
                  catwalk.renderJoins = function(results)
                  {
                    replaceChildNodes('grid_'+ results['join'], catwalk.renderObjectGrid(results) );
                  }
                  catwalk.renderDisplayColumn = function(column)
                  {
                    return TR(null, TD(null, SPAN(null,column.label) ), TD(null,column.value));
                  }
                  catwalk.renderObjectColumns = function(values,mapFunction)
                  {
                    return TABLE({'class':'detail_grid','cellpadding':'5'}, TBODY(null, map(mapFunction,values) ) );
                  }
                  catwalk.retrieveFormEdit = function(id,objectName)
                  {
                    var e = loadJSONDoc('instance?tg_format=json&objectName='+objectName+'&id='+id);
                    e.addCallback(catwalk.renderFormEdit);
                  }
                  catwalk.renderFormEdit = function(result)
                  {
                    var formEdit = createDOM('DIV',null,
                                              catwalk.renderObjectMenu(result['objectName']) ,
                                              catwalk.renderObjectColumns(result['values'],catwalk.renderFormEditColumn),
                                              catwalk.renderButton('Cancel',"catwalk.retrieveDisplayObject("+ result['id'] +",'"+ result['objectName'] +"')"),
                                              catwalk.renderButton('Update',"catwalk.retrieveObjectUpdate('"+ result['objectName'] +"',"+ result['id'] +")")
                                            );
                    replaceChildNodes('content',formEdit);
                  }
                  catwalk.renderOptions = function(option)
                  {
                    if(option.selected) return createDOM('OPTION',{'value':option.value,'selected':'selected'},option.label);
                    return createDOM('OPTION',{'value':option.value},option.label);
                  }
                  catwalk.renderStringColumn = function(column)
                  {
                    if(typeof(column.value) == 'undefined') column.value = '';
                    if(column.length != null && column.length > 0)
                    {
                      return TD(null, createDOM('INPUT',
                                                {'class':'txtfield',
                                                 'type':'text',
                                                 'name':column.name,
                                                 'value':column.value,
                                                 'maxlength':column.length
                                                }
                                               ) 
                               );
                    }

                    return TD(null, createDOM('TEXTAREA',
                                               {'name':column.name, 'class':'txtarea'},
                                               column.value
                                             ) 
                             );
                  }
                  catwalk.validate = function(field,expectedType)
                  {
                    switch(expectedType) 
                    {
                      case 'int':
                        if(isNaN(parseInt(field.value))) field.value = field.defaultValue;
                      case 'float':
                        if(isNaN(parseFloat(field.value))) field.value = field.defaultValue;
                    }
                  }
                  catwalk.renderNumericCol = function(column,digitType)
                  {
                    if(typeof(column.value) == 'undefined') column.value = '';
                    return TD(null, createDOM('INPUT',
                                              {'class':'txtfield',
                                               'type':'text',
                                               'name':column.name,
                                               'value':column.value,
                                               'onblur':'catwalk.validate(this,"'+ digitType +'")'
                                              }
                                             ) 
                             );
                  }

                  catwalk.renderFormEditColumn = function(column)
                  {
                    var columnValue;
                    switch(column.type)
                    {
                      case 'SOStringCol':
                        columnValue = catwalk.renderStringColumn(column);
                        break;
                      case 'SOIntCol':
                        columnValue = catwalk.renderNumericCol(column,'int');
                        break;
                      case 'SOFloatCol':
                        columnValue = catwalk.renderNumericCol(column,'float');
                        break;
                      case 'SOForeignKey':
                        columnValue = catwalk.renderForeignKeyColumn(column);
                        break;
                      case 'SOBoolCol':
                        columnValue = TD(null, catwalk.renderBoolField(column));
                        break;
                      case 'SOEnumCol':
                        columnValue =  TD(null,catwalk.renderEnumCol(column));
                        break;
                      case 'SODateCol':
                        var d = isoTimestamp(column.value +' 00:00:00');
                        columnValue = TD(null,
                                         catwalk.renderDateField(column.name,
                                                                 d.getFullYear(),
                                                                 d.getMonth() +1,
                                                                 d.getDate())
                                        );
                        break;
                      case 'SODateTimeCol':
                        var d = isoTimestamp(column.value);
                        columnValue = TD(null,catwalk.renderDateTimeField(column.name,
                                                           d.getFullYear(),
                                                           d.getMonth() +1,
                                                           d.getDate(),
                                                           d.getHours(),
                                                           d.getMinutes(),
                                                           d.getSeconds() ));
                        break;
                      default:
                        columnValue = TD(null, createDOM('INPUT',{'class':'txtfield','type':'text','name':column.name,'value':column.value}) );
                    }
                    return TR(null, TD(null, SPAN(null,column.label) ), columnValue);
                  }
                  catwalk.retrieveObjectUpdate = function(objectName,id)
                  {
                    var postVars =catwalk.collectPostVars(document.myform);
                    postVars+= '&objectName='+ objectName +'&id='+ id;
                    var d = postJSONDoc('update',postVars); 
                    d.addCallback(catwalk.renderValues);
                  }
                  function postJSONDoc(url,postVars)
                  {
                    var req = getXMLHttpRequest();
                    req.open('POST',url,true);
                    req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
                    var data = postVars; //queryString(postVars);
                    var d = sendXMLHttpRequest(req,data);
                    return d.addCallback(evalJSONRequest);
                  }
                  catwalk.retrieveRemove= function (id,objectName)
                  {
                    if(!confirm('Shure you want to remove object '+ objectName +':'+ id +'?')) return;
                    var d = postJSONDoc('remove','tg_format=json&objectName='+objectName+'&id='+ id);
                    d.addCallback(catwalk.renderValues);
                  }
                </script>
                <style>
                  body { margin:0;padding:0;font-family:verdana,sans-serif;font-size:12px; }
                  td { font-family:verdana,sans-serif;font-size:12px; }
                  h1 {font-size:12px;color:#666;margin:0;padding:0;margin-top:10px;margin-left:6px;}
                  #top { height:25px;background-color:#598B5A;border-bottom:2px solid #466B47;color:#fff;font-size:14px;}
                  #top span {margin-top:10px;margin-right:10px;float:right; }
                  #left_col { padding-left:10px;padding-right:30px; }
                  #left_col ul { list-style-type:none;padding:0;margin:0; }
                  #content { border-left:1px solid #e3e3e3;padding-left:10px; }
                  .menu_link {margin-top:10px;margin-bottom:10px;font-size:12px;color:#598B5A; display:block;font-weight:900; }
                  .detail_grid td {border-bottom:1px solid #e3e3e3;padding:4px;padding-left:10px; }
                  .table_head { font-weight:900; }
                  .buts {background-color:#598B5A;color:#fff; border-bottom:2px solid #466B47;margin-top:10px;margin-right:10px; }
                  .txtfield {width:300px;font-size:12px; }
                  .txtarea {width:300px;font-size:12px; }
                  .action {font-size:10px;text-decoration:none;background-color:#598B5A;color:#fff; border-bottom:2px solid #466B47;padding:3px;margin-right:5px; }
                </style>
              </head>

              <body onload="catwalk.retrieveModels()">
              <div id="top">
                <span>CatWalk</span>
              </div>
              <table cellpadding="0" cellspacing="0" border="0">
                <tr>
                  <td id="left_col" valign="top" nowrap>
                    <a href="javascript:catwalk.retrieveModels()" class="menu_link">Available objects</a>
                    <div id="models"></div>
                  </td>
                  <td id="main_area" valign="top">
                    <form name="myform">
                    <div id="content"></div>
                    </form>
                  </td>
               </tr>
              </table>
              </body>
              </html>
              """
