$(document).ready(function() { // Pagination $('select#per_page').change(function() { this.form.submit(); }); // "Toggle" checkbox for object lists (PK column) $('input:checkbox.toggle').click(function() { $(this).closest('table').find('input:checkbox[name=pk]:visible').prop('checked', $(this).prop('checked')); // Show the "select all" box if present if ($(this).is(':checked')) { $('#select_all_box').removeClass('hidden'); } else { $('#select_all').prop('checked', false); } }); // Uncheck the "toggle" and "select all" checkboxes if an item is unchecked $('input:checkbox[name=pk]').click(function (event) { if (!$(this).attr('checked')) { $('input:checkbox.toggle, #select_all').prop('checked', false); } }); // Enable hidden buttons when "select all" is checked $('#select_all').click(function() { if ($(this).is(':checked')) { $('#select_all_box').find('button').prop('disabled', ''); } else { $('#select_all_box').find('button').prop('disabled', 'disabled'); } }); // Slugify function slugify(s, num_chars) { s = s.replace(/[^\-\.\w\s]/g, ''); // Remove unneeded chars s = s.replace(/^[\s\.]+|[\s\.]+$/g, ''); // Trim leading/trailing spaces s = s.replace(/[\-\.\s]+/g, '-'); // Convert spaces and decimals to hyphens s = s.toLowerCase(); // Convert to lowercase return s.substring(0, num_chars); // Trim to first num_chars chars } var slug_field = $('#id_slug'); slug_field.change(function() { $(this).attr('_changed', true); }); if (slug_field) { var slug_source = $('#id_' + slug_field.attr('slug-source')); var slug_length = slug_field.attr('maxlength'); slug_source.on('keyup change', function() { if (slug_field && !slug_field.attr('_changed')) { slug_field.val(slugify($(this).val(), (slug_length ? slug_length : 50))); } }) } // Bulk edit nullification $('input:checkbox[name=_nullify]').click(function() { $('#id_' + this.value).toggle('disabled'); }); // Set formaction and submit using a link $('a.formaction').click(function(event) { event.preventDefault(); var form = $(this).closest('form'); form.attr('action', $(this).attr('href')); form.submit(); }); // Parse URLs which may contain variable refrences to other field values function parseURL(url) { var filter_regex = /\{\{([a-z_]+)\}\}/g; var match; var rendered_url = url; var filter_field; while (match = filter_regex.exec(url)) { filter_field = $('#id_' + match[1]); var custom_attr = $('option:selected', filter_field).attr('api-value'); if (custom_attr) { rendered_url = rendered_url.replace(match[0], custom_attr); } else if (filter_field.val()) { rendered_url = rendered_url.replace(match[0], filter_field.val()); } else if (filter_field.attr('nullable') == 'true') { rendered_url = rendered_url.replace(match[0], 'null'); } } return rendered_url } // Assign color picker selection classes function colorPickerClassCopy(data, container) { if (data.element) { // Swap the style $(container).attr('style', $(data.element).attr("style")); } return data.text; } // Color Picker $('.netbox-select2-color-picker').select2({ allowClear: true, placeholder: "---------", theme: "bootstrap", templateResult: colorPickerClassCopy, templateSelection: colorPickerClassCopy, width: "off" }); // Static choice selection $('.netbox-select2-static').select2({ allowClear: true, placeholder: "---------", theme: "bootstrap", width: "off" }); // API backed selection // Includes live search and chained fields // The `multiple` setting may be controled via a data-* attribute $('.netbox-select2-api').select2({ allowClear: true, placeholder: "---------", theme: "bootstrap", width: "off", ajax: { delay: 500, url: function(params) { var element = this[0]; var url = parseURL(element.getAttribute("data-url")); if (url.includes("{{")) { // URL is not fully rendered yet, abort the request return false; } return url; }, data: function(params) { var element = this[0]; // Paging. Note that `params.page` indexes at 1 var offset = (params.page - 1) * 50 || 0; // Base query params var parameters = { q: params.term, limit: 50, offset: offset, }; // Allow for controlling the brief setting from within APISelect parameters.brief = ( $(element).is('[data-full]') ? undefined : true ); // filter-for fields from a chain var attr_name = "data-filter-for-" + $(element).attr("name"); var form = $(element).closest('form'); var filter_for_elements = form.find("select[" + attr_name + "]"); filter_for_elements.each(function(index, filter_for_element) { var param_name = $(filter_for_element).attr(attr_name); var is_nullable = $(filter_for_element).attr("nullable"); var is_visible = $(filter_for_element).is(":visible"); var value = $(filter_for_element).val(); if (param_name && is_visible && value) { parameters[param_name] = value; } else if (param_name && is_visible && is_nullable) { parameters[param_name] = "null"; } }); // Conditional query params $.each(element.attributes, function(index, attr){ if (attr.name.includes("data-conditional-query-param-")){ var conditional = attr.name.split("data-conditional-query-param-")[1].split("__"); var field = $("#id_" + conditional[0]); var field_value = conditional[1]; if ($('option:selected', field).attr('api-value') === field_value){ var _val = attr.value.split("="); parameters[_val[0]] = _val[1]; } } }); // Additional query params $.each(element.attributes, function(index, attr){ if (attr.name.includes("data-additional-query-param-")){ var param_name = attr.name.split("data-additional-query-param-")[1]; if (param_name in parameters) { if (Array.isArray(parameters[param_name])) { parameters[param_name].push(attr.value) } else { parameters[param_name] = [parameters[param_name], attr.value] } } else { parameters[param_name] = attr.value; } } }); // This will handle params with multiple values (i.e. for list filter forms) return $.param(parameters, true); }, processResults: function (data) { var element = this.$element[0]; $(element).children('option').attr('disabled', false); var results = data.results; results = results.reduce((results,record,idx) => { record.text = record[element.getAttribute('display-field')] || record.name; record.id = record[element.getAttribute('value-field')] || record.id; if(element.getAttribute('disabled-indicator') && record[element.getAttribute('disabled-indicator')]) { // The disabled-indicator equated to true, so we disable this option record.disabled = true; } if( record.group !== undefined && record.group !== null && record.site !== undefined && record.site !== null ) { results[record.site.name + ":" + record.group.name] = results[record.site.name + ":" + record.group.name] || { text: record.site.name + " / " + record.group.name, children: [] } results[record.site.name + ":" + record.group.name].children.push(record); } else if( record.group !== undefined && record.group !== null ) { results[record.group.name] = results[record.group.name] || { text: record.group.name, children: [] } results[record.group.name].children.push(record); } else if( record.site !== undefined && record.site !== null ) { results[record.site.name] = results[record.site.name] || { text: record.site.name, children: [] } results[record.site.name].children.push(record); } else if ( (record.group !== undefined || record.group == null) && (record.site !== undefined || record.site === null) ) { results['global'] = results['global'] || { text: 'Global', children: [] } results['global'].children.push(record); } else { results[idx] = record } return results; },Object.create(null)); results = Object.values(results); // Handle the null option, but only add it once if (element.getAttribute('data-null-option') && data.previous === null) { var null_option = $(element).children()[0]; results.unshift({ id: null_option.value, text: null_option.text }); } // Check if there are more results to page var page = data.next !== null; return { results: results, pagination: { more: page } }; } } }); // Flatpickr selectors $('.date-picker').flatpickr({ allowInput: true }); $('.datetime-picker').flatpickr({ allowInput: true, enableSeconds: true, enableTime: true, time_24hr: true }); $('.time-picker').flatpickr({ allowInput: true, enableSeconds: true, enableTime: true, noCalendar: true, time_24hr: true }); // API backed tags var tags = $('#id_tags'); if (tags.length > 0 && tags.val().length > 0){ tags = $('#id_tags').val().split(/,\s*/); } else { tags = []; } tag_objs = $.map(tags, function (tag) { return { id: tag, text: tag, selected: true } }); // Replace the django issued text input with a select element $('#id_tags').replaceWith(''); $('#id_tags').select2({ tags: true, data: tag_objs, multiple: true, allowClear: true, placeholder: "Tags", theme: "bootstrap", width: "off", ajax: { delay: 250, url: netbox_api_path + "extras/tags/", data: function(params) { // Paging. Note that `params.page` indexes at 1 var offset = (params.page - 1) * 50 || 0; var parameters = { q: params.term, brief: 1, limit: 50, offset: offset, }; return parameters; }, processResults: function (data) { var results = $.map(data.results, function (obj) { // If tag contains space add double quotes if (/\s/.test(obj.name)) obj.name = '"' + obj.name + '"' return { id: obj.name, text: obj.name } }); // Check if there are more results to page var page = data.next !== null; return { results: results, pagination: { more: page } }; } } }); $('#id_tags').closest('form').submit(function(event){ // django-taggit can only accept a single comma seperated string value var value = $('#id_tags').val(); if (value.length > 0){ var final_tags = value.join(', '); $('#id_tags').val(null).trigger('change'); var option = new Option(final_tags, final_tags, true, true); $('#id_tags').append(option).trigger('change'); } }); if( $('select#id_mode').length > 0 ) { $('select#id_mode').on('change', function () { if ($(this).val() == '') { $('select#id_untagged_vlan').val(); $('select#id_untagged_vlan').trigger('change'); $('select#id_tagged_vlans').val([]); $('select#id_tagged_vlans').trigger('change'); $('select#id_untagged_vlan').parent().parent().hide(); $('select#id_tagged_vlans').parent().parent().hide(); } else if ($(this).val() == 100) { $('select#id_tagged_vlans').val([]); $('select#id_tagged_vlans').trigger('change'); $('select#id_untagged_vlan').parent().parent().show(); $('select#id_tagged_vlans').parent().parent().hide(); } else if ($(this).val() == 200) { $('select#id_untagged_vlan').parent().parent().show(); $('select#id_tagged_vlans').parent().parent().show(); } else if ($(this).val() == 300) { $('select#id_tagged_vlans').val([]); $('select#id_tagged_vlans').trigger('change'); $('select#id_untagged_vlan').parent().parent().show(); $('select#id_tagged_vlans').parent().parent().hide(); } }); $('select#id_mode').trigger('change'); } // Scroll up an offset equal to the first nav element if a hash is present // Cannot use '#navbar' because it is not always visible, like in small windows function headerOffsetScroll() { if (window.location.hash) { // Short wait needed to allow the page to scroll to the element setTimeout(function() { window.scrollBy(0, -$('nav').height()) }, 10); } } // Account for the header height when hash-scrolling window.addEventListener('load', headerOffsetScroll); window.addEventListener('hashchange', headerOffsetScroll); // Offset between the preview window and the window edges const IMAGE_PREVIEW_OFFSET_X = 20; const IMAGE_PREVIEW_OFFSET_Y = 10; // Preview an image attachment when the link is hovered over $('a.image-preview').on('mouseover', function(e) { // Twice the offset to account for all sides of the picture var maxWidth = window.innerWidth - (e.clientX + (IMAGE_PREVIEW_OFFSET_X * 2)); var maxHeight = window.innerHeight - (e.clientY + (IMAGE_PREVIEW_OFFSET_Y * 2)); var img = $('').attr('id', 'image-preview-window').css({ display: 'none', position: 'absolute', maxWidth: maxWidth + 'px', maxHeight: maxHeight + 'px', left: e.pageX + IMAGE_PREVIEW_OFFSET_X + 'px', top: e.pageY + IMAGE_PREVIEW_OFFSET_Y + 'px', boxShadow: '0 0px 12px 3px rgba(0, 0, 0, 0.4)', }); // Remove any existing preview windows and add the current one $('#image-preview-window').remove(); $('body').append(img); // Once loaded, show the preview if the image is indeed an image img.on('load', function(e) { if (e.target.complete && e.target.naturalWidth) { $('#image-preview-window').fadeIn('fast'); } }); // Begin loading img.attr('src', e.target.href); }); // Fade the image out; it will be deleted when another one is previewed $('a.image-preview').on('mouseout', function() { $('#image-preview-window').fadeOut('fast'); }); });