﻿window.fx = {};

fx.core =
{
    services: {},
    data: {},
    id: function (length)
    {
        var id = [];
        for (var i = 0; i < (length || 8); i++)
        {
            id.push(Math.round(Math.random() * 9));
        }
        return 'id-' + id.join('');
    },
    log: function (msg)
    {
        if (window.console)
        {
            console.error(msg);
        }
        else if (!fx.core.log.errors)
        {
            var error = document.createElement('DIV');
            error.innerHTML = '<font color="red">' + msg.replace(/<\/?html>|<\/?head>|<\/?body>|<\/?title>/ig, '') + '</font>';
            document.body.appendChild(error);
        }
    },
    list: function (items, onadd)
    {
        this.array = items || [];
        this.add = function (item)
        {
            this.array.push(item);
            if (onadd) onadd(item);
        };
        this.where = function (func)
        {
            var results = [];
            for (var i = 0; i < this.array.length; i++)
            {
                if (func(this.array[i], i))
                {
                    results.push(this.array[i]);
                }
            }
            return results;
        };
        this.where(onadd);
    },
    abstractMethod: function (description)
    {
        var method = function ()
        {
            fx.core.log('abstract method: ' + description);
        };
        method.abstract = true;
        return method;
    },
    virtualMethod: function (description)
    {
        var method = function ()
        {
            fx.core.log('virtual method: ' + description);
        };
        method.virtual = true;
        return method;
    },
    isVirtual: function (obj, method)
    {
        return (obj && obj[method].virtual);
    },
    createType: function (baseClass, prototype, constructor)
    {
        var type = function ()
        {
            baseClass.apply(this, arguments);
            if (constructor)
            {
                constructor.apply(this, arguments);
            }
        };
        type.prototype = new baseClass();
        type.prototype.base = {};
        if (prototype)
        {
            for (var method in prototype)
            {
                if (baseClass.prototype[method])
                {
                    type.prototype.base[method] = baseClass.prototype[method];
                }
                type.prototype[method] = prototype[method];
            }
        }
        return type;
    },
    createEnum: function (params)
    {
        var enumType = {};
        for (var i = 0; i < arguments.length; i++)
        {
            enumType[arguments[i]] = i;
        }
        return enumType;
    },
    getEnum: function (enumType, value)
    {
        if (typeof (value) == "number")
        {
            return value;
        }
        else if (typeof (value) == "string")
        {
            for (var key in enumType)
            {
                if (key == value)
                {
                    return enumType[key];
                }
            }
        }
    },
    getBool: function (value)
    {
        return (value && value.toString().toLowerCase() == 'true');
    },
    toQuery: function (obj, converter)
    {
        var query = [];
        if (obj)
        {
            if (typeof (obj.push) == 'function')
            {
                obj = fx.core.toDictionary(obj);
            }
            for (var key in obj)
            {
                var value = obj[key];
                if (converter)
                {
                    value = converter(value);
                }
                if (value)
                {
                    query.push(key);
                    query.push('=');
                    query.push(encodeURIComponent(value));
                    query.push('&');
                }
            }
            if (query.length != 0)
            {
                query.pop();
            }
        }
        return query.join('');
    },
    jsonDateRegEx: new RegExp('^\\d{1,2}[/.]\\d{1,2}[/.]\\d{2,4}$'),
    jsonDateMonthSwap: false,
    jsonDateDelimiter: '/',
    toJson: function (obj, name)
    {
        if (obj === 0)
        {
            return '0';
        }
        if (obj === false)
        {
            return 'false';
        }
        if ((name == "OptedOut" || name == "IsActive" || name == "UseEmail" || name == "OffStudyDrug") && obj !== true)
        {
            return 'false';
        }
        var json = [];
        if (obj)
        {
            var objType = typeof (obj);
            if (objType == 'object')
            {
                if (typeof (obj.push) == 'function')
                {
                    json.push('[');
                    for (var i = 0; i < obj.length; i++)
                    {
                        json.push(fx.core.toJson(obj[i]));
                        json.push(',');
                    }
                    if (json.pop() == '[')
                    {
                        json.push('[');
                    }
                    json.push(']');
                }
                else
                {
                    json.push('{');
                    if (obj.__type && obj.__type != "FormContext:#fx.ui")
                    {
                        json.push('"__type":"' + obj.__type + '",');
                    }
                    for (var key in obj)
                    {
                        var subType = typeof (obj[key]);
                        if (key != '__type' && (subType == 'object' || subType == 'string' || (subType != 'undefined' && subType != 'function')))
                        {
                            json.push('"' + key + '"');
                            json.push(':');
                            json.push(fx.core.toJson(obj[key], key));
                            json.push(',');
                        }
                    }
                    if (json.pop() == '{')
                    {
                        json.push('{');
                    }
                    json.push('}');
                }
            }
            else if (objType == 'string' && fx.core.jsonDateRegEx.test(obj) && name && name != "EventDate" && (name == 'date' || name.indexOf('Date') == 0 || name.indexOf('Date') == name.length - 4))
            {
                json.push('"\\/Date(');
                json.push(Date.parse(obj));
                json.push(')\\/"');
            }
            else if (objType == 'string')
            {
                json.push('"');
                json.push(obj.replace(/\\/g, '\\\\').replace(/"/g, '\\"').split('\r').join('').split('\n').join(' '));
                json.push('"');
            }
            else if (objType != "function")
            {
                json.push(obj.toString());
            }
        }
        return json.join('') || 'null';
    },
    toObject: function (js)
    {
        var obj = null;
        if (js)
        {
            var dateRegExp = new RegExp('"\\\\/Date\\(([^\\)]*)[^"]*"', 'g');
            var dateReplace = function (str, dateString)
            {
                var date = new Date(eval(dateString));
                if (fx.core.jsonDateMonthSwap === true)
                {
                    return '"' + date.getDate() + fx.core.jsonDateDelimiter + (date.getMonth() + 1) + fx.core.jsonDateDelimiter + date.getFullYear() + '"';
                }
                else
                {
                    return '"' + (date.getMonth() + 1) + fx.core.jsonDateDelimiter + date.getDate() + fx.core.jsonDateDelimiter + date.getFullYear() + '"';
                }
            };
            try
            {
                eval('obj = ' + js.replace(dateRegExp, dateReplace) + ';');
            }
            catch (e)
            {
                fx.core.log('There was an error while parsing the javascript :\n\n' + js);
            }
        }
        return obj;
    },
    toDictionary: function (keyValuePairs, parser)
    {
        var dictionary = {};
        if (keyValuePairs != null)
        {
            if (typeof (keyValuePairs.push) == 'function')
            {
                for (var k = 0; k < keyValuePairs.length; k++)
                {
                    if (keyValuePairs[k])
                    {
                        var value;
                        if (parser)
                        {
                            value = parser(keyValuePairs[k].Value);
                        }
                        else
                        {
                            value = keyValuePairs[k].Value;
                        }
                        if (value)
                        {
                            var key = (dictionary, keyValuePairs[k].Key || keyValuePairs[k].Name);
                            if (key.indexOf('.') > -1)
                            {
                                var parts = key.split('.');
                                var obj = dictionary;
                                for (var i = 0; i < parts.length - 1; i++)
                                {
                                    var part = parts[i];
                                    if (!obj[part] || typeof (obj[part]) != "object")
                                    {
                                        obj[part] = {};
                                    }
                                    obj = obj[part];
                                }
                                obj[parts[parts.length - 1]] = value;
                            }
                            else
                            {
                                dictionary[key] = value;
                            }
                        }
                    }
                }
            }
            else if (typeof (keyValuePairs) == 'object')
            {
                if (parser)
                {
                    for (var key in keyValuePairs)
                    {
                        if (keyValuePairs[key])
                        {
                            var value = parser(keyValuePairs[key]);
                            if (value)
                            {
                                dictionary[key] = value;
                            }
                        }
                    }
                }
                else
                {
                    dictionary = keyValuePairs;
                }
            }
        }
        return dictionary;
    }
};
fx.services = {};
fx.services.urlMappings = {};

fx.service =
{
    create: function (id, action, parameters, httpGet, headers, callback, contextHeader, parser)
    {
        fx.services[id] =
        {
            id: id,
            action: action,
            httpGet: httpGet,
            parameters: fx.core.toDictionary(parameters),
            headers: headers,
            callback: callback,
            contextHeader: contextHeader,
            parser: parser
        };
    },
    update: function (id)
    {
        var service = fx.services[id];
        var loadingTimeout = fx.ui.progress(service.id);
        var method = (service.httpGet == true ? fx.service.get : fx.service.post);
        method(service.action, service.parameters, service.headers, function (data, context)
        {
            window.clearTimeout(loadingTimeout);
            if (service.callback)
            {
                service.callback(data, context);
            }
        }, service.contextHeader, service.parser);
    },
    invoke: function (action, httpGet, parameters, callback, parameterParser)
    {
        var method = (fx.core.getBool(httpGet) ? fx.service.get : fx.service.post);
        var params = fx.core.toDictionary(parameters, parameterParser);
        return method((fx.services.urlMappings[action] || action), params, null, callback);
    },
    get: function (url, parameters, headers, callback, contextHeader, parser)
    {
        var request = fx.service._create('GET', url, callback, headers, (!parser ? fx.core.toQuery : null), (parser || fx.service._xmlParser), contextHeader);
        request.send(fx.core.toQuery(parameters));
        return fx.service._end(request, (parser || fx.service._xmlParser), contextHeader);
    },
    post: function (action, parameters, headers, callback, contextHeader, parser)
    {
        var request = fx.service._create('POST', action, callback, headers, (!parser ? fx.core.toJson : null), (parser || fx.service._jsonParser), contextHeader);
        request.setRequestHeader('Content-Type', 'application/json');
        request.send(fx.core.toJson(parameters));
        return fx.service._end(request, (parser || fx.service._jsonParser), contextHeader);
    },
    _xmlParser: function (xml)
    {
        return fx.xaml.parse(xml);
    },
    _jsonParser: function (json)
    {
        var responseObject = fx.core.toObject(json);
        if (responseObject)
        {
            if (responseObject.Message)
            {
                fx.core.log(responseObject.Message);
            }
            else if (responseObject.d || responseObject.d === false || responseObject.d === 0)
            {
                return responseObject.d;
            }
            else
            {
                return responseObject;
            }
        }
    },
    _create: function (method, url, callback, headers, formatter, parser, contextHeader)
    {
        var request = new (window.XMLHttpRequest || ActiveXObject)('Microsoft.XMLHTTP');
        if (callback)
        {
            request.onreadystatechange = function () { fx.service._end(request, parser, contextHeader, callback); };
        }
        request.open(method, url, Boolean(callback));
        if (headers)
        {
            for (var key in headers)
            {
                if (formatter)
                {
                    request.setRequestHeader(key, formatter(headers[key]));
                }
                else
                {
                    request.setRequestHeader(key, headers[key]);
                }
            }
        }
        return request;
    },
    _end: function (request, parser, contextHeader, callback)
    {
        if (request && request.readyState == 4)
        {
            var responseObject = parser(request.responseText);
            if ((responseObject || responseObject === false || responseObject === 0) && callback)
            {
                var responseContext = null;
                if (contextHeader)
                {
                    responseContext = parser(request.getResponseHeader(contextHeader));
                }
                callback(responseObject, responseContext);
            }
            request.onreadystatechange = function () { };
            request = null;
            return responseObject;
        }
    }
};
fx.xaml =
{
    parse: function(xaml)
    {
        xaml = xaml.replace(/<!--[^-]+-->/g, '');
        var elements = xaml.match(/<[^>]+>|<\/?[^>]+>|[^\s<>]+/g);
        if (elements && elements.length > 0)
        {
            var typeResolver = new fx.xaml.typeResolver();
            var rootElement, parentElement, parentProperty;
            for (var index = 0; index < elements.length; index++)
            {
                var tag = elements[index];
                if (tag.charAt(0) == '<')
                {
                    var name = tag.match(/\/?\w+([\.:][\w\.]+)?/)[0];
                    if (parentElement && name == '/' + parentElement.Tag)
                    {
                        parentElement = parentElement.Parent;
                        parentProperty = null;
                        if (parentElement && parentElement._property)
                        {
                            parentProperty = parentElement._property;
                            delete parentElement._property;
                        }
                    }
                    else if (name.indexOf('.') > -1)
                    {
                        parentProperty = name.split('.')[1];
                    }
                    else if (parentProperty && name == '/' + parentProperty)
                    {
                        parentProperty = null;
                    }
                    else
                    {
                        var properties = fx.xaml._parseProperties(tag, /\w+([\.:]\w+)?="[^"]*"/g, typeResolver);
                        var type = typeResolver.getType(name);
                        var element = typeResolver.createInstance(type, properties);
                        element.Tag = name;
                        element.Parent = parentElement;
                        var targetElement = (parentProperty ? parentElement[parentProperty] : parentElement);
                        if (!targetElement && parentProperty)
                        {
                            parentElement[parentProperty] = element;
                        }
                        else if (!targetElement)
                        {
                            targetElement = element;
                            rootElement = element;
                            parentElement = element;
                        }
                        else if (typeof (targetElement.add) == 'function')
                        {
                            targetElement.add(element);
                        }
                        else if (typeof (targetElement.push) == 'function')
                        {
                            targetElement.push(element);
                        }
                        else if (parentProperty)
                        {
                            fx.core.log('xaml error - property is not a collection "' + parentElement.Tag + '.' + parentProperty + '"');
                        }
                        else
                        {
                            fx.core.log('xaml error - element cannot have children "' + parentElement.Tag + '"');
                        }
                        if (tag.indexOf('/>') == -1)
                        {
                            parentElement._property = parentProperty;
                            parentProperty = null;
                            parentElement = element;
                        }
                    }
                }
            }
            rootElement.typeResolver = typeResolver;
            return rootElement;
        }
    },
    _parseProperties: function(xaml, match, typeResolver)
    {
        var properties = {};
        var nameValues = (xaml.match(match) || []);
        for (var index = 0; index < nameValues.length; index++)
        {
            var nameValue = nameValues[index];
            var propertyIndex = nameValue.indexOf('=');
            var name = nameValue.substring(0, propertyIndex);
            var value = nameValue.substring(propertyIndex + 1);
            if (value.indexOf('"') == 0 || value.indexOf("'") == 0)
            {
                value = value.slice(1, -1);
            }
            if (name.indexOf('xmlns') == 0)
            {
                var keyIndex = name.indexOf(':');
                var key = (keyIndex > -1 ? name.substring(keyIndex + 1) : '_default');
                var ns = value.replace('clr-namespace:', '').split(';')[0];
                typeResolver.addNamespace(key, ns);
                continue;
            }
            if (value.indexOf('{') == 0)
            {
                if (value.indexOf('{}') == 0)
                {
                    value = fx.core.toObject(value.substring(2));
                }
                else
                {
                    value = fx.xaml._parseExtension(value, typeResolver);
                }
            }

            var nameIndex = name.indexOf(':');
            if (nameIndex > -1)
            {
                name = name.substring(nameIndex + 1);
            }

            properties[name] = value;
        }
        return properties;
    },
    _parseExtension: function(markup, typeResolver)
    {
        var name;
        var extension;
        var extensionIndex = markup.indexOf(' ');
        if (extensionIndex == -1)
        {
            name = markup.slice(1, -1);
            var type = typeResolver.getType(name);
            extension = typeResolver.createInstance(type);
        }
        else
        {
            name = markup.substring(1, extensionIndex);
            var type = typeResolver.getType(name);
            if (markup.indexOf('=') > -1)
            {
                var properties = fx.xaml._parseProperties(markup.slice(1, -1), /\w+(\.\w+)?=+[^,]+/g, typeResolver);
                extension = typeResolver.createInstance(type, properties);
            }
            else
            {
                var property = markup.slice(extensionIndex + 1, -1);
                extension = new type(property);
            }
        }
        return extension;
    },
    typeResolver: function()
    {
        this.Types = {};
        this.Namespaces = {};
        this.addNamespace = function(key, ns)
        {
            if (ns)
            {
                if (ns.indexOf('/') > -1)
                {
                    this.Namespaces[key] = this.Namespaces[ns];
                }
                else
                {
                    this.Namespaces[key] = ns;
                }
            }
        };
        this.getType = function(name)
        {
            var ns;
            if (name.indexOf(':') > -1)
            {
                ns = name.split(':')[0];
                name = name.split(':')[1];
            }
            else
            {
                ns = '_default';
            }
            if (!this.Namespaces[ns])
            {
                fx.core.log('xaml error - namespace could not be resolved: ' + ns);
                return fx.ui.Element;
            }
            var typeString = this.Namespaces[ns] + '.' + name;
            if (this.Types[typeString])
            {
                return this.Types[typeString];
            }
            else
            {
                var typeParts = typeString.split('.');
                var type = window;
                for (var index = 0; index < typeParts.length; index++)
                {
                    type = type[typeParts[index]];
                    if (!type)
                    {
                        fx.core.log('xaml error - type could not be resolved: ' + typeString);
                        return fx.ui.Element;
                    }
                }
                this.Types[typeString] = type;
                type.prototype.__type = this.getJSONTypeString(typeString);
                return type;
            }
        };
        this.getJSONTypeString = function(typeString)
        {
            var parts = typeString.split('.');
            var name = parts[parts.length - 1];
            return name + ':#' + typeString.substring(0, typeString.length - name.length - 1);
        };
        this.createInstance = function(type, properties)
        {
            var instance = new type();
            if (properties)
            {
                for (var name in properties)
                {
                    instance[name] = properties[name];
                    if (instance[name] instanceof fx.ui.Binding)
                    {
                        instance[name].Parent = instance;
                    }
                }
            }
            return instance;
        };
    }
};
fx.ui =
{
    urlMappings: {},
    json: function(json)
    {
        var id = fx.core.id();
        document.write(fx.ui.load(id, url, callback));
        return id;
    },
    page: function(url, callback)
    {
        var id = fx.core.id();
        document.write(fx.ui.load(id, url, callback));
        return id;
    },
    write: function(serviceAction, serviceParameters, httpGet, headers)
    {
        var id = fx.core.id();
        document.write(fx.ui.create(id));
        fx.service.create(id, serviceAction, serviceParameters, httpGet, headers, function(data)
        {
            if (data != null)
            {
                var element;
                if (typeof (data.push) == "function")
                {
                    element = new fx.ui.Grid();
                    element.Items = data;
                }
                else if (typeof (data) == "object")
                {
                    element = new fx.ui.Form();
                    element.DataContext = data;
                }
                else
                {
                    element = new fx.ui.Text();
                    element.Content = data.toString();
                }
                fx.ui.html(id, function() { return element.render(); });
            }
            else
            {
                fx.ui.html(id, 'No Data');
            }
        });
        fx.service.update(id);
        return id;
    },
    create: function(id, className, innerHTML, width, height)
    {
        var html = [];
        var browserClass = (navigator.userAgent.indexOf('IE') > -1 ? 'ie-only' : 'not-ie');
        if (navigator.userAgent.indexOf('IE 6') > -1)
        {
            browserClass += ' ie-6';
        }
        html.push('<div id="' + id + '"');
        html.push(' class="fx ' + browserClass + ' ' + (className || '') + '"');
        var style = '';
        if (width)
        {
            style += 'width:' + width + 'px;';
        }
        if (height)
        {
            style += 'height:' + height + 'px;';
        }
        if (style)
        {
            html.push(' style="' + style + '"');
        }
        html.push('>');
        html.push(innerHTML || '');
        html.push('</div>');
        return html.join('');
    },
    load: function(id, url, callback)
    {
        fx.service.create(id, url + '?' + (new Date()).toString(), null, true, null, function(page)
        {
            fx.ui.html(id, function() { return page.render(); });
            if (callback)
            {
                callback(page);
            }
        });
        fx.service.update(id);
        return fx.ui.create(id);
    },
    progress: function(id)
    {
        return window.setTimeout(function()
        {
            fx.ui.html(id, '<div class="fx-loading"><span>Loading...</span></div>');
        }, 500);
    },
    html: function(id, html)
    {
        var element = document.getElementById(id);
        while (element.firstChild)
        {
            element.removeChild(element.firstChild);
        }
        if (typeof (html) == 'function')
        {
            element.innerHTML = '<div class="fx-loading"><span>Loading...</span></div>';
            window.setTimeout(function()
            {
                element.innerHTML = html();
            }, 10);
        }
        else
        {
            element.innerHTML = html;
        }
    },
    hover: function(element, className, defaultClassName)
    {
        if (element.className == defaultClassName)
        {
            element.className = className;
        }
    },
    createHover: function(className, hoverClassName)
    {
        return ' onmouseover=\'fx.ui.hover(this,"' + hoverClassName + '","' + className + '");\' onmouseout=\'fx.ui.hover(this,"' + className + '","' + hoverClassName + '");\'';
    }
};
fx.ui.HorizontalAlignment = fx.core.createEnum('Left', 'Center', 'Right', 'Stretch');

fx.ui.Element = fx.core.createType(Object,
{
    renderContent: fx.core.abstractMethod('fx.ui.Element.renderContent()'),
    render: function ()
    {
        var html = this.renderContent();
        if (this.RenderContainer)
        {
            return fx.ui.create(this.id, this.ClassName, html, this.Width, this.Height);
        }
        else
        {
            return html;
        }
    },
    getContext: function (ownerOut)
    {
        if (this.DataContext == null && this.Parent != null)
        {
            return this.Parent.getContext(ownerOut);
        }
        else if (this.DataContext instanceof fx.ui.ServiceBinding)
        {
            if (ownerOut)
            {
                ownerOut.id = this.id;
            }
            var result = this.getValue(this.DataContext);
            this.DataContext = result;
            return this.DataContext;
        }
        else
        {
            return this.getValue(this.DataContext, ownerOut);
        }
    },
    getStretch: function ()
    {
        if (this.HorizontalAlignment == null && this.Parent != null)
        {
            return this.Parent.getStretch();
        }
        else
        {
            var alignment = fx.core.getEnum(fx.ui.HorizontalAlignment, this.HorizontalAlignment);
            return (alignment == fx.ui.HorizontalAlignment.Stretch);
        }
    },
    getValue: function (binding, ownerOut)
    {
        if (!binding)
        {
            return '';
        }
        else if (binding instanceof fx.ui.Binding)
        {
            return binding.getValue(ownerOut);
        }
        else
        {
            if (ownerOut)
            {
                ownerOut.id = this.id;
            }
            return binding;
        }
    },
    setValue: function (binding, value)
    {
        if (binding instanceof fx.ui.Binding)
        {
            binding.setValue(value);
        }
    },
    getContent: function (content)
    {
        if (content instanceof fx.ui.Binding) content = content.getValue();
        if (content && content.indexOf('{') > -1 && content.indexOf('}') > -1)
        {
            var bindings = content.split('{');
            for (var i = 0; i < bindings.length; i++)
            {
                if (bindings[i].indexOf('Binding') == 0)
                {
                    var path = bindings[i].split('}')[0];
                    var key = '{' + path + '}';
                    path = path.replace('Binding', '').replace(' ', '');
                    var prop = path.split(',');
                    var binding = new fx.ui.Binding(prop[0], this);
                    for (var p = 1; p < prop.length; p++)
                    {
                        var kv = prop[p].split('=');
                        binding[kv[0]] = kv[1];
                    }
                    content = content.replace(key, binding.getValue(null, true) || '');
                }
            }
        }
        return content;
    }
},
function ()
{
    this.id = fx.core.id();
    if (!fx.ui.Element._elements) fx.ui.Element._elements = {};
    fx.ui.Element._elements[this.id] = this;
});

fx.ui.Binding = fx.core.createType(fx.ui.Element,
{
    getValue: function (ownerOut, clear)
    {
        var value, context;
        if (this.Source && this.Source.getValue)
        {
            return this.Source.getValue(this, ownerOut);
        }
        else if (this.Source)
        {
            return this.Source;
        }
        if (this.Parent)
        {
            if (ownerOut == null)
            {
                ownerOut = {};
                context = this.Parent.getContext(ownerOut);
                if (context && this.Mode == 'TwoWay')
                {
                    if (clear || !fx.ui.Binding._bindings[ownerOut.id])
                    {
                        fx.ui.Binding._bindings[ownerOut.id] = {};
                    }
                    fx.ui.Binding._bindings[ownerOut.id][this.id] = this;
                }
            }
            else
            {
                context = this.Parent.getContext(ownerOut);
            }
            if (this.Path && this.Path != '.')
            {
                var data = context;
                var parts = this.Path.split('.');
                for (var p = 0; p < parts.length; p++)
                {
                    if (data && parts[p])
                    {
                        data = data[parts[p]];
                    }
                }
                value = data;
            }
            else
            {
                value = context;
            }
        }
        if (this.Converter)
        {
            return this.Converter.Convert(value, context);
        }
        else
        {
            return (value === 0 ? "0" : value);
        }
    },
    setValue: function (value)
    {
        if (this.Parent)
        {
            var ownerOut = {};
            var context = this.Parent.getContext(ownerOut, true);
            if (this.Path && this.Path != '.')
            {
                var data = context;
                var parts = this.Path.split('.');
                for (var p = 0; p < parts.length; p++)
                {
                    if (data && parts[p])
                    {
                        var part = parts[p];
                        if (p == parts.length - 1)
                        {
                            data[part] = value;
                            fx.ui.Binding.applyBindings(ownerOut.id, part);
                        }
                        if (!data[part]) data[part] = {};
                        data = data[part];
                    }
                }
            }
            else
            {
                fx.ui.Element._elements[ownerOut.id].DataContext = value;
                fx.ui.Binding.applyBindings(ownerOut.id);
            }
        }
    }
},
function (path, element)
{
    this.Path = path;
    this.Parent = element;
    this.Converter = null;
});

fx.ui.Binding._bindings = {};

fx.ui.Binding.parse = function (markup, element)
{
    if (markup && markup.indexOf('{Binding') == 0)
    {
        var path = markup.split(' ')[1].split('}')[0];
        return new fx.ui.Binding(path, element);
    }
    if (markup && markup.indexOf('{UrlQueryBinding') == 0)
    {
        return new fx.ui.UrlQueryBinding();
    }
    return markup;
};

fx.ui.Binding.applyBindings = function (id, property)
{
    if (fx.ui.Binding._bindings[id])
    {
        for (var key in fx.ui.Binding._bindings[id])
        {
            var binding = fx.ui.Binding._bindings[id][key];
            if (!property || binding.Path.indexOf(property) > -1)
            {
                var element = binding.Parent;
                if (!(element instanceof fx.ui.Field))
                {
                    while (element && !(element instanceof fx.ui.Editor))
                    {
                        element = element.Parent;
                    }
                }
                if (element)
                {
                    fx.ui.html(element.id, function () { return element.render(); });
                }
            }
        }
    }
};

fx.ui.ServiceBinding = fx.core.createType(fx.ui.Binding,
{
    add: function (parameter)
    {
        this.ServiceParameters.push(parameter);
    },
    getValue: function()
    {
        var element = this.Parent;
        return fx.service.invoke(this.ServiceAction, this.HttpGet, this.ServiceParameters, null, function(binding) { return element.getValue(binding); });
    },
    onAction: function (callback)
    {
        fx.ui.ServiceBinding.onAction(this.Parent, this, callback);
    }
},
function (action)
{
    this.ServiceAction = action;
    this.ServiceParameters = [];
    this.HttpGet = false;
});

fx.ui.ServiceBinding.onAction = function (element, action, callback)
{
    var outParameter;
    if (action.ServiceAction.indexOf('(') > -1)
    {
        var methodParameters = action.ServiceAction.split('(')[1].split(')')[0].split(',');
        action.ServiceParameters = [];
        for (var i = 0; i < methodParameters.length; i++)
        {
            var parts = methodParameters[i].split('=');
            action.ServiceParameters.push({
                Name: parts[0],
                Value: fx.ui.Binding.parse(parts[1], element)
            });
        }
        action.ServiceAction = action.ServiceAction.split('(')[0];
    }
    if (action.ServiceParameters)
    {
        for (var i = 0; i < action.ServiceParameters.length; i++)
        {
            var parameter = action.ServiceParameters[i];
            if (parameter instanceof fx.ui.OutParameter)
            {
                outParameter = parameter;
                break;
            }
        }
    }
    fx.service.invoke(action.ServiceAction, false, action.ServiceParameters, function (result)
    {
        fx.ui.ServiceBinding.onActionCompleted(element, result, outParameter);
        if (callback)
        {
            callback(result);
        }
    }, function (binding)
    {
        return element.getValue(binding);
    });
};

fx.ui.ServiceBinding.onActionCompleted = function (element, result, outParameter)
{
    if (outParameter)
    {
        element.setValue(outParameter.Target, result);
    }
    else if (typeof (result) == "string" && result.indexOf("http") != 0)
    {
        alert(result);
    }
    else if (result && result.__type)
    {
        if (result.__type.indexOf('ActionResult:') == 0)
        {
            alert('Action Successful: ' + result.Success);
        }
        else if (result.__type.indexOf('MessageResult:') == 0)
        {
            alert(result.Message);
        }
        else
        {
            //alert('Unsupported ActionResult: ' + result.__type);
        }
    }
    if (typeof (element.OnActionCompleted) == 'function')
    {
        element.OnActionCompleted(result);
    }
    else if (typeof (element.OnActionCompleted) == 'string')
    {
        new Function("result", element.OnActionCompleted)(result);
    }
};

fx.ui.Parameter = fx.core.createType(fx.ui.Element, null,
function(name, value)
{
    this.Name = name;
    this.Value = value;
});

fx.ui.OutParameter = fx.core.createType(fx.ui.Element, null,
function(target)
{
    this.Target = target;
});

fx.ui.SelectedItem = fx.core.createType(fx.ui.Binding,
{
    getValue: function()
    {
        var element = this.Parent;
        while (element && !element.SelectedItem)
        {
            element = element.Parent;
        }
        return (element ? element.SelectedItem : null);
    }
});

fx.ui.UrlQueryBinding = fx.core.createType(fx.ui.Binding,
{
    getValue: function()
    {
        if (this.Path)
        {
            return fx.ui.UrlQueryBinding.getQuery(this.Path);
        }
        else
        {
            return document.location.href;
        }
    }
});
fx.ui.UrlQueryBinding.getQuery = function(name)
{
    var query = document.location.search;
    if (query && query.indexOf(name + '=') > 0)
    {
        query = query.split(name + '=')[1].split('&')[0];
        return unescape(query).replace('+', ' ');
    }
    else
    {
        return "";
    }
};

fx.ui.CompareBinding = fx.core.createType(fx.ui.Binding,
{
    setValue: function (value)
    {
        var compare = this.getValue();
        if (compare != value)
        {
            this.Parent.Invalidate();
        }
        else
        {
            this.Parent.ClearValidation();
        }
    }
});

fx.ui.ClientMethodBinding = fx.core.createType(fx.ui.Binding,
{
    getValue: function ()
    {
        return fx.ui.ClientMethodBinding.invokeMethod(this.Path);
    }
});
fx.ui.ClientMethodBinding.invokeMethod = function(name)
{
    return window[name]();
};

fx.ui.Panel = fx.core.createType(fx.ui.Element,
{
    add: function(element)
    {
        this.Elements.push(element);
    },
    renderContent: function()
    {
        var html = [];
        if (this.Elements.length == 1 && !this.Elements[0].renderHeader && !this.Elements[0].renderRow && !this.HorizontalAlignment)
        {
            html.push(this.Elements[0].render());
        }
        else if (this.Elements.length > 0)
        {
            var wrap = this.getValue(this.WrapElements);
            var alignment = fx.core.getEnum(fx.ui.HorizontalAlignment, this.HorizontalAlignment);
            html.push('<table cellspacing="0" cellpadding="0" ');
            if (this.getStretch())
            {
                html.push(' width="100%" ');
            }
            if (this.StyleKey)
            {
                html.push(' class="' + this.StyleKey + '" ');
            }
            if (this.Background)
            {
                html.push(' bgcolor="' + this.Background + '" ');
            }
            switch (alignment)
            {
                case fx.ui.HorizontalAlignment.Center:
                    html.push(' align="center" ');
                    break;
                case fx.ui.HorizontalAlignment.Right:
                    html.push(' align="right" ');
                    break;
            }
            html.push('>');
            if (this.Elements[0].renderHeader)
            {
                var columnSpan = (wrap ? this.Elements.length : 1);
                this.Elements[0].renderHeader(html, columnSpan);
            }
            if (wrap)
            {
                html.push('<tbody>');
                html.push('<tr id="' + this.id + '-panel">');
            }
            else
            {
                html.push('<tbody id="' + this.id + '-panel">');
            }
            for (var i = 0; i < this.Elements.length; i++)
            {
                var element = this.Elements[i];
                if (element.renderRow)
                {
                    element.renderRow(html, i, wrap);
                }
                else
                {
                    if (!wrap)
                    {
                        html.push('<tr>');
                    }
                    html.push('<td valign="top" ');
                    if (i == this.FillIndex)
                    {
                        html.push('style="padding-top:3px;width:100%;"');
                    }
                    else if(wrap || i>0)
                    {
                        html.push('style="padding-top:3px;"');
                    }
                    html.push('>');
                    html.push(element.render());
                    html.push('</td>');
                    if (!wrap)
                    {
                        html.push('</tr>');
                    }
                }
            }
            if (wrap)
            {
                html.push('</tr>');
            }
            html.push('</tbody>');
            html.push('</table>');
        }
        return html.join('');
    }
},
function()
{
    this.Elements = [];
});

fx.ui.Section = fx.core.createType(fx.ui.Panel,
{
	renderContent: function()
	{
		if (!this.getValue(this.Hidden))
		{
			return this.base.renderContent.call(this);
		}
		else
		{
			return "";
		}
	},
	renderRow: function(html, index, wrap)
	{
		if (!this.getValue(this.Hidden))
		{
			if (!wrap)
			{
				html.push('<tr>');
			}
			html.push('<td valign="top">');
			if (fx.core.getEnum(fx.ui.Section.HeaderStyle, this.HeaderStyle) == fx.ui.Section.HeaderStyle.Bottom)
			{
				html.push('<div class="fx-section fx-header-bottom">');
			}
			else
			{
				html.push('<div class="fx-section">');
			}
			html.push('<div class="fx-header">' + this.Header + '</div>');
			html.push('<div class="fx-content" id="' + this.id + '">');
			html.push(this.renderContent());
			html.push('</div>');
			html.push('</div>');
			html.push('</td>');
			if (!wrap)
			{
				html.push('</tr>');
			}
		}
	}
},
function(header)
{
	this.Header = header;
});

fx.ui.Section.HeaderStyle = fx.core.createEnum('Top', 'Bottom', 'Expand');

fx.ui.Tab = fx.core.createType(fx.ui.Panel,
{
	renderHeader: function(html, columnSpan)
	{
		var tabs = {};
		var tabIndex = 0;
		html.push('<thead><tr><td colspan="' + columnSpan + '">');
		html.push('<table class="fx-tabs" cellspacing="0" cellpadding="0"');
		if (navigator.userAgent.indexOf('Firefox') > -1) { html.push(' style="margin-left:1px;"'); }
		html.push('><tr>');
		var selectedTab, firstIndex, firstTab;
		for (var i = 0; i < this.Parent.Elements.length; i++)
		{
			var element = this.Parent.Elements[i];
			if (element instanceof fx.ui.Tab && !tabs[element.Header] && !element.getValue(element.Hidden))
			{
				tabs[element.Header] = true;
				html.push('<td id="' + this.Parent.id + '-tab-' + tabIndex + '"');
				if (!element.Url)
				{
					html.push(' onclick=\'fx.ui.Tab.onSelect("' + this.Parent.id + '", ' + tabIndex + ', "' + element.Header + '");\'');
				}
				html.push(fx.ui.createHover('fx-tab', 'fx-tab tab-hover'));
				if (tabIndex == 0)
				{
					firstTab = element;
					firstIndex = html.length;
				}
				if (fx.core.getBool(element.Selected))
				{
					selectedTab = element;
					html.push(' class="fx-tab tab-selected"');
				}
				else
				{
					html.push(' class="fx-tab"');
				}
				html.push('>');
				if (element.Url)
				{
					html.push('<a href="' + element.Url + '" style="text-decoration:none;color:white;">');
				}
				if (element.Icon)
				{
					html.push('<img border="0" src="' + element.Icon + '" width="16" height="16" />');
				}
				html.push('<span>' + element.Header + '</span>');
				if (element.Url)
				{
					html.push('</a>');
				}
				html.push('</td>');
				tabIndex++;
			}
		}
		html.push('</tr></table></td></tr></thead>');
		if (!selectedTab)
		{
			firstTab.Selected = true;
			html[firstIndex] = ' class="fx-tab tab-selected"';
		}
	},
	renderRow: function(html, index, wrap)
	{
		if (!this.getValue(this.Hidden))
		{
			if (!wrap)
			{
				html.push('<tr tab="' + this.Header + '"');
				if (!fx.core.getBool(this.Selected))
				{
					html.push(' style="display:none;"');
				}
				html.push('><td valign="top">');
			}
			else
			{
				html.push('<td valign="top" tab="' + this.Header + '"');
				if (index > 0)
				{
					html.push(' style="display:none;"');
				}
				html.push('>');
			}
			html.push('<div class="fx-content" id="' + this.id + '">');
			html.push(this.renderContent());
			html.push('</div>');
			html.push('</td>');
			if (!wrap)
			{
				html.push('</tr>');
			}
		}
	}
},
function(header)
{
	this.Header = header;
});

fx.ui.Tab.onSelect = function(id, index, tab)
{
   	var panel = document.getElementById(id + '-panel');
   	for (var t = 0; t < panel.childNodes.length; t++)
   	{
   		var tabElement = document.getElementById(id + '-tab-' + t);
   		if (!tabElement)
   		{
   			break;
   		}
   		if (t == index)
   		{
   			tabElement.className = 'fx-tab tab-selected';
   		}
   		else
   		{
   			tabElement.className = 'fx-tab';
   		}
   	}
   	for (var t = 0; t < panel.childNodes.length; t++)
   	{
   		var tabPanel = panel.childNodes[t];
   		if (tabPanel.getAttribute('tab') == tab || tabPanel.firstChild.getAttribute('tab') == tab)
   		{
   			tabPanel.style.display = '';
   		}
   		else
   		{
   			tabPanel.style.display = 'none';
   		}
   	}
};

fx.ui.Text = fx.core.createType(fx.ui.Element,
{
    renderContent: function ()
    {
        var html = [];
        var style = '';
        if (this.Bold)
        {
            style += 'font-weight:bold;';
        }
        if (this.Size)
        {
            style += 'font-size:' + this.Size + 'pt;';
        }
        if (this.Color)
        {
            style += 'color:' + this.Color + ';';
        }
        html.push('<span style="' + style + '"');
        if (this.StyleKey)
        {
            html.push(' class="' + this.StyleKey + '"');
        }
        html.push('>');
        var content = this.getContent(this.Content);
        if (content)
        {
            content = content.split('&lt;').join('<');
            content = content.split('&gt;').join('>');
            content = content.split('&quot;').join('"');
            html.push(content);
        }
        html.push('</span>');
        return html.join('');
    }
},
function (text)
{
    this.Text = text;
});

fx.ui.Button = fx.core.createType(fx.ui.Element,
{
    renderContent: function ()
    {
        var html = [];
        html.push('<input type="button" ');
        html.push('value="' + this.getValue(this.Text) + '" ');
        if (this.Click)
        {
            html.push('onclick=\'fx.ui.Button.onClick("' + this.id + '");\' ');
        }
        else if (this.OnClick)
        {
            html.push('onclick="' + this.OnClick + '" ');
        }
        if (this.StyleKey)
        {
            html.push(' class="fx-button ' + this.StyleKey + '"');
        }
        else
        {
            html.push(' class="fx-button"');
        }
        html.push('/>');
        return html.join('');
    }
},
function (text)
{
    this.Text = text;
});

fx.ui.Button.onClick = function(id)
{
    var button = fx.ui.Element._elements[id];
    if (button.Click instanceof fx.ui.ServiceBinding)
    {
        fx.ui.ServiceBinding.onAction(button, button.Click);
    }
};

fx.ui.Link = fx.core.createType(fx.ui.Element,
{
    renderContent: function()
    {
        var html = [];
        var linkStyle = fx.core.getEnum(fx.ui.Link.LinkStyle, this.LinkStyle);
        var className = 'fx-link';
        switch (linkStyle)
        {
            case fx.ui.Link.LinkStyle.Button:
                className = 'fx-link-button';
                break;
            case fx.ui.Link.LinkStyle.Tab:
                className = 'fx-link-tab';
                break;
            case fx.ui.Link.LinkStyle.RibbonButton:
                className = 'fx-link-ribbon-button';
                break;
        }
        if (this.StyleKey)
        {
            className += ' ' + this.StyleKey;
        }
        html.push('<a class="' + className + '" ');
        html.push(fx.ui.createHover(className, className + ' link-hover'));
        var url = this.getValue(this.Url);
        if (url)
        {
            html.push('href="' + this.getContent(url) + '"');
        }
        else
        {
            html.push('href="#"');
        }
        if (this.Popup)
        {
            html.push(' onclick=\'fx.ui.Link.showPopup("' + this.id + '");return false;\'');
        }
        else if (this.Click)
        {
            html.push(' onclick=\'fx.ui.Button.onClick("' + this.id + '");return false;\' ');
        }
        else if (this.ShowPopup)
        {
            html.push(' onclick=\'fx.ui.Link.showWindow("' + url + '");return false;\' ');
        }
        var icon = this.getValue(this.Icon);
        if (icon)
        {
            html.push(' style="text-decoration:none;"');
        }
        html.push('>');
        if (icon)
        {
            html.push('<img border="0" src="' + icon + '" width="16" height="16" />');
        }
        html.push('<span');
        if (this.Color)
        {
            html.push(' style="color:' + this.Color + ';"');
        }
        html.push('>');
        html.push(this.getValue(this.Text));
        html.push('</span>');
        html.push('</a>');
        return html.join('');
    }
},
function(text)
{
    this.Text = text;
});

fx.ui.Link.LinkStyle = fx.core.createEnum('Link', 'Button', 'Tab', 'RibbonButton');

fx.ui.Link.showPopup = function(id)
{
    var link = fx.ui.Element._elements[id];
    var shade = document.createElement("DIV");
    var popup = document.createElement("DIV");
    var popupElement = document.createElement("DIV");
    popupElement.id = "popup-" + id;
    popup.className = "fx-popup";
    shade.className = "fx-popup-shade";
    document.body.className = "fx-popup-shaded";
    popupElement.appendChild(shade);
    popupElement.appendChild(popup);
    document.body.appendChild(popupElement);
    popup.innerHTML = link.Popup.render();
    fx.ui.Link.PopupID = id;
    shade.onclick = function() { fx.ui.Link.hidePopup(); };
};

fx.ui.Link.showPopupElement = function (element)
{
    var shade = document.createElement("DIV");
    var popup = document.createElement("DIV");
    var popupElement = document.createElement("DIV");
    popupElement.id = "popup-" + element.id;
    popup.className = "fx-popup";
    shade.className = "fx-popup-shade";
    document.body.className = "fx-popup-shaded";
    popupElement.appendChild(shade);
    popupElement.appendChild(popup);
    document.body.appendChild(popupElement);
    popup.innerHTML = element.render();
    fx.ui.Link.PopupID = element.id;
    shade.onclick = function () { fx.ui.Link.hidePopup(); };
};

fx.ui.Link.showWindow = function(url)
{
    fx.ui.Link.hidePopup();
    if (url.indexOf(".ashx?") > -1 || url.indexOf("http") == 0)
    {
        if (!window.open(url))
        {
            document.location = url;
        }
    }
    else
    {
        var shade = document.createElement("DIV");
        var popup = document.createElement("DIV");
        var popupElement = document.createElement("DIV");
        var id = fx.core.id();
        popupElement.id = "popup-" + id;
        popup.className = "fx-popup-window";
        document.body.className = "fx-popup-shaded";
        popupElement.appendChild(shade);
        popupElement.appendChild(popup);
        document.body.appendChild(popupElement);
        if (navigator.userAgent.indexOf('IE 6') > -1)
        {
            var frame = document.createElement("IFRAME");
            popup.appendChild(frame);
            frame.width = "100%";
            frame.height = "100%";
            frame.src = url;
        }
        else
        {
            popup.innerHTML = '<iframe width="100%" height="100%" src="' + url + '"></iframe>';
        }
        fx.ui.Link.PopupID = id;
        shade.onclick = function() { fx.ui.Link.hidePopup(); };
    }
};

fx.ui.Link.hidePopup = function()
{
    if (fx.ui.Link.PopupID && !fx.ui.Link.keepPopup)
    {
        var popupElement = document.getElementById("popup-" + fx.ui.Link.PopupID);
        if (popupElement)
        {
            document.body.removeChild(popupElement);
        }
        document.body.className = "";
        fx.ui.Link.PopupID = null;
    }
};

fx.ui.Image = fx.core.createType(fx.ui.Element,
{
    renderContent: function()
    {
        var html = [];
        html.push('<img class="fx-image"');
        if (this.Width) html.push(' width="' + this.Width + '"');
        if (this.Height) html.push(' height="' + this.Height + '"');
        if (this.StyleKey) html.push(' class="' + this.StyleKey + '"');
        html.push(' src="' + this.getValue(this.Source) + '" />');
        return html.join('');
    }
},
function(source)
{
    this.Source = source;
});

fx.ui.Tree = fx.core.createType(fx.ui.Element,
{
    add: function(item)
    {
        this.Items.push(item);
    },
    render: function()
    {
        var items = this.getValue(this.Items);
        var html;
        if (items)
        {
            html = this.renderItems(items, this.ItemsPath);
        }
        else
        {
            html = 'No Items';
        }
        return html;
    },
    renderItems: function(items, itemsPath)
    {
        var html = [];
        for (var i = 0; i < items.length; i++)
        {
            var item = items[i];
            var itemId = fx.core.id();
            fx.ui.Tree._items[itemId] = item;
            this.ItemTemplate.DataContext = item;
            html.push('<div class="fx-tree-node" onclick=\'fx.ui.Tree.onSelect("' + this.id + '", "' + itemId + '");\'>');
            if (item[itemsPath])
            {
                html.push('<span class="fx-tree-command">+</span>');
            }
            else
            {
                html.push('<span class="fx-tree-command">&nbsp;</span>');
            }
            html.push(this.ItemTemplate.render());
            html.push('</div>');
            if (item[itemsPath])
            {
                html.push('<div class="fx-tree-branch collapsed">');
                html.push(this.renderItems(item[itemsPath], itemsPath));
                html.push('</div>');
            }
        }
        return html.join('');
    }
},
function()
{
    this.Items = [];
    this.RenderContainer = true;
    this.ClassName = 'fx-tree';
});

fx.ui.Tree._items = {};

fx.ui.Tree.onSelect = function(id, itemId)
{
    var tree = fx.ui.Element._elements[id];
    tree.SelectedItem = fx.ui.Tree._items[itemId];
    if (tree.SelectionChanged instanceof fx.ui.ServiceBinding)
    {
        fx.ui.ServiceBinding.onAction(tree, tree.SelectionChanged);
    }
};

fx.ui.Frame = fx.core.createType(fx.ui.Element,
{
    render: function()
    {
        if (this.Name)
        {
            fx.ui.Frame.targets[this.Name] = this;
        }
        var html = '';
        var source = this.getValue(this.Source);
        if (source && source.render)
        {
            source.Parent = this;
            html = source.render();
        }
        else if (source)
        {
            var content_id = fx.core.id();
            var url = this.getContent(source);
            var _this = this;
            html = fx.ui.load(content_id, url, function(element)
            {
                element.Parent = _this;
                return element.render();
            });
        }
        else
        {
            html = 'Frame source "' + source + '" could not be resolved.';
        }
        return fx.ui.create(this.id, 'fx-frame', html);
    },
    setSource: function(source)
    {
        this.Source = source;
        fx.ui.html(this.id, this.render());
    }
},
function(source, name)
{
    this.Source = source;
    this.RenderContainer = true;
    this.ClassName = 'fx-frame';
    this.Name = name;
});

fx.ui.Frame.targets = {};

fx.ui.Frame.navigate = function(url, target)
{
    var frame = fx.ui.Frame.targets[target];
    if (frame)
    {
        frame.setSource(url);
    }
};
fx.ui.Form = fx.core.createType(fx.ui.Element,
{
    add: function (field)
    {
        this.Fields.push(field);
    },
    renderContent: function ()
    {
        if (this.Source instanceof fx.ui.ServiceBinding)
        {
            var form = this;
            this.Source.onAction(function (data)
            {
                if (form.Parent instanceof fx.ui.Step)
                {
                    var context = form.Parent.getContext();
                    var stepIndex = form.Parent.Parent.getStepIndex(form.Parent.id) + 1;
                    context['Step' + stepIndex] = data;
                }
                else
                {
                    form.DataContext = data;
                }
                fx.ui.html(form.id, form.renderForm());
            });
            return '<div class="fx-loading"><span>Loading...</span></div>';
        }
        else
        {
            return this.renderForm();
        }
    },
    renderForm: function ()
    {
        var panel = new fx.ui.Panel();
        var sections = {};
        var readOnly = this.getValue(this.ReadOnly);
        for (var i = 0; i < this.Fields.length; i++)
        {
            var item = this.Fields[i];
            var section = sections[item.Category];
            var field = new fx.ui.Field(item, readOnly);
            if (!section)
            {
                section = sections[item.Category] = new fx.ui.Section(item.Category || 'General');
                panel.add(section);
                section.Parent = panel;
            }
            section.HeaderStyle = this.SectionStyle;
            section.add(field);
            field.Parent = section;
        }
        panel.WrapElements = this.WrapSections;
        panel.Parent = this;
        return panel.render();
    }
},
function ()
{
    this.Fields = [];
    this.RenderContainer = true;
    this.ClassName = 'fx-form';
});

fx.ui.Field = fx.core.createType(fx.ui.Element,
{
    renderContent: function()
    {
        var html = [];
        if (!this.getValue(this.Hidden))
        {
            html.push('<label>');
            html.push(this.getContent(this.Label));
            html.push('</label>');
            this.renderField(html);
        }
        return html.join('');
    },
    renderField: function(html)
    {
        if (!this.getValue(this.Hidden))
        {
            if (this.Editor)
            {
                html.push(this.Editor.render());
            }
            else
            {
                if (!this.DisplayTemplate)
                {
                    var value = this.getValue(this.Value);
                    html.push('<span class="text-wrap">');
                    html.push(value);
                    html.push('</span>');
                }
                else
                {
                    this.DisplayTemplate.DataContext = this.DataContext;
                    html.push(this.DisplayTemplate.render());
                }
            }
        }
    }
},
function(field)
{
    if (field)
    {
        this.Value = field.Value;
        this.Editor = field.Editor;
        this.DisplayTemplate = field.DisplayTemplate;
        this.Label = field.Label;
        this.Category = field.Category;
        this.Hidden = field.Hidden;
    }
    this.RenderContainer = true;
    this.ClassName = 'fx-field';
});

fx.ui.Editor = fx.core.createType(fx.ui.Element,
{
    getValueChangedHandler: function (valueJS)
    {
        return 'fx.ui.Editor.onValueChanged("' + this.id + '",' + (valueJS || 'this.value') + ');';
    },
    Invalidate: function ()
    {
        var element = document.getElementById(this.id);
        if (element)
        {
            element.style.background = '#e7b6b6';
        }
        this.Invalid = true;
    },
    ClearValidation: function ()
    {
        var element = document.getElementById(this.id);
        if (element)
        {
            element.style.background = 'white';
        }
        this.Invalid = null;
    }
},
function (value, required, element)
{
    this.Value = new fx.ui.Binding(value, this);
    this.Required = required;
});

fx.ui.Editor.onValueChanged = function (id, value)
{
    var editor = fx.ui.Element._elements[id];
    editor.ClearValidation();
    editor.setValue(editor.Value, value);
};

fx.ui.FormContext = fx.core.createType(Object);

fx.ui.TextBox = fx.core.createType(fx.ui.Editor,
{
    renderContent: function ()
    {
        var html = [];
        var value = (this.getValue(this.Value) || '');
        if (this.Prefix)
        {
            this.RenderContainer = true;
            html.push('<span>');
            html.push(this.getContent(this.Prefix));
            html.push('</span>');
        }
        if (this.Lines && this.Lines > 1)
        {
            html.push('<textarea id="' + this.id + '" class="fx-text-box" ');
            html.push('rows="' + this.Lines + '"');
            html.push('onblur=\'' + this.getValueChangedHandler() + '\' ');
            html.push('>');
            html.push(value);
            html.push('</textarea>');
        }
        else
        {
            html.push('<input id="' + this.id + '" class="fx-text-box" type="text" ');
            if (this.PromptText)
            {
                html.push('value="' + (value || this.PromptText) + '" ');
                html.push('onfocus=\'if(this.value=="' + this.PromptText + '"){this.value="";}\' ');
                html.push('onblur=\'if(this.value==""){this.value="' + this.PromptText + '";}');
                html.push(this.getValueChangedHandler() + '\' ');
            }
            else
            {
                html.push('value="' + value + '" ');
                html.push('onblur=\'' + this.getValueChangedHandler() + '\' ');
            }
            if (this.MaxLength)
            {
                html.push('maxlength="' + this.MaxLength + '" ');
            }
            html.push('/>');
        }
        return html.join('');
    }
});

fx.ui.Password = fx.core.createType(fx.ui.Editor,
{
    renderContent: function()
    {
        var html = [];
        html.push('<input id="' + this.id + '" type="password" class="fx-text-box" ');
        html.push('onblur=\'' + this.getValueChangedHandler() + '\' ');
        html.push('/>');
        return html.join('');
    }
});

fx.ui.CustomField = fx.core.createType(fx.ui.Editor,
{
    renderContent: function()
    {
        var html = [];
        var urlToken = '?';
        if (this.Source.indexOf('?') > -1)
        {
            urlToken = '&';
        }
        var url = this.getContent(this.Source) + urlToken + 'CustomFieldId=' + this.id;
        html.push('<iframe id="' + this.id + '_frame" width="220" height="25" class="fx-custom-field" frameborder="0" ');
        if (navigator.userAgent.indexOf('IE 6') > -1)
        {
            var _this = this;
            this.interval = window.setInterval(function()
            {
                var frame = document.getElementById(_this.id + '_frame');
                if (frame)
                {
                    window.clearInterval(_this.interval);
                    frame.src = url;
                }
            }, 100);
        }
        else
        {
            html.push('src="' + url + '" ');
        }
        html.push('></iframe>');
        return html.join('');
    }
});

fx.ui.CustomField.AutoSize = function(id, width, height)
{
	var frame = document.getElementById(id + '_frame');
	frame.style.width = width + 'px';
	frame.style.height = height + 'px';
};

fx.ui.CheckBox = fx.core.createType(fx.ui.Editor,
{
	renderContent: function()
	{
		var html = [];
		html.push('<input type="checkbox" class="fx-check-box" ');
		if (this.OnClick)
		{
			html.push('onclick=\'' + this.OnClick + '\' ');
		}
		else
		{
			html.push('onclick=\'' + this.getValueChangedHandler('this.checked') + '\' ');
		}
		if (fx.core.getBool(this.getValue(this.Value)))
		{
			html.push('checked');
		}
		html.push('/>');
		return html.join('');
	}
});

fx.ui.ListBox = fx.core.createType(fx.ui.Editor,
{
    add: function (item)
    {
        if (!this.Items)
        {
            this.Items = [];
        }
        if (item instanceof fx.ui.Editor)
        {
            this.Items.push(item.getValue(item.Value));
        }
        else if (item instanceof fx.ui.Text)
        {
            this.Items.push(item.Content);
        }
        else
        {
            this.Items.push(item);
        }
    },
    renderContent: function ()
    {
        if (this.IsDataService)
        {
            var provider = new fx.ui.DataServicesGridProvider();
            provider.grid = this;
            this.DisablePaging = true;
            provider.setupData(this.Items, function (list, data)
            {
                if (data != null)
                {
                    fx.ui.html(list.id, function () { return list.renderItems(data); });
                }
                else
                {
                    fx.ui.html(list.id, 'No Data');
                }
            });
            return 'Loading...';
        }
        else
        {
            var items = this.getValue(this.Items);
            if (items instanceof fx.ui.ServiceBinding)
            {
                var _this = this;
                var httpGet = (items.ServiceAction.indexOf('?') > -1 ? true : null);
                var parameters = fx.core.toDictionary(items.ServiceParameters, function (binding) { return _this.getValue(binding); });
                fx.service.create(this.id, items.ServiceAction, parameters, httpGet, null, function (data)
                {
                    if (data != null)
                    {
                        fx.ui.html(_this.id, function () { return _this.renderItems(data); });
                    }
                    else
                    {
                        fx.ui.html(_this.id, 'No Data');
                    }
                });
                fx.service.update(this.id);
            }
            var html = '';
            if (items)
            {
                html = this.renderItems(items);
            }
            return html;
        }
    },
    renderItems: function (items)
    {
        var html = [];
        var value = (this.getValue(this.Value) || '');
        var listStyle = (fx.core.getEnum(fx.ui.ListBox.ListStyle, this.ListStyle) || fx.ui.ListBox.ListStyle.DropDown);
        if (listStyle == fx.ui.ListBox.ListStyle.RadioButtonList || listStyle == fx.ui.ListBox.ListStyle.CheckBoxList)
        {
            html.push('<div id="' + this.id + '" style="height:' + (this.ListSize ? this.ListSize * 20 : 150) + 'px;overflow-y:auto;">');
            if (items && items.length > 0)
            {
                for (var i = 0; i < items.length; i++)
                {
                    var itemId = fx.core.id();
                    var valueMember = (this.ValueMemberPath ? items[i][this.ValueMemberPath] : items[i]);
                    var displayMember = (this.DisplayMemberPath ? items[i][this.DisplayMemberPath] : items[i]);
                    if (this.WrapItems)
                    {
                        html.push('<span style="padding:3px 6px;">');
                    }
                    else
                    {
                        html.push('<div style="padding:3px 6px;clear:both;">');
                    }
                    html.push('<input id="' + itemId + '" type="' + (listStyle == fx.ui.ListBox.ListStyle.RadioButtonList ? 'radio' : 'checkbox') + '" class="fx-check-box" ');
                    html.push('onclick=\'' + this.getValueChangedHandler('"' + valueMember + '"') + '\' ');
                    html.push('name="' + this.id + '" style="float:left;"');
                    if (valueMember == value)
                    {
                        html.push('checked');
                    }
                    html.push('/>');
                    html.push('<label for="' + itemId + '" style="float:left;">' + displayMember + '</label>');
                    if (this.WrapItems)
                    {
                        html.push('</span>');
                    }
                    else
                    {
                        html.push('</div>');
                    }
                }
            }
            html.push('</div>');
        }
        else if (listStyle == fx.ui.ListBox.ListStyle.DropDown || listStyle == fx.ui.ListBox.ListStyle.ListBox)
        {
            html.push('<select id="' + this.id + '" class="fx-list-box" ');
            html.push('onchange=\'' + this.getValueChangedHandler() + '\' ');
            if (listStyle == fx.ui.ListBox.ListStyle.ListBox)
            {
                html.push('size="' + (this.ListSize ? this.ListSize : 4) + '" ');
            }
            html.push('>');
            html.push('<option value="" ');
            if (!value)
            {
                html.push('selected');
            }
            html.push('>');
            html.push('Select...');
            html.push('</option>');
            if (items && items.length > 0)
            {
                for (var i = 0; i < items.length; i++)
                {
                    var valueMember = (this.ValueMemberPath ? items[i][this.ValueMemberPath] : items[i]);
                    var displayMember = (this.DisplayMemberPath ? items[i][this.DisplayMemberPath] : items[i]);
                    html.push('<option value="' + valueMember + '" ');
                    if (valueMember == value)
                    {
                        html.push('selected');
                    }
                    html.push('>');
                    html.push(displayMember);
                    html.push('</option>');
                }
            }
            html.push('</select>');
        }
        return html.join('');
    }
},
function ()
{
    this.RenderContainer = true;
});

fx.ui.ListBox.ListStyle = fx.core.createEnum('DropDown', 'ListBox', 'RadioButtonList', 'CheckBoxList');

fx.ui.DateBox = fx.core.createType(fx.ui.Editor,
{
    renderContent: function ()
    {
        var date = this.getValue(this.Value);
        var today = (date && Date.parse(date) && new Date(Date.parse(date))) || new Date();
        var day = today.getDate();
        var month = today.getMonth() + 1;
        var year = today.getFullYear();
        var html = [];

        if (!date)
        {
            fx.ui.Editor.onValueChanged(this.id, month + "/" + day + "/" + year);
        }

        html.push('<table id="' + this.id + '" class="fx-text-box" cellspacing="0"><tr><td>');

        html.push('<select style="width:100%;" id="' + this.id + '_day" onchange=\'' + this.getValueChangedHandler('fx.ui.DateBox.getDate("' + this.id + '")') + '\'>');
        for (var d = 1; d <= 31; d++)
        {
            if (d == day)
            {
                html.push('<option value="' + d + '" selected>' + d + '</option>');
            }
            else
            {
                html.push('<option value="' + d + '">' + d + '</option>');
            }
        }
        html.push('</select>');

        html.push('</td><td>');

        var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        html.push('<select style="width:100%;" id="' + this.id + '_month" onchange=\'' + this.getValueChangedHandler('fx.ui.DateBox.getDate("' + this.id + '")') + '\'>');
        for (var m = 1; m <= 12; m++)
        {
            if (m == month)
            {
                html.push('<option value="' + m + '" selected>' + months[m - 1] + '</option>');
            }
            else
            {
                html.push('<option value="' + m + '">' + months[m - 1] + '</option>');
            }
        }
        html.push('</select>');

        html.push('</td><td>');

        html.push('<select style="width:100%;" id="' + this.id + '_year" onchange=\'' + this.getValueChangedHandler('fx.ui.DateBox.getDate("' + this.id + '")') + '\'>');
        for (var y = 1900; y <= 2100; y++)
        {
            if (y == year)
            {
                html.push('<option value="' + y + '" selected>' + y + '</option>');
            }
            else
            {
                html.push('<option value="' + y + '">' + y + '</option>');
            }
        }
        html.push('</select>');

        html.push('</td></tr></table>');

        return html.join('');
    }
});

fx.ui.DateBox.getDate = function (id)
{
    var editor = fx.ui.Element._elements[id];
    var dayList = document.getElementById(id + "_day");
    var monthList = document.getElementById(id + "_month");
    var yearList = document.getElementById(id + "_year");
    return monthList.value + "/" + dayList.value + "/" + yearList.value;
};

fx.ui.NumberBox = fx.core.createType(fx.ui.Editor,
{
    renderContent: function()
    {
        var html = [];
        var value = this.getValue(this.Value);
        var valueChangedHandler = this.getValueChangedHandler();
        html.push('<input id="' + this.id + '" type="text" class="fx-number-box" ');
        html.push('onkeypress=\'return fx.ui.NumberBox.onKeyPress("' + this.id + '",(event.keyCode || event.which));\' ');
        html.push('onkeydown=\'return fx.ui.NumberBox.onKeyDown("' + this.id + '",(event.keyCode || event.which));\' ');
        html.push('onblur=\'fx.ui.NumberBox.onChange("' + this.id + '");\' ');
        html.push('value="' + (value || 0) + '"');
        html.push('/>');
        return html.join('');
    }
});

fx.ui.NumberBox.onKeyPress = function(id, key)
{
    var editor = fx.ui.Element._elements[id];
    var allowDecimals = fx.core.getBool(editor.getValue(editor.AllowDecimals));
    if (key == 8 || key == 9 || key == 13 || key == 45 || (allowDecimals && key == 46))
    {
        return true;
    }
    else if (key != 38 && key != 40)
    {
        return !(key < 48 || key > 57);
    }
};

fx.ui.NumberBox.onKeyDown = function(id, key)
{
    var editor = fx.ui.Element._elements[id];
    var allowDecimals = fx.core.getBool(editor.getValue(editor.AllowDecimals));
    var field = document.getElementById(id);
    var negative = field.value.indexOf('-') == 0;
    var value = parseFloat((field.value.replace(/[^\d.]/g, '') || 0));
    var increment = (allowDecimals ? .01 : 1);
    if (negative)
    {
        value = (-value);
    }
    if (key == 38)
    {
        value += increment;
    }
    else if (key == 40)
    {
        value -= increment;
    }
    field.value = value;
    fx.ui.NumberBox.onChange(id);
    return true;
};

fx.ui.NumberBox.onChange = function(id)
{
    var editor = fx.ui.Element._elements[id];
    var field = document.getElementById(id);
    var allowDecimals = fx.core.getBool(editor.getValue(editor.AllowDecimals));
    var negative = field.value.indexOf('-') == 0;
    var value = parseFloat((field.value.replace(/[^\d.]/g, '') || 0));
    if (negative)
    {
        value = (-value);
    }
    if (editor.MinValue && value < editor.MinValue)
    {
        value = editor.MinValue;
    }
    if (editor.MaxValue && value > editor.MaxValue)
    {
        value = editor.MaxValue;
    }
    if (!allowDecimals)
    {
        value = Math.round(value);
    }
    field.value = value;
    fx.ui.Editor.onValueChanged(id, value);
};


fx.ui.Grid = fx.core.createType(fx.ui.Element,
{
    add: function (column)
    {
        this.Columns.push(column);
    },
    renderContent: function ()
    {
        this.Provider.grid = this;
        this.SelectedRow = null;
        this.SelectedItems = null;
        var data = this.Provider.setupData(this.Items, fx.ui.Grid.load);
        var html = '';
        if (data)
        {
            html = this.renderItems(data);
        }
        return html;
    },
    renderItems: function (items)
    {
        var html = [];
        this.SelectedRow = null;
        this.SelectedItems = null;
        var alignment = fx.core.getEnum(fx.ui.HorizontalAlignment, this.HorizontalAlignment);
        html.push('<table class="fx-grid" cellspacing="0" cellpadding="0" ');
        if (this.getStretch())
        {
            html.push(' width="100%" ');
        }
        switch (alignment)
        {
            case fx.ui.HorizontalAlignment.Center:
                html.push(' align="center" ');
                break;
            case fx.ui.HorizontalAlignment.Right:
                html.push(' align="right" ');
                break;
        }
        html.push('>');
        var columnSpan = this.renderColumns(html);
        html.push('<tbody>');
        if (items != null && items.length > 0)
        {
            for (var r = 0; r < items.length; r++)
            {
                html.push('<tr id="' + this.id + '-row-' + r + '"');
                html.push(fx.ui.createHover('', 'row-hover'));
                html.push(' onclick=\'fx.ui.Grid.select("' + this.id + '", ' + r + ');\'');
                html.push('>');
                for (var c = 0; c < this.Columns.length; c++)
                {
                    var column = this.Columns[c];
                    if (!this.getValue(column.Hidden))
                    {
                        html.push('<td>');
                        column.DataContext = items[r];
                        html.push(column.render());
                        html.push('</td>');
                    }
                }
                html.push('</tr>');
            }
        }
        else
        {
            html.push('<tr><td colspan="' + columnSpan + '">');
            html.push('<span class="fx-grid-empty">No results found.</span>');
            html.push('</td></tr>');
        }
        html.push('</tbody>');
        this.renderFooter(html, columnSpan);
        html.push('</table>');
        return html.join('');
    },
    renderColumns: function (html)
    {
        html.push('<thead>');
        this.renderFilters(html);
        html.push('<tr>');
        var columnSpan = 0;
        for (var c = 0; c < this.Columns.length; c++)
        {
            var column = this.Columns[c];
            if (!this.getValue(column.Hidden))
            {
                html.push('<th>');
                if (fx.core.getBool(this.AllowSorting) && fx.core.getBool(column.AllowSort) && column.Value && column.Value.Path)
                {
                    html.push('<a href=\'javascript:fx.ui.Grid.sort("' + this.id + '", "' + column.Value.Path + '");\'>');
                    html.push(column.Label);
                    html.push('</a>');
                }
                else
                {
                    html.push(column.Label);
                }
                html.push('</th>');
                columnSpan++;
            }
        }
        if (columnSpan == 0)
        {
            columnSpan += 1;
            html.push('<tr><th colspan="' + columnSpan + '">');
            html.push('No Data');
            html.push('</th></tr>');
        }
        html.push('</tr>');
        html.push('</thead>');
        return columnSpan;
    },
    renderFilters: function (html)
    {
        if (fx.core.getBool(this.AllowFiltering))
        {
            var columnSpan = 0;
            var filters = [];
            for (var c = 0; c < this.Columns.length; c++)
            {
                var column = this.Columns[c];
                if (!this.getValue(column.Hidden))
                {
                    if (column.AllowFilter)
                    {
                        filters.push('<option value="' + column.Value.Path + '"');
                        if (this.FilterColumn == column.Value.Path)
                        {
                            filters.push(' selected');
                        }
                        filters.push('>' + column.Label + '</option>');
                    }
                    columnSpan++;
                }
            }
            if (filters.length > 0)
            {
                html.push('<tr class="filter-row"><td colspan="' + columnSpan + '">');
                html.push('<span class="filter-label">Search:</span>');
                html.push('<select class="filter-list" id="' + this.id + '-filter">' + filters.join('') + '</select>');
                html.push('<input class="filter-text" id="' + this.id + '-search" ');
                html.push('value="' + (this.FilterValue || '') + '" ');
                html.push('type="text" onkeyup=\'if((event.keyCode || event.which)==13)fx.ui.Grid.filter("' + this.id + '");\' />');
                html.push('<a class="filter-button" href=\'javascript:fx.ui.Grid.filter("' + this.id + '");\'>');
                html.push('<span>GO</span>');
                html.push('</a>');
                html.push('</td></tr>');
            }
        }
    },
    renderFooter: function (html, columnSpan)
    {
        html.push('<tfoot>');
        if (fx.core.getBool(this.AllowPaging))
        {
            html.push('<tr class="pager-row"><td colspan="' + columnSpan + '" align="right">');
            if (this.RecordCount > this.PageSize)
            {
                var previousPage = this.PageIndex == 0 ? 0 : this.PageIndex - 1;
                var nextPage = this.PageIndex + 1;
                var lastPage = Math.ceil(this.RecordCount / this.PageSize);
                html.push('<span style="float:left;">' + this.RecordCount + ' records</span>');
                html.push('<a class="page-first" href=\'javascript:fx.ui.Grid.page("' + this.id + '", 0);\'><span>|&lt;</span></a>');
                html.push('<a class="page-previous" href=\'javascript:fx.ui.Grid.page("' + this.id + '", ' + previousPage + ');\'><span>&lt;&lt;</span></a>');
                html.push('<span class="page-index">Page ' + nextPage + ' of ' + lastPage + '</span>');
                html.push('<a class="page-next" href=\'javascript:fx.ui.Grid.page("' + this.id + '", ' + nextPage + ');\'><span>&gt;&gt;</span></a>');
                html.push('<a class="page-last" href=\'javascript:fx.ui.Grid.page("' + this.id + '", ' + (lastPage - 1) + ');\'><span>&gt;|</span></a>');
            }
            else
            {
                html.push('<span style="float:left;padding:3px;">' + this.RecordCount + ' records</span>');
            }
            html.push('</td></tr>');
        }
        html.push('</tfoot>');
    }
},
function (items)
{
    this.Columns = [];
    this.Items = items;
    this.PageIndex = 0;
    this.PageSize = 10;
    this.RecordCount = 1000;
    this.AllowFiltering = false;
    this.AllowSorting = false;
    this.AllowPaging = false;
    this.SortAscending = true;
    this.Provider = new fx.ui.Grid.defaultProvider(this);
    this.RenderContainer = true;
});

fx.ui.Grid.sort = function(id, column)
{
    var grid = fx.ui.Element._elements[id];
    if (!fx.core.isVirtual(grid.Provider, 'applySort'))
    {
        grid.Provider.applySort(column);
    }
};

fx.ui.Grid.page = function (id, index)
{
    var grid = fx.ui.Element._elements[id];
    if (!fx.core.isVirtual(grid.Provider, 'applyPaging'))
    {
        grid.Provider.applyPaging(index);
    }
};

fx.ui.Grid.filter = function(id)
{
    var grid = fx.ui.Element._elements[id];
    if (!fx.core.isVirtual(grid.Provider, 'applyFilter'))
    {
        grid.Provider.applyFilter();
    }
};

fx.ui.FilterType = fx.core.createEnum('Equals', 'NotEquals', 'StartsWith', 'NotStartsWith', 'EndsWith', 'NotEndsWith', 'Contains', 'NotContains');

fx.ui.Grid.select = function (id, row)
{
    var grid = fx.ui.Element._elements[id];
    if (grid.SelectedRow && document.getElementById(grid.SelectedRow))
    {
        document.getElementById(grid.SelectedRow).className = "";
    }
    grid.SelectedRow = grid.id + "-row-" + row;
    if (grid.DetailsTemplate)
    {
        var panel = new fx.ui.Panel();
        panel.Parent = grid.Parent;
        panel.WrapElements = grid.DetailsOnRight;
        panel.add(grid);
        grid.DetailsTemplate.DataContext = grid.Provider.getSelectedItem(row);
        panel.add(grid.DetailsTemplate);
        fx.ui.html(grid.id, function () { return panel.render(); });
    }
    if (grid.SelectedRow && document.getElementById(grid.SelectedRow))
    {
        document.getElementById(grid.SelectedRow).className = "row-selected";
    }
    if (grid.Parent instanceof fx.ui.Step)
    {
        grid.Parent.OnGridSelected(grid.Provider.getSelectedItem(row));
    }
};
fx.ui.Grid.selectMultiple = function(id, row, selected)
{
	var grid = fx.ui.Element._elements[id];
	if (!grid.SelectedItems)
	{
		grid.SelectedItems = {};
	}
	var element = document.getElementById(id + "-row-" + row);
	if (selected)
	{
		element.className = "row-selected";
		grid.SelectedItems[row] = grid.Provider.getSelectedItem(row);
	}
	else
	{
		element.className = "";
		delete grid.SelectedItems[row];
	}
};

fx.ui.Grid.load = function(grid, data)
{
    if (data != null)
    {
        if (!fx.core.isVirtual(grid.Provider, 'setupColumns'))
        {
            grid.Provider.setupColumns(data[0]);
        }
        else
        {
            grid.Provider.setupColumns(grid, data[0]);
        }
        fx.ui.html(grid.id, function() { return grid.renderItems(data); });
    }
    else
    {
        fx.ui.html(grid.id, 'No Data');
    }
};

fx.ui.Column = fx.core.createType(fx.ui.Field,
{
    renderContent: function()
    {
        var html = [];
        this.renderField(html);
        return html.join('');
    }
},
function(columnName)
{
    this.Value = new fx.ui.Binding(columnName, this);
    this.Label = (columnName && columnName.replace('_', ' ')) || '';
    this.AllowSort = true;
    this.AllowFilter = true;
});

fx.ui.Grid.Provider = fx.core.createType(Object,
{
    setupData: fx.core.abstractMethod('fx.ui.Grid.Provider.setupData(items, callback)'),
    setupColumns: fx.core.abstractMethod('fx.ui.Grid.Provider.setupColumns(item)'),
    getSelectedItem: fx.core.abstractMethod('fx.ui.Grid.Provider.getSelectedItem(row)'),
    addColumn: fx.core.virtualMethod('fx.ui.Grid.Provider.addColumn(column, item)'),
    applySort: fx.core.virtualMethod('fx.ui.Grid.Provider.applySort(column)'),
    applyPaging: fx.core.virtualMethod('fx.ui.Grid.Provider.applyPaging(index)'),
    applyFilter: fx.core.virtualMethod('fx.ui.Grid.Provider.applyFilter()')
},
function(grid)
{
    this.grid = grid;
});

fx.ui.Grid.defaultProvider = fx.core.createType(fx.ui.Grid.Provider,
{
    syncContext: function (context)
    {
        if (context)
        {
            this.grid.AllowSorting = context.AllowSorting;
            this.grid.AllowPaging = context.AllowPaging;
            this.grid.AllowFiltering = context.AllowFiltering;
            this.grid.PageIndex = context.PageIndex;
            this.grid.PageSize = context.PageSize;
            this.grid.RecordCount = context.RecordCount;
            this.grid.SortColumn = context.SortColumn;
            this.grid.SortAscending = context.SortAscending;
            this.grid.FilterColumn = context.FilterColumn;
            this.grid.FilterType = context.FilterType;
            this.grid.FilterValue = context.FilterValue;
        }
    },
    setupData: function (items, callback)
    {
        if (!fx.core.getBool(this.grid.DisableCaching) && this.cache)
        {
            return this.cache;
        }
        else if (items instanceof fx.ui.ServiceBinding)
        {
            var _this = this;
            var httpGet = (items.ServiceAction.indexOf('?') > -1 ? true : null);
            var parameters = fx.core.toDictionary(items.ServiceParameters, function (binding) { return _this.grid.getValue(binding); });
            fx.service.create(this.grid.id, items.ServiceAction, parameters, httpGet, null, function (data, context)
            {
                _this.cache = data;
                _this.syncContext(context);
                callback(_this.grid, data);
            }, 'GridContext');
            this.update();
        }
        else if (items)
        {
            return fx.ui.Grid.ClientProvider.update(this.grid);
        }
    },
    setupColumns: function (item)
    {
        if (item && (!this.grid.Columns || this.grid.Columns.length == 0))
        {
            this.grid.Columns = [];
            if (typeof (item) == 'object')
            {
                for (var column in item)
                {
                    this.grid.Provider.addColumn(column, item);
                }
            }
            if (this.grid.Columns.length == 0)
            {
                this.grid.Columns.push(new fx.ui.Column(''));
            }
        }
    },
    addColumn: function (column, item)
    {
        if (column.indexOf('_') != 0 && column.indexOf('ID') != column.length - 2)
        {
            this.grid.Columns.push(new fx.ui.Column(column));
        }
    },
    getSelectedItem: function (row)
    {
        if (this.cache)
        {
            return this.cache[row];
        }
    },
    applySort: function (column)
    {
        this.grid.PageIndex = 0;
        if (this.grid.SortColumn == column)
        {
            this.grid.SortAscending = !this.grid.SortAscending;
        }
        this.grid.SortColumn = column;
        this.update();
    },
    applyPaging: function (index)
    {
        var lastPage = Math.round(this.grid.RecordCount / this.grid.PageSize);
        if (index != this.grid.PageIndex && index > -1 && index <= lastPage)
        {
            this.grid.PageIndex = index;
            this.update();
        }
    },
    applyFilter: function ()
    {
        this.grid.PageIndex = 0;
        this.grid.FilterColumn = document.getElementById(this.grid.id + '-filter').value;
        this.grid.FilterType = fx.ui.FilterType.StartsWith;
        this.grid.FilterValue = document.getElementById(this.grid.id + '-search').value;
        this.update();
    },
    update: function ()
    {
        var service = fx.services[this.grid.id];
        if (service)
        {
            service.headers =
            {
                GridContext:
                {
                    AllowPaging: (this.grid.AllowPaging || true),
                    AllowSorting: (this.grid.AllowSorting || true),
                    AllowFiltering: (this.grid.SortAscending || true),
                    PageIndex: (this.grid.PageIndex || 0),
                    PageSize: (this.grid.PageSize || 10),
                    RecordCount: (this.grid.RecordCount || 0),
                    SortColumn: this.grid.SortColumn,
                    SortAscending: (this.grid.SortAscending || false),
                    FilterColumn: this.grid.FilterColumn,
                    FilterType: (this.grid.FilterType || fx.ui.FilterType.StartsWith),
                    FilterValue: this.grid.FilterValue,
                    __type: 'GridContext:#Itrica.Runtime.Contracts.ServiceModel'
                }
            };
            fx.service.update(this.grid.id);
        }
        else
        {
            var items = fx.ui.Grid.ClientProvider.update(this.grid);
            fx.ui.Grid.load(this.grid, items);
        }
    }
});

fx.ui.Grid.ClientProvider =
{
    update: function(grid)
    {
        var items = grid.getValue(grid.Items);
        if (items)
        {
            if (fx.core.getBool(grid.AllowFiltering) && grid.FilterColumn && grid.FilterValue)
            {
                var filter = [];
                for (var i = 0; i < items.length; i++)
                {
                    if (items[i][grid.FilterColumn].toLowerCase().indexOf(grid.FilterValue.toLowerCase()) > -1)
                    {
                        filter.push(items[i]);
                    }
                }
                items = filter;
            }
            grid.RecordCount = items.length;
            if (fx.core.getBool(grid.AllowSorting) && grid.SortColumn)
            {
                items.sort(function(a, b)
                {
                    if (grid.SortAscending)
                    {
                        return a[grid.SortColumn] < b[grid.SortColumn] ? 0 : 1;
                    }
                    else
                    {
                        return a[grid.SortColumn] > b[grid.SortColumn] ? 0 : 1;
                    }
                });
            }
            if (fx.core.getBool(grid.AllowPaging))
            {
                var page = [];
                var index = (grid.PageIndex * grid.PageSize);
                for (var i = index; i < items.length && i < (index + grid.PageSize); i++)
                {
                    page.push(items[i]);
                }
                items = page;
            }
        }
        return items;
    }
};
fx.ui.DataServicesGridProvider = fx.core.createType(fx.ui.Grid.defaultProvider,
{
    getheaders: function ()
    {
        return { Accept: 'application/json', 'Content-Type': 'application/json' };
    },
    _create: function (action, params, callback)
    {
        var _this = this;
        this.BaseUrl = action;
        var parameters = fx.core.toDictionary(params, function (binding) { return _this.grid.getValue(binding); });
        fx.service.create(this.grid.id, action, parameters, true, this.getheaders(), function (data)
        {
            if (data && data.error)
            {
                fx.ui.html(_this.grid.id, data.error.message.value);
            }
            else
            {
                _this.cache = data.results;
                _this.grid.RecordCount = data.__count;
                callback(_this.grid, data.results);
            }
        }, null, fx.service._jsonParser);
        this.update();
    },
    setupData: function (items, callback)
    {
        this.grid.AllowSorting = true;
        this.grid.AllowFiltering = true;
        this.grid.AllowPaging = true;
        if (!fx.core.getBool(this.grid.DisableCaching) && this.cache)
        {
            return this.cache;
        }
        else if (items instanceof fx.ui.ServiceBinding)
        {
            this._create(items.ServiceAction, items.ServiceParameters, callback);
        }
        else
        {
            items = this.grid.getValue(items);
            if (items && items.__deferred)
            {
                this._create(items.__deferred.uri, null, callback);
            }
            else
            {
                fx.core.log('DataServicesGridProvider can only be used with a ServiceBinding');
            }
        }
    },
    addColumn: function (column, item)
    {
        if (column.indexOf('_') != 0 && (!item[column] || !item[column].__deferred) && column.indexOf('ID') != column.length - 2)
        {
            this.grid.Columns.push(new fx.ui.Column(column));
        }
    },
    update: function ()
    {
        var service = fx.services[this.grid.id];
        if (service)
        {
            service.action = this.BaseUrl + (this.BaseUrl.indexOf('?') > -1 ? '&' : '?') + '$inlinecount=allpages';
            if (fx.core.getBool(this.grid.AllowFiltering) && this.grid.FilterColumn && this.grid.FilterValue)
            {
                service.action += '&$filter=indexof(' + this.grid.FilterColumn + ',\'' + this.grid.FilterValue + '\')gt-1';
            }
            if (fx.core.getBool(this.grid.AllowSorting) && this.grid.SortColumn)
            {
                service.action += '&$orderby=' + this.grid.SortColumn + (this.grid.SortAscending ? ' asc' : ' desc');
            }
            if (fx.core.getBool(this.grid.AllowPaging) && !this.grid.DisablePaging)
            {
                service.action += '&$skip=' + (this.grid.PageIndex * this.grid.PageSize) + '&$top=' + this.grid.PageSize;
            }
            fx.service.update(this.grid.id);
        }
    }
});
fx.ui.MockGridProvider = fx.core.createType(fx.ui.Grid.defaultProvider,
{
    setupData: function(items, callback)
    {
        if (!items)
        {
            var mockItems = [];
            for (var i = 0; i < 50; i++)
            {
                var mockObj = {};
                for (var c = 0; c < this.grid.Columns.length; c++)
                {
                    var column = this.grid.Columns[c];
                    if (!column.Value.Path)
                    {
                        column.Value.Path = column.Label;
                    }
                    mockObj[column.Value.Path] = column.Label + ' ' + i;
                }
                mockItems.push(mockObj);
            }
            this.grid.Items = mockItems;
            this.grid.AllowSorting = true;
            this.grid.AllowPaging = true;
            this.grid.AllowFiltering = true;
            this.grid.PageSize = 10;
            return fx.ui.Grid.ClientProvider.update(this.grid);
        }
        else if (items)
        {
            return fx.ui.Grid.ClientProvider.update(this.grid);
        }
    },
    update: function()
    {
        var items = fx.ui.Grid.ClientProvider.update(this.grid);
        fx.ui.Grid.load(this.grid, items);
    }
});


fx.ui.Wizard = fx.core.createType(fx.ui.Panel,
{
    renderContent: function ()
    {
        var context = this.DataContext;
        if (context != null)
        {
            for (var key in context)
            {
                if (key != "base" && key.indexOf("_") != 0 && key.indexOf("Step") == -1)
                {
                    delete context[key];
                }
                else if (key.indexOf("Step") == 0)
                {
                    context[key] = {};
                }
            }
        }
        return this.renderWizard(0);
    },
    renderWizard: function (index)
    {
        if (this.Elements && this.Elements.length > 0 && this.Elements.length > index)
        {
            var html = [];
            if (!fx.core.getBool(this.HideBreadcrumb))
            {
                html.push('<div class="fx-breadcrumb">');
                html.push(this.renderBreadcrumb(index));
                html.push('</div>');
            }

            html.push('<div class="fx-step">');
            html.push(this.renderStep(index));
            html.push('</div>');

            if (!fx.core.getBool(this.HideNavbar))
            {
                html.push('<div class="fx-navbar">');
                html.push(this.renderButtons(index));
                html.push('</div>');
            }
            return html.join('');
        }
        else
        {
            return '';
        }
    },
    renderBreadcrumb: function (index)
    {
        var html = [];
        if (this.ReturnUrl)
        {
            html.push('<a href="' + this.ReturnUrl + '">');
            html.push((this.ReturnUrlLabel || '<< Back'));
            html.push('</a>');
            html.push('<span class="fx-separator">');
            html.push('&nbsp;&nbsp;>>&nbsp;&nbsp;');
            html.push('</span>');
        }
        for (var i = 0; i < this.Elements.length; i++)
        {
            if (i == index)
            {
                html.push('<span class="fx-selected-item">');
                html.push(this.Elements[i].Title);
                html.push('</span>');
            }
            else
            {
                html.push('<a href="javascript:void(0);" onclick=\'fx.ui.Wizard.GoToStep("' + this.id + '",' + i + ');\'>');
                html.push(this.Elements[i].Title);
                html.push('</a>');
            }
            if (i != this.Elements.length - 1)
            {
                html.push('<span class="fx-separator">');
                html.push('&nbsp;&nbsp;>>&nbsp;&nbsp;');
                html.push('</span>');
            }
        }
        return html.join('');
    },
    renderStep: function (index)
    {
        return this.Elements[index].render();
    },
    renderButtons: function (index)
    {
        var html = [];
        html.push('<table width="100%"><tr>');
        html.push('<td width="100%">');
        if (this.ReturnUrl && !fx.core.getBool(this.Elements[index].RequireConfirmation))
        {
            html.push('<a href="' + this.ReturnUrl + '">');
            html.push((this.ReturnUrlLabel || 'Cancel'));
            html.push('</a>');
        }
        html.push('</td>');
        if (index > 0)
        {
            html.push('<td nowrap>');
            html.push('<a href="javascript:void(0);" onclick=\'fx.ui.Wizard.GoToStep("' + this.id + '",' + (index - 1) + ');\'>');
            html.push(((fx.core.getBool(this.Elements[index].RequireConfirmation) ? 'Decline' : null) || this.PreviousLabel || '<< Previous'));
            html.push('</a>');
            html.push('</td>');
        }
        if (index < this.Elements.length - 1)
        {
            html.push('<td nowrap>');
            html.push('<a class="primary-button" href="javascript:void(0);" onclick=\'fx.ui.Wizard.onNext("' + this.id + '","' + this.Elements[index].id + '",' + (index + 1) + ');\'>');
            html.push('<span>');
            html.push(((fx.core.getBool(this.Elements[index].RequireConfirmation) ? 'Accept' : null) || this.NextLabel || 'Next >>'));
            html.push('</span>');
            html.push('</a>');
            html.push('</td>');
        }
        else if (this.Elements[index].Next)
        {
            html.push('<td nowrap>');
            html.push('<a class="primary-button" href="javascript:void(0);" onclick=\'fx.ui.Wizard.onNext("' + this.id + '","' + this.Elements[index].id + '",' + (index + 1) + ');\'>');
            html.push('<span>');
            html.push(((fx.core.getBool(this.Elements[index].RequireConfirmation) ? 'Accept' : null) || this.FinishLabel || 'Finish >|'));
            html.push('</span>');
            html.push('</a>');
            html.push('</td>');
        }
        html.push('</tr></table>');
        return html.join('');
    },
    getStepIndex: function (stepId)
    {
        for (var i = 0; i < this.Elements.length; i++)
        {
            if (this.Elements[i].id == stepId)
            {
                return i;
            }
        }
        return -1;
    }
},
function (icon, title, description)
{
    this.Icon = icon;
    this.Title = title;
    this.Description = description;
    this.ClassName = 'fx-wizard';
    this.RenderContainer = true;
    this.DataContext = { Step1: {} };
});

fx.ui.Wizard.onNext = function (id, stepId, index)
{
    var wizard = fx.ui.Element._elements[id];
    var step = fx.ui.Element._elements[stepId];
    var context = wizard.getContext();
    var stepContext = {};
    var currentIndex = index - 1;
    if (currentIndex > -1)
    {
        if (step.Elements[0] instanceof fx.ui.Grid)
        {
            var grid = step.Elements[0];
            if (fx.core.getBool(grid.SelectMultiple) && grid.SelectedItems)
            {
                var items = [];
                for (var item in grid.SelectedItems)
                {
                    if (grid.SelectedItemPath)
                    {
                        items.push(grid.SelectedItems[item][grid.SelectedItemPath]);
                    }
                    else
                    {
                        items.push(grid.SelectedItems[item]);
                    }
                }
                if (fx.core.getBool(grid.RequireSelection) && items.length == 0)
                {
                    alert('Please select at least one item to continue.');
                    return;
                }
                else if (items.length < grid.MustSelect)
                {
                    alert('Please select at least ' + grid.MustSelect + ' item(s) to continue.');
                    return;
                }
                else if (items.length == grid.MustSelect || (!grid.MustSelect))
                {
                    stepContext = items;
                }
            }
            else if (grid.SelectedRow)
            {
                stepContext = grid.Provider.getSelectedItem(grid.SelectedRow.split('-row-')[1]);
            }
            else if (fx.core.getBool(grid.RequireSelection) || grid.MustSelect == 1)
            {
                alert('Please select an item to continue.');
                return;
            }
        }
        if (step.Elements[0] instanceof fx.ui.Form)
        {
            var validationMessages = [];
            var form = step.Elements[0];
            for (var i = 0; i < form.Fields.length; i++)
            {
                var field = form.Fields[i];
                if (field.Editor && fx.core.getBool(field.Editor.Required) && !wizard.getValue(field.Editor.Value))
                {
                    field.Editor.Invalidate();
                    validationMessages.push(field.Label + ' is required.');
                }
                else if (field.Editor && field.Editor.Invalid)
                {
                    validationMessages.push(field.Label + ' is invalid.');
                }
            }
            if (validationMessages.length > 0)
            {
                alert(validationMessages.join('\n'));
                return;
            }
        }
    }
    if (context)
    {
        var newContext = {};
        for (var key in context)
        {
            if (key != "base" && key.indexOf("_") != 0)
            {
                if (stepContext && key.indexOf("Step") == -1)
                {
                    stepContext[key] = context[key];
                }
                newContext[key] = context[key];
            }
        }
        for (var key in stepContext)
        {
            newContext["Step" + index] = stepContext;
            break;
        }
        wizard.DataContext = newContext;
    }
    if (step.Next instanceof fx.ui.ServiceBinding)
    {
        if (index < wizard.Elements.length)
        {
            step.OnActionCompleted = 'fx.ui.Wizard.GoToStep("' + id + '",' + index + ');';
        }
        else if (wizard.ReturnUrl)
        {
            step.OnActionCompleted = 'document.location = "' + wizard.ReturnUrl + '";';
        }
        else
        {
            step.OnActionCompleted = 'alert("Wizard Complete!");';
        }
        var cbk = step.Elements[0].OutputHandler;
        if (cbk) cbk = window[cbk];
        fx.ui.ServiceBinding.onAction(step, step.Next, cbk);
    }
    else
    {
        fx.ui.Wizard.GoToStep(id, index);
    }
};

fx.ui.Wizard.GoToStep = function (id, index)
{
    var wizard = fx.ui.Element._elements[id];
    if (wizard.Elements[index].Url)
    {
        var url = wizard.getContent(wizard.getValue(wizard.Elements[index].Url));
        if (url.indexOf(".ashx?") > -1 || url.indexOf("http") == 0)
        {
            var step = window.open(url);
            if (index + 1 < wizard.Elements.length)
            {
                fx.ui.Wizard.GoToStep(id, index + 1);
            }
            else if (wizard.ReturnUrl)
            {
                document.location = wizard.ReturnUrl;
            }
            if (!step)
            {
                document.location = url;
            }
        }
        else
        {
            fx.ui.Link.showWindow(url);
            fx.ui.Link.keepPopup = true;
            if (index + 1 < wizard.Elements.length)
            {
                fx.ui.Wizard.GoToStep(id, index + 1);
            }
            else if (wizard.ReturnUrl)
            {
                document.location = wizard.ReturnUrl;
            }
            window.setTimeout(function ()
            {
                fx.ui.Link.keepPopup = null;
            }, 100);
        }
    }
    else
    {
        fx.ui.html(id, wizard.renderWizard(index));
    }
};

fx.ui.Step = fx.core.createType(fx.ui.Panel,
{
    renderContent: function ()
    {
        var html = [];
        if (this.Description)
        {
            html.push('<div class="fx-description">');
            html.push(this.Description);
            html.push('</div>');
        }
        html.push(this.base.renderContent.call(this));
        return html.join('');
    },
    OnGridSelected: function (grid, item)
    {
        if (fx.core.getBool(grid.SelectMultiple) && grid.SelectedItems)
        {
        }
        else
        {
            var stepIndex = this.Parent.getStepIndex(this.id);
            //fx.ui.Wizard.onNext(this.Parent.id, this.id, stepIndex + 1);
        }
    }
},
function (title, description)
{
    this.Title = title;
    this.Description = description;
});

fx.core.log = function(message)
{
    alert(message);
};

