var session_cookie_name = "PHPSESSID";
/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/

var Base64 = {

    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    encode : function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

        }

        return output;
    },

    // public method for decoding
    decode : function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {

            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }

        }

        output = Base64._utf8_decode(output);

        return output;

    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        }

        return string;
    }

}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  AES implementation in JavaScript (c) Chris Veness 2005-2009                                   */
/*   - see http://csrc.nist.gov/publications/PubsFIPS.html#197                                    */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */


/* http://www.movable-type.co.uk/scripts/aes.html */

var Aes = {};  // Aes namespace

/*
 * AES Cipher function: encrypt 'input' state with Rijndael algorithm
 *   applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
 *
 * @param {Number[]} input 16-byte (128-bit) input state array
 * @param {Number[][]} w   Key schedule as 2D byte-array (Nr+1 x Nb bytes)
 * @returns {Number[]}     Encrypted output state array
 */
Aes.Cipher = function(input, w) {    // main Cipher function [§5.1]
  var Nb = 4;               // block size (in words): no of columns in state (fixed at 4 for AES)
  var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

  var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];

  state = Aes.AddRoundKey(state, w, 0, Nb);

  for (var round=1; round<Nr; round++) {
    state = Aes.SubBytes(state, Nb);
    state = Aes.ShiftRows(state, Nb);
    state = Aes.MixColumns(state, Nb);
    state = Aes.AddRoundKey(state, w, round, Nb);
  }

  state = Aes.SubBytes(state, Nb);
  state = Aes.ShiftRows(state, Nb);
  state = Aes.AddRoundKey(state, w, Nr, Nb);

  var output = new Array(4*Nb);  // convert state to 1-d array before returning [§3.4]
  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
  return output;
}

/*
 * Perform Key Expansion to generate a Key Schedule
 *
 * @param {Number[]} key Key as 16/24/32-byte array
 * @returns {Number[][]} Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes)
 */
Aes.KeyExpansion = function(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
  var Nb = 4;            // block size (in words): no of columns in state (fixed at 4 for AES)
  var Nk = key.length/4  // key length (in words): 4/6/8 for 128/192/256-bit keys
  var Nr = Nk + 6;       // no of rounds: 10/12/14 for 128/192/256-bit keys

  var w = new Array(Nb*(Nr+1));
  var temp = new Array(4);

  for (var i=0; i<Nk; i++) {
    var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
    w[i] = r;
  }

  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
    w[i] = new Array(4);
    for (var t=0; t<4; t++) temp[t] = w[i-1][t];
    if (i % Nk == 0) {
      temp = Aes.SubWord(Aes.RotWord(temp));
      for (var t=0; t<4; t++) temp[t] ^= Aes.Rcon[i/Nk][t];
    } else if (Nk > 6 && i%Nk == 4) {
      temp = Aes.SubWord(temp);
    }
    for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
  }

  return w;
}

/*
 * ---- remaining routines are private, not called externally ----
 */

Aes.SubBytes = function(s, Nb) {    // apply SBox to state S [§5.1.1]
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) s[r][c] = Aes.Sbox[s[r][c]];
  }
  return s;
}

Aes.ShiftRows = function(s, Nb) {    // shift row r of state S left by r bytes [§5.1.2]
  var t = new Array(4);
  for (var r=1; r<4; r++) {
    for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
    for (var c=0; c<4; c++) s[r][c] = t[c];         // and copy back
  }          // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
  return s;  // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
}

Aes.MixColumns = function(s, Nb) {   // combine bytes of each col of state S [§5.1.3]
  for (var c=0; c<4; c++) {
    var a = new Array(4);  // 'a' is a copy of the current column from 's'
    var b = new Array(4);  // 'b' is a•{02} in GF(2^8)
    for (var i=0; i<4; i++) {
      a[i] = s[i][c];
      b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
    }
    // a[n] ^ b[n] is a•{03} in GF(2^8)
    s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
    s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
    s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
    s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
  }
  return s;
}

Aes.AddRoundKey = function(state, w, rnd, Nb) {  // xor Round Key into state S [§5.1.4]
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
  }
  return state;
}

Aes.SubWord = function(w) {    // apply SBox to 4-byte word w
  for (var i=0; i<4; i++) w[i] = Aes.Sbox[w[i]];
  return w;
}

Aes.RotWord = function(w) {    // rotate 4-byte word w left by one byte
  var tmp = w[0];
  for (var i=0; i<3; i++) w[i] = w[i+1];
  w[3] = tmp;
  return w;
}

// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
Aes.Sbox =  [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
             0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
             0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
             0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
             0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
             0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
             0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
             0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
             0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
             0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
             0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
             0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
             0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
             0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
             0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
             0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];

// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
Aes.Rcon = [ [0x00, 0x00, 0x00, 0x00],
             [0x01, 0x00, 0x00, 0x00],
             [0x02, 0x00, 0x00, 0x00],
             [0x04, 0x00, 0x00, 0x00],
             [0x08, 0x00, 0x00, 0x00],
             [0x10, 0x00, 0x00, 0x00],
             [0x20, 0x00, 0x00, 0x00],
             [0x40, 0x00, 0x00, 0x00],
             [0x80, 0x00, 0x00, 0x00],
             [0x1b, 0x00, 0x00, 0x00],
             [0x36, 0x00, 0x00, 0x00] ];


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2009                      */
/*   - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf                       */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var AesCtr = {};  // AesCtr namespace

/*
 * Encrypt a text using AES encryption in Counter mode of operation
 *
 * Unicode multi-byte character safe
 *
 * @param {String} plaintext Source text to be encrypted
 * @param {String} password  The password to use to generate a key
 * @param {Number} nBits     Number of bits to be used in the key (128, 192, or 256)
 * @returns {string}         Encrypted text
 */
AesCtr.encrypt = function(plaintext, password, nBits) {
  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  if (!(nBits==128 || nBits==192 || nBits==256)) return '';  // standard allows 128/192/256 bit keys
  plaintext = Utf8.encode(plaintext);
  password = Utf8.encode(password);
  //var t = new Date();  // timer

  // use AES itself to encrypt password to get cipher key (using plain password as source for key
  // expansion) - gives us well encrypted key
  var nBytes = nBits/8;  // no bytes in key
  var pwBytes = new Array(nBytes);
  for (var i=0; i<nBytes; i++) {
    pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
  }
  var key = Aes.Cipher(pwBytes, Aes.KeyExpansion(pwBytes));  // gives us 16-byte key
  key = key.concat(key.slice(0, nBytes-16));  // expand key to 16/24/32 bytes long

  // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
  // block counter in 2nd 8 bytes
  var counterBlock = new Array(blockSize);
  var nonce = (new Date()).getTime();  // timestamp: milliseconds since 1-Jan-1970
  var nonceSec = Math.floor(nonce/1000);
  var nonceMs = nonce%1000;
  // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
  for (var i=0; i<4; i++) counterBlock[i] = (nonceSec >>> i*8) & 0xff;
  for (var i=0; i<4; i++) counterBlock[i+4] = nonceMs & 0xff;
  // and convert it to a string to go on the front of the ciphertext
  var ctrTxt = '';
  for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);

  // generate key schedule - an expansion of the key into distinct Key Rounds for each round
  var keySchedule = Aes.KeyExpansion(key);

  var blockCount = Math.ceil(plaintext.length/blockSize);
  var ciphertxt = new Array(blockCount);  // ciphertext as array of strings

  for (var b=0; b<blockCount; b++) {
    // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
    // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
    for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
    for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)

    var cipherCntr = Aes.Cipher(counterBlock, keySchedule);  // -- encrypt counter block --

    // block size is reduced on final block
    var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
    var cipherChar = new Array(blockLength);

    for (var i=0; i<blockLength; i++) {  // -- xor plaintext with ciphered counter char-by-char --
      cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b*blockSize+i);
      cipherChar[i] = String.fromCharCode(cipherChar[i]);
    }
    ciphertxt[b] = cipherChar.join('');
  }

  // Array.join is more efficient than repeated string concatenation in IE
  var ciphertext = ctrTxt + ciphertxt.join('');
  ciphertext = Base64.encode(ciphertext);  // encode in base64

  //alert((new Date()) - t);
  return ciphertext;
}

/*
 * Decrypt a text encrypted by AES in counter mode of operation
 *
 * @param {String} ciphertext Source text to be encrypted
 * @param {String} password   The password to use to generate a key
 * @param {Number} nBits      Number of bits to be used in the key (128, 192, or 256)
 * @returns {String}          Decrypted text
 */
AesCtr.decrypt = function(ciphertext, password, nBits) {
  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  if (!(nBits==128 || nBits==192 || nBits==256)) return '';  // standard allows 128/192/256 bit keys
  ciphertext = Base64.decode(ciphertext);
  password = Utf8.encode(password);
  //var t = new Date();  // timer

  // use AES to encrypt password (mirroring encrypt routine)
  var nBytes = nBits/8;  // no bytes in key
  var pwBytes = new Array(nBytes);
  for (var i=0; i<nBytes; i++) {
    pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
  }
  var key = Aes.Cipher(pwBytes, Aes.KeyExpansion(pwBytes));
  key = key.concat(key.slice(0, nBytes-16));  // expand key to 16/24/32 bytes long

  // recover nonce from 1st 8 bytes of ciphertext
  var counterBlock = new Array(8);
  ctrTxt = ciphertext.slice(0, 8);
  for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);

  // generate key schedule
  var keySchedule = Aes.KeyExpansion(key);

  // separate ciphertext into blocks (skipping past initial 8 bytes)
  var nBlocks = Math.ceil((ciphertext.length-8) / blockSize);
  var ct = new Array(nBlocks);
  for (var b=0; b<nBlocks; b++) ct[b] = ciphertext.slice(8+b*blockSize, 8+b*blockSize+blockSize);
  ciphertext = ct;  // ciphertext is now array of block-length strings

  // plaintext will get generated block-by-block into array of block-length strings
  var plaintxt = new Array(ciphertext.length);

  for (var b=0; b<nBlocks; b++) {
    // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
    for (var c=0; c<4; c++) counterBlock[15-c] = ((b) >>> c*8) & 0xff;
    for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff;

    var cipherCntr = Aes.Cipher(counterBlock, keySchedule);  // encrypt counter block

    var plaintxtByte = new Array(ciphertext[b].length);
    for (var i=0; i<ciphertext[b].length; i++) {
      // -- xor plaintxt with ciphered counter byte-by-byte --
      plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
      plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
    }
    plaintxt[b] = plaintxtByte.join('');
  }

  // join array of blocks into single plaintext string
  var plaintext = plaintxt.join('');
  plaintext = Utf8.decode(plaintext);  // decode from UTF8 back to Unicode multi-byte chars

  //alert((new Date()) - t);
  return plaintext;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Base64 class: Base 64 encoding / decoding (c) Chris Veness 2002-2009                          */
/*    note: depends on Utf8 class                                                                 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Base64 = {};  // Base64 namespace

Base64.code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/*
 * Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
 * (instance method extending String object). As per RFC 4648, no newlines are added.
 *
 * @param {String} str The string to be encoded as base-64
 * @param {Boolean} [utf8encode=false] Flag to indicate whether str is Unicode string to be encoded
 *   to UTF8 before conversion to base64; otherwise string is assumed to be 8-bit characters
 * @returns {String} Base64-encoded string
 */
Base64.encode = function(str, utf8encode) {  // http://tools.ietf.org/html/rfc4648
  utf8encode =  (typeof utf8encode == 'undefined') ? false : utf8encode;
  var o1, o2, o3, bits, h1, h2, h3, h4, e=[], pad = '', c, plain, coded;
  var b64 = Base64.code;

  plain = utf8encode ? str.encodeUTF8() : str;

  c = plain.length % 3;  // pad string to length of multiple of 3
  if (c > 0) { while (c++ < 3) { pad += '='; plain += '\0'; } }
  // note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars

  for (c=0; c<plain.length; c+=3) {  // pack three octets into four hexets
    o1 = plain.charCodeAt(c);
    o2 = plain.charCodeAt(c+1);
    o3 = plain.charCodeAt(c+2);

    bits = o1<<16 | o2<<8 | o3;

    h1 = bits>>18 & 0x3f;
    h2 = bits>>12 & 0x3f;
    h3 = bits>>6 & 0x3f;
    h4 = bits & 0x3f;

    // use hextets to index into code string
    e[c/3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
  }
  coded = e.join('');  // join() is far faster than repeated string concatenation in IE

  // replace 'A's from padded nulls with '='s
  coded = coded.slice(0, coded.length-pad.length) + pad;

  return coded;
}

/*
 * Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
 * (instance method extending String object). As per RFC 4648, newlines are not catered for.
 *
 * @param {String} str The string to be decoded from base-64
 * @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded
 *   from UTF8 after conversion from base64
 * @returns {String} decoded string
 */
Base64.decode = function(str, utf8decode) {
  utf8decode =  (typeof utf8decode == 'undefined') ? false : utf8decode;
  var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded;
  var b64 = Base64.code;

  coded = utf8decode ? str.decodeUTF8() : str;


  for (var c=0; c<coded.length; c+=4) {  // unpack four hexets into three octets
    h1 = b64.indexOf(coded.charAt(c));
    h2 = b64.indexOf(coded.charAt(c+1));
    h3 = b64.indexOf(coded.charAt(c+2));
    h4 = b64.indexOf(coded.charAt(c+3));

    bits = h1<<18 | h2<<12 | h3<<6 | h4;

    o1 = bits>>>16 & 0xff;
    o2 = bits>>>8 & 0xff;
    o3 = bits & 0xff;

    d[c/4] = String.fromCharCode(o1, o2, o3);
    // check for padding
    if (h4 == 0x40) d[c/4] = String.fromCharCode(o1, o2);
    if (h3 == 0x40) d[c/4] = String.fromCharCode(o1);
  }
  plain = d.join('');  // join() is far faster than repeated string concatenation in IE

  return utf8decode ? plain.decodeUTF8() : plain;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
/*              single-byte character encoding (c) Chris Veness 2002-2009                         */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Utf8 = {};  // Utf8 namespace

/*
 * Encode multi-byte Unicode string into utf-8 multiple single-byte characters
 * (BMP / basic multilingual plane only)
 *
 * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
 *
 * @param {String} strUni Unicode string to be encoded as UTF-8
 * @returns {String} encoded string
 */
Utf8.encode = function(strUni) {
  // use regular expressions & String.replace callback function for better efficiency
  // than procedural approaches
  var strUtf = strUni.replace(
      /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
      function(c) {
        var cc = c.charCodeAt(0);
        return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
    );
  strUtf = strUtf.replace(
      /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
      function(c) {
        var cc = c.charCodeAt(0);
        return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
    );
  return strUtf;
}

/*
 * Decode utf-8 encoded string back into multi-byte Unicode characters
 *
 * @param {String} strUtf UTF-8 string to be decoded back to Unicode
 * @returns {String} decoded string
 */
Utf8.decode = function(strUtf) {
  var strUni = strUtf.replace(
      /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
        return String.fromCharCode(cc); }
    );
  strUni = strUni.replace(
      /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
        return String.fromCharCode(cc); }
    );
  return strUni;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/*
cogs.js
*/

/*
http://ejohn.org/blog/simple-javascript-inheritance/

Provides a Class object which can be extended
Inspired by base2 and Prototype
*/
var Cogs_api_url = '/__api/%s';

(function(){
    var initializing = false, fnTest = /xyz/.test(function(){
        xyz;
    }) ? /\b_super\b/ : /.*/;

    // The base Class implementation (does nothing)
    this.Class = function(){};

    // Create a new Class that inherits from this class
    Class.extend = function(prop) {
        var _super = this.prototype;

        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;

        // Copy the properties over onto the new prototype
        for (var name in prop) {
            // Check if we're overwriting an existing function
            prototype[name] = typeof prop[name] == "function" &&
            typeof _super[name] == "function" && fnTest.test(prop[name]) ?
            (function(name, fn){
                return function() {
                    var tmp = this._super;

                    // Add a new ._super() method that is the same method
                    // but on the super-class
                    this._super = _super[name];

                    // The method only need to be bound temporarily, so we
                    // remove it when we're done executing
                    var ret = fn.apply(this, arguments);
                    this._super = tmp;

                    return ret;
                };
            })(name, prop[name]) :
            prop[name];
        }

        // The dummy class constructor
        function Class() {
            // All construction is actually done in the init method
            if ( !initializing && this.init )
                this.init.apply(this, arguments);
        }

        // Populate our constructed prototype object
        Class.prototype = prototype;

        // Enforce the constructor to be what we expect
        Class.constructor = Class;

        // And make this class extendable
        Class.extend = arguments.callee;

        return Class;
    };
})();


var cogs = {
    _show_overlay: false,
    _overlay_showing: false,
    _overlay_str: '',
    _worker_data: [],
    version: '',

    error_handler: function(e, context){
        throw e;
    },
    set_error_handler: function (handler){
        this.error_handler = handler;
    },
    global_class: function(class_name){
        if (class_name.indexOf('.') > -1){
            var class_arr = class_name.split('.');
            var class_part = class_arr.shift();
            var global_obj = window[class_part];
            while (class_arr.length){
                class_part = class_arr.shift();
                if (global_obj[class_part]){
                    global_obj = global_obj[class_part];
                }
                else {
                    throw new cogs.error.Error('Class ' + class_name + ' not defined in global namespace');
                }
            }
        }
        else {
            global_obj = window[class_name];
        }

        return global_obj;
    },
    check_update: function(){
        /* Invalidate the model cache */
        try {
            window.setTimeout(function(){
                var store = cogs.get_store();
                var now = new Date();
                var last_checked = store.get('cogs.last_modified_check');
                if (last_checked){
                    now.setTime(last_checked);
                }
                var mod_res = cogs.rpc_call('Model', 'modified', [now]);
                //cogs.debug(mod_res, 'Model.modified res');
                for (var mm=0;mm<mod_res.length;mm++){
                    store.remove(mod_res[mm]);

                    // Delete templates and controller if a plugin loaded/unloaded
                    var decoded_key = Base64.decode(mod_res[mm]);
                    if (decoded_key.substr(0, 11) == 'plugininfo/'){
                        store.remove_with_prefix('template.');
                        store.remove_with_prefix('controller.');
                    }
                    else if (decoded_key.substr(0, 9) == 'category/'){
                        store.remove('category_tree');
                    }
                }
                store.set('cogs.last_modified_check', new Date().getTime());

                // Delete controllers and templates from a previous version
                var cache_version = store.get('cogs.last_version');
                if (cogs.version && (cache_version != cogs.version)){
                    cogs.update_application();

                    store.set('cogs.last_version', cogs.version);
                }
            }, 500);
        }
        catch (e){
            cogs.debug('Error in check_update', '', 'error');
            throw e;
        }
    },
    update_application: function(){
        window.setTimeout(function(){
            var store = cogs.get_store();
            
            store.remove_with_prefix('template.');
            store.remove_with_prefix('controller.');
        }, 500);
    },
    i18n: {
        language: '',
        translations: {},
        cache: function(group, data, language){
            if (!language){
                if (cogs.i18n.language){
                    language = cogs.i18n.language;
                }
                else {
                    language = $.cookie('COGS_LANGUAGE');
                    if (!language){
                        language = 'en';
                    }
                    cogs.i18n.language = language;
                }
            }
            var store = cogs.get_store();
            var store_name = 'i18n.' + group + '.' + language;

            if (typeof data == 'undefined'){
                // getting cache
                var i18n_data = store.get(store_name);
                
                return i18n_data;
            }
            else {
                // setting cache
                store.set(store_name, data);
            }

            return null;
        },
        clear_cache: function(group, language){
            if (!language){
                language = $.cookie('COGS_LANGUAGE');
                if (!language){
                    language = 'en';
                }
            }
            var store = cogs.get_store();
            store.remove('i18n.' + group + '.' + language);
        },
        translate: function(group_name, content, subs){
            //cogs.debug('TRANSLATING ' + content + ' in group ' + group_name);
            var translated_content = content;
            if (!subs){
                subs = [];
            }

            if (group_name){
                // Do translation here
                var group_name_array = group_name.split('#', 2);
                if (group_name_array.length < 2){
                    cogs.debug('i18n must supply name as name and group - ' + content);
                }
                else {
                    var group = group_name_array[0];
                    var name = group_name_array[1];
                    
                    var cached_translations = cogs.i18n.cache(group);

                    if (cached_translations){
                        if (cached_translations[name]){
                            translated_content = cached_translations[name]['translation'];
                        }
                        else {
                            // request add
                            cogs.i18n.request_translation(group, name, content);
                        }
                    }
                    else {
                        var group_data_config = {
                            url: '/__translation/data',
                            data: {
                                'group': group
                            },
                            async: false
                        };
                        var xhr = $.ajax(group_data_config);
                        
                        var response_data = JSON.parse(xhr.responseText);
                        if (response_data && response_data.translations){
                            var translation_strings = response_data.translations.strings;

                            cogs.i18n.cache(group, translation_strings);

                            if (translation_strings && translation_strings[name]){
                                translated_content = translation_strings[name]['translation'];
                            }
                            else {
                                // request add
                                cogs.i18n.request_translation(group, name, content);
                            }
                        }
                        else {
                            // request add
                            cogs.debug('BAD RESPONSE FOR ' + group);
                            cogs.i18n.clear_cache(group);
                            cogs.i18n.request_translation(group, name, content);
                        }
                    }
                }
            }
            else {
                cogs.debug('Cannot translate '+content+' without name attr - returning original');
            }

            if (typeof translated_content == 'undefined'){
                cogs.debug('Translation was undefined ' + group_name);
                translated_content = content;
            }

            if (subs && subs.length){
                return $.vsprintf(translated_content, subs);
            }
            else {
                return translated_content;
            }
            
        },
        request_translation: function(group, name, content){
            /*cogs.debug('Requesting update for ' + group + '#' + name + ', ' + content);
            var request_opts = {
                url: '/__translation/request',
                data: {
                    group: group,
                    name: name,
                    content: content
                },
                type: 'POST'
            };
            $.ajax(request_opts);*/
        }
    },
    db: {
        get: function(keys){
            if (!(keys instanceof Array)){
                keys = [keys];
            }
            //cogs.debug(keys);
            var models = [];

            for (var k=0;k<keys.length;k++){
                try {
                    var key = keys[k];
                    if (!key){
                        cogs.debug(keys, 'Keys supplied to get');
                        throw new cogs.error.StoreError('Invalid key supplied to get');
                    }

                    var model_class = key.class_name();
                
                    var global_class = cogs.global_class(model_class);
                    if (global_class){
                        var obj = new global_class();
                        models.push(obj.get(key));
                    }
                }
                catch (e){
                    cogs.debug('Exception in db.get');
                    cogs.debug(e);
                    
                    throw e;
                }
                //cogs.debug(obj);
            }
            //cogs.debug('cogs.db.get');
            //cogs.debug(models);

            if (models.length == 1){
                return models.pop();
            }
            else if (models.length == 0){
                return null;
            }

            return models;
        },
        run_in_transaction: function(func, args){
            alert('run_in_transaction not supported yet');
        }
    },
    datastore: {
        store: null,
        override: null,
        NullDatastore: Class.extend({
            storage: {},
            name: function(){
                return 'null';
            },
            set: function(key, value){
                this.storage[key] = value;
            },
            get: function(key){
                return this.storage[key];
            },
            get_all: function(){
                return this.storage;
            },
            save_model: function(m){
                if (m._loaded){
                    var obj = {};
                    for (var field_name in m._fields){
                        obj[field_name] = m._fields[field_name].value_to_json(m._values[field_name]);
                    }
                    //cogs.debug(obj, 'OBJ to save in datastore');
                    obj.key = m.key().toString();
                    obj.kind = m.key().kind();
                    this.set(m.key().toString(), obj);
                }
            },
            load_model: function(m){
                try {
                    var json_obj = this.get(m.key().toString());
                    if (json_obj){
                        //cogs.debug(json_obj, 'Loading model data from store');
                        m = m.load_from_json(json_obj);
                        return m;
                    }
                }
                catch (e){
                    cogs.debug(m, 'Error loading model from store', 'error');
                }
                
                return false;
            },
            remove: function(key){
                delete this.storage[key];
            },
            remove_with_prefix: function(prefix){
                var data = this.get_all();
                for (var k in data){
                    if (k.indexOf(prefix) === 0){
                        this.remove(k);
                    }
                }
            },
            clear: function(){
                this.storage = {};
            }
        })
    },
    datastore_type: function(){
        if (cogs.datastore.override){
            return cogs.datastore.override;
        }
        else {
            /*if (window.webkitIndexedDB || window.mozIndexedDB || window.indexedDB){
                return 'indexeddb';
            }
            else */if (window.localStorage){
                return 'global';
            }
            else {
                return 'null';
            }
            //return (window.openDatabase)?'database':(typeof localStorage != 'undefined')?'global':'null';
        }
    },
    get_store: function (){
        if (cogs.datastore.store){
            return cogs.datastore.store;
        }
        
        var datastore_type = this.datastore_type();

        //cogs.debug('using '+datastore_type+' storage');

        switch (datastore_type){
            case 'indexeddb':
                cogs.datastore.store = new cogs.datastore.IndexedDBDatastore();
                break;
            case 'database':
                cogs.datastore.store = new cogs.datastore.DatabaseDatastore();
                break;
            case 'global':
                cogs.datastore.store = new cogs.datastore.GlobalStorageDatastore();
                break;
            case 'null':
            default:
                cogs.datastore.store = new cogs.datastore.NullDatastore();
                break;
        }

        return cogs.datastore.store;
    },
    server_config: function(){
        var ret = cogs.rpc_call('_SERVER', 'config', []);

        return ret;
    },
    debug: function(str, title, type){
        /* Sometimes (in a worker) document and window are undefined */
        if (cogs.version && cogs.version.indexOf('dev') != 0 && type != 'error'){
            return;
        }
        
        type = type || 'log';
        var c = cogs.debug.caller;
        var caller_func = '';
        if (c){
            var caller_func_match = c.toString().match(/^function (\w+)\(/);
            if (caller_func_match){
                caller_func = caller_func_match[1] + '()';
            }
            else {
                caller_func = 'ANONYMOUS FUNCTION ' + c.toString().substring(0,50);
            }
        }

        if (typeof document == 'undefined') {
            postMessage("{command:'log', type:'"+type+"', params:{str:"+escape(str)+"}}");
        }
        else {
            var dc, log_fn;
            if (window.console){
                log_fn = window.console.log;
            }
            
            switch (type){
                case 'debug':
                    log_fn = window.console.debug;
                    break;
                case 'info':
                    log_fn = window.console.info;
                    break;
                case 'warn':
                    log_fn = window.console.warn;
                    break;
                case 'error':
                    log_fn = window.console.error;
                    break;
            }
            
            if (typeof window.console != 'undefined' && typeof log_fn == 'function'){

                if (title){
                    log_fn.apply(window.console, [cogs.clone(title)]);
                }
                log_fn.apply(window.console, [cogs.clone(str)]);

                if (caller_func){
                    window.console.log('dumped in ' + caller_func);
                }
            }
            else if (dc = document.getElementById('cogs_debug')){
                var obj_to_html = function(obj){
                    var out = '';
                    var o;
                    if (typeof obj == 'object'){
                        if (obj.toString){
                            out += obj.toString();
                        }
                        else {
                            for (o in obj){
                                out += o + ' = ' + obj_to_html(obj[o]) + '<br>';
                            }
                        }
                    }
                    else if (typeof obj == 'string'){
                        out = obj.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\n/g,"<br>");
                    }
                    else if (typeof obj == 'boolean'){
                        out = (obj)?'true':'false';
                    }
                    else if (typeof obj == 'number'){
                        out = obj.toString();
                    }
                    else if (typeof obj == 'array'){
                        out = '[';
                        for (o=0;o<obj.length;o++){
                            out += o + '=' + obj_to_html(obj[o]);
                        }
                        out += ']';
                    }
                    else if (typeof obj == 'function'){
                        //out = obj.toString();
                        out = 'FUNCTION';
                    }
                    else if (typeof obj == 'undefined'){
                        out = 'undefined';
                    }
                    /*else if (obj && typeof obj.toString == 'function') {
                        out = obj.toString().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\n/g,"<br>");
                    }*/
                    else {
                        out = '<hr>UNHANDLED LOG TYPE - ' + (typeof obj);
                    }
                    return out;
                };
                
                dc.innerHTML += '<hr>';
                if (title){
                    dc.innerHTML += '<b>'+title+'</b><br>';
                }
                dc.innerHTML +=  obj_to_html(str);

                if (caller_func){
                    dc.innerHTML += '<br>dumped in ' + caller_func;
                }
            }
            else {
            //alert(str);
            }
        }
    },
    debug_group: function(name){
        if (typeof window.console != 'undefined' && typeof window.console.group == 'function'){
            window.console.group(name);
        }
    },
    debug_group_end: function(){
        if (typeof window.console != 'undefined' && typeof window.console.groupEnd == 'function'){
            window.console.groupEnd();
        }
    },
    trace: function(){
        if (cogs.version.indexOf('dev') != 0){
            return;
        }
        if (typeof window.console != 'undefined' && typeof window.console.trace == 'function'){
            window.console.trace();
        }
    },
    info: function(str, title, url){
        title = title || 'Info';
        url = url || '';

        if (url){
            str += '<br /><a href="'+url+'">more</a>'
        }

        var notification_obj = window.webkitNotifications;
        if (notification_obj) {
            var popup = null;
            if (notification_obj.checkPermission() == 0) {
                popup = notification_obj.createNotification(null, title, str);
            }
            
            if (popup){
                popup.show();
                return;
            }
        }

        $.jGrowl.defaults.position = 'bottom-right';
        $.jGrowl(str, {life: 7000, position: 'bottom-right'});
    },
    alert: function(str, title, url){
        title = title || 'Alert';
        url = url || '';

        alert(str);
    },
    confirm: function(options){
        var res = window.confirm(options.message);
        
        if (res && typeof options.yes == 'function'){
            options.yes(options.data);
        }
        else if (!res && typeof options.no == 'function'){
            options.no(options.data);
        }
        
        return res;
    },
    prompt: function(options){
        var res = window.prompt(options.message);
        
        if (res && typeof options.ok == 'function'){
            options.ok(res, options.data);
        }
        else if (!res && typeof options.cancel == 'function'){
            options.cancel(options.data);
        }
        
        return res;
    },
    check_overlay: function(){
        if (cogs._show_overlay){
            if (cogs._overlay_showing){
                return;
            }
            
            var str = cogs._overlay_str || cogs.i18n.translate('system#pleasewait', 'Please Wait');

            var window_height = $(window).height();
            var overlay = document.createElement('div');
            $(overlay).attr('id', 'cogs_overlay');
            $(overlay).attr('style', 'position:fixed;top:0;left:0;width:100%;height:'+window_height+'px;background-color:#000000;filter:Alpha(Opacity=75);opacity:0.75;color:white;text-align:center;font-size:3em;padding-top:'+(window_height/2)+'px;');

            var load_img = document.createElement('img');
            load_img.src = '/images/wait.gif';
            $(load_img).load(function(){
                $(overlay).append(load_img);
                $(overlay).append(document.createElement('br'));
                $(overlay).append(str);
                $('body').append(overlay);
            });
            cogs._overlay_showing = true;
        }
        else {
            $('#cogs_overlay').remove();
            cogs._overlay_showing = false;
        }
    },
    show_overlay: function(str){
        /* A global variable shows its visibility and then hide or show based on the content
         * the code has to run in a loop checking once a second */
        cogs._show_overlay = true;
        cogs._overlay_str = str;
    },
    hide_overlay: function(){
        cogs._show_overlay = false;
        cogs._overlay_str = null;
    },
    show_loading_spinner: function (){
        //if (!cogs.overlay_showing){
            $(document.body).css('cursor', 'wait');
            $('#spinner').show();
        //}
    },
    hide_loading_spinner: function (){
        $(document.body).css('cursor', 'default');
        $('#spinner').hide();
    },
    clone: function(obj){
        if ($.isPlainObject(obj)){
            return $.extend(true, {}, obj);
        }
        else if ($.isArray(obj)){
            clone = [];
            for (var i=0;i<obj.length;i++){
                clone[i] = cogs.clone(obj[i]);
            }
            return clone;
        }
        
        return obj;
        
        /*var clone = null;
        if ($.isPlainObject(obj)){
            clone = {};
            for (var k in obj){
                clone[k] = cogs.clone(obj[k]);
            }
        }
        else if ($.isArray(obj)){
            clone = [];
            for (var i=0;i<obj.length;i++){
                clone[i] = cogs.clone(obj[i]);
            }
        }
        else {
            clone = obj;
        }

        return clone;*/
    },
    start_cb: function (){
        var url = '/callback.php';
        var xhr = get_http_request();

        var position = 0;
        var data = '';
        var current_chunk = '';
        var store = cogs.get_store();

        xhr.multipart = true;
        xhr.open("GET", url, false);

        xhr.onload = function(e) {
            if (e.target){
                var result = e.target.responseXML;
                //cogs.debug(e.target.responseText);
                if (result){
                    //cogs.debug(result);
                    var updates = result.getElementsByTagName('update');
                    if (updates && updates.length){
                        for (var u in updates){
                            cogs.debug('deleting : ' + updates[u].firstChild.nodeValue);
                            store.del(new cogs.db.Key(updates[u].firstChild.nodeValue));
                        }
                    }
                }
            }
        };

        xhr.send(null);

        return false;
    },
    rpc_call: function(class_name, method_name, args, this_tag, output_format){
        var parsed_ret = false;
        if (!args){
            args = {};
        }
        output_format = output_format || 'json';

        cogs.debug('RPC call for '  + class_name + '.' + method_name);
        cogs.debug(args);

        var data = '<request output="json">';
        data += '<method>' + class_name + '.' + method_name + '</method>';
        if (this_tag){
            data += '<this>' + this_tag + '</this>';
        }
        data += '<params>';
        //cogs.debug(args);
        for (var a in args){
            var arg_type = 'json';
            if (args[a] instanceof Date){
                arg_type = 'date';
            }
            data += '<param name="'+a+'" type="'+arg_type+'"><![CDATA[' + JSON.stringify(args[a]) + ']]></param>';
        }
        data += '</params>';
        data += '</request>';
        
        cogs.debug(data);

        try {
            var xhr = this.api_xml(data, {
                async: false
            });
        }
        catch (e){
            cogs.debug('Exception in rpc_call');
            throw e;
        }

        var ret = false;
        try {
            //cogs.debug(xhr.responseText, 'RPC returned');
            ret = JSON.parse(xhr.responseText);
            ret = ret['_return'];
            cogs.debug(ret, 'JSON parsed return');
        }
        catch (e){
            cogs.debug('Error parsing rpc response of "'+xhr.responseText+'"');
            cogs.debug(e);
            throw e;
        }
        
        try {
            var unserialize_model = function(model_data){
                if ($.isArray(model_data)){
                    for (var i=0;i<model_data.length;i++){
                        model_data[i] = unserialize_model(model_data[i]);
                    }
                }
                else if ($.isPlainObject(model_data)){
                    if (model_data && model_data['is_model']){
                        // Must be a model
                        try {
                            var m = new Model().load_from_json(model_data);
                            model_data = m;
                        }
                        catch (e){
                            cogs.debug('ERROR loading model from json');
                            cogs.debug(e);
                            throw e;
                        }
                    }
                    else if (model_data && model_data['is_key']){
                        // Must be a key
                        try {
                            var k = new cogs.db.Key().from_encoded(model_data['encoded']);
                            model_data = k;
                        }
                        catch (e){
                            cogs.debug('ERROR loading key from json');
                            cogs.debug(e);
                            throw e;
                        }
                    }
                    else {
                        for (var i in model_data){
                            model_data[i] = unserialize_model(model_data[i]);
                        }
                    }
                }

                return model_data;
            };


            if (typeof ret['success'] != 'undefined' && ret['success']){
                //cogs.debug(ret, 'ret');
                parsed_ret = unserialize_model(ret.data);
                //cogs.debug(parsed_ret, 'parsed_ret');
                //cogs.debug(parsed_ret);
            }
            else if (typeof ret.exception != 'undefined'){
                if (typeof cogs.error[ret.exception] != 'undefined'){
                    throw new cogs.error[ret.exception](ret.error);
                }
            }
            else {
                throw new cogs.error.Error(ret.error);
            }
        }
        catch (e){
            cogs.debug('Error unserializing rpc response of "'+xhr.responseText+'"');
            cogs.debug(e);
            throw e;
        }

        //cogs.debug('Parsed return from RPC');
        //cogs.debug(parsed_ret);

        return parsed_ret;
    },
    api_url: function(type){
        if (typeof type == 'undefined'){
            type = 'xml';
        }
        return Cogs_api_url.replace('%s', type);
    },
    api_call: function(ajax_options){
        if (typeof ajax_options.type == 'undefined'){
            ajax_options.type = 'POST';
        }

        var xhr = $.ajax(ajax_options);

        if (!ajax_options.async && (xhr.status >= 400)){
            throw new cogs.error.NetworkError('Server returned error code '+xhr.status, xhr.status);
        }

        return xhr;
    },
    api_xml: function(data, options){
        options = options || {};
        var url = this.api_url('xml');

        try {
            options.url = url;
            // Authentication token sent twice to prevent CSRF
            //cogs.debug(document.cookie);
            if (typeof data == 'string'){
                data = this.doc_from_string(data);
            //data.auth = auth;
            }

            var req_tag = data.getElementsByTagName('request').item(0);
            if (req_tag){
                var auth_tag = data.createElement('auth');
                var token_tag = data.createElement('token');

                var session_cookie_value = $.cookie(session_cookie_name);
                // cookie_name is null on IE sometimes
                if (session_cookie_value){
                    var cookie_name_section = data.createCDATASection(session_cookie_value);
                    token_tag.appendChild(cookie_name_section);
                }
                auth_tag.appendChild(token_tag);

                req_tag.appendChild(auth_tag);
            }
            
            if (cogs.api_worker && cogs._worker_data && options.async){
                cogs.debug('ASYNC worker!');
                cogs.debug(options);
                cogs.debug(data);
                var worker_ref = cogs._worker_data.length;
                
                cogs._worker_data[worker_ref] = options;
                
                //var worker_data = JSON.stringify({ref:worker_ref, data:this.string_from_doc(data), url: options.url});
                var worker_data = {ref:worker_ref, data:this.string_from_doc(data), url: options.url};
                
                cogs.debug(worker_data, 'sending message to worker');
                cogs.api_worker.postMessage(worker_data);
            }
            else {

                options.data = this.string_from_doc(data);
                options.async = false;

                var xhr = this.api_call(options);

                //cogs.debug('RPC XML request options');
                //cogs.debug(options);
                //cogs.debug(options.data);
                //cogs.debug('RPC XML request');
                //cogs.debug(data);
                //cogs.debug('XML RPC returned');
                //cogs.debug(xhr.responseText);

                var e = null;
                if (!options.async){
                    // Parse response and throw error here
                    if (xhr.responseXML){
                        var exception = xhr.responseXML.getElementsByTagName('exception').item(0);
                        if (exception){
                            var exception_name_attr = exception.getAttribute('name');
                            var msg = 'Unknown '+exception_name_attr+' in XML API';
                            if (typeof cogs.error[exception_name_attr] != 'undefined'){
                                if (exception.firstChild){
                                    msg = exception.firstChild.nodeValue;
                                }
                                e = new cogs.error[exception_name_attr](msg);
                            }
                            else {
                                if (exception.firstChild){
                                    msg = exception.firstChild.nodeValue;
                                }
                                e = new cogs.error.Error(msg);
                            }
                            
                            if (e && options.error && typeof options.error == 'function'){
                                options.error(e);
                            }
                            else {
                                throw e;
                            }
                        }
                    }
                }
            }
        }
        catch (e){
            cogs.debug('Error in api_xml');
            cogs.debug(e);
            throw e;
        }

        return xhr;
    },
    api_json: function(data, options){
        options = options || {};
        var url = this.api_url('json');

        options.url = url;
        // Authentication token sent twice to prevent CSRF
        var auth = {token:$.cookie(session_cookie_name)};
        if (session_cookie_name == ''){
            throw new cogs.error.ForbiddenError('session_cookie_name is not set - API calls will not work!');
        }
        if (typeof data == 'string'){
            try {
                data = JSON.parse(data);
                data.auth = auth;
            }
            catch (e){
                throw new cogs.error.Error('error parsing json parameters');
            }
        }
        else {
            data.auth = auth;
        }

        options.data = JSON.stringify(data);
        options.async = false;

        var xhr = this.api_call(options);

        //cogs.debug('RPC JSON request options');
        //cogs.debug(options);
        //cogs.debug('RPC returned');
        //cogs.debug(xhr.responseText);

        if (!options.async){
            // Parse response and throw error here
            if (xhr.responseText){
                try {
                    var response_obj = JSON.parse(xhr.responseText);
                    if (typeof response_obj == 'undefined'){
                        cogs.debug(xhr.responseText, 'Invalid response in api_json');
                        throw new cogs.error.Error('Could not parse response in api_json');
                    }
                    if (typeof response_obj._return == 'undefined'){
                        cogs.debug(xhr.responseText, 'Invalid _return in api_json');
                        cogs.debug(options.data, 'Request data in api_json');
                        throw new cogs.error.Error('_return is not defined in api_json');
                    }
                    response_obj = response_obj._return;
                }
                catch (e){
                    // response not json
                    //cogs.debug(xhr.responseText, 'RESPONSE NOT VALID JSON');
                    return xhr;
                }
                
                //cogs.debug(response_obj, 'response_obj in api_json');

                if (!response_obj.success){
                    if (!response_obj.error){
                        response_obj.error = 'Unknown error in cogs.api_json';
                        cogs.debug(xhr.responseText, 'cogs.api_json response');
                    }
                    
                    if (typeof cogs.error[response_obj.exception] != 'undefined'){
                        throw new cogs.error[response_obj.exception](response_obj.error);
                    }
                    else {
                        throw new cogs.error.Error(response_obj.error);
                    }
                }
            }
        }

        return xhr;
    },
    doc_from_string: function(str){
        var domdoc = null;
        try {
            domdoc = new ActiveXObject("Microsoft.XMLDOM");
            domdoc.async = false;
            domdoc.loadXML(str);
        }
        catch (e){
            var parser = new DOMParser();
            domdoc = parser.parseFromString(str, "text/xml");
        }

        return domdoc;
    },
    string_from_doc: function(doc){
        var str = '';

        if (doc.xml) {
            // IE code
            str = doc.xml;
        }
        else {
            // Code for Mozilla etc
            try {
                var s = new XMLSerializer();
                str = s.serializeToString(doc);
            }
            catch (e){
                str = '';
            }
        }

        return str;
    }

};




cogs.api_worker = null;
if (typeof Worker != 'undefined'){
    try {
        cogs.api_worker = new Worker('/jscore/cogs/api_worker.js');

        cogs.api_worker.addEventListener('message', function(e){
            //cogs.debug('Worker returned message!');
            //cogs.debug(e.data);

            if (typeof e.data.worker_ref != 'undefined'){
                var options = cogs._worker_data[e.data.worker_ref];

                //cogs.debug('options for ref are');
                //cogs.debug(options);

                if (options.success){
                    options.success(e.data.data, null, null, e.data.response_ref);
                }
            }
        }, false);

        cogs.api_worker.postMessage('start');
    }
    catch (e){
        cogs.api_worker = null;
    }
}


/* http://www.peterbe.com/plog/isint-function */
function is_int(x) {
    var y = parseInt(x);
    return (isNaN(y))?false:(x==y && x.toString()==y.toString());
}


window.setInterval(cogs.check_overlay, 100);


/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

cogs.webapp = {};

cogs.webapp.Event = {
    listen_url: '/__event/listen/',
    register_url: '/__event/register',
    listeners: {},
    http_req: null,
    evt_source: null,
    client_id: null,
    register_server_event: function(event_name){
        cogs.debug('Registering event ' + event_name + ' with the server');
        var url = cogs.webapp.Event.register_url + '?client_id=' + cogs.webapp.Event.client_id + '&event=' + event_name;
        var http_req = new XMLHttpRequest();
        //http_req.multipart   = true;
        http_req.async   = true;
        cogs.debug('Registering event at url ' + url);
        http_req.open('GET', url, true);
        //http_req.onload = cogs.webapp.Event.receive_event;
        http_req.send( null );
    },
    parse_event: function(raw_data){
        //cogs.debug(raw_data);
        var event_name = '';
        var event_data = null;
        var event_lines = raw_data.split('\n');
        for (var l in event_lines){
            var event_line_arr = event_lines[l].split(':', 2);
            if (event_line_arr[0] == 'id'){
                event_name = event_line_arr[1];
            }
            else if (event_line_arr[0] == 'data'){
                event_data = event_line_arr[1];
            }
        }

        if (event_name){
            return {name:event_name, data:event_data};
        }
        else {
            return false;
        }
    },
    receive_event: function(evt){
        //cogs.debug(evt);
        if (evt.target.readyState == 4) {
            var raw_data = evt.target.responseText;
            var event_obj = cogs.webapp.Event.parse_event(raw_data);
            if (event_obj){
                if (event_obj.name == 'client_id'){
                    cogs.webapp.Event.client_id = event_obj.data;
                    cogs.debug('Got client id ' + cogs.webapp.Event.client_id);
                    // register all the events
                    for (var listener in cogs.webapp.Event.listeners){
                        cogs.webapp.Event.register_server_event(listener);
                    }
                }
                cogs.webapp.Event.fire_event(event_obj.name, event_obj.data);
            }
        }
    },
    error: function(err){
        cogs.debug('Error connecting comet server');
        cogs.debug(err);
    },
    fire_event: function(event_name, event_data){
        if (typeof cogs.webapp.Event.listeners[event_name] != 'undefined'){
            for (var e in cogs.webapp.Event.listeners[event_name]){
                cogs.webapp.Event.listeners[event_name][e](event_name, event_data);
            }
        }
    },
    onmessage_listener: function(event){
        if (event.lastEventId == 'client_id'){
            cogs.webapp.Event.client_id = event.data;
            cogs.debug('Got client id ' + cogs.webapp.Event.client_id);
            // register all the events
            for (var listener in cogs.webapp.Event.listeners){
                cogs.webapp.Event.register_server_event(listener);
            }
        }
        
        cogs.webapp.Event.fire_event(event.lastEventId, event.data);
    },
    listen: function (){
        cogs.debug('Starting listen');
        if (cogs.webapp.Event.http_req == null && cogs.webapp.Event.evt_source == null){
            if (typeof EventSource != 'undefined'){
                cogs.debug('cogs.webapp.Event using EventSource');
                cogs.webapp.Event.evt_source = new EventSource(cogs.webapp.Event.listen_url + 'dom');
                cogs.webapp.Event.evt_source.onmessage = cogs.webapp.Event.onmessage_listener;
                cogs.webapp.Event.evt_source.onerror = cogs.webapp.Event.error;
            }
            else {
                cogs.debug('cogs.webapp.Event falling back to XMLHTTPRequest');
                cogs.webapp.Event.http_req = new XMLHttpRequest();
                cogs.webapp.Event.http_req.multipart = true;
                cogs.webapp.Event.http_req.async = true;
                cogs.webapp.Event.http_req.open('GET', cogs.webapp.Event.listen_url + 'replace', true);
                cogs.webapp.Event.http_req.onload = cogs.webapp.Event.receive_event;
                cogs.webapp.Event.http_req.onerror = cogs.webapp.Event.error;
                cogs.webapp.Event.http_req.send( null );
            }
        }
    },
    register_event_handler: function (event_name, handler){
        if (typeof cogs.webapp.Event.listeners[event_name] == 'undefined'){
            cogs.webapp.Event.listeners[event_name] = [];
        }
        var index = cogs.webapp.Event.listeners[event_name].length;
        cogs.webapp.Event.listeners[event_name].push(handler);
        //cogs.debug(cogs.webapp.Event.listeners[event_name]);
        cogs.webapp.Event.listen();
        return index;
    },
    unregister_event_handler: function (event_name, event_index){
        var new_events = [];
        for (var e=0;e<cogs.webapp.Event.listeners[event_name].length;e++){
            if (e != event_index){
                new_events.push(cogs.webapp.Event.listeners[event_name][e]);
            }
        }
        cogs.webapp.Event.listeners[event_name] = new_events;
    }
};



/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */


var _CONTROLLER = null;

var Controller_hooks = {};

var Controller = {
    current_template_name: '',
    current: '',
    _current_data: {},
    controllers: {},
    call_stack: {},
    call_stack_positions: {},
    worker: null,
    options: {},
    mapping: null,
    template: {name:null},
    _hooks: {},
    init: function(mapping, options){
        Controller.options = options || {};
        Controller.mapping = mapping;

        //$.historyInit(Controller.hash_change, '');
        $.hash.init();
        $(document).hashchange(Controller.hash_change);

        // Make sure the hashchange is called on first load
        var init_hash = (document.location.hash)?document.location.hash.replace(/^#/, ''):'home';
        Controller.hash_change(null, init_hash);
        //cogs.debug('init controller');

        // add a page load callback for all pages

    },
    data: function(index, value){
        if (typeof index != 'undefined'){
            if (typeof value == 'undefined'){
                return _CONTROLLER._current_data[index];
            }
            else {
                return _CONTROLLER._current_data[index] = value;
            }
        }
        else {
            return _CONTROLLER._current_data;
        }
    },
    load_controller: function(url, controller_class, data){
        data = data || {};

        if (Controller.current != controller_class){
            // unload any previous controller
            if (_CONTROLLER && typeof _CONTROLLER.unload == 'function'){
                _CONTROLLER.unload();
            }

            if (_CONTROLLER){
                delete _CONTROLLER;
            }

            if (typeof window[controller_class] != 'undefined'){
                cogs.debug(controller_class + ' EXISTS!');
                Controller.controller_loaded(controller_class, data);
            }
            else {
                var store = cogs.get_store();
                var stored_controller = store.get('controller.' + controller_class);
                
                if (stored_controller){
                    //cogs.debug('loading stored controller');
                    eval(stored_controller);
                    window[controller_class] = eval(controller_class);
                    Controller.controller_loaded(controller_class, data);
                }
                else {
                    $.ajax({
                        url: url,
                        dataType: 'text',
                        cache: true,
                        success: function (script_str){
                            var store = cogs.get_store();
                            store.set('controller.' + controller_class, script_str);
                            eval(script_str);
                            window[controller_class] = eval(controller_class);
                            Controller.controller_loaded(controller_class, data);
                        },
                        error: function(xhr, text_status, e){
                            if (xhr.status == 404){
                                cogs.alert('Page not found');
                            }
                            else if (xhr.status >= 500){
                                cogs.alert('Server error, please refresh the page');
                            }
                            else {
                                cogs.debug('AJAX error loading controller');
                                cogs.debug(xhr, 'xhr');
                                cogs.debug(xhr.statusCode(), 'xhr statusCode');
                                cogs.debug(xhr.responseText, 'xhr responseText');
                                cogs.debug(text_status, 'text_status');
                                cogs.debug(e, 'e');
                                cogs.debug(e.message, 'e.message');
                            }
                        }
                    });
                }
            }
        }
        else {
            // Only send modified data to data_change
            var prev_data = _CONTROLLER.data();
            var modified_data = {};

            for (var d in data){
                if (typeof prev_data[d] == 'undefined' || prev_data[d] != data[d]){
                    modified_data[d] = data[d];
                }
            }

            _CONTROLLER._current_data = data;
            if (typeof _CONTROLLER.data_change == 'function'){
                _CONTROLLER.data_change(modified_data);
            }
        }
    },
    controller_loaded: function(controller_class, data){
        if (typeof window[controller_class] != 'undefined') {
            _CONTROLLER = window[controller_class];

            _CONTROLLER.init(data);
            _CONTROLLER._current_data = data;
            //_CONTROLLER.data_change(data);

            Controller.current = controller_class;
        }
        else {
            cogs.debug('Controller ' + controller_class + ' not found');
        }
    },
    unload: function(){
        /* need to reimplement */
    },
    ready: function(){
        /* need to reimplement */
    },
    worker_message : function(e){
        //cogs.debug('worker message');
        //cogs.debug(e);
        var message = JSON.parse(e.data);
        cogs.debug('received command '+message.command+' from worker ');
        switch (message.command){
            case 'load_template':
                $t.load_template(message.params.name);
                break;
            case 'render_template':
                cogs.debug('showing template');
                $t.show();
                break;
            case 'data_change':
                cogs.debug('new data received from worker');
                cogs.debug(message.params.data);

                $t.data('message', params.data);
                break;
            case 'log':
                cogs.debug(message.params.str);
                break;
        }
    },
    parse_get_data: function (raw_str){
        var data = {};
        var get_kv_pair_arr = raw_str.split('&');

        for (var i=0;i<get_kv_pair_arr.length;i++){
            var get_kv_arr = get_kv_pair_arr[i].split('=');
            var kv_name = get_kv_arr.shift();
            data[kv_name] = unescape(get_kv_arr.join('='));
        }

        return data;
    },
    location: function(l){
        // build a url data object
        var url_obj = {};
        url_obj.host = l.host;
        url_obj.port = l.port || 80;
        url_obj.path = l.pathname;
        url_obj.data = Controller.parse_get_data(l.search.substr(1));
        url_obj.hash = l.hash.substr(1);
        //cogs.debug(l);
        if (url_obj.hash){
            var hash_data_arr = url_obj.hash.split('@@');
            if (hash_data_arr.length > 1){
                url_obj.hash_data = {};
                var hash_data_str = hash_data_arr[1];

                url_obj.hash_data = Controller.parse_get_data(hash_data_str);


                url_obj.hash = hash_data_arr[0];
            }
        }

        return url_obj;
    },
    mapping_data: function(url_obj){
        var mapping_data = null;
        
        if (typeof Controller.mapping == 'function'){
            mapping_data = Controller.mapping(url_obj);
        }
        else if ($.isArray(Controller.mapping)) {
            mapping_data = [];
            for (var m=0;m<Controller.mapping.length;m++){
                var map = Controller.mapping[m];
                if (map.path == '*' || map.path == url_obj.path){
                    if (map.hash == '*' || map.hash == url_obj.hash){
                        mapping_data.push({
                            name: map.name,
                            file: map.file,
                            data: map.data || url_obj.hash_data
                        });
                    }
                }
            }
        }
        
        return mapping_data;
    },
    hash_change: function(e, name){
        cogs.debug('Hash change event ' + name);
        var url_obj = Controller.location(document.location);

        var mapping_data = Controller.mapping_data(url_obj);

        if ($.isArray(mapping_data)){
            for (var m=0;m<mapping_data.length;m++){
                if (mapping_data[m] && mapping_data[m].file && mapping_data[m].name){
                    Controller.load_controller(mapping_data[m].file, mapping_data[m].name, mapping_data[m].data);
                }
            }
        }
        else if (mapping_data && mapping_data.file && mapping_data.name){
            Controller.load_controller(mapping_data.file, mapping_data.name, mapping_data.data);
        }
        else {
            cogs.debug('No map for ' + name, null, 'error');
        }
        
        // Hide anyoverlay that was left
        cogs.hide_overlay();
        cogs.hide_loading_spinner();

        return;
        
    },
    setup_dialog: function(name, options){
        var min_width = 600;
        var width = 600;
        if (options){
            if (typeof options.min_width != 'undefined'){
                min_width = options.min_width;
            }
            if (typeof options.width != 'undefined'){
                width = options.width;
            }
        }
        else {
            options = {};
        }
        var form_id = options.form_id || name+'_add_form';
        //alert(options.form_id);

        //cogs.debug('setting up ' + name + ' dialog');
        var d = $('#'+name+'_add_dialog').get(0);
        if (!d){
            cogs.debug('dialog #' + name + '_add_dialog NOT found in dom');
        }
        
        var dialog_opts = {
            autoOpen: false,
            minWidth: min_width,
            width: width,
            modal: true,
            buttons: {}
        };
        var dialog_ok = options.dialog_ok || 'OK';
        var dialog_cancel = options.dialog_cancel || 'Cancel';

        dialog_opts.buttons[dialog_ok] = function(){
            if (typeof options.ok_callback == 'function'){
                var ok_res = options.ok_callback();
            }
            
            if (ok_res !== false){
                $('#'+form_id).submit();
            }
        };
        dialog_opts.buttons[dialog_cancel] = function(){
            if (typeof options.cancel_callback == 'function'){
                options.cancel_callback();
            }

            $('#'+name+'_add_dialog').xdialog('close');
            //clear the form fields
            $('#'+form_id + ' input').val('');
            $('#'+form_id + ' textarea').val('');
            $('#'+form_id + ' select').val('');
        };

        $('#'+name+'_add_dialog').xdialog(dialog_opts);
    },
    destroy_dialog: function(name){
        $('#'+name+'_add_dialog').xdialog('destroy');
        //$('#'+name+'_add_dialog').remove();
    },
    overlay: function(base_controller, overlay_controller){
        for (var member in overlay_controller){
            if (typeof base_controller[member] == 'function'){
                if (typeof base_controller.call_stack[member] == 'undefined'){
                    base_controller.call_stack[member] = [];
                    base_controller.call_stack_positions[member] = 0;
                }
                base_controller.call_stack[member].push(base_controller[member]);
                base_controller.call_stack_positions[member]++;
            }
            base_controller[member] = overlay_controller[member];
        }
    },
    process_stack: function (stack_name, params){
        var retval = null;
        if (!params){
            params = [];
        }
        if (typeof this.call_stack[stack_name] != 'undefined'){
            //cogs.debug('processing call stack for '+stack_name);

            var stack_position = --this.call_stack_positions[stack_name];
            if (typeof this.call_stack[stack_name][stack_position] == 'function'){
                var f = this.call_stack[stack_name][stack_position];
                if (f){
                    retval = f.apply(null, params);
                }
            }
            /* reset stack counter the stack if processed if posistion = 0 */
            if (stack_position == 0){
                this.call_stack_positions[stack_name] = this.call_stack[stack_name].length;
            }
        }

        return retval;
    },
    reset_stack: function(stack_name){
        this.call_stack_positions[stack_name] = this.call_stack[stack_name].length;
    },
    register_hook: function(signal_name, callback){
        if (typeof Controller._hooks[signal_name] == 'undefined'){
            Controller._hooks[signal_name] = [];
        }
        Controller._hooks[signal_name].push(callback);
    },
    call_plugins: function(signal_name, obj){
        if (typeof Controller._hooks[signal_name] == 'undefined'){
            return true;
        }

        var res = true;
        for (var h=0;h<Controller._hooks[signal_name].length;h++){
            res = Controller._hooks[signal_name][h](signal_name, obj);
            if (res === false){
                return false;
            }
        }
    },
    set_title: function(title){
        document.title = title;
    }
};


//var CONTROLLER = new Controller();


/* Date Format Method
// copyright Stephen Chapman, 20th November 2007, 6th May 2009
// http://javascript.about.com
// permission to use this JavaScript on your web page is granted
// provided that all of the code below in this script (including these
// comments) is used without any alteration */

Date.prototype.getMDay = function() {return (this.getDay() + 6) %7;};Date.prototype.getISOYear = function() {var thu = new Date(this.getFullYear(),this.getMonth(),this.getDate()+3-this.getMDay());return thu.getFullYear();};Date.prototype.getISOWeek = function() {var onejan = new Date(this.getISOYear(),0,1);var wk = Math.ceil((((this - onejan) / 86400000) + onejan.getMDay()+1)/7);if (onejan.getMDay() > 3) wk--;return wk;};Date.prototype.getJulian = function() {return Math.floor((this / 86400000) - (this.getTimezoneOffset()/1440) + 2440587.5);};Date.prototype.getMonthName = function() {var m = ['January','February','March','April','May','June','July','August','September','October','November','December'];return m[this.getMonth()];};Date.prototype.getMonthShort = function() {var m = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];return m[this.getMonth()];};Date.prototype.getDayName = function() {var d = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];return d[this.getDay()];};Date.prototype.getDayShort = function() {var d = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];return d[this.getDay()];};Date.prototype.getOrdinal = function() {var d = this.getDate();switch(d) {case 1: case 21: case 31: return 'st'; case 2: case 22: return 'nd'; case 3: case 23: return 'rd'; default: return 'th';}};Date.prototype.getDOY = function() {var onejan = new Date(this.getFullYear(),0,1);return Math.ceil((this - onejan) / 86400000);}; Date.prototype.getWeek = function() {var onejan = new Date(this.getFullYear(),0,1); return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7);};Date.prototype.getStdTimezoneOffset = function() {var jan = new Date(this.getFullYear(), 0, 1); var jul = new Date(this.getFullYear(), 6, 1); return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());}; Date.prototype.getDST = function() {return this.getTimezoneOffset() < this.getStdTimezoneOffset();};Date.prototype.getSwatch = function() {var swatch = ((this.getUTCHours() + 1)%24) + this.getUTCMinutes()/60 +  this.getUTCSeconds()/3600; return Math.floor(swatch*1000/24);};function _daysInMonth(month,year) {var dd = new Date(year, month, 0);return dd.getDate();};Date.prototype.format = function(f) {var fmt = f.split(''); var res = ''; for (var i = 0, l = fmt.length; i < l; i++) {switch(fmt[i]) {case '^':  res += fmt[++i]; break;  case 'd': var d = this.getDate();  res += ((d<10)?'0':'')+d;  break; case 'D': res += this.getDayShort(); break; case 'j': res += this.getDate(); break; case 'l': res += this.getDayName(); break; case 'S': res += this.getOrdinal(); break; case 'w': res += this.getDay(); break; case 'z': res += this.getDOY() - 1; break;  case 'R': var dy = this.getDOY(); if (dy<9) dy = '0'+dy; res += (dy > 99)? dy : '0'+dy; break; case 'F': res += this.getMonthName(); break; case 'm': var m = this.getMonth()+1; res += ((m<10)?'0':'')+m;  break; case 'M': res += this.getMonthShort(); break; case 'n': res += (this.getMonth()+1); break; case 't': res += _daysInMonth(this.getMonth()+1, this.getFullYear()); break; case 'L': res += (_daysInMonth(2, this.getFullYear()) == 29)? 1:0; break; case 'Y': res += this.getFullYear(); break; case 'y': var y = this.getFullYear().toString().substr(3); res += ((y<10)?'0':'')+y; break; case 'a': res += (this.getHours()>11)?'pm':'am'; break; case 'A': res += (this.getHours()>11)?'PM':'AM'; break; case 'g': var h = this.getHours()%12; res += (h==0)?12:h; break; case 'G': res += this.getHours(); break; case 'h': var h = this.getHours()%12; res += (h==0)?12:(h>9)?h:'0'+h; break; case 'H': var h = this.getHours(); res += (h>9)?h:'0'+h; break; case 'i': var m = this.getMinutes(); res += (m>9)?m:'0'+m; break; case 's': var s = this.getSeconds(); res += (s>9)?s:'0'+s; break; case 'O': var m = this.getTimezoneOffset(); var s = (m<0)?'+':'-'; m = Math.abs(m); var h = (m/60).toFixed(0); m = m%60; res += s + ((h>9)?h:'0'+h) + ((m>9)?m:'0'+m); break; case 'P': var m = this.getTimezoneOffset(); var s = (m<0)?'+':'-'; m = Math.abs(m); var h = (m/60).toFixed(0); m = m%60; res += s + ((h>9)?h:'0'+h) + ':' + ((m>9)?m:'0'+m); break; case 'U': res += (this.getTime()/1000).toFixed(0); break; case 'I': res += this.getDST() ? 1:0; break; case 'K': res += this.getDST() ? 'DST':'Std'; break; case 'c': res += this.format('Y-m-d^TH:i:sP'); break; case 'r': res += this.format('D, j M Y H:i:s P'); break; case 'Z': var tz = this.getTimezoneOffset() * -60; res += tz; break; case 'W': res += this.getISOWeek(); break; case 'X': res += this.getWeek(); break;  case 'x': var w = this.getWeek();res += ((w<10)?'0':'')+w; break;  case 'B': res += this.getSwatch(); break;  case 'N':  var d = this.getDay(); res += d?d:7; break; case 'u': res += this.getMilliseconds()*1000; break; case 'o':  res += this.getISOYear(); break; case 'J': res += this.getJulian(); break; case 'e': case 'T': break;  default: res += fmt[i];}}return res;}
/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */


/* errors.js */

cogs.error = {};

cogs.error.Error = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "Error";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}

cogs.error.NotFoundError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "NotFoundError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}

cogs.error.StoreError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "StoreError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}

cogs.error.DuplicateIndexError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "DuplicateIndexError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}

cogs.error.BadValuesError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "BadValuesError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}


cogs.error.ForbiddenError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "ForbiddenError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}


cogs.error.LoginRequiredError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "LoginRequiredError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}



cogs.error.NetworkError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "NetworkError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}



cogs.error.KindError = function (message, code){
    this.message = message;
    this.code = code;
    if (typeof this.code == 'undefined'){
        this.code = -1;
    }
    this.name = "KindError";
    this.toString = function(){
        return this.name + ' (' + this.code + ') : ' + this.message;
    }
}


/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/*
 *
 * Usage :
 *
 * $t = $('#content').xtemplate();
 * $t.data('foo', 'bar');
 * $t.data({'baz': 'qux'});
 * $t.template('<template>foo is <var name="foo" /><br />baz is <var name="baz" /></template>');
 * $t.render();
 *
 *
 * Data and template can be supplied in the constrictor as options
 *
 * $t = $('#content').xtemplate({
 *     data: {'foo': 'bar', 'baz': 'qux'},
 *     template: '<template>foo is <var name="foo" /><br />baz is <var name="baz" /></template>'
 * });
 * $t.render();
 *
 * Other options
 *
 * use_cache = true/false whether to cache the templates.  A refresh will clear the cache for now.
 *             TODO : Use expires header from server
 *
 * anim_type = 'fade'/'slide' The type of animation.  Defaults to fade
 *
 * anim_speed = 'slow'/'fast'/number in milliseconds If set the template will use an animation when rendering and use
 *              that speed.  If null, the template will not animate.
 *
 * load_url = If given, must be a function which takes the name of the template and returns the url that should be used
 *            to fetch it.  Default is "function(name){return '/templates/' + name + '.xml'}"
 *
 * translation_url = Function to take the language and translation name and returns the url to load.
 *
 * config = Configuration variables.  Currently supported decimal_separator, thousand_separator, decimal_places
 */


/* Functions because IE does not support importNode()
 * See : http://www.alistapart.com/articles/crossbrowserscripting/ */
if (document.ELEMENT_NODE == null) {
    document.ELEMENT_NODE = 1;
    document.ATTRIBUTE_NODE = 2;
    document.TEXT_NODE = 3;
    document.CDATA_SECTION_NODE = 4;
    document.ENTITY_REFERENCE_NODE = 5;
    document.ENTITY_NODE = 6;
    document.PROCESSING_INSTRUCTION_NODE = 7;
    document.COMMENT_NODE = 8;
    document.DOCUMENT_NODE = 9;
    document.DOCUMENT_TYPE_NODE = 10;
    document.DOCUMENT_FRAGMENT_NODE = 11;
    document.NOTATION_NODE = 12;
}

function _importNode(node, allChildren) {
    var newNode;
    switch (node.nodeType) {
        case document.ELEMENT_NODE:
            newNode = this.createElement(node.nodeName);
            /* does the node have any attributes to add? */
            var il;
            if (node.attributes && node.attributes.length > 0){
                il = node.attributes.length;
                for (var i = 0;  i < il; i++){
                    if (node.attributes[i]){
                        var attr_name = node.attributes[i].nodeName;
                        var attr_value = node.getAttribute(node.attributes[i].nodeName);
                        newNode.setAttribute(attr_name, attr_value);
                    }
                }
            }
            /* are we going after children too, and does the node have any? */
            if (allChildren && node.childNodes && node.childNodes.length > 0){
                il = node.childNodes.length;
                for (i = 0; i < il; i++){
                    newNode.appendChild(this.importNode(node.childNodes[i], allChildren));
                }
            }
            break;
        case document.DOCUMENT_FRAGMENT_NODE:
            newNode = this.createDocumentFragment();
            if (node.childNodes && node.childNodes.length > 0){
                il = node.childNodes.length;
                for (i = 0; i < il ;i++){
                    newNode.appendChild(this.importNode(node.childNodes[i], allChildren));
                }
            }
            break;
        case document.TEXT_NODE:
        case document.CDATA_SECTION_NODE:
        case document.COMMENT_NODE:
            newNode = this.createTextNode(node.nodeValue);
            break;
    }

    return newNode;

};



(function($) {
    $.fn.xtemplate = function(options){
        //debug(this);
        options = options || {};
        options.config = options.config || {};

        var settings = $.extend({
            use_cache: false,
            anim_speed: null,
            anim_type: 'fade',
            load_url: function(name){return '/templates/' + name + '.xml'},
            block: null,
            parent: null
        }, options);

        var data = options.data || {};
        var template = options.template || null;

        $(this).data('data', data);
        $(this).data('template', template);

        var xt = new XTemplate(this, settings);

        $(document).triggerHandler('xtemplateloaded', [xt]);

        return xt;
    }

    function XTemplate(jqobj, settings){
        //debug(jqobj);
        this.jqobj = jqobj;
        this.template_cache = {};
        this.blocks = {};
        this.command_delegates = {};
        this.sections_rendered = [];
        this.blocks_rendered = [];
        this.show_on_load = false;
        this.settings = settings;
        this.rendered = false;
        this.block = settings.block;
        this.parent = settings.parent;
        this.editable_connected = false;
        this.template_name = '';
        this.element_missing = false;
        
        if ($(jqobj).length == 0){
            cogs.debug(jqobj, 'Template element '+jqobj.selector+' not found in construction', 'warn');
        }
        
        window.xtemplate_instances = window.xtemplate_instances || [];
        this.instance_id = window.xtemplate_instances.length;
        window.xtemplate_instances.push(this);
        // an object for the editable tag to use
        this.editable = {
            add_callbacks: {},
            edit_callbacks: {},
            cancel_callbacks: {},
            save_callbacks: {},
            del_callbacks: {},
            editlist_callbacks: {},
            editors_setup:{},
            group_modes:{},
            wym_options: null,
            mode: function(group_name){
                return this.group_modes[group_name];
            },
            enforce_mode: function(group_name){
                if (this.group_modes[group_name] == 'edit'){
                    this.edit_mode(group_name);
                }
                else {
                    this.display_mode(group_name);
                }
            },
            edit_mode: function(group_name){
                
                this.group_modes[group_name] = 'edit';

                $('*[data-editgroup="'+group_name+'"][data-edittype="edit"][type!="hidden"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="display"]').hide();

                $('*[data-editgroup="'+group_name+'"][data-edittype="savebutton"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="cancelbutton"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="donebutton"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="deletebutton"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="editbutton"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="addbutton"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="editlistbutton"]').show();

                if (this.wym_options){
                    try {
                        var self = this;
                        $('*[data-editgroup='+group_name+'][data-texttype="richtext"]').each(function(){
                            var instance_id = $(this).attr('data-wymeditor-instance');
                            if (typeof instance_id != 'undefined'){
                                var editor = $.wymeditors(instance_id);
                                $(editor.box()).show();
                            }
                            else {
                                // Set up the editor again
                                instance_id = WYMeditor.INSTANCES.length;
                                $(this).wymeditor(self.wym_options);
                                $(this).attr('data-wymeditor-instance', instance_id);
                            }
                            $(this).hide();
                        });
                    }
                    catch (e){
                        cogs.error_handler(e, 'Error creating editor template.js line 231');
                    }
                }

            },
            display_mode: function(group_name){

                this.group_modes[group_name] = 'display';

                if (this.wym_options){
                    try {
                        var self = this;                
                        $('*[data-editgroup="'+group_name+'"][data-texttype="richtext"]').each(function(){
                            var instance_id = $(this).attr('data-wymeditor-instance');
                            if (typeof instance_id == 'undefined'){
                                instance_id = WYMeditor.INSTANCES.length;
                                $(this).wymeditor(self.wym_options);
                                $(this).attr('data-wymeditor-instance', instance_id);
                            }
                            var editor = $.wymeditors(instance_id);
                            $(editor.box()).hide();
                            self.editors_setup[group_name] = true;
                        });
                    }
                    catch (e){
                        cogs.error_handler(e, 'Error creating editor template.js line 251');
                    }
                }

                $('*[data-editgroup="'+group_name+'"][data-edittype="edit"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="display"]').show();

                $('*[data-editgroup="'+group_name+'"][data-edittype="savebutton"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="cancelbutton"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="donebutton"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="deletebutton"]').hide();
                $('*[data-editgroup="'+group_name+'"][data-edittype="editbutton"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="addbutton"]').show();
                $('*[data-editgroup="'+group_name+'"][data-edittype="editlistbutton"]').hide();

                /*$('*[data-editgroup='+group_name+'][data-texttype=richtext]').each(function(){
                    var editor_id = $(this).attr('data-editorid');
                    if (typeof editor_id != 'undefined'){
                        var editor = $.wymeditors(editor_id);
                        $(editor.box()).hide();
                    }
                });*/
            },
            add: function(group_name, callback){
                if (typeof this.add_callbacks[group_name] == 'undefined'){
                    this.add_callbacks[group_name] = [];
                }

                if (callback){
                    this.add_callbacks[group_name].unshift(callback);
                }
                else {
                    for (var c=0;c<this.add_callbacks[group_name].length;c++){
                        if (typeof this.add_callbacks[group_name][c] == 'function'){
                            var res = this.add_callbacks[group_name][c]();
                            if (res === false){
                                return;
                            }
                        }
                    }
                    
                    $('#'+group_name+'_add_dialog').xdialog('open');
                }
            },
            edit: function(group_name, callback){
                if (typeof this.edit_callbacks[group_name] == 'undefined'){
                    this.edit_callbacks[group_name] = [];
                }

                if (callback){
                    this.edit_callbacks[group_name].unshift(callback);
                }
                else {
                    for (var c=0;c<this.edit_callbacks[group_name].length;c++){
                        var res = this.edit_callbacks[group_name][c]($('*[data-editgroup="'+group_name+'"][data-edittype="edit"]'));
                        if (res === false){
                            return;
                        }
                    }
                    
                    this.edit_mode(group_name);
                }
            },
            cancel: function(group_name, callback){
                if (typeof this.cancel_callbacks[group_name] == 'undefined'){
                    this.cancel_callbacks[group_name] = [];
                }

                if (callback){
                    this.cancel_callbacks[group_name].unshift(callback);
                }
                else {
                    for (var c=0;c<this.cancel_callbacks[group_name].length;c++){
                        if (typeof this.cancel_callbacks[group_name][c] == 'function'){
                            var res = this.cancel_callbacks[group_name][c]($('*[data-editgroup="'+group_name+'"][data-edittype="edit"]'));
                            if (res === false){
                                return;
                            }
                        }
                    }

                    this.display_mode(group_name);
                }
            },
            save: function(group_name, callback){
                if (typeof this.save_callbacks[group_name] == 'undefined'){
                    this.save_callbacks[group_name] = [];
                }

                if (callback){
                    this.save_callbacks[group_name].unshift(callback);
                }
                else {
                    /* update editors */
                    if (typeof $.wymeditors == 'function'){
                        $('*[data-editgroup="'+group_name+'"][data-texttype="richtext"]').each(function(){
                            var instance_id = $(this).attr('data-wymeditor-instance');
                            if (typeof instance_id != 'undefined'){
                                var editor = $.wymeditors(instance_id);
                                editor.update();
                            }
                        });
                    }


                    var input_elements = {};
                    var raw_input_elements = $('*[data-editgroup="'+group_name+'"][data-edittype="edit"]');
                    for (var e=0;e<raw_input_elements.length;e++){
                        var next_input = null;
                        var next_name = null;
                        var next_key = null;

                        if (raw_input_elements[e].tagName.toUpperCase() == 'SPAN'){
                            var type = $(raw_input_elements[e]).data('datatype');
                            if (type == 'boolean'){
                                // boolean values are 2 radio buttons in a span tag
                                var radio_yes = $('input[type="radio"][value="1"]', raw_input_elements[e]).get(0);
                                var radio_checked = $('input[type="radio"]:checked', raw_input_elements[e]).get(0);
                                var radio_value = $(radio_checked).val();

                                var radio_name = radio_yes.name;
                                if ($(raw_input_elements[e]).attr('data-key')){
                                    radio_name = radio_name.replace('[' + $(raw_input_elements[e]).attr('data-key') + ']', '');
                                }
                                // create a new input text entry with either 0 or 1
                                next_input = $('<input type="text" />');
                                next_input.attr('name', radio_name);
                                next_input.attr('value', radio_value);

                                if ($(raw_input_elements[e]).attr('data-key')){
                                    $(next_input).attr('data-key', $(raw_input_elements[e]).attr('data-key'));
                                }

                                var orig_value = $(raw_input_elements[e]).attr('data-originalvalue');
                                orig_value = (orig_value == 'true')?'1':'0';
                                next_input.attr('data-originalvalue', orig_value);

                                next_input = next_input.get(0);
                            }
                            else if (type == 'datetime'){
                                next_input = $('<input type="text" />');
                                var date_input = $('input[type="date"]', raw_input_elements[e]);
                                var time_input = $('input[type="time"]', raw_input_elements[e]);
                                if ($(raw_input_elements[e]).attr('data-key')){
                                    $(next_input).attr('data-key', $(raw_input_elements[e]).attr('data-key'));
                                }
                                var datetime_val = '';
                                var date_val = $(date_input).val();
                                if (date_val){
                                    var time_val = $(time_input).val();
                                    if (time_val == ''){
                                        time_val = '00:00';
                                    }
                                    datetime_val = date_val + ' ' + time_val;
                                }
                                next_input.attr('value', datetime_val);
                                next_input.attr('name', $(raw_input_elements[e]).attr('name'));
                                next_input.attr('data-originalvalue', $(raw_input_elements[e]).attr('data-originalvalue'));
                                if (datetime_val != $(raw_input_elements[e]).attr('data-originalvalue')){
                                    next_input.attr('data-modified', '1');
                                }
                            }
                        }
                        else {
                            next_input = raw_input_elements[e];
                            //input_elements.push(raw_input_elements[e]);
                        }

                        if ($(next_input).attr('name')){
                            next_name = $(next_input).attr('name');
                        }
                        else if ($(next_input).attr('id')){
                            next_name = $(next_input).attr('id');
                        }

                        if ($(next_input).attr('data-key')){
                            next_key = $(next_input).attr('data-key');
                        }


                        if (next_key && next_name && next_input){
                            if (typeof input_elements[next_key] == 'undefined'){
                                input_elements[next_key] = {};
                            }
                            input_elements[next_key][next_name] = next_input;
                            this.update_modified(next_input);
                        }
                        else if (next_name && next_input){
                            input_elements[next_name] = next_input;
                            this.update_modified(next_input);
                        }

                    }

                    //debug(raw_input_elements);
                    //cogs.debug(input_elements, 'Final input elements');

                    //return;

                    for (var c=0;c<this.save_callbacks[group_name].length;c++){
                        if (typeof this.save_callbacks[group_name][c] == 'function'){
                            var res = this.save_callbacks[group_name][c](input_elements);
                            if (res === false){
                                return;
                            }
                        }
                    }

                    this.display_mode(group_name);
                }
            },
            del: function(group_name, callback, key){
                if (typeof this.del_callbacks[group_name] == 'undefined'){
                    this.del_callbacks[group_name] = [];
                }

                if (callback){
                    this.del_callbacks[group_name].unshift(callback);
                }
                else {
                    for (var c=0;c<this.del_callbacks[group_name].length;c++){
                        if (typeof this.del_callbacks[group_name][c] == 'function'){
                            var res = this.del_callbacks[group_name][c](key);
                            if (res === false){
                                return;
                            }
                        }
                    }

                    this.display_mode(group_name);
                }
            },
            editlist: function(group_name, callback, key){
                if (typeof this.editlist_callbacks[group_name] == 'undefined'){
                    this.editlist_callbacks[group_name] = [];
                }

                if (callback){
                    this.editlist_callbacks[group_name].unshift(callback);
                }
                else {
                    for (var c=0;c<this.editlist_callbacks[group_name].length;c++){
                        if (typeof this.editlist_callbacks[group_name][c] == 'function'){
                            var res = this.editlist_callbacks[group_name][c](key);
                            if (res === false){
                                return;
                            }
                        }
                    }

                    //this.display_mode(group_name);
                }
            },
            update_modified: function(input_obj){
                // callback to set data-modified automatically on all editable fields
                var mod_value = (this.is_modified(input_obj))?1:0;
                $(input_obj).attr('data-modified', mod_value);
            },
            is_modified: function(input_obj){
                //this.update_modified(input_obj);
                var current_value = $(input_obj).val();
                var original_value = $(input_obj).attr('data-originalvalue');
                if (typeof original_value == 'undefined'){
                    return false;
                }
                var mod_value = (current_value == original_value)?false:true;
                if (mod_value){
                    debug(current_value + ' != ' + original_value + ' in editable.is_modified');
                }
                return mod_value;
            },
            button_click: function(evt){
                var template = window.xtemplate_instances[evt.data.template_id];
                
                if (template){
                    $(template.jqobj).triggerHandler('editable_' + $(this).data('edittype') + '_click', {group:$(this).data('editgroup'), key:$(this).data('key'), template:template});
                }
            },
            
            
            connect_buttons: function(group_name, callbacks){
                cogs.debug('WARNING : connect_buttons is no longer used (' + group_name + ')', '', 'warn');
            },
            disconnect_buttons: function(group_name){
                cogs.debug('WARNING : disconnect_buttons is no longer used (' + group_name + ')', '', 'warn');
            },
            reset: function(group_name){
                cogs.debug('WARNING : reset is no longer used (' + group_name + ')', '', 'warn');
            },
            reset_all: function(){
                this.add_callbacks = {};
                this.edit_callbacks = {};
                this.cancel_callbacks = {};
                this.save_callbacks = {};
                this.del_callbacks = {};
                this.editlist_callbacks = {};
            }
        };
        
        this.editable.template_id = this.instance_id;
        
        if (typeof WYMeditor != 'undefined'){
            this.editable.wym_options = {
                //we customize the XHTML structure of WYMeditor by overwriting 
                //the value of boxHtml. In this example, "CONTAINERS" and 
                //"CLASSES" have been moved from "wym_area_right" to "wym_area_top":
                boxHtml:"<div class='wym_box'>"
                        + "<div class='wym_area_top'>"
                        + WYMeditor.TOOLS
                        + WYMeditor.CONTAINERS
                        + "</div>"
                        + "<div class='wym_area_left'></div>"
                        + "<div class='wym_area_right'>"
                        + "</div>"
                        + "<div class='wym_area_main'>"
                        + WYMeditor.HTML
                        + WYMeditor.IFRAME
                        + WYMeditor.STATUS
                        + "</div>"
                        + "<div class='wym_area_bottom'>"
                        + "</div>"
                        + "</div>",
                
                //postInit is a function called when WYMeditor instance is ready
                //wym is the WYMeditor instance
                postInit: function(wym) {
                    if (!wym._box){
                        cogs.debug('wym._box does not exist!', '', 'error');
                        cogs.trace();
                        return;
                    }
                    //we make all sections in area_top render as dropdown menus:
                    try {
                        jQuery(wym._box)
                                        //first we have to select them:
                                        .find(".wym_area_top .wym_section")
                                        //then we remove the existing class which make some of them render as a panels:
                                        .removeClass("wym_panel")
                                        //then we add the class which will make them render as a dropdown menu:
                                        .addClass("wym_dropdown")
                                        //finally we add some css to make the dropdown menus look better:
                                        .css("width", "160px")
                                        .css("float", "left")
                                        .css("margin-right", "5px")
                                        .find("ul")
                                        .css("width", "140px");

                        //add a ">" character to the title of the new dropdown menus (visual cue)
                        jQuery(wym._box).find(".wym_tools, .wym_classes ")
                                         .find(WYMeditor.H2)
                                         .append("<span>&nbsp;&gt;</span>");
                    }
                    catch (e){
                        cogs.debug('Error in WYMEditor.postInit');
                        cogs.debug(e);
                    }
                }
            };
        }
        // End editable


        
        this.preload_i18n = function(xml_node){
            //cogs.debug('Preloading i18n');
            //cogs.debug(xml_node);
            var i18n_nodes = [];
            if (typeof xml_node.getElementsByTagNameNS == 'function'){
                i18n_nodes = xml_node.getElementsByTagNameNS('http://cogs/xtl.dtd', 'i18n');
            }
            else {
                i18n_nodes = xml_node.getElementsByTagName('i18n');
            }
            var required_groups = [];
            for (var i=0;i<i18n_nodes.length;i++){
                var i18n_node = i18n_nodes[i];
                var i18n_full_name = $(i18n_node).attr('name');
                if (i18n_full_name){
                    var i18n_name_arr = i18n_full_name.split('#');
                    if (i18n_name_arr.length == 2){
                        var i18n_group = i18n_name_arr[0];
                        var i18n_cache = cogs.i18n.cache(i18n_group);
                        if (!i18n_cache){
                            if ($.inArray(i18n_group, required_groups) == -1){
                                required_groups.push(i18n_group);
                            }
                        }
                    }
                }
                else {
                    cogs.debug(i18n_node, 'i18n tag found without name', 'error');
                }
            }
            
            if (required_groups.length == 0){
                return;
            }

            var joined_groups = required_groups.join(',');
            
            var ajax_opts = {
                async: false,
                data: {group: joined_groups},
                url: '/__translation/data'
            };
            var resp = $.ajax(ajax_opts);

            if (resp.status == 200){
                var obj = JSON.parse(resp.responseText);
                //cogs.debug('PArsed response');
                //cogs.debug(obj);
                if (obj.translations){
                    if (required_groups.length == 1){
                        cogs.i18n.cache(obj.translations.group, obj.translations.strings);
                    }
                    else {
                        for (var t=0;t<obj.translations.length;t++){
                            if (obj.translations[t]){
                                cogs.i18n.cache(obj.translations[t].group, obj.translations[t].strings);
                            }
                        }
                    }
                }
            }
        }

        this.data = function(name, val){
            var d = $(this.jqobj).data('data') || {};

            if ($.isPlainObject(name)){
                for (var i in name){
                    //debug('setting OBJECT template data ' + i + ' to ' + name[i]);
                    d[i] = name[i];
                }
            }
            else if (typeof val == 'undefined'){
                if (typeof name == 'undefined'){
                    // getting all data
                    return d;
                }
                // getting value
                return d[name];
            }
            else {
                //debug('setting template data ' + name + ' to ');
                //debug(val);
                d[name] = val;
            }
            $(this.jqobj).data('data', d);

            $(this.jqobj).triggerHandler('datachanged', {name:name, value:val});

            return this;
        }



        this.reset = function(){
            this.blocks = {};
            this.rendered = false;
            this.sections_rendered = [];
            $(this.jqobj).data('template', null);
            $(this.jqobj).data('data', {});
            //this.unbind('rendered');
            //this.unbind('renderedsection');

            $(this.jqobj).triggerHandler('reset', {});

            //this.unbind('reset');
            //this.unbind('templatechanged');
            //this.unbind('datachanged');

            // reset editable
            this.editable.reset_all();
        }

        this.template = function(t){
            var domdoc = null;
            if (!t || typeof t == 'undefined'){
                debug('Invalid object supplied to template()');
                return;
            }
            else if (typeof t == 'string'){
                // load into a dom document
                domdoc = doc_from_string(t);
            }
            else if (typeof t == 'object' && t.nodeName == '#document-fragment'){
                // create a new domdocument with an empty template node
                domdoc = doc_from_string('<template></template>');
                domdoc.getElementsByTagName('template')[0].appendChild(t);
            }
            else {
                domdoc = t;
            }
            
            if (!this.jqobj.length){
                cogs.debug('Loading template with nothing to append to!', '', 'warn');
                this.jqobj = $(this.jqobj.selector);
                if (this.jqobj.length){
                    cogs.debug('OK - Found template holder now');
                }
            }

            // Preload translations so they do not have to make loads of http requests
            this.preload_i18n(domdoc);
            
            //debug(dom);
            $(this.jqobj).data('template', domdoc);

            $(this.jqobj).triggerHandler('templatechanged', {});
        }

        this.load = function(name, options){
            this.template_name = name;
            options = options || {};
            var use_cache = this.settings.use_cache;
            if (use_cache){
                var store = cogs.get_store();
                var cached_template = store.get('template.' + name);
                
                if (cached_template){
                    debug('Loading from cache - ' + name);

                    this.template(cogs.doc_from_string(cached_template));
                    if (typeof options.success == 'function'){
                        options.success(name);
                    }

                    $(this.jqobj).triggerHandler('templateloaded', {name:name});

                    return;
                }
            }
            
            // not in cache or not using it
            var self = this;
            $.ajax({
                url: this.settings.load_url(name),
                success: function(tx){
                    if (self.settings.use_cache){
                        var store = cogs.get_store();
                        store.set('template.' + name, cogs.string_from_doc(tx));
                        //self.template_cache[name] = tx;
                    }
                    self.template(tx);
                    if (typeof options.success == 'function'){
                        options.success(name);
                    }

                    $(self.jqobj).triggerHandler('templateloaded', {name:name});
                },
                error: function(xhr, status, e){
                    if (typeof options.error == 'function'){
                        options.error(name);
                    }
                    else {
                        throw e;
                    }
                }
            });
        }

        this.render = function(cb){
            var xml_doc = $(this.jqobj).data('template');
            //cogs.debug('RENDERING WITH DATA');
            //cogs.debug($(this.jqobj).data('data'));

            //cogs.debug(xml_doc);
            if (!xml_doc){
                debug('XML document is null');
                return false;
            }

            if (typeof xml_doc == 'string'){
                xml_doc = doc_from_string(xml_doc);
                $(this.jqobj).data('template', xml_doc);
            }

            var xml_node = xml_doc.getElementsByTagName('template')[0];
            var html_node = document.createElement('div');

            for (var c=0;c<xml_node.childNodes.length;c++){
                var parsed_node = this.parse_node(xml_node.childNodes[c]);
                if (parsed_node){
                    html_node.appendChild(parsed_node);
                }
            }

            //debug(html_node);

            var jqobj = this.jqobj;
            var self = this;
            this.show_node(html_node, this.jqobj, function(){
                self.editable.reset_all();
                
                $(jqobj).triggerHandler('rendered', {});
                for (var b=0;b<self.blocks_rendered.length;b++){
                    $(jqobj).triggerHandler('renderedblock', {name:self.blocks_rendered[b]});
                }
                for (var s=0;s<self.sections_rendered.length;s++){
                    $(jqobj).triggerHandler('renderedsection', {name:self.sections_rendered[s]});
                }
            });


            // Connect editable buttons
            if (!this.block){
                
                if (!this.editable_connected){
                    $(jqobj).on('click', '*[data-edittype="editbutton"]', {template_id: this.instance_id}, this.editable.button_click);
                    $(jqobj).on('click', '*[data-edittype="savebutton"]', {template_id: this.instance_id}, this.editable.button_click);
                    $(jqobj).on('click', '*[data-edittype="addbutton"]', {template_id: this.instance_id}, this.editable.button_click);
                    $(jqobj).on('click', '*[data-edittype="donebutton"]', {template_id: this.instance_id}, this.editable.button_click);
                    $(jqobj).on('click', '*[data-edittype="cancelbutton"]', {template_id: this.instance_id}, this.editable.button_click);
                    $(jqobj).on('click', '*[data-edittype="deletebutton"]', {template_id: this.instance_id}, this.editable.button_click);
                    
                    $(jqobj).bind('editable_editbutton_click', function(evt, data){
                        data.template.editable.edit(data.group);
                    });
                    $(jqobj).bind('editable_savebutton_click', function(evt, data){
                        data.template.editable.save(data.group);
                    });
                    $(jqobj).bind('editable_addbutton_click', function(evt, data){
                        data.template.editable.add(data.group);
                    });
                    $(jqobj).bind('editable_donebutton_click', function(evt, data){
                        data.template.editable.cancel(data.group);
                    });
                    $(jqobj).bind('editable_cancelbutton_click', function(evt, data){
                        data.template.editable.cancel(data.group);
                    });
                    $(jqobj).bind('editable_deletebutton_click', function(evt, data){
                        data.template.editable.del(data.group, null, data.key);
                    });
                }
                
                this.editable_connected = true;
            }

            this.rendered = true;

            return true;
        }

        this.bind = function(evt_name, cb){
            $(this.jqobj).bind(evt_name, cb);
        }

        this.unbind = function(evt_name, fn){
            $(this.jqobj).unbind(evt_name, fn);
        }

        this.register_command_delegate = function(cmd_name, delegate){
            this.command_delegates[cmd_name] = delegate;
        }

        this.show_node = function(node, target, callback){
            try {
                if (typeof target == 'string'){
                    target = $(target);
                }

                if (this.settings.anim_speed){
                    try {
                        var anim_type = this.settings.anim_type;
                        var anim_speed = this.settings.anim_speed;

                        if (anim_type == 'slide'){

                            $(target).slideUp(this.settings.anim_speed, function(){
                                $(this).empty();
                                while (node.childNodes.length){
                                    try {
                                        $(target).append(node.childNodes[0]);
                                    }
                                    catch (e){
                                        cogs.handle_error(e);
                                    }
                                }
                                if (typeof callback == 'function'){
                                    $(this).slideDown(anim_speed, callback);
                                }
                                else {
                                    $(this).slideDown(anim_speed);
                                }
                            });
                        }
                        else {
                            $(target).fadeTo(this.settings.anim_speed, 0.5, function(){
                                $(this).empty();
                                while (node.childNodes.length){
                                    $(target).append(node.childNodes[0]);
                                }
                                if (typeof callback == 'function'){
                                    $(this).fadeTo(anim_speed, 1, callback);
                                }
                                else {
                                    $(this).fadeTo(anim_speed, 1);
                                }
                            });
                        }
                    }
                    catch (e){
                        debug('error animating');
                        throw e;
                    }
                }
                else {
                    $(target).empty();
                    while (node.childNodes.length){
                        $(target).append(node.childNodes[0]);
                    }

                    if (typeof callback == 'function'){
                        try {
                            callback();
                        }
                        catch (e){
                            debug('Error calling callback in show node');
                            
                            cogs.debug(callback);
                            throw e;
                        }
                    }
                }
            }
            catch (e){
                debug('Error in show_node');
                throw e;
            }

        }

        this.render_section = function(name, one_time_cb){
            // Run in background
            var self = this;
            window.setTimeout(function(){
                if (typeof name == 'undefined'){
                    return;
                }
                var template_data = $(self.jqobj).data('template');
                if (!template_data){
                    /* template not loaded yet, set a flag to show once the template arrives */
                    self.show_on_load = true;
                    return;
                }
                var template_node = template_data.getElementsByTagName('template').item(0);
                if (template_node){
                    var has_ns = false;
                    var ns_name = '';
                    for (var a=0;a<template_node.attributes.length;a++){
                        if (template_node.attributes[a].value == 'http://cogs/xtl.dtd'){
                            has_ns = true;
                            var ns_arr = template_node.attributes[a].name.split(':', 2);
                            ns_name = ns_arr[1];
                        }
                    }
                    var section_tag_name = 'section';
                    var section_nodes = [];
                    if (has_ns && template_node.getElementsByTagNameNS){
                        section_nodes = template_node.getElementsByTagNameNS('http://cogs/xtl.dtd', section_tag_name);
                    }
                    else {
                        if (has_ns && ns_name){
                            section_tag_name = ns_name + ':' + section_tag_name;
                        }
                        section_nodes = template_node.getElementsByTagName(section_tag_name);
                    }
                }
                else {
                    cogs.debug('Could not load template node in render_section');
                }

                var section_node = null;
                for (var s=0;s<section_nodes.length;s++){
                    var section_name = $(section_nodes[s]).attr('name');
                    if (section_name == name){
                        section_node = section_nodes[s].cloneNode(true);
                        break;
                    }
                }


                if (section_node){
                    var output = self.parse_node(section_node);
                    var jqobj = self.jqobj;
                    //cogs.debug('Parsed section');
                    self.show_node(output, '#_template_section_' + name, function(){

                        $(jqobj).triggerHandler('renderedsection', {name:name});

                        if (one_time_cb){
                            one_time_cb();
                        }
                        
                        try {
                            $('input[type="date"]').datepicker({dateFormat:'yy-mm-dd'});
                        }
                        catch (e){
                            cogs.debug('datepicker not included');
                        }
                    });
                }
                else {
                    debug('Template ERROR : Could not find section ' + name);
                }
            }, 50);
        }

        this.parse_block = function(block_name, block_data){
            cogs.debug('global parse_block ' + block_name);
            var output = document.createElement('div');
            var bt = $(output).xtemplate({block: block_name, parent:this});
            bt.data(block_data);

            // copy the blocks through
            for (var b in this.blocks){
                //debug(this.blocks[b]);
                bt.blocks[b] = this.blocks[b];
            }

            //debug(bt.blocks);

            var template_doc = document.implementation.createDocument('', '', null);
            var template_node = template_doc.createElement('template');
            try {
                var imported_block = template_doc.importNode(bt.blocks[block_name], true);
            }
            catch (e) {
                // importNode throws an error on ie
                template_doc.importNode = _importNode;
                imported_block = template_doc.importNode(bt.blocks[block_name], true);
            }
            template_node.appendChild(imported_block);
            template_doc.appendChild(template_node);

            bt.template(template_doc);

            // render the template into the holder node
            bt.render();
            
            var jqobj = this.jqobj;
            $(jqobj).triggerHandler('blockrendered', {block:block_name});

            var frag = document.createDocumentFragment();
            while (output.firstChild){
                frag.appendChild(output.firstChild);
            }
            
            return frag;
        }

        this.sub_value_cb = function(matches){
            var value = this.var_value(matches.replace(/^##/, '').replace(/##$/, ''));
            if (value === null){
                value = '';
            }
            return value;
        }

        this.replace_inline_vars = function(xml_node){
            /* replace any inline variables marked with ## */
            try {
                var inline_regex = /##([^#]*)##/;
                var matches = [];
                var var_value = '';

                if (xml_node.attributes){
                    for (var a=0;a<xml_node.attributes.length;a++){
                        matches = xml_node.attributes[a].value.match(inline_regex);
                        while (matches){
                            var_value = this.var_value(matches[1]);
                            if (var_value === null){
                                var_value = '';
                            }
                            var parsed_value = xml_node.attributes[a].value.replace(matches[0], var_value);
                            xml_node.setAttribute(xml_node.attributes[a].name, parsed_value);

                            matches = xml_node.attributes[a].value.match(inline_regex);
                        }
                    }
                }

                if (xml_node.tagName){
                    if (xml_node.firstChild && xml_node.firstChild.nodeType == 3){ // if it is a text node, replace anything there
                        matches = xml_node.firstChild.nodeValue.match(inline_regex);
                        while (matches && matches.length == 2){
                            var_value = this.var_value(matches[1]) || '';
                            var replaced_content = xml_node.firstChild.nodeValue.replace(matches[0], var_value);
                            xml_node.firstChild.nodeValue = replaced_content;
                            matches = xml_node.firstChild.nodeValue.match(inline_regex);
                        }
                    }
                }
            }
            catch (e){
                cogs.debug('Error in replace_inline_vars');
                throw e;
            }

            return xml_node;
        }

        this.parse_conditional_str = function(cond_str){
            try {
                var code_str = cond_str;
                var re = /[`a-zA-Z_][a-zA-Z0-9\._\|':]*[a-zA-Z0-9`']/g;
                var matches = cond_str.match(re);
                var m = 0;
                var match_pos = 0;
                //cogs.debug(cond_str, 'Parsing str');
                //cogs.debug(matches, 'Matches from cond str');

                for (m=0;m<matches.length;m++){
                    match_pos = code_str.indexOf(matches[m]);
                    var code_val = this.var_value(matches[m]);
                    if (code_val === null){
                        // check if it is in a string, then return the string, otherwise it is an undefined variable
                        if (code_str.substr(match_pos-1, 1) == "'"){
                            code_val = matches[m];
                        }
                        else if (matches[m].substr(0, 1) == "`"){
                            code_val = eval(matches[m].substr(0, 1).substr(0, -1));
                        }
                        else {
                            code_val = null;
                        }
                    }
                    else if (typeof code_val == 'string') {
                        code_val = "'" + code_val + "'";
                    }
                    else if (typeof code_val == 'object' && code_val.toString) {
                        code_val = "'" + code_val.toString() + "'";
                    }
                    
                    code_str = code_str.replace(matches[m], code_val);
                }
            }
            catch (e){
                cogs.debug('Error in parse_conditional_str with str ' + cond_str);
                throw e;
            }
            
            return code_str;
        }

        this.parse_node = function(xml_node){
            var html_node = null;

            var child = null;
            var c = 0;
            var parsed_node = null;

            // strip comments
            if (xml_node.nodeType == 8){
                return document.createTextNode('');
            }

            var namespace_prefix = null;
            var tag_name = null;


            if (xml_node.tagName){
                var filter_str = '';
                var xml_doc = xml_node.ownerDocument;


                xml_node = this.replace_inline_vars(xml_node);

                //if (!namespace_prefix && xml_doc.lookupPrefix){
                //    namespace_prefix = xml_doc.lookupPrefix('http://cogs/xtl.dtd');
                //}
                //else {
                    namespace_prefix = xml_node.prefix;
                //}

                //cogs.debug(namespace_prefix);
                //cogs.debug(xml_node.tagName);

                tag_name = xml_node.tagName.toLowerCase();
                if (namespace_prefix){
                    tag_name = tag_name.replace(namespace_prefix + ':', '');
                }
                else if (!namespace_prefix && tag_name.indexOf(':') > -1){
                    /* IE does not set namespace when using importNode copy, this breaks parse_block */
                    namespace_prefix = tag_name.substr(0, tag_name.indexOf(':'));
                    tag_name = tag_name.substr(tag_name.indexOf(':')+1);
                }
                    //cogs.debug('namespace_prefix ' + xml_node.nodeName + ' = ' + namespace_prefix);
                    //cogs.debug('tag_name = ' + tag_name);

                // Check for external delegates
                if (this.command_delegates[tag_name]){
                    return this.command_delegates[tag_name].apply(this, [xml_node]);
                }

                switch (tag_name){
                    case 'var':
                        try {
                            xml_node = this.replace_inline_vars(xml_node);
                            var var_name = $(xml_node).attr('name');
                            filter_str = $(xml_node).attr('filter');

                            var var_value = this.var_value(var_name, filter_str);
                            if (var_value === null){
                                var_value = '';
                            }
                            if (typeof var_value == 'object'){
                                if (var_value.toString){
                                    var_value = var_value.toString();
                                }
                            }

                            // HACK : Filter for safe
                            if (typeof filter_str != 'undefined' && filter_str.indexOf('safe') > -1){
                                html_node = document.createElement('span');
                                html_node.innerHTML = var_value;
                            }
                            else {
                                html_node = document.createTextNode(var_value);
                            }

                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' line 1217');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        //html_node.innerHTML = var_value;
                        break;
                    case 'set':
                        try {
                            var set_name = $(xml_node).attr('name');
                            var set_value = $(xml_node).attr('value');

                            if (typeof set_value == 'string'){
                                this.data(set_name, set_value);
                                
                                var obj_value = this.var_value(set_value);
                                if (obj_value){
                                    this.data(set_name, obj_value);
                                }
                                else {
                                    this.data(set_name, set_value);
                                }
                            }
                            else {
                                // check if we have <cycle> children
                                var cycle_children = null;
                                if (namespace_prefix){
                                    if (xml_node.getElementsByTagNameNS){
                                        cycle_children = xml_node.getElementsByTagNameNS('http://cogs/xtl.dtd', 'cycle');
                                    }
                                    else {
                                        // IE
                                        cycle_children = xml_node.getElementsByTagName(namespace_prefix + ':cycle');
                                    }
                                }
                                else {
                                    cycle_children = xml_node.getElementsByTagName('cycle');
                                }
                                //cogs.debug(cycle_children);
                                if (cycle_children.length){
                                    var current_value = this.var_value(set_name);
                                    if (current_value == null){
                                        // For some reason cycle_children.item(0).nodeValue does not work
                                        this.data(set_name, $(cycle_children.item(0)).text());
                                    }
                                    else {
                                        for (var cc=0;cc<cycle_children.length;cc++){
                                            if (current_value == $(cycle_children.item(cc)).text()){
                                                // if we are on the last child then use the value from the first
                                                if (cc == (cycle_children.length-1)){
                                                    this.data(set_name, $(cycle_children.item(0)).text());
                                                }
                                                else {
                                                    this.data(set_name, $(cycle_children.item(cc+1)).text());
                                                }
                                            }
                                        }
                                    }
                                }
                                else {
                                    /* Has child nodes which must be evaluated to text */

                                    var value_text_node = document.createTextNode('');
                                    if (xml_node && xml_node.hasChildNodes()){
                                        for (c=0;c<xml_node.childNodes.length;c++){
                                            child = xml_node.childNodes[c]
                                            if (child.nodeType != 3){
                                                // not a txt node so send it for re-parsing
                                                child = this.parse_node(child);
                                            }

                                            if (child.nodeType == 3){
                                                // only supports text nodes
                                                value_text_node.appendData($.trim(child.nodeValue));
                                            }
                                        }

                                        this.data(set_name, value_text_node.nodeValue);
                                    }
                                }
                            }

                            html_node = document.createTextNode('');
                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' line 1302');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;
                    case 'unset':
                        try {
                            var set_name = $(xml_node).attr('name');
                            this.data(set_name, null);
                        }
                        catch (e){
                            debug('error unsetting variable');
                            debug(e);
                        }
                        break;
                    case 'filter':
                        try {
                            filter_str = $(xml_node).attr('name');
                            if (!filter_str){
                                return null;
                            }
                            html_node = document.createDocumentFragment();
                            if (xml_node && xml_node.hasChildNodes()){
                                for (c=0;c<xml_node.childNodes.length;c++){
                                    child = xml_node.childNodes[c]
                                    if (child.nodeType != 3){
                                        // not a txt node so send it for re-parsing
                                        child = this.parse_node(child);
                                    }

                                    if (child.nodeType == 3){
                                        // only supports text nodes
                                        var text = this.apply_filter(child.nodeValue, filter_str);
                                        html_node = document.createTextNode(text);
                                    }
                                }
                            }
                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' line 1342');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;
                    case 'loop':
                        try {
                            var arr = null;
                            var loop_start = null;
                            var loop_max = null;

                            var array_var_name = $(xml_node).attr('array');
                            var sub_var_name = $(xml_node).attr('as');
                            var key_var_name = $(xml_node).attr('key');
                            if (array_var_name){
                                arr = this.var_value(array_var_name);
                            }

                            var start_attr = $(xml_node).attr('start');
                            var end_attr = $(xml_node).attr('end');

                            if (arr && ($.isArray(arr) || arr.length)){
                                loop_start = 0;
                                loop_max = arr.length - 1;
                            }
                            else if (typeof start_attr != 'undefined' && typeof end_attr != 'undefined'){
                                loop_start = start_attr;
                                loop_max = end_attr;
                            }


                            if (typeof loop_max == 'string' && isNaN(parseInt(loop_max))){
                                loop_max = this.var_value(loop_max);
                            }
                            if (typeof loop_start == 'string' && isNaN(parseInt(loop_start))){
                                loop_start = this.var_value(loop_start);
                            }

                            html_node = document.createDocumentFragment();

                            // Else node must be child not grandchild (might be in an if node)

                            if (xml_node && xml_node.hasChildNodes()){
                                for (var e=0;e<xml_node.childNodes.length;e++){
                                    if (xml_node.childNodes[e].tagName == 'else' || xml_node.childNodes[e].tagName == namespace_prefix + ':else'){
                                        var else_node = xml_node.removeChild(xml_node.childNodes[e]);
                                    }
                                }
                            }

                            var empty = true;

                            if (loop_start !== null && loop_max !== null && (loop_start != loop_max+1)){

                                try {
                                    
                                    for (var a=loop_start;a<=loop_max;a++){
                                        if (sub_var_name){
                                            this.data(sub_var_name, arr[a]);
                                        }
                                        /* always set the loop variables */
                                        this.data('_loop',  {counter:a,
                                                             revcounter:loop_max-(a-loop_start),
                                                             first:(a==loop_start),
                                                             last:(a==loop_max)});

                                        if (xml_node && xml_node.hasChildNodes()){
                                            try {
                                                for (c=0;c<xml_node.childNodes.length;c++){
                                                    var parsed_node = this.parse_node(xml_node.childNodes[c].cloneNode(true));
                                                    if (parsed_node){
                                                        html_node.appendChild(parsed_node);
                                                    }
                                                }
                                            }
                                            catch (e){
                                                debug('Error inside loop with tag ' + xml_node.childNodes[c].tagName);
                                                debug(parsed_node, 'Parsed node');
                                                throw e;
                                            }
                                        }

                                        empty = false;
                                    }
                                }
                                catch (e){
                                    debug('Parse failed looping an array');
                                    debug(a, 'Loop counter');
                                    throw e;
                                }
                            }
                            else if ($.isPlainObject(arr)){
                                //alert('looping an object');
                                try {
                                    a=0;
                                    for (var k in arr){
                                        if (sub_var_name){
                                            this.data(sub_var_name, arr[k]);
                                        }
                                        if (key_var_name){
                                            this.data(key_var_name, k);
                                        }
                                        /* always set the loop variables */
                                        this.data('_loop',  {counter:a,
                                                             revcounter:loop_max-(a-loop_start),
                                                             first:(a==loop_start),
                                                             last:(a==loop_max),
                                                             key:k});

                                        if (xml_node && xml_node.hasChildNodes()){
                                            for (c=0;c<xml_node.childNodes.length;c++){
                                                html_node.appendChild(this.parse_node(xml_node.childNodes[c].cloneNode(true)));
                                            }
                                        }

                                        a++;

                                        empty = false;
                                    }
                                }
                                catch (e){
                                    debug('Error looping plain object');
                                    debug(k, 'current variable');
                                    throw e;
                                }
                            }

                            if (empty && else_node && else_node.hasChildNodes()) {
                                //alert('empty!');
                                for (var e=0;e<else_node.childNodes.length;e++){
                                    html_node.appendChild(this.parse_node(else_node.childNodes[e].cloneNode(true)));
                                }
                            }
                        }
                        catch (e){
                            debug('Parse loop failed with array ' + array_var_name);
                            cogs.trace();
                            debug(e);
                        }

                        break;

                    case 'if':
                        try {
                            xml_node = this.replace_inline_vars(xml_node);
                            html_node = document.createDocumentFragment();

                            var cond_str = $(xml_node).attr('cond');
                            var original_cond = cond_str;
                            
                            //cogs.debug(cond_str, 'Parsing in if cond');

                            cond_str = this.parse_conditional_str(cond_str);
                            //cogs.debug(cond_str, 'Parsing after parse_conditional_str in if cond');

                            try {
                                var cond_eval = eval(cond_str);
                                var tf = (cond_eval)?'true':'false';
                            }
                            catch (e){
                                debug('Conditional evaluation "'+original_cond+'" evaluated to "'+cond_str+'" failed with error "'+e.message+'"');
                                cond_eval = false;
                            }

                            /* only ever 1 else node in an if statement */
                            var else_node = null;
                            if (xml_node && xml_node.hasChildNodes()){
                                for (var e=0;e<xml_node.childNodes.length;e++){
                                    if (xml_node.childNodes[e].tagName == 'else' || xml_node.childNodes[e].tagName == namespace_prefix + ':else'){
                                        else_node = xml_node.childNodes[e];
                                        continue;
                                    }
                                }
                            }

                            if (cond_eval){
                                /* parse the rest of this clause as per normal, ignoring the else clause */
                                if (xml_node && xml_node.hasChildNodes()){
                                    for (c=0;c<xml_node.childNodes.length;c++){
                                        /* do not parse the else node */
                                        if (xml_node.childNodes[c].tagName != 'else' && xml_node.childNodes[c].tagName != namespace_prefix + ':else'){
                                            var parsed_node = this.parse_node(xml_node.childNodes[c]);
                                            if (parsed_node){
                                                html_node.appendChild(parsed_node);
                                            }
                                        }
                                    }
                                }
                            }
                            else {
                                /* condition failed, only insert the else clause */
                                if (else_node && else_node.hasChildNodes()){
                                    for (c=0;c<else_node.childNodes.length;c++){
                                        html_node.appendChild(this.parse_node(else_node.childNodes[c]));
                                    }
                                }
                            }
                        }
                        catch (e){
                            debug('Exception thrown in if ("'+cond_str+'")' + e.message);
                            debug(e);
                        }
                        break;

                    case 'dropdown':
                        try {
                            html_node = document.createElement('select');

                            var array_name = $(xml_node).attr('array');
                            var key = $(xml_node).attr('value');
                            var text = $(xml_node).attr('text');
                            var selected = $(xml_node).attr('selected');
                            var blank_value = $(xml_node).attr('blank');

                            var selected_value = '';
                            var opt = null;

                            if (typeof blank_value != 'undefined'){
                                var opt = document.createElement('option');
                                opt.value = '';
                                opt.text = blank_value;
                                html_node.add(opt, null);
                            }

                            if (selected){
                                selected_value = this.var_value(selected);
                                if (selected_value){
                                    selected_value = selected_value.toString();
                                }
                            }

                            if (array_name){
                                var array_value = this.var_value(array_name);

                                if ($.isArray(array_value)){

                                    for (var i=0;i<array_value.length;i++){
                                        var item = array_value[i];
                                        if ((typeof item[key] != 'undefined') && (typeof item[key] != 'null')){
                                            opt = document.createElement('option');
                                            opt.value = item[key];
                                            opt.text = item[text];
                                            if (opt.value == selected_value){
                                                opt.selected = true;
                                                //debug('SELECTED : ' + selected_value);
                                            }

                                            html_node.add(opt, null);
                                        }
                                        else if (typeof item == 'string'){
                                            opt = document.createElement('option');
                                            opt.value = item;
                                            opt.text = item;
                                            if (opt.value == selected_value){
                                                opt.selected = true;
                                            }
                                            html_node.add(opt, null);
                                        }
                                        else {
                                            debug('array attribute '+array_name+' of dropdown does not have an item with the key ' + key);
                                            debug(item);
                                            debug(key);
                                        }
                                    }
                                }
                                else {
                                    debug(array_value, 'array attribute '+array_name+' of dropdown is not an array');
                                }
                            }
                            else {
                                var child_options = $('option', xml_node);
                                if (child_options.length){
                                    //alert('child options');
                                    for (var c=0;c<child_options.length;c++){
                                        var opt = document.createElement('option');
                                        opt.value = $(child_options[c]).attr('value');
                                        opt.text = $(child_options[c]).text();
                                        if (opt.value == selected_value){
                                            opt.selected = true;
                                        }

                                        html_node.add(opt, null);
                                    }
                                }
                                else {
                                    debug('Dropdown not supplied with array or child options');
                                    throw new Error();
                                }
                            }

                            // pass through all other attributes
                            for (var a=0;a<xml_node.attributes.length;a++){
                                if ($.inArray(xml_node.attributes[a].name, ['array', 'value', 'text', 'selected', 'blank']) == -1){
                                    $(html_node).attr(xml_node.attributes[a].name, xml_node.attributes[a].value);
                                }
                            }
                            //$(html_node).attr('name', $(xml_node).attr('name'));
                            //$(html_node).attr('id', $(xml_node).attr('id'));
                            //$(html_node).attr('style', $(xml_node).attr('style'));
                            //$(html_node).attr('class', $(xml_node).attr('class'));

                            // hack for editable, should pass through all unknown attributes
                            $(html_node).attr('data-editgroup', $(xml_node).attr('data-editgroup'));
                            $(html_node).attr('data-edittype', $(xml_node).attr('data-edittype'));

                            //debug(html_node);

                        }
                        catch (e){
                            debug('Error in dropdown ' + e.message);
                            debug(e);
                        }
                        break;

                    case 'i18n':
                        try {
                            var content = '';
                            if (xml_node.firstChild){
                                var var_node = xml_node.getElementsByTagName('var')[0];
                                if (var_node){
                                    var parsed = this.parse_node(var_node);
                                    if (parsed){
                                        content = parsed.nodeValue;
                                    }
                                }
                                else {
                                    content = xml_node.firstChild.nodeValue;
                                }
                            }
                            var translated_content = content;

                            if (typeof _ != 'function'){
                                debug('jquery.gettext.js must be loaded for text translation');
                            }
                            else {
                                var attrs = xml_node.attributes;

                                var is_plural = false;
                                var plurals = [];
                                for (var a=0;a<attrs.length;a++){
                                    if (attrs[a].name.indexOf('plural-') == 0){
                                        var plural_number = parseInt(attrs[a].name.substring(7)) - 1;
                                        plurals[plural_number] = attrs[a].value;
                                        is_plural = true;
                                    }
                                }
                                //if (is_plural){
                                //    plurals[0] = content;
                                //}

                                var plural_count_attr = $(xml_node).attr('count');
                                var plural_count = (plural_count_attr)?this.var_value(plural_count_attr):0;
                                if (is_plural && plural_count){
                                    plurals.unshift(content);
                                    plurals.push(plural_count);
                                    // n_ takes parameters like this (singular, plural2, plural3, count)
                                    translated_content = n_.apply(null, plurals);
                                }
                                else {
                                    translated_content = _(content);
                                }



                                var sub_vars_text = $(xml_node).attr('sub');
                                var sub_vars_array = [];
                                if (sub_vars_text){
                                    var in_string = false;
                                    var current = '';
                                    // split the subvars, some filters can have spaces within their args
                                    for (var s=0;s<sub_vars_text.length;s++){
                                        if (sub_vars_text[s] == "'"){
                                            in_string = !in_string;
                                            current += sub_vars_text[s];
                                        }
                                        else if (sub_vars_text[s] == " "){
                                            if (in_string){
                                                current += sub_vars_text[s];
                                            }
                                            else {
                                                sub_vars_array.push(current);
                                                current = '';
                                            }
                                        }
                                        else {
                                            current += sub_vars_text[s];
                                        }
                                    }
                                    if (current){
                                        sub_vars_array.push(current);
                                    }
                                    //var sub_vars_array = sub_vars_text.split(' ');
                                    var subs = [];
                                    for (s=0;s<sub_vars_array.length;s++){
                                        var sub = this.var_value(sub_vars_array[s]);
                                        if (!sub){
                                            sub = '';
                                        }
                                        subs.push(sub);
                                    }

                                    if (subs.length){
                                        if (typeof $.vsprintf == 'function'){
                                            translated_content = $.vsprintf(translated_content, subs);
                                        }
                                        else {
                                            debug('jquery.sprintf.js must be loaded for text substitution');
                                        }
                                    }
                                }
                            }

                            var t_name = $(xml_node).attr('name');
                            translated_content = cogs.i18n.translate(t_name, content, subs);

                            html_node = document.createTextNode(translated_content);
                        }
                        catch (e){
                            cogs.debug('error parsing i18n '+ e.message);
                            cogs.error_handler(e);
                        }
                        break;

                    case 'section':
                        try {
                            html_node = document.createElement('div');
                            var section_name = $(xml_node).attr('name');

                            $(html_node).attr('style', 'display:inline');
                            $(html_node).attr('id', '_template_section_' + section_name);

                            if (xml_node.hasChildNodes()){
                                for (c=0;c<xml_node.childNodes.length;c++){
                                    parsed_node = this.parse_node(xml_node.childNodes[c].cloneNode(true));
                                    if (parsed_node){
                                        $(html_node).append($(parsed_node));
                                    }
                                }
                            }
                            if ($.inArray(section_name, this.sections_rendered) == -1){
                                this.sections_rendered.push(section_name);
                            }

                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' line 1816');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;

                    case 'editable':
                        try {
                            html_node = xml_doc.createDocumentFragment();
                            var original_value = null;
                            var var_name = $(xml_node).attr('var');
                            var group_name = $(xml_node).attr('group');
                            var current_style = $(xml_node).attr('style');
                            if (typeof current_style == 'undefined'){
                                current_style = '';
                            }
                            var current_name = $(xml_node).attr('name');
                            var current_id = $(xml_node).attr('id');
                            var current_class = $(xml_node).attr('class');
                            var editable_type = $(xml_node).attr('type');

                            var edit_filter = $(xml_node).attr('edit-filter') || '';
                            if (edit_filter){
                                edit_filter = '|' + edit_filter;
                            }

                            switch (editable_type){
                                case 'dropdown':
                                    var edit_node = xml_doc.createElement('dropdown');
                                    $(edit_node).attr('data-editgroup', group_name);
                                    $(edit_node).attr('data-edittype', 'edit');
                                    $(edit_node).attr('style', 'display:none;'+current_style);
                                    $(edit_node).attr('array', $(xml_node).attr('array'));
                                    $(edit_node).attr('value', $(xml_node).attr('selected'));
                                    $(edit_node).attr('text', $(xml_node).attr('text'));
                                    $(edit_node).attr('selected', $(xml_node).attr('selected'));

                                    $('option', xml_node).each(function(){
                                        $(edit_node).append(this);
                                    });

                                    if (!var_name){
                                        var_name = $(xml_node).attr('selected');
                                    }
                                    original_value = $(xml_node).attr('selected');
                                    //alert($(xml_node).attr('selected'));
                                    break;
                                case 'richtext':
                                case 'text':
                                    var edit_node = xml_doc.createElement('textarea');
                                    $(edit_node).attr('data-editgroup', group_name);
                                    $(edit_node).attr('data-edittype', 'edit');
                                    $(edit_node).attr('data-texttype', editable_type);
                                    $(edit_node).attr('style', 'display:none;'+current_style);
                                    var edit_var_node = xml_doc.createElement('var');
                                    $(edit_var_node).attr('name', var_name);
                                    
                                    var edit_display_var_node = edit_var_node.cloneNode(true);
                                    // TODO : Adding edit-filter of 'trim' adds extra quotes to text
                                    $(edit_display_var_node).attr('filter', edit_filter.substr(1));
                                    $(edit_node).append(edit_display_var_node);
                                    //cogs.debug(edit_display_var_node);
                                    //debug(edit_node);
                                    //return document.createTextNode('EDITABLE');
                                    break;
                                case 'list':
                                    var edit_node = xml_doc.createElement('textarea');
                                    $(edit_node).attr('data-editgroup', group_name);
                                    $(edit_node).attr('data-edittype', 'edit');
                                    $(edit_node).attr('style', 'display:none;'+current_style);
                                    var edit_var_node = xml_doc.createElement('loop');
                                    $(edit_var_node).attr('array', var_name);
                                    $(edit_var_node).attr('as', 'var_item');
                                    $(edit_node).text('##' + var_name + edit_filter + '|join:\'\n\'' +'##');
                                    break;
                                case 'bool':
                                    var edit_node = xml_doc.createElement('span');
                                    $(edit_node).attr('data-datatype', 'boolean');
                                    var label_node_y = xml_doc.createElement('label');
                                    var label_node_n = xml_doc.createElement('label');
                                    var edit_node_y = xml_doc.createElement('input');
                                    var edit_node_n = xml_doc.createElement('input');
                                    var tristate = $(xml_node).attr('tristate');
                                    $(edit_node_y).attr('type', 'radio');
                                    $(edit_node_n).attr('type', 'radio');
                                    $(edit_node_y).attr('value', '1');
                                    $(edit_node_n).attr('value', '0');
                                    $(edit_node_y).attr('name', current_name);
                                    $(edit_node_n).attr('name', current_name);
                                    $(edit_node_y).attr('data-selectedif', var_name);
                                    if (tristate){
                                        $(edit_node_n).attr('data-selectedif', '!' + var_name + '|isnull && !' + var_name);
                                    }
                                    else {
                                        $(edit_node_n).attr('data-selectedif', '!' + var_name);
                                    }
                                    $(edit_node_y).attr('onclick', '$(this).attr("data-modified", 1);');
                                    $(edit_node_n).attr('onclick', '$(this).attr("data-modified", 1);');

                                    label_node_y.appendChild(edit_node_y);
                                    label_node_n.appendChild(edit_node_n);
                                    label_node_y.appendChild(xml_doc.createTextNode(cogs.i18n.translate('cogs/template#yes', 'Yes')));
                                    label_node_n.appendChild(xml_doc.createTextNode(cogs.i18n.translate('cogs/template#no', 'No')));

                                    edit_node.appendChild(label_node_y);
                                    edit_node.appendChild(label_node_n);
                                    
                                    if (tristate){
                                        var label_node_m = xml_doc.createElement('label');
                                        var edit_node_m = xml_doc.createElement('input');
                                        $(edit_node_m).attr('type', 'radio');
                                        $(edit_node_m).attr('value', '');
                                        $(edit_node_m).attr('name', current_name);
                                        $(edit_node_m).attr('onclick', '$(this).attr("data-modified", 1);');
                                        $(edit_node_m).attr('data-selectedif', var_name + '|isnull');
                                        label_node_m.appendChild(edit_node_m);
                                        label_node_m.appendChild(xml_doc.createTextNode(cogs.i18n.translate('cogs/template#notset', 'Not Set')));
                                        edit_node.appendChild(label_node_m);
                                    }

                                    $(edit_node).attr('data-editgroup', group_name);
                                    $(edit_node).attr('data-edittype', 'edit');
                                    $(edit_node).attr('style', 'display:none;'+current_style);
                                    
                                    //cogs.debug(edit_node);
                                    //var edit_var_node = document.createElement('var');
                                    //$(edit_var_node).attr('name', var_name);
                                    //$(edit_node).text('##' + var_name + '##');
                                    break;
                                case 'enum':
                                    var edit_node = xml_doc.createElement('select');
                                    var var_value = this.var_value(var_name);
                                    $(edit_node).attr('data-editgroup', group_name);
                                    $(edit_node).attr('data-edittype', 'edit');
                                    $(edit_node).attr('style', 'display:none;'+current_style);
                                    //debug(xml_node.childNodes);
                                    for (c=0;c<xml_node.childNodes.length;c++){
                                        child = xml_node.childNodes[c];
                                        if (child.tagName == 'choice'){
                                            //debug(child.nodeValue);
                                            var opt = xml_doc.createElement('option');
                                            opt.appendChild(xml_doc.createTextNode(child.nodeValue));
                                            $(opt).attr('value', $(child).attr('value'));
                                            opt.value = $(child).attr('value');
                                            if (opt.value == var_value){
                                                //debug('selected is '+var_value);
                                                $(opt).attr('data-selectedif', 'true');
                                                //$(opt).attr('selected', 'selected');
                                                //opt.defaultSelected = true;
                                                opt.selected = true;
                                            }
                                            edit_node.appendChild(opt);
                                        }
                                        //edit_node.appendChild(child);
                                    }
                                    edit_node.selectedIndex = 3;
                                    //debug(edit_node);
                                    break;
                                case 'string':
                                case 'int':
                                case 'float':
                                case 'date':
                                case 'datetime':
                                case 'time':
                                default:
                                    try {
                                        var edit_node = xml_doc.createElement('input');
                                        try {
                                            switch (editable_type){
                                                case 'date':
                                                    $(edit_node).attr('type', 'date');
                                                    break;
                                                case 'datetime':
                                                    edit_node = xml_doc.createElement('span');
                                                    $(edit_node).attr('data-datatype', 'datetime');
                                                    var edit_node_d = xml_doc.createElement('input');
                                                    $(edit_node_d).attr('type', 'date');
                                                    $(edit_node_d).attr('name', current_name + '_date');
                                                    $(edit_node_d).attr('value', '##'+var_name+'|dateformat:\'Y-m-d\'##');
                                                    
                                                    var edit_node_t = xml_doc.createElement('input');
                                                    $(edit_node_t).attr('type', 'time');
                                                    $(edit_node_t).attr('style', 'width:60px;'+current_style);
                                                    $(edit_node_t).attr('name', current_name + '_time');
                                                    $(edit_node_t).attr('value', '##'+var_name+'|dateformat:\'H:i\'##');
                                                    
                                                    //var script_node = xml_doc.createElement('script', '$(document).ready(function(){$("input[name=\"'+current_name+'_date\"]"){}}).datepicker();');
                                                    
                                                    $(edit_node).append(edit_node_d);
                                                    $(edit_node).append(edit_node_t);
                                                    //$(edit_node).append(script_node);
                                                    
                                                    $(edit_node).attr('data-editgroup', group_name);
                                                    $(edit_node).attr('data-edittype', 'edit');
                                                    break;
                                                case 'time':
                                                    $(edit_node).attr('type', 'time');
                                                    break;
                                                case 'int':
                                                    $(edit_node).attr('type', 'number');
                                                    $(edit_node).attr('step', '1');
                                                    var min = $(xml_node).attr('min');
                                                    if (min != null){
                                                        $(edit_node).attr('min', min);
                                                    }
                                                    var max = $(xml_node).attr('max');
                                                    if (max != null){
                                                        $(edit_node).attr('max', max);
                                                    }
                                                    if (current_style != ''){
                                                        current_style += ';';
                                                    }
                                                    current_style += 'width:50px;';
                                                    break;
                                                case 'float':
                                                    $(edit_node).attr('type', 'number');
                                                    $(edit_node).attr('step', '0.01');
                                                    var min = $(xml_node).attr('min');
                                                    if (min != null){
                                                        $(edit_node).attr('min', min);
                                                    }
                                                    var max = $(xml_node).attr('max');
                                                    if (max != null){
                                                        $(edit_node).attr('max', max);
                                                    }
                                                    break;
                                                default:
                                                    $(edit_node).attr('type', 'text');
                                                    break;
                                            }
                                        }
                                        catch (e){
                                            $(edit_node).attr('type', 'text');
                                        }
                                        $(edit_node).attr('data-editgroup', group_name);
                                        $(edit_node).attr('data-edittype', 'edit');
                                        $(edit_node).attr('style', 'display:none;'+current_style);
                                        $(edit_node).attr('value', '##'+var_name+edit_filter+'##');
                                        //$(edit_node).attr('data-value', '##'+var_name+'##');
                                        if (editable_type == 'int'){
                                            $(edit_node).attr('size', 5);
                                        }
                                        else if (editable_type == 'float'){
                                            $(edit_node).attr('size', 9);
                                        }
                                        else {
                                            var forced_size = $(xml_node).attr('size');
                                            if (forced_size){
                                                $(edit_node).attr('size', forced_size);
                                            }
                                        }
                                    }
                                    catch (e){
                                        cogs.debug('Error creating string/int/float editable');
                                        throw e;
                                    }
                                    break;
                            }

                            // set the data-originalvalue so that we can tell if it was modified
                            if (original_value !== null){
                                $(edit_node).attr('data-originalvalue', '##'+original_value+edit_filter+'##');
                            }
                            else {
                                $(edit_node).attr('data-originalvalue', '##'+var_name+edit_filter+'##');
                            }
                            
                            //set a callback to set data-modified
                            try {
                                var self = this;
                                $(edit_node).live('keyup', function(){self.editable.update_modified(this)});
                                $(edit_node).live('focusout', function(){self.editable.update_modified(this)});
                            }
                            catch (e){
                                cogs.debug('Error appending keyup or focusout callbacks');
                            }

                            // pass all unknown attributes through to the editable node
                            try {
                                var known_attrs = ['var', 'group', 'style', 'name', 'id', 'class', 'type', 'size'];
                                for (var a=0;a<xml_node.attributes.length;a++){
                                    if ($.inArray(xml_node.attributes[a].name, known_attrs) == -1){
                                        //debug('pass through ' + xml_node.attributes[a].name);
                                        $(edit_node).attr(xml_node.attributes[a].name, xml_node.attributes[a].value);
                                    }
                                }
                            }
                            catch (e){
                                cogs.debug('Error passing through known attributes in editable');
                                throw e;
                            }

                            try {
                                var display_node = xml_doc.createElement('span');
                                $(display_node).attr('data-editgroup', group_name);
                                $(display_node).attr('data-edittype', 'display');
                                var var_node = xml_doc.createElement('var');
                                $(var_node).attr('name', var_name);
                                var display_filter = $(xml_node).attr('display-filter');
                                $(var_node).attr('filter', display_filter);
                                display_node.appendChild(var_node);
                                html_node.appendChild(display_node);

                                if (current_name){
                                    $(edit_node).attr('name', current_name);
                                }
                                if (current_id){
                                    $(edit_node).attr('id', current_id);
                                }
                                if (current_class){
                                    $(edit_node).attr('class', current_class);
                                }

                                //debug(edit_node);
                                html_node.appendChild(edit_node);

                                //cogs.debug('HERE');

                            }
                            catch (e){
                                cogs.debug('Error creating display node in editable');
                                throw e;
                            }

                            try {
                                var final_node = this.parse_node(html_node);
                            }
                            catch (e){
                                cogs.debug('Error parsing display node in editable');
                                throw e;
                            }

                            return final_node;

                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' (in editable)');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;

                    case 'editbuttons':
                        try {
                            var buttons = {};
                            var buttons_used = ['add', 'cancel', 'edit', 'save'];

                            var group_name = $(xml_node).attr('group');
                            var buttons_override_str = $(xml_node).attr('buttons');
                            if (buttons_override_str){
                                buttons_used = buttons_override_str.split(' ');
                            }

                            html_node = document.createElement('span');
                            buttons.add = document.createElement('button');
                            buttons.cancel = document.createElement('button');
                            buttons.done = document.createElement('button');
                            buttons.edit = document.createElement('button');
                            buttons.save = document.createElement('button');

                            buttons.add.innerHTML = cogs.i18n.translate('cogs/template#editableadd', 'Add');
                            buttons.cancel.innerHTML = cogs.i18n.translate('cogs/template#editablecancel', 'Cancel');
                            buttons.done.innerHTML = cogs.i18n.translate('cogs/template#editabledone', 'Done');
                            buttons.edit.innerHTML = cogs.i18n.translate('cogs/template#editableedit', 'Edit');
                            buttons.save.innerHTML = cogs.i18n.translate('cogs/template#editablesave', 'Save');

                            for (var btn_name in buttons){
                                if ($.inArray(btn_name, buttons_used) > -1){
                                    $(buttons[btn_name]).attr('class', btn_name + '_' + group_name + '_button');
                                    $(buttons[btn_name]).attr('data-editgroup', group_name);
                                    $(buttons[btn_name]).attr('data-edittype', btn_name + 'button');

                                    /*var i18n_node = document.createElement('i18n');
                                    i18n_node.appendChild(document.createTextNode(buttons[btn_name].innerHTML));
                                    $(buttons[btn_name]).empty();
                                    // parse the node so that the translation is applied
                                    buttons[btn_name].appendChild(this.parse_node(i18n_node));*/

                                    html_node.appendChild(buttons[btn_name]);
                                }
                            }

                            // add a linebreak
                            var btn_br = document.createElement('br');
                            $(btn_br).attr('style', 'clear:left');
                            html_node.appendChild(btn_br);
                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' (in editbuttons)');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;

                    case 'block':
                        try {
                            var block_name = $(xml_node).attr('name');
                            if (!block_name.match(/[a-zA-Z0-9_]*/)){
                                cogs.debug('Block names must be a-z 0-9 or _ - Ignoring ' + block_name);
                            }
                            else {
                                var varmap_name = 'varmap';
                                if (xml_node.prefix){
                                    varmap_name = xml_node.prefix + ':' + varmap_name;
                                }
                                //cogs.debug(xml_node.prefix + ' in varmap ' + varmap_name);
                                var varmaps = [];
                                // check to see if we are outputting or saving
                                var saving = false;
                                if (xml_node.childNodes.length){
                                    saving = true;
                                    for (c=0;c<xml_node.childNodes.length;c++){
                                        if (xml_node.childNodes[c].nodeName == varmap_name){
                                            //debug('Not saving because we have varmaps');
                                            varmaps.push({
                                                from: $(xml_node.childNodes[c]).attr('from'),
                                                to: $(xml_node.childNodes[c]).attr('to')
                                            });
                                            saving = false;
                                        }
                                    }
                                }

                                if (saving){
                                    // setting the block contents, or outputting with variable mappings
                                    if (typeof this.blocks[block_name] != 'undefined'){
                                        cogs.debug('Warning overwriting block ' + block_name);
                                    }
                                    //cogs.debug('saving block '+block_name);
                                    var template_document = xml_node.ownerDocument.createDocumentFragment();
                                    //var template_root = template_document.createElement('template');

                                    for (c=0;c<xml_node.childNodes.length;c++){
                                        var child_node = xml_node.childNodes[c].cloneNode(true);
                                        template_document.appendChild(child_node);
                                    }
                                    //template_document.appendChild(template_root);
                                    this.blocks[block_name] = template_document;
                                    //debug(xml_node);
                                    html_node = document.createTextNode(' ');
                                }
                                else {
                                    try {
                                        // outputting the block (create a new template and render it)
                                        //cogs.debug('outputting block '+block_name);
                                        html_node = document.createElement('div');
                                        var bt = $(html_node).xtemplate({block:block_name, parent:this});
                                        var bd = this.data();
                                        bt.data(bd);
                                        if (varmaps.length){
                                            for (var v=0;v<varmaps.length;v++){
                                                var varmap = varmaps[v];
                                                var from = varmap['from'];
                                                var to = varmap['to'];
                                                //debug(from + ' => ' + to);
                                                var from_val = this.var_value(from);
                                                //debug(from_val);
                                                bt.data(to, from_val);
                                            }
                                        }

                                        // copy the blocks through
                                        bt.blocks = this.blocks;

                                        if (typeof this.blocks[block_name] != 'undefined'){
                                            var template_doc = this.blocks[block_name].cloneNode(true);
                                            //alert(template_doc.nodeName);
                                            bt.template(template_doc);

                                            // render the template into the holder node
                                            bt.render();
                                            
                                            var frag_node = document.createDocumentFragment();
                                            while (html_node.firstChild){
                                                frag_node.appendChild(html_node.firstChild);
                                            }
                                            html_node = frag_node;

                                            var jqobj = this.jqobj;
                                            $(jqobj).triggerHandler('parsedblock', {block:block_name});

                                            this.blocks_rendered.push(block_name);
                                        }
                                        else {
                                            cogs.debug('Could not find block ' + block_name);
                                        }
                                    }
                                    catch (e){
                                        debug('error in outputting block');
                                        debug(e);
                                        debug(e.stack);
                                        cogs.trace();
                                        throw e;
                                    }
                                }
                            }
                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' (in block)');
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;

                    default:
                        try {
                            html_node = document.createElement(xml_node.tagName);
                            for (var n=0;n<xml_node.attributes.length;n++){
                                try {
                                    //cogs.debug('setting attr ' + xml_node.attributes[n].name + ' on tag ' + xml_node.tagName);
                                    $(html_node).attr(xml_node.attributes[n].name, xml_node.attributes[n].value);
                                }
                                catch (e){
                                    // IE throws an exception when setting type to anything it does not understand
                                    if (xml_node.attributes[n].name == 'type' && xml_node.tagName == 'input'){
                                        $(html_node).attr(xml_node.attributes[n].name, 'text');
                                    }
                                    else {
                                        cogs.debug('Error setting attribute ' + n + ' ' + xml_node.attributes[n].name + '=' + xml_node.attributes[n].value);
                                    }
                                }
                            }

                            // Handle data-selectedif on input elements
                            //cogs.debug(html_node);
                            if (html_node.tagName.toLowerCase() == 'input' || html_node.tagName.toLowerCase() == 'option'){
                                var selectedif = $(html_node).attr('data-selectedif');
                                //cogs.debug(selectedif);
                                if (selectedif){
                                    var cond_res = this.parse_conditional_str(selectedif);
                                    //cogs.debug(cond_res);
                                    var res = false;
                                    try {
                                        res = eval(cond_res);
                                    }
                                    catch (e){
                                        cogs.debug('error evaluating "'+cond_res+'"', '', 'warn');
                                        res = false;
                                    }
                                    if (res){
                                        //cogs.debug(cond_res + ' evaluated to true');
                                        var input_type = $(html_node).attr('type');
                                        switch (input_type){
                                            case 'checkbox':
                                            case 'radio':
                                                $(html_node).attr('checked', 'checked');
                                                break;
                                            default:
                                                if (html_node.tagName.toLowerCase() == 'option'){
                                                    $(html_node).attr('selected', 'selected');
                                                }
                                                else {
                                                    cogs.debug('Do not understand data-selectedif on a input with type ' + input_type);
                                                }
                                                break;
                                        }
                                        //cogs.debug(html_node);
                                    }
                                    else {
                                        //cogs.debug(cond_res + ' evaluated to false');
                                    }
                                }
                            }

                                                
                            if ((typeof xml_node != 'undefined') && xml_node.childNodes && xml_node.childNodes.length){
                                for (c=0;c<xml_node.childNodes.length;c++){
                                    parsed_node = this.parse_node(xml_node.childNodes[c].cloneNode(true));

                                    /* IE8 does not like appending text nodes to style elements */
                                    if (html_node.nodeName.toLowerCase() == 'style'){
                                            
                                        if (html_node.styleSheet && typeof html_node.styleSheet == 'object'){
                                            try {
                                                html_node.styleSheet.cssText += parsed_node.nodeValue;
                                            }
                                            catch (e){
                                                cogs.debug('has styleSheet but error');
                                                cogs.debug(e);
                                            }
                                        }
                                        else {
                                            $(html_node).append(parsed_node);
                                        }
                                    }
                                    else {
                                        if (typeof parsed_node != 'undefined'){
                                            try {
                                                $(html_node).append(parsed_node);
                                            }
                                            catch (e){
                                                try {
                                                    /* IE8 does not like appending script nodes with jquery, use standard DOM method */
                                                    if (html_node.node_name == 'SCRIPT'){
                                                        html_node.appendChild(parsed_node);
                                                    }
                                                }
                                                catch (e){
                                                    cogs.alert('error appending tag ' + html_node.nodeName + ' - ' + e.message);
                                                }
                                            //
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        catch (e){
                            debug('error in parsing tag ' + xml_node.tagName + ' line 2372');
                            debug(xml_node);
                            debug(e);
                            cogs.trace();
                            throw e;
                        }
                        break;
                }


            }
            else if (xml_node.nodeType == 3 || xml_node.nodeType == 4) {
                /* text nodes */
                html_node = document.createTextNode(xml_node.nodeValue);
            }
            else if (xml_node.nodeType == 11) {
                /* document fragment nodes */
                html_node = document.createDocumentFragment();
                for (var c=0;c<xml_node.childNodes.length;c++){
                    html_node.appendChild(this.parse_node(xml_node.childNodes[c]));
                }
            }
            else {
                debug(xml_node);
                debug('no tag or type ' + xml_node.nodeType);
            }

            return html_node;
        }

        this.var_value = function(var_name, filter){
            //debug('GETTING VAR_VALUE FOR ' + var_name);
            try {
                if (!var_name){
                    return null;
                }
                if (!filter){
                    try {
                        var var_filter_arr = var_name.split('|');
                        if (var_filter_arr.length > 1){
                            var_name = var_filter_arr.shift();
                            filter = var_filter_arr.shift();
                        }
                    }
                    catch (e){
                        cogs.debug('Error parsing filter');
                        cogs.debug(var_name);
                        throw e;
                    }
                }
                if (var_name.substr(0, 1) == '`'){
                    var_name = var_name.substr(1);
                    var_name = var_name.substr(0, var_name.length-1);

                    var var_value = null;
                    try {
                        var_value = eval(var_name);
                    }
                    catch (e){
                        cogs.debug('Error parsing global variable ' + var_name + ' ' + e.message);
                    }
                    current = var_value;
                }
                else {
                    try {
                        //debug('SPLITTING VAR_NAME ' + var_name);
                        var var_arr = var_name.split('.');
                        //debug('var_arr');
                        //debug(var_arr);
                        //debug(var_arr.length);
                        var current = $(this.jqobj).data('data');
                        //debug('CURRENT');
                        //debug(current);
                        while (typeof current != 'undefined' && current && var_arr.length){
                            var var_part = var_arr.shift();
                            //debug('VAR_PART = ' + var_part);
                            current = current[var_part];
                        }
                        //debug('CURRENT NOW');
                        //debug(current);
                    }
                    catch (e){
                        cogs.debug('ERROR Parsing array in var_value');
                        cogs.debug(var_arr);
                    }
                }

                if (filter){
                    //debug('APPLYING FILTER ' + filter);
                    current = this.apply_filter(current, filter);
                }

                if (typeof current == 'undefined'){
                    //debug('current is undefined');
                    current = null;
                }

                //debug('RETURNING VAR_VALUE ' + current);
                return current;
            }
            catch (e){
                cogs.debug('error in var_value ' + e.message);
                cogs.debug(e);
                throw e;
            }
        }

        this.exec_filter = function(str, filter, args){
            var str_is_undefined = false;
            if (typeof str == 'undefined'){
                str = '';
                str_is_undefined = true;
            }
            var filtered = str;

            switch ($.trim(filter)){
                case 'type':
                    if (str_is_undefined){
                        filtered = 'undefined';
                    }
                    filtered = typeof str;
                    break;
                case 'uppercase':
                    if (str && typeof str == 'string'){
                        filtered = str.toUpperCase();
                    }
                    break;
                case 'lowercase':
                    if (str && typeof str == 'string'){
                        filtered = str.toLowerCase();
                    }
                    break;
                case 'sentencecase':
                    if (str && typeof str == 'string'){
                        filtered = str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
                    }
                    break;
                case 'titlecase':
                    if (str && typeof str == 'string'){
                        filtered = this._titlecase(str);
                    }
                    break;
                case 'namecase':
                    if (str && typeof str == 'string'){
                        if (str == str.toLowerCase() || str == str.toUpperCase()){
                            filtered = properizeName(str);
                        }
                    }
                    break;
                case 'ifempty':
                    if (!str){
                        filtered = args[0];
                    }
                    break;
                case 'isnull':
                    filtered = (str === null || str === undefined || str === '');
                    break;
                case 'truncate':
                    if (str && typeof str == 'string'){
                        filtered = str.substr(0, parseInt(args[0]));
                    }
                    if (args[1] && str.length > parseInt(args[0])){
                        filtered += args[1];
                    }
                    break;
                case 'truncatewords':
                    if (str && typeof str == 'string'){
                        filtered = str.substr(0, parseInt(args[0]));
                        if (str.substr(parseInt(args[0]), 1) != ' '){
                            var filtered_arr = filtered.split(' ');
                            filtered_arr.pop();
                            filtered = filtered_arr.join(' ');
                        }
                    }
                    if (args[1] && str.length > parseInt(args[0])){
                        filtered += args[1];
                    }
                    break;
                case 'truncatehover':
                    if (str && typeof str == 'string'){
                        filtered = str.substr(0, parseInt(args[0]));
                        filtered = '<span title="'+str+'">'+filtered+'</span>';
                    }
                    break;
                case 'urlize':
                    if (str && typeof str == 'string'){
                        filtered = '<a href="'+str+'">' + str + '</a>';
                    }
                    break;
                case 'length':
                    if (!str || typeof str == 'undefined'){
                        str = '';
                    }

                    if (str && typeof str == 'string' || $.isArray(str) || (typeof str.length == 'number')){
                        filtered = str.length;
                    }
                    break;
                case 'join':
                    filtered = '';
                    if (str && $.isArray(str) && args[0]){
                        filtered = str.join(args[0]);
                    }
                    break;
                case 'isarray':
                    filtered = ($.isArray(str))?true:false;
                    break;
                case 'contains':
                    filtered = false;
                    //cogs.debug(args, 'contains args');
                    //cogs.debug(str, 'contains value');
                    if (args[0]){
                        //var search = this.var_value(args[0]);
                        var search = args[0];
                        filtered = ($.inArray(search, str) > -1)?true:false;
                    }
                    break;
                case 'nl2br':
                    filtered = str;
                    if (filtered && (typeof filtered == 'string')){
                        while (filtered.match('\n')){
                            filtered = filtered.replace('\n', '<br>');
                        }
                    }
                    break;
                case 'nl2p':
                    filtered = str;
                    if (filtered && (typeof filtered == 'string')){
                        while (filtered.match('\n')){
                            filtered = filtered.replace('\n', '</p><p>');
                        }
                    }
                    filtered = '<p>' + filtered + '</p>'
                    break;
                case 'yesno':
                    // if there are 3 parameters and it is null then return it, otherwise normal bool rules
                    var result = 'isundefined';
                    
                    if (typeof str == 'boolean'){
                        result = (str)?'yes':'no';
                    }
                    else if (typeof str == 'string'){
                        if (str == ''){
                            result = 'unknown';
                        }
                        else {
                            result = (str != '0')?'yes':'no';
                        }
                    }
                    else if (str == null){
                        result = 'unknown';
                    }
                    else {
                        result = (str)?'yes':'no';
                    }
                    
                    var result_strs = {};
                    result_strs.yes = (typeof args[0] != 'undefined')?args[0]:cogs.i18n.translate('cogs/template#yes', 'Yes');
                    result_strs.no = (typeof args[1] != 'undefined')?args[1]:cogs.i18n.translate('cogs/template#no', 'No');
                    result_strs.unknown = (typeof args[2] != 'undefined')?args[2]:cogs.i18n.translate('cogs/template#notset', 'Not Set');
                    result_strs.isundefined = 'Unknown';
                    
                    filtered = result_strs[result];
                    break;
                case 'replace':
                    filtered = str;
                    if (args.length != 2){
                        cogs.debug('Replace filter must have 2 arguments, I got ' + args.length);
                        cogs.debug(args);
                    }
                    if (filtered && typeof filtered == 'string'){
                        var search = args[0];
                        var replace = args[1];
                        var replace_loops = 0;
                        while (filtered.indexOf(search) > -1){
                            filtered = filtered.replace(search, replace);
                            replace_loops++;
                            if (replace_loops>200){
                                cogs.debug('Way too many replacements, breaking');
                                break;
                            }
                        }
                    }
                    break;
                case 'dump':
                    filtered = dump_var(str);
                    break;
                case 'safe':
                    debug('running ' + str + ' through safe');
                    filtered = str;
                    break;
                case 'abs':
                    filtered = Math.abs(str);
                    break;
                case 'multiply':
                    var multiplier = parseFloat(args[0]);
                    if (isNaN(multiplier)){
                        multiplier = parseFloat(this.var_value(args[0]));
                    }
                    if (!isNaN(multiplier)){
                        filtered = parseFloat(str) * multiplier;
                    }
                    break;
                case 'plus':
                    var plus = parseFloat(args[0]);
                    if (isNaN(plus)){
                        plus = parseFloat(this.var_value(args[0]));
                    }
                    if (!isNaN(plus)){
                        filtered = parseFloat(str) + plus;
                    }
                    break;
                case 'minus':
                    var minus = parseFloat(args[0]);
                    if (isNaN(minus)){
                        minus = parseFloat(this.var_value(args[0]));
                    }
                    if (!isNaN(minus)){
                        filtered = parseFloat(str) - minus;
                    }
                    break;
                case 'intformat':
                    var int_val = parseInt(str);
                    if (isNaN(int_val)){
                        int_val = 0;
                    }
                    filtered = int_val;
                    break;
                case 'floatformat':
                    var config = window.xtemplate_instances[0].settings.config || {};

                    var decimal_separator = config.decimal_separator || '.';

                    var float_format = parseFloat(str);
                    var decimal_places = args[0];
                    if (typeof decimal_places == 'undefined'){
                        decimal_places = -1;
                    }
                    decimal_places = parseInt(decimal_places);

                    /* ignore negative for now, will make it so that decimals are only returned if not 0 */
                    if (decimal_places < 0){
                        decimal_places *= -1;
                    }
                    if (isNaN(float_format)){
                        float_format = 0;
                    }

                    var str_formatted = float_format.toFixed(decimal_places).toString();
                    var formatted_arr = str_formatted.split('.', 2);
                    filtered = formatted_arr.join(decimal_separator);
                    break;
                case 'moneyformat':
                    var config = window.xtemplate_instances[0].settings.config || {};

                    var float_format = parseFloat(str);
                    //cogs.debug(args, 'args to moneyformat', 'warn');
                    var decimal_places = parseInt(args[0]);
                    if (isNaN(decimal_places)){
                        decimal_places = config.decimal_places || 2;
                    }
                    var decimal_separator = config.decimal_separator || '.';
                    var thousand_separator = config.thousand_separator || ',';
                    decimal_places = parseInt(decimal_places);

                    /* ignore negative for now, will make it so that decimals are only returned if not 0 */
                    if (decimal_places < 0){
                        decimal_places *= -1;
                    }
                    if (isNaN(float_format)){
                        float_format = 0;
                    }

                    var str_float = '0.00';
                    if (decimal_places == 0){
                        str_float = parseInt(float_format).toString();
                        decimal_separator = '';
                    }
                    else {
                        str_float = float_format.toFixed(decimal_places).toString();
                    }
                    //cogs.debug(decimal_places, 'decimal_places to moneyformat', 'warn');
                    //cogs.debug(str_float, 'str_float to moneyformat', 'warn');

                    var float_str_array = str_float.split('.', 2);
                    var int_part = float_str_array[0];
                    var decimal_part = float_str_array[1] || '';
                    var thousandized = '';
                    var back_pos = 0;
                    for (var t=int_part.length;t>=0;t--){
                        back_pos = int_part.length - t + 1;
                        thousandized = int_part.substr(t, 1) + thousandized;
                        if (back_pos%3 == 1 && back_pos!=1 && t>0){
                            thousandized = thousand_separator + thousandized;
                        }
                    }

                    filtered = thousandized + decimal_separator + decimal_part;
                    break;
                case 'weightformat':
                    var weight = parseFloat(str);
                    var base_format = args[0];
                    var new_format = base_format || 'g';
                    switch (base_format){
                        case 'kg':
                            if (weight < 1){
                                weight = weight * 1000;
                                new_format = 'g';
                            }
                            break;
                        default:
                        case 'g':
                            if (weight > 1000){
                                weight = weight / 1000;
                                new_format = 'kg';
                            }
                            break;
                    }
                    filtered = weight.toString() + ' ' + new_format;
                    break;
                case 'dateformat':
                    /* format function added at top of this file via prototype */
                    var config = window.xtemplate_instances[0].settings.config || {};

                    var datetime_short = config.datetime_short || 'Y-m-d H:i';
                    var datetime_long = config.datetime_long || 'd F Y H:i:s';
                    var date_short = config.date_short || 'Y-m-d';
                    var date_long = config.date_long || 'd F Y';
                    /* TODO : fix so that it is a date already */
                    if (str){
                        if (typeof str.unix_time != 'undefined'){
                            // Not a date object, convert it
                            var mydate = new Date();
                            mydate.setTime(str.unix_time * 1000);
                            str = mydate;
                        }
                        //var mydate = new Date();
                        //if (str.unix_time){
                            //str.setTime(str.unix_time * 1000);
                        if ($.isArray(args)){
                            if (typeof str.format == 'function'){
                                if (args[0] == 'DATETIME_SHORT'){
                                    filtered = str.format(datetime_short);
                                }
                                else if (args[0] == 'DATETIME_LONG'){
                                    filtered = str.format(datetime_long);
                                }
                                else if (args[0] == 'DATE_SHORT'){
                                    filtered = str.format(date_short);
                                }
                                else if (args[0] == 'DATE_LONG'){
                                    filtered = str.format(date_long);
                                }
                                else {
                                    filtered = str.format(args.join(':'));
                                }
                            }
                            else {
                                debug('WARNING: object suppled to dateformat not a date');
                                debug(str);
                            }
                        }
                    }
                    else {
                        filtered = '';
                    }
                    //}
                    break;
                case 'striptags':
                    if (str){
                        filtered = str.replace(/(<([^>]+)>)/ig, " ");
                    }
                    break;
                case 'trim':
                    if (str){
                        filtered = $.trim(str);
                    }
                case 'tojson':
                    if (str){
                        filtered = JSON.stringify(str);
                    }
                    break;
                default:
                    debug('Unrecognized filter "' + filter + '"');
                    break;
            }

            return filtered;
        }

        this.apply_filter = function(str, filter_str){
            try {
                var filtered_str = str;
                var filter_arr = [];
                var last_char = '';
                var c = 1;
                var sub_str = '';
                var in_string = false;

                var this_char = filter_str.substring(0, 1);
                while (this_char){
                    if (this_char == ' ' && !in_string){
                        filter_arr.push(sub_str + ' ');
                        sub_str = '';
                    }
                    else if (this_char == "'" && last_char != '\\'){
                        in_string = !in_string;
                    }
                    else {
                        sub_str += this_char;
                    }

                    last_char = this_char;
                    this_char = filter_str.substring(c, c+1);
                    c++;
                }
                if (sub_str != ''){
                    filter_arr.push(sub_str);
                }
                
                for (var f=0;f<filter_arr.length;f++){
                    try {
                        var filter_raw = filter_arr[f];
                        var filter_arg_arr = filter_raw.split(':');
                        var filter = filter_arg_arr.shift();
                        //cogs.debug(filter_arg_arr, filter + ' args from ' + filter_raw + ', original = ' + filter_str);

                        filtered_str = this.exec_filter(filtered_str, filter, filter_arg_arr);
                    }
                    catch (e){
                        cogs.debug('Error in exec_filter filtered_str=' + filtered_str + ' filter=' + filter + ' msg=' + e.message);
                    }
                }

                return filtered_str;
            }
            catch (e){
                cogs.debug('Error in apply_filter ' + e.message);
                cogs.debug('str');
                cogs.debug(str);
                cogs.debug('filter_str');
                cogs.debug(filter_str);
                if (filter_str == 'length'){
                    return 0;
                }
                return '';
            }
        }
    }

    /* Private functions */

    function debug(str){
        if (window.console){
            window.console.log(str);
        }
    }

    function doc_from_string(str){
        var domdoc = null;
        try {
            domdoc = new ActiveXObject("Microsoft.XMLDOM");
            domdoc.async = "false";
            domdoc.loadXML(str);
        }
        catch (e){
            try {
                var parser = new DOMParser();
                domdoc = parser.parseFromString(str, "text/xml");

                if (domdoc.getElementsByTagName('parsererror')[0]){
                    cogs.debug('Unable to parse xml template');
                    cogs.debug(domdoc);
                    cogs.debug(str);

                    domdoc = null;
                }
            }
            catch (e2){
                cogs.debug('Unable to parse xml template');
                cogs.debug(e2);
            }
        }

        return domdoc;
    }


    function dump_var(obj){
        var dumped_str = '';
        if (typeof obj == 'object'){
            for (var i in obj){
                if (typeof obj[i] == 'object'){
                    dumped_str += dump_var(obj[i]) + '<br />';
                }
                else if (typeof obj[i] != 'function') {
                    dumped_str += i + ' = ' + obj[i] + '<br />';
                }
            }
        }

        return dumped_str;
    }

})(jQuery);


/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* datatypes.js */

cogs.db.Blob = Class.extend({
    content: null,
    ref: null,
    mimetype: null,
    init: function(content, mimetype){
        this.content = content;
        this.mimetype = mimetype;
        //cogs.debug(this.mimetype);
    },
    from_ref: function(ref){
        var b = new cogs.db.Blob();
        b.ref = ref;
        return b;
    },
    get_data_url: function() {
        if (this.mimetype && this.content){
            return 'data:'+this.mimetype+';base64,'+this.content;
        }
        else {
            var data_url = '';
            $.ajax({
                async: false,
                url: this.url() + '&data_url=1',
                success: function(data){
                    data_url = data;
                }
            });
            return data_url;
        }
    },
    url: function(){
        return '/__blob?ref=' + this.ref;
    },
    content_extension: function(){
        var type = this.mimetype;
        // Make sure this is in sync with db/blob.php
        var lookup = {
            'image/gif' : 'gif',
            'image/png' : 'png',
            'image/jpeg' : 'jpeg',
            'text/plain' : 'txt',
            'application/pdf' : 'pdf',
            'image/x-ico' : 'ico',
            'application/x-dvi' : 'dvi',
            'image/bmp' : 'bmp',
            'image/x-windows-bmp' : 'bmp',
            'application/x-bzip2' : 'bz2',
            'application/x-javascript' : 'js',
            'application/msword' : 'doc',
            'application/excel' : 'xls',
            'application/vnd.ms-excel' : 'xls',
            'application/x-excel' : 'xls',
            'application/x-msexcel' : 'xls',
            'application/xml' : 'xml',
            'text/xml' : 'xml',
            'application/x-compressed' : 'zip',
            'application/x-zip-compressed' : 'zip',
            'application/zip' : 'zip',
            'multipart/x-zip' : 'zip',
            'application/ogg' : 'ogx',
            'video/ogg' : 'ogv',
            'audio/ogg' : 'oga',
            'audio/ogg' : 'ogg',
            'audio/ogg' : 'spx',
            'audio/flac' : 'flac',
            'application/annodex' : 'anx',
            'audio/annodex' : 'axa',
            'video/annodex' : 'axv',
            'application/xspf+xml' : 'xspf'
        };
        var type_array = type.split(';');
        var base_type = type_array[0];
        if (typeof lookup[base_type] != 'undefined'){
            return lookup[base_type];
        }
        else {
            var base_type_arr = base_type.split('/');
            if (base_type_arr.length > 1){
                return base_type_arr[1];
            }
            else {
                return base_type;
            }
        }
    },
    toString: function() {
        return this.content;
    }
});


cogs.db.GeoPt = Class.extend({
    latitude: 0,
    longitude: 0,
    init: function(latitude, longitude){
        this.latitude = latitude;
        this.longitude = longitude;
    },
    toString: function(){
        return this.latitude + ', ' + this.longitude;
    }
});

cogs.db.PhoneNumber = Class.extend({
    number: '',
    extension: '',
    init: function(number, extension){
        extension = extension || '';
        this.number = number;
        this.extension = extension;
    },
    toString: function(){
        var str = this.number;
        if (this.extension){
            str += ' (' + this.extension + ')';
        }
        return str;
    }
});

cogs.db.TranslatableString = Class.extend({
    _original: '',
    _name: '',
    init: function(original, name){
        this._original = original;
        this._name = name;
    },
    original: function(){
        return this._original;
    },
    name: function(){
        return this._name;
    },
    toString: function(){
        return this._original;
    },
    translate: function(language){
        if (!language || !this._name){
            return this._original;
        }
        return cogs.i18n.translate(this._name, this._original, [], language);
    }
});


cogs.db.TranslatableText = Class.extend({
    _original: '',
    _name: '',
    init: function(original, name){
        this._original = original;
        this._name = name;
    },
    original: function(){
        return this._original;
    },
    name: function(){
        return this._name;
    },
    toString: function(){
        return this._original;
    },
    translate: function(language){
        if (!language || !this._name){
            return this._original;
        }
        return cogs.i18n.translate(this._name, this._original, [], language);
    }
});

/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* key.js */
var Key = Class.extend({
    _kind: null,
    _id: null,
    _encoded: '',
    _name: '',
    _path: [],
    init: function(encoded){
        //		this._kind = null;
        //		this._id = null;
        //		this._encoded = '';

        if (encoded){
            if (typeof encoded != 'string'){
                cogs.debug('encoded must be a string in Key.init() it was ' + (typeof encoded));

                throw {message:'encoded must be a string in Key.init()'};
            }

            var decoded = Base64.decode(encoded);
            if (decoded){
                var decoded_arr = decoded.split('/');
                if (decoded_arr.length > 2){
                    // The id may contain a slash
                    var decoded_kind = decoded_arr.shift();
                    var decoded_id = decoded_arr.join('/');
                    decoded_arr = [];
                    decoded_arr.push(decoded_kind);
                    decoded_arr.push(decoded_id);
                }
                if (decoded_arr.length == 2){
                    this._path = decoded_arr;
                    this._encoded = encoded;
                    this._kind = decoded_arr[0].toLowerCase();
                    this._id = decoded_arr[1];
                }
            }
        }
    },
    from_path: function(path){
        var k = new cogs.db.Key();
        k._path = path;

        var id_or_name = path[path.length-1];

        k._id = id_or_name;
        k._name = id_or_name;

        k._kind = path[path.length-2];

        return k;
    },
    from_full: function(str){
        if (str != null){
            var key_matches = str.match(/\[([a-zA-Z0-9\=]*)\]/);
            if (key_matches){
                var key = (new cogs.db.Key()).from_encoded(key_matches[1]);
                return key;
            }
        }

        return null;
    },
    from_encoded: function(str){
        if (str){
            var key = new cogs.db.Key(str);
            if (key._kind && key._id){
                return key;
            }
        }
        return null;
    },
    id: function(){
        return this._id;
    },
    name: function(){
        return this._name;
    },
    class_name: function(){
        if (!this._kind){
            throw new Error('Cannot call class_name on cogs.db.Key when kind is null');
        }
        var model_class = this._kind.toLowerCase();
        while (model_class.indexOf("\\") > -1){
            model_class = model_class.replace("\\", '.');
        }
        return model_class;
    },
    kind: function(){
        return this._kind;
    },
    is_valid: function() {
        return this.kind() && this.id();
    },
    id_or_name: function(){
        if (this._name){
            return this._name;
        }
        else if (this._id){
            return this._id;
        }
        return null;
    },
    has_id_or_name: function(){
        return (this._name || this._id);
    }
});

Key.prototype.toString = function(){
    if (this._encoded == ''){
        this._encoded = Base64.encode(this._path.join('/'));
    }
    return this._encoded;
};

cogs.db.Key = Key;
/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* model.js */

var Model_load_async_callbacks = [];
var Model_multi_data_callbacks = [];
var Model_encrypt_password = null;

if (typeof cogs.db == 'undefined'){
    cogs.db = {};
};

cogs.db.Model_receive = function(event_name, event_data){
    cogs.debug('Event named ' + event_name + ' received');
    //cogs.debug(event_data);

    var model_key = new cogs.db.Key(event_data);
    if (model_key){
        event_data = {event_name:event_name, key:model_key};
        // Throw the custom event at body element
        switch (event_name){
            case '/db/model/create':
                $('body').trigger('modelcreate', event_data);
                break;
            case '/db/model/update':
                $('body').trigger('modelupdate', event_data);
                break;
            case '/db/model/delete':
                $('body').trigger('modeldelete', event_data);
                break;
        }

        cogs.debug(model_key.kind());
        cogs.debug(model_key);
    }
};

cogs.db.Model_listen = function (){
    cogs.webapp.Event.register_event_handler('/db/model/update', cogs.db.Model_receive);
    cogs.webapp.Event.register_event_handler('/db/model/create', cogs.db.Model_receive);
    cogs.webapp.Event.register_event_handler('/db/model/delete', cogs.db.Model_receive);
};


var Model = Class.extend({
    _dbname: '',
    _values: {},
    _modified_values: {},
    _loaded: false,
    _language: 'en',
    init: function(initial_data){
        this._values = {};
        this._modified_values = {};
        this._loaded = false;
        for (var i in initial_data){
            this[i] = initial_data[i];
        }
    },
    toString: function(){
        //return 'A model';
        return 'Instance of ' + this.kind() + '\nKey: ' + this.key() + '\nID : ' + this.key().id() + '\nPath : ' + this._key._path.join('/');
    },
    __rpc_call: function(name, args) {
        //cogs.debug('rpc call for ' + name);
        //cogs.debug('with args');
        //cogs.debug(args);

        var method_name, class_name;
        var class_method = name.split('.');
        if (class_method.length > 2){
            /* Namespaced model */
            method_name = class_method.pop();
            class_name = '\\' + class_method.join('\\');
        }
        else if (class_method.length == 2) {
            class_name = class_method[0];
            method_name = class_method[1];
        }
        else {
            throw new cogs.error.Error('RPC call must be in the form class.method');
        }

        return cogs.rpc_call(class_name, method_name, args, this.tag());
    },
    set_field: function(k, v) {
        // TODO : This is shortcut because it does not work for arrays or objects

        this._values[k] = v;
        this._modified_values[k] = true;

        return;


        if ($.isArray(v) || $.isPlainObject(v)){
            this._values[k] = v;
            this._modified_values[k] = true;
        }

        if (this._values[k] != v){
            this._values[k] = v;
            this._modified_values[k] = true;
        }
    },
    kind: function() {
        return this._dbname;
    },
    php_class_name: function(){
        return this.kind().replace(/_/g, '\\');
    },
    key: function() {
        return (this._key==null)?new cogs.db.Key():this._key;
    },
    id: function() {
        var key = this._key;
        if (key != null){
            return key.id();
        }
        return null;
    },
    set_language: function(language){
        this._language = language;
    },
    encrypt_xml: function(plain_str){
        if (!Model_encrypt_password){
            return plain_str;
        }
        return AesCtr.encrypt(plain_str, Model_encrypt_password, 256);
    },
    decrypt_xml: function(node_container){
        if (!Model_encrypt_password){
            return '';
        }
        return AesCtr.decrypt(node_container.nodeValue, Model_encrypt_password, 256);
    },
    xml_to_doc: function(xml_str){
        // http://www.w3schools.com/dom/dom_parser.asp
        var doc = cogs.doc_from_string(xml_str);

        return doc;
    },
    load_from_xml_string: function(xml_str) {
        try {
            var doc = cogs.doc_from_string(xml_str);
            return this.load_from_xml(doc);
        }
        catch (e){
            cogs.debug('Error in load_from_xml_string');
            cogs.error_handler(e);
        }
    },
    load_from_xml: function(xml) {
        try {
            //cogs.debug(xml, 'loading xml');
            if (!xml){
                return false;
            }
            if (typeof xml == 'string'){
                return this.load_from_xml_string(xml);
            }

            try {
                if (xml.tagName != 'entity'){
                    if (!xml.getAttribute){
                        var root_node = xml.getElementsByTagName('entity');
                        if (!root_node){
                            return false;
                        }
                        xml = root_node.item(0);
                        if (!xml || xml.nodeType != 1){
                            return false;
                        }
                    }
                }
            }
            catch (e){
                cogs.debug('Getting root entity threw error');
                throw e;
            }

            var key_enc = xml.getAttribute('key');
            if (!key_enc){
                cogs.debug(xml, 'NO KEY ATTRIBUTE');
                return false;
            }
            this._key = new cogs.db.Key(key_enc);


            //var property_tags = xml.getElementsByTagName('property');
            var property_tags = xml.childNodes;
            //cogs.debug(property_tags);
            if (!property_tags){
                cogs.debug('NO PROPERTY TAGS');
                return false;
            }
            var property_tag = xml.firstChild;
            while (property_tag){
                if (property_tag.nodeName != 'property'){
                    property_tag = property_tag.nextSibling;
                    continue;
                }
                    //cogs.debug('PARSING');
                    //cogs.debug(property_tag);
                var name = property_tag.getAttribute('name');
                var encrypted = property_tag.getAttribute('encrypted');

                if (!encrypted){
                    if (typeof this._fields[name] != 'undefined'){
                        try {
                            // Check that this value was not set before data loaded
                            if (typeof this._values[name] == 'undefined'){
                                this._values[name] = this._fields[name].xml_to_value(property_tag);
                            }
                        }
                        catch (e){
                            cogs.debug('Property xml_to_value threw error - ' + name + ' type : ' + this._fields[name].data_type);
                            throw e;
                        }
                        //cogs.debug('PARSED VALUE of ' + name);
                        //cogs.debug(this._values[name]);
                    }
                    else {
                        cogs.debug(name + ' not found in model _fields ');
                    }
                }
                else {
                    var decrypted_xml = this.decrypt_xml(property_tag);
                    if (decrypted_xml){
                        var xml_doc = cogs.doc_from_string(decrypted_xml);
                        if (xml_doc){
                            this._values[name] = this._fields[name].xml_to_value(xml_doc.firstChild);
                        }
                    }
                }

                property_tag = property_tag.nextSibling;
            }
            

            this._loaded = true;
            
            var store = cogs.get_store();
            store.save_model(this);
        }
        catch (e){
            cogs.debug('load_from_xml threw error');
            cogs.error_handler(e);
        }

        //cogs.debug('ABOUT TO RETURN FROM load_from_xml');
        //cogs.debug(this);

        return this;
    },
    load_from_json: function (json_obj){
        var m = null;
        //cogs.debug(json_obj, 'loading data');
        //cogs.debug(this, 'this in load_from_json');
        try {
            if (json_obj['key']){
                var model_key = new cogs.db.Key().from_encoded(json_obj['key']);
                if (model_key && model_key.is_valid()){
                    try {
                        var kind = json_obj['kind'].replace(/\\/g, '.');
                    }
                    catch (e){
                        cogs.debug('Kind not set in load_from_json');
                        cogs.debug(json_obj, 'Obj loaded');
                        throw e;
                    }

                    try {
                        var global_obj = cogs.global_class(kind);
                        m = new global_obj();
                    }
                    catch (e){
                        cogs.debug('Kind ' + kind + ' not defined in global namespace');
                        return json_obj;
                    }

                    m._key = model_key;

                    for (var name in m._fields){
                        if (typeof m['set_'+name] == 'function'){
                            //cogs.debug(name, 'json property name');
                            try {
                                m['set_'+name](m._fields[name].json_to_value(json_obj[name]));
                            }
                            catch (e){
                                cogs.debug(e, 'Error in Property.json_to_value ' + name);
                                cogs.debug(json_obj);
                                throw e;
                            }
                        }
                    }

                    m._loaded = true;

                    var store = cogs.get_store();
                    store.save_model(m);
                }
            }
        }
        catch (e){
            cogs.debug(e, 'Error in Model.load_from_json');
            throw e;
        }
        
        return m;
    },
    load_data_async: function(options) {
        //cogs.debug('Load data async ' + this.key().toString());
        options = options || {};
        var store = cogs.get_store();
        var store_res = store.load_model(this);
        if (store_res){
            //cogs.debug(store_res, 'GOT MODEL!');
            //cogs.debug(store_res.data(), 'GOT MODEL DATA!');
            
            try {
                var model_data = store_res.data();
                if (options.success && typeof options.success == 'function'){
                    options.success(model_data, store_res, options.data);
                }

                return;
            }
            catch (e){
                
            }
        }
        
        var self = this;
        window.setTimeout(function(){
            
            try {
                /*if (this._loaded && options.success && !options.async){
                    options.success(self, options.data);
                    return true;
                }*/

                options.recursive_data = options.recursive_data || [];

                var current_ref = Model_load_async_callbacks.length;
                Model_load_async_callbacks[current_ref] = {
                    success:options.success,
                    error:options.error,
                    model:self,
                    data:options.data,
                    recursive_data:options.recursive_data
                };

                var xml_request = '<request><method>Model.data</method><params>';
                xml_request += '<param name="key">' + self.key().toString() + '</param>';
                xml_request += '<param name="ref">' + current_ref + '</param>';
                xml_request += '</params></request>';

                var api_options = {
                    type: 'POST',
                    async: true,
                    success: function (data, status, xhr, response_ref){
                        var callback_data = {};
                        var entities = null;
                        var xml_data = null;
                        
                        try {
                            if (xhr){
                                response_ref = xhr.getResponseHeader('X-Cogs-Ref');
                                callback_data = Model_load_async_callbacks[response_ref];
                                xml_data = xhr.responseXML;
                            }
                            else {
                                // Response from a worker
                                callback_data = Model_load_async_callbacks[response_ref];
                                xml_data = cogs.doc_from_string(data);
                            }
                            
                            
                            if (xml_data){
                                entities = xml_data.getElementsByTagName('entity');
                            }
                            else {
                                cogs.debug('Server returned no usable data ' + data);
                                return false;
                            }


                            if (entities){
                                callback_data.model.load_from_xml(xml_data);
                                //var store = cogs.get_store();
                                //cogs.debug('putting in store after load_data_async_complete');
                                //store.put(callback_data.model);
                                callback_data.model._loaded = true;
                            }
                            else {
                                cogs.debug('Server returned no usable data ' + xhr.responseText);
                                return false;
                            }

                            // Need to clone the values otherwise the data overwrites reference models
                            var model_data = cogs.clone(callback_data.model._values);
                            model_data.key = callback_data.model.key().toString();
                            model_data.id = callback_data.model.key().id();

                            if (options.recursive){
                                if (callback_data.recursive_data.length){
                                    for (var v in model_data){
                                        if (model_data[v]){
                                            for (var r=0;r<callback_data.recursive_data.length;r++){
                                                if (typeof model_data[v].key == 'function' && callback_data.recursive_data[r].key == model_data[v].key().toString()){
                                                    model_data[v] = callback_data.recursive_data[r];
                                                }
                                                else if ($.isArray(model_data[v])){
                                                    for (var i=0;i<model_data[v].length;i++){
                                                        if (typeof model_data[v][i].key == 'function' && callback_data.recursive_data[r].key == model_data[v][i].key().toString()){
                                                            model_data[v][i] = callback_data.recursive_data[r];
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                // Load data from submodels
                                for (v in model_data){
                                    if (model_data[v] && typeof model_data[v].data == 'function'){
                                        model_data[v] = model_data[v].data(options);
                                    }
                                    else if ($.isArray(model_data[v])){
                                        for (i=0;i<model_data[v].length;i++){
                                            if (typeof model_data[v][i].data == 'function'){
                                                model_data[v][i] = model_data[v][i].data(options);
                                            }
                                        }
                                    }
                                }
                            }


                            callback_data.success(model_data, callback_data.model, callback_data.data);

                            return true;
                        }
                        catch (e){
                            //alert(e.message);
                            cogs.error_handler(e);

                            return false;
                        }
                    },
                    error: function (xhr, status, e){
                        cogs.debug(Model_load_async_callbacks.length);
                        cogs.debug(xhr);
                        cogs.error_handler(e);
                        cogs.debug('Model::load_data_async_error');
                    }
                };

                cogs.api_xml(xml_request, api_options);
            }
            catch (e){
                cogs.error_handler(e);
            }

        }, 500);
    },
    load_data: function() {
        try {
            var store = cogs.get_store();
            var store_res = store.load_model(this);
            if (store_res){
                //cogs.debug(store_res, 'GOT MODEL in load_data!');
                //cogs.debug(store_res.data(), 'GOT MODEL DATA!');

                try {
                    var model_data = store_res.data();

                    this._values = model_data;
                    this._loaded = true;

                    return true;
                }
                catch (e){
                    cogs.debug(store_res, 'Error in loading stored data');
                    store.remove(this.key().toString());
                }
            }
        
        
            var xml_request = '<request>';
            xml_request += '<method>Model.data</method>';
            xml_request += '<params>';
            xml_request += '<param name="language">' + this._language + '</param>';
            xml_request += '<param name="key">' + this.key().toString() + '</param>';
            xml_request += '</params>';
            xml_request += '</request>';

            var options = {
                type: 'POST',
                async: false
            };

            try {
                var xhr = cogs.api_xml(xml_request, options);
            }
            catch (e){
                if (e.name == 'NotFoundError'){
                    cogs.debug('Data not found for key ' + this.key().toString());
                }
                throw e;
            }


            //cogs.debug('Response from Model.data');
            //cogs.debug(xhr.responseText);

            var response_xml = xhr.responseXML;
            var exceptions = [];
            if (!response_xml){
                response_xml = cogs.doc_from_string(xhr.responseText);
                if (!response_xml){
                    throw new cogs.error.StoreError('Unable to parse XML response ' + xhr.responseText);
                }
            }
            var entities = response_xml.getElementsByTagName('entity');
            if (entities.length){
                //var key_enc = entities[0].getAttribute('key');
                //var tag = xhr.responseXML.getElementsByTagName('key')[0].firstChild.nodeValue;

                //var key = new cogs.db.Key(key_enc);
                //cogs.debug(key.kind());
                //eval('model = new ' + key.kind().toLowerCase());
                //cogs.debug('loading from xml response');
                var res = this.load_from_xml(response_xml);
                if (!res){
                    cogs.debug(xhr.responseText, 'Error in loading data');
                    throw new cogs.error.StoreError('Error loading data ' + xhr.responseText);
                }

                this._loaded = true;

                //var store = cogs.get_store();
                //cogs.debug('putting in store after load_data');
                //store.put(this);
                //cogs.debug('MODEL AFTER XML load');
                //cogs.debug(this._values);
                //cogs.debug(res);

            //model = this;

            }
            else if (exceptions = response_xml.getElementsByTagName('entity') && exceptions.length){
                if (typeof exceptions[0].getAttribute('name') == 'string' && typeof cogs.error[exceptions[0].getAttribute('name')] != 'undefined'){
                    throw new cogs.error[exceptions[0].getAttribute('name')](exceptions[0].nodeValue);
                }
                else {
                    throw new cogs.error.StoreError('Server returned no usable data ' + xhr.responseText, 0);
                }
            }
            else {
                throw new cogs.error.StoreError('Server returned no usable data ' + xhr.responseText, 0);
            }
        }
        catch (e){
            cogs.debug('Error in load_data');
            cogs.debug(e);
            throw e;
        }

        return true;
    },
    get: function(keys) {
        var models = [];
        //var store = cogs.get_store();

        if (!(keys instanceof Array)){
            var key = keys;
            keys = [];
            keys.push(key);
        }

        for (var k=0;k<keys.length;k++){
            if (!keys[k] || !keys[k].is_valid()){
                continue;
            }
            //var model = store.get(keys[k]);
            var model = null;
            if (!model){
                var model_class = keys[k].class_name();
                var global_class = cogs.global_class(model_class);
                model = new global_class();
                model._key = keys[k];
                model._loaded = false;
            }
            
            if (model){
                models.push(model);
            }
        }

        if (models.length == 1){
            return models[0];
        }
        else if (models.length == 0){
            return null;
        }

        return models;
    },
    put: function(options) {
        options = options || {};
        options.async = options.async || false;
        try {
            try {
                var modified = false;
                //cogs.debug('Number of modified values in ' + this.kind());
                //cogs.debug(this._modified_values);
                for (var m in this._modified_values){
                    //cogs.debug(m);
                    modified = true;
                    break;
                }
                
                if (!modified){
                    cogs.debug('put called but model not modified (supposedly)!');
                    //return false;
                }

                Controller.call_plugins(this.kind()+':put_pre', this);
                
                cogs.debug(this, 'Saving model');

                var xml = this.to_put_xml();

                var post_xml = '<request output="json">';
                post_xml += '<method>Model.put</method>';
                post_xml += '<params>';
                post_xml += '<param name="class">'+this.kind()+'</param>';
                post_xml += '<param name="model" type="xml">' + xml + '</param>';
                post_xml += '</params>';
                post_xml += '</request>';

                cogs.debug(post_xml, 'Put XML');
            }
            catch (e){
                if (typeof options.error == 'function'){
                    options.error(e);
                }
                else {
                    throw e;
                }
            }
            //return false;
            

            var xhr_options = {
                type: 'POST',
                async: false
            };
            if (options.async){
                xhr_options.async = true;
                xhr_options.success = function(response_data, text_status, xhr){
                    if (typeof response_data == 'string') {
                        try {
                            response_data = JSON.parse(response_data);
                            response_data = response_data._return;
                            cogs.debug(response_data, 'Model put response');
                        }
                        catch (e){
                            var err = e;
                            
                            var xml = cogs.doc_from_string(response_data);
                            
                            if (xml){
                                // It is probably an error
                                var exception_node = xml.getElementsByTagName('exception');
                                if (exception_node.length){
                                    var exception_type = exception_node[0].getAttribute('name');
                                    var exception_str = exception_node[0].firstChild.nodeValue;
                                    
                                    if (typeof cogs.error[exception_type] != 'function'){
                                        exception_type = 'Error';
                                    }
                                    
                                    err = new cogs.error[exception_type](exception_str);
                                }
                            }
                            
                            if (typeof options.error == 'function') {
                                return options.error(err, xhr);
                            }
                            else {
                                cogs.debug(response_data);
                                throw err;
                            }
                        }
                    }
                    
                    //cogs.debug(options.success, 'options.success');
                    //cogs.debug(response_data.success, 'response_data.success');
                    
                    if (options.success && response_data.success){
                        try {
                            //cogs.debug(response_data.data, 'response_data.data');
                            var obj = new Model();
                            obj = obj.load_from_json(response_data.data);
                            /* clear from the local datastore */
                            obj.delete_local();
                            
                            if (typeof options.success == 'function'){
                                options.success(response_data.data, obj, xhr);
                            }
                            else if (typeof options.error == 'function') {
                                e = new cogs.error.StoreError('No success function specified');
                                options.error(e, xhr);
                            }
                        }
                        catch (e){
                            cogs.debug('Model.put caught exception - ' + e.message);
                            cogs.debug(e);
                            if (typeof options.error == 'function'){
                                options.error(e, xhr);
                            }
                            else {
                                throw e;
                            }
                        }
                    }
                    else if (!response_data.success && response_data.exception && typeof cogs.error[response_data.exception] == 'function'){
                        //alert('error in put');
                        var e = new cogs.error[response_data.exception](response_data.error);
                        if (typeof options.error == 'function'){
                            options.error(e, xhr);
                        }
                        else {
                            throw e;
                        }
                    }
                };
                xhr_options.error = function (xhr, text_status, e){
                    if (typeof options.error == 'function'){
                        options.error(e, xhr);
                    }
                    else {
                        throw e;
                    }
                }

                var do_put = function(){
                    try {
                        cogs.api_xml(post_xml, xhr_options);
                    }
                    catch (e){
                        cogs.hide_loading_spinner();
                        if (typeof options.error == 'function'){
                            options.error(e, null);
                        }
                        else {
                            cogs.debug('Exception in Model.put');
                            cogs.debug(e);
                            throw e;
                        }
                    }
                }
                window.setTimeout(do_put, 50);
                
                return true;
            }

            var xhr = cogs.api_xml(post_xml, xhr_options);
            if (xhr.responseText){
                //cogs.debug('Model.put xhr.responseText');
                cogs.debug(xhr.responseText);
                //return false;
                try {
                    var response_obj = JSON.parse(xhr.responseText);
                    response_obj = response_obj._return;
                    cogs.debug(response_obj);
                }
                catch (e){
                    cogs.debug(e);
                    cogs.debug(xhr.responseText);
                    cogs.debug(cogs.error.StoreError);
                    if (typeof options.error == 'function') {
                        var ex = new cogs.error.StoreError('Failed to parse server response after put ' + xhr.responseText);
                        options.error(ex, xhr);
                    }
                    else {
                        throw new cogs.error.StoreError('Failed to parse server response after put ' + xhr.responseText);
                    }
                }
                
                if (response_obj.success){
                    //return true;
                    /*
                     * Set the key for the object and then mark it as not loaded, the next call for data
                     * will load it from the server
                     */
                    this._key = new cogs.db.Key().from_encoded(response_obj.data.key);
                    this._values = {};
                    this._modified_values = {};
                    this._loaded = false;

                    Controller.call_plugins(this.kind()+':put_post', this);
            
                    /* clear from the local datastore */
                    this.delete_local();

                    return this;
                }
                else {
                    if (typeof options.error == 'function') {
                        if (response_obj.exception && typeof cogs.error[response_obj.exception] != 'undefined'){
                            var e = new cogs.error[response_obj.exception](response_obj.error);
                            options.error(e, xhr);
                        }
                        
                    }
                    else {
                        var e = new cogs.error[response_obj.exception](response_obj.error);
                        throw e;
                    }
                }
            }
            else {
                var e = new cogs.error.StoreError('Server returned error data ' + xhr.responseText);
                if (typeof options.error == 'function') {
                    options.error(e, xhr);
                }
                else {
                    throw e;
                }
            }
        }
        catch (e){
            cogs.debug('Unknown error in put');
            
            cogs.debug(e);
            throw e;
        }
    },
    all: function(){
        var q = new Query();
        //cogs.debug(this._dbname);
        q.init(this._dbname);

        return q;
    },
    delete_local: function(){
        var store = cogs.get_store();

        var key = this.key().toString();
        cogs.debug('Removing ' + key + ' from datastore');
        store.remove(key);
    },
    del: function(options){
        options = options || {};
        try {
            //cogs.debug('Delete url would be ' + url);
            var del_xml = '<request output="json">';
            del_xml += '<method>Model.delete</method>';
            del_xml += '<params>';
            del_xml += '<param name="key">' + this.key().toString() + '</param>';
            del_xml += '</params>';
            del_xml += '</request>';

            var xhr;

            if (options.async){
                var self = this;
                var xhr_options = {};
                xhr_options.async = options.async;
                xhr_options.type = 'POST';
                xhr_options.success = function(response_data, text_status, xhr){
                    try {
                        response_data = JSON.parse(response_data);
                        response_data = response_data._return;
                    }
                    catch (e){
                        if (typeof options.error == 'function'){
                            options.error(e, response_data);
                        }
                        else {
                            throw e;
                        }
                    }
                    
                    if (response_data.success && typeof options.success == 'function'){
                        options.success(response_data.data);
                        self.delete_local();
                    }
                    else if (!response_data.success && response_data.error){
                        var e;
                        if (typeof cogs.error[response_data.exception] == 'function'){
                            e = new cogs.error[response_data.exception](response_data.error);
                        }
                        else {
                            e = new cogs.error.Error(response_data.error);
                        }
                        
                        
                        if (typeof options.error == 'function'){
                            options.error(e, response_data);
                        }
                        else {
                            throw e;
                        }
                    }
                };

                var do_del = function(){
                    cogs.api_xml(del_xml, xhr_options);
                };
                window.setTimeout(do_del, 50);
            }
            else {
                xhr = cogs.api_xml(del_xml, {
                    type: 'POST',
                    async: false
                });

                if (xhr.responseText){
                    //cogs.debug('xhr.responseText');
                    //cogs.debug(xhr.responseText);
                    var response_data = JSON.parse(xhr.responseText);
                    response_data = response_data._return;

                    if (response_data.success){
                        this._key = new cogs.db.Key().from_encoded(response_data.data);
                        this._values = {};
                        this._loaded = false;

                        /* clear from the local datastore */
                        this.delete_local();

                        return true;
                    }
                    else if (!response_data.success && response_data.error) {
                        var e;
                        if (typeof cogs.error[response_data.exception] == 'function'){
                            e = new cogs.error[response_data.exception](response_data.error);
                        }
                        else {
                            e = new cogs.error.Error(response_data.error);
                        }
                        
                        
                        if (typeof options.error == 'function'){
                            options.error(e, response_data);
                        }
                        else {
                            throw e;
                        }
                    }
                    else {
                        throw new cogs.error.StoreError('Response from del() was not success');
                    }
                }
                
                cogs.debug(xhr);
                cogs.debug(xhr.responseText);
                throw new cogs.error.StoreError('Server returned error data');
            }
        }
        catch (e){
            cogs.error_handler(e);
        }
    },
    del_async: function(){
        try {
            var del_xml = '<request><method>Model.delete</method><params><param name="key">' + this.key().toString() + '</param></params></request>';

            var xhr = cogs.api_xml(del_xml, {
                //data: {xml:xml},
                type: 'POST',
                async: true
            });
        }
        catch (e){
            cogs.error_handler(e);
        }

        return xhr;
        // do not care about the result for now
    },
    get_by_id: function(ids){
        //cogs.debug('Loading id : ' + ids);
        if (!ids){
            return null;
        }
        if (!(ids instanceof Array)){
            var id = ids;
            ids = new Array();
            ids.push(id);
        }
        var models = [];
        for (var i in ids){
            //			alert([this.kind(), ids[i]]);
            try {
                var m = this.get(new cogs.db.Key().from_path([this.php_class_name(), ids[i]]));
                m.load_data();
                models.push(m);
            }
            catch (e){
                if (e.name != 'NotFoundError'){
                    models.push(m);
                }
                //alert('exception in get_by_id ' + e.name);
            }
            //if (m.load_data()){
            //    models.push(m);
            //alert(m);
            //}
        }
        //		alert('models = ' + models);
        if (models.length == 0){
            return null;
        }
        else if (models.length == 1){
            return models[0];
        }
        return models;
    },
    search: function(search_terms, query_obj, options){
        options = options || {};
        try {
            var kind = '';
            if (this && this._dbname){
                //cogs.debug('searching '+this._dbname+' for ' + search_terms);
                var query_json = '';
                kind = this.kind();
            }
            if (kind == ''){
                // search for all models
                kind = '*';
            }
            if (query_obj){
                query_json = JSON.stringify(query_obj.to_json());
            }
        }
        catch (e){
            alert('Caught error parsing search params - ' + e.message);
            throw e;
        }


        //data: '<request><method>Model.data</method><params><param name="key">' + this.key().toString() + '</param></params></request>'
        //alert(url);
        //return false;
        var xml = '<request output="json">';
        xml += '<method>Model.search</method>';
        xml += '<params>';
        xml += '<param name="keywords"><![CDATA['+search_terms+']]></param>';
        xml += '<param name="class"><![CDATA['+kind+']]></param>';
        xml += '<param name="query"><![CDATA['+query_json+']]></param>';
        xml += '</params>';
        xml += '</request>';

        //cogs.debug(xml);

        /*if (query_obj){
            cogs.debug(query_obj);
            cogs.debug(query_obj.to_json());
        }*/
        //cogs.debug(url);

        try {
            var xhr = cogs.api_xml(xml, {
                type: 'POST',
                async: false
            });

            //cogs.debug(xhr.responseText);
            //return false;

            //cogs.debug(xhr.responseText);
            if (xhr.responseText){
                //cogs.debug('server returned');
                //cogs.debug(xhr.responseText);
                var responseObj = JSON.parse(xhr.responseText);
                responseObj = responseObj._return;
                //cogs.debug(responseObj, 'responseObj');

                var result = {};
                var r = 0;
                var q = null;
                var query_keys = [];
                var global_class;
                if (kind == '*'){
                    result = {};
                    // result is grouped by type
                    for (var type in responseObj.data){
                        global_class = cogs.global_class(type);
                        result[type] = new global_class().all();
                        query_keys[type] = [];
                        for (r=0;r<responseObj.data[type].length;r++){
                            query_keys[type].push(responseObj.data[type][r]);
                        }
                        result[type].filter('key', 'in', query_keys[type]);
                    }
                }
                else {
                    var keys_q = [];
                    for (r=0;r<responseObj.data.length;r++){
                        var k = new cogs.db.Key(responseObj.data[r]);
                        if (!q){
                            global_class = cogs.global_class(k.kind());
                            q = new global_class().all();
                        }
                        keys_q.push(k.toString());
                    }
                    if (q){
                        q.filter('key', 'in', keys_q);
                    }

                    return q;
                }

                return result;
            }
            else {
                throw new cogs.error.StoreError('Server returned unknown error data', xhr.status);
                cogs.debug(xhr);
            }
        }
        catch (e){
            cogs.debug('Search threw error');
            cogs.error_handler(e);
        }
    },
    tag: function() {
        /*data = this.data();
        if (data.modified){
            var d = data.modified;
            //cogs.debug(d);
        }
        else {
            var d = new Date();
        }*/
        //cogs.debug(this.key());
        if (!this.key() || !this.key().kind()){
            return null;
        }
        var d = new Date();
        //var dtstr = d.getFullYear() + '-' + $.sprintf('%02d', (d.getMonth() + 1)) + '-' + $.sprintf('%02d', d.getDate());
        var dtstr = '';
        //alert(dtstr);
        str = 'tag:' + document.location.host + ','+dtstr+':'+this.key().kind()+'[' + this.key().toString() + ']';

        return str;
    },
    to_xml: function() {
        var tag = this.tag();
        if (tag == null){
            tag = '';
        }

        //cogs.debug('calling to_xml');
        //cogs.debug(this._modified_values);

        var ns = "xmlns=\"http://www.w3.org/2005/Atom\" xmlns:gd=\"http://schemas.google.com/g/2005\"";
        var entity_start_str = '<entity kind="'+this.kind()+'" key="'+this.key().toString()+'" language="'+this._language+'" '+ns+'>';
        var entity_end_str = '</entity>';

        var xml_str = entity_start_str + entity_end_str;

        var xmldoc = null;
        try {
            xmldoc = new ActiveXObject("Microsoft.XMLDOM");
            xmldoc.async = "false";
            xmldoc.loadXML(xml_str);
        }
        catch (e){
            var parser = new DOMParser();
            xmldoc = parser.parseFromString(xml_str, "text/xml");
        }
        var entity_node = xmldoc.getElementsByTagName('entity').item(0);

        var key_node = xmldoc.createElement('key');
        key_node.appendChild(xmldoc.createTextNode(tag));
        entity_node.appendChild(key_node);


        //xml += '<key>'+tag+'</key>';
        //cogs.debug(this._values);
        for (var key in this._fields){
            //cogs.debug(key + ' = ' + this._modified_values[key]);
            var property_node = null;
            if (!this._fields[key].encrypted){
                property_node = this._fields[key].value_to_xml(this._values[key], xmldoc);
                if (property_node){
                    if (typeof property_node == 'string'){
                        cogs.debug('Property to_xml returned a string! (offending property below)');
                        cogs.debug(this._fields[key]);
                        continue;
                    }

                }
            }
            else {
                var plain_xml_node = this._fields[key].value_to_xml(this._values[key], xmldoc);
                var plain_xml_str = null;
                if (plain_xml_node.xml){
                    plain_xml_str = plain_xml_node.xml;
                }
                else {
                    plain_xml_str = (new XMLSerializer()).serializeToString(plain_xml_node);
                }
                /* Opera adds the xml declaration to serialized XML */
                if (plain_xml_str.substr(0, 5) == '<?xml'){
                    plain_xml_str = plain_xml_str.substr(plain_xml_str.indexOf('?>') + 2);
                }
                //cogs.debug('Encrypting ' + plain_xml);
                var encrypted_xml = this.encrypt_xml(plain_xml_str);
                if (encrypted_xml){
                    property_node = xmldoc.createElement('property');
                    property_node.setAttribute('name', key);
                    property_node.setAttribute('type', this._fields[key].data_type);
                    property_node.setAttribute('encrypted', 'true');
                    property_node.appendChild(xmldoc.createCDATASection(encrypted_xml));
                    //xmldoc += '<property name="'+key+'" type="'+this._fields[key].data_type+'" encrypted="true"><![CDATA[' + encrypted_xml + ']]></property>';
                }
            }

            if (property_node){
                if (typeof this._modified_values[key] != 'undefined'){
                    property_node.setAttribute('modified', 'true')
                }
                entity_node.appendChild(property_node);
            }
        }

        return xmldoc;
    },
    to_put_xml: function(){
        var xmldoc = this.output('xmldoc');
        var entitynode = xmldoc.getElementsByTagName('entity').item(0);
        for (var c=0;c<entitynode.childNodes.length;c++){
            var modified_attr = entitynode.childNodes[c].getAttribute('modified');
            if (!modified_attr){
                entitynode.removeChild(entitynode.childNodes[c]);
                c--;
            }
        }
        
        var xml_str = '';
        if (xmldoc.xml){
            xml_str = xmldoc.xml;
        }
        else {
            xml_str = (new XMLSerializer()).serializeToString(xmldoc);
        }

        //cogs.debug('PUT XML : ' + xml_str);

        /* Opera adds the xml declaration to serialized XML */
        if (xml_str.substr(0, 5) == '<?xml'){
            xml_str = xml_str.substr(xml_str.indexOf('?>') + 2);
        }

        return xml_str;
    },
    output: function(output_format){
        switch (output_format){
            case 'xmldoc':
                return this.to_xml();
                break;
            case 'xml':
                var xmldoc = this.to_xml();
                var xml_str = '';
                if (xmldoc.xml){
                    xml_str = xmldoc.xml;
                }
                else {
                    xml_str = (new XMLSerializer()).serializeToString(xmldoc);
                }

                return xml_str;
                break;
            default:
                throw new cogs.error.Error('Unknown output format "'+output_format+'"');
                break;
        }
    },
    data: function (options) {
        /* This can be called with recursive as an optional parameter */
        options = options || {};
        var recursive = options.recursive || false;
        options.async = options.async || false;
        
        // Broken at the moment
        options.async = false;
        
        
        var recursive_data = [];
        //alert('calling data()');
        
        var handle_exception = function(e, options){
            if (options.error && typeof options.error == 'function'){
                return options.error(e);
            }
            else {
                throw e;
            }
        }

        /*if (recursive){
            //alert('is recursive');
            try {
                var recursive_models = [];
                for (var p in this._fields){
                    switch (this._fields[p].data_type){
                        case 'reference':
                            if (this['get_' + p]()){
                                //recursive_keys.push(this['get_' + p]().key().toString());
                                recursive_models.push(this['get_' + p]());
                            }
                            break;
                        case 'list':
                            if (this['get_' + p]()){
                                if (this._fields[p].list_type == 'reference'){
                                    var list = this['get_' + p]();
                                    for (var l=0;l<list.length;l++){
                                        recursive_models.push(list[l]);
                                    }
                                    //recursive_keys.push(this['get_' + p]().key().toString());
                                }
                            }
                            break;
                    }
                }
            }
            catch (e){
                return handle_exception(e, options);
            }

            try {
                recursive_data = this.multi_data(recursive_models);
            }
            catch (e){
                cogs.debug(e, 'caught exception with multi_data ' + e.message);
                throw e;
            }
        }

        if (options.async){
            try {
                this.load_data_async({
                    success: options.success,
                    error: options.error,
                    recursive: options.recursive,
                    recursive_data: recursive_data
                });
            }
            catch (e) {
                handle_exception(e, options);
            }
            return null;
        }*/

        //alert(recursive);
        if (!this._loaded){
            try {
                this.load_data();
            }
            catch (e){
                return handle_exception(e, options);
            }
        }
        
        //cogs.debug(this._values, 'this._values before data');
        
        var values = cogs.clone(this._values);
        var success_function = options.success;
        //var error_function = options.error;
        options.success = null;
        //options.error = null;
        // Load submodels
        for (var field_name in this._fields){
            if (values[field_name] && this._fields[field_name].data_type == 'submodel' && typeof values[field_name].data == 'function' && options.recursive){
                values[field_name] = values[field_name].data(options);
            }
            else if (values[field_name] && this._fields[field_name].data_type == 'reference' && typeof values[field_name].data == 'function' && options.recursive){
                values[field_name] = values[field_name].data(options);
            }
            else if (values[field_name] && this._fields[field_name].data_type == 'list' && this._fields[field_name].list_type == 'submodel' && $.isArray(values[field_name]) && options.recursive){
                for (var i=0;i<values[field_name].length;i++){
                    if (typeof values[field_name][i].data == 'function'){
                        values[field_name][i] = values[field_name][i].data(options);
                    }
                }
            }
            else if (values[field_name] && this._fields[field_name].data_type == 'list' && this._fields[field_name].list_type == 'reference' && $.isArray(values[field_name]) && options.recursive){
                for (var i=0;i<values[field_name].length;i++){
                    if (typeof values[field_name][i].data == 'function'){
                        values[field_name][i] = values[field_name][i].data(options);
                    }
                }
            }
        }
        //cogs.debug(this._values, 'this._values AFTER data');

        if (success_function && typeof success_function == 'function'){
            success_function(values, this);
        }

        values.id = this.id();
        values.key = this.key().toString();
        //cogs.debug('values in data()');
        //cogs.debug(this);
        //cogs.debug(this._values);
        return values;
    },
    multi_data: function (objects, options){
        options = options || {};
        try {
            var data = [];
            var model = null;

            var post_data = {};
            var keys = [];
            
            var store = cogs.get_store();
            if (store.name() != 'null'){
                var number_loaded = 0;
                for (var i=0;i<objects.length;i++){
                    var loaded = store.load_model(objects[i]);
                    if (loaded){
                        try {
                            var loaded_opts = {recursive:options.recursive};
                            data.push(loaded.data(loaded_opts));
                            number_loaded++;
                        }
                        catch (e){
                            cogs.debug(loaded, 'Error loading data in Model.multi_data', 'error');
                        }
                    }
                    else {
                        try {
                            data.push('');
                            if (typeof objects[i].key != 'function'){
                                cogs.debug(objects[i], 'key is not a function in Model.multi_data', 'error');
                            }
                            //cogs.debug(objects[i]);
                            keys.push(objects[i].key().toString());
                        }
                        catch (e){
                            cogs.debug(objects[i], 'Error loading object in Model.multi_data', 'error');
                        }
                    }
                }
                
                if (data.length == number_loaded){
                    if (options.success && typeof options.success == 'function'){
                        try {
                            options.success(data, options.data);
                        }
                        catch (e){
                            cogs.debug(data, 'Error calling success function of multi_data', 'error');
                            cogs.debug(options.success);
                        }
                    }

                    return data;
                }
            }

            if (keys.length == 0){
                //cogs.debug(data, 'Model data from cache');
                return data;
            }

            post_data.method = 'Model.multi_data';
            post_data.output = 'xml';
            post_data.params = {keys:keys};

            //cogs.debug(xml);

            var xhr = cogs.api_json(post_data, {
                type: 'POST',
                async: false
            });

        }
        catch (e){
            cogs.debug(objects, 'ERROR in multi_data load', 'error');
            cogs.trace();
            throw e;
        }

        try {
            //cogs.debug(xhr.responseText);
            //return false;

            var response_xml = xhr.responseXML;
            if (!response_xml){
                // sometimes it is notparsed, we have to try to reparse it
                try {
                    response_xml = new ActiveXObject('Microsoft.XMLDOM');
                    response_xml.async = 'false';
                    response_xml.loadXML(xhr.responseText);
                }
                catch (e){
                    response_xml = new DOMParser().parseFromString(xhr.responseText, 'text/xml');
                }
            }

            if (response_xml){
                //cogs.debug(xhr.responseXML);
                var entities = response_xml.getElementsByTagName('entity');
                for (var e=0;e<entities.length;e++){
                    model = cogs.db.get(new cogs.db.Key(entities[e].getAttribute('key')));
                    //returned_keys.push(new cogs.db.Key(entities[e].getAttribute('key')));
                    model.load_from_xml(entities[e]);
                    //cogs.debug(model._loaded);
                    
                    for (var d=0;d<data.length;d++){
                        if (data[d] == ''){
                            data[d] = model.data(options);
                            //cogs.debug(data[d], 'Model data');
                            break;
                        }
                    }
                }

                //var returned_models = cogs.db.get(returned_keys);
            }
            else {
                throw new cogs.error.StoreError('Unable to parse server response in Model.multi_data');// + xhr.responseText);
            }
        }
        catch (e){
            cogs.debug('ERROR in multi_data parse');
            if (options.error && typeof options.error == 'function'){
                options.error(e);
            }
            else {
                throw e;
            }
        }

        if (options.success && typeof options.success == 'function'){
            try {
                options.success(data, options.data);
            }
            catch (e){
                cogs.debug(data, 'Error in Model.multi_data success function');
                cogs.debug(e);
                throw e;
            }
        }
                    
        return data;
    },
    _multi_data_message: function(event){
        cogs.debug('Received message');
        cogs.debug(event.data);
        var response_ref = event.data.ref;
        var callback_data = Model_multi_data_callbacks[response_ref];

        //event.data.options.success(event.data, callback_data.data);
    },
    /*multi_data_worker: function(objects, options){
        var url = this.api_url('json');
        var current_ref = Model_multi_data_callbacks.length;

        Model_multi_data_callbacks.push(options);

        var worker = new Worker('/jsworker/model_data_loader.js');
        worker.onmessage = this._multi_data_message;
        worker.onerror = this._multi_data_error;

        var message = {};
        message.ref = current_ref;
        message.keys = [];
        //message.options = options;
        message.url = url;

        cogs.debug(worker);


        for (var i in objects){
            message.keys.push(objects[i].key().toString());
        }
        cogs.debug(message);

        worker.postMessage(message);
    },*/
    multi_data_async: function(objects, options){
        return new Model().multi_data(objects, options);
        
        options = options || {};
        var post_data = {};
        var keys = [];

        for (var i in objects){
            if (objects[i] && typeof objects[i].key == 'function'){
                keys.push(objects[i].key().toString());
            }
        }

        if (keys.length == 0){
            if (typeof options.success == 'function'){
                options.success([], options.data);
            }
        }
        else {
            var current_ref = Model_multi_data_callbacks.length;
            
            post_data.method = 'Model.multi_data';
            post_data.params = {keys:keys, ref:current_ref};
            
            Model_multi_data_callbacks.push(options);

            cogs.api_json(post_data, {
                type: 'POST',
                async: true,
                success: function(resp_data, status, xhr) {
                    //cogs.debug('Multi data complete!');
                    //cogs.debug(xhr.responseXML);
                    try {
                        var data = [];
                        var response_ref = xhr.getResponseHeader('X-Cogs-Ref');
                        var content_type = xhr.getResponseHeader('Content-Type');

                        if (content_type.substr(0, 16) == 'application/json'){
                            var response_obj = JSON.parse(xhr.responseText);
                            response_obj = response_obj._return;
                            //cogs.debug(response_obj, 'Multi_data_async response');
                            if (!response_obj || response_obj.success == false){
                                if (typeof callback_data.error == 'function'){
                                    callback_data.error();
                                }
                            }
                        }
                        //var response_ref = xhr.responseXML.firstChild.getAttribute('ref');
                        var callback_data = Model_multi_data_callbacks[response_ref];

                        var iter = callback_data._iter;
                        for (var i=0;i<iter.length;i++){
                            var key_str = iter[i].key().toString();
                            //var raw_xml_element = $('entity[key="'+key_str+'"]', xhr.responseXML)[0];
                            for (var r=0;r<response_obj.data.length;r++){
                                if (response_obj.data[r].key == key_str){
                                    //cogs.debug(iter[i]._loaded, 'model before load');
                                    try {
                                        iter[i] = iter[i].load_from_json(response_obj.data[r]);
                                        //cogs.debug(iter[i], 'model after load');
                                        //cogs.debug(response_obj.data[r], 'data passed to load_from_json');
                                        data.push(iter[i].data());
                                    }
                                    catch (e){
                                        cogs.debug('Load from json threw error - ' + e.message);
                                        throw e;
                                    }
                                }
                            }
                        }

                        //cogs.debug('multi_data_complete callback data');
                        //cogs.debug(callback_data);
                        if (typeof callback_data.success == 'function'){
                            callback_data.success(data, callback_data.data);
                        }
                        else {
                            cogs.debug('Could not call callback in Model.multi_data_complete');
                        }
                    }
                    catch (e){
                        cogs.debug('Multi_data exception! - ' + e.message);
                        cogs.debug(e);
                        cogs.error_handler(e);
                    }
                },
                error: function(xhr, text_status, error){
                    cogs.debug('Multi data error! ');
                    cogs.debug(xhr);
                    cogs.debug(text_status);
                    cogs.debug(error);
                }
            });
        }
    }
});

/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* modellist.js */

cogs = cogs || {};
cogs.db = cogs.db || {};

cogs.db.ModelList = Class.extend({
    _models: [],
    init: function(models){
        this._models = models;
    },
    put: function(options){
        var obj_xml = '';
        var kind = '';
        
        for (var m=0;m<this._models.length;m++){
            var obj = this._models[m];
            kind = obj.kind();
            
            Controller.call_plugins(obj.kind()+':put_pre', obj);
            obj_xml += obj.to_put_xml();
            
            obj.delete_local();
        }

        if (obj_xml && kind){
            var post_xml = '<request output="json">';
            post_xml += '<method>Model.put</method>';
            post_xml += '<params>';
            post_xml += '<param name="class">'+kind+'</param>';
            post_xml += '<param name="model" type="xml"><entities>' + obj_xml + '</entities></param>';
            post_xml += '</params>';
            post_xml += '</request>';

            cogs.debug(post_xml);
            
            var xhr_options = {};
            
            var do_put = function(){
                try {
                    cogs.api_xml(post_xml, xhr_options);
                }
                catch (e){
                    if (typeof options.error == 'function'){
                        options.error(e, null);
                    }
                    else {
                        cogs.debug('Exception in ModelList.put');
                        cogs.debug(e);
                        throw e;
                    }
                }
            }
            window.setTimeout(do_put, 50);
        }
    }
});

/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */


var Expando = Model.extend({
    is_expando: function (){
        return true;
    },
    kind: function() {
        var k = this.key();
        if (k && k.kind()){
            return k.kind();
        }
        else {
            return this._dbname;
        }
    }
});


/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */


cogs.db.Paginator = Class.extend({
    _page_size: 20,
    _page_number: 1,
    _max_pages: 100,
    _query: null,
    iterator: function(){
        return this._query.iterator();
    },
    init: function(query){
        this._query = query;
    },
    _recalc: function(){
        if (this._query){
            this._query.limit(this._page_size);

            var offset = (this._page_number -1) * this._page_size;
            this._query.offset(offset);
        }
    },
    page_size: function(page_size){
        if (typeof page_size != 'undefined'){
            page_size = (page_size < 1)?1:page_size;
            this._page_size = page_size;

            this._recalc();
        }
        else {
            page_size = this._page_size;
        }

        return page_size;
    },
    page: function(page_number){
        if (typeof page_number != 'undefined'){
            page_number = parseInt(page_number);
            //cogs.debug('Setting page to ' + page_number);
            page_number = (page_number < 1)?1:page_number;
            this._page_number = page_number;
            
            this._recalc();
        }
        else {
            page_number = this._page_number;
        }

        return page_number;
    },
    page_invalid: function(){
        return (this._page_number > this._max_pages);
    },
    data: function(){
        var count = 0;
        if (this._query){
            count = this._query.count();
            //pre($count);
            this._max_pages = Math.ceil(count / this._page_size);
        }
        else {
            this._max_pages = 1;
        }
        
        var data = {
            'current_page' : this._page_number,
            'max_pages' : this._max_pages,
            'item_count': count,
            'item_start': ((this._page_number - 1) * this._page_size) + 1,
            'item_end': ((this._page_number - 1) * this._page_size) + this._page_size,
            'page_size': this._page_size
        };

        if (data.item_end > data.item_count){
            data.item_end = data.item_count;
        }

        return data;
    },
    query_data: function(options){
        if (this._query){
            return this._query.data(options);
        }
        else {
            return [];
        }
    }
});

/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* property.js */

/* Extend the date object so that it can parse iso */
function parse_iso(iso_str){
    var iso_arr = iso_str.split(' ');
    var date = null;
    var time = null;

    if (iso_arr.length == 2){
        date = iso_arr[0];
        time = iso_arr[1];
    }
    else {
        if (iso_str.indexOf('-') > 0){
            /* date */
            date = iso_str;
        }
        else if (iso_str.indexOf(':') > -1){
            /* time */
            time = iso_str;
        }
    }

    if (date){
        var date_arr = date.split('-');
    }
    if (time){
        var time_arr = time.split(':');
    }

    var date_obj = new Date();

    if (date){
        // deduct 1 from month
        var int_month = parseInt(date_arr[1], 10) -1; /* 10 here is to signify base 10 so it deducts leading 0s */
        date_obj.setUTCFullYear(date_arr[0], int_month, date_arr[2]);
    }
    else {
        date_obj.setUTCFullYear(0, 0, 0);
    }


    if (time){
        date_obj.setHours(parseInt(time_arr[0], 10), parseInt(time_arr[1], 10), parseInt(time_arr[2], 10), 0);
    }
    else {
        date_obj.setHours(0, 0, 0, 0);
    }

    return date_obj;
}

function leading_zero(val){
    var intval = parseInt(val, 10);
    if (intval < 10){
        return '0'+intval;
    }
    return val;
}



var Property = Class.extend({
    init: function(data){
        //this._super(data);
        if (typeof data == 'undefined'){
            data = {encrypted: false};
        }
        this._regex = /.*/;
        this.data_type = 'string';
        this.encrypted = data.encrypted;
    },
    get_value_for_datastore: function(model_instance){
        fields = model_instance._fields;
        //		$datastore_value = UnhandledDatastoreType;
        return fields[this._property_name];
    },
    make_value_from_datastore: function(value){
        return value;
    },
    value_to_xml: function(value, domdoc){
        var data_type = this.data_type;
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
            value = '';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);

        var content = null;
        if (data_type == 'int'){
            content = domdoc.createTextNode(parseInt(value));
        }
        if (data_type == 'float'){
            content = domdoc.createTextNode(parseFloat(value));
        }
        else {
            content = domdoc.createCDATASection(value);
        }

        if (content){
            xml_node.appendChild(content);
        }

        return xml_node;
    },
    xml_to_value: function(node){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof node == 'undefined' || node == null){
            return '';
        }
        if (node.firstChild){
            return node.firstChild.nodeValue;
        }
        return '';
    },
    json_to_value: function(json_obj){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof json_obj == 'undefined' || json_obj == null){
            return null;
        }

        return json_obj;
    },
    value_to_json: function(val){
        if (typeof val == 'undefined' || val == null){
            return null;
        }

        return val;
    },
    type_to_property: function(type, name){
        var new_property = null;

        switch (type){
            case 'blob':
                new_property = new BlobProperty({name:name});
                break;
            case 'bool':
                new_property = new BooleanProperty({name:name});
                break;
            case 'date':
                new_property = new DateProperty({name:name});
                break;
            case 'datetime':
                new_property = new DateTimeProperty({name:name});
                break;
            case 'email':
                new_property = new EmailProperty({name:name});
                break;
            case 'float':
                new_property = new FloatProperty({name:name});
                break;
            case 'geopt':
                new_property = new GeoPtProperty({name:name});
                break;
            case 'im':
                new_property = new IMProperty({name:name});
                break;
            case 'int':
                new_property = new IntegerProperty({name:name});
                break;
            case 'json':
                new_property = new JsonProperty({name:name});
                break;
            case 'link':
                new_property = new LinkProperty({name:name});
                break;
            case 'list':
                new_property = new ListProperty({name:name});
                break;
            case 'phonenumber':
                new_property = new PhoneNumberProperty({name:name});
                break;
            case 'query':
                new_property = new QueryProperty({name:name});
                break;
            case 'rating':
                new_property = new RatingProperty({name:name});
                break;
            case 'reference':
            case 'model':
                new_property = new ReferenceProperty({name:name});
                break;
            case 'key':
                new_property = new KeyProperty({name:name});
                break;
            case 'string':
                new_property = new StringProperty({name:name});
                break;
            case 'submodel':
                new_property = new SubModelProperty({name:name});
                break;
            case 'text':
                new_property = new TextProperty({name:name});
                break;
            case 'time':
                new_property = new TimeProperty({name:name});
                break;
            case 'translatablestring':
                new_property = new TranslatableStringProperty({name:name});
                break;
            case 'translatabletext':
                new_property = new TranslatableTextProperty({name:name});
                break;
            case 'xml':
                new_property = new XmlProperty({name:name});
                break;
            case 'null':
                new_property = new NullProperty({name:name});
                break;
            default:
                break;
        }

        return new_property;
    }
});

var StringProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'string';
        this.property_name = data.name;
    }
});

var TranslatableStringProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'translatablestring';
        this.property_name = data.name;
    }
});

var NullProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'null';
        this.property_name = data.name;
    }
});

var TextProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'text';
        this.property_name = data.name;
    }
});

var XmlProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'xml';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof node == 'undefined' || node == null){
            return '';
        }
        if (node.firstChild){
            return Base64.decode(node.firstChild.nodeValue);
        }
        return '';
    },
    value_to_xml: function(value, domdoc){
        var data_type = this.data_type;
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);

        if (data_type != 'null') {
            xml_node.appendChild(domdoc.createCDATASection(Base64.encode(value)));
        }

        return xml_node;
    }
});

var TranslatableTextProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'translatabletext';
        this.property_name = data.name;
    }
});


var IntegerProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'int';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof node == 'undefined' || node == null){
            return '';
        }
        if (node.firstChild){
            return parseInt(node.firstChild.nodeValue);
        }
        return '';
    }
});

var RatingProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'rating';
        this.property_name = data.name;
    }
});

var FloatProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'float';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof node == 'undefined' || node == null){
            return '';
        }
        if (node.firstChild){
            return parseFloat(node.firstChild.nodeValue);
        }
        return '';
    }
});

var BooleanProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'bool';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        if (typeof node == 'undefined' || node == null){
            return false;
        }
        if (node.firstChild && node.firstChild.nodeValue){
            //cogs.debug(node.firstChild.nodeValue);
            return (node.firstChild.nodeValue.toLowerCase()=='true')?true:false;
        }
        return false;
    }
});

var GeoPtProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'geopt';
        this.property_name = data.name;
    },
    xml_to_value: function(value){
        for (var c=0;c<value.childNodes.length;c++){
            if (value.childNodes[c].tagName == 'georss:point'){
                var geopt = {};
                var raw_loc = value.childNodes[c].firstChild.nodeValue;
                if (raw_loc){
                    var raw_loc_arr = raw_loc.split(' ');
                    if (raw_loc_arr.length == 2){
                        geopt.latitude = parseFloat(raw_loc_arr[0]);
                        geopt.longitude = parseFloat(raw_loc_arr[1]);

                        return geopt;
                    }
                }
            }
        }
        return null;
    }
});

var EmailProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'email';
        this.property_name = data.name;
    },
    xml_to_value: function(value){
        for (var c=0;c<value.childNodes.length;c++){
            if (value.childNodes[c].tagName == 'gd:email'){
                // need to entity decode
                var email_encoded = value.childNodes[c].getAttribute('address');
                var email_decoded = email_encoded.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
                return email_decoded;
            }
        }
        return null;
    }
    // TODO : value to xml is needed
});

var IMProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'im';
        this.property_name = data.name;
    }
});

var PhoneNumberProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'phonenumber';
        this.property_name = data.name;
    },
    xml_to_value: function(value){
        var phone_sub_node = value.getElementsByTagName('phoneNumber');
        if (phone_sub_node.length == 1 && phone_sub_node[0].firstChild){
            return new cogs.db.PhoneNumber(phone_sub_node[0].firstChild.nodeValue);
        }
        return null;
    },
    value_to_xml: function(value, domdoc){
        var data_type = this.data_type;
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);

        if (data_type != 'null') {
            var phone_node = domdoc.createElement('phoneNumber');
            phone_node.appendChild(domdoc.createCDATASection(value));

            xml_node.appendChild(phone_node);
            //xmlvalue = '<phoneNumber><![CDATA[' + value + ']]></phoneNumber>';
        }

        return xml_node;
    },
    json_to_value: function(json_obj){
        var val = null;
        if (json_obj){
            val = new cogs.db.PhoneNumber(json_obj.number, json_obj.extension);
        }
        return val;
    },
    value_to_json: function(val){
        var json_obj = null;
        if (val){
            json_obj = {number:val.number, extension:val.extension};
        }
        return json_obj;
    }
});

var LinkProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'link';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        //cogs.debug(node);
        //return value;
        if (typeof node.getAttribute != 'function'){
            return '';
        }
        else {
            var href = '';
            for (var lc=0;lc<node.childNodes.length;lc++){
                if (node.childNodes[lc].tagName == 'link'){
                    href = node.childNodes[lc].getAttribute('href');
                    break;
                }
            }
            return unescape(href);
        }
    },
    value_to_xml: function(value, domdoc){
        //var xml = '';
        //var xmlvalue = '<link href="" />';

        var data_type = this.data_type;
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);


        if (data_type != 'null') {
            var link_node = domdoc.createElement('link');
            link_node.setAttribute('href', value);

            xml_node.appendChild(link_node);
            //xmlvalue = '<link href="'+value+'" />';
        }

        return xml_node;
    }
});

var BlobProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'blob';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        var str = '';
        var c = 0, b = null;
        //cogs.debug('BLOB!');
        //cogs.debug(node, 'BLOB!');
        if (node.firstChild){
            /* Firefox and maybe other browsers truncate nodeValue to 4096 and create
             * new nodes for the difference */
            var node_str = node.firstChild.nodeValue;
            if (node_str){
                str += node_str;
                while (node_str.length == 4096 && node.childNodes[c+1]){
                    c++;
                    node_str = node.childNodes[c].nodeValue;
                    str += node_str;
                }
            }
            //cogs.debug(str);
            if (str){
                var a = str.split(':');
                //cogs.debug(a[0]);
                var b = new cogs.db.Blob(a[1], a[0]);
                //cogs.debug(b.get_data_url());
                //cogs.debug(b);
                //return b;
            }
        }
        
        if (node.getAttribute('ref')){
            if (b){
                b.ref = node.getAttribute('ref');
            }
            else {
                b = new cogs.db.Blob().from_ref(node.getAttribute('ref'));
            }
        }

        return b;
    },
    json_to_value: function(json_obj){
        var b = null;
        if (json_obj){
            //cogs.debug(json_obj, 'Loading blob from this');
            if (json_obj.mime && json_obj.data){
                b = new cogs.db.Blob(json_obj.data, json_obj.mime);
            }
            if (json_obj.ref){
                if (b){
                    b.ref = json_obj.ref;
                }
                else {
                    b = new cogs.db.Blob().from_ref(json_obj.ref);
                }
            }
        }
        
        return b;
    },
    value_to_json: function(val){
        var json_obj = null;
        if (val){
            return {ref: val.ref};
        }
        
        return json_obj;
    }
});

var ReferenceProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'reference';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        //cogs.debug('key from xml');
        var str = null;
        var obj = null;
        if (node && node.firstChild && node.getAttribute('type') == 'model'){
            var kind = node.firstChild.getAttribute('kind');
            var global_class = cogs.global_class(kind);
            if (kind && global_class){
                obj = new global_class();
                var new_node = node.ownerDocument.createElement('entity');
                new_node.setAttribute('kind', node.getAttribute('kind'));
                new_node.setAttribute('language', node.getAttribute('language'));
                new_node.setAttribute('key', node.getAttribute('key'));
                for (var n=0;n<node.childNodes.length;n++){
                    new_node.appendChild(node.childNodes[n].cloneNode(true));
                }
                obj.load_from_xml(new_node);
            }
        }
        else if (node.firstChild){
            str = node.firstChild.nodeValue;
            var key = new cogs.db.Key().from_full(str);
            //cogs.debug(key);
            //cogs.debug('before get');
            if (key){
                obj = cogs.db.get(key);
            }
        }

        return obj;
    },
    value_to_xml: function(value, domdoc){
        var xmlvalue = null;
        var xml_node = null;
        //cogs.debug('reference');
        try {
            //cogs.debug('tag is ' + tag);
            var data_type = 'null';

            if ((typeof value != 'undefined') && value){
                cogs.debug(value, 'Property value ');
                var tag = value.tag();

                if (tag == null) {
                    /* A new unsaved object */
                    data_type = 'entity';
                    xmlvalue = value.output('xmldoc');
                    if (xmlvalue){
                        xmlvalue = domdoc.importNode(xmlvalue.firstChild, true);
                    }
                }
                else {
                    data_type = this.data_type;
                    xmlvalue = domdoc.createCDATASection(tag);
                }
            }

            xml_node = domdoc.createElement('property');
            xml_node.setAttribute('name', this.property_name);
            xml_node.setAttribute('type', data_type);
            if (xmlvalue){
                xml_node.appendChild(xmlvalue);
            }


            //xml = '<property name="'+this.property_name+'" type="'+data_type+'">';
            //xml += xmlvalue;
            //xml += '</property>';
        }
        catch (e){
            //xml = '<property name="'+this.property_name+'" type="null"></property>';

            cogs.debug('Error in ReferenceProperty.value_to_xml()');
            cogs.error_handler(e);
            cogs.debug('Value was');
            cogs.debug(value);
        }

        return xml_node;
    },
    json_to_value: function(json_obj){
        try {
            if (json_obj && json_obj.key){
                //cogs.debug(json_obj, 'json_to_value for reference');
                return cogs.db.get(new cogs.db.Key().from_encoded(json_obj.key));
            }
        }
        catch (e){
            cogs.debug(json_obj, 'Error in ReferenceProperty.json_to_value', 'error');
        }

        return null;
    },
    value_to_json: function(val){
        var json_obj = null;
        try {
            if (val){
                json_obj = {
                    key: val.key().toString(), 
                    kind:val.key().kind().replace(/\\/g, '.'), 
                    is_model:true, 
                    language: val._language, 
                    id:val.key().id(),
                    tag: val.tag().replace(/\\/g, '.')
                };
            }
        }
        catch (e){
            cogs.debug(val, 'ReferenceProperty value');
            var msg = 'ReferenceProperty.value_to_json - ' + e.message;
            throw new cogs.error.Error(msg);
        }
        
        return json_obj;
    }
});

var KeyProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'reference';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        if (typeof node == 'undefined' || node == null){
            return '';
        }
        if (node.firstChild && node.firstChild.nodeValue){
            return new cogs.db.Key().from_encoded(node.firstChild.nodeValue);
        }
        return '';
    },
    value_to_xml: function(value, domdoc){
        var data_type = 'key';
        if ((typeof value == 'undefined') || !value){
            data_type = 'null';
            value = '';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);

        var content = domdoc.createCDATASection(value.toString());

        if (content){
            xml_node.appendChild(content);
        }

        return xml_node;
    },
    json_to_value: function(json_obj){
        var value = null;
        if (json_obj){
            value = new cogs.db.Key().from_encoded(json_obj);
        }
        
        return value;
    },
    value_to_json: function(val){
        var json_obj = null;
        if (val){
            json_obj = val.toString();
        }
        
        return json_obj;
    }
});

var SubModelProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'submodel';
        this.property_name = data.name;
    },
    xml_to_value: function(node){
        //cogs.debug('submodel from xml');
        var obj = {};
        var properties = node.getElementsByTagName('subproperty');
//        cogs.debug(properties);

        for (var p=0;p<properties.length;p++){
            var property = properties[p];
            var name = property.getAttribute('name');
            var type = property.getAttribute('type');

            var new_property = new Property().type_to_property(type, name);

            if (new_property){
                obj[name] = new_property.xml_to_value(property);
            }
            else {
                cogs.debug('Unhandled property type ' + type + ' in subproperty');
                obj[name] = property.nodeValue;
            }
        }

        var kind = node.getAttribute('kind');
        //cogs.debug('KIND = ' + kind);
        //cogs.debug(node);

        if (kind){
            return eval('new ' + kind + '(obj)');
        }

        return null;
    },
    value_to_xml: function(value, domdoc){
        //var xmlvalue = '';
        var data_type = this.data_type;
        if ((typeof value == 'undefined') || !value){
            data_type = 'null';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);

        //var xml = '<property name="'+this.property_name+'" type="'+data_type+'">';
        //cogs.debug('reference');
        //cogs.debug(value._fields);
        try {
            if (value){
                var data = value.data();
                //cogs.debug(data);

                for (var field_name in value._fields){
                    //cogs.debug('subproperty ' + key);
                    /*var field_type = value._fields[field_name].data_type;

                    var subproperty = new Property().type_to_property(field_type, field_name);*/

                    var field_value = data[field_name];
                    var subproperty = value._fields[field_name];
                    var field_type = subproperty.data_type;
                    
                    if ((typeof field_value == 'undefined') || !field_value){
                        field_type = 'null';
                        field_value = '';
                    }


                    var subproperty_node = domdoc.createElement('subproperty');
                    subproperty_node.setAttribute('name', field_name);
                    subproperty_node.setAttribute('type', field_type);

                    var property_node = subproperty.value_to_xml(field_value, domdoc);
                    for (var s=0;s<property_node.childNodes.length;s++){
                        subproperty_node.appendChild(property_node.childNodes[s].cloneNode(true));
                    }

                    //cogs.debug(subproperty_node);
                    xml_node.appendChild(subproperty_node.cloneNode(true));
                    //cogs.debug(xml_node);
                    //cogs.debug(xml_node.childNodes.length);
                    //xml += '<subproperty name="'+key+'" type="'+field_type+'">'+field_value+'</subproperty>';
                }
            }
        }
        catch (e){
            //xml = '<property name="'+this.property_name+'" type="null"></property>';

            cogs.debug('Error in SubModelProperty.value_to_xml()');
            cogs.debug(value, 'Value was');
            cogs.error_handler(e);
        }
        //xml += '</property>';

        return xml_node;
    },
    json_to_value: function(json_obj){
        var submodel = null;

        if (json_obj && json_obj.is_model && json_obj.kind){
            var global_class = cogs.global_class(json_obj.kind);
            if (global_class){
                submodel = new global_class();
                for (var f in submodel._fields){
                    var property_name = submodel._fields[f].property_name;
                    var value = cogs.clone(json_obj[property_name]);
                    submodel['set_' + property_name](submodel._fields[f].json_to_value(value));
                }
            }
        }
        
        //cogs.debug(submodel, 'Loaded submodel');

        return submodel;
    },
    value_to_json: function(val){
        var json_obj = null;
        
        try {
            if (val){
                json_obj = {};
                for (var f in val._fields){
                    var property_name = val._fields[f].property_name;
                    var value = cogs.clone(val._values[f]);
                    try {
                        json_obj[property_name] = val._fields[f].value_to_json(value);
                    }
                    catch (e){
                        cogs.debug(json_obj, 'Error processing SubModelProperty.value_to_json field = ' + f + ' - ' + e.message, 'error');
                        throw e;
                    }
                }
                json_obj.kind = val._kind;
                json_obj.is_model = true;
                json_obj.is_submodel = true;
            }
        }
        catch (e){
            var msg = 'SubModelProperty.value_to_json - ' + e.message;
            throw new cogs.error.Error(msg);
        }
        
        return json_obj;
    }
});

var JsonProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'json';
        this.property_name = data.name;
    },
    get_value_for_datastore: function(model_instance){
        fields = model_instance._fields;
        return JSON.stringify(fields[this._property_name]);
    },
    make_value_from_datastore: function(value){
        try {
            return JSON.parse(value);
        }
        catch (e){
            cogs.error_handler(e);
            cogs.debug('error parsing ' + value);
        }
        return null;
    },
    xml_to_value: function(node){
        //cogs.debug('value is ' + node.firstChild.nodeValue);
        try {
            if (node.firstChild && node.firstChild.nodeValue != ''){
                return JSON.parse(node.firstChild.nodeValue);
            }
        }
        catch (e){
            cogs.error_handler(e);
            cogs.debug('error parsing ' + node.firstChild.nodeValue);
        }
        return null;
    },
    value_to_xml: function(value, domdoc){
        var data_type = this.data_type;
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
            value = '';
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);
        xml_node.appendChild(domdoc.createCDATASection(JSON.stringify(value)));

        //xml = '<property name="'+this.property_name+'" type="'+data_type+'">';
        //xml += '<![CDATA[' + JSON.stringify(value) + ']]>';
        //xml += '</property>';

        return xml_node;
    }
});

var ListProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'list';
        this.list_type = data.list_type;
        this.property_name = data.name;
        this.list_options = data.list_options;
    },
    _child_property: function(){
        var property = null;
        var item_name = this.property_name;
        var item_type = this.list_type;

        //var property = new Property().type_to_property(item_type, item_name);

        switch (item_type){
            case 'string':
                property = new StringProperty({name:item_name});
                break;
            case 'int':
                property = new IntegerProperty({name:item_name});
                break;
            case 'float':
                property = new FloatProperty({name:item_name});
                break;
            case 'reference':
                property = new ReferenceProperty({name:item_name, 'class':this.list_options['class']});
                break;
            case 'submodel':
                property = new SubModelProperty({name:item_name, 'class':this.list_options['class']});
                break;
            case 'blob':
                property = new BlobProperty({name:item_name});
                break;
            default:
                cogs.debug(item_type);
                cogs.debug('Unhandled sub property type "' + item_type + '" for ' + item_name);
                break;
        }

        return property;
    },
    xml_to_value: function(node){
        //cogs.debug('xml to value for list');
        //cogs.debug(node);
        try {
            var items = node.getElementsByTagName('item');
            var list = [];
            var parsed_value = null;
            var item_name = $(node).attr('name');
            //var item_type = $(node).attr('type');

            //var property = new Property().type_to_property(item_type, item_name);

            if (items.length == 0){
                return list;
            }

            switch ($(items[0]).attr('type')){
                case 'string':
                    var property = new StringProperty({name:item_name});
                    break;
                case 'int':
                    var property = new IntegerProperty({name:item_name});
                    break;
                case 'float':
                    var property = new FloatProperty({name:item_name});
                    break;
                case 'reference':
                    var property = new ReferenceProperty({name:item_name, 'class':this.list_options['class']});
                    break;
                case 'submodel':
                    var property = new SubModelProperty({name:item_name, 'class':this.list_options['class']});
                    break;
                case 'blob':
                    var property = new BlobProperty({name:item_name});
                    break;
                case 'null':
                case null:
                    return list;
                    break;
                default:
                    cogs.debug(node);
                    cogs.debug('Unhandled sub property type "' + $(items[0]).attr('type') + '" for ' + $(items[0]).attr('name'));
                    return list;
                    break;
            }

            try {
                for (var i=0;i<items.length;i++){
                    var item = items[i];
                    //cogs.debug(item);
                    // create a node called property instead of item
                    var new_node = node.ownerDocument.createElement('property');
                    var name_attr = item.getAttribute('name');
                    if (name_attr){
                        new_node.setAttribute('name', name_attr);
                    }
                    var type_attr = item.getAttribute('type');
                    if (type_attr){
                        new_node.setAttribute('type', type_attr);
                    }
                    var kind_attr = item.getAttribute('kind');
                    if (kind_attr){
                        new_node.setAttribute('kind', kind_attr);
                    }
                    while (item.childNodes.length){
                        var child = item.removeChild(item.childNodes[0]);
                        new_node.appendChild(child.cloneNode(true));
                    }
                    //cogs.debug(new_node);
                    if (property){
                        try {
                            parsed_value = property.xml_to_value(new_node);
                        }
                        catch (e){
                            cogs.debug('Error in parsing cloned node in ListProperty');
                            cogs.debug(e.message);
                            throw e;
                        }
                        //cogs.debug(parsed_value);
                        list.push(parsed_value);
                    }
                }
            }
            catch (e){
                cogs.debug('Error in cloning node in ListProperty');
                cogs.debug(e.message);
                throw e;
            }
        }
        catch (e){
            cogs.debug('Error in ListProperty.xml_to_value()');
            cogs.error_handler(e);
            cogs.debug('node was');
            cogs.debug(node);
        }
        //cogs.debug(list);
        return list;
        //return JSON.parse(str);
    },
    value_to_xml: function(value, domdoc){
        //cogs.debug('list value to xml');
        //cogs.debug(value);
        var data_type = this.data_type;
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
            value = '';
        }
        var list_node = domdoc.createElement('property');
        list_node.setAttribute('name', this.property_name);
        list_node.setAttribute('type', data_type);

        //var xml = '<property name="'+this.property_name+'" type="'+data_type+'">';
        var child_property_type = this.list_type;
        var child_class = '';
        //cogs.debug(this);
        switch (child_property_type){
            case 'string':
                child_class = 'StringProperty';
                child_property_type = 'string';
                break;
            case 'integer':
                child_class = 'IntegerProperty';
                child_property_type = 'integer';
                break;
            case 'float':
                child_class = 'FloatProperty';
                child_property_type = 'float';
                break;
            case 'list':
                child_class = 'ListProperty';
                child_property_type = 'list';
                break;
            case 'reference':
                child_class = 'ReferenceProperty';
                child_property_type = 'reference';
                break;
            case 'submodel':
                child_class = 'SubModelProperty';
                child_property_type = 'submodel';
                break;
            case 'blob':
                child_class = 'BlobProperty';
                child_property_type = 'blob';
                break;
            default:
                cogs.debug('UNHANDLED list type ' + child_property_type);
                break;
        }

        try {
            var child_property = eval('new ' + child_class + '({name:"'+this.property_name+'"});');
        }
        catch (e){
            cogs.error_handler(e);
        }
        //cogs.debug('Child property of list');
        //cogs.debug(child_property.value_to_xml);


        var item_nodes = [];
        
        for (var i in value){
            /* TODO - Make this handle all types of list */
            child_property.property_name = this.property_name + '['+i+']';
            //cogs.debug(child_property);
            //cogs.debug('converting ' + this.property_name + ' ['+i+']');
            //cogs.debug(value);
            var xmlval = child_property.value_to_xml(value[i], domdoc);

            if (xmlval){
                // migrate from property tag to item tag
                item_nodes.push(domdoc.createElement('item'));
                for (var a=0;a<xmlval.attributes.length;a++){
                    item_nodes[i].setAttribute(xmlval.attributes[a].name, xmlval.attributes[a].value);
                }
                for (var c=0;c<xmlval.childNodes.length;c++){
                    item_nodes[i].appendChild(xmlval.childNodes[c].cloneNode(true));
                }
            }
            //var xmlval = value[i];
            //cogs.debug(xmlval);
            //xml += xmlval.replace('<property', '<item').replace('</property', '</item');
        }
        //xml += '</property>';

        for (var n=0;n<item_nodes.length;n++){
            list_node.appendChild(item_nodes[n]);
        }

        return list_node.cloneNode(true);
    },
    json_to_value: function(json_obj){
        try {
            var items = json_obj;
            if (!items){
                items = [];
            }
            var list = [];
            var parsed_value = null;
            
            var property = this._child_property();

            for (var i=0;i<items.length;i++){
                var item = items[i];
                //cogs.debug(item);
                if (property){
                    parsed_value = property.json_to_value(item);
                    //cogs.debug(parsed_value);
                    list.push(parsed_value);
                }
            }
        }
        catch (e){
            cogs.debug('Error in ListProperty.json_to_value()');
            cogs.error_handler(e);
            cogs.debug('json_obj was');
            cogs.debug(json_obj);
        }
        //cogs.debug(json_obj, 'List json to value');
        //cogs.debug(list, 'List json to value VALUE');

        return list;

    },
    value_to_json: function(val){
        var json_obj = [];
        
        if (val){
            var property = this._child_property();
            
            if (property){
                for (var i=0;i<val.length;i++){
                    json_obj.push(property.value_to_json(val[i]));
                }
            }
        }
        //cogs.debug(json_obj, 'List value to json');
        
        return json_obj;
    }
});

var DateTimeProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'datetime';
        this.property_name = data.name;
    },
    value_to_xml: function(value, domdoc){
        //cogs.debug(value + '=' + (typeof value));
        //cogs.debug(value);

        var data_type = this.data_type;
        var value_str = '';

        if ((typeof value == 'undefined') || (value == null) || !value){
            data_type = 'null';
        }
        else {
            if (this.data_type.indexOf('date') > -1){
                //cogs.debug(value);
                value_str = value.format('Y-m-d');
                if (this.data_type.indexOf('time') > -1){
                    value_str += ' ';
                }
            }
            if (this.data_type.indexOf('time') > -1){
                value_str += value.format('H:i:s');
            }
        }

        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);
        xml_node.appendChild(domdoc.createTextNode(value_str));

        //var xml = '<property name="'+this.property_name+'" type="'+data_type+'">';
        //xml += value_str;
        //xml += '</property>';

        //cogs.debug(value_str);

        //this.gggg();

        return xml_node;
    },
    xml_to_value: function(node){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        //cogs.debug('datefrmat');
        if (typeof node == 'undefined' || node == null){
            return null;
        }
        var str = '';
        if (node.firstChild){
            str = node.firstChild.nodeValue;
        }

        if (!str){
            return null;
        }

        var dateval = parse_iso(str);
        //var dateval = new Date(xmlval);
        //cogs.debug(dateval + '=' + (typeof dateval));

        return dateval;
    },
    json_to_value: function(json_obj){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof json_obj == 'undefined' || json_obj == null){
            return null;
        }

        if (json_obj.unix_time == null){
            return null;
        }
        
        var date_obj = new Date();
        date_obj.setTime(json_obj.unix_time * 1000);

        return date_obj;
    },
    value_to_json: function(val){
        //cogs.debug(xmlval + '=' + (typeof xmlval));
        if (typeof val == 'undefined' || val == null || !val){
            return null;
        }
        if (typeof val.getTime != 'function'){
            cogs.debug(val, 'Value is defined, but not a Date in DateTimeProperty.value_to_json', 'warn');
            return null;
        }
        
        return {unix_time: val.getTime() / 1000};
    }
});

var DateProperty = DateTimeProperty.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'date';
        this.property_name = data.name;
    }
});

var TimeProperty = DateTimeProperty.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'time';
        this.property_name = data.name;
    }
});


var QueryProperty = Property.extend({
    init: function(data){
        this._super(data);
        this.data_type = 'query';
        this.property_name = data.name;
    },
    value_to_xml: function(value, domdoc){
        cogs.debug(value);
        var data_type = this.data_type;
        var value_str = '';
        if ((typeof value == 'undefined') || (value == null)){
            data_type = 'null';
        }
        else {
            value_str = JSON.stringify(value.to_json());
        }
        var xml_node = domdoc.createElement('property');
        xml_node.setAttribute('name', this.property_name);
        xml_node.setAttribute('type', data_type);
        xml_node.appendChild(domdoc.createCDATASection(value_str));

        //var xml = '<property name="'+this.property_name+'" type="'+data_type+'">';
        //xml += value_str;
        //xml += '</property>';

        return xml_node;
    },
    xml_to_value: function(node){
        //cogs.debug(node);
        var q = null;
        try {
            if (typeof node == 'undefined' || node == null){
                return null;
            }
            var str = '';

            if (node.firstChild){
                str = node.firstChild.nodeValue;
            }

            q = new Query().from_json(JSON.parse(str));
        }
        catch (e){
            cogs.error_handler(e);
            q = null;
        }

        return q;

    },
    value_to_json: function(val){
        var json_obj = null;
        if (val){
            json_obj = val.to_json();
        }
        return json_obj;
    },
    json_to_value: function(json_obj){
        var val = null;
        if (json_obj){
            val = new Query().from_json(json_obj);
        }
        return val;
    }
});


/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* query.js */
var Query_load_async_callbacks = [];
var Query_data_async_callbacks = [];

var Query = Class.extend({
    _ordering: [],
    _filter: [],
    _distinct_column: '',
    _keywords: '',
    _model_class: '',
    _limit: 0,
    _offset: 0,
    iterator: function(){
        /*
         * Using __iterator__ does not work because the class extension does not allow it
         */
        return this.get();
    },
    init: function(model_class){
        this._model_class = model_class;
        this._ordering = [];
        this._filter = [];
        this._distinct_column = '';
        this._limit = 0;
    },
    filter: function(column, operator, value, options){
        if (options == null){
            options = [];
        }
        if (column && operator){
            var obj = {
                column:column,
                operator:operator,
                value:value,
                options:options
            };
            
            this._filter.push(obj);
        }
        else {
            cogs.debug('Invalid data supplied to filter');
            cogs.debug(obj);
        }

        return this;
    },
    filter_and: function(){
        this._filter.push({join:'and'});
        return this;
    },
    filter_or: function(){
        this._filter.push({join:'or'});
        return this;
    },
    keywords: function (kw){
        this._keywords = kw;
        return this;
    },
    distinct: function(column) {
        this._distinct_column = column;
        return this;
    },
    order: function(column, order) {
        if (!order){
            order = 'ASC';
        }
        if (column){
            this._ordering.push({column:column, order:order});
        }

        return this;
    },
    limit: function(limit) {
        this._limit = limit;

        return this;
    },
    offset: function(offset) {
        this._offset = offset;

        return this;
    },
    update: function(key, value) {
        try {
            var post_data = {method: 'Query.update', params: {query:this.to_json(), key:key, value:value}};

            var objects = [];

            cogs.debug(post_data);

            var xhr = cogs.api_json(post_data, {
                type: 'POST',
                async: false
            });


            if (xhr.status == 200){
                var data = JSON.parse(xhr.responseText);

                cogs.debug(data);
            }
            else {
                throw new cogs.errors.Error('Server returned error code ' + xhr.status);
            }

        }
        catch (e){
            cogs.error_handler(e);
        }
        
        return objects;
    },
    get: function() {
        var post_data = {method: 'Query.get', params: this.to_json()};

        var objects = [];

        try {
            //try {
                var xhr = cogs.api_json(post_data, {
                    type: 'POST',
                    async: false
                });
            //}
            //catch (e){
            //    throw new cogs.error.Error('error in api_json ' + e.message);
            //}


            if (xhr.status == 200){
                //cogs.debug(xhr.responseText, 'JSON Query.get responseText');
                try {
                    var response = JSON.parse(xhr.responseText);
                }
                catch (e) {
                    throw new cogs.error.Error('error parsing JSON response!');
                }
                
                //cogs.debug(response, 'JSON Query.get response');
                if (response){
                    var data = response['_return'];
                }
                
                //cogs.debug(data, 'data from Query.get');
                if (data.success && data.data.length){
                    for (var k=0;k<data.data.length;k++){
                        var key = new cogs.db.Key().from_encoded(data.data[k]);

                        eval('var obj'+k+' = cogs.db.get(key)');

                        objects.push(eval('obj'+k));
                    }
                }
            }
            else {
                throw new cogs.errors.Error('Server returned error code ' + xhr.status);
            }
        }
        catch (e){
            cogs.error_handler(e);
        }


        return objects;
    },
    fetch_one: function(){
        var prev_limit = this._limit;
        this._limit = 1;
        var obj = this.iterator()[0];
        this._limit = prev_limit;
        return obj;
    },
    get_async_complete: function (xhr, status){
        try {
            var response_ref = xhr.getResponseHeader('X-Cogs-Ref');
            var callback_data = Query_load_async_callbacks[response_ref];

            var iterator = [];

            var response = JSON.parse(xhr.responseText);
            if (response){
                var data = response['_return'];
            }

            if (!data.success && data.exception){
                if (typeof cogs.error[data.exception] != 'undefined'){
                    throw new cogs.error[data.exception](data.error);
                }
                else {
                    throw new cogs.error.Error(data.error);
                }
            }

            for (var i=0;i<data.data.length;i++){
                var key = new cogs.db.Key(data.data[i]);

                if (!key){
                    continue;
                }

                //eval('var m = cogs.db.get(key)');
                var m = cogs.db.get(key);

                if (!m){
                    continue;
                }

                iterator.push(m);
            }

            callback_data.success(iterator, callback_data.data);
        }
        catch (e){
            cogs.error_handler(e);
        }
    },
    get_async_error: function (xhr, status){
        var response_ref = xhr.getResponseHeader('X-Cogs-Ref');
        var callback_data = Query_load_async_callbacks[response_ref];

        if (callback_data.error){
            callback_data.error(callback_data.data);
        }
    },
    get_async: function (options){
        try {
            var current_ref = Query_load_async_callbacks.length;
            Query_load_async_callbacks[current_ref] = {success:options.success, error:options.error, query:this, data:options.data};

            var params = this.to_json();
            params.ref = current_ref;

            var post_data = {method: 'Query.get', params: params};

            var api_options = {
                async: true,
                type: 'POST',
                complete: this.get_async_complete,
                error: this.get_async_error
            };

            if (typeof options.error != 'undefined' && options.error){
                api_options.error = options.error;
            }

            cogs.api_json(post_data, api_options);
        }
        catch (e){
            cogs.error_handler(e);
        }
    },
    count: function (options){
        try {
            var count = 0;
            var params = this.to_json();

            var post_data = {method: 'Query.count', params: params};

            var api_options = {
                type: 'POST',
                'async': false
            };

            var xhr = cogs.api_json(post_data, api_options);

            if (xhr.status == 200){
                var result_obj = JSON.parse(xhr.responseText);
                result_obj = result_obj._return;
                if (result_obj.success){
                    count = result_obj.data;
                }
                else if (result_obj.exception){
                    if (typeof cogs.error[result_obj.exception] != 'undefined'){
                        throw new cogs.error[result_obj.exception](result_obj.error);
                    }
                    else {
                        throw new cogs.error.Error(result_obj.error);
                    }
                }
            }
        }
        catch (e){
            cogs.error_handler(e);
        }

        return count;
    },
    data: function(options){
        options = options || {};

        if (options.async){
            return this.data_async(options);
        }

        try {
            var objects = [];
            var iter = this.iterator();
            //cogs.debug(iter, 'Data iter');

            for (var s in iter){
                objects.push(iter[s]);
            }

            var md_options = {};
            md_options.recursive = options.recursive;

            var data = new Model().multi_data(objects, md_options);

            if (this._ordering.length){
                // only supports single column ordering
                var col = this._ordering[0].column;
                var order = this._ordering[0].order;
                var order_str_part = (order == 'ASC')?'-1:1':'1:-1';
                var ordering_func_str = 'if (a.'+col+' == b.'+col+'){return 0;} return (a.'+col+' < b.'+col+')?' + order_str_part;
    //            var ordering_func_str = 'return (a.'+col+' < b.'+col+')?-1:1;';
                //cogs.debug(ordering_func_str);
                var sort_func = new Function('a', 'b', ordering_func_str);
                //cogs.debug(sort_func);
                data.sort(sort_func);
            }

            if (options.success && typeof options.success == 'function'){
                options.success(data);
            }
        }
        catch (e){
            if (options.error && typeof options.error == 'function'){
                options.error(e);
            }
            else {
                throw e;
            }
        }
        
        return data;
    },
    data_async: function(options){
        //var current_ref = Query_data_async_callbacks.length;
        //Query_data_async_callbacks[current_ref] = {success:options.success, error:options.error, query:this, data:options.data};

        var self = this;
        window.setTimeout(function(){
            try {
                var objects = [];
                var iter = self.iterator();

                for (var s in iter){
                    objects.push(iter[s]);
                }
                //cogs.debug('Query:data_async');
                //cogs.debug(objects);

                // Try to keep the objects in the same order so store the original order now
                options._iter = iter;

                var md_options = {};
                md_options.recursive = options.recursive;
                var data = new Model().multi_data_async(objects, md_options);

                if (self._ordering.length){
                    // only supports single column ordering
                    var col = self._ordering[0].column;
                    var order = self._ordering[0].order;
                    var order_str_part = (order == 'ASC')?'-1:1':'1:-1';
                    var ordering_func_str = 'if (a.'+col+' == b.'+col+'){return 0;} return (a.'+col+' < b.'+col+')?' + order_str_part;
        //            var ordering_func_str = 'return (a.'+col+' < b.'+col+')?-1:1;';
                    //cogs.debug(ordering_func_str);
                    var sort_func = new Function('a', 'b', ordering_func_str);
                    //cogs.debug(sort_func);
                    data.sort(sort_func);
                }

                if (options.success && typeof options.success == 'function'){
                    options.success(data);
                }
            }
            catch (e){
                if (options.error && typeof options.error == 'function'){
                    options.error(e);
                }
                else {
                    throw e;
                }
            }
            
        }, 500);
    },
    toString: function(){
        return 'Query';
    },
    json_filter: function(filters){
        //alert(filters);
        //cogs.debug('filter to json');
        //cogs.debug(filters);
        var json_filters = [];
        var value = null;
        for (var f=0;f<filters.length;f++){
            if (typeof filters[f].value != 'undefined'){
                if (filters[f].value && typeof filters[f].value.key == 'function'){
                    value = {'key':filters[f].value.key().toString(), 'is_model':true};
                }
                else if (filters[f].value && typeof filters[f].value._values != 'undefined'){
                    var sm_data = {};
                    for (var smk in filters[f].value._values){
                        if (typeof filters[f].value._values[smk].key == 'function'){
                            sm_data[smk] = filters[f].value._values[smk].key().toString();
                        }
                        else {
                            sm_data[smk] = filters[f].value._values[smk];
                        }
                    }
                    sm_data.kind = filters[f].value._kind;
                    value = {'data':sm_data, 'is_submodel':true};
                }
                else if (filters[f].value && $.isArray(filters[f].value)){
                    value = [];
                    for (var i=0;i<filters[f].value.length;i++){
                         if (typeof filters[f].value[i].key == 'function'){
                             value.push({'key':filters[f].value[i].key().toString(), 'is_model':true});
                         }
                         else {
                            value.push(filters[f].value[i]);
                         }
                    }
                }
                else {
                    value = filters[f].value;
                }

                json_filters.push({'column': filters[f].column, 'operator': filters[f].operator, 'value': value});
            }
            else if (typeof filters[f].join != 'undefined'){
                json_filters.push(filters[f].join.toUpperCase());
            }
            value = null;
        }
        
        //cogs.debug(json_filters);
        return json_filters;
    },
    to_json: function(){
        var json = {};
        json.model_class = this._model_class;
        //cogs.debug(this.json_filter(this._filter));
        
        json.filtering = this.json_filter(this._filter);
        json.ordering = this._ordering;
        json.keywords = this._keywords;

        json.limit = this._limit;
        json.offset = this._offset;
        json.distinct_column = this._distinct_column;
        
        return json;
    },
    from_json: function(json_obj){
        try {
            if (typeof json_obj == 'string'){
                json_obj = JSON.parse(json_obj);
                if (!json_obj){
                    cogs.debug(json_obj, 'Invalid string supplied to Query.from_json');
                    cogs.trace();
                    return null;
                }
            }
            if (!json_obj){
                cogs.debug(json_obj, 'Invalid object supplied to Query.from_json');
                cogs.trace();
                return null;
            }
            
            var q = new Query();
            q.init(json_obj.model_class.toLowerCase());

            for (var f=0;f<json_obj.filtering.length;f++){
                if (typeof json_obj.filtering[f] == 'string'){
                    if (json_obj.filtering[f] == 'AND'){
                        q.filter_and();
                    }
                    else if (json_obj.filtering[f] == 'OR'){
                        q.filter_or();
                    }
                }
                else {
                    var value = '';
                    if (json_obj.filtering[f].is_model){
                        var value_data = json_obj.filtering[f].value;
    //                    var value_data = JSON.parse(json_obj.filtering[f].value);
                        value = value_data.key;
                    }
                    else {
                        value = json_obj.filtering[f].value;
                    }
                    q.filter(json_obj.filtering[f].column, json_obj.filtering[f].operator, value);
                }
            }

            for (var o=0;o<json_obj.ordering.length;o++){
                q.order(json_obj.ordering[o].column, json_obj.ordering[o].value);
            }

            q.limit(json_obj.limit);
            q.offset(json_obj.offset);
            if (json_obj.distinct_column){
                q.distinct(json_obj.distinct_column);
            }
            
            if (json_obj.keywords){
                q.keywords(json_obj.keywords);
            }

            return q;
        }
        catch (e){
            cogs.debug(json_obj, 'Error loading query from JSON');
            cogs.error_handler(e);
        }

        return null;

    },
    reset: function (){
        this._ordering = [];
        this._filter = [];
        this._distinct_column = '';
        this._limit = 0;
    }
});


/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */

/* submodel.js */

var SubModel = Class.extend({
    _values: {},
    _fields: {},
    _kind: '',
    init: function(initial_data){
        this._values = initial_data;
    },
    data: function(options){
        options = options || {};
        var data = cogs.clone(this._values);
        //cogs.debug(options, 'Options in submodel data()');
        if (options.recursive){
            //cogs.debug(data, 'data in submodel');
            for (var v in data){
                if (data[v] && typeof data[v].data == 'function'){
                    data[v] = data[v].data(options);
                }
            }
        }
            //cogs.debug(data, 'data in submodel after recursive load');

        return data;
    },
    set_field: function(k, v) {
        if (!this._values){
            this._values = {};
        }
        if (!this._modified_values){
            this._modified_values = {};
        }
        this._values[k] = v;
        this._modified_values[k] = true;
    }
});



/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */
/* datastore/database.js */

cogs.datastore.DatabaseDatastore = cogs.datastore.NullDatastore.extend({
    init: function() {
        this.db = openDatabase("cogs", "1.0", "Client side caching", 200000);
        if (!this.db){
            cogs.debug('Failed to open database');
        }
    },
    set: function(key, value){
        if (!this.db){
            return;
        }
        
        var key = model.key();
        if (key){
            this.create_table(model);
        }
    },
    save_model: function(m){
        for (var key in m._fields){
            
        }
    },
    get: function(key){
        return null;
    },
    remove: function(key){
        return null;
    },
    remove_with_prefix: function(prefix){
        
    },
    create_table: function(model){
        table_name = model.kind();
        stmt = 'CREATE TABLE ' + table_name + '(id INTEGER UNIQUE, key TEXT UNIQUE, ';
        fields = new Array();
        for (f in model._fields){
            fields.push(f + ' ' + this.get_type(model._fields[f]));
        }
        stmt += fields.join(',');
        stmt += ')';

        cogs.debug(stmt);
        //alert(this.db);

        this.db.transaction(function(tx) {
            tx.executeSql(stmt, []);
        });
    },
    get_type: function(property){
        switch (property.data_type){
            case 'text':
                return 'TEXT';
                break;

            case 'int':
                return 'INTEGER';
                break;

            case 'float':
                return 'REAL';
                break;

            case 'blob':
                return 'BLOB';
                break;

            case 'string':
            default:
                return 'TEXT';
                break;
        }
    }
});



/* datastore/globalstorage.js */


cogs.datastore.GlobalStorageDatastore = cogs.datastore.NullDatastore.extend({
    name: function(){
        return 'localStorage';
    },
    init: function(){
        
    },
    set: function(key, value){
        //cogs.debug(value, 'saving ' + key + ' to datastore');
        try {
            window.localStorage.setItem(key, JSON.stringify(value));
        }
        catch (e){
            // iOS devices need key removing first
            try {
                window.localStorage.removeItem(key);
                window.localStorage.setItem(key, JSON.stringify(value));
            }
            catch (e){
                // Just give up!
                cogs.debug('Error saving ' + e.message);
            }
        }
    },
    save_model: function(m){
        try {
            //cogs.debug(m._values, 'Saving model');
            if (m._loaded){
                var obj = {};
                for (var field_name in m._fields){
                    try {
                        obj[field_name] = m._fields[field_name].value_to_json(m._values[field_name]);
                    }
                    catch (e){
                        cogs.debug(m, 'Error saving field ' + field_name + ' to local storage ' + e.message, 'error');
                        cogs.debug(m._fields[field_name], 'Field causing error', 'error');
                        throw e;
                    }
                }
                //cogs.debug(obj, 'OBJ to save in datastore');
                obj.key = m.key().toString();
                obj.kind = m.key().kind();
                this.set(m.key().toString(), obj);
            }
        }
        catch (e){
            cogs.debug('Error saving model to local storage ' + e.message);
            cogs.debug(e);
        }
    },
    get: function(key){
        try {
            var value = window.localStorage.getItem(key);
            return JSON.parse(value);
        }
        catch (e){
            return null;
        }
    },
    get_all: function(){
        return window.localStorage;
    },
    remove: function(key){
        try {
            //cogs.debug('Deleting cache ' + key);
            window.localStorage.removeItem(key);
        }
        catch (e){
        }
    },
    remove_with_prefix: function(prefix){
        var prefix_len = prefix.length;
        for (var k in window.localStorage){
            if (k.substr(0, prefix_len) === prefix){
                this.remove(k);
            }
        }
    },
    clear: function(){
        try {
            window.localStorage.clear();
        }
        catch (e){
        }
    }
});



/*
 * The Cogs Framework, a PHP/Javascript MVC framework.
 *
 * Copyright (C) 2009 Michael Yeates
 *
 * This library 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 library 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 library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * See http://www.opensource.org/licenses/
 *
 */
/* datastore/database.js */

cogs.datastore.IndexedDBDatastore = cogs.datastore.NullDatastore.extend({
    init: function() {
        
    },
    set: function(key, value){
        
    },
    save_model: function(m){
        
    },
    load_model: function(m){
        
    },
    get: function(keys){
        return null;
    },
    remove: function(key){
        return null;
    },
    remove_with_prefix: function(prefix){
        
    },
    clear: function(){
        
    }
});



cogs.version = "0.9.48.2";
