﻿/*
vNexus 18/10/2010
*/



(function($) {

    delayedTransition = function(targetid) {
        var elm = $('#' + targetid)[0];
        elm.style.WebkitTransform = 'rotate(77deg)';
        elm.style.opacity = '0.5';
    };

    var global_vLog = false;

    $.fn.vNexus = function(options) {

        var Config = {
            logging: false
        };
        if (options) {
            jQuery.extend(Config, options);
        };

        var Us = this;
        var vValidatorList = {};
        var vPanelList = {};
        var vTooltipList = {};
        var vDatasourceList = {};
        var vRendererList = {};
        var vEditList = {};

        global_vLog = Config.logging;

        //FOR TABLE COLUMNS
        function lookupcell(thetable, rownumber, columnnumber) {
            var idx = 0;
            var tot = thetable.rows.length;
            tot = thetable.rows[rownumber].cells.length;
            for (i = 0; i < thetable.rows[rownumber].cells.length; i++) {
                if (columnnumber == idx) return thetable.rows[rownumber].cells[i];
                idx = idx + thetable.rows[rownumber].cells[i].colSpan;
            }
            return null;
        }


        function iscolumnvisible(thetable, whichcolumn) {
            var tc = lookupcell(thetable, 0, whichcolumn);
            if (tc == null) return false;
            if (tc.style.display == 'none') return false;
            return true;
        }

        function hidecolumns(thetable, whichcolumns) {
            for (ir = 0; ir < thetable.rows.length; ir++) {
                //get the cell then hide it
                for (j = 0; j < whichcolumns.length; j++) {
                    var tc = lookupcell(thetable, ir, whichcolumns[j]);
                    if (tc != null) tc.style.display = 'none';
                }
            }
            return true;
        }

        function showcolumns(thetable, whichcolumns) {
            for (ir = 0; ir < thetable.rows.length; ir++) {
                for (j = 0; j < whichcolumns.length; j++) {
                    var tc = lookupcell(thetable, ir, whichcolumns[j]);
                    if (tc != null) tc.style.display = '';
                }
            }
            return true;
        }

        function getTransformProperty(element) {
            // Try transform first for forward compatibility
            var properties = ['transform', 'WebkitTransform', 'MozTransform'];
            var p;
            while (p = properties.shift()) {
                if (typeof element.style[p] != 'undefined') {
                    return p;
                }
            }

            return '';
        }



        function animatestyle(showing, targetid, currentid, $allpanels, stylestring) {
            if (stylestring == null || stylestring == '') {
                if (showing) $('#' + targetid).show();
                else $('#' + targetid).hide();
                return;
            }
            var $targ = $('#' + targetid);
            var styles = stylestring.split(",");
            switch (styles[0]) {
                case '3Dpanels':
                    //only respond to show
                    if (!showing) return;
                    //the panels are spun around

                    //get the master div (the one we spin)
                    $master = $targ.closest('.panel3D');

                    //sort out which is the front face (current panel) and the next back face (target panel)

                    //we leave the front face alone (because it will flip behind)

                    //but we need to make the right back one visible with the same class as the other back
                    if ($('#' + currentid).hasClass('panelfront')) {
                        //make the existing back hidden
                        $allpanels.siblings('.panelback').removeClass('panelback').hide();
                        //make the target the opposite
                        $targ.removeClass('panelfront').addClass('panelback').show();
                    } else {
                        $allpanels.siblings('.panelfront').removeClass('panelfront').hide();
                        $targ.removeClass('panelback').addClass('panelfront').show();
                    }


                    //finally spin the container

                    if ($master.hasClass('flipped3D')) {
                        $master.removeClass('flipped3D');
                    } else {
                        $master.addClass('flipped3D');
                    }

                    break;
                case 'css':
                    var addcss = styles[1];
                    var initcss = styles[2] == null ? '' : styles[2];

                    //is there an init style?
                    if (initcss != "") {
                        //if so we set this and then have to run the next transition on another thread
                        $targ.addClass(initcss).show();
                        setTimeout("$('#" + targetid + "').addClass('" + addcss + "');", 0);
                        $("#test").one('webkitTransitionEnd', function() { $("#test").removeClass(initcss).removeClass(addcss); });

                    }
                    else {
                        $targ.one('webkitTransitionEnd', function() { $(this).removeClass(addcss); });
                        $targ.show().addClass(addcss);
                    }
                    break;
                default:
                    var time = styles[1] == null ? 300 : styles[1];


                    //are CSS3 transitions possible?
                    //var test = $targ.css('transitionProperty');

                    switch (styles[0]) {
                        case 'hide':
                            $targ.hide();
                            break;
                        case 'show':
                            $targ.show();
                            break;
                        case 'fadein':
                            $targ.fadeIn(time);
                            break;
                        case 'fadeout':
                            $targ.fadeOut(time);
                            break;
                        case 'cssfall':
                            $targ.show();
                            var test = $targ[0].style.WebKitTransform = 'rotate(77)';
                            var test = $targ[0].style.WebKitTransition = 'all 1s linear';
                            var test = $targ[0].style.WebKitTransform = 'rotate(0)';
                            //alert("hide by cssfall");
                            break;
                        case 'cssland':
                            $targ.show();
                            var test = $targ[0].style.WebKitTransform = 'rotate(77)';
                            var test = $targ[0].style.WebKitTransition = 'all 1s linear';
                            var test = $targ[0].style.WebKitTransform = 'rotate(0)';
                            break;
                        case 'cssslideup':
                            $targ.show();
                            $targ.addClass('trans_slidedown');
                            /*
                            //get parent container height
                            alert(getTransformProperty($targ[0]));
                            $targ[0].style.display = 'block';
                            //if (getTransformProperty($targ[0]) != '') {
                            //$targ.bind('webkitTransitionEnd', function() { alert("done!"); });
                            $targ[0].style.WebkitTransition = 'all 2s ease'
                            setTimeout("delayedTransition('" + targetid + "');", 0);
                            //$targ[0].style[getTransformProperty($targ[0])] = 'rotate(77deg)';
                            //}*/
                            break;
                        case 'slideup':
                            //get parent container height

                            var h = $targ.parent().height()
                            $targ.css({ position: 'absolute', top: h + 'px', display: 'block' });
                            $('#' + targetid).animate({ top: '0px' }, time, 'easeout');
                            break;
                        case 'slidedown':
                            //position the element
                            $('#' + targetid).animate(time);
                            break;
                        case 'slideleft':
                            //position the element
                            $('#' + targetid).animate(time);
                            break;
                    }
                    break;
            }



        }


        this.testmethod = function(hello) {
            alert(hello);
        }

        this.getdatasource = function(dsname) {
            return vDatasourceList[dsname];
        }

        this.getrenderer = function(rndname) {
            return vRendererList[rndname];
        }

        this.findpanelorslave = function(element) {
            //this returns the closest parent panel be it a panel or a slaved panel
            //if the element has a targetpanel attribute then that can override the panel
            //the element is within
            var tpn = $(element).attr('data-targetpanel');
            if (tpn == undefined) {
                var thisone = $(element).closest('[data-vpanel],[data-vpanel-slave]');
            }
            else {
                var thisone = $('[data-vpanel=' + tpn + ']');
                if (thisone == '') alert("could not find data-targetpanel:" + tpn);
            }
            return thisone;
        }

        this.findpanelorslavefromname = function(panelname) {
            //this returns either a panel or slave panel based on name
            var thisone = $('[data-vpanel=' + panelname + ']');
            if (thisone != null) return thisone;
            thisone = $('[data-vpanel-slave=' + panelname + ']');
            return thisone;
        }

        this.findpanelorslavename = function(thepanel) {
            var tst = thepanel.attr('data-vpanel');
            return thepanel.attr('data-vpanel') != null ? thepanel.attr('data-vpanel') : thepanel.attr('data-vpanel-slave');
        }

        this.findpanel = function(element) {
            //this returns the closest parent panel
            var thisone = $(element).closest('[data-vpanel]');
            return thisone;
        }

        this.globalrefresh = function() {
            $.each(vValidatorList, function(i, v) {
                v.initialisevalidators(null);
            });
            $.each(vTooltipList, function(i, v) {
                v.initialisetips();
            });
            $.each(vPanelList, function(i, v) {
                v.initialisepanel();
            });
        }

        this.panelrefresh = function(thepanel) {
            //because the validators have placeholders on then we need to give
            //them a change to initialise after a panel has been updated
            $.each(vValidatorList, function(i, v) {
                v.initialisevalidators($(thepanel));
            });
        }


        //lets bind to get CLICK events
        $(document).bind('click', function(e) {
            var $t = $(e.target);

            //is the click within a vEdit element?
            var $ve = $t.closest('[data-vedit]');
            if ($ve.length > 0) {
                //initialise this vedit area if we're not editing already
                if ($ve.attr('data-vedit-editing') != 'true') {
                    vLog('vNexus', "begin vedit on :" + $ve.attr('data-vedit'));
                    vEditList[$ve.attr('data-vedit')].beginediting($ve);
                } else {
                    vLog('vNexus', "already veditting :" + $ve.attr('data-vedit'));
                }
            }


            //this is for HTML style panels, the ability to load remote content into them
            if ($t.hasClass('vnx-link')) {

                if ($t.attr('target') != null && $t.attr('target') != "") {
                    if (!e.altKey && !e.shiftKey) {
                        return
                    }
                }
                $ourpanel = Us.findpanelorslave($t);
                var panelname = Us.findpanelorslavename($ourpanel);
                var tp = $t.attr('data-targetpanel');
                if (tp != null) {
                    $ourpanel = Us.findpanelorslavefromname(tp);
                } else {
                    tp = panelname;
                }
                //sort out the data, either inline or from a url
                var linkd = $t.attr('data-linkdata');
                var linkdata = vQueryStringToObject(linkd);
                //alert("click"+$t[0].tagName);

                if ($t[0].tagName == 'A') $t.attr('href', '#');

                vPanelList[tp].doRefresh($ourpanel, $t, linkdata);
            };

            //validate the specified group
            if ($t.hasClass('vvl-validategroup')) {
                var vvl = $t.attr('data-vvl-group');
                vValidatorList[vvl].validategroup($t);
            };

            //perform an data select
            if ($t.hasClass('vnx-dataselect')) {
                //target datasource and data then trigger the datasource itself
                var tds = $t.attr('data-targetds');
                var selectdatastring = $t.attr('data-selectdata');
                //turn the url parameter style string into an object map for the datasource
                var selectdata = vQueryStringToObject(selectdatastring);
                vDatasourceList[tds].selectparameters(selectdata); ;
            };

            //perform an insert
            if ($t.hasClass('vnx-insert')) {
                $ourpanel = Us.findpanelorslave($t);
                var panelname = Us.findpanelorslavename($ourpanel);
                //parse any attributes on the trigger element to pass to the routine
                var placeholdertemplateID = $t.attr('data-placeholder-templateID');
                var placeholderposition = $t.attr('data-placeholder-position');
                var placeholdertargetID = $t.attr('data-placeholder-targetID');
                var errortemplateid = $t.attr('data-placeholder-errortemplateID');

                var insertdata = vQueryStringToObject($t.attr('data-insertdata'));
                //now pass this to the correct panel for further processing
                vPanelList[panelname].doInsert($ourpanel, $t, placeholdertemplateID, placeholdertargetID, placeholderposition, insertdata, errortemplateid);
            };

            //perform an select targetting a specific panel. A select means selecting an element within
            //the panels list. not going to a database. quote often there might be additional events after
            //the select, determined by the panels onafterselect trigger.
            if ($t.hasClass('vnx-select')) {
                //find our panel object (the parent of where the event bubbled up from)
                var $ourpanel = Us.findpanelorslave($t);
                //get the name of the panel
                var panelname = Us.findpanelorslavename($ourpanel);
                if (vPanelList[panelname] != null) {
                    //update selected on our panel, send the panel and the triggering element
                    vPanelList[panelname].doSelect($ourpanel, $t, null, false);
                }

                //are there any other panels to select as well?
                var otherpanels = $t.attr('data-extratargetpanel');
                var setcurpos = $t.attr('data-extrasetposition');
                if (otherpanels != null) {
                    //as well as updating the key we can set the position of the panel however
                    //you probably would never do that with the main panel selection however you
                    //probably would do that with the extra ones. default is to set the current position
                    //for extra targets
                    var scp = [];
                    if (setcurpos != null) {
                        scp = setcurpos.split(",");
                        if (scp.length <= 1) {
                            var setcurpos = setcurpos == null ? true : setcurpos;
                        }
                    } else {
                        setcurpos = $t.attr('data-extrasetposition') == null ? true : ($t.attr('data-extrasetposition') == "false" ? false : true);
                    }

                    $.each(otherpanels.split(","), function(i, v) {
                        //we have the panel name but we need to find the panel itself
                        $ourpanel = Us.findpanelorslavefromname(v);
                        var cpos = scp[i] != null ? (scp[i] == "false" ? false : true) : setcurpos;
                        if (vPanelList[v] != null) vPanelList[v].doSelect($ourpanel, $t, null, cpos);
                    });
                }

            };


            //perform an update
            if ($t.hasClass('vnx-update')) {
                $ourpanel = Us.findpanelorslave($t);
                var panelname = Us.findpanelorslavename($ourpanel);
                var updatedata = vQueryStringToObject($t.attr('data-updatedata'));
                vPanelList[panelname].doUpdate($ourpanel, $t, updatedata);
            };

            //perform an select
            if ($t.hasClass('vnx-delete')) {
                //what is our nexus called?
                $ourpanel = Us.findpanelorslave($t);
                var panelname = Us.findpanelorslavename($ourpanel);
                var deletedata = vQueryStringToObject($t.attr('data-deletedata'));

                //update selected on our panel, send the panel and the triggering element
                vPanelList[panelname].doDelete($ourpanel, $t, deletedata);
            };

            //show or hide a target element
            if ($t.hasClass('vnx-showhide')) {
                //what is our nexus called?
                var targetid = $t.attr('data-targetid');
                var showhide = $t.attr('data-show');
                //do we show or hide this, is the name of the link the same as show?
                //in which case we need to show this (it is hidden already)
                if ($t.html() == $t.attr('data-show')) {
                    $t.html($t.attr('data-hide'));
                    $('#' + targetid).show();
                } else {
                    $t.html($t.attr('data-show'));
                    $('#' + targetid).hide();
                }
                return false;
            };

            if ($t.hasClass('vnx-toggleclass')) {
                //what is our nexus called?
                var targetid = $t.attr('data-targetid');
                if (targetid == "..") targetid = $t.parent();
                else if (targetid == null) targetid = $t;

                var c1 = $t.attr('data-class1');
                var c2 = $t.attr('data-class2');

                if ($(targetid).hasClass(c1)) {
                    $(targetid).removeClass(c1).addClass(c2);
                } else {
                    $(targetid).removeClass(c2).addClass(c1);
                }
                return false;
            };

            //show or hide columns from a table
            if ($t.hasClass('vnx-showhidecolumns')) {
                //get the table id.
                var targetid = $t.attr('data-targetid');
                var showhidename = $t.attr('data-show');
                var toshow = $t.html() == showhidename;
                var list1 = $t.attr('data-list1').split(",");
                list1 = jQuery.map(list1, function(n, i) { return parseInt(n); });
                var list2 = $t.attr('data-list2').split(",");
                list2 = jQuery.map(list2, function(n, i) { return parseInt(n); });
                var thetable = $('#' + targetid)[0];
                var testcolumn = list1[0];
                if (iscolumnvisible(thetable, testcolumn)) {
                    hidecolumns(thetable, list1);
                    showcolumns(thetable, list2);
                }
                else {
                    hidecolumns(thetable, list2);
                    showcolumns(thetable, list1);
                }
                if (toshow) $t.html($t.attr('data-hide'));
                else $t.html(showhidename);
                //return false;
            };

            //show hide a series of panels based on clicks
            if ($t.hasClass('vnx-showtab')) {
                var targetid = $t.attr('data-tabid');
                var tabgroup = $t.attr('data-tabgroupname');
                var hidestyle = $t.attr('data-hidetransition');
                var showstyle = $t.attr('data-showtransition');
                var inactiveclass = $t.attr('data-inactiveclass');
                var activeclass = $t.attr('data-activeclass');

                var selectedclass = $t.attr('data-selectedclass');
                if (selectedclass == null) selectedclass = "selected";

                var $tabgroup = $('[data-tabgroup=' + tabgroup + ']');

                var $va = $tabgroup.siblings('.vnexus-active');
                var sID = $va != null ? $va.attr('id') : null;
                if (sID == null) {
                    if (activeclass != null) {
                        var sng = $('.' + activeclass);
                        sID = sng.attr('id');
                        //sID = $('[data-tabgroup=' + tabgroup + '] .' + activeclass).attr('id');
                    } else {
                        sID = $('[data-tabgroup=' + tabgroup + ']:visible').attr('id');
                    }
                }



                //hide it or hide all
                if (sID != null) {
                    if (hidestyle != undefined) {
                        animatestyle(false, sID, sID, $tabgroup, $t.attr('data-hidetransition'));
                    }
                    if (inactiveclass != undefined) {
                        $('#' + sID).removeClass(activeclass).addClass(inactiveclass);
                    }
                } else {
                    //hide all the current tab(s)
                    $('[data-tabgroup=' + tabgroup + ']').hide().removeClass(inactiveclass);
                }

                //show the target
                if (showstyle != undefined) {
                    animatestyle(true, targetid, sID, $tabgroup, $t.attr('data-showtransition'));
                }
                if (activeclass != undefined) {
                    $('#' + targetid).removeClass(inactiveclass).addClass(activeclass);
                }

                $tabgroup.removeClass('vnexus-active');
                $('#' + targetid).addClass('vnexus-active');

                //remove selected class from triggers
                $('[data-tabgroupname=' + tabgroup + ']').removeClass(selectedclass);

                //add to new link
                $t.addClass(selectedclass);

                //return false;
            };

            if ($t.hasClass('vnx-showdialog')) {
                var dialogid = $t.attr('data-dialogid');
                $('#' + dialogid).trigger('vdialog-open', null);
            };

            if ($t.hasClass('vnx-switchsrc')) {
                //this is the target which will have it's src changed
                var targetid = $t.attr('data-targetid');
                var $tid = $('#' + targetid);
                //get the current src
                var cs = $tid.attr('src');
                //now swap the filename part
                var ind = cs.lastIndexOf('/');
                var jp = cs.substr(0, ind) + "/" + $t.attr('rel');
                $tid.attr('src',jp);

            };

            //refresh single panel
            if ($t.hasClass('vnx-refresh')) {
                $ourpanel = Us.findpanelorslave($t);
                var panelname = Us.findpanelorslavename($ourpanel);
                var refreshdata = vQueryStringToObject($t.attr('data-refreshdata'));

                //update selected on our panel, send the panel and the triggering element
                vPanelList[panelname].doRefresh($ourpanel, $t, refreshdata);
            };

            //refresh all elements on the page
            if ($t.hasClass('vnx-global-refresh')) {
                Us.globalrefresh();

            };

            //this disables a button once clicked on to prevent multiple clicks
            if ($t.hasClass('vnx-clickonce')) {
                //do we have validation on this button?

                //so we disable it
                if (!$t.hasClass('vnx-disabled')) $t.addClass('vnx-disabled');

                //we can either clear it via a return from an operation or we add a timer in here to clear after a moment anyway


            };

            //a test alert
            if ($t.hasClass('vnx-alert')) {
                alert("Test from vnx");
            };

            //stops the chain of events
            if ($t.hasClass('vnx-stop')) {
                return false;
            };

            //lastly we run any vnx-click events on the element, this is so we can offer a delayed click after all the above has run
            //if we bind a click to the element then the bubble up won't happen until *after* the element click.
            $t.trigger('vnx-click', e);

            //if there's an href and we don't want the page to jump we return false from here
            var test = $t.attr('href');
            if ($t.attr('href') == "#") {
                return false; //stop the page jumping
            }
        });


        $(this).bind('mouseover', function(e) {
            var $t = $(e.target);

            //is this a hover?
            var hi = $t.attr('data-hoverimg');
            if (hi != null) {
                $t.attr('data-oldimg', $t.attr('src'));
                $t.attr('src', hi);
            }

            //is this a popup?
            var pu = $t.attr('data-popup');
            if (pu != null) {
                //add an event to the popup window to hide when mouseout
                $pu = $('#' + pu);
                //if the parent isn't the body then move the DOM element to the body (so absolute will work correctly)
                if ($pu.parent()[0].tagName != "BODY") {
                    $pu.detach().appendTo("body");
                };
                //adjust the position of the popup
                //alert("x:" + $t.offset().left + " y:" + $t.offset().top);
                $pu.css({ position: 'absolute', left: $t.offset().left + 'px', top: $t.offset().top + 'px' });
                $pu.bind('mouseleave', function(e) { $(this).hide(); $(this).unbind('mouseleave'); });
                $pu.show();
            }

            //is this part of a tooltip?
            var vtp = $t.attr('data-vtip');
            if (vtp != null) {
                vLog('vNexus', "mouseover opentip:" + $t[0].tagName);
                vTooltipList[vtp].opentip(e, $t);
            }
        });

        $(this).bind('mouseout', function(e) {
            var $t = $(e.target);

            //is this part of a tooltip?
            var vtp = $t.attr('data-vtip');
            if (vtp != null) {
                vLog('vNexus', "mouseout closetip:" + $t[0].tagName);
                vTooltipList[vtp].closetip($t);
            }

            var hi = $t.attr('data-oldimg');
            if (hi != null) {
                $t.attr('src', hi);
            }
        });


        $(this).bind('focusin', function(e) {
            var $t = $(e.target);
            vLog('vNexus', "focus in");
            //is this part of a vvl?
            var vvl = $t.attr('data-vvl');
            if (vvl != null) {
                //find the correct validator and pass it the focus in
                vValidatorList[vvl].focusin($t);
            }

            //copy the value into old so we can tell if its changed
            if ($t.val() != null) {
                $t.attr('oldvalue', $t.val());
            }
        });


        $(this).bind('focusout', function(e) {
            var $t = $(e.target);
            vLog('vNexus', "focus out on " + $t[0].tagName);

            //does this element have validation on it?
            var vvl = $t.attr('data-vvl');
            if (vvl != null) {
                vLog('vNexus', "focus out");
                vValidatorList[vvl].focusout($t);
            }


            //is this element a vEdit?
            var ve = $t.attr('data-vedit');
            if (ve != null) {
                vEditList[ve].endediting($t);
                //yes, it's a vedit (an editable DOM element, not a field)
                if ($t.attr('oldvalue') != null) {
                    //it's the inner html that is being edited, not the value
                    if ($t.html() != $t.attr('oldvalue')) {
                        vLog('vNexus', "FOCUS OUT: vEDIT VALUE HAS CHANGED");
                        $t.trigger('vnx-change', [$t.attr('oldvalue'), $t.html()]);
                    } else {
                        vLog('vNexus', "FOCUS OUT: vEDIT NOT CHANGED");
                    }
                }

            } else {
                //does the old value not match the new value?
                //note: not sure why we don't do anything if null?
                //if ($t.attr('oldvalue') != null) {

                if ($t.val() != $t.attr('oldvalue')) {
                    vLog('vNexus', "FOCUS OUT: VALUE CHANGED:" + $t.val());
                    $t.trigger('vnx-change', [$t.attr('oldvalue'), $t.val()]);
                }
                //}
            }
        });


        $(document).bind('keyup', function(e) {

            if (event.keyCode == '13') {
                var $t = $(e.target);
                var triggerlink = $t.attr('data-vnx-entertriggers');
                if (triggerlink != null) {
                    $('#' + triggerlink).trigger('click');
                    //alert("trigger this " + triggerlink);
                }
            };

        });

        //lets create our own events
        ////////////////////////////////////////////////////////////////////////////

        $(this).bind('vnx-reg-vvalidate', function(e, vvalidate) {
            vValidatorList[vvalidate.applyto()] = vvalidate;
        });
        $(this).bind('vnx-reg-vtooltip', function(e, vtooltip) {
            vTooltipList[vtooltip.applyto()] = vtooltip;
        });
        $(this).bind('vnx-reg-vpanel', function(e, vpanel) {
            vPanelList[vpanel.applyto()] = vpanel;
        });
        $(this).bind('vnx-reg-vdatasource', function(e, vdatasource) {
            vDatasourceList[vdatasource.applyto()] = vdatasource;
        });
        $(this).bind('vnx-reg-vrenderer', function(e, vrenderer) {
            vRendererList[vrenderer.applyto()] = vrenderer;
        });
        $(this).bind('vnx-reg-vedit', function(e, vedit) {
            vEditList[vedit.applyto()] = vedit;
        });
        //triggering this will refresh and reinitialise the elements on the page
        $(this).bind('vnx-global-refresh', function(e) {
            //refresh all our elements
            Us.globalrefresh();
        });

        //triggering this will refresh and reinitialise the elements on the page
        $(this).bind('vnx-refresh', function(e, panelname, extradata) {
            var $t = $(e.target);
            var $p = Us.findpanelorslavefromname(panelname);
            vPanelList[panelname].doRefresh($p, $t, extradata);
        });

        //triggering this will refresh and reinitialise the elements on the page
        $(this).bind('vvl-validateone', function(e, element) {
            var $t = $(e.target);
            var $elm = $(element);
            var whichvalidator = $elm.attr('data-vvl');
            vLog('vNexus', "vvl-validateone now :" + whichvalidator);
            vValidatorList[whichvalidator].validateone($elm);
        });

        //for manual validation of elements from script
        $(this).bind('vvl-validategroup', function(e, triggerelement, groupname) {
            var $t = $(triggerelement);
            vValidatorList[groupname].validategroup($t);
        });

        //perform a select on a given panel
        $(this).bind('vnx-select', function(e, panelname, newkey, setcurrentpos) {
            var $t = $(e.target);
            var $p = Us.findpanelorslavefromname(panelname);
            vPanelList[panelname].doSelect($p, $t, newkey, setcurrentpos);
        });


        //triggering this will tell the target datasource to update
        $(this).bind('vnx-dataselect', function(e, targetds, data) {
            //send the data select on to the appropriate datasource
            //if the datasource changes data then it can notify any panels
            //that are registered with it
            var o = vDatasourceList[targetds];
            if (vDatasourceList[targetds] != null) {
                vDatasourceList[targetds].selectparameters(data);
            } else {
                vLog('vNexus', "ERROR trying to access non existant datasource:" + targetds);
            }
        });

        //this bubbles up from an element that has changed on the page, it probably originates
        //from the focus in/focus out events handled above
        $(this).bind('vnx-change', function(e, oldvalue, newvalue) {
            vLog('vNexus', "vnx change event: new value: " + newvalue + " (old : " + oldvalue + ")");
            //is this part of a panel?
            var $t = $(e.target);
            var $panel = Us.findpanelorslave($t);
            var panelname = Us.findpanelorslavename($panel);

            //is this part of a select  menu change?
            if ($t.hasClass('vnx-menuupdate')) {
                var $menu = $t.attr('data-menutargetid') == null ? $t : $('#' + $t.attr('data-menutargetid'));
                $menu.load($t.attr('data-menuupdateURL'), 'opt=' + $t.val());
            }


            //we can stop an element from auto anything by adding the noauto class
            if (panelname != null && !$t.hasClass('vnx-noauto')) {
                //pass a change to the panel to deal with it locally
                vPanelList[panelname].doValueChange($panel, $t, oldvalue, newvalue);
            }
            //do we want to do anything else with an element that has changed??

        });


        $(document).data('vnexus', this);

        //is there any setup to the dom to do?

    };



    //global logging function, can be turned on and off
    vLog = function(from, message) {
        if (!global_vLog) return;
        var v = window.console;
        if (window.console && window.console.log) {
            window.console.log(from + ":" + message)
        }
        else {
            if ($('#vlogoutput').length == 0) {
                $('<div/>', { id: "vlogoutput",
                    css: { position: "fixed", fontSize: "11px", backgroundColor: "#eee", overflowY: "scroll", bottom: "0px", width: "100%", height: "128px" }
                }).appendTo("body");
            }
            $('#vlogoutput').prepend("<p style='padding:0px;margin:0px'>" + new Date().asString("HH:MM:SS:MS") + ":<b>" + from + "</b>:" + message + "</p>");
        }
    };

    vQueryStringToObject = function(qs) {
        if (qs == null) return {};
        var q = (typeof qs === 'string' ? qs : window.location.search), o = { 'f': function(v) { return unescape(v).replace(/\+/g, ' '); } }, options = (typeof qs === 'object' && typeof options === 'undefined') ? qs : options, o = jQuery.extend({}, o, options), params = {};
        jQuery.each(q.match(/^\??(.*)$/)[1].split('&'), function(i, p) {
            p = p.split('=');
            p[1] = o.f(p[1]);
            params[p[0]] = params[p[0]] ? ((params[p[0]] instanceof Array) ? (params[p[0]].push(p[1]), params[p[0]]) : [params[p[0]], p[1]]) : p[1];
        });
        return params;

    }
    //return a float or a null if it cannot be converted
    vFloatOrNull = function(element) {
        var test = $(element).val();
        var source = $(element).val() != "" ? $(element).val() : $(element).html().replace(/[^0-9\.]/g, "");
        var conv = parseFloat(source);
        return isNaN(conv) ? null : conv;
    };

    //global render function
    var _tmplCache = {} //global object for storing compiled templates
    vRender = function(templatename, str, data, dataset) {

        if (str == null) vLog("vRender", "No renderer given");
        //vLog('vrenderstart', templatename + ":" + str); 


        var err = "";
        try {


            var func = _tmplCache[templatename];
            if (!func) {

                /***** START RENDER CODE ******/


                var strfunct = str.replace('{{', '&#123;'); //replace curly braces
                strfunct = strfunct.replace('}}', '&#125;');
                strfunct = strfunct.replace('%7B', '{');    // {} in urls get encoded so code them back
                strfunct = strfunct.replace('%7D', '}');
                strfunct = strfunct.replace(/[\r\t\n]/g, " "); //turn special characters into spaces...

                strfunct = strfunct.replace(/[\{]/g, '}*');

                var ca = strfunct.split("}"); //split on { and } but the script starts now have an asterisk
                var started = false;
                var newstring = ""

                var mode = 0; 	//0 beginning
                //1 Literal
                //2 Javascript

                //iterate through the chunks, anything with a * is a command
                //


                for (var i = 0; i < ca.length; i++) {
                    //if(i) newstring += ',';

                    //vLog('vRender', "PRE[" + ca[i] + "]");
                    var chunk = ca[i].replace(/\s{2,}/g, "");
                    //vLog('vRender', "POST[" + chunk + "]");


                    if (chunk.substr(0, 1) == "*") {				//this is a command

                        if (chunk.substr(1, 1) == "=") {

                            if (mode != 1) newstring += ";p.push("; 	//if the last wasn't a literal then we're starting a push again...
                            else newstring += ","; 				//or we're just continuing it

                            newstring += chunk.substr(2, 999); 	//this is a direct request for data
                            mode = 1;
                        }
                        else {
                            //this could be a meta command (or straight javascript)
                            if (mode == 1) newstring += ");";
                            //else newstring += ";";
                            mode = 2;
                            var cmd = chunk.substr(1, 999); 		//get the command
                            var cmdsplt = cmd.split("|");
                            switch (cmdsplt[0]) {
                                case "hello":
                                    newstring += "alert('helloworld');";
                                    break;
                                case "round":
                                    // {round|field|places}
                                    if (cmdsplt.length == 3) {
                                        var places = cmdsplt[2];
                                        var field = cmdsplt[1];
                                        newstring += "p.push((d['" + field + "']).toFixed(" + places + "));";
                                    }
                                    break;
                                case "datetime":
                                    if (cmdsplt.length == 2) {
                                        var format = cmdsplt[1];
                                        newstring += "p.push(new Date().asString('" + format + "'));";
                                    }
                                    else {
                                        var field = cmdsplt[1];
                                        var format = cmdsplt[2];
                                        newstring += "p.push(Date.fromJSONstring(d['" + field + "']).asString('" + format + "'));";
                                    }
                                    break;
                                case "selectoptions":
                                    try {
                                        var cmdsplt = cmd.split("|");

                                        var names = cmdsplt[1];         //a list of names to use
                                        var values = cmdsplt[2];        //a list of values to use
                                        var selectedfield = cmdsplt[3]; //the field that contains the selected value
                                        //is this a direct list or a field reference?
                                        if (names.substr(0, 2) == "d." || names.substr(0, 7) == "innerd.") {
                                            //use values of specified fields
                                            newstring += "var allnames=" + names + ".split(',');";
                                            newstring += "var allvalues=" + values + ".split(',');";
                                        }
                                        else {
                                            //use actual values
                                            newstring += "var allnames='" + names + "'.split(',');";
                                            newstring += "var allvalues='" + values + "'.split(',');";
                                        }
                                        newstring += "for(var smi=0;smi<allnames.length;smi++) { p.push('<option value=\"');p.push(allvalues[smi]);if(allvalues[smi]+''==" + selectedfield + ".toString()) { p.push('\" selected >'); } else {; p.push('\" >') };p.push(allnames[smi]); p.push('</option>'); };";
                                    }
                                    catch (err) {
                                    }
                                    break;
                                case "loop":
                                    var whichdata = cmdsplt[1];
                                    newstring += "for(var i=0;i<" + whichdata + ".length;i++) { var innerd = " + whichdata + "[i];";
                                    break;
                                case "/loop":
                                    newstring += "}";
                                    break;
                                case "switch":
                                    var cmdsplt = cmd.split("|");
                                    newstring += "switch(" + cmdsplt[1] + ") {";
                                    break;
                                case "case":
                                    var cmdsplt = cmd.split("|");
                                    newstring += "case " + cmdsplt[1] + ":";
                                    break;
                                case "/case":
                                    newstring += "break;";
                                    break;
                                case "defaultcase":
                                    newstring += "default:";
                                    break;
                                case "/defaultcase":
                                    newstring += "break;";
                                    break;
                                case "/switch":
                                    newstring += "};";
                                    break;
                                case "if":
                                    var cmdsplt = cmd.split("|");
                                    newstring += "if(" + cmdsplt[1] + ") {";
                                    break;
                                case "/if":
                                    newstring += "};";
                                    break;
                                default:
                                    //plain ol javascript
                                    newstring += cmd;
                                    break;
                            }
                        }

                    }
                    else {
                        if (chunk != '') {

                            if (mode != 1) newstring += ";p.push(";
                            else newstring += ",";

                            newstring += "'" + chunk + "'"; //literal
                            mode = 1;
                        }
                    }
                }

                var strFunc = "var p=[];" + newstring + ");return p.join('');";

                /***** END RENDER CODE ******/

                vLog('vRender', "compiled:" + templatename + ":" + strFunc);

                func = new Function("d", "ds", strFunc);
                _tmplCache[templatename] = func;
            }
            return func(data, dataset);
        }
        catch (e) {
            err = e.message;
        }


        return "[ERROR: " + err + "]";
    }



})(jQuery);




Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
Date.abbrDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
Date.abbrMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date.firstDayOfWeek = 1;
Date.format = 'dd/mm/yyyy';
//Date.format = 'mm/dd/yyyy';
//Date.format = 'yyyy-mm-dd';
//Date.format = 'dd mmm yy';


Date.fullYearStart = '20';

(function() {


    function add(name, method) {
        if (!Date.prototype[name]) {
            Date.prototype[name] = method;
        }
    };


    add("isLeapYear", function() {
        var y = this.getFullYear();
        return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
    });


    add("isWeekend", function() {
        return this.getDay() == 0 || this.getDay() == 6;
    });


    add("isWeekDay", function() {
        return !this.isWeekend();
    });


    add("getDaysInMonth", function() {
        return [31, (this.isLeapYear() ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][this.getMonth()];
    });


    add("getDayName", function(abbreviated) {
        return abbreviated ? Date.abbrDayNames[this.getDay()] : Date.dayNames[this.getDay()];
    });


    add("getMonthName", function(abbreviated) {
        return abbreviated ? Date.abbrMonthNames[this.getMonth()] : Date.monthNames[this.getMonth()];
    });


    add("getDayOfYear", function() {
        var tmpdtm = new Date("1/1/" + this.getFullYear());
        return Math.floor((this.getTime() - tmpdtm.getTime()) / 86400000);
    });


    add("getWeekOfYear", function() {
        return Math.ceil(this.getDayOfYear() / 7);
    });


    add("setDayOfYear", function(day) {
        this.setMonth(0);
        this.setDate(day);
        return this;
    });


    add("addYears", function(num) {
        this.setFullYear(this.getFullYear() + num);
        return this;
    });


    add("addMonths", function(num) {
        var tmpdtm = this.getDate();

        this.setMonth(this.getMonth() + num);

        if (tmpdtm > this.getDate())
            this.addDays(-this.getDate());

        return this;
    });


    add("addDays", function(num) {
        //this.setDate(this.getDate() + num);
        this.setTime(this.getTime() + (num * 86400000));
        return this;
    });

    add("addHours", function(num) {
        this.setHours(this.getHours() + num);
        return this;
    });


    add("addMinutes", function(num) {
        this.setMinutes(this.getMinutes() + num);
        return this;
    });


    add("addSeconds", function(num) {
        this.setSeconds(this.getSeconds() + num);
        return this;
    });

    add("zeroTime", function() {
        this.setMilliseconds(0);
        this.setSeconds(0);
        this.setMinutes(0);
        this.setHours(0);
        return this;
    });

    add("middayTime", function() {
        this.setMilliseconds(0);
        this.setSeconds(0);
        this.setMinutes(0);
        this.setHours(12);
        return this;
    });

    add("timeElapsed", function() {
        var d = new Date();
        var ms = d.getTime() - this.getTime();
        return ms + "ms";
    });

    add("setTime", function(hr, min, sec) {
        this.setMilliseconds(0);
        this.setSeconds(0);
        this.setMinutes(0);
        this.setHours(0);
        return this;
    });

    add("dateEqualTo", function(comparedate) {
        if (this.getFullYear() != comparedate.getFullYear()) return false;
        if (this.getMonth() != comparedate.getMonth()) return false;
        if (this.getDate() != comparedate.getDate()) return false;
        return true;
    });


    add("asString", function(format) {
        var r = format || Date.format;
        return r
			.split('yyyy').join(this.getFullYear())
			.split('yy').join((this.getFullYear() + '').substring(2))
			.split('mmmm').join(this.getMonthName(false))
			.split('mmm').join(this.getMonthName(true))
			.split('mm').join(_zeroPad(this.getMonth() + 1))
			.split('dd').join(_zeroPad(this.getDate()))
			.split('d').join(this.getDayName(false))
			.split('HH').join(this.getHours())
			.split('MM').join(_zeroPad(this.getMinutes()))
			.split('SS').join(_zeroPad(this.getSeconds()))
			.split('MS').join(_zeroPad(this.getMilliseconds()));
    });

    Date.fromJSONstring = function(s) {
        var o = s.replace(/[^0-9]/g, "");
        var d = new Date(parseInt(o))
        return d;
    };


    Date.fromString = function(s) {
        var f = Date.format;
        var d = new Date('01/01/1977');

        var mLength = 0;

        var iM = f.indexOf('mmmm');
        if (iM > -1) {
            for (var i = 0; i < Date.monthNames.length; i++) {
                var mStr = s.substr(iM, Date.monthNames[i].length);
                if (Date.monthNames[i] == mStr) {
                    mLength = Date.monthNames[i].length - 4;
                    break;
                }
            }
            d.setMonth(i);
        } else {
            iM = f.indexOf('mmm');
            if (iM > -1) {
                var mStr = s.substr(iM, 3);
                for (var i = 0; i < Date.abbrMonthNames.length; i++) {
                    if (Date.abbrMonthNames[i] == mStr) break;
                }
                d.setMonth(i);
            } else {
                d.setMonth(Number(s.substr(f.indexOf('mm'), 2)) - 1);
            }
        }

        var iY = f.indexOf('yyyy');

        if (iY > -1) {
            if (iM < iY) {
                iY += mLength;
            }
            d.setFullYear(Number(s.substr(iY, 4)));
        } else {
            if (iM < iY) {
                iY += mLength;
            }
            // TODO - this doesn't work very well - are there any rules for what is meant by a two digit year?
            d.setFullYear(Number(Date.fullYearStart + s.substr(f.indexOf('yy'), 2)));
        }
        var iD = f.indexOf('dd');
        if (iM < iD) {
            iD += mLength;
        }
        d.setDate(Number(s.substr(iD, 2)));
        if (isNaN(d.getTime())) {
            return false;
        }
        return d;
    };

    // utility method
    var _zeroPad = function(num) {
        var s = '0' + num;
        return s.substring(s.length - 2)
        //return ('0'+num).substring(-2); // doesn't work on IE :(
    };

})();

