(function($) {

    $.manageAjax = (function() {
        var cache = {},
			queues = {},
			presets = {},
			activeRequest = {},
			allRequests = {},
			defaults = {
			    queue: true, //clear
			    maxRequests: 1,
			    abortOld: false,
			    preventDoubbleRequests: true,
			    cacheResponse: false,
			    complete: function() { },
			    error: function(ahr, status) {
			        var opts = this;
			        if (status && status.indexOf('error') != -1) {
			            setTimeout(function() {
			                var errStr = status + ': ';
			                if (ahr.status) {
			                    errStr += 'status: ' + ahr.status + ' | ';
			                }
			                errStr += 'URL: ' + opts.url;
			                throw new Error(errStr);
			            }, 1);
			        }
			    },
			    success: function() { },
			    abort: function() { }
			}
		;

        function create(name, settings) {
            var publicMethods = {};
            presets[name] = presets[name] ||
				{};

            $.extend(true, presets[name], $.ajaxSettings, defaults, settings);
            if (!allRequests[name]) {
                allRequests[name] = {};
                activeRequest[name] = {};
                activeRequest[name].queue = [];
                queues[name] = [];
            }
            $.each($.manageAjax, function(fnName, fn) {
                if ($.isFunction(fn) && fnName.indexOf('_') !== 0) {
                    publicMethods[fnName] = function(param) {
                        fn(name, param);
                    };
                }
            });
            return publicMethods;
        }

        function complete(opts, args) {

            if (args[1] == 'success') {
                opts.success.apply(opts, [args[0].successData, args[1]]);
                if (opts.global) {
                    $.event.trigger("ajaxSuccess", args);
                }
            }

            if (args[1] === 'abort') {
                opts.abort.apply(opts, args);
                if (opts.global) {
                    $.active--;
                    $.event.trigger("ajaxAbort", args);
                }
            }

            opts.complete.apply(opts, args);

            if (opts.global) {
                $.event.trigger("ajaxComplete", args);
            }

            if (opts.global && !$.active) {
                $.event.trigger("ajaxStop");
            }
            //args[0] = null; 
        }

        function proxy(oldFn, fn) {
            return function(xhr, s, e) {
                fn.call(this, xhr, s, e);
                oldFn.call(this, xhr, s, e);
                xhr = null;
                e = null;
            };
        }


        function callQueueFn(name) {
            var q = queues[name];
            if (q && q.length) {
                var fn = q.shift();
                if (fn) {
                    fn();
                }
            }
        }


        function add(name, opts) {
            if (!presets[name]) {
                create(name, opts);
            }
            opts = $.extend({}, presets[name], opts);
            //aliases
            var allR = allRequests[name],
				activeR = activeRequest[name],
				queue = queues[name];

            var id = opts.type + '_' + opts.url.replace(/\./g, '_'),
				oldComplete = opts.complete,
				ajaxFn = function() {
				    activeR[id] = {
				        xhr: $.ajax(opts),
				        ajaxManagerOpts: opts
				    };
				    activeR.queue.push(id);
				    return id;
				}
				;

            if (opts.data) {
                id += (typeof opts.data == 'string') ? opts.data : $.param(opts.data);
            }

            if (opts.preventDoubbleRequests && allRequests[name][id]) {
                return false;
            }

            allR[id] = true;

            opts.complete = function(xhr, s, e) {
                if (opts.abortOld) {
                    $.each(activeR.queue, function(i, activeID) {
                        if (activeID == id) {
                            return false;
                        }
                        abort(name, activeID);
                        return activeID;
                    });
                }
                oldComplete.call(this, xhr, s, e);
                //stop memory leak
                if (activeRequest[name][id]) {
                    if (activeRequest[name][id] && activeRequest[name][id].xhr) {
                        activeRequest[name][id].xhr = null;
                    }
                    activeRequest[name][id] = null;
                }
                xhr = null;
                activeRequest[name].queue = $.grep(activeRequest[name].queue, function(qid) {
                    return (qid !== id);
                });
                allR[id] = false;
                e = null;
                delete activeRequest[name][id];
            };

            if (cache[id]) {
                ajaxFn = function() {
                    activeR.queue.push(id);
                    complete(opts, cache[id]);
                    return id;
                };
            } else if (opts.cacheResponse) {
                opts.complete = proxy(opts.complete, function(xhr, s) {
                    if (s !== "success" && s !== "notmodified") {
                        return false;
                    }
                    cache[id][0].responseXML = xhr.responseXML;
                    cache[id][0].responseText = xhr.responseText;
                    cache[id][1] = s;
                    //stop memory leak
                    xhr = null;
                    return id; //strict
                });

                opts.success = proxy(opts.success, function(data, s) {
                    cache[id] = [{
                        successData: data,
                        ajaxManagerOpts: opts
                    }, s];
                    data = null;
                });
            }

            ajaxFn.ajaxID = id;

            if (opts.queue) {
                opts.complete = proxy(opts.complete, function() {

                    callQueueFn(name);
                });

                if (opts.queue === 'clear') {
                    queue = clear(name);
                }

                queue.push(ajaxFn);

                if (activeR.queue.length < opts.maxRequests) {
                    callQueueFn(name);
                }
                return id;
            }
            return ajaxFn();
        }

        function clear(name, shouldAbort) {
            $.each(queues[name], function(i, fn) {
                allRequests[name][fn.ajaxID] = false;
            });
            queues[name] = [];

            if (shouldAbort) {
                abort(name);
            }
            return queues[name];
        }

        function getXHR(name, id) {
            var ar = activeRequest[name];
            if (!ar || !allRequests[name][id]) {
                return false;
            }
            if (ar[id]) {
                return ar[id].xhr;
            }
            var queue = queues[name],
				xhrFn;
            $.each(queue, function(i, fn) {
                if (fn.ajaxID == id) {
                    xhrFn = [fn, i];
                    return false;
                }
                return xhrFn;
            });
            return xhrFn;
        }

        function abort(name, id) {
            var ar = activeRequest[name];
            if (!ar) {
                return false;
            }
            function abortID(qid) {
                if (qid !== 'queue' && ar[qid] && typeof ar[qid].xhr !== 'unedfiend' && typeof ar[qid].xhr.abort !== 'unedfiend') {
                    ar[qid].xhr.abort();
                    complete(ar[qid].ajaxManagerOpts, [ar[qid].xhr, 'abort']);
                }
                return null;
            }
            if (id) {
                return abortID(id);
            }
            return $.each(ar, abortID);
        }

        function unload() {
            $.each(presets, function(name) {
                clear(name, true);
            });
            cache = {};
        }

        return {
            defaults: defaults,
            add: add,
            create: create,
            cache: cache,
            abort: abort,
            clear: clear,
            getXHR: getXHR,
            _activeRequest: activeRequest,
            _complete: complete,
            _allRequests: allRequests,
            _unload: unload
        };
    })();
    
    $(window).unload($.manageAjax._unload);
})(jQuery);
/*
* jQuery Autocomplete plugin 1.1
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
*/

; (function($) {

    $.fn.extend({
        autocomplete: function(urlOrData, options) {
            var isUrl = typeof urlOrData == "string";
            options = $.extend({}, $.Autocompleter.defaults, {
                url: isUrl ? urlOrData : null,
                data: isUrl ? null : urlOrData,
                delay: isUrl ? $.Autocompleter.defaults.delay : 10,
                max: options && !options.scroll ? 10 : 150
            }, options);

            // if highlight is set to false, replace it with a do-nothing function
            options.highlight = options.highlight || function(value) { return value; };

            // if the formatMatch option is not specified, then use formatItem for backwards compatibility
            options.formatMatch = options.formatMatch || options.formatItem;

            return this.each(function() {
                new $.Autocompleter(this, options);
            });
        },
        result: function(handler) {
            return this.bind("result", handler);
        },
        search: function(handler) {
            return this.trigger("search", [handler]);
        },
        flushCache: function() {
            return this.trigger("flushCache");
        },
        setOptions: function(options) {
            return this.trigger("setOptions", [options]);
        },
        unautocomplete: function() {
            return this.trigger("unautocomplete");
        }

    });

    $.Autocompleter = function(input, options) {

        var KEY = {
            UP: 38,
            DOWN: 40,
            DEL: 46,
            TAB: 9,
            RETURN: 13,
            ESC: 27,
            COMMA: 188,
            PAGEUP: 33,
            PAGEDOWN: 34,
            BACKSPACE: 8
        };


        var searchAjaxManager = $.manageAjax.create('acQueue', {
            queue: true,
            maxRequests: 1,
            abortOld: true,
            cacheResponse: true
        });

        // Create $ object for input element
        var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

        var timeout;
        var previousValue = "";
        var cache = $.Autocompleter.Cache(options);
        var hasFocus = 0;
        var lastKeyPressCode;
        var config = {
            mouseDownOnSelect: false
        };
        var select = $.Autocompleter.Select(options, input, selectCurrent, config);

        var blockSubmit;

        // prevent form submit in opera when selecting with return key
        $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
            if (blockSubmit) {
                blockSubmit = false;
                return false;
            }
        });

        // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
        $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
            // a keypress means the input has focus
            // avoids issue where input had focus before the autocomplete was applied
            hasFocus = 1;
            // track last key pressed
            lastKeyPressCode = event.keyCode;
            switch (event.keyCode) {

                case KEY.UP:
                    event.preventDefault();
                    if (select.visible()) {
                        select.prev();
                    } else {
                        onChange(0, true);
                    }
                    break;

                case KEY.DOWN:
                    event.preventDefault();
                    if (select.visible()) {
                        select.next();
                    } else {
                        onChange(0, true);
                    }
                    break;

                case KEY.PAGEUP:
                    event.preventDefault();
                    if (select.visible()) {
                        select.pageUp();
                    } else {
                        onChange(0, true);
                    }
                    break;

                case KEY.PAGEDOWN:
                    event.preventDefault();
                    if (select.visible()) {
                        select.pageDown();
                    } else {
                        onChange(0, true);
                    }
                    break;

                // matches also semicolon  
                case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
                case KEY.TAB:
                case KEY.RETURN:
                    if (selectCurrent()) {
                        // stop default to prevent a form submit, Opera needs special handling
                        event.preventDefault();
                        blockSubmit = true;
                        return false;
                    }
                    break;

                case KEY.ESC:
                    select.hide();
                    break;

                default:
                    clearTimeout(timeout);
                    timeout = setTimeout(onChange, options.delay);
                    break;
            }
        }).focus(function() {
            // track whether the field has focus, we shouldn't process any
            // results if the field no longer has focus
            hasFocus++;
        }).blur(function() {
            hasFocus = 0;
            if (!config.mouseDownOnSelect) {
                hideResults();
            }
        }).click(function() {
            // show select when clicking in a focused field
            if (hasFocus++ > 1 && !select.visible()) {
                onChange(0, true);
            }
        }).bind("search", function() {
            // TODO why not just specifying both arguments?
            var fn = (arguments.length > 1) ? arguments[1] : null;
            function findValueCallback(q, data) {
                var result;
                if (data && data.length) {
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].result.toLowerCase() == q.toLowerCase()) {
                            result = data[i];
                            break;
                        }
                    }
                }
                if (typeof fn == "function") fn(result);
                else $input.trigger("result", result && [result.data, result.value]);
            }
            $.each(trimWords($input.val()), function(i, value) {
                request(value, findValueCallback, findValueCallback);
            });
        }).bind("flushCache", function() {
            cache.flush();
        }).bind("setOptions", function() {
            $.extend(options, arguments[1]);
            // if we've updated the data, repopulate
            if ("data" in arguments[1])
                cache.populate();
        }).bind("unautocomplete", function() {
            select.unbind();
            $input.unbind();
            $(input.form).unbind(".autocomplete");
        });


        function selectCurrent() {
            var selected = select.selected();
            if (!selected)
                return false;

            var v = selected.result;
            previousValue = v;

            if (options.multiple) {
                var words = trimWords($input.val());
                if (words.length > 1) {
                    var seperator = options.multipleSeparator.length;
                    var cursorAt = $(input).selection().start;
                    var wordAt, progress = 0;
                    $.each(words, function(i, word) {
                        progress += word.length;
                        if (cursorAt <= progress) {
                            wordAt = i;
                            return false;
                        }
                        progress += seperator;
                    });
                    words[wordAt] = v;
                    // TODO this should set the cursor to the right position, but it gets overriden somewhere
                    //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
                    v = words.join(options.multipleSeparator);
                }
                v += options.multipleSeparator;
            }

            $input.val(v);
            hideResultsNow();
            $input.trigger("result", [selected.data, selected.value]);
            return true;
        }

        function onChange(crap, skipPrevCheck) {
            if (lastKeyPressCode == KEY.DEL) {
                select.hide();
                return;
            }

            var currentValue = $input.val();

            if (!skipPrevCheck && currentValue == previousValue)
                return;

            previousValue = currentValue;

            currentValue = lastWord(currentValue);
            if (currentValue.length >= options.minChars) {
                $input.addClass(options.loadingClass);
                if (!options.matchCase)
                    currentValue = currentValue.toLowerCase();
                request(currentValue, receiveData, hideResultsNow);
            } else {
                stopLoading();
                select.hide();
            }
        };

        function trimWords(value) {
            if (!value)
                return [""];
            if (!options.multiple)
                return [$.trim(value)];
            return $.map(value.split(options.multipleSeparator), function(word) {
                return $.trim(value).length ? $.trim(word) : null;
            });
        }

        function lastWord(value) {
            if (!options.multiple)
                return value;
            var words = trimWords(value);
            if (words.length == 1)
                return words[0];
            var cursorAt = $(input).selection().start;
            if (cursorAt == value.length) {
                words = trimWords(value)
            } else {
                words = trimWords(value.replace(value.substring(cursorAt), ""));
            }
            return words[words.length - 1];
        }

        // fills in the input box w/the first match (assumed to be the best match)
        // q: the term entered
        // sValue: the first matching result
        function autoFill(q, sValue) {
            // autofill in the complete box w/the first match as long as the user hasn't entered in more data
            // if the last user key pressed was backspace, don't autofill
            if (options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE) {
                // fill in the value (keep the case the user has typed)
                $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
                // select the portion of the value not typed by the user (so the next character will erase)
                $(input).selection(previousValue.length, previousValue.length + sValue.length);
            }
        };

        function hideResults() {
            clearTimeout(timeout);
            timeout = setTimeout(hideResultsNow, 200);
        };

        function hideResultsNow() {
            var wasVisible = select.visible();
            select.hide();
            clearTimeout(timeout);
            stopLoading();
            if (options.mustMatch) {
                // call search and run callback
                $input.search(
				function(result) {
				    // if no value found, clear the input box
				    if (!result) {
				        if (options.multiple) {
				            var words = trimWords($input.val()).slice(0, -1);
				            $input.val(words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : ""));
				        }
				        else {
				            $input.val("");
				            $input.trigger("result", null);
				        }
				    }
				}
			);
            }
        };

        function receiveData(q, data) {
            if (data && data.length && hasFocus) {
                stopLoading();
                select.display(data, q);
                autoFill(q, data[0].value);
                select.show();
            } else {
                hideResultsNow();
            }
        };

        function request(term, success, failure) {
            if (!options.matchCase)
                term = term.toLowerCase();
            var data = cache.load(term);
            // recieve the cached data
            if (data && data.length) {
                success(term, data);
                // if an AJAX url has been supplied, try loading the data now
            } else if ((typeof options.url == "string") && (options.url.length > 0)) {

                var extraParams = {
                    timestamp: +new Date()
                };
                $.each(options.extraParams, function(key, param) {
                    extraParams[key] = typeof param == "function" ? param() : param;
                });

                //$.ajax({
                searchAjaxManager.add({
                    // try to leverage ajaxQueue plugin to abort previous requests
                    mode: "abort",
                    // limit abortion to this input
                    port: "autocomplete" + input.name,
                    dataType: options.dataType,
                    url: options.url,
                    data: $.extend({
                        q: lastWord(term),
                        limit: options.max
                    }, extraParams),
                    success: function(data) {
                        var parsed = options.parse && options.parse(data) || parse(data);
                        cache.add(term, parsed);
                        success(term, parsed);
                    }
                });
            } else {
                // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
                select.emptyList();
                failure(term);
            }
        };

        function parse(data) {
            var parsed = [];
            var rows = data.split("\n");
            for (var i = 0; i < rows.length; i++) {
                var row = $.trim(rows[i]);
                if (row) {
                    row = row.split("|");
                    parsed[parsed.length] = {
                        data: row,
                        value: row[0],
                        result: options.formatResult && options.formatResult(row, row[0]) || row[0]
                    };
                }
            }
            return parsed;
        };

        function stopLoading() {
            $input.removeClass(options.loadingClass);
        };

    };

    $.Autocompleter.defaults = {
        inputClass: "ac_input",
        resultsClass: "ac_results",
        loadingClass: "ac_loading",
        minChars: 1,
        delay: 400,
        matchCase: false,
        matchSubset: true,
        matchContains: false,
        cacheLength: 10,
        max: 100,
        mustMatch: false,
        extraParams: {},
        selectFirst: true,
        formatItem: function(row) { return row[0]; },
        formatMatch: null,
        autoFill: false,
        width: 0,
        multiple: false,
        multipleSeparator: ", ",
        highlight: function(value, term) {
            return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
        },
        scroll: true,
        scrollHeight: 180
    };

    $.Autocompleter.Cache = function(options) {

        var data = {};
        var length = 0;

        function matchSubset(s, sub) {
            if (!options.matchCase)
                s = s.toLowerCase();
            var i = s.indexOf(sub);
            if (options.matchContains == "word") {
                i = s.toLowerCase().search("\\b" + sub.toLowerCase());
            }
            if (i == -1) return false;
            return i == 0 || options.matchContains;
        };

        function add(q, value) {
            if (length > options.cacheLength) {
                flush();
            }
            if (!data[q]) {
                length++;
            }
            data[q] = value;
        }

        function populate() {
            if (!options.data) return false;
            // track the matches
            var stMatchSets = {},
			nullData = 0;

            // no url was specified, we need to adjust the cache length to make sure it fits the local data store
            if (!options.url) options.cacheLength = 1;

            // track all options for minChars = 0
            stMatchSets[""] = [];

            // loop through the array and create a lookup structure
            for (var i = 0, ol = options.data.length; i < ol; i++) {
                var rawValue = options.data[i];
                // if rawValue is a string, make an array otherwise just reference the array
                rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;

                var value = options.formatMatch(rawValue, i + 1, options.data.length);
                if (value === false)
                    continue;

                var firstChar = value.charAt(0).toLowerCase();
                // if no lookup array for this character exists, look it up now
                if (!stMatchSets[firstChar])
                    stMatchSets[firstChar] = [];

                // if the match is a string
                var row = {
                    value: value,
                    data: rawValue,
                    result: options.formatResult && options.formatResult(rawValue) || value
                };

                // push the current match into the set list
                stMatchSets[firstChar].push(row);

                // keep track of minChars zero items
                if (nullData++ < options.max) {
                    stMatchSets[""].push(row);
                }
            };

            // add the data items to the cache
            $.each(stMatchSets, function(i, value) {
                // increase the cache size
                options.cacheLength++;
                // add to the cache
                add(i, value);
            });
        }

        // populate any existing data
        setTimeout(populate, 25);

        function flush() {
            data = {};
            length = 0;
        }

        return {
            flush: flush,
            add: add,
            populate: populate,
            load: function(q) {
                if (!options.cacheLength || !length)
                    return null;
                /* 
                * if dealing w/local data and matchContains than we must make sure
                * to loop through all the data collections looking for matches
                */
                if (!options.url && options.matchContains) {
                    // track all matches
                    var csub = [];
                    // loop through all the data grids for matches
                    for (var k in data) {
                        // don't search through the stMatchSets[""] (minChars: 0) cache
                        // this prevents duplicates
                        if (k.length > 0) {
                            var c = data[k];
                            $.each(c, function(i, x) {
                                // if we've got a match, add it to the array
                                if (matchSubset(x.value, q)) {
                                    csub.push(x);
                                }
                            });
                        }
                    }
                    return csub;
                } else
                // if the exact item exists, use it
                    if (data[q]) {
                    return data[q];
                } else
                    if (options.matchSubset) {
                    for (var i = q.length - 1; i >= options.minChars; i--) {
                        var c = data[q.substr(0, i)];
                        if (c) {
                            var csub = [];
                            $.each(c, function(i, x) {
                                if (matchSubset(x.value, q)) {
                                    csub[csub.length] = x;
                                }
                            });
                            return csub;
                        }
                    }
                }
                return null;
            }
        };
    };

    $.Autocompleter.Select = function(options, input, select, config) {
        var CLASSES = {
            ACTIVE: "ac_over"
        };

        var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;

        // Create results
        function init() {
            if (!needsInit)
                return;
            element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);

            list = $("<ul/>").appendTo(element).mouseover(function(event) {
                if (target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
                    active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
                    $(target(event)).addClass(CLASSES.ACTIVE);
                }
            }).click(function(event) {
                $(target(event)).addClass(CLASSES.ACTIVE);
                select();
                // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
                input.focus();
                return false;
            }).mousedown(function() {
                config.mouseDownOnSelect = true;
            }).mouseup(function() {
                config.mouseDownOnSelect = false;
            });

            if (options.width > 0)
                element.css("width", options.width);

            needsInit = false;
        }

        function target(event) {
            var element = event.target;
            while (element && element.tagName != "LI")
                element = element.parentNode;
            // more fun with IE, sometimes event.target is empty, just ignore it then
            if (!element)
                return [];
            return element;
        }

        function moveSelect(step) {
            listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
            movePosition(step);
            var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
            if (options.scroll) {
                var offset = 0;
                listItems.slice(0, active).each(function() {
                    offset += this.offsetHeight;
                });
                if ((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                    list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
                } else if (offset < list.scrollTop()) {
                    list.scrollTop(offset);
                }
            }
        };

        function movePosition(step) {
            active += step;
            if (active < 0) {
                active = listItems.size() - 1;
            } else if (active >= listItems.size()) {
                active = 0;
            }
        }

        function limitNumberOfItems(available) {
            return options.max && options.max < available
			? options.max
			: available;
        }

        function fillList() {
            list.empty();
            var max = limitNumberOfItems(data.length);
            for (var i = 0; i < max; i++) {
                if (!data[i])
                    continue;
                var formatted = options.formatItem(data[i].data, i + 1, max, data[i].value, term);
                if (formatted === false)
                    continue;
                var li = $("<li/>").html(options.highlight(formatted, term)).addClass(i % 2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
                $.data(li, "ac_data", data[i]);
            }
            listItems = list.find("li");
            if (options.selectFirst) {
                listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
                active = 0;
            }
            // apply bgiframe if available
            if ($.fn.bgiframe)
                list.bgiframe();
        }

        return {
            display: function(d, q) {
                init();
                data = d;
                term = q;
                fillList();
            },
            next: function() {
                moveSelect(1);
            },
            prev: function() {
                moveSelect(-1);
            },
            pageUp: function() {
                if (active != 0 && active - 8 < 0) {
                    moveSelect(-active);
                } else {
                    moveSelect(-8);
                }
            },
            pageDown: function() {
                if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
                    moveSelect(listItems.size() - 1 - active);
                } else {
                    moveSelect(8);
                }
            },
            hide: function() {
                element && element.hide();
                listItems && listItems.removeClass(CLASSES.ACTIVE);
                active = -1;
            },
            visible: function() {
                return element && element.is(":visible");
            },
            current: function() {
                return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
            },
            show: function() {
                var offset = $(input).offset();
                element.css({
                    width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
                    top: offset.top + input.offsetHeight,
                    left: offset.left
                }).show();
                if (options.scroll) {
                    list.scrollTop(0);
                    list.css({
                        maxHeight: options.scrollHeight,
                        overflow: 'auto'
                    });

                    if ($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
                        var listHeight = 0;
                        listItems.each(function() {
                            listHeight += this.offsetHeight;
                        });
                        var scrollbarsVisible = listHeight > options.scrollHeight;
                        list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight);
                        if (!scrollbarsVisible) {
                            // IE doesn't recalculate width when scrollbar disappears
                            listItems.width(list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")));
                        }
                    }

                }
            },
            selected: function() {
                var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
                return selected && selected.length && $.data(selected[0], "ac_data");
            },
            emptyList: function() {
                list && list.empty();
            },
            unbind: function() {
                element && element.remove();
            }
        };
    };

    $.fn.selection = function(start, end) {
        if (start !== undefined) {
            return this.each(function() {
                if (this.createTextRange) {
                    var selRange = this.createTextRange();
                    if (end === undefined || start == end) {
                        selRange.move("character", start);
                        selRange.select();
                    } else {
                        selRange.collapse(true);
                        selRange.moveStart("character", start);
                        selRange.moveEnd("character", end);
                        selRange.select();
                    }
                } else if (this.setSelectionRange) {
                    this.setSelectionRange(start, end);
                } else if (this.selectionStart) {
                    this.selectionStart = start;
                    this.selectionEnd = end;
                }
            });
        }
        var field = this[0];
        if (field.createTextRange) {
            var range = document.selection.createRange(),
			orig = field.value,
			teststring = "<->",
			textLength = range.text.length;
            range.text = teststring;
            var caretAt = field.value.indexOf(teststring);
            field.value = orig;
            this.selection(caretAt, caretAt + textLength);
            return {
                start: caretAt,
                end: caretAt + textLength
            }
        } else if (field.selectionStart !== undefined) {
            return {
                start: field.selectionStart,
                end: field.selectionEnd
            }
        }
    };

})(jQuery);
(function($) {
    $.jGrowl = function(m, o) { if ($('#jGrowl').size() == 0) $('<div id="jGrowl"></div>').addClass($.jGrowl.defaults.position).appendTo('body'); $('#jGrowl').jGrowl(m, o); }; $.fn.jGrowl = function(m, o) {
        if ($.isFunction(this.each)) {
            var args = arguments; return this.each(function() {
                var self = this; if ($(this).data('jGrowl.instance') == undefined) { $(this).data('jGrowl.instance', new $.fn.jGrowl()); $(this).data('jGrowl.instance').startup(this); }
                if ($.isFunction($(this).data('jGrowl.instance')[m])) { $(this).data('jGrowl.instance')[m].apply($(this).data('jGrowl.instance'), $.makeArray(args).slice(1)); } else { $(this).data('jGrowl.instance').create(m, o); } 
            });
        };
    }; $.extend($.fn.jGrowl.prototype, { defaults: { pool: 0, header: '', group: '', sticky: false, position: 'center', glue: 'after', theme: 'default', corners: '10px', check: 250, life: 3000, speed: 'normal', easing: 'swing', closer: true, closeTemplate: '&times;', closerTemplate: '<div>Fermer tout</div>', log: function(e, m, o) { }, beforeOpen: function(e, m, o) { }, open: function(e, m, o) { }, beforeClose: function(e, m, o) { }, close: function(e, m, o) { }, animateOpen: { opacity: 'show' }, animateClose: { opacity: 'hide'} }, notifications: [], element: null, interval: null, create: function(message, o) { var o = $.extend({}, this.defaults, o); this.notifications[this.notifications.length] = { message: message, options: o }; o.log.apply(this.element, [this.element, message, o]); }, render: function(notification) { var self = this; var message = notification.message; var o = notification.options; var notification = $('<div class="jGrowl-notification' + ((o.group != undefined && o.group != '') ? ' ' + o.group : '') + '"><div class="close">' + o.closeTemplate + '</div><div class="header">' + o.header + '</div><div class="message">' + message + '</div></div>').data("jGrowl", o).addClass(o.theme).children('div.close').bind("click.jGrowl", function() { $(this).parent().trigger('jGrowl.close'); }).parent(); (o.glue == 'after') ? $('div.jGrowl-notification:last', this.element).after(notification) : $('div.jGrowl-notification:first', this.element).before(notification); $(notification).bind("mouseover.jGrowl", function() { $(this).data("jGrowl").pause = true; }).bind("mouseout.jGrowl", function() { $(this).data("jGrowl").pause = false; }).bind('jGrowl.beforeOpen', function() { o.beforeOpen.apply(self.element, [self.element, message, o]); }).bind('jGrowl.open', function() { o.open.apply(self.element, [self.element, message, o]); }).bind('jGrowl.beforeClose', function() { o.beforeClose.apply(self.element, [self.element, message, o]); }).bind('jGrowl.close', function() { $(this).trigger('jGrowl.beforeClose').animate(o.animateClose, o.speed, o.easing, function() { $(this).remove(); o.close.apply(self.element, [self.element, message, o]); }); }).trigger('jGrowl.beforeOpen').animate(o.animateOpen, o.speed, o.easing, function() { $(this).data("jGrowl").created = new Date(); }).trigger('jGrowl.open'); if ($.fn.corner != undefined) $(notification).corner(o.corners); if ($('div.jGrowl-notification:parent', this.element).size() > 1 && $('div.jGrowl-closer', this.element).size() == 0 && this.defaults.closer != false) { $(this.defaults.closerTemplate).addClass('jGrowl-closer').addClass(this.defaults.theme).appendTo(this.element).animate(this.defaults.animateOpen, this.defaults.speed, this.defaults.easing).bind("click.jGrowl", function() { $(this).siblings().children('div.close').trigger("click.jGrowl"); if ($.isFunction(self.defaults.closer)) self.defaults.closer.apply($(this).parent()[0], [$(this).parent()[0]]); }); }; }, update: function() {
        $(this.element).find('div.jGrowl-notification:parent').each(function() { if ($(this).data("jGrowl") != undefined && $(this).data("jGrowl").created != undefined && ($(this).data("jGrowl").created.getTime() + $(this).data("jGrowl").life) < (new Date()).getTime() && $(this).data("jGrowl").sticky != true && ($(this).data("jGrowl").pause == undefined || $(this).data("jGrowl").pause != true)) { $(this).trigger('jGrowl.close'); } }); if (this.notifications.length > 0 && (this.defaults.pool == 0 || $(this.element).find('div.jGrowl-notification:parent').size() < this.defaults.pool)) { this.render(this.notifications.shift()); }
        if ($(this.element).find('div.jGrowl-notification:parent').size() < 2) { $(this.element).find('div.jGrowl-closer').animate(this.defaults.animateClose, this.defaults.speed, this.defaults.easing, function() { $(this).remove(); }); };
    }, startup: function(e) { this.element = $(e).addClass('jGrowl').append('<div class="jGrowl-notification"></div>'); this.interval = setInterval(function() { jQuery(e).data('jGrowl.instance').update(); }, this.defaults.check); if ($.browser.msie && parseInt($.browser.version) < 7 && !window["XMLHttpRequest"]) $(this.element).addClass('ie6'); }, shutdown: function() { $(this.element).removeClass('jGrowl').find('div.jGrowl-notification').remove(); clearInterval(this.interval); } 
    }); $.jGrowl.defaults = $.fn.jGrowl.prototype.defaults;
})(jQuery);;
function popupManager() {

    this.backgroupPopupSelector = ".backgroundPopup";
    this.popupContainerSelector = ".popupContainer";

    
    
    //0 means disabled; 1 means enabled;  
    this.popupStatus = 0;

    this.init();
}

// Prototype
popupManager.prototype = {

    init: function() {
        $("body").append("<div class='backgroundPopup'></div>");
        $("body").append("<div class='popupContainer'></div>");

        var self = this;
        $(this.popupContainerSelector + " .close").live("click", function() {
            self.disablePopup();
        });

        $(this.popupContainerSelector + " .cancel").live("click", function() {
            self.disablePopup();
        });
    },


    //loading popup 
    loadPopup: function(url, callback, options) {
        $(".popupContainer").empty();
        var offsetTop = 90;
        if (options) {
            if (options.offsetTop) {
                offsetTop = options.offsetTop;
            }
        }

        var self = this;
        if (url.indexOf("?") != -1)
            var noCacheUrl = url + "&x=" + (new Date()).getTime();
        else
            var noCacheUrl = url + "?x=" + (new Date()).getTime();
        $(this.popupContainerSelector).load(noCacheUrl + " .popupContent", function() {

            //loads popup only if it is disabled
            if (self.popupStatus == 0) {

                if (offsetTop) {
                    $(self.popupContainerSelector).css({
                        "top": offsetTop + "px"
                    });
                }
                var realTop = parseInt(self.GetScrollPage().top) + 20;
                if (realTop > 0) {
                    $(self.popupContainerSelector).css({
                        "top": realTop + "px"
                    });
                }
                $(self.backgroupPopupSelector).css({
                    "opacity": "0.7"
                });
                $(self.backgroupPopupSelector).fadeIn("slow");
                $(self.popupContainerSelector).fadeIn("slow");
                self.popupStatus = 1;

                $(self.backgroupPopupSelector).css({
                    "height": $(document).height()
                });

                //self.centerPopup();

                if (callback) {
                    callback();
                }
            }
        });
    },

    //disabling popup
    disablePopup: function(callback) {

        //disables popup only if it is enabled  
        if (this.popupStatus == 1) {
            $(this.backgroupPopupSelector).fadeOut("slow");
            $(this.popupContainerSelector).fadeOut("slow");
            this.popupStatus = 0;
        }

        if (callback)
            callback();
    },

    //centering popup
    centerPopup: function() {

        //request data for centering  
        var windowWidth = document.documentElement.clientWidth;
        var windowHeight = document.documentElement.clientHeight;
        var popupHeight = $(this.popupContainerSelector).height();
        var popupWidth = $(this.popupContainerSelector).width();

        //centering
        $(this.popupContainerSelector).css({
            "position": "absolute",
            "top": windowHeight / 2 - popupHeight / 2,
            "left": windowWidth / 2 - popupWidth / 2
        });
        //only need force for IE6
        $(this.backgroupPopupSelector).css({
            "height": windowHeight
        });

    },

    GetScrollPage: function() {
        var Left;
        var Top;
        var DocRef;

        if (window.innerWidth) {
            with (window) {
                Left = pageXOffset;
                Top = pageYOffset;
            }
        } else { // Cas Explorer a part
            if (document.documentElement && document.documentElement.clientWidth)
                DocRef = document.documentElement;
            else
                DocRef = document.body;

            with (DocRef) {
                Left = scrollLeft;
                Top = scrollTop;
            }
        }
        return ({ top: Top, left: Left });
    }

}
// Constantes
var evPhotoFormat = {};
evPhotoFormat.ORIGINAL = "original";
evPhotoFormat.SMALL = "50_50";
evPhotoFormat.THUMBNAIL = "152_106";
evPhotoFormat.LARGE = "370_216";

var evPhotoModes = { VVDC : "VVDC", AMAZON : "S3"};

// Constructeur
function evphoto(hash, copyright, ext, aws) {

    this.hash = hash;
    this.copyright = copyright;
    this.ext = ext;
    this.obj = null;
    this.external = null;
    this.aws = aws || false;
    this.s3BaseDomain = "http://evaway.s3.amazonaws.com/photos/";
}

// Prototype
evphoto.prototype = {


    // Retourne le copyright
    getCopyright: function () {
        return this.copyright;
    },
       
    // Retourne l'url de la photo, indépendemment de l'infrastructure de stockage
    getUrl: function (width, height, format) {
        if (PHOTO_MODE == evPhotoModes.VVDC /* || this.aws == false*/) {
            return this.getVVDCUrl(width, height);
        }
        else {
            return this.getS3Url(format);
        }
    },

    // Retourne l'url VVDc de la photo
    getVVDCUrl: function (width, height) {

        var url = "";
        if (this.obj) {
            var url = "/photos/" + this.obj.type + "/" + this.obj.id + "/" + this.hash + this.ext;
            if (width && height)
                url += "?width=" + width + "&height=" + height + "&crop=auto";
        }
        return url;
    },

    // retourne l'url S3 de la photo
    getS3Url: function (format) {
        return this.s3BaseDomain + format + "/" + this.hash.substr(0, 2) + "/" + this.hash + this.ext;
    }

}



// Constructeur
function externalphoto(url, thumburl, copyright) {

    this.url = url;
    this.thumburl = thumburl || null;
    this.copyright = copyright;
    this.obj = null;
    this.external = true;
}

// Prototype
externalphoto.prototype = {


    // Retourne le copyright
    getCopyright: function () {
        return this.copyright;
    }

}
/*
 * Date prototype extensions. Doesn't depend on any
 * other code. Doens't overwrite existing methods.
 *
 * Adds dayNames, abbrDayNames, monthNames and abbrMonthNames static properties and isLeapYear,
 * isWeekend, isWeekDay, getDaysInMonth, getDayName, getMonthName, getDayOfYear, getWeekOfYear,
 * setDayOfYear, addYears, addMonths, addDays, addHours, addMinutes, addSeconds methods
 *
 * Copyright (c) 2006 Jörn Zaefferer and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 *
 * Additional methods and properties added by Kelvin Luck: firstDayOfWeek, dateFormat, zeroTime, asString, fromString -
 * I've added my name to these methods so you know who to blame if they are broken!
 * 
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 */

/**
 * An Array of day names starting with Sunday.
 * 
 * @example dayNames[0]
 * @result 'Sunday'
 *
 * @name dayNames
 * @type Array
 * @cat Plugins/Methods/Date
 */
Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

/**
 * An Array of abbreviated day names starting with Sun.
 * 
 * @example abbrDayNames[0]
 * @result 'Sun'
 *
 * @name abbrDayNames
 * @type Array
 * @cat Plugins/Methods/Date
 */
Date.abbrDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

/**
 * An Array of month names starting with Janurary.
 * 
 * @example monthNames[0]
 * @result 'January'
 *
 * @name monthNames
 * @type Array
 * @cat Plugins/Methods/Date
 */
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

/**
 * An Array of abbreviated month names starting with Jan.
 * 
 * @example abbrMonthNames[0]
 * @result 'Jan'
 *
 * @name monthNames
 * @type Array
 * @cat Plugins/Methods/Date
 */
Date.abbrMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

/**
 * The first day of the week for this locale.
 *
 * @name firstDayOfWeek
 * @type Number
 * @cat Plugins/Methods/Date
 * @author Kelvin Luck
 */
Date.firstDayOfWeek = 1;

/**
 * The format that string dates should be represented as (e.g. 'dd/mm/yyyy' for UK, 'mm/dd/yyyy' for US, 'yyyy-mm-dd' for Unicode etc).
 *
 * @name format
 * @type String
 * @cat Plugins/Methods/Date
 * @author Kelvin Luck
 */
Date.format = 'dd/mm/yyyy';
//Date.format = 'mm/dd/yyyy';
//Date.format = 'yyyy-mm-dd';
//Date.format = 'dd mmm yy';

/**
 * The first two numbers in the century to be used when decoding a two digit year. Since a two digit year is ambiguous (and date.setYear
 * only works with numbers < 99 and so doesn't allow you to set years after 2000) we need to use this to disambiguate the two digit year codes.
 *
 * @name format
 * @type String
 * @cat Plugins/Methods/Date
 * @author Kelvin Luck
 */
Date.fullYearStart = '20';

(function() {

	/**
	 * Adds a given method under the given name 
	 * to the Date prototype if it doesn't
	 * currently exist.
	 *
	 * @private
	 */
	function add(name, method) {
		if( !Date.prototype[name] ) {
			Date.prototype[name] = method;
		}
	};
	
	/**
	 * Checks if the year is a leap year.
	 *
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.isLeapYear();
	 * @result true
	 *
	 * @name isLeapYear
	 * @type Boolean
	 * @cat Plugins/Methods/Date
	 */
	add("isLeapYear", function() {
		var y = this.getFullYear();
		return (y%4==0 && y%100!=0) || y%400==0;
	});
	
	/**
	 * Checks if the day is a weekend day (Sat or Sun).
	 *
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.isWeekend();
	 * @result false
	 *
	 * @name isWeekend
	 * @type Boolean
	 * @cat Plugins/Methods/Date
	 */
	add("isWeekend", function() {
		return this.getDay()==0 || this.getDay()==6;
	});
	
	/**
	 * Check if the day is a day of the week (Mon-Fri)
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.isWeekDay();
	 * @result false
	 * 
	 * @name isWeekDay
	 * @type Boolean
	 * @cat Plugins/Methods/Date
	 */
	add("isWeekDay", function() {
		return !this.isWeekend();
	});
	
	/**
	 * Gets the number of days in the month.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getDaysInMonth();
	 * @result 31
	 * 
	 * @name getDaysInMonth
	 * @type Number
	 * @cat Plugins/Methods/Date
	 */
	add("getDaysInMonth", function() {
		return [31,(this.isLeapYear() ? 29:28),31,30,31,30,31,31,30,31,30,31][this.getMonth()];
	});
	
	/**
	 * Gets the name of the day.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getDayName();
	 * @result 'Saturday'
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getDayName(true);
	 * @result 'Sat'
	 * 
	 * @param abbreviated Boolean When set to true the name will be abbreviated.
	 * @name getDayName
	 * @type String
	 * @cat Plugins/Methods/Date
	 */
	add("getDayName", function(abbreviated) {
		return abbreviated ? Date.abbrDayNames[this.getDay()] : Date.dayNames[this.getDay()];
	});

	/**
	 * Gets the name of the month.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getMonthName();
	 * @result 'Janurary'
	 *
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getMonthName(true);
	 * @result 'Jan'
	 * 
	 * @param abbreviated Boolean When set to true the name will be abbreviated.
	 * @name getDayName
	 * @type String
	 * @cat Plugins/Methods/Date
	 */
	add("getMonthName", function(abbreviated) {
		return abbreviated ? Date.abbrMonthNames[this.getMonth()] : Date.monthNames[this.getMonth()];
	});

	/**
	 * Get the number of the day of the year.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getDayOfYear();
	 * @result 11
	 * 
	 * @name getDayOfYear
	 * @type Number
	 * @cat Plugins/Methods/Date
	 */
	add("getDayOfYear", function() {
		var tmpdtm = new Date("1/1/" + this.getFullYear());
		return Math.floor((this.getTime() - tmpdtm.getTime()) / 86400000);
	});
	
	/**
	 * Get the number of the week of the year.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.getWeekOfYear();
	 * @result 2
	 * 
	 * @name getWeekOfYear
	 * @type Number
	 * @cat Plugins/Methods/Date
	 */
	add("getWeekOfYear", function() {
		return Math.ceil(this.getDayOfYear() / 7);
	});

	/**
	 * Set the day of the year.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.setDayOfYear(1);
	 * dtm.toString();
	 * @result 'Tue Jan 01 2008 00:00:00'
	 * 
	 * @name setDayOfYear
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("setDayOfYear", function(day) {
		this.setMonth(0);
		this.setDate(day);
		return this;
	});
	
	/**
	 * Add a number of years to the date object.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.addYears(1);
	 * dtm.toString();
	 * @result 'Mon Jan 12 2009 00:00:00'
	 * 
	 * @name addYears
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("addYears", function(num) {
		this.setFullYear(this.getFullYear() + num);
		return this;
	});
	
	/**
	 * Add a number of months to the date object.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.addMonths(1);
	 * dtm.toString();
	 * @result 'Tue Feb 12 2008 00:00:00'
	 * 
	 * @name addMonths
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("addMonths", function(num) {
		var tmpdtm = this.getDate();
		
		this.setMonth(this.getMonth() + num);
		
		if (tmpdtm > this.getDate())
			this.addDays(-this.getDate());
		
		return this;
	});
	
	/**
	 * Add a number of days to the date object.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.addDays(1);
	 * dtm.toString();
	 * @result 'Sun Jan 13 2008 00:00:00'
	 * 
	 * @name addDays
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("addDays", function(num) {
		this.setDate(this.getDate() + num);
		return this;
	});
	
	/**
	 * Add a number of hours to the date object.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.addHours(24);
	 * dtm.toString();
	 * @result 'Sun Jan 13 2008 00:00:00'
	 * 
	 * @name addHours
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("addHours", function(num) {
		this.setHours(this.getHours() + num);
		return this;
	});

	/**
	 * Add a number of minutes to the date object.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.addMinutes(60);
	 * dtm.toString();
	 * @result 'Sat Jan 12 2008 01:00:00'
	 * 
	 * @name addMinutes
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("addMinutes", function(num) {
		this.setMinutes(this.getMinutes() + num);
		return this;
	});
	
	/**
	 * Add a number of seconds to the date object.
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.addSeconds(60);
	 * dtm.toString();
	 * @result 'Sat Jan 12 2008 00:01:00'
	 * 
	 * @name addSeconds
	 * @type Date
	 * @cat Plugins/Methods/Date
	 */
	add("addSeconds", function(num) {
		this.setSeconds(this.getSeconds() + num);
		return this;
	});
	
	/**
	 * Sets the time component of this Date to zero for cleaner, easier comparison of dates where time is not relevant.
	 * 
	 * @example var dtm = new Date();
	 * dtm.zeroTime();
	 * dtm.toString();
	 * @result 'Sat Jan 12 2008 00:01:00'
	 * 
	 * @name zeroTime
	 * @type Date
	 * @cat Plugins/Methods/Date
	 * @author Kelvin Luck
	 */
	add("zeroTime", function() {
		this.setMilliseconds(0);
		this.setSeconds(0);
		this.setMinutes(0);
		this.setHours(0);
		return this;
	});
	
	/**
	 * Returns a string representation of the date object according to Date.format.
	 * (Date.toString may be used in other places so I purposefully didn't overwrite it)
	 * 
	 * @example var dtm = new Date("01/12/2008");
	 * dtm.asString();
	 * @result '12/01/2008' // (where Date.format == 'dd/mm/yyyy'
	 * 
	 * @name asString
	 * @type Date
	 * @cat Plugins/Methods/Date
	 * @author Kelvin Luck
	 */
	add("asString", function() {
		var r = Date.format;
		return r
			.split('yyyy').join(this.getFullYear())
			.split('yy').join((this.getFullYear() + '').substring(2))
			.split('mmm').join(this.getMonthName(true))
			.split('mm').join(_zeroPad(this.getMonth()+1))
			.split('dd').join(_zeroPad(this.getDate()));
    });


    add("toIso", function(format, offset) {
        /* accepted values for the format [1-6]:
        1 Year:
        YYYY (eg 1997)
        2 Year and month:
        YYYY-MM (eg 1997-07)
        3 Complete date:
        YYYY-MM-DD (eg 1997-07-16)
        4 Complete date plus hours and minutes:
        YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
        5 Complete date plus hours, minutes and seconds:
        YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
        6 Complete date plus hours, minutes, seconds and a decimal
        fraction of a second
        YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
        */
        if (!format) { var format = 6; }
        if (!offset) {
            var offset = 'Z';
            var date = this;
        } else {
            var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
            var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
            offsetnum *= ((d[1] == '-') ? -1 : 1);
            var date = new Date(Number(Number(this) + (offsetnum * 60000)));
        }

        var zeropad = function(num) { return ((num < 10) ? '0' : '') + num; }

        var str = "";
        str += date.getUTCFullYear();
        if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
        if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
        if (format > 3) {
            str += "T" + zeropad(date.getUTCHours()) +
                   ":" + zeropad(date.getUTCMinutes());
        }
        if (format > 5) {
            var secs = Number(date.getUTCSeconds() + "." +
                       ((date.getUTCMilliseconds() < 100) ? '0' : '') +
                       zeropad(date.getUTCMilliseconds()));
            str += ":" + zeropad(secs);
        } else if (format > 4) { str += ":" + zeropad(date.getUTCSeconds()); }

        if (format > 3) { str += offset; }
        return str;
    });

	
	
	/**
	 * Returns a new date object created from the passed String according to Date.format or false if the attempt to do this results in an invalid date object
	 * (We can't simple use Date.parse as it's not aware of locale and I chose not to overwrite it incase it's functionality is being relied on elsewhere)
	 *
	 * @example var dtm = Date.fromString("12/01/2008");
	 * dtm.toString();
	 * @result 'Sat Jan 12 2008 00:00:00' // (where Date.format == 'dd/mm/yyyy'
	 * 
	 * @name fromString
	 * @type Date
	 * @cat Plugins/Methods/Date
	 * @author Kelvin Luck
	 */
	Date.fromString = function(s)
	{
		var f = Date.format;
		var d = new Date('01/01/1977');
		var iY = f.indexOf('yyyy');
		if (iY > -1) {
			d.setFullYear(Number(s.substr(iY, 4)));
		} else {
			// 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 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);
		}
		d.setDate(Number(s.substr(f.indexOf('dd'), 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 :(
	};
	
})();
/*global window */
/*global $ */
/*global EV: true */
/*global console: true */
var evaway, EV;
evaway = EV = window.evaway = window.EV = (function () {
    
    var EV = function evaway() { };
    EV.prototype = {};
    return EV;
} ());

// Espace de context global
evaway.globalContext = {};

// Core
evaway.core = {};

// Domain
evaway.domain = {};

// Data
evaway.data = {};

// Utils
evaway.utils = {};

// Map Core
evaway.map = {};

// UI
evaway.ui = {};

// Map UI
evaway.ui.map = {};

// Components
evaway.components = {};

// External
evaway.ext = {};
evaway.ext.google = {};
evaway.ext.yahoo = {};
evaway.ext.facebook = {};

evaway.randIdGen = (function (EV) {
    
	var RandGen = function () {
		this.generatedIds = [];
		this.range = 10000;
	};
	
	RandGen.prototype = {

		getId : function () {
			var randId;
			do {
				randId =  Math.floor(Math.random() * (this.range + 1));
			} while ($.inArray(randId, this.generatedIds) !== -1);
			
			this.generatedIds.push(randId);		
			return randId;	
		}	
	};
	
	return new RandGen();

}(evaway));

// Init des objets globaux
evaway.init = function (config) {

    if (config) {
        evaway.globalContext.needChooseWishes = config.hasRoadtrips || false;
        evaway.globalContext.pageName = config.pageName || null;
        evaway.globalContext.domain = config.domain || null;
        evaway.globalContext.facebookAppId = config.facebookAppId || null;
    }

    // Le gestionnaire de popup
    if (evaway.ui.popupManager) {
        evaway.globalContext.dialogManager = new evaway.ui.popupManager();
    }

    // Le gestionnaire de session
    if (evaway.core.sessionManager) {
        evaway.globalContext.sessionManager = new evaway.core.sessionManager();
    }

    // Le blocker 
    if (evaway.ui.blocker) {
        evaway.globalContext.blocker = new evaway.ui.blocker();
    }

};

evaway.initProxies = function () {
    evaway.globalContext.usersRestProxy = new EV.data.restProxy(evaway.globalContext.domain + '/api/rest/users');
    evaway.globalContext.roadtripsRestProxy = new EV.data.restProxy(evaway.globalContext.domain + '/api/rest/roadtrips');
    evaway.globalContext.stepsRestProxy = new EV.data.restProxy(evaway.globalContext.domain + '/api/rest/steps');
    evaway.globalContext.roadtripStepsRestProxy = new EV.data.restProxy(evaway.globalContext.domain + '/api/rest/roadtripssteps');
    evaway.globalContext.roadtripSubstepsRestProxy = new EV.data.restProxy(evaway.globalContext.domain + '/api/rest/roadtripssubsteps');
};

// Validator formulaire
//$.tools.validator.localize("fr", {
//    '*': 'RESSOURCE A EDITER : GenericErrorText',
//    '[required]': 'Champ obligatoire'
//});  


// Console Definition for debug
if (console === null || console === undefined) {
    var console = {};
    console.log = function () {  };
}
/*global evaway */
evaway.domain.location = function (config, wslocation) {

    this.jsType = "evaway.domain.location";

    this.id = null;
    this.name = null;
    this.nbStars = null;
    this.stepableObject = null;
    this._conf = config;
    this.availabilityInfo = null;
    this._rootRedirect = "/Redirect.aspx";

    var loca = null;

    if (config) {
        this._MarkerConstructor = config.markerManager;
        this._styler = config.stepStyler;
    }

    this._initMapMarker = function (location, MarkerConstructor) {
        if (MarkerConstructor !== null && typeof MarkerConstructor === 'function') {
            var config = { latitude: 'latitude', longitude: 'longitude' },
                mark = null;

            mark = new MarkerConstructor(location, config, location._styler);
            location.setMapMarker(mark);
        }
    };

    this.init = function (location, wslocation) {
        location.id = wslocation.id;
        location.nbStars = wslocation.nbStars;
        location.defaultPrice = wslocation.price;
        location.defaultRedirect = wslocation.defaultRedirect;
        location.defaultVendorId = wslocation.vendorIds[0];
        location.geoDistance = wslocation.geoDistance;
        location.category = wslocation.category;

        if (location.category == 1) {
            location._rootRedirect = "/CompareOffers.aspx";
        }

        // Stepable object 
        if (wslocation.stepableObject) {
            var loca = wslocation.stepableObject;
            location.stepableObject = new evaway.domain.step({}, loca);
        }

        // Init du marker map
        if (location._MarkerConstructor) {
            location.destroyMapMarker();
            location._initMapMarker(location, location._MarkerConstructor);
        }

    };

    if (wslocation) {
        this.init(this, wslocation);
    }

};

evaway.domain.location.prototype = {

    getRedirectUrl: function () {

        if (this.availabilityInfo) {
            return this._rootRedirect + "?vendor_id=" + this.defaultVendorId + "&amp;url=" + this.availabilityInfo.redirectUrl;
        } else {
            return this._rootRedirect +  "?vendor_id=" + this.defaultVendorId + "&amp;url=" + this.defaultRedirect;
        }
    },

    getVendorId: function () {

        if (this.availabilityInfo) {
            return this.availabilityInfo.vendorId;
        } else {
            return this.defaultVendorId;
        }
    },

    getPrice: function () {

        if (this.availabilityInfo) {
            return this.availabilityInfo.price;
        } else {
            return this.defaultPrice;
        }
    },

    hideOnMap: function () {

        //        if (this.stepableObject) {
        //            this.stepableObject.hideOnMap(null);
        //        }

        if (this._mapMarker) {
            this._mapMarker.hide(null);
        }
    },

    showOnMap: function (map) {

        //        if (this.stepableObject) {
        //            return this.stepableObject.showOnMap(map);
        //        }        
        if (this._mapMarker) {
            this._mapMarker.show(map, this._styler);
        }

    },

    getExtent: function () {

        var extent = null;
        if (this.stepableObject) {
            extent = this.stepableObject.getExtent();
        }

        return extent;
    },

    getReferencePhotoUrl: function (format) {

        return this.stepableObject.getReferencePhotoUrl(format);
    },

    getFullId: function () {

        return this.stepableObject.getFullId();
    },

    getMapMarker: function () {

        return this._mapMarker;
    },

    destroyMapMarker: function () {

        if (this._mapMarker) {
            this._mapMarker.hide();
        }
    },

    setMapMarker: function (marker) {

        this._mapMarker = marker;
    },

    getLatitude: function () {

        return this.stepableObject.latitude;
    },
    
    getLongitude: function () {

        return this.stepableObject.longitude;
    }
};
/*global evaway */
evaway.domain.searchParam = function () {

    this.jsType = "evaway.domain.searchParam";
    this.start = 0;
    this.limit = 10;
    this.sort = null;
    this.mode = null;
    this.sortDir = "ASC";
    this.searchCriterias = [];

};

evaway.domain.searchParam.prototype = {

    clone: function () {

        var newSearchParams = new evaway.domain.searchParam();
        newSearchParams.start = this.start;
        newSearchParams.limit = this.limit;
        newSearchParams.sort = this.sort;
        newSearchParams.mode = this.mode;
        newSearchParams.sortDir = this.sortDir;
        newSearchParams.searchCriterias = this.searchCriterias;
        return newSearchParams;
    },

    setCriterias: function (criterias) {

        this.searchCriterias = criterias;
    },

    getCriteriaValue: function (field) {

        var value = null,
            i;
        for (i = 0; i < this.searchCriterias.length; i++) {
            if (this.searchCriterias[i].field === field) {
                value = this.searchCriterias[i].value;
                break;
            }
        }
        return value;
    },

    hasCriteria: function (criteria) {

        var pos = -1,
            i;
        for (i = 0; i < this.searchCriterias.length; i++) {
            if (this.searchCriterias[i].field === criteria) {
                pos = i;
                break;
            }
        }
        return (pos > -1);
    },

    containsCriteria: function (criteria) {

        var pos = -1,
            i;
        for (i = 0; i < this.searchCriterias.length; i++) {
            if (this.searchCriterias[i].field === criteria.field) {
                pos = i;
                break;
            }
        }
        return pos;
    },

    addCriteria: function (field, value) {

        var criteria = { field: field, value: value };
        if (this.containsCriteria(criteria) > -1) {
            this.removeCriteria(criteria.field);
        }
        this.searchCriterias.push(criteria);
    },

    removeCriteria: function (field) {

        var criteria = { field: field },
            pos = this.containsCriteria(criteria);
        if (pos > -1) {
            this.searchCriterias.splice(pos, 1);
        }
    },

    toWSObject: function () {

        var wsSearchParams = {};
        wsSearchParams.start = this.start;
        wsSearchParams.limit = this.limit;
        if (this.mode) {
            wsSearchParams.mode = this.mode;
        }
        if (this.sort) {
            wsSearchParams.sort = this.sort;
            if (this.sortDir) {
                wsSearchParams.sortDir = this.sortDir;
            }
        }
        wsSearchParams.searchCriterias = this.searchCriterias;

        return wsSearchParams;
    }


};
/*global EV */
/*global evaway */
evaway.domain.step = function (config, wsstep) {

    this.jsType = "evaway.domain.step";

    this.refName = null;
    this.refDescription = null;
    this.country = null;
    this.objectId = null;
    this.objectType = null;
    this.creatorId = null;
    this.latitude = null;
    this.longitude = null;


    //partie edit step popup
    this.name = null;
    this.description = null;
    this.showdesc = false;
    this.userRating = null;
    this.editStepStatus = null;
    this.creator = null;
    this.fake = null;
    this._photos = [];
    this._nbPhotos = 0;

    this._initMapMarker = function (step, MarkerConstructor) {
        var config = null;
        if (MarkerConstructor !== null && typeof MarkerConstructor === 'function') {
            config = { latitude: 'latitude', longitude: 'longitude', label: 'objectId' };

            step.setMapMarker(new MarkerConstructor(step, config, step.getStyler()));
        }
    };

    this._conf = config;
    this._wsstep = wsstep;

    if (config) {
        this._markerConstructor = config.markerManager;
        this._stepStyler = config.stepStyler;
        this._substepStyler = config.substepStyler;
    }

    this._randId = EV.randIdGen.getId();
    this._mapMarker = null;
    this.defaultPhoto = null;

    this._initFromWsData = function (step, wsstep) {

        // Properties        
        step.objectId = wsstep.objectId;
        step.objectType = wsstep.objectType;
        step.refDescription = wsstep.refDescription;
        step.refName = wsstep.refName;
        step.latitude = wsstep.latitude;
        step.longitude = wsstep.longitude;
        step.country = wsstep.country;
        step.nbUsed = wsstep.nbUsed;
        step.rating = wsstep.rating;
        step.nbRatings = wsstep.nbRatings;

        if (wsstep.photos) {
            step.defaultPhoto = new EV.domain.photo(wsstep.photos);
        }

        if (step._markerConstructor) {
            step._initMapMarker(step, step._markerConstructor);
        }

    };

    if (wsstep) {
        this._initFromWsData(this, wsstep);
    }

};

// public API -- prototype
evaway.domain.step.prototype = {

    //Properties
    getRandId: function () {

        return this._randId;
    },

    setRandId: function (randId) {

        this._randId = randId;
    },

    getLatitude: function () {

        return this.latitude;
    },

    setLatitude: function (lat) {

        this.latitude = lat;
        this._initMapMarker(this, this._markerConstructor);
    },

    getLongitude: function () {

        return this.longitude;
    },

    setLongitude: function (lng) {

        this.longitude = lng;
        this._initMapMarker(this, this._markerConstructor);
    },

    getMapMarker: function () {

        return this._mapMarker;
    },

    setMapMarker: function (marker) {

        this._mapMarker = marker;
    },


    getExtent: function () {
        var extent = {},
            offset = 0.001;
        extent.minX = this.longitude - offset;
        extent.maxX = this.longitude + offset;
        extent.minY = this.latitude - offset;
        extent.maxY = this.latitude + offset;
        return extent;
    },

    getFullId: function () {

        if (this.objectId && this.objectType) {
            return this.objectType.substring(0, 1) + "_" + this.objectId;
        } else {
            return null;
        }
    },

    setStyler: function (styler) {

        this._stepStyler = styler;
        if (this._mapMarker) {
            this._mapMarker.setStyler(styler);
        }
    },



    // Public Methods
    clone: function () {

        if (this._conf && this._wsstep) {
            return new evaway.domain.step(this._conf, this._wsstep);
        }
    },

    // WS-mapping
    toWSObject: function () {

        var wsstep = {};
        if (this.objectId) {
            wsstep.objectId = this.objectId;
        }
        if (this.objectType) {
            wsstep.objectType = this.objectType;
        }
        if (this.creatorId) {
            wsstep.creatorId = this.creatorId;
        }
        if (this.refName) {
            wsstep.refName = this.refName;
        }
        if (this.refDescription) {
            wsstep.refDescription = this.refDescription;
        }
        if (this.country) {
            wsstep.country = this.country;
        }
        if (this.getFullId()) {
            wsstep.fullId = this.getFullId();
        }
        if (this.latitude) {
            wsstep.latitude = this.latitude;
        }
        if (this.longitude) {
            wsstep.longitude = this.longitude;
        }
        if (this.creator) {
            wsstep.creator = this.creator.id;
        }
        if (this.name) {
            wsstep.name = this.name;
        }
        if (this.userRating !== null) {
            wsstep.userRating = this.userRating;
        }
        wsstep.photosPopup = [];
        for (i = 0; i < this._nbPhotos; i++) {
            wsstep.photosPopup.push(this._photos[i].toWSObject());
        }
        if (this.fake) {
            wsstep.fake = this.fake;
        }
        if (this.description) {
            wsstep.description = this.description;
        }
        return wsstep;
    },

    destroy: function () {

        this.hideOnMap();
        this._mapMarker = null;

        // JSLint interdit cette ligne :
        //      Le delete ne porte que sur une propriete.
        //delete this;
        if (this.objectId) {
            delete this.objectId;
        }
        if (this.objectType) {
            delete this.objectType;
        }
        if (this.creatorId) {
            delete this.creatorId;
        }
        if (this.refName) {
            delete this.refName;
        }
        if (this.refDescription) {
            delete this.refDescription;
        }
        if (this.country) {
            delete this.country;
        }
        if (this.latitude) {
            delete this.latitude;
        }
        if (this.longitude) {
            delete this.longitude;
        }
    },

    getStyler: function () {

        if (this.isSubstep === true) {
            return this._substepStyler;
        } else {
            return this._stepStyler;
        }
    },

    getCoords: function () {

        if (this.getLatitude() && this.getLongitude()) {
            return { lat: this.getLatitude(), lng: this.getLongitude() };
        } else {
            return null;
        }
    },

    zoomTo: function () {

        if (this._mapMarker) {
            this._mapMarker.zoomTo();
        }
    },

    hideOnMap: function () {

        if (this._mapMarker) {
            this._mapMarker.hide(null);
        }
    },

    showOnMap: function (map) {

        if (this._mapMarker) {
            this._mapMarker.show(map, this.getStyler());
        }
    },

    getReferencePhotoUrl: function (format) {

        var url = "";
        if (this.defaultPhoto) {
            url = this.defaultPhoto.getUrl(format);
        }
        else {
            url = evaway.domain.photoFormats.empty[format];
        }
        return url;
    },

    getReferencePhotoCopyright: function () {

        var copyright = "";
        if (this.defaultPhoto) {
            copyright = this.defaultPhoto.copyright;
        }
        return copyright;
    },

    getObjectType: function () {

        return this.objectType;
    },

    getObjectId: function () {

        return this.objectId;
    },

    setUserRating: function (note) {

        if (note && typeof note === "number") {
            this.userRating = note;
        }
    },

    setPhotos: function (photoArray) {
        var i;
        this.clearPhotos();
        for (i = 0; i < photoArray.length; i++) {
            this._photos.push(photoArray[i]);
            this._nbPhotos++;
        }
    },

    clearPhotos: function () {

        this._photos = [];
        this._nbPhotos = 0;
    },

    save: function (callback) {
        var self = this,
            proxy = evaway.globalContext.stepsRestProxy;
        if (proxy) {

            proxy.update(self.getFullId(), self, function (result) {

                if (result.Code === "OK")
                    console.log("well done");
                else
                    console.log("arrff");

                if (callback && typeof callback === "function")
                    callback();

            });

        }

    }

};
/*global evaway */
evaway.domain.photoFormats = {};
evaway.domain.photoFormats.ORIGINAL = "original";
evaway.domain.photoFormats.SMALL = "50_50";
evaway.domain.photoFormats.THUMBNAIL = "152_106";
evaway.domain.photoFormats.LARGE = "370_216";
evaway.domain.photoFormats.empty = {};
var url = "http://evaway.s3.amazonaws.com/photos/empty/empty-small.jpg";
evaway.domain.photoFormats.empty.SMALL = url;
evaway.domain.photoFormats.empty[evaway.domain.photoFormats.SMALL] = evaway.domain.photoFormats.empty.SMALL;
url = "http://evaway.s3.amazonaws.com/photos/empty/empty-thumbnail.jpg";
evaway.domain.photoFormats.empty.THUMBNAIL = url;
evaway.domain.photoFormats.empty[evaway.domain.photoFormats.THUMBNAIL] = evaway.domain.photoFormats.empty.THUMBNAIL;
url = "http://evaway.s3.amazonaws.com/photos/empty/empty-large.jpg";
evaway.domain.photoFormats.empty.LARGE = url;
evaway.domain.photoFormats.empty[evaway.domain.photoFormats.LARGE] = evaway.domain.photoFormats.empty.LARGE;

evaway.domain.photo = function (photo, item, config) {

    this.jsType = "evaway.domain.photo";

    this.hash = null;
    this.item = item || null;

    //public
    if (photo) {
        if (photo.hash) {
            this.hash = photo.hash;
        }
        if (photo.ext) {
            this.ext = photo.ext;
        }
        if (photo.copyright) {
            this.copyright = photo.copyright;
        }
        if (photo.url) {
            this.url = photo.url;
        }
        if (photo.thumbnailUrl) {
            this.thumbnailUrl = photo.thumbnailUrl;
        }
        if (photo.copyrightURL) {
            this.copyrightURL = photo.copyrightURL;
        }
    }
    this.isReference = false;

    this._s3BaseDomain = "http://evaway.s3.amazonaws.com/photos/";
    if (config) {
        if (config.s3BaseDomain) {
            this._s3BaseDomain = config.s3BaseDomain;
        }
    }

};

evaway.domain.photo.prototype = {

    // Retourne le copyright
    getCopyright: function () {

        return this.copyright;
    },

    // Retourne le copyright
    getCopyrightURL: function () {

        return this.copyrightURL;
    },

    // retourne l'url S3 de la photo
    getS3Url: function (format) {

        var result = this._s3BaseDomain + format + "/";
        result += this.hash.substr(0, 2) + "/";
        result += this.hash + this.ext;
        return result;
    },

    // Retourne l'url de la photo, indépendemment de l'infrastructure de stockage
    getUrl: function (format) {

        if (this.hash !== null) {
            return this.getS3Url(format);
        } else {
            if (format !== evaway.domain.photoFormats.ORIGINAL && this.thumbnailUrl) {
                return this.thumbnailUrl;
            } else if (this.url) {
                return this.url;
            }
        }

        return "";
    },

    getUid: function () {

        if (this.hash) {
            return this.hash;
        } else if (this.url) {
            return this.url;
        }
    },

    toWSObject: function () {

        var wsphoto = {};
        if (this.hash) {
            wsphoto.hash = this.hash;
        }
        if (this.copyright) {
            wsphoto.copyright = this.copyright;
        }
        if (this.ext) {
            wsphoto.ext = this.ext;
        }
        if (this.url) {
            wsphoto.url = this.url;
        }
        return wsphoto;
    }


};
/*global EV */
/*global evaway */
evaway.domain.acresult = function (config, wsacresult) {

    this.jsType = "evaway.domain.acresult";

    this.id = null;
    this.type = null;

    this._conf = config;
    this._wsacresult = wsacresult;
    this._step = null;
    this._country = null;


    this._initFromWsData = function (acresult, wsacresult) {

        if (wsacresult.id) {
            acresult.id = wsacresult.id;
        }
        if (wsacresult.type) {
            acresult.type = wsacresult.type;
        }

        if (wsacresult.step) {
            acresult._step = new evaway.domain.step({}, wsacresult.step);
        }

        if (wsacresult.country) {
            acresult._country = wsacresult.country;
        }
                
    };

    if (wsacresult) {
        this._initFromWsData(this, wsacresult);
    }

};

// public API -- prototype
evaway.domain.acresult.prototype = {



    // WS-mapping
    toWSObject: function () {

        var wsacresult = {};
        return wsacresult;
    }

};
/*global $ */
/*global EV */
/*global Base64 */
/*global evaway */
/*global console */
evaway.data.store = function (name, config) {

    this.jsType = "evaway.data.store";
    this.name = name;
    this.datastore = [];
    this.indexedDatastore = {};
    this.workingDataset = [];
    this.nbItems = 0;
    this.nbWorkingItems = 0;
    this._proxy = null;
    this._restProxy = null;
    this._searchParams = null;

    var temp = null;

    if (config) {
        this._restRootUrl = config.restRootUrl;
        this._userId = config.userId;
        this._token = config.token;
        this._idField = config.idField;
        this._itemConstructor = config.itemConstructor;
        this._constructorConfig = config.constructorConfig;
        if (config.proxy) {
            this._proxy = config.proxy;
        }
    }

    this.init = function () {

        // Si le datastore possède une configuration de type rest, 
        // on initialise un restProxy 
        if (this._restRootUrl) {
            temp = { userId: this._userId, token: this._token };
            this._restProxy = new EV.data.restProxy(this._restRootUrl, temp);
        }
    };

    this._initItem = function (store, data) {
        var item = null,
            isNull = (store._itemConstructor !== null),
            isFunction = (typeof store._itemConstructor === 'function');
        if (isNull && isFunction) {
            item = new store._itemConstructor(store._constructorConfig, data);
        }
        return item;
    };

    this.triggerEvent = function (event, data) {
        $(this).trigger(event, data);
    };

    this.bindEvent = function (event, method) {
        if (event && method && typeof method === "function") {
            $(this).bind(event, method);
        }
    };

    this.init();

};

evaway.data.store.prototype = {

    // Event Listeners
    addOnGetItemsReadyListener: function (method) {

        this.bindEvent("onGetItemsReady", method);
    },

    addOnStoreItemsListener: function (method) {

        this.bindEvent("onStoreItems", method);
    },

    addOnItemSavedListener: function (method) {

        this.bindEvent("onItemSaved", method);
    },


    // Methods
    _buildAuthHeader: function (req) {

        var authStr = "VVDC:" + this._userId + ":" + this._token;
        req.setRequestHeader('vvdc-rest-auth', Base64.encode(authStr));
    },

    getNbItems: function () {

        return this.nbItems;
    },

    getNbWorkingItems: function () {

        return this.nbWorkingItems;
    },

    add: function (item) {

        this.datastore.push(item);
        if (item.id) {
            this.indexedDatastore[item.id] = item;
        }

        this.nbItems = this.datastore.length;
    },

    getAt: function (pos) {

        return this.datastore[pos];
    },

    getWorkingItemAt: function (pos) {

        return this.workingDataset[pos];
    },

    getById: function (id) {

        return this.indexedDatastore[id];
    },

    // TODO : remplacer callback par système d'event ?!
    getItem: function (id, callback) {

        var self = this;
        $.ajax({
            url: self._restRootUrl + id,
            type: 'GET',
            beforeSend: function (req) { self._buildAuthHeader(req); },
            success: function (result) {
                if (result.Code === "OK") {
                    var item = self._initItem(self, result.Data);
                    if (item) {
                        self.add(item);
                    }
                }
                if (callback) {
                    callback();
                }
            }
        });

    },

    flushItems: function () {

        this.workingDataset = [];
        this.nbWorkingItems = 0;
        this.nbItems = 0;
    },

    setItems: function (data) {
        this.workingDataset = data.Data;
        this.nbWorkingItems = data.Data.length;
        this.nbItems = data.NumFound;
    },

    _storeItems: function (data) {
        var self = this;
        self.workingDataset = [];
        self.nbWorkingItems = 0;
        var res = {},
            len = null,
            i = null,
            id = null;

        if (data.Data) {
            len = data.Data.length;

            if (this._searchParams) {
                res.Start = this._searchParams.start;
            }
            else {
                res.Start = 0;
            }

            for (i = 0; i < len; i++) {
                id = data.Data[i][self._idField];
                if (id) {
                    self.indexedDatastore[id] = self._initItem(self, data.Data[i]);
                    self.indexedDatastore[id].__index = res.Start + i + 1;
                    self.datastore.push(self.indexedDatastore[id]);
                    self.workingDataset.push(self.indexedDatastore[id]);
                }
            }

            res.Data = self.workingDataset;
            res.Code = data.Code;
            res.NumFound = data.NumFound;
            res.Message = data.Message;


            self.nbItems += len;
            self.nbWorkingItems = len;
        }

        self.triggerEvent("onStoreItems", [res]);
    },

    searchItems: function (searchParams, callback) {

        var self = this;

        function storeItems(data) {
            self.workingDataset = [];
            self.nbWorkingItems = 0;
            var res = {},
            len = null,
            i = null,
            id = null;

            if (data.Data) {
                len = data.Data.length;
                for (i = 0; i < len; i++) {
                    id = data.Data[i][self._idField];
                    if (id) {
                        self.indexedDatastore[id] = self._initItem(self, data.Data[i]);
                        self.datastore.push(self.indexedDatastore[id]);
                        self.workingDataset.push(self.indexedDatastore[id]);
                    }
                }

                res.Data = self.workingDataset;
                res.Code = data.Code;
                res.NumFound = data.NumFound;
                res.Message = data.Message;
                res.Start = searchParams.start;

                self.nbItems += len;
                self.nbWorkingItems = len;
            }

            self.triggerEvent("onGetItemsReady", [res]);
        }

        self._proxy.searchItems(searchParams, storeItems);

    },

    getItems: function (start, limit, query, filter) {

        var self = this;
        function storeItems(data) {

            self.workingDataset = [];
            self.nbWorkingItems = 0;
            var res = {},
                len = null,
                i = null,
                id = null;

            if (data.Data) {
                len = data.Data.length;
                for (i = 0; i < len; i++) {
                    id = data.Data[i][self._idField];
                    if (id) {
                        self.indexedDatastore[id] = self._initItem(self, data.Data[i]);
                        self.datastore.push(self.indexedDatastore[id]);
                        self.workingDataset.push(self.indexedDatastore[id]);
                    }
                }

                res.Data = self.workingDataset;
                res.Code = data.Code;
                res.NumFound = data.NumFound;
                res.Message = data.Message;
                res.Start = start;

                self.nbItems += len;
                self.nbWorkingItems = len;
            }

            self.triggerEvent("onGetItemsReady", [res]);
        }

        this._proxy.getItems(start, limit, query, filter, storeItems);
    },

    updateItem: function (id, callback) {

        var self = this,
            item = this.indexedDatastore[id];

        if (item.toWSObject && typeof item.toWSObject === 'function') {
            item = item.toWSObject();
        }

        if (item) {
            if (this._restRootUrl) {
                $.ajax({
                    url: self._restRootUrl + id,
                    type: 'PUT',
                    data: JSON.stringify({ wsdata: item }),
                    contentType: "application/json",
                    datatype: "json",
                    processData: false,
                    beforeSend: function (req) { self._buildAuthHeader(req); },
                    success: function (data) {
                        console.log("item updated");
                        if (callback && typeof callback === "function") {
                            callback(data.Data);
                        }
                    }
                });
            }
        }
    },

    //    save: function (id, callback) {

    //        var self = this;
    //        this.updateItem(id, function (data) {
    //            if (callback && typeof callback == "function") {
    //                callback(data);
    //            }
    //        });

    //    },

    save: function (item, callback) {

        var self = this;
        if (this._restProxy) {
            if (item[self._idField] === null || item[self._idField] < 0) {
                this._restProxy.create(item, function (result) {

                    /*
                    if (result.Code !== "OK") {
                    // TODO : afficher message quand la sauvegarde s'est mal passée 
                    }
                    */
                    if (callback && typeof callback === "function") {
                        callback();
                    }
                    console.log("store : onItemSaved : creation");
                    self.triggerEvent("onItemSaved", result);
                });
            } else {

                this._restProxy.update(item[self._idField], item, function (result) {
                    /*
                    if (result.Code !== "OK") {
                    // TODO : afficher message quand la sauvegarde s'est mal passée 
                    }
                    */

                    if (callback && typeof callback === "function") {
                        callback();
                    }

                    console.log("store : onItemSaved : update");
                    self.triggerEvent("onItemSaved", [result]);
                });
            }
        }
    }

};
/*global $ */
/*global evaway */
/*global Base64 */

evaway.data.searchProxy = function (config) {
    
    this.jsType = "evaway.data.searchProxy";
    this._name = "";
    this._domain = "";
    this._rawUrl = ""; //
    this._mode = "";

    if (config.name) {
        this._name = config.name;
    }
    if (config.domain) {
        this._domain = config.domain;
    }
    if (config.rawUrl) {
        this._rawUrl = config.rawUrl;
    }
    if (config.mode) {
        this._mode = config.mode;
    }

    this._ajaxManager = $.manageAjax.create(this._name, {
        queue: "clear",
        abortOld: "true",
        cacheResponse: true
    });

};

evaway.data.searchProxy.prototype = {

    _buildAuthHeader: function (req) {
        
        var authStr = "VVDC:" + this._userId + ":" + this._token;
        req.setRequestHeader('vvdc-rest-auth', Base64.encode(authStr));
    },

    searchItems: function (searchParams, callback) {
        
        var self = this;
        $.ajax({
            url: (self._domain + self._rawUrl),
            dataType: "json",
            data: {
                s : JSON.stringify(searchParams.toWSObject())
            },
            success: function (data) {
                if (callback && typeof callback === "function") {
                    callback(data);
                }
            }
        });
    },

    getItems: function (start, limit, query, filter, callback) {
        
        var self = this;
        $.manageAjax.add(self._name, {
            url: self._domain + self._rawUrl,
            dataType: "json",
            data: {
                mode: self._mode,
                limit: limit,
                start: start,
                query: query,
                filter: filter
            },
            success: function (data) {
                if (callback && typeof callback === "function") {
                    callback(data);
                }
            }
        });
    }

};
/*global $ */
/*global console */
/*global currentActivePage */
/*global evaway */
evaway.ui.standardList = function (config) {

    this.jsType = "evaway.ui.standardList";

    //private
    this._nbItemsPerPage = 6;
    this._maxPages = 7;
    this._currentDataSet = null;
    this._bindClick = true;
    this._bindMouseEnter = true;
    this._bindMouseLeave = true;
    this._useCache = true;
    this._cache = {};
    this._usePager = true;

    if (config) {

        if (config.nbItemsPerPage) {
            this._nbItemsPerPage = config.nbItemsPerPage;
        }
        if (config.maxPages) {
            this._maxPages = config.maxPages;
        }
        if (config.usePager != null || config.usePager != undefined) {
            this._usePager = config.usePager;
        }
        if (config.resultListSelector) {
            this._resultListSelector = config.resultListSelector;
        }
        if (config.itemTemplateSelector) {
            this._itemTemplateSelector = config.itemTemplateSelector;
        }
        if (config.pagerTemplateSelector) {
            this._pagerTemplateSelector = config.pagerTemplateSelector;
        }
        if (config.bindClick !== null && config.bindClick !== undefined) {
            this._bindClick = config.bindClick;
        }
        if (config.bindMouseEnter !== null && config.bindMouseEnter !== undefined) {
            this._bindMouseEnter = config.bindMouseEnter;
        }
        if (config.bindMouseLeave !== null && config.bindMouseLeave !== undefined) {
            this._bindMouseLeave = config.bindMouseLeave;
        }
    }

    this._initTemplates = function (list) {
        list.itemTemplate = $(this._itemTemplateSelector).template();
        list.pagerTemplate = $(this._pagerTemplateSelector).template();
    };

    this._init = function (list) {
        var clickCallback,
            pos,
            mouseEnterCallback,
            mouseLeaveCallback;
        if (this._bindClick === true) {
            clickCallback = function (e) {
                e.stopPropagation();
                pos = $(list._resultListSelector).find("li").index(this);
                list.triggerEvent("onItemClick", [pos]);
                return false;
            };

            $(list._resultListSelector).find("li").live("click", clickCallback);
        }

        if (this._bindMouseEnter === true) {
            mouseEnterCallback = function (e) {
                e.stopPropagation();
                pos = $(list._resultListSelector).find("li").index(this);
                list.triggerEvent("onItemMouseEnter", [pos]);
                return false;
            };

            $(list._resultListSelector).find("li").live("mouseenter", mouseEnterCallback);
        }

        if (this._bindMouseLeave === true) {
            mouseLeaveCallback = function (e) {
                e.stopPropagation();
                pos = $(list._resultListSelector).find("li").index(this);
                list.triggerEvent("onItemMouseLeave", [pos]);
                return false;
            };

            $(list._resultListSelector).find("li").live("mouseleave", mouseLeaveCallback);
        }
    };


    this.triggerEvent = function (event, data) {
        $(this).trigger(event, data);
    };

    this.bindEvent = function (event, method) {
        if (event && method && typeof method === "function") {
            $(this).bind(event, method);
        }
    };

    this._initTemplates(this);
    this._init(this);
};

evaway.ui.standardList.prototype = {

    // Event listeners    
    addOnSearchResultClickListener: function (method) {

        this.bindEvent("onSearchResultClick", method);
    },

    addOnPaginClickListener: function (method) {

        this.bindEvent("onPaginClick", method);
    },

    addOnItemClickListener: function (method) {

        this.bindEvent("onItemClick", method);
    },

    addOnItemMouseEnterListener: function (method) {

        this.bindEvent("onItemMouseEnter", method);
    },
    addOnItemMouseLeaveListener: function (method) {

        this.bindEvent("onItemMouseLeave", method);
    },


    // Methods

    getCacheKey: function () {

        if (this._currentDataSet) {
            return this._currentDataSet.Start;
        } else {
            return null;
        }
    },

    destroy: function () {

        $(this._resultListSelector).find("li").die("click");
    },

    clearPager: function () {

        $(this._resultListSelector).parent().find(".pagin-container").remove();
    },

    clearList: function () {

        //$(this._resultListSelector).parent().hide();
        $(this._resultListSelector).empty();
        this.clearPager();
    },

    constructPager: function () {

        var self = this,
            currentActivePage = 0,
            nbPages,
            pagerData,
            i,
            n,
            a,
            calc,
            item;
        if (this._currentDataSet.Start !== undefined) {
            calc = Math.floor(this._currentDataSet.Start / this._nbItemsPerPage);
            currentActivePage = calc;
        }

//        debugger;
        calc = Math.ceil(this._currentDataSet.NumFound / this._nbItemsPerPage);
        nbPages = Math.min(calc, this._maxPages);
        pagerData = {};
        pagerData.items = [];
        pagerData.nbPages = nbPages;
        pagerData.currentActivePage = currentActivePage;

        if (nbPages > 1) {
            for (i = 0; i < nbPages; i++) {
                item = {};
                item.pageNum = i + 1;
                if (i !== currentActivePage) {
                    item.cssclass = "";
                } else {
                    item.cssclass = "selected";
                }
                pagerData.items.push(item);
            }
        }

        this.clearPager();

        // Binding de la pagin
        if (nbPages > 1) {

            $(this._resultListSelector).after($.tmpl(this.pagerTemplate, pagerData));

            n = ".pagin";
            a = "a";
            $(this._resultListSelector).parent().find(n).children(a).click(function () {
                var page,
                    offset;
                page = parseInt($(this).text(), 10);
                offset = (page - 1) * self._nbItemsPerPage;
                self.triggerEvent("onPaginClick", [{ offset: offset}]);
                return false;
            });
        }

    },

    setData: function (data) {

        var i;
        this._currentDataSet = data;
        if (data.Data) {
            for (i = 0; i < data.Data.length; i++) {
                this._currentDataSet.Data[i].__index = data.Start + i + 1;
            }
        }
    },

    render: function (hide) {
        this.clearList();
        var template = this.itemTemplate,
            data = this._currentDataSet.Data;
        $(this._resultListSelector).append($.tmpl(template, data));
        if (this._usePager) {
            this.constructPager();
        }


        //        var htmln, parentHtml;
        //        if (this._useCache && this._cache[this.getCacheKey()]) {
        //            console.log("using cache for list rendering");
        //            html = this._cache[this.getCacheKey()];
        //            $(this._resultListSelector).parent().append(html);
        //        }
        //        else {
        //            $(this._resultListSelector).append($.tmpl(this.itemTemplate, 
        //                                                    this._currentDataSet.Data));
        //            this.constructPager();

        //            if (this._useCache) {
        //                        parentHtml = $(this._resultListSelector).parent().html();
        //                this._cache[this.getCacheKey()] = parentHtml;
        //            }
        //        }

        //        if (hide == null && hide == undefined && hide != false)
        //            $(this._resultListSelector).parent().show();
    }

};
/*global $ */
/*global evaway */
/*global _gaq */
/*global lsDataStore */

evaway.ui.stepSearcher = function (roadtrip, config) {

    // public 
    this.roadtrip = roadtrip;
    this.searchParams = new evaway.domain.searchParam();

    //private
    this._list = null;
    this._stepsDataStore = null;
    this._hotelsDataStore = null;
    this._type = "p"; //pois

    if (config) {

        if (config.inputSelector) {
            this._inputSelector = config.inputSelector;
        }
        if (config.createStepSelector) {
            this._createStepSelector = config.createStepSelector;
        }
        if (config.stepsStore) {
            this._stepsDataStore = config.stepsStore;
        }
        if (config.hotelsStore) {
            this._hotelsDataStore = config.hotelsStore;
        }
        if (config.listConfig) {
            this._listConfig = config.listConfig;
        }
        if (config.newStepStyler) {
            this._newStepStyler = config.newStepStyler;
        }
        if (config.stepsProxy) {
            this._stepsProxy = config.stepsProxy;
        }
        if (config.searchTypeSelector) {
            this._searchTypeSelector = config.searchTypeSelector;
        }
    }

    this._init = function (searcher) {

        searcher.searchParams.start = 0;
        searcher.searchParams.limit = 9;
        searcher.searchParams.mode = "autocomplete";

        // Init de la liste
        if (searcher._listConfig) {
            searcher._list = new evaway.ui.standardList(searcher._listConfig);

            // Mise à jour de la liste lorsque la recup du search est terminé
            if (searcher._stepsDataStore) {
                searcher._stepsDataStore.addOnGetItemsReadyListener(function (event,
                                                                                data) {
                    searcher._list.setData(data);
                    searcher._list.render();
                    $(searcher._listConfig.resultListSelector).parent().show();
                    $(".results-pane").show();

                    searcher.triggerEvent("onSearchResultsDisplay", []);
                });
            }

            // Mise à jour de la liste lorsque la recup du search est terminé
            if (searcher._hotelsDataStore) {
                searcher._hotelsDataStore.addOnGetItemsReadyListener(function (event,
                                                                                data) {
                    var newData = [],
                        i;
                    for (i = 0; i < data.Data.length; i++) {
                        newData.push({ type: "hotel", _step: data.Data[i].stepableObject });
                    }

                    data.Data = newData;
                    searcher._list.setData(data);
                    searcher._list.render();
                    $(searcher._listConfig.resultListSelector).parent().show();
                    $(".results-pane").show();
                    searcher.triggerEvent("onSearchResultsDisplay", []);
                });
            }

            // Binding de la pagin
            searcher._list.addOnPaginClickListener(function (event, data) {
                var offset = data.offset,
                    inputText = $(searcher._inputSelector).val();
                searcher.search(offset, inputText);
            });

            // Binding du clic sur un item
            searcher._list.addOnItemClickListener(function (event, pos) {
                searcher.clearList();
                searcher.triggerEvent("onSearchResultClick", { item: searcher._list._currentDataSet.Data[pos] });
            });

            // Binding du clic sur creation de poi
            // Popup de creation d'un nouveau poi
            if (searcher._createStepSelector) {
                $(searcher._createStepSelector).live("click", function (event) {
                    //                    var createStepPopupInstance,
                    //                        temp;
                    //                    temp = new evaway.components.createStepPopup(searcher, 
                    //                                                createStepPopupInstance.show());
                    //                    createStepPopupInstance = temp;

                    var createStepPopupInstance = new evaway.components.createStepPopup(searcher, searcher._stepsProxy);
                    createStepPopupInstance.show();
                });
            }

        }

        // effacement du contenu de l'input lors du focus
        $(searcher._inputSelector).focus(function () {
            $(this).val("");
        });

        // binding de la recherche sur le keyup dans l'input
        $(searcher._inputSelector).keyup(function () {
            var inputText = $(this).val();
            searcher.search(0, inputText);
        });

        // binding de la recherche au clic sur rechercher
        $(".search-submit").click(function () {
            var inputText = $(searcher._inputSelector).val();
            searcher.search(0, inputText);
        });

        // Vidage de la liste
        $(".results-pane .empty-list").live("click", function () {
            searcher.clearList();
        });


    };

    this.triggerEvent = function (event, data) {
        $(this).trigger(event, data);
    };

    this.bindEvent = function (event, method) {
        if (event && method && typeof method === "function") {
            $(this).bind(event, method);
        }
    };

    this._init(this);
};

evaway.ui.stepSearcher.prototype = {

    // Event listeners
    addOnSearchResultClickListener: function (method) {

        this.bindEvent("onSearchResultClick", method);
    },

    addOnSearchResultsDisplayListener: function (method) {

        this.bindEvent("onSearchResultsDisplay", method);
    },

    addOnClearListListener: function (method) {

        this.bindEvent("onClearList", method);
    },

    getActiveDatastore: function () {

        this._type = $(this._searchTypeSelector + ":checked").val();
        if (this._type === "p") {
            return this._stepsDataStore;
        } else if (this._type === "l") {
            return this._hotelsDataStore;
        }
        else {
            return this._stepsDataStore;
        }
    },


    search: function (start, inputText) {
        this.searchParams.start = start;
        this.searchParams.addCriteria("query", inputText);
        this.getActiveDatastore().searchItems(this.searchParams);
    },

    clearList: function () {

        this._list.clearList();
        $(this._listConfig.resultListSelector).parent().hide();
        this.triggerEvent("onClearList", []);
    },

    getInput: function () {

        return $(this._inputSelector).val();
    }

};

/*global $ */
/*global Base64 */
/*global evaway */
evaway.map.geocoderV3 = function () {
    this._geocode = new google.maps.Geocoder();
};

evaway.map.geocoderV3.prototype = {

    getLatLngByName: function (name, callback) {
        var config, result;
        config = { address: name };
        this._geocode.geocode(config, callback);
    },

    getNameByLatLng: function (latLng, callback) {
        var config, result;
        config = { 'location': latLng };
        this._geocode.geocode(config, callback);
    }
};

