<commit_before>{% extends 'forms/base_widget.html' %}

{% block html %}
    <div class="i-location-field" id="{{ field.id }}-wrapper">
        <input type="hidden" id="{{ field.id }}" name="{{ field.name }}"
               value="{{ field._value() | tojson | forceescape }}">
        <div class="wrapper">
            <div class="typeahead-container venue-container">
                <div class="typeahead-field">
                    <input id="location-venue-{{ field.id }}"
                           class="i-location-input-field"
                           name="location-venue-{{ field.name }}"
                           type="text"
                           placeholder="{% trans %}Venue{% endtrans %}"
                           autocomplete="off">
                    <span class="keyword-highlighter"></span>
                    <span class="toggle-results-list icon-arrow-down"></span>
                    <div class="typeahead-results">
                        <div class="results-list-container" id="location-venue-{{ field.id }}-results"></div>
                    </div>
                </div>
            </div>
            <div class="typeahead-container room-container">
                <div class="typeahead-field">
                    <input id="location-room-{{ field.id }}"
                           class="i-location-input-field"
                           name="location-room-{{ field.name }}"
                           type="text"
                           placeholder="{% trans %}Room{% endtrans %}"
                           autocomplete="off">
                    <span class="keyword-highlighter"></span>
                    <span class="toggle-results-list icon-arrow-down"></span>
                    <div class="typeahead-results">
                        <div class="results-list-container" id="location-room-{{ field.id }}-results"></div>
                    </div>
                </div>
            </div>
            <textarea id="location-address-{{ field.id }}" name="location-address"
                      placeholder="{% trans %}Address{% endtrans %}"></textarea>
            <div class="location-inheritance-field">
                <div class="checkbox-container">
                    <input id="location-inheritance-{{ field.id }}"
                           name="location-inheritance-{{ field.name }}"
                           type="checkbox">
                    <label for="location-inheritance-{{ field.id }}">{% trans %}Use default{% endtrans %}</label>
                    <i class="info-helper"></i>
                </div>
            </div>
        </div>
    </div>
    <div class="clearfix"></div>
{% endblock %}

{% block javascript %}
    <script>
        (function(global) {
            'use strict';

            var field = $('#{{ field.id }}');
            var helperText = field.closest('.form-field').find('.location-inheritance-field .info-helper');
            var venueInput = $('#location-venue-{{ field.id }}');
            var roomInput = $('#location-room-{{ field.id }}');
            var address = $('#location-address-{{ field.id }}');
            var usingDefault = $('#location-inheritance-{{ field.id }}');
            var venues = {{ venues | tojson }};
            var venueNames = {{ venues.data | map(attribute='name') | list | tojson }};
            var rooms = {{ rooms | tojson }};
            var hiddenData = field.val() ? JSON.parse(field.val()) : {};  // Contains all location related data sent to
                                                                          // the server
            var latestUsedField = null;
            var latestSelectedOption = {field: '', value: ''};
            var currentScrollPos = 0;
            var delta = 0;
            var validRoom = false;
            var validVenue = false;
            var preventGuessing = false;
            var roomJustCleared = false;
            var venueJustChanged = false;
            var justUseOptionSelected = false;
            var resultsWereOpen = false;

            function initializeInputs() {
                address.val(hiddenData.address);
                roomInput.val(hiddenData.room_name);
                venueInput.val(hiddenData.venue_name);
                if (hiddenData.venue_id) {
                    highlightOption(venueInput, venueInput.val());
                }
                if (hiddenData.room_id) {
                    highlightOption(roomInput, roomInput.val());
                }
                usingDefault.prop('checked', hiddenData.inheriting);
            }

            function toggleDefaultLocation(isChecked, isInitStep) {
                var message = ($T.gettext('Using default location from: {0} ({1})').format({{ parent[1] | tojson }},
                                                                                           {{ parent[0] | tojson }}));
                var text = isChecked ? message : $T.gettext('Using custom location');
                helperText.attr('title', text);
                venueInput.prop('disabled', isChecked).toggleClass('disabled', isChecked);
                roomInput.prop('disabled', isChecked).toggleClass('disabled', isChecked);
                address.prop('disabled', isChecked).toggleClass('disabled', isChecked);
                field.closest('.i-location-field').toggleClass('disabled', isChecked);

                hiddenData.inheriting = isChecked;
                field.val(JSON.stringify(hiddenData));

                /* Prefill the disabled inputs with the inherited location when the default location is checked
                (clear them otherwise) */
                if (!isInitStep) {
                    if (isChecked) {
                        hiddenData = {{ field.data.source.inherited_widget_location_data | tojson }};
                        hiddenData.inheriting = true;
                        initializeInputs();
                    } else {
                        clearField(venueInput);
                    }
                    field.val(JSON.stringify(hiddenData)).trigger('change');
                }
            }

            function reorderCategories(venueName) {
                /* When the choice of venue changes, the rooms of the selected venue need to appear on the top of the
                room typeahead list, simply by updating the groupOrder option, moving the selected venue to the first
                position of the array. */
                var groupOrder = [venueName];
                for (var i = venueNames.length - 1; i >= 0; i--) {
                    if (!~groupOrder.indexOf(venueNames[i])) {
                        groupOrder.push(venueNames[i]);
                    }
                }
                global.Typeahead[roomInput.selector].options.groupOrder = groupOrder;
            }

            function updateVenueOnRoomChange(item) {
                /* Override the venue based on the room selection */
                if (item.venue_id != hiddenData.venue_id) {
                    venueJustChanged = true;
                    var data = {item_name: item.group, venue_id: item.venue_id, room_id: item.id};
                    venueInput.val(item.group).trigger('typeahead:click-on-option', data);
                }
            }

            function clearRoom() {
                delete hiddenData.room_id;
                delete hiddenData.room_name;
                roomInput.val('');
                roomInput.siblings('.keyword-highlighter').removeClass('visible');
                roomJustCleared = true;
                postSelectionActions(roomInput, '');
            }

            function clearVenue() {
                delete hiddenData.venue_id;
                delete hiddenData.venue_name;
                venueInput.val('');
                venueInput.siblings('.keyword-highlighter').removeClass('visible');
                venueJustChanged = true;
                postSelectionActions(venueInput, '');
                clearRoom();
            }

            function clearField(field) {
                if (field.is(venueInput)) {
                    clearVenue();
                } else if (field.is(roomInput)) {
                    clearRoom();
                }
            }

            function updateResultsList(node, resultHtmlList) {
                /* Make sure the result dropdown is displayed just below the input field */
                var resultsWrapper = node.siblings('.typeahead-results');
                resultsWrapper.css('top', node.outerHeight() + 'px');

                /* Create/delete the 'just-use' option based on the input */
                var justUse = node.parent().find('.just-use-option');
                if (node.val()) {
                    if (!justUse.length) {
                        justUse = $('<div>', {
                            'class': 'just-use-option'
                        })
                        .on('click', function() {
                            node.trigger('typeahead:click-on-custom-option', {item_name: node.val()});
                        });
                        resultsWrapper.append(justUse);
                    }
                    justUse.text($T.gettext('Just use "{0}"').format(node.val()));
                } else {
                    justUse.remove();
                }

                /* Always mark the first option as active in order for the keyboard inputs to work */
                var firstOption = resultHtmlList.find('li.typeahead-item').first();
                if (!firstOption.length) {
                    node.parent().find('.just-use-option').addClass('active');
                    justUseOptionSelected = true;
                } else {
                    resultHtmlList.find('li.typeahead-item').first().addClass('active');
                    justUseOptionSelected = false;
                }
                return resultHtmlList;
            }

            function scrollResultsList(inputField) {
                var parent = inputField.parent();
                var activeOption = parent.find('.typeahead-results').find('.typeahead-item.active');
                var dropdownList = parent.find('.typeahead-list');
                if (activeOption.length != 0) {
                    if (activeOption.position().top + activeOption.outerHeight() > dropdownList.outerHeight()) {
                        delta = activeOption.position().top + activeOption.outerHeight() - dropdownList.outerHeight();
                        currentScrollPos += delta;
                        dropdownList.animate({
                            scrollTop: currentScrollPos
                        }, 50);
                    } else if (activeOption.position().top < dropdownList.position().top){
                        currentScrollPos += activeOption.position().top;
                        dropdownList.animate({
                            scrollTop: currentScrollPos
                        }, 50);
                    }
                }
                parent.find('.just-use-option').toggleClass('active', !activeOption.length);
                justUseOptionSelected = !activeOption.length;
            }

            function postSelectionActions(inputField, value) {
                field.val(JSON.stringify(hiddenData)).trigger('change');
                if (inputField.is(venueInput)) {
                    latestVenueValue = value;
                } else if (inputField.is(roomInput)) {
                    latestRoomValue = value;
                }
            }

            function highlightOption(inputField, value) {
                var highlighter = inputField.siblings('.keyword-highlighter');
                highlighter.html(value).addClass('visible');
            }

            function cleanQuery(query) {
                return query ? query.trim() : '';
            }

            function findRoomInVenue(query, venue) {
                if (rooms[venue]) {
                    return $.grep(rooms[venue].data, function(room) {
                        return room.name.toUpperCase() == query.toUpperCase();
                    });
                }
                return [];
            }

            function toggleOpenResultsList(inputField) {
                if (inputField.is(venueInput) && roomInput.closest('.typeahead-container').hasClass('result')) {
                    roomInput.trigger('typeahead:close-results-list');
                    venueInput.trigger('typeahead:close-results-list');
                    venueInput.trigger('typeahead:open-results-list');
                } else if (inputField.is(roomInput) && venueInput.closest('.typeahead-container').hasClass('result')) {
                    venueInput.trigger('typeahead:close-results-list');
                    roomInput.trigger('typeahead:close-results-list');
                    roomInput.trigger('typeahead:open-results-list');
                }
            }

            function resetSelectedOption() {
                if (latestUsedField.is(venueInput)) {
                    venueInput.val(latestVenueValue);
                    if (hiddenData.venue_id) {
                        highlightOption(venueInput, latestVenueValue)
                    }
                } else if (latestUsedField.is(roomInput)) {
                    roomInput.val(latestRoomValue);
                    if (hiddenData.room_id) {
                        highlightOption(roomInput, latestRoomValue)
                    }
                }
            }

            initializeInputs();

            var latestVenueValue = venueInput.val();
            var latestRoomValue = roomInput.val();

            usingDefault.on('click', function() {
                toggleDefaultLocation(this.checked);
            });

            toggleDefaultLocation(usingDefault.prop('checked'), true);

            venueInput.typeahead({
                source: venues,
                minLength: 0,
                searchOnFocus: true,
                resultContainer: '#' + venueInput.siblings('.typeahead-results').find('.results-list-container').attr('id'),
                template: {% raw %}'<span id="location-venue-{{id}}">{{name}}</span>'{% endraw %},
                // Used to keep the dropdown list open while there are no results (required by the 'just-use' option)
                emptyTemplate: function() { return ''; },
                display: 'name',
                hint: true,
                callback: {
                    onInit: function(node) {
                        this.query = node.val();  // Updates the results dropdown on init
                    },
                    onClickBefore: function() {
                        // Used in the onHideLayout callback to identify the reason of the dropdown closure
                        validVenue = true;
                    },
                    onClickAfter: function(node, a, item) {
                        venueInput.trigger('typeahead:click-on-option', {
                            item_name: item.name,
                            venue_id: item.id
                        });
                    },
                    onLayoutBuiltBefore: function(node, query, result, resultHtmlList) {
                        return updateResultsList(node, resultHtmlList);
                    },
                    onSearch: function(node, query) {
                        this.query = cleanQuery(query);
                    },
                    onShowLayout: function() {
                        venueInput.siblings('.keyword-highlighter').removeClass('visible');
                        currentScrollPos = 0;
                    },
                    onHideLayout: function(node, query) {
                        if (justUseOptionSelected) {
                            venueInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                        } else if (!validVenue && node.val() && !preventGuessing) {
                            /* Try to guess the choice of the user in case the results dropdown closes without a
                            selection. If an exact match is found, select it, otherwise use the 'just-use' option */
                            var result = $.grep(venues.data, function(venue) {
                                return venue.name.toUpperCase() == query.toUpperCase();
                            });
                            if (result.length) {
                                $('#location-venue-' + result[0].id).parent().trigger('click');
                            } else {
                                venueInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                            }
                        }
                        preventGuessing = false;
                        validVenue = false;
                    },
                    onNavigateBefore: function(node, query, event) {
                        if (event.keyCode == K.TAB) {
                            preventGuessing = true
                        }
                        /* Used in the keydown event handler of the input fields to identify whether the results
                        dropdown were open before the keydown (since the focus remains on the field even after a selection). */
                        resultsWereOpen = node.closest('.typeahead-container').hasClass('result');
                    },
                    onResult: function(node, query, result, resultCount) {
                        node.parent().find('.just-use-option').toggleClass('active', !resultCount);
                        justUseOptionSelected = !resultCount;
                    }
                }
            });

            roomInput.typeahead({
                source: rooms,
                minLength: 0,
                searchOnFocus: true,
                group: true,
                hint: true,
                maxItem: 0,
                resultContainer: '#' + roomInput.siblings('.typeahead-results').find('.results-list-container').attr('id'),
                template: {% raw %}'<span id="location-room-{{id}}">{{name}}</span>'{% endraw %},
                emptyTemplate: function() { return ''; },
                display: 'name',
                callback: {
                    onInit: function(node) {
                        this.query = node.val();
                    },
                    onClickBefore: function() {
                        validRoom = true;
                    },
                    onClickAfter: function(node, a, item) {
                        updateVenueOnRoomChange(item);

                        hiddenData.room_id = item.id;
                        delete hiddenData.room_name;

                        postSelectionActions(roomInput, item.name);
                        latestSelectedOption = {field: node.attr('id'), value: item.name};
                        highlightOption(roomInput, item.name);
                        roomInput.attr('title', item.name);
                    },
                    onLayoutBuiltBefore: function(node, query, result, resultHtmlList) {
                        return updateResultsList(node, resultHtmlList);
                    },
                    onSearch: function(node, query) {
                        this.query = cleanQuery(query);
                    },
                    onShowLayout: function() {
                        roomInput.siblings('.keyword-highlighter').removeClass('visible');
                        currentScrollPos = 0;
                    },
                    onHideLayout: function(node, query) {
                        if (justUseOptionSelected) {
                            roomInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                        } else if (!validRoom && node.val() && !preventGuessing) {
                            var result = [];
                            var venueValue = venueInput.val();
                            if (venueValue) {
                                result = findRoomInVenue(query, venueValue);
                            } else {
                                for (var i = 0; i < venueNames.length && !result.length; i++) {
                                    result = findRoomInVenue(query, venueNames[i]);
                                }
                            }
                            if (result.length) {
                                $('#location-room-' + result[0].id).parent().trigger('click');
                            } else {
                                roomInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                            }
                        }
                        preventGuessing = false;
                        validRoom = false;
                    },
                    onNavigateBefore: function(node, query, event) {
                        if (event.keyCode == K.TAB) {
                            preventGuessing = true;
                        }
                        resultsWereOpen = node.closest('.typeahead-container').hasClass('result');
                    },
                    onResult: function(node, query, result, resultCount) {
                        node.parent().find('.just-use-option').toggleClass('active', !resultCount);
                        justUseOptionSelected = !resultCount;
                    }
                }
            });

            venueInput.on('typeahead:click-on-option', function(e, data) {
                reorderCategories(data.item_name);
                /* The room needs to be cleared when the venue changes */
                if (hiddenData.venue_id && data.venue_id != hiddenData.venue_id && !data.room_id) {
                    clearField(roomInput);
                }
                hiddenData.venue_name = data.item_name;
                hiddenData.venue_id = data.venue_id;
                postSelectionActions(venueInput, data.item_name);
                highlightOption(venueInput, data.item_name);
                venueInput.attr('title', data.item_name);
                latestSelectedOption = {field: venueInput.attr('id'), value: data.item_name};
            })
            .on('typeahead:click-on-custom-option', function(e, data) {
                if (hiddenData.room_id) {
                    clearField(roomInput);
                }
                delete hiddenData.venue_id;
                hiddenData.venue_name = data.item_name;
                postSelectionActions(venueInput, data.item_name);
                venueInput.val(data.item_name);
                validVenue = true;
                justUseOptionSelected = false;
                venueInput.trigger('typeahead:close-results-list');
            });

            roomInput.on('typeahead:click-on-custom-option', function(e, data) {
                delete hiddenData.room_id;
                hiddenData.room_name = data.item_name;
                postSelectionActions(roomInput, data.item_name);
                roomInput.val(data.item_name);
                validRoom = true;
                justUseOptionSelected = false;
                roomInput.trigger('typeahead:close-results-list');
            });

            $('#{{ field.id }}-wrapper .i-location-input-field').on('typeahead:close-results-list', function() {
                global.Typeahead["#" + this.id].hideLayout();
            }).on('typeahead:open-results-list', function() {
                global.Typeahead["#" + this.id].showLayout();
            })
            .on('focus.typeahead', function() {
                /* In case the input is cleared, this ensures that the results dropdown will contain all available
                options. */
                var $this = $(this);
                if (roomJustCleared && $this.is(roomInput)) {
                    $this.trigger('input.typeahead');
                    roomJustCleared = false;
                }
                if (venueJustChanged && $this.is(venueInput)) {
                    $this.trigger('input.typeahead');
                    venueJustChanged = false;
                }
                field.closest('.ui-dialog-content').css('overflow', 'inherit');  // Make sure the results dropdown are
                                                                                 // displayed above the dialog.
                toggleOpenResultsList($this);
                latestUsedField = $(this);
            })
            .on('keydown', function(e) {
                var $this = $(this);
                if (resultsWereOpen) {
                    if (e.keyCode == K.TAB) {
                        if ($this.val()) {
                            // Treat tab as the return key
                            var returnKey = jQuery.Event("keydown");
                            returnKey.which = K.ENTER;
                            returnKey.keyCode = K.ENTER;
                            $this.trigger(returnKey);
                        } else {
                            clearField($this);
                        }
                    } else if (e.keyCode == K.ENTER) {
                        e.preventDefault();
                        if ($this.val()) {
                            if (latestSelectedOption.value == '' || latestSelectedOption.field == $this.attr('id') && latestSelectedOption.value != $this.val()) {
                                $this.trigger('typeahead:click-on-custom-option', {item_name: $this.val()});
                            }
                        } else {
                            clearField($this);
                        }
                    } else if (e.keyCode == K.ESCAPE) {
                        e.preventDefault();
                        resetSelectedOption();
                    } else if (~[K.UP, K.DOWN].indexOf(e.keyCode)) {
                        scrollResultsList($this);
                        $this.trigger('focus.typeahead');
                    }
                }
            })
            .on('click', function() {
                /* Since focus remains on the field after selection, we need special handling for the click event in
                order to reopen the results list */
                global.Typeahead["#" + this.id].showLayout();
                $(this).trigger('input');
            });

            $('#{{ field.id }}-wrapper .toggle-results-list').on('click', function() {
                var container = $(this).parent().parent();
                var input = container.find('.i-location-input-field').eq(0);
                input.trigger(container.hasClass('result') ? 'typeahead:close-results-list' : 'focus.typeahead');
            });

            address.on('keyup', function() {
                hiddenData.address = address.val();
                field.val(JSON.stringify(hiddenData)).trigger('change');<commit_msg>Clear address when using custom location<commit_after>{% extends 'forms/base_widget.html' %}

{% block html %}
    <div class="i-location-field" id="{{ field.id }}-wrapper">
        <input type="hidden" id="{{ field.id }}" name="{{ field.name }}"
               value="{{ field._value() | tojson | forceescape }}">
        <div class="wrapper">
            <div class="typeahead-container venue-container">
                <div class="typeahead-field">
                    <input id="location-venue-{{ field.id }}"
                           class="i-location-input-field"
                           name="location-venue-{{ field.name }}"
                           type="text"
                           placeholder="{% trans %}Venue{% endtrans %}"
                           autocomplete="off">
                    <span class="keyword-highlighter"></span>
                    <span class="toggle-results-list icon-arrow-down"></span>
                    <div class="typeahead-results">
                        <div class="results-list-container" id="location-venue-{{ field.id }}-results"></div>
                    </div>
                </div>
            </div>
            <div class="typeahead-container room-container">
                <div class="typeahead-field">
                    <input id="location-room-{{ field.id }}"
                           class="i-location-input-field"
                           name="location-room-{{ field.name }}"
                           type="text"
                           placeholder="{% trans %}Room{% endtrans %}"
                           autocomplete="off">
                    <span class="keyword-highlighter"></span>
                    <span class="toggle-results-list icon-arrow-down"></span>
                    <div class="typeahead-results">
                        <div class="results-list-container" id="location-room-{{ field.id }}-results"></div>
                    </div>
                </div>
            </div>
            <textarea id="location-address-{{ field.id }}" name="location-address"
                      placeholder="{% trans %}Address{% endtrans %}"></textarea>
            <div class="location-inheritance-field">
                <div class="checkbox-container">
                    <input id="location-inheritance-{{ field.id }}"
                           name="location-inheritance-{{ field.name }}"
                           type="checkbox">
                    <label for="location-inheritance-{{ field.id }}">{% trans %}Use default{% endtrans %}</label>
                    <i class="info-helper"></i>
                </div>
            </div>
        </div>
    </div>
    <div class="clearfix"></div>
{% endblock %}

{% block javascript %}
    <script>
        (function(global) {
            'use strict';

            var field = $('#{{ field.id }}');
            var helperText = field.closest('.form-field').find('.location-inheritance-field .info-helper');
            var venueInput = $('#location-venue-{{ field.id }}');
            var roomInput = $('#location-room-{{ field.id }}');
            var address = $('#location-address-{{ field.id }}');
            var usingDefault = $('#location-inheritance-{{ field.id }}');
            var venues = {{ venues | tojson }};
            var venueNames = {{ venues.data | map(attribute='name') | list | tojson }};
            var rooms = {{ rooms | tojson }};
            var hiddenData = field.val() ? JSON.parse(field.val()) : {};  // Contains all location related data sent to
                                                                          // the server
            var latestUsedField = null;
            var latestSelectedOption = {field: '', value: ''};
            var currentScrollPos = 0;
            var delta = 0;
            var validRoom = false;
            var validVenue = false;
            var preventGuessing = false;
            var roomJustCleared = false;
            var venueJustChanged = false;
            var justUseOptionSelected = false;
            var resultsWereOpen = false;

            function initializeInputs() {
                address.val(hiddenData.address);
                roomInput.val(hiddenData.room_name);
                venueInput.val(hiddenData.venue_name);
                if (hiddenData.venue_id) {
                    highlightOption(venueInput, venueInput.val());
                }
                if (hiddenData.room_id) {
                    highlightOption(roomInput, roomInput.val());
                }
                usingDefault.prop('checked', hiddenData.inheriting);
            }

            function toggleDefaultLocation(isChecked, isInitStep) {
                var message = ($T.gettext('Using default location from: {0} ({1})').format({{ parent[1] | tojson }},
                                                                                           {{ parent[0] | tojson }}));
                var text = isChecked ? message : $T.gettext('Using custom location');
                helperText.attr('title', text);
                venueInput.prop('disabled', isChecked).toggleClass('disabled', isChecked);
                roomInput.prop('disabled', isChecked).toggleClass('disabled', isChecked);
                address.prop('disabled', isChecked).toggleClass('disabled', isChecked);
                field.closest('.i-location-field').toggleClass('disabled', isChecked);

                hiddenData.inheriting = isChecked;
                field.val(JSON.stringify(hiddenData));

                /* Prefill the disabled inputs with the inherited location when the default location is checked
                (clear them otherwise) */
                if (!isInitStep) {
                    if (isChecked) {
                        hiddenData = {{ field.data.source.inherited_widget_location_data | tojson }};
                        hiddenData.inheriting = true;
                        initializeInputs();
                    } else {
                        clearField(venueInput);
                        address.val('');
                        delete hiddenData.address;
                    }
                    field.val(JSON.stringify(hiddenData)).trigger('change');
                }
            }

            function reorderCategories(venueName) {
                /* When the choice of venue changes, the rooms of the selected venue need to appear on the top of the
                room typeahead list, simply by updating the groupOrder option, moving the selected venue to the first
                position of the array. */
                var groupOrder = [venueName];
                for (var i = venueNames.length - 1; i >= 0; i--) {
                    if (!~groupOrder.indexOf(venueNames[i])) {
                        groupOrder.push(venueNames[i]);
                    }
                }
                global.Typeahead[roomInput.selector].options.groupOrder = groupOrder;
            }

            function updateVenueOnRoomChange(item) {
                /* Override the venue based on the room selection */
                if (item.venue_id != hiddenData.venue_id) {
                    venueJustChanged = true;
                    var data = {item_name: item.group, venue_id: item.venue_id, room_id: item.id};
                    venueInput.val(item.group).trigger('typeahead:click-on-option', data);
                }
            }

            function clearRoom() {
                delete hiddenData.room_id;
                delete hiddenData.room_name;
                roomInput.val('');
                roomInput.siblings('.keyword-highlighter').removeClass('visible');
                roomJustCleared = true;
                postSelectionActions(roomInput, '');
            }

            function clearVenue() {
                delete hiddenData.venue_id;
                delete hiddenData.venue_name;
                venueInput.val('');
                venueInput.siblings('.keyword-highlighter').removeClass('visible');
                venueJustChanged = true;
                postSelectionActions(venueInput, '');
                clearRoom();
            }

            function clearField(field) {
                if (field.is(venueInput)) {
                    clearVenue();
                } else if (field.is(roomInput)) {
                    clearRoom();
                }
            }

            function updateResultsList(node, resultHtmlList) {
                /* Make sure the result dropdown is displayed just below the input field */
                var resultsWrapper = node.siblings('.typeahead-results');
                resultsWrapper.css('top', node.outerHeight() + 'px');

                /* Create/delete the 'just-use' option based on the input */
                var justUse = node.parent().find('.just-use-option');
                if (node.val()) {
                    if (!justUse.length) {
                        justUse = $('<div>', {
                            'class': 'just-use-option'
                        })
                        .on('click', function() {
                            node.trigger('typeahead:click-on-custom-option', {item_name: node.val()});
                        });
                        resultsWrapper.append(justUse);
                    }
                    justUse.text($T.gettext('Just use "{0}"').format(node.val()));
                } else {
                    justUse.remove();
                }

                /* Always mark the first option as active in order for the keyboard inputs to work */
                var firstOption = resultHtmlList.find('li.typeahead-item').first();
                if (!firstOption.length) {
                    node.parent().find('.just-use-option').addClass('active');
                    justUseOptionSelected = true;
                } else {
                    resultHtmlList.find('li.typeahead-item').first().addClass('active');
                    justUseOptionSelected = false;
                }
                return resultHtmlList;
            }

            function scrollResultsList(inputField) {
                var parent = inputField.parent();
                var activeOption = parent.find('.typeahead-results').find('.typeahead-item.active');
                var dropdownList = parent.find('.typeahead-list');
                if (activeOption.length != 0) {
                    if (activeOption.position().top + activeOption.outerHeight() > dropdownList.outerHeight()) {
                        delta = activeOption.position().top + activeOption.outerHeight() - dropdownList.outerHeight();
                        currentScrollPos += delta;
                        dropdownList.animate({
                            scrollTop: currentScrollPos
                        }, 50);
                    } else if (activeOption.position().top < dropdownList.position().top){
                        currentScrollPos += activeOption.position().top;
                        dropdownList.animate({
                            scrollTop: currentScrollPos
                        }, 50);
                    }
                }
                parent.find('.just-use-option').toggleClass('active', !activeOption.length);
                justUseOptionSelected = !activeOption.length;
            }

            function postSelectionActions(inputField, value) {
                field.val(JSON.stringify(hiddenData)).trigger('change');
                if (inputField.is(venueInput)) {
                    latestVenueValue = value;
                } else if (inputField.is(roomInput)) {
                    latestRoomValue = value;
                }
            }

            function highlightOption(inputField, value) {
                var highlighter = inputField.siblings('.keyword-highlighter');
                highlighter.html(value).addClass('visible');
            }

            function cleanQuery(query) {
                return query ? query.trim() : '';
            }

            function findRoomInVenue(query, venue) {
                if (rooms[venue]) {
                    return $.grep(rooms[venue].data, function(room) {
                        return room.name.toUpperCase() == query.toUpperCase();
                    });
                }
                return [];
            }

            function toggleOpenResultsList(inputField) {
                if (inputField.is(venueInput) && roomInput.closest('.typeahead-container').hasClass('result')) {
                    roomInput.trigger('typeahead:close-results-list');
                    venueInput.trigger('typeahead:close-results-list');
                    venueInput.trigger('typeahead:open-results-list');
                } else if (inputField.is(roomInput) && venueInput.closest('.typeahead-container').hasClass('result')) {
                    venueInput.trigger('typeahead:close-results-list');
                    roomInput.trigger('typeahead:close-results-list');
                    roomInput.trigger('typeahead:open-results-list');
                }
            }

            function resetSelectedOption() {
                if (latestUsedField.is(venueInput)) {
                    venueInput.val(latestVenueValue);
                    if (hiddenData.venue_id) {
                        highlightOption(venueInput, latestVenueValue)
                    }
                } else if (latestUsedField.is(roomInput)) {
                    roomInput.val(latestRoomValue);
                    if (hiddenData.room_id) {
                        highlightOption(roomInput, latestRoomValue)
                    }
                }
            }

            initializeInputs();

            var latestVenueValue = venueInput.val();
            var latestRoomValue = roomInput.val();

            usingDefault.on('click', function() {
                toggleDefaultLocation(this.checked);
            });

            toggleDefaultLocation(usingDefault.prop('checked'), true);

            venueInput.typeahead({
                source: venues,
                minLength: 0,
                searchOnFocus: true,
                resultContainer: '#' + venueInput.siblings('.typeahead-results').find('.results-list-container').attr('id'),
                template: {% raw %}'<span id="location-venue-{{id}}">{{name}}</span>'{% endraw %},
                // Used to keep the dropdown list open while there are no results (required by the 'just-use' option)
                emptyTemplate: function() { return ''; },
                display: 'name',
                hint: true,
                callback: {
                    onInit: function(node) {
                        this.query = node.val();  // Updates the results dropdown on init
                    },
                    onClickBefore: function() {
                        // Used in the onHideLayout callback to identify the reason of the dropdown closure
                        validVenue = true;
                    },
                    onClickAfter: function(node, a, item) {
                        venueInput.trigger('typeahead:click-on-option', {
                            item_name: item.name,
                            venue_id: item.id
                        });
                    },
                    onLayoutBuiltBefore: function(node, query, result, resultHtmlList) {
                        return updateResultsList(node, resultHtmlList);
                    },
                    onSearch: function(node, query) {
                        this.query = cleanQuery(query);
                    },
                    onShowLayout: function() {
                        venueInput.siblings('.keyword-highlighter').removeClass('visible');
                        currentScrollPos = 0;
                    },
                    onHideLayout: function(node, query) {
                        if (justUseOptionSelected) {
                            venueInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                        } else if (!validVenue && node.val() && !preventGuessing) {
                            /* Try to guess the choice of the user in case the results dropdown closes without a
                            selection. If an exact match is found, select it, otherwise use the 'just-use' option */
                            var result = $.grep(venues.data, function(venue) {
                                return venue.name.toUpperCase() == query.toUpperCase();
                            });
                            if (result.length) {
                                $('#location-venue-' + result[0].id).parent().trigger('click');
                            } else {
                                venueInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                            }
                        }
                        preventGuessing = false;
                        validVenue = false;
                    },
                    onNavigateBefore: function(node, query, event) {
                        if (event.keyCode == K.TAB) {
                            preventGuessing = true
                        }
                        /* Used in the keydown event handler of the input fields to identify whether the results
                        dropdown were open before the keydown (since the focus remains on the field even after a selection). */
                        resultsWereOpen = node.closest('.typeahead-container').hasClass('result');
                    },
                    onResult: function(node, query, result, resultCount) {
                        node.parent().find('.just-use-option').toggleClass('active', !resultCount);
                        justUseOptionSelected = !resultCount;
                    }
                }
            });

            roomInput.typeahead({
                source: rooms,
                minLength: 0,
                searchOnFocus: true,
                group: true,
                hint: true,
                maxItem: 0,
                resultContainer: '#' + roomInput.siblings('.typeahead-results').find('.results-list-container').attr('id'),
                template: {% raw %}'<span id="location-room-{{id}}">{{name}}</span>'{% endraw %},
                emptyTemplate: function() { return ''; },
                display: 'name',
                callback: {
                    onInit: function(node) {
                        this.query = node.val();
                    },
                    onClickBefore: function() {
                        validRoom = true;
                    },
                    onClickAfter: function(node, a, item) {
                        updateVenueOnRoomChange(item);

                        hiddenData.room_id = item.id;
                        delete hiddenData.room_name;

                        postSelectionActions(roomInput, item.name);
                        latestSelectedOption = {field: node.attr('id'), value: item.name};
                        highlightOption(roomInput, item.name);
                        roomInput.attr('title', item.name);
                    },
                    onLayoutBuiltBefore: function(node, query, result, resultHtmlList) {
                        return updateResultsList(node, resultHtmlList);
                    },
                    onSearch: function(node, query) {
                        this.query = cleanQuery(query);
                    },
                    onShowLayout: function() {
                        roomInput.siblings('.keyword-highlighter').removeClass('visible');
                        currentScrollPos = 0;
                    },
                    onHideLayout: function(node, query) {
                        if (justUseOptionSelected) {
                            roomInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                        } else if (!validRoom && node.val() && !preventGuessing) {
                            var result = [];
                            var venueValue = venueInput.val();
                            if (venueValue) {
                                result = findRoomInVenue(query, venueValue);
                            } else {
                                for (var i = 0; i < venueNames.length && !result.length; i++) {
                                    result = findRoomInVenue(query, venueNames[i]);
                                }
                            }
                            if (result.length) {
                                $('#location-room-' + result[0].id).parent().trigger('click');
                            } else {
                                roomInput.trigger('typeahead:click-on-custom-option', {item_name: query});
                            }
                        }
                        preventGuessing = false;
                        validRoom = false;
                    },
                    onNavigateBefore: function(node, query, event) {
                        if (event.keyCode == K.TAB) {
                            preventGuessing = true;
                        }
                        resultsWereOpen = node.closest('.typeahead-container').hasClass('result');
                    },
                    onResult: function(node, query, result, resultCount) {
                        node.parent().find('.just-use-option').toggleClass('active', !resultCount);
                        justUseOptionSelected = !resultCount;
                    }
                }
            });

            venueInput.on('typeahead:click-on-option', function(e, data) {
                reorderCategories(data.item_name);
                /* The room needs to be cleared when the venue changes */
                if (hiddenData.venue_id && data.venue_id != hiddenData.venue_id && !data.room_id) {
                    clearField(roomInput);
                }
                hiddenData.venue_name = data.item_name;
                hiddenData.venue_id = data.venue_id;
                postSelectionActions(venueInput, data.item_name);
                highlightOption(venueInput, data.item_name);
                venueInput.attr('title', data.item_name);
                latestSelectedOption = {field: venueInput.attr('id'), value: data.item_name};
            })
            .on('typeahead:click-on-custom-option', function(e, data) {
                if (hiddenData.room_id) {
                    clearField(roomInput);
                }
                delete hiddenData.venue_id;
                hiddenData.venue_name = data.item_name;
                postSelectionActions(venueInput, data.item_name);
                venueInput.val(data.item_name);
                validVenue = true;
                justUseOptionSelected = false;
                venueInput.trigger('typeahead:close-results-list');
            });

            roomInput.on('typeahead:click-on-custom-option', function(e, data) {
                delete hiddenData.room_id;
                hiddenData.room_name = data.item_name;
                postSelectionActions(roomInput, data.item_name);
                roomInput.val(data.item_name);
                validRoom = true;
                justUseOptionSelected = false;
                roomInput.trigger('typeahead:close-results-list');
            });

            $('#{{ field.id }}-wrapper .i-location-input-field').on('typeahead:close-results-list', function() {
                global.Typeahead["#" + this.id].hideLayout();
            }).on('typeahead:open-results-list', function() {
                global.Typeahead["#" + this.id].showLayout();
            })
            .on('focus.typeahead', function() {
                /* In case the input is cleared, this ensures that the results dropdown will contain all available
                options. */
                var $this = $(this);
                if (roomJustCleared && $this.is(roomInput)) {
                    $this.trigger('input.typeahead');
                    roomJustCleared = false;
                }
                if (venueJustChanged && $this.is(venueInput)) {
                    $this.trigger('input.typeahead');
                    venueJustChanged = false;
                }
                field.closest('.ui-dialog-content').css('overflow', 'inherit');  // Make sure the results dropdown are
                                                                                 // displayed above the dialog.
                toggleOpenResultsList($this);
                latestUsedField = $(this);
            })
            .on('keydown', function(e) {
                var $this = $(this);
                if (resultsWereOpen) {
                    if (e.keyCode == K.TAB) {
                        if ($this.val()) {
                            // Treat tab as the return key
                            var returnKey = jQuery.Event("keydown");
                            returnKey.which = K.ENTER;
                            returnKey.keyCode = K.ENTER;
                            $this.trigger(returnKey);
                        } else {
                            clearField($this);
                        }
                    } else if (e.keyCode == K.ENTER) {
                        e.preventDefault();
                        if ($this.val()) {
                            if (latestSelectedOption.value == '' || latestSelectedOption.field == $this.attr('id') && latestSelectedOption.value != $this.val()) {
                                $this.trigger('typeahead:click-on-custom-option', {item_name: $this.val()});
                            }
                        } else {
                            clearField($this);
                        }
                    } else if (e.keyCode == K.ESCAPE) {
                        e.preventDefault();
                        resetSelectedOption();
                    } else if (~[K.UP, K.DOWN].indexOf(e.keyCode)) {
                        scrollResultsList($this);
                        $this.trigger('focus.typeahead');
                    }
                }
            })
            .on('click', function() {
                /* Since focus remains on the field after selection, we need special handling for the click event in
                order to reopen the results list */
                global.Typeahead["#" + this.id].showLayout();
                $(this).trigger('input');
            });

            $('#{{ field.id }}-wrapper .toggle-results-list').on('click', function() {
                var container = $(this).parent().parent();
                var input = container.find('.i-location-input-field').eq(0);
                input.trigger(container.hasClass('result') ? 'typeahead:close-results-list' : 'focus.typeahead');
            });

            address.on('keyup input', function() {
                hiddenData.address = address.val();
                field.val(JSON.stringify(hiddenData)).trigger('change');
{'fraction_non_alphanumeric': 0.11392617137325714, 'fraction_numerical': 0.0008563078240473575, 'mean_word_length': 1.2783951140894054, 'pattern_counts': {'":': 0, '<': 107, '<?xml version=': 0, '>': 107, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2551229', 'n_tokens_mistral': 12854, 'n_tokens_neox': 11831, 'n_words': 2388}