
    Milk.Ctrl.LightTemplate = Milk.Ctrl.Template

    /**
     * MultiSearchBox widget
     */
    Milk.Ctrl.MultiSearchBox = function (id) {
        this.id           = id
        this.n            = null
        this.d            = null
        this.timeoutId    = null
        this.searchDelay  = 500
        this.searchLength = 3
        this.resultsLimit = 20
        this.resultIdx    = null
        this.resultCount  = 0
    }

    Milk.Ctrl.MultiSearchBox.prototype = new Milk.Ctrl.Form()

    Milk.Ctrl.MultiSearchBox.prototype.init = function () {
        this.n = _$(this.id).firstChild
        this.d = this.n.nextSibling

        // Fix for min-width in IE
        if (this.n.offsetWidth < 120) this.n.style.width = '120px'

        var i, b, c = this
        FLQ.e.add(this.n, 'focus', function () { FLQ.addClass(c.n, 'focussed'); c.clearlabel() })
        b = function () { FLQ.removeClass(c.n, 'focussed'); setTimeout('Milk.get(\''+c.id+'\').cancelSearch()', 200) }
        FLQ.e.add(this.n, 'blur', b)
        FLQ.e.add(this.n, 'keypress', function (e) { c.keypress(e) })

        if (FLQ.isArr(this.value)) {
            for (i=0; i < this.value.length; i++) {
                this.addvalue({'value':this.value[i][0], 'label':this.value[i][1]})
            }
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.clearlabel = function () {
        if (this.n.value == this.label) {
            this.n.value = ''
            FLQ.removeClass(this.n, 'msb-label')
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.keypress = function (e) {
        if (e.keyCode && e.keyCode == 13) {
            if (this.resultIdx !== null) {
                this.selectResult()
            } else {
                this.sendSignal('command', {'value':this.getValue(), 'validate':false})
            }
        } else if (e.keyCode && e.keyCode == 40) { // down arrow or right
            this.focusResult(1)
        } else if (e.keyCode && e.keyCode == 38) { // up arrow or left
            this.focusResult(-1)
        } else {
            this.xmlSearch()
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.setvalue = function (args) {
    }

    Milk.Ctrl.MultiSearchBox.prototype.addvalue = function (args) {
        if (Milk.getArg(args, 'value') && Milk.getArg(args, 'label')) {
            var c = this, d = document.createElement('div')
            FLQ.addClass(d, 'msb-item')
            d.setAttribute('milkvalue', args['value'])
            d.setAttribute('milklabel', args['label'])
            d.appendChild(document.createTextNode(args['label']))
            FLQ.e.add(d, 'click', function () { c.removevalue(d) });
            this.d.appendChild(d)
            this.n.value = ''
            this.n.focus()
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.removevalue = function (d) {
        this.d.removeChild(d)
    }

    Milk.Ctrl.MultiSearchBox.prototype.getValue = function () {
        if (this.d && this.d.childNodes && this.d.childNodes.length > 0) {
            var i, o, v = []
            for (i=0; i < this.d.childNodes.length; i++) {
                var o = this.d.childNodes[i]
                v.push([o.getAttribute('milkvalue'), o.getAttribute('milklabel')])
            }

            return v
        }

        return null
    }

    Milk.Ctrl.MultiSearchBox.prototype.getPostData = function () {
        if (this.hasChanged()) {
            var o = {}
            o[this.id] = this.getValue()

            return o
        }

        return null
    }

    Milk.Ctrl.MultiSearchBox.prototype.xmlSearch = function () {
        if (this.timeoutId) clearTimeout(this.timeoutId)
        if (this.n.value.length >= this.searchLength) {
            this.timeoutId = setTimeout('Milk.get(\''+this.id+'\').sendSearchSignal();', this.searchDelay)
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.sendSearchSignal = function () {
        this.sendSignal('xmlsearch', {'value':this.n.value, 'validate':false, 'send':false})
    }

    Milk.Ctrl.MultiSearchBox.prototype.xmlresult = function (args) {
        if (args['svr'] && args['svr'].responseXML) {
            this.cancelSearch()
            var results = args['svr'].responseXML.getElementsByTagName('result')
            var l = document.createElement('div')
            l.setAttribute('id', this.id+'-searchresults')
            l.style.width = (this.n.offsetWidth-2)+'px'
            FLQ.addClass(l, 'msb-result')
            if (results.length > 0 && results.length <= this.resultsLimit) {
                var c = this
                for (var i=0; i < results.length; i++) {
                    if (results[i].getElementsByTagName('id')[0]) {
                        var id = results[i].getElementsByTagName('id')[0].firstChild.nodeValue
                        var label = results[i].getElementsByTagName('label')[0].firstChild.nodeValue
                        var rlabel = (results[i].getElementsByTagName('rlabel').length ? results[i].getElementsByTagName('rlabel')[0].firstChild.nodeValue : label)
                        l.appendChild(v = document.createElement('div'))
                        FLQ.addClass(v, 'item')
                        v.setAttribute('milkid', id)
                        v.setAttribute('milklabel', label)
                        v.appendChild(document.createTextNode(rlabel))
                        var f = function (e) {
                            var el = FLQ.e.getTarget(e)
                            var id = el.getAttribute('milkid')
                            var label = el.getAttribute('milklabel')
                            c.addvalue({'value':id, 'label':label})
                            c.cancelSearch()
                        }
                        FLQ.e.add(v, 'click', f)
                    }
                }
                this.resultCount = results.length
            } else if (results.length > this.resultsLimit) {
                this.resultCount = 0
                l.appendChild(document.createTextNode('Too many results. Please refine your search'))
            } else {
                this.resultCount = 0
                l.appendChild(document.createTextNode('No results found'))
            }
            _$(this.id).appendChild(l)
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.searchResults = function () {
        return document.getElementById(this.id+'-searchresults')
    }

    Milk.Ctrl.MultiSearchBox.prototype.cancelSearch = function () {
        if (this.searchResults()) { this.searchResults().parentNode.removeChild(this.searchResults()) }
    }

    Milk.Ctrl.MultiSearchBox.prototype.focusResult = function (step) {
        var r, c
        if (r = this.searchResults()) {
            if (step > 0 && (this.resultIdx == null || this.resultIdx+step >= this.resultCount)) {
                c = 0
            } else if (step < 0 && (this.resultIdx == null || this.resultIdx+step < 0)) {
                c = this.resultCount
            } else {
                c = this.resultIdx+step
            }

            if (r.childNodes && r.childNodes[c] && FLQ.hasClass(r.childNodes[c], 'item')) {
                FLQ.addClass(r.childNodes[c], 'item-focussed')
                if (this.resultIdx !== null) FLQ.removeClass(r.childNodes[this.resultIdx], 'item-focussed')
                this.resultIdx = c
            }
        }
    }

    Milk.Ctrl.MultiSearchBox.prototype.selectResult = function () {
        if (this.resultIdx !== null && this.searchResults() && this.searchResults().childNodes && this.searchResults().childNodes[this.resultIdx]) {
            var value = this.searchResults().childNodes[this.resultIdx].getAttribute('milkid')
            var label = this.searchResults().childNodes[this.resultIdx].getAttribute('milklabel')
            this.addvalue({'value':value, 'label':label})
            this.cancelSearch()
        }
    };

    Milk.Ctrl.SignalRedirect = function (id) {
        this.id = id
    }

    Milk.Ctrl.SignalRedirect.prototype = new MilkCtrl()

    Milk.Ctrl.SignalRedirect.prototype.init = function () { }

    Milk.Ctrl.SignalRedirect.prototype.catchsig = function (args) {
        if (FLQ.isSet(typeof (v = Milk.getArg(args, 'value')))) {
            if (FLQ.isArr(v)) {
                this.sendSignal(v[0])
            } else {
                this.sendSignal(v)
            }
        }
    };

    Milk.Ctrl.GMap = function (id) {
        this.id      = id
        this.n       = null
        this.maptype = 'ROADMAP'
        this.map     = null
        this.bounds  = null
        this.points  = []
        this.icon
    }

    Milk.Ctrl.GMap.prototype = new MilkCtrl()

    Milk.Ctrl.GMap.prototype.init = function () {
        this.n = _$(this.id)

        if (google.maps) {
            var s = new google.maps.LatLng(-34.397, 150.644);
            var opt = {
                zoom:17,
                center:s,
                mapTypeId:google.maps.MapTypeId.SATELLITE,
                autoReshape:true
            }

            this.map = new google.maps.Map(this.n, opt)
            this.bounds = new google.maps.LatLngBounds()

            this.icon = new google.maps.MarkerImage('/img/mapmarker.png')

            var i, ll
            for (i=0; this.points[i]; i++) {
                if (m = this.points[i][0].match(/POINT\((-?[0-9\.]+), (-?[0-9\.]+)/i)) {
                    ll = new google.maps.LatLng(m[1], m[2])
                    this.addMarker(ll, this.points[i][1])
                    this.bounds.extend(ll)
                }
            }

            if (this.points.length > 1) {
                if (this.bounds) this.map.fitBounds(this.bounds)
            } else {
                if (ll) this.map.setCenter(ll)
            }
        }
    }

    Milk.Ctrl.GMap.prototype.geocode = function (args) {
        var a, g, c = this
        if (a = Milk.getArg(args, 'value')) {
            g = new google.maps.Geocoder()
            g.geocode({ 'address': a}, function (r,s) { c.set_geocode(r,s) })
        }
    }

    Milk.Ctrl.GMap.prototype.set_geocode = function (r, s) {
        if (s == google.maps.GeocoderStatus.OK && this.map) {
            this.map.setCenter(r[0].geometry.location);
            var marker = new google.maps.Marker({
                map: this.map, 
                position: r[0].geometry.location
            });
            this.map.setZoom(15)
        }
    }

    Milk.Ctrl.GMap.prototype.setvalue = function (args) {
        // do something
    }

    Milk.Ctrl.GMap.prototype.addMarker = function (ll, url) {
        marker = new google.maps.Marker({
            map:       this.map,
            draggable: true,
            icon:      this.icon,
            animation: google.maps.Animation.DROP,
            position:  ll
        })

        if (url && url.length) {
            google.maps.event.addListener(marker, 'click', function () { window.location = url })
        }
    };


