From e03d6277698f4e1e03d9336ba017bae130d4353c Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 11 Mar 2020 20:34:54 +0100 Subject: Misc JS linting and naming tweaks (#10652) - lowercase all js filenames except Vue components - enable new lint rules, mostly focused on shorter code - autofix new lint violations - apply misc transformations indexOf -> includes and onevent-> addEventListener Co-authored-by: Antoine GIRARD --- web_src/js/features/clipboard.js | 2 +- web_src/js/features/contextPopup.js | 76 - web_src/js/features/contextpopup.js | 76 + web_src/js/features/dropzone.js | 2 +- web_src/js/features/gitGraph.js | 13 - web_src/js/features/gitgraph.js | 13 + web_src/js/features/userHeatmap.js | 98 - web_src/js/features/userheatmap.js | 98 + web_src/js/index.js | 140 +- web_src/js/publicPath.js | 11 - web_src/js/publicpath.js | 11 + web_src/js/vendor/gitGraph.js | 424 ---- web_src/js/vendor/gitgraph.js | 424 ++++ web_src/js/vendor/semanticDropdown.js | 4300 --------------------------------- web_src/js/vendor/semanticdropdown.js | 4300 +++++++++++++++++++++++++++++++++ 15 files changed, 4987 insertions(+), 5001 deletions(-) delete mode 100644 web_src/js/features/contextPopup.js create mode 100644 web_src/js/features/contextpopup.js delete mode 100644 web_src/js/features/gitGraph.js create mode 100644 web_src/js/features/gitgraph.js delete mode 100644 web_src/js/features/userHeatmap.js create mode 100644 web_src/js/features/userheatmap.js delete mode 100644 web_src/js/publicPath.js create mode 100644 web_src/js/publicpath.js delete mode 100644 web_src/js/vendor/gitGraph.js create mode 100644 web_src/js/vendor/gitgraph.js delete mode 100644 web_src/js/vendor/semanticDropdown.js create mode 100644 web_src/js/vendor/semanticdropdown.js (limited to 'web_src') diff --git a/web_src/js/features/clipboard.js b/web_src/js/features/clipboard.js index bd4a664c7a..a3b6b26eb3 100644 --- a/web_src/js/features/clipboard.js +++ b/web_src/js/features/clipboard.js @@ -2,7 +2,7 @@ export default async function initClipboard() { const els = document.querySelectorAll('.clipboard'); if (!els || !els.length) return; - const { default: ClipboardJS } = await import(/* webpackChunkName: "clipboard" */'clipboard'); + const {default: ClipboardJS} = await import(/* webpackChunkName: "clipboard" */'clipboard'); const clipboard = new ClipboardJS(els); clipboard.on('success', (e) => { diff --git a/web_src/js/features/contextPopup.js b/web_src/js/features/contextPopup.js deleted file mode 100644 index 5acfa9c293..0000000000 --- a/web_src/js/features/contextPopup.js +++ /dev/null @@ -1,76 +0,0 @@ -import { svg } from '../utils.js'; - -const { AppSubUrl } = window.config; - -export default function initContextPopups() { - const refIssues = $('.ref-issue'); - if (!refIssues.length) return; - - refIssues.each(function () { - const [index, _issues, repo, owner] = $(this).attr('href').replace(/[#?].*$/, '').split('/').reverse(); - issuePopup(owner, repo, index, $(this)); - }); -} - -function issuePopup(owner, repo, index, $element) { - $.get(`${AppSubUrl}/api/v1/repos/${owner}/${repo}/issues/${index}`, (issue) => { - const createdAt = new Date(issue.created_at).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); - - let body = issue.body.replace(/\n+/g, ' '); - if (body.length > 85) { - body = `${body.substring(0, 85)}...`; - } - - let labels = ''; - for (let i = 0; i < issue.labels.length; i++) { - const label = issue.labels[i]; - const labelName = emojify.replace(label.name); - const red = parseInt(label.color.substring(0, 2), 16); - const green = parseInt(label.color.substring(2, 4), 16); - const blue = parseInt(label.color.substring(4, 6), 16); - let color = '#ffffff'; - if ((red * 0.299 + green * 0.587 + blue * 0.114) > 125) { - color = '#000000'; - } - labels += `
${labelName}
`; - } - if (labels.length > 0) { - labels = `

${labels}

`; - } - - let octicon, color; - if (issue.pull_request !== null) { - if (issue.state === 'open') { - color = 'green'; - octicon = 'octicon-git-pull-request'; // Open PR - } else if (issue.pull_request.merged === true) { - color = 'purple'; - octicon = 'octicon-git-merge'; // Merged PR - } else { - color = 'red'; - octicon = 'octicon-git-pull-request'; // Closed PR - } - } else if (issue.state === 'open') { - color = 'green'; - octicon = 'octicon-issue-opened'; // Open Issue - } else { - color = 'red'; - octicon = 'octicon-issue-closed'; // Closed Issue - } - - $element.popup({ - variation: 'wide', - delay: { - show: 250 - }, - html: ` -
-

${issue.repository.full_name} on ${createdAt}

-

${svg(octicon, 16)} ${issue.title} #${index}

-

${body}

- ${labels} -
-` - }); - }); -} diff --git a/web_src/js/features/contextpopup.js b/web_src/js/features/contextpopup.js new file mode 100644 index 0000000000..6feaa768c0 --- /dev/null +++ b/web_src/js/features/contextpopup.js @@ -0,0 +1,76 @@ +import {svg} from '../utils.js'; + +const {AppSubUrl} = window.config; + +export default function initContextPopups() { + const refIssues = $('.ref-issue'); + if (!refIssues.length) return; + + refIssues.each(function () { + const [index, _issues, repo, owner] = $(this).attr('href').replace(/[#?].*$/, '').split('/').reverse(); + issuePopup(owner, repo, index, $(this)); + }); +} + +function issuePopup(owner, repo, index, $element) { + $.get(`${AppSubUrl}/api/v1/repos/${owner}/${repo}/issues/${index}`, (issue) => { + const createdAt = new Date(issue.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'}); + + let body = issue.body.replace(/\n+/g, ' '); + if (body.length > 85) { + body = `${body.substring(0, 85)}...`; + } + + let labels = ''; + for (let i = 0; i < issue.labels.length; i++) { + const label = issue.labels[i]; + const labelName = emojify.replace(label.name); + const red = parseInt(label.color.substring(0, 2), 16); + const green = parseInt(label.color.substring(2, 4), 16); + const blue = parseInt(label.color.substring(4, 6), 16); + let color = '#ffffff'; + if ((red * 0.299 + green * 0.587 + blue * 0.114) > 125) { + color = '#000000'; + } + labels += `
${labelName}
`; + } + if (labels.length > 0) { + labels = `

${labels}

`; + } + + let octicon, color; + if (issue.pull_request !== null) { + if (issue.state === 'open') { + color = 'green'; + octicon = 'octicon-git-pull-request'; // Open PR + } else if (issue.pull_request.merged === true) { + color = 'purple'; + octicon = 'octicon-git-merge'; // Merged PR + } else { + color = 'red'; + octicon = 'octicon-git-pull-request'; // Closed PR + } + } else if (issue.state === 'open') { + color = 'green'; + octicon = 'octicon-issue-opened'; // Open Issue + } else { + color = 'red'; + octicon = 'octicon-issue-closed'; // Closed Issue + } + + $element.popup({ + variation: 'wide', + delay: { + show: 250 + }, + html: ` +
+

${issue.repository.full_name} on ${createdAt}

+

${svg(octicon, 16)} ${issue.title} #${index}

+

${body}

+ ${labels} +
+` + }); + }); +} diff --git a/web_src/js/features/dropzone.js b/web_src/js/features/dropzone.js index f5d5b36a18..428f1d677a 100644 --- a/web_src/js/features/dropzone.js +++ b/web_src/js/features/dropzone.js @@ -1,5 +1,5 @@ export default async function createDropzone(el, opts) { - const [{ default: Dropzone }] = await Promise.all([ + const [{default: Dropzone}] = await Promise.all([ import(/* webpackChunkName: "dropzone" */'dropzone'), import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'), ]); diff --git a/web_src/js/features/gitGraph.js b/web_src/js/features/gitGraph.js deleted file mode 100644 index a18c575163..0000000000 --- a/web_src/js/features/gitGraph.js +++ /dev/null @@ -1,13 +0,0 @@ -export default async function initGitGraph() { - const graphCanvas = document.getElementById('graph-canvas'); - if (!graphCanvas) return; - - const { default: gitGraph } = await import(/* webpackChunkName: "gitgraph" */'../vendor/gitGraph.js'); - - const graphList = []; - $('#graph-raw-list li span.node-relation').each(function () { - graphList.push($(this).text()); - }); - - gitGraph(graphCanvas, graphList); -} diff --git a/web_src/js/features/gitgraph.js b/web_src/js/features/gitgraph.js new file mode 100644 index 0000000000..c0006a2596 --- /dev/null +++ b/web_src/js/features/gitgraph.js @@ -0,0 +1,13 @@ +export default async function initGitGraph() { + const graphCanvas = document.getElementById('graph-canvas'); + if (!graphCanvas) return; + + const {default: gitGraph} = await import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js'); + + const graphList = []; + $('#graph-raw-list li span.node-relation').each(function () { + graphList.push($(this).text()); + }); + + gitGraph(graphCanvas, graphList); +} diff --git a/web_src/js/features/userHeatmap.js b/web_src/js/features/userHeatmap.js deleted file mode 100644 index 4862bc436c..0000000000 --- a/web_src/js/features/userHeatmap.js +++ /dev/null @@ -1,98 +0,0 @@ -import Vue from 'vue'; - -const { AppSubUrl, heatmapUser } = window.config; - -export default async function initHeatmap() { - const el = document.getElementById('user-heatmap'); - if (!el) return; - - const { CalendarHeatmap } = await import(/* webpackChunkName: "userheatmap" */'vue-calendar-heatmap'); - Vue.component('calendarHeatmap', CalendarHeatmap); - - const vueDelimeters = ['${', '}']; - - Vue.component('activity-heatmap', { - delimiters: vueDelimeters, - - props: { - user: { - type: String, - required: true - }, - suburl: { - type: String, - required: true - }, - locale: { - type: Object, - required: true - } - }, - - data() { - return { - isLoading: true, - colorRange: [], - endDate: null, - values: [], - totalContributions: 0, - }; - }, - - mounted() { - this.colorRange = [ - this.getColor(0), - this.getColor(1), - this.getColor(2), - this.getColor(3), - this.getColor(4), - this.getColor(5) - ]; - this.endDate = new Date(); - this.loadHeatmap(this.user); - }, - - methods: { - loadHeatmap(userName) { - const self = this; - $.get(`${this.suburl}/api/v1/users/${userName}/heatmap`, (chartRawData) => { - const chartData = []; - for (let i = 0; i < chartRawData.length; i++) { - self.totalContributions += chartRawData[i].contributions; - chartData[i] = { date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions }; - } - self.values = chartData; - self.isLoading = false; - }); - }, - - getColor(idx) { - const el = document.createElement('div'); - el.className = `heatmap-color-${idx}`; - document.body.appendChild(el); - - const color = getComputedStyle(el).backgroundColor; - - document.body.removeChild(el); - - return color; - } - }, - - template: '

total contributions in the last 12 months

' - }); - - new Vue({ - delimiters: vueDelimeters, - el, - - data: { - suburl: AppSubUrl, - heatmapUser, - locale: { - contributions: 'contributions', - no_contributions: 'No contributions', - }, - }, - }); -} diff --git a/web_src/js/features/userheatmap.js b/web_src/js/features/userheatmap.js new file mode 100644 index 0000000000..9a8e606b1a --- /dev/null +++ b/web_src/js/features/userheatmap.js @@ -0,0 +1,98 @@ +import Vue from 'vue'; + +const {AppSubUrl, heatmapUser} = window.config; + +export default async function initHeatmap() { + const el = document.getElementById('user-heatmap'); + if (!el) return; + + const {CalendarHeatmap} = await import(/* webpackChunkName: "userheatmap" */'vue-calendar-heatmap'); + Vue.component('calendarHeatmap', CalendarHeatmap); + + const vueDelimeters = ['${', '}']; + + Vue.component('activity-heatmap', { + delimiters: vueDelimeters, + + props: { + user: { + type: String, + required: true + }, + suburl: { + type: String, + required: true + }, + locale: { + type: Object, + required: true + } + }, + + data() { + return { + isLoading: true, + colorRange: [], + endDate: null, + values: [], + totalContributions: 0, + }; + }, + + mounted() { + this.colorRange = [ + this.getColor(0), + this.getColor(1), + this.getColor(2), + this.getColor(3), + this.getColor(4), + this.getColor(5) + ]; + this.endDate = new Date(); + this.loadHeatmap(this.user); + }, + + methods: { + loadHeatmap(userName) { + const self = this; + $.get(`${this.suburl}/api/v1/users/${userName}/heatmap`, (chartRawData) => { + const chartData = []; + for (let i = 0; i < chartRawData.length; i++) { + self.totalContributions += chartRawData[i].contributions; + chartData[i] = {date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions}; + } + self.values = chartData; + self.isLoading = false; + }); + }, + + getColor(idx) { + const el = document.createElement('div'); + el.className = `heatmap-color-${idx}`; + document.body.appendChild(el); + + const color = getComputedStyle(el).backgroundColor; + + document.body.removeChild(el); + + return color; + } + }, + + template: '

total contributions in the last 12 months

' + }); + + new Vue({ + delimiters: vueDelimeters, + el, + + data: { + suburl: AppSubUrl, + heatmapUser, + locale: { + contributions: 'contributions', + no_contributions: 'No contributions', + }, + }, + }); +} diff --git a/web_src/js/index.js b/web_src/js/index.js index 32ed72f139..cd13372b42 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -2,24 +2,23 @@ /* exported timeAddManual, toggleStopwatch, cancelStopwatch */ /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ -import './publicPath.js'; +import './publicpath.js'; import './polyfills.js'; import Vue from 'vue'; import 'jquery.are-you-sure'; -import './vendor/semanticDropdown.js'; -import { svg } from './utils.js'; +import './vendor/semanticdropdown.js'; +import {svg} from './utils.js'; -import initContextPopups from './features/contextPopup.js'; +import initContextPopups from './features/contextpopup.js'; import initHighlight from './features/highlight.js'; -import initGitGraph from './features/gitGraph.js'; +import initGitGraph from './features/gitgraph.js'; import initClipboard from './features/clipboard.js'; -import initUserHeatmap from './features/userHeatmap.js'; +import initUserHeatmap from './features/userheatmap.js'; import createDropzone from './features/dropzone.js'; - import ActivityTopAuthors from './components/ActivityTopAuthors.vue'; -const { AppSubUrl, StaticUrlPrefix, csrf } = window.config; +const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; function htmlEncode(text) { return jQuery('
').text(text).html(); @@ -99,7 +98,6 @@ function initEditDiffTab($form) { }); } - function initEditForm() { if ($('.edit.form').length === 0) { return; @@ -184,7 +182,7 @@ function initReactionSelector(parent) { reactions = '.reactions > '; } - parent.find(`${reactions}a.label`).popup({ position: 'bottom left', metadata: { content: 'title', title: 'none' } }); + parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}}); parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) { const vm = this; @@ -192,9 +190,7 @@ function initReactionSelector(parent) { if ($(this).hasClass('disabled')) return; - const actionURL = $(this).hasClass('item') - ? $(this).closest('.select-reaction').data('action-url') - : $(this).data('action-url'); + const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url'); const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`; $.ajax({ type: 'POST', @@ -235,9 +231,7 @@ function insertAtCursor(field, value) { if (field.selectionStart || field.selectionStart === 0) { const startPos = field.selectionStart; const endPos = field.selectionEnd; - field.value = field.value.substring(0, startPos) - + value - + field.value.substring(endPos, field.value.length); + field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length); field.selectionStart = startPos + value.length; field.selectionEnd = startPos + value.length; } else { @@ -262,13 +256,13 @@ function retrieveImageFromClipboardAsBlob(pasteEvent, callback) { return; } - const { items } = pasteEvent.clipboardData; + const {items} = pasteEvent.clipboardData; if (typeof items === 'undefined') { return; } for (let i = 0; i < items.length; i++) { - if (items[i].type.indexOf('image') === -1) continue; + if (!items[i].type.includes('image')) continue; const blob = items[i].getAsFile(); if (typeof (callback) === 'function') { @@ -282,11 +276,11 @@ function retrieveImageFromClipboardAsBlob(pasteEvent, callback) { function uploadFile(file, callback) { const xhr = new XMLHttpRequest(); - xhr.onload = function () { + xhr.addEventListener('load', () => { if (xhr.status === 200) { callback(xhr.responseText); } - }; + }); xhr.open('post', `${AppSubUrl}/attachments`, true); xhr.setRequestHeader('X-Csrf-Token', csrf); @@ -493,8 +487,8 @@ function initCommentForm() { htmlEncode($(this).text())}`); break; case '#assignee_id': - $list.find('.selected').html(`` - + `${ + $list.find('.selected').html(`` + + `${ htmlEncode($(this).text())}`); } $(`.ui${select_id}.list .no-select`).addClass('hide'); @@ -654,7 +648,7 @@ function initRepository() { window.location.href = $choice.data('url'); } }, - message: { noResults: $dropdown.data('no-results') } + message: {noResults: $dropdown.data('no-results')} }); } @@ -797,13 +791,11 @@ function initRepository() { $.post(update_url, { _csrf: csrf, target_branch: targetBranch - }) - .success((data) => { - $branchTarget.text(data.base_branch); - }) - .always(() => { - reload(); - }); + }).success((data) => { + $branchTarget.text(data.base_branch); + }).always(() => { + reload(); + }); }; const pullrequest_target_update_url = $(this).data('target-update-url'); @@ -814,8 +806,7 @@ function initRepository() { $.post($(this).data('update-url'), { _csrf: csrf, title: $editInput.val() - }, - (data) => { + }, (data) => { $editInput.val(data.title); $issueTitle.text(data.title); pullrequest_targetbranch_change(pullrequest_target_update_url); @@ -887,7 +878,7 @@ function initRepository() { const filenameDict = {}; dz = await createDropzone($dropzone[0], { url: $dropzone.data('upload-url'), - headers: { 'X-Csrf-Token': csrf }, + headers: {'X-Csrf-Token': csrf}, maxFiles: $dropzone.data('max-file'), maxFilesize: $dropzone.data('max-size'), acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), @@ -1142,8 +1133,8 @@ function initMigration() { const toggleMigrations = function () { const authUserName = $('#auth_username').val(); const cloneAddr = $('#clone_addr').val(); - if (!$('#mirror').is(':checked') && (authUserName && authUserName.length > 0) - && (cloneAddr !== undefined && (cloneAddr.startsWith('https://github.com') || cloneAddr.startsWith('http://github.com')))) { + if (!$('#mirror').is(':checked') && (authUserName && authUserName.length > 0) && + (cloneAddr !== undefined && (cloneAddr.startsWith('https://github.com') || cloneAddr.startsWith('http://github.com')))) { $('#migrate_items').show(); } else { $('#migrate_items').hide(); @@ -1203,8 +1194,7 @@ function initPullRequestReview() { .on('mouseenter', function () { const parent = $(this).closest('td'); $(this).closest('tr').addClass( - parent.hasClass('lines-num-old') || parent.hasClass('lines-code-old') - ? 'focus-lines-old' : 'focus-lines-new' + parent.hasClass('lines-num-old') || parent.hasClass('lines-code-old') ? 'focus-lines-old' : 'focus-lines-new' ); }) .on('mouseleave', function () { @@ -1225,8 +1215,8 @@ function initPullRequestReview() { let ntr = tr.next(); if (!ntr.hasClass('add-comment')) { ntr = $(`${ - isSplit ? '' - : '' + isSplit ? '' : + '' }`); tr.after(ntr); } @@ -1297,7 +1287,7 @@ function initWikiForm() { // FIXME: still send render request when return back to edit mode const render = function () { sideBySideChanges = 0; - if (sideBySideTimeout != null) { + if (sideBySideTimeout !== null) { clearTimeout(sideBySideTimeout); sideBySideTimeout = null; } @@ -1306,8 +1296,7 @@ function initWikiForm() { mode: 'gfm', context: $editArea.data('context'), text: plainText - }, - (data) => { + }, (data) => { preview.innerHTML = `
${data}
`; emojify.run($('.editor-preview')[0]); $(preview).find('pre code').each((_, e) => { @@ -1324,7 +1313,7 @@ function initWikiForm() { render(); } // or delay preview by timeout - if (sideBySideTimeout != null) { + if (sideBySideTimeout !== null) { clearTimeout(sideBySideTimeout); sideBySideTimeout = null; } @@ -1479,8 +1468,7 @@ function setSimpleMDE($editArea) { mode: 'gfm', context: $editArea.data('context'), text: plainText - }, - (data) => { + }, (data) => { preview.innerHTML = `
${data}
`; emojify.run($('.editor-preview')[0]); }); @@ -1658,7 +1646,7 @@ function initEditor() { apiCall = extension; } - if (previewLink.length && apiCall && previewFileModes && previewFileModes.length && previewFileModes.indexOf(apiCall) >= 0) { + if (previewLink.length && apiCall && previewFileModes && previewFileModes.length && previewFileModes.includes(apiCall)) { dataUrl = previewLink.data('url'); previewLink.data('url', dataUrl.replace(/(.*)\/.*/i, `$1/${mode}`)); previewLink.show(); @@ -1667,7 +1655,7 @@ function initEditor() { } // If this file is a Markdown extensions, we will load that editor and return - if (markdownFileExts.indexOf(extWithDot) >= 0) { + if (markdownFileExts.includes(extWithDot)) { if (setSimpleMDE($editArea)) { return; } @@ -1683,7 +1671,7 @@ function initEditor() { CodeMirror.autoLoadMode(codeMirrorEditor, mode); } - if (lineWrapExtensions.indexOf(extWithDot) >= 0) { + if (lineWrapExtensions.includes(extWithDot)) { codeMirrorEditor.setOption('lineWrapping', true); } else { codeMirrorEditor.setOption('lineWrapping', false); @@ -1708,7 +1696,7 @@ function initEditor() { // - https://codemirror.net/doc/manual.html#keymaps codeMirrorEditor.setOption('extraKeys', { Tab(cm) { - const spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' '); + const spaces = new Array(parseInt(cm.getOption('indentUnit')) + 1).join(' '); cm.replaceSelection(spaces); } }); @@ -2071,7 +2059,7 @@ function searchUsers() { }); }); - return { results: items }; + return {results: items}; } }, searchFields: ['login', 'full_name'], @@ -2085,7 +2073,7 @@ function searchTeams() { minCharacters: 2, apiSettings: { url: `${AppSubUrl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`, - headers: { 'X-Csrf-Token': csrf }, + headers: {'X-Csrf-Token': csrf}, onResponse(response) { const items = []; $.each(response.data, (_i, item) => { @@ -2095,7 +2083,7 @@ function searchTeams() { }); }); - return { results: items }; + return {results: items}; } }, searchFields: ['name', 'description'], @@ -2118,7 +2106,7 @@ function searchRepositories() { }); }); - return { results: items }; + return {results: items}; } }, searchFields: ['full_name'], @@ -2153,7 +2141,7 @@ function initCodeView() { } }).trigger('hashchange'); } - $('.fold-code').on('click', ({ target }) => { + $('.fold-code').on('click', ({target}) => { const box = target.closest('.file-content'); const folded = box.dataset.folded !== 'true'; target.classList.add(`fa-chevron-${folded ? 'right' : 'down'}`); @@ -2165,11 +2153,11 @@ function initCodeView() { const $row = $blob.parent().parent(); $.get(`${$blob.data('url')}?${$blob.data('query')}&anchor=${$blob.data('anchor')}`, (blob) => { $row.replaceWith(blob); - $(`[data-anchor="${$blob.data('anchor')}"]`).on('click', (e) => { insertBlobExcerpt(e); }); + $(`[data-anchor="${$blob.data('anchor')}"]`).on('click', (e) => { insertBlobExcerpt(e) }); $('.diff-detail-box.ui.sticky').sticky(); }); } - $('.ui.blob-excerpt').on('click', (e) => { insertBlobExcerpt(e); }); + $('.ui.blob-excerpt').on('click', (e) => { insertBlobExcerpt(e) }); } function initU2FAuth() { @@ -2198,7 +2186,7 @@ function u2fSigned(resp) { $.ajax({ url: `${AppSubUrl}/user/u2f/sign`, type: 'POST', - headers: { 'X-Csrf-Token': csrf }, + headers: {'X-Csrf-Token': csrf}, data: JSON.stringify(resp), contentType: 'application/json; charset=utf-8', }).done((res) => { @@ -2215,7 +2203,7 @@ function u2fRegistered(resp) { $.ajax({ url: `${AppSubUrl}/user/settings/security/u2f/register`, type: 'POST', - headers: { 'X-Csrf-Token': csrf }, + headers: {'X-Csrf-Token': csrf}, data: JSON.stringify(resp), contentType: 'application/json; charset=utf-8', success() { @@ -2238,7 +2226,6 @@ function checkError(resp) { return true; } - function u2fError(errorType) { const u2fErrors = { browser: $('#unsupported-browser'), @@ -2259,8 +2246,8 @@ function u2fError(errorType) { } function initU2FRegister() { - $('#register-device').modal({ allowMultiple: false }); - $('#u2f-error').modal({ allowMultiple: false }); + $('#register-device').modal({allowMultiple: false}); + $('#u2f-error').modal({allowMultiple: false}); $('#register-security-key').on('click', (e) => { e.preventDefault(); u2fApi.ensureSupport() @@ -2337,7 +2324,7 @@ function initTemplateSearch() { apiSettings: { url: `${AppSubUrl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`, onResponse(response) { - const filteredResponse = { success: true, results: [] }; + const filteredResponse = {success: true, results: []}; filteredResponse.results.push({ name: '', value: '' @@ -2417,7 +2404,7 @@ $(document).ready(async () => { await createDropzone('#dropzone', { url: $dropzone.data('upload-url'), - headers: { 'X-Csrf-Token': csrf }, + headers: {'X-Csrf-Token': csrf}, maxFiles: $dropzone.data('max-file'), maxFilesize: $dropzone.data('max-size'), acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), @@ -2515,12 +2502,12 @@ $(document).ready(async () => { }); $('.issue-action').click(function () { - let { action } = this.dataset; - let { elementId } = this.dataset; + let {action} = this.dataset; + let {elementId} = this.dataset; const issueIDs = $('.issue-checkbox').children('input:checked').map(function () { return this.dataset.issueId; }).get().join(); - const { url } = this.dataset; + const {url} = this.dataset; if (elementId === '0' && url.substr(-9) === '/assignee') { elementId = ''; action = 'clear'; @@ -2529,7 +2516,7 @@ $(document).ready(async () => { // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload if (action === 'close' || action === 'open') { // uncheck all checkboxes - $('.issue-checkbox input[type="checkbox"]').each((_, e) => { e.checked = false; }); + $('.issue-checkbox input[type="checkbox"]').each((_, e) => { e.checked = false }); } reload(); }); @@ -3031,8 +3018,8 @@ function initFilterBranchTagDropdown(selector) { const vm = this; const items = vm.items.filter((item) => { - return ((vm.mode === 'branches' && item.branch) || (vm.mode === 'tags' && item.tag)) - && (!vm.searchTerm || item.name.toLowerCase().indexOf(vm.searchTerm.toLowerCase()) >= 0); + return ((vm.mode === 'branches' && item.branch) || (vm.mode === 'tags' && item.tag)) && + (!vm.searchTerm || item.name.toLowerCase().includes(vm.searchTerm.toLowerCase())); }); vm.active = (items.length === 0 && vm.showCreateNewBranch ? 0 : -1); @@ -3226,7 +3213,7 @@ function initTopicbar() { if (xhr.responseJSON.invalidTopics.length > 0) { topicPrompts.formatPrompt = xhr.responseJSON.message; - const { invalidTopics } = xhr.responseJSON; + const {invalidTopics} = xhr.responseJSON; const topicLables = topicDropdown.children('a.ui.label'); topics.split(',').forEach((value, index) => { @@ -3248,7 +3235,7 @@ function initTopicbar() { topicDropdown.dropdown({ allowAdditions: true, forceSelection: false, - fields: { name: 'description', value: 'data-value' }, + fields: {name: 'description', value: 'data-value'}, saveRemoteData: false, label: { transition: 'horizontal flip', @@ -3276,20 +3263,20 @@ function initTopicbar() { const query = stripTags(this.urlData.query.trim()); let found_query = false; const current_topics = []; - topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, e) => { current_topics.push(e.dataset.value); }); + topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, e) => { current_topics.push(e.dataset.value) }); if (res.topics) { let found = false; for (let i = 0; i < res.topics.length; i++) { // skip currently added tags - if (current_topics.indexOf(res.topics[i].topic_name) !== -1) { + if (current_topics.includes(res.topics[i].topic_name)) { continue; } if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()) { found_query = true; } - formattedResponse.results.push({ description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name }); + formattedResponse.results.push({description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name}); found = true; } formattedResponse.success = found; @@ -3297,7 +3284,7 @@ function initTopicbar() { if (query.length > 0 && !found_query) { formattedResponse.success = true; - formattedResponse.results.unshift({ description: query, 'data-value': query }); + formattedResponse.results.unshift({description: query, 'data-value': query}); } else if (query.length > 0 && found_query) { formattedResponse.results.sort((a, b) => { if (a.description.toLowerCase() === query.toLowerCase()) return -1; @@ -3308,7 +3295,6 @@ function initTopicbar() { }); } - return formattedResponse; }, }, @@ -3427,7 +3413,7 @@ function initIssueList() { apiSettings: { url: issueSearchUrl, onResponse(response) { - const filteredResponse = { success: true, results: [] }; + const filteredResponse = {success: true, results: []}; const currIssueId = $('#new-dependency-drop-list').data('issue-id'); // Parse the response from the api to work with our dropdown $.each(response, (_i, issue) => { diff --git a/web_src/js/publicPath.js b/web_src/js/publicPath.js deleted file mode 100644 index 120740d708..0000000000 --- a/web_src/js/publicPath.js +++ /dev/null @@ -1,11 +0,0 @@ -/* This sets up webpack's chunk loading to load resources from the 'public' - directory. This file must be imported before any lazy-loading is being attempted. */ - -if (document.currentScript && document.currentScript.src) { - const url = new URL(document.currentScript.src); - __webpack_public_path__ = url.pathname.replace(/\/[^/]*?\/[^/]*?$/, '/'); -} else { - // compat: IE11 - const script = document.querySelector('script[src*="/index.js"]'); - __webpack_public_path__ = script.getAttribute('src').replace(/\/[^/]*?\/[^/]*?$/, '/'); -} diff --git a/web_src/js/publicpath.js b/web_src/js/publicpath.js new file mode 100644 index 0000000000..392c03e700 --- /dev/null +++ b/web_src/js/publicpath.js @@ -0,0 +1,11 @@ +// This sets up webpack's chunk loading to load resources from the 'public' +// directory. This file must be imported before any lazy-loading is being attempted. + +if (document.currentScript && document.currentScript.src) { + const url = new URL(document.currentScript.src); + __webpack_public_path__ = url.pathname.replace(/\/[^/]*?\/[^/]*?$/, '/'); +} else { + // compat: IE11 + const script = document.querySelector('script[src*="/index.js"]'); + __webpack_public_path__ = script.getAttribute('src').replace(/\/[^/]*?\/[^/]*?$/, '/'); +} diff --git a/web_src/js/vendor/gitGraph.js b/web_src/js/vendor/gitGraph.js deleted file mode 100644 index 12d7a2eddf..0000000000 --- a/web_src/js/vendor/gitGraph.js +++ /dev/null @@ -1,424 +0,0 @@ -/* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js - Changes include conversion to ES6 and linting fixes */ - -/* - * @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD 3-Clause - * Copyright (c) 2011, Terrence Lee - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -export default function gitGraph(canvas, rawGraphList, config) { - if (!canvas.getContext) { - return; - } - - if (typeof config === 'undefined') { - config = { - unitSize: 20, - lineWidth: 3, - nodeRadius: 4 - }; - } - - const flows = []; - const graphList = []; - - const ctx = canvas.getContext('2d'); - - const devicePixelRatio = window.devicePixelRatio || 1; - const backingStoreRatio = ctx.webkitBackingStorePixelRatio - || ctx.mozBackingStorePixelRatio - || ctx.msBackingStorePixelRatio - || ctx.oBackingStorePixelRatio - || ctx.backingStorePixelRatio || 1; - - const ratio = devicePixelRatio / backingStoreRatio; - - const init = function () { - let maxWidth = 0; - let i; - const l = rawGraphList.length; - let row; - let midStr; - - for (i = 0; i < l; i++) { - midStr = rawGraphList[i].replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, ''); - - maxWidth = Math.max(midStr.replace(/(_|\s)/g, '').length, maxWidth); - - row = midStr.split(''); - - graphList.unshift(row); - } - - const width = maxWidth * config.unitSize; - const height = graphList.length * config.unitSize; - - canvas.width = width * ratio; - canvas.height = height * ratio; - - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - - ctx.lineWidth = config.lineWidth; - ctx.lineJoin = 'round'; - ctx.lineCap = 'round'; - - ctx.scale(ratio, ratio); - }; - - const genRandomStr = function () { - const chars = '0123456789ABCDEF'; - const stringLength = 6; - let randomString = '', rnum, i; - for (i = 0; i < stringLength; i++) { - rnum = Math.floor(Math.random() * chars.length); - randomString += chars.substring(rnum, rnum + 1); - } - - return randomString; - }; - - const findFlow = function (id) { - let i = flows.length; - - while (i-- && flows[i].id !== id); - - return i; - }; - - const findColomn = function (symbol, row) { - let i = row.length; - - while (i-- && row[i] !== symbol); - - return i; - }; - - const findBranchOut = function (row) { - if (!row) { - return -1; - } - - let i = row.length; - - while (i-- - && !(row[i - 1] && row[i] === '/' && row[i - 1] === '|') - && !(row[i - 2] && row[i] === '_' && row[i - 2] === '|')); - - return i; - }; - - const findLineBreak = function (row) { - if (!row) { - return -1; - } - - let i = row.length; - - while (i-- - && !(row[i - 1] && row[i - 2] && row[i] === ' ' && row[i - 1] === '|' && row[i - 2] === '_')); - - return i; - }; - - const genNewFlow = function () { - let newId; - - do { - newId = genRandomStr(); - } while (findFlow(newId) !== -1); - - return { id: newId, color: `#${newId}` }; - }; - - // Draw methods - const drawLine = function (moveX, moveY, lineX, lineY, color) { - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(moveX, moveY); - ctx.lineTo(lineX, lineY); - ctx.stroke(); - }; - - const drawLineRight = function (x, y, color) { - drawLine(x, y + config.unitSize / 2, x + config.unitSize, y + config.unitSize / 2, color); - }; - - const drawLineUp = function (x, y, color) { - drawLine(x, y + config.unitSize / 2, x, y - config.unitSize / 2, color); - }; - - const drawNode = function (x, y, color) { - ctx.strokeStyle = color; - - drawLineUp(x, y, color); - - ctx.beginPath(); - ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); - ctx.fill(); - }; - - const drawLineIn = function (x, y, color) { - drawLine(x + config.unitSize, y + config.unitSize / 2, x, y - config.unitSize / 2, color); - }; - - const drawLineOut = function (x, y, color) { - drawLine(x, y + config.unitSize / 2, x + config.unitSize, y - config.unitSize / 2, color); - }; - - const draw = function (graphList) { - let colomn, colomnIndex, prevColomn, condenseIndex, breakIndex = -1; - let x, y; - let color; - let nodePos; - let tempFlow; - let prevRowLength = 0; - let flowSwapPos = -1; - let lastLinePos; - let i, l; - let condenseCurrentLength, condensePrevLength = 0; - - let inlineIntersect = false; - - // initiate color array for first row - for (i = 0, l = graphList[0].length; i < l; i++) { - if (graphList[0][i] !== '_' && graphList[0][i] !== ' ') { - flows.push(genNewFlow()); - } - } - - y = (canvas.height / ratio) - config.unitSize * 0.5; - - // iterate - for (i = 0, l = graphList.length; i < l; i++) { - x = config.unitSize * 0.5; - - const currentRow = graphList[i]; - const nextRow = graphList[i + 1]; - const prevRow = graphList[i - 1]; - - flowSwapPos = -1; - - condenseCurrentLength = currentRow.filter((val) => { - return (val !== ' ' && val !== '_'); - }).length; - - // pre process begin - // use last row for analysing - if (prevRow) { - if (!inlineIntersect) { - // intersect might happen - for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { - if (prevRow[colomnIndex + 1] - && (prevRow[colomnIndex] === '/' && prevRow[colomnIndex + 1] === '|') - || ((prevRow[colomnIndex] === '_' && prevRow[colomnIndex + 1] === '|') - && (prevRow[colomnIndex + 2] === '/'))) { - flowSwapPos = colomnIndex; - - // swap two flow - tempFlow = { id: flows[flowSwapPos].id, color: flows[flowSwapPos].color }; - - flows[flowSwapPos].id = flows[flowSwapPos + 1].id; - flows[flowSwapPos].color = flows[flowSwapPos + 1].color; - - flows[flowSwapPos + 1].id = tempFlow.id; - flows[flowSwapPos + 1].color = tempFlow.color; - } - } - } - - if (condensePrevLength < condenseCurrentLength - && ((nodePos = findColomn('*', currentRow)) !== -1 // eslint-disable-line no-cond-assign - && (findColomn('_', currentRow) === -1))) { - flows.splice(nodePos - 1, 0, genNewFlow()); - } - - if (prevRowLength > currentRow.length - && (nodePos = findColomn('*', prevRow)) !== -1) { // eslint-disable-line no-cond-assign - if (findColomn('_', currentRow) === -1 - && findColomn('/', currentRow) === -1 - && findColomn('\\', currentRow) === -1) { - flows.splice(nodePos + 1, 1); - } - } - } // done with the previous row - - prevRowLength = currentRow.length; // store for next round - colomnIndex = 0; // reset index - condenseIndex = 0; - condensePrevLength = 0; - breakIndex = -1; // reset break index - while (colomnIndex < currentRow.length) { - colomn = currentRow[colomnIndex]; - - if (colomn !== ' ' && colomn !== '_') { - ++condensePrevLength; - } - - // check and fix line break in next row - if (colomn === '/' && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === '|') { - /* eslint-disable-next-line */ - if ((breakIndex = findLineBreak(nextRow)) !== -1) { - nextRow.splice(breakIndex, 1); - } - } - // if line break found replace all '/' with '|' after breakIndex in previous row - if (breakIndex !== -1 && colomn === '/' && colomnIndex > breakIndex) { - currentRow[colomnIndex] = '|'; - colomn = '|'; - } - - if (colomn === ' ' - && currentRow[colomnIndex + 1] - && currentRow[colomnIndex + 1] === '_' - && currentRow[colomnIndex - 1] - && currentRow[colomnIndex - 1] === '|') { - currentRow.splice(colomnIndex, 1); - - currentRow[colomnIndex] = '/'; - colomn = '/'; - } - - // create new flow only when no intersect happened - if (flowSwapPos === -1 - && colomn === '/' - && currentRow[colomnIndex - 1] - && currentRow[colomnIndex - 1] === '|') { - flows.splice(condenseIndex, 0, genNewFlow()); - } - - // change \ and / to | when it's in the last position of the whole row - if (colomn === '/' || colomn === '\\') { - if (!(colomn === '/' && findBranchOut(nextRow) === -1)) { - /* eslint-disable-next-line */ - if ((lastLinePos = Math.max(findColomn('|', currentRow), - findColomn('*', currentRow))) !== -1 - && (lastLinePos < colomnIndex - 1)) { - while (currentRow[++lastLinePos] === ' '); - - if (lastLinePos === colomnIndex) { - currentRow[colomnIndex] = '|'; - } - } - } - } - - if (colomn === '*' - && prevRow - && prevRow[condenseIndex + 1] === '\\') { - flows.splice(condenseIndex + 1, 1); - } - - if (colomn !== ' ') { - ++condenseIndex; - } - - ++colomnIndex; - } - - condenseCurrentLength = currentRow.filter((val) => { - return (val !== ' ' && val !== '_'); - }).length; - - // do some clean up - if (flows.length > condenseCurrentLength) { - flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); - } - - colomnIndex = 0; - - // a little inline analysis and draw process - while (colomnIndex < currentRow.length) { - colomn = currentRow[colomnIndex]; - prevColomn = currentRow[colomnIndex - 1]; - - if (currentRow[colomnIndex] === ' ') { - currentRow.splice(colomnIndex, 1); - x += config.unitSize; - - continue; - } - - // inline interset - if ((colomn === '_' || colomn === '/') - && currentRow[colomnIndex - 1] === '|' - && currentRow[colomnIndex - 2] === '_') { - inlineIntersect = true; - - tempFlow = flows.splice(colomnIndex - 2, 1)[0]; - flows.splice(colomnIndex - 1, 0, tempFlow); - currentRow.splice(colomnIndex - 2, 1); - - colomnIndex -= 1; - } else { - inlineIntersect = false; - } - - color = flows[colomnIndex].color; - - switch (colomn) { - case '_': - drawLineRight(x, y, color); - - x += config.unitSize; - break; - - case '*': - drawNode(x, y, color); - break; - - case '|': - drawLineUp(x, y, color); - break; - - case '/': - if (prevColomn - && (prevColomn === '/' - || prevColomn === ' ')) { - x -= config.unitSize; - } - - drawLineOut(x, y, color); - - x += config.unitSize; - break; - - case '\\': - drawLineIn(x, y, color); - break; - } - - ++colomnIndex; - } - - y -= config.unitSize; - } - }; - - init(); - draw(graphList); -} -// @end-license diff --git a/web_src/js/vendor/gitgraph.js b/web_src/js/vendor/gitgraph.js new file mode 100644 index 0000000000..12d7a2eddf --- /dev/null +++ b/web_src/js/vendor/gitgraph.js @@ -0,0 +1,424 @@ +/* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js + Changes include conversion to ES6 and linting fixes */ + +/* + * @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD 3-Clause + * Copyright (c) 2011, Terrence Lee + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +export default function gitGraph(canvas, rawGraphList, config) { + if (!canvas.getContext) { + return; + } + + if (typeof config === 'undefined') { + config = { + unitSize: 20, + lineWidth: 3, + nodeRadius: 4 + }; + } + + const flows = []; + const graphList = []; + + const ctx = canvas.getContext('2d'); + + const devicePixelRatio = window.devicePixelRatio || 1; + const backingStoreRatio = ctx.webkitBackingStorePixelRatio + || ctx.mozBackingStorePixelRatio + || ctx.msBackingStorePixelRatio + || ctx.oBackingStorePixelRatio + || ctx.backingStorePixelRatio || 1; + + const ratio = devicePixelRatio / backingStoreRatio; + + const init = function () { + let maxWidth = 0; + let i; + const l = rawGraphList.length; + let row; + let midStr; + + for (i = 0; i < l; i++) { + midStr = rawGraphList[i].replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, ''); + + maxWidth = Math.max(midStr.replace(/(_|\s)/g, '').length, maxWidth); + + row = midStr.split(''); + + graphList.unshift(row); + } + + const width = maxWidth * config.unitSize; + const height = graphList.length * config.unitSize; + + canvas.width = width * ratio; + canvas.height = height * ratio; + + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + + ctx.lineWidth = config.lineWidth; + ctx.lineJoin = 'round'; + ctx.lineCap = 'round'; + + ctx.scale(ratio, ratio); + }; + + const genRandomStr = function () { + const chars = '0123456789ABCDEF'; + const stringLength = 6; + let randomString = '', rnum, i; + for (i = 0; i < stringLength; i++) { + rnum = Math.floor(Math.random() * chars.length); + randomString += chars.substring(rnum, rnum + 1); + } + + return randomString; + }; + + const findFlow = function (id) { + let i = flows.length; + + while (i-- && flows[i].id !== id); + + return i; + }; + + const findColomn = function (symbol, row) { + let i = row.length; + + while (i-- && row[i] !== symbol); + + return i; + }; + + const findBranchOut = function (row) { + if (!row) { + return -1; + } + + let i = row.length; + + while (i-- + && !(row[i - 1] && row[i] === '/' && row[i - 1] === '|') + && !(row[i - 2] && row[i] === '_' && row[i - 2] === '|')); + + return i; + }; + + const findLineBreak = function (row) { + if (!row) { + return -1; + } + + let i = row.length; + + while (i-- + && !(row[i - 1] && row[i - 2] && row[i] === ' ' && row[i - 1] === '|' && row[i - 2] === '_')); + + return i; + }; + + const genNewFlow = function () { + let newId; + + do { + newId = genRandomStr(); + } while (findFlow(newId) !== -1); + + return { id: newId, color: `#${newId}` }; + }; + + // Draw methods + const drawLine = function (moveX, moveY, lineX, lineY, color) { + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.moveTo(moveX, moveY); + ctx.lineTo(lineX, lineY); + ctx.stroke(); + }; + + const drawLineRight = function (x, y, color) { + drawLine(x, y + config.unitSize / 2, x + config.unitSize, y + config.unitSize / 2, color); + }; + + const drawLineUp = function (x, y, color) { + drawLine(x, y + config.unitSize / 2, x, y - config.unitSize / 2, color); + }; + + const drawNode = function (x, y, color) { + ctx.strokeStyle = color; + + drawLineUp(x, y, color); + + ctx.beginPath(); + ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); + ctx.fill(); + }; + + const drawLineIn = function (x, y, color) { + drawLine(x + config.unitSize, y + config.unitSize / 2, x, y - config.unitSize / 2, color); + }; + + const drawLineOut = function (x, y, color) { + drawLine(x, y + config.unitSize / 2, x + config.unitSize, y - config.unitSize / 2, color); + }; + + const draw = function (graphList) { + let colomn, colomnIndex, prevColomn, condenseIndex, breakIndex = -1; + let x, y; + let color; + let nodePos; + let tempFlow; + let prevRowLength = 0; + let flowSwapPos = -1; + let lastLinePos; + let i, l; + let condenseCurrentLength, condensePrevLength = 0; + + let inlineIntersect = false; + + // initiate color array for first row + for (i = 0, l = graphList[0].length; i < l; i++) { + if (graphList[0][i] !== '_' && graphList[0][i] !== ' ') { + flows.push(genNewFlow()); + } + } + + y = (canvas.height / ratio) - config.unitSize * 0.5; + + // iterate + for (i = 0, l = graphList.length; i < l; i++) { + x = config.unitSize * 0.5; + + const currentRow = graphList[i]; + const nextRow = graphList[i + 1]; + const prevRow = graphList[i - 1]; + + flowSwapPos = -1; + + condenseCurrentLength = currentRow.filter((val) => { + return (val !== ' ' && val !== '_'); + }).length; + + // pre process begin + // use last row for analysing + if (prevRow) { + if (!inlineIntersect) { + // intersect might happen + for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { + if (prevRow[colomnIndex + 1] + && (prevRow[colomnIndex] === '/' && prevRow[colomnIndex + 1] === '|') + || ((prevRow[colomnIndex] === '_' && prevRow[colomnIndex + 1] === '|') + && (prevRow[colomnIndex + 2] === '/'))) { + flowSwapPos = colomnIndex; + + // swap two flow + tempFlow = { id: flows[flowSwapPos].id, color: flows[flowSwapPos].color }; + + flows[flowSwapPos].id = flows[flowSwapPos + 1].id; + flows[flowSwapPos].color = flows[flowSwapPos + 1].color; + + flows[flowSwapPos + 1].id = tempFlow.id; + flows[flowSwapPos + 1].color = tempFlow.color; + } + } + } + + if (condensePrevLength < condenseCurrentLength + && ((nodePos = findColomn('*', currentRow)) !== -1 // eslint-disable-line no-cond-assign + && (findColomn('_', currentRow) === -1))) { + flows.splice(nodePos - 1, 0, genNewFlow()); + } + + if (prevRowLength > currentRow.length + && (nodePos = findColomn('*', prevRow)) !== -1) { // eslint-disable-line no-cond-assign + if (findColomn('_', currentRow) === -1 + && findColomn('/', currentRow) === -1 + && findColomn('\\', currentRow) === -1) { + flows.splice(nodePos + 1, 1); + } + } + } // done with the previous row + + prevRowLength = currentRow.length; // store for next round + colomnIndex = 0; // reset index + condenseIndex = 0; + condensePrevLength = 0; + breakIndex = -1; // reset break index + while (colomnIndex < currentRow.length) { + colomn = currentRow[colomnIndex]; + + if (colomn !== ' ' && colomn !== '_') { + ++condensePrevLength; + } + + // check and fix line break in next row + if (colomn === '/' && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === '|') { + /* eslint-disable-next-line */ + if ((breakIndex = findLineBreak(nextRow)) !== -1) { + nextRow.splice(breakIndex, 1); + } + } + // if line break found replace all '/' with '|' after breakIndex in previous row + if (breakIndex !== -1 && colomn === '/' && colomnIndex > breakIndex) { + currentRow[colomnIndex] = '|'; + colomn = '|'; + } + + if (colomn === ' ' + && currentRow[colomnIndex + 1] + && currentRow[colomnIndex + 1] === '_' + && currentRow[colomnIndex - 1] + && currentRow[colomnIndex - 1] === '|') { + currentRow.splice(colomnIndex, 1); + + currentRow[colomnIndex] = '/'; + colomn = '/'; + } + + // create new flow only when no intersect happened + if (flowSwapPos === -1 + && colomn === '/' + && currentRow[colomnIndex - 1] + && currentRow[colomnIndex - 1] === '|') { + flows.splice(condenseIndex, 0, genNewFlow()); + } + + // change \ and / to | when it's in the last position of the whole row + if (colomn === '/' || colomn === '\\') { + if (!(colomn === '/' && findBranchOut(nextRow) === -1)) { + /* eslint-disable-next-line */ + if ((lastLinePos = Math.max(findColomn('|', currentRow), + findColomn('*', currentRow))) !== -1 + && (lastLinePos < colomnIndex - 1)) { + while (currentRow[++lastLinePos] === ' '); + + if (lastLinePos === colomnIndex) { + currentRow[colomnIndex] = '|'; + } + } + } + } + + if (colomn === '*' + && prevRow + && prevRow[condenseIndex + 1] === '\\') { + flows.splice(condenseIndex + 1, 1); + } + + if (colomn !== ' ') { + ++condenseIndex; + } + + ++colomnIndex; + } + + condenseCurrentLength = currentRow.filter((val) => { + return (val !== ' ' && val !== '_'); + }).length; + + // do some clean up + if (flows.length > condenseCurrentLength) { + flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); + } + + colomnIndex = 0; + + // a little inline analysis and draw process + while (colomnIndex < currentRow.length) { + colomn = currentRow[colomnIndex]; + prevColomn = currentRow[colomnIndex - 1]; + + if (currentRow[colomnIndex] === ' ') { + currentRow.splice(colomnIndex, 1); + x += config.unitSize; + + continue; + } + + // inline interset + if ((colomn === '_' || colomn === '/') + && currentRow[colomnIndex - 1] === '|' + && currentRow[colomnIndex - 2] === '_') { + inlineIntersect = true; + + tempFlow = flows.splice(colomnIndex - 2, 1)[0]; + flows.splice(colomnIndex - 1, 0, tempFlow); + currentRow.splice(colomnIndex - 2, 1); + + colomnIndex -= 1; + } else { + inlineIntersect = false; + } + + color = flows[colomnIndex].color; + + switch (colomn) { + case '_': + drawLineRight(x, y, color); + + x += config.unitSize; + break; + + case '*': + drawNode(x, y, color); + break; + + case '|': + drawLineUp(x, y, color); + break; + + case '/': + if (prevColomn + && (prevColomn === '/' + || prevColomn === ' ')) { + x -= config.unitSize; + } + + drawLineOut(x, y, color); + + x += config.unitSize; + break; + + case '\\': + drawLineIn(x, y, color); + break; + } + + ++colomnIndex; + } + + y -= config.unitSize; + } + }; + + init(); + draw(graphList); +} +// @end-license diff --git a/web_src/js/vendor/semanticDropdown.js b/web_src/js/vendor/semanticDropdown.js deleted file mode 100644 index c9144adf53..0000000000 --- a/web_src/js/vendor/semanticDropdown.js +++ /dev/null @@ -1,4300 +0,0 @@ -/*! - * # Fomantic-UI - Dropdown - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/* - * Copyright 2019 The Gitea Authors - * Released under the MIT license - * http://opensource.org/licenses/MIT - * This version has been modified by Gitea to improve accessibility. - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.dropdown = function(parameters) { - var - $allModules = $(this), - $document = $(document), - - moduleSelector = $allModules.selector || '', - - hasTouch = ('ontouchstart' in document.documentElement), - clickEvent = hasTouch - ? 'touchstart' - : 'click', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - lastAriaID = 1, - returnedValue - ; - - $allModules - .each(function(elementIndex) { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.dropdown.settings, parameters) - : $.extend({}, $.fn.dropdown.settings), - - className = settings.className, - message = settings.message, - fields = settings.fields, - keys = settings.keys, - metadata = settings.metadata, - namespace = settings.namespace, - regExp = settings.regExp, - selector = settings.selector, - error = settings.error, - templates = settings.templates, - - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = $(settings.context), - $text = $module.find(selector.text), - $search = $module.find(selector.search), - $sizer = $module.find(selector.sizer), - $input = $module.find(selector.input), - $icon = $module.find(selector.icon), - $clear = $module.find(selector.clearIcon), - - $combo = ($module.prev().find(selector.text).length > 0) - ? $module.prev().find(selector.text) - : $module.prev(), - - $menu = $module.children(selector.menu), - $item = $menu.find(selector.item), - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), - - activated = false, - itemActivated = false, - internalChange = false, - iconClicked = false, - element = this, - instance = $module.data(moduleNamespace), - - selectActionActive, - initialLoad, - pageLostFocus, - willRefocus, - elementNamespace, - id, - selectObserver, - menuObserver, - module - ; - - module = { - - initialize: function() { - module.debug('Initializing dropdown', settings); - - if( module.is.alreadySetup() ) { - module.setup.reference(); - } - else { - if (settings.ignoreDiacritics && !String.prototype.normalize) { - settings.ignoreDiacritics = false; - module.error(error.noNormalize, element); - } - - module.setup.layout(); - - if(settings.values) { - module.change.values(settings.values); - } - - module.refreshData(); - - module.save.defaults(); - module.restore.selected(); - - module.create.id(); - module.bind.events(); - - module.observeChanges(); - module.instantiate(); - - module.aria.setup(); - } - - }, - - instantiate: function() { - module.verbose('Storing instance of dropdown', module); - instance = module; - $module - .data(moduleNamespace, module) - ; - }, - - destroy: function() { - module.verbose('Destroying previous dropdown', $module); - module.remove.tabbable(); - module.remove.active(); - $menu.transition('stop all'); - $menu.removeClass(className.visible).addClass(className.hidden); - $module - .off(eventNamespace) - .removeData(moduleNamespace) - ; - $menu - .off(eventNamespace) - ; - $document - .off(elementNamespace) - ; - module.disconnect.menuObserver(); - module.disconnect.selectObserver(); - }, - - observeChanges: function() { - if('MutationObserver' in window) { - selectObserver = new MutationObserver(module.event.select.mutation); - menuObserver = new MutationObserver(module.event.menu.mutation); - module.debug('Setting up mutation observer', selectObserver, menuObserver); - module.observe.select(); - module.observe.menu(); - } - }, - - disconnect: { - menuObserver: function() { - if(menuObserver) { - menuObserver.disconnect(); - } - }, - selectObserver: function() { - if(selectObserver) { - selectObserver.disconnect(); - } - } - }, - observe: { - select: function() { - if(module.has.input() && selectObserver) { - selectObserver.observe($module[0], { - childList : true, - subtree : true - }); - } - }, - menu: function() { - if(module.has.menu() && menuObserver) { - menuObserver.observe($menu[0], { - childList : true, - subtree : true - }); - } - } - }, - - create: { - id: function() { - id = (Math.random().toString(16) + '000000000').substr(2, 8); - elementNamespace = '.' + id; - module.verbose('Creating unique id for element', id); - }, - userChoice: function(values) { - var - $userChoices, - $userChoice, - isUserValue, - html - ; - values = values || module.get.userValues(); - if(!values) { - return false; - } - values = Array.isArray(values) - ? values - : [values] - ; - $.each(values, function(index, value) { - if(module.get.item(value) === false) { - html = settings.templates.addition( module.add.variables(message.addResult, value) ); - $userChoice = $('
') - .html(html) - .attr('data-' + metadata.value, value) - .attr('data-' + metadata.text, value) - .addClass(className.addition) - .addClass(className.item) - ; - if(settings.hideAdditions) { - $userChoice.addClass(className.hidden); - } - $userChoices = ($userChoices === undefined) - ? $userChoice - : $userChoices.add($userChoice) - ; - module.verbose('Creating user choices for value', value, $userChoice); - } - }); - return $userChoices; - }, - userLabels: function(value) { - var - userValues = module.get.userValues() - ; - if(userValues) { - module.debug('Adding user labels', userValues); - $.each(userValues, function(index, value) { - module.verbose('Adding custom user value'); - module.add.label(value, value); - }); - } - }, - menu: function() { - $menu = $('
') - .addClass(className.menu) - .appendTo($module) - ; - }, - sizer: function() { - $sizer = $('') - .addClass(className.sizer) - .insertAfter($search) - ; - } - }, - - search: function(query) { - query = (query !== undefined) - ? query - : module.get.query() - ; - module.verbose('Searching for query', query); - if(module.has.minCharacters(query)) { - module.filter(query); - } - else { - module.hide(null,true); - } - }, - - select: { - firstUnfiltered: function() { - module.verbose('Selecting first non-filtered element'); - module.remove.selectedItem(); - $item - .not(selector.unselectable) - .not(selector.addition + selector.hidden) - .eq(0) - .addClass(className.selected) - ; - }, - nextAvailable: function($selected) { - $selected = $selected.eq(0); - var - $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), - $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), - hasNext = ($nextAvailable.length > 0) - ; - if(hasNext) { - module.verbose('Moving selection to', $nextAvailable); - $nextAvailable.addClass(className.selected); - } - else { - module.verbose('Moving selection to', $prevAvailable); - $prevAvailable.addClass(className.selected); - } - } - }, - - aria: { - setup: function() { - var role = module.aria.guessRole(); - if( role !== 'menu' ) { - return; - } - $module.attr('aria-busy', 'true'); - $module.attr('role', 'menu'); - $module.attr('aria-haspopup', 'menu'); - $module.attr('aria-expanded', 'false'); - $menu.find('.divider').attr('role', 'separator'); - $item.attr('role', 'menuitem'); - $item.each(function (index, item) { - if( !item.id ) { - item.id = module.aria.nextID('menuitem'); - } - }); - $text = $module - .find('> .text') - .eq(0) - ; - if( $module.data('content') ) { - $text.attr('aria-hidden'); - $module.attr('aria-label', $module.data('content')); - } - else { - $text.attr('id', module.aria.nextID('menutext')); - $module.attr('aria-labelledby', $text.attr('id')); - } - $module.attr('aria-busy', 'false'); - }, - nextID: function(prefix) { - var nextID; - do { - nextID = prefix + '_' + lastAriaID++; - } while( document.getElementById(nextID) ); - return nextID; - }, - setExpanded: function(expanded) { - if( $module.attr('aria-haspopup') ) { - $module.attr('aria-expanded', expanded); - } - }, - refreshDescendant: function() { - if( $module.attr('aria-haspopup') !== 'menu' ) { - return; - } - var - $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), - $activeItem = $menu.children('.' + className.active).eq(0), - $selectedItem = ($currentlySelected.length > 0) - ? $currentlySelected - : $activeItem - ; - if( $selectedItem ) { - $module.attr('aria-activedescendant', $selectedItem.attr('id')); - } - else { - module.aria.removeDescendant(); - } - }, - removeDescendant: function() { - if( $module.attr('aria-haspopup') == 'menu' ) { - $module.removeAttr('aria-activedescendant'); - } - }, - guessRole: function() { - var - isIcon = $module.hasClass('icon'), - hasSearch = module.has.search(), - hasInput = ($input.length > 0), - isMultiple = module.is.multiple() - ; - if ( !isIcon && !hasSearch && !hasInput && !isMultiple ) { - return 'menu'; - } - return 'unknown'; - } - }, - - setup: { - api: function() { - var - apiSettings = { - debug : settings.debug, - urlData : { - value : module.get.value(), - query : module.get.query() - }, - on : false - } - ; - module.verbose('First request, initializing API'); - $module - .api(apiSettings) - ; - }, - layout: function() { - if( $module.is('select') ) { - module.setup.select(); - module.setup.returnedObject(); - } - if( !module.has.menu() ) { - module.create.menu(); - } - if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { - module.verbose('Adding clear icon'); - $clear = $('') - .addClass('remove icon') - .insertBefore($text) - ; - } - if( module.is.search() && !module.has.search() ) { - module.verbose('Adding search input'); - $search = $('') - .addClass(className.search) - .prop('autocomplete', 'off') - .insertBefore($text) - ; - } - if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { - module.create.sizer(); - } - if(settings.allowTab) { - module.set.tabbable(); - } - $item.attr('tabindex', '-1'); - }, - select: function() { - var - selectValues = module.get.selectValues() - ; - module.debug('Dropdown initialized on a select', selectValues); - if( $module.is('select') ) { - $input = $module; - } - // see if select is placed correctly already - if($input.parent(selector.dropdown).length > 0) { - module.debug('UI dropdown already exists. Creating dropdown menu only'); - $module = $input.closest(selector.dropdown); - if( !module.has.menu() ) { - module.create.menu(); - } - $menu = $module.children(selector.menu); - module.setup.menu(selectValues); - } - else { - module.debug('Creating entire dropdown from select'); - $module = $('
') - .attr('class', $input.attr('class') ) - .addClass(className.selection) - .addClass(className.dropdown) - .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) - .insertBefore($input) - ; - if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { - module.error(error.missingMultiple); - $input.prop('multiple', true); - } - if($input.is('[multiple]')) { - module.set.multiple(); - } - if ($input.prop('disabled')) { - module.debug('Disabling dropdown'); - $module.addClass(className.disabled); - } - $input - .removeAttr('required') - .removeAttr('class') - .detach() - .prependTo($module) - ; - } - module.refresh(); - }, - menu: function(values) { - $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); - $item = $menu.find(selector.item); - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); - }, - reference: function() { - module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); - // replace module reference - $module = $module.parent(selector.dropdown); - instance = $module.data(moduleNamespace); - element = $module.get(0); - module.refresh(); - module.setup.returnedObject(); - }, - returnedObject: function() { - var - $firstModules = $allModules.slice(0, elementIndex), - $lastModules = $allModules.slice(elementIndex + 1) - ; - // adjust all modules to use correct reference - $allModules = $firstModules.add($module).add($lastModules); - } - }, - - refresh: function() { - module.refreshSelectors(); - module.refreshData(); - }, - - refreshItems: function() { - $item = $menu.find(selector.item); - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); - }, - - refreshSelectors: function() { - module.verbose('Refreshing selector cache'); - $text = $module.find(selector.text); - $search = $module.find(selector.search); - $input = $module.find(selector.input); - $icon = $module.find(selector.icon); - $combo = ($module.prev().find(selector.text).length > 0) - ? $module.prev().find(selector.text) - : $module.prev() - ; - $menu = $module.children(selector.menu); - $item = $menu.find(selector.item); - $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); - }, - - refreshData: function() { - module.verbose('Refreshing cached metadata'); - $item - .removeData(metadata.text) - .removeData(metadata.value) - ; - }, - - clearData: function() { - module.verbose('Clearing metadata'); - $item - .removeData(metadata.text) - .removeData(metadata.value) - ; - $module - .removeData(metadata.defaultText) - .removeData(metadata.defaultValue) - .removeData(metadata.placeholderText) - ; - }, - - toggle: function() { - module.verbose('Toggling menu visibility'); - if( !module.is.active() ) { - module.show(); - } - else { - module.hide(); - } - }, - - show: function(callback, preventFocus) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if(!module.can.show() && module.is.remote()) { - module.debug('No API results retrieved, searching before show'); - module.queryRemote(module.get.query(), module.show); - } - if( module.can.show() && !module.is.active() ) { - module.debug('Showing dropdown'); - if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { - module.remove.message(); - } - if(module.is.allFiltered()) { - return true; - } - if(settings.onShow.call(element) !== false) { - module.aria.setExpanded(true); - module.aria.refreshDescendant(); - module.animate.show(function() { - if( module.can.click() ) { - module.bind.intent(); - } - if(module.has.search() && !preventFocus) { - module.focusSearch(); - } - module.set.visible(); - callback.call(element); - }); - } - } - }, - - hide: function(callback, preventBlur) { - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.active() && !module.is.animatingOutward() ) { - module.debug('Hiding dropdown'); - if(settings.onHide.call(element) !== false) { - module.aria.setExpanded(false); - module.aria.removeDescendant(); - module.animate.hide(function() { - module.remove.visible(); - // hidding search focus - if ( module.is.focusedOnSearch() && preventBlur !== true ) { - $search.blur(); - } - callback.call(element); - }); - } - } else if( module.can.click() ) { - module.unbind.intent(); - } - }, - - hideOthers: function() { - module.verbose('Finding other dropdowns to hide'); - $allModules - .not($module) - .has(selector.menu + '.' + className.visible) - .dropdown('hide') - ; - }, - - hideMenu: function() { - module.verbose('Hiding menu instantaneously'); - module.remove.active(); - module.remove.visible(); - $menu.transition('hide'); - }, - - hideSubMenus: function() { - var - $subMenus = $menu.children(selector.item).find(selector.menu) - ; - module.verbose('Hiding sub menus', $subMenus); - $subMenus.transition('hide'); - }, - - bind: { - events: function() { - module.bind.keyboardEvents(); - module.bind.inputEvents(); - module.bind.mouseEvents(); - }, - keyboardEvents: function() { - module.verbose('Binding keyboard events'); - $module - .on('keydown' + eventNamespace, module.event.keydown) - ; - if( module.has.search() ) { - $module - .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) - ; - } - if( module.is.multiple() ) { - $document - .on('keydown' + elementNamespace, module.event.document.keydown) - ; - } - }, - inputEvents: function() { - module.verbose('Binding input change events'); - $module - .on('change' + eventNamespace, selector.input, module.event.change) - ; - }, - mouseEvents: function() { - module.verbose('Binding mouse events'); - if(module.is.multiple()) { - $module - .on(clickEvent + eventNamespace, selector.label, module.event.label.click) - .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) - ; - } - if( module.is.searchSelection() ) { - $module - .on('mousedown' + eventNamespace, module.event.mousedown) - .on('mouseup' + eventNamespace, module.event.mouseup) - .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) - .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) - .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) - .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) - .on('focus' + eventNamespace, selector.search, module.event.search.focus) - .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) - .on('blur' + eventNamespace, selector.search, module.event.search.blur) - .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) - ; - if(module.is.multiple()) { - $module - .on(clickEvent + eventNamespace, module.event.click) - ; - } - } - else { - if(settings.on == 'click') { - $module - .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) - .on(clickEvent + eventNamespace, module.event.test.toggle) - ; - } - else if(settings.on == 'hover') { - $module - .on('mouseenter' + eventNamespace, module.delay.show) - .on('mouseleave' + eventNamespace, module.delay.hide) - ; - } - else { - $module - .on(settings.on + eventNamespace, module.toggle) - ; - } - $module - .on('mousedown' + eventNamespace, module.event.mousedown) - .on('mouseup' + eventNamespace, module.event.mouseup) - .on('focus' + eventNamespace, module.event.focus) - .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) - ; - if(module.has.menuSearch() ) { - $module - .on('blur' + eventNamespace, selector.search, module.event.search.blur) - ; - } - else { - $module - .on('blur' + eventNamespace, module.event.blur) - ; - } - } - $menu - .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) - .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) - .on('click' + eventNamespace, selector.item, module.event.item.click) - ; - }, - intent: function() { - module.verbose('Binding hide intent event to document'); - if(hasTouch) { - $document - .on('touchstart' + elementNamespace, module.event.test.touch) - .on('touchmove' + elementNamespace, module.event.test.touch) - ; - } - $document - .on(clickEvent + elementNamespace, module.event.test.hide) - ; - } - }, - - unbind: { - intent: function() { - module.verbose('Removing hide intent event from document'); - if(hasTouch) { - $document - .off('touchstart' + elementNamespace) - .off('touchmove' + elementNamespace) - ; - } - $document - .off(clickEvent + elementNamespace) - ; - } - }, - - filter: function(query) { - var - searchTerm = (query !== undefined) - ? query - : module.get.query(), - afterFiltered = function() { - if(module.is.multiple()) { - module.filterActive(); - } - if(query || (!query && module.get.activeItem().length == 0)) { - module.select.firstUnfiltered(); - } - if( module.has.allResultsFiltered() ) { - if( settings.onNoResults.call(element, searchTerm) ) { - if(settings.allowAdditions) { - if(settings.hideAdditions) { - module.verbose('User addition with no menu, setting empty style'); - module.set.empty(); - module.hideMenu(); - } - } - else { - module.verbose('All items filtered, showing message', searchTerm); - module.add.message(message.noResults); - } - } - else { - module.verbose('All items filtered, hiding dropdown', searchTerm); - module.hideMenu(); - } - } - else { - module.remove.empty(); - module.remove.message(); - } - if(settings.allowAdditions) { - module.add.userSuggestion(module.escape.htmlEntities(query)); - } - if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { - module.show(); - } - } - ; - if(settings.useLabels && module.has.maxSelections()) { - return; - } - if(settings.apiSettings) { - if( module.can.useAPI() ) { - module.queryRemote(searchTerm, function() { - if(settings.filterRemoteData) { - module.filterItems(searchTerm); - } - var preSelected = $input.val(); - if(!Array.isArray(preSelected)) { - preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; - } - $.each(preSelected,function(index,value){ - $item.filter('[data-value="'+value+'"]') - .addClass(className.filtered) - ; - }); - afterFiltered(); - }); - } - else { - module.error(error.noAPI); - } - } - else { - module.filterItems(searchTerm); - afterFiltered(); - } - }, - - queryRemote: function(query, callback) { - var - apiSettings = { - errorDuration : false, - cache : 'local', - throttle : settings.throttle, - urlData : { - query: query - }, - onError: function() { - module.add.message(message.serverError); - callback(); - }, - onFailure: function() { - module.add.message(message.serverError); - callback(); - }, - onSuccess : function(response) { - var - values = response[fields.remoteValues] - ; - if (!Array.isArray(values)){ - values = []; - } - module.remove.message(); - module.setup.menu({ - values: values - }); - - if(values.length===0 && !settings.allowAdditions) { - module.add.message(message.noResults); - } - callback(); - } - } - ; - if( !$module.api('get request') ) { - module.setup.api(); - } - apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); - $module - .api('setting', apiSettings) - .api('query') - ; - }, - - filterItems: function(query) { - var - searchTerm = module.remove.diacritics(query !== undefined - ? query - : module.get.query() - ), - results = null, - escapedTerm = module.escape.string(searchTerm), - regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', - beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) - ; - // avoid loop if we're matching nothing - if( module.has.query() ) { - results = []; - - module.verbose('Searching for matching values', searchTerm); - $item - .each(function(){ - var - $choice = $(this), - text, - value - ; - if($choice.hasClass(className.unfilterable)) { - results.push(this); - return true; - } - if(settings.match === 'both' || settings.match === 'text') { - text = module.remove.diacritics(String(module.get.choiceText($choice, false))); - if(text.search(beginsWithRegExp) !== -1) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { - results.push(this); - return true; - } - } - if(settings.match === 'both' || settings.match === 'value') { - value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); - if(value.search(beginsWithRegExp) !== -1) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { - results.push(this); - return true; - } - else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { - results.push(this); - return true; - } - } - }) - ; - } - module.debug('Showing only matched items', searchTerm); - module.remove.filteredItem(); - if(results) { - $item - .not(results) - .addClass(className.filtered) - ; - } - - if(!module.has.query()) { - $divider - .removeClass(className.hidden); - } else if(settings.hideDividers === true) { - $divider - .addClass(className.hidden); - } else if(settings.hideDividers === 'empty') { - $divider - .removeClass(className.hidden) - .filter(function() { - // First find the last divider in this divider group - // Dividers which are direct siblings are considered a group - var lastDivider = $(this).nextUntil(selector.item); - - return (lastDivider.length ? lastDivider : $(this)) - // Count all non-filtered items until the next divider (or end of the dropdown) - .nextUntil(selector.divider) - .filter(selector.item + ":not(." + className.filtered + ")") - // Hide divider if no items are found - .length === 0; - }) - .addClass(className.hidden); - } - }, - - fuzzySearch: function(query, term) { - var - termLength = term.length, - queryLength = query.length - ; - query = (settings.ignoreSearchCase ? query.toLowerCase() : query); - term = (settings.ignoreSearchCase ? term.toLowerCase() : term); - if(queryLength > termLength) { - return false; - } - if(queryLength === termLength) { - return (query === term); - } - search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { - var - queryCharacter = query.charCodeAt(characterIndex) - ; - while(nextCharacterIndex < termLength) { - if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { - continue search; - } - } - return false; - } - return true; - }, - exactSearch: function (query, term) { - query = (settings.ignoreSearchCase ? query.toLowerCase() : query); - term = (settings.ignoreSearchCase ? term.toLowerCase() : term); - return term.indexOf(query) > -1; - - }, - filterActive: function() { - if(settings.useLabels) { - $item.filter('.' + className.active) - .addClass(className.filtered) - ; - } - }, - - focusSearch: function(skipHandler) { - if( module.has.search() && !module.is.focusedOnSearch() ) { - if(skipHandler) { - $module.off('focus' + eventNamespace, selector.search); - $search.focus(); - $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); - } - else { - $search.focus(); - } - } - }, - - blurSearch: function() { - if( module.has.search() ) { - $search.blur(); - } - }, - - forceSelection: function() { - var - $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), - $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), - $selectedItem = ($currentlySelected.length > 0) - ? $currentlySelected - : $activeItem, - hasSelected = ($selectedItem.length > 0) - ; - if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { - module.debug('Forcing partial selection to selected item', $selectedItem); - $selectedItem[0].click(); - return; - } - else { - module.remove.searchTerm(); - } - }, - - change: { - values: function(values) { - if(!settings.allowAdditions) { - module.clear(); - } - module.debug('Creating dropdown with specified values', values); - module.setup.menu({values: values}); - $.each(values, function(index, item) { - if(item.selected == true) { - module.debug('Setting initial selection to', item[fields.value]); - module.set.selected(item[fields.value]); - if(!module.is.multiple()) { - return false; - } - } - }); - - if(module.has.selectInput()) { - module.disconnect.selectObserver(); - $input.html(''); - $input.append(''); - $.each(values, function(index, item) { - var - value = settings.templates.deQuote(item[fields.value]), - name = settings.templates.escape( - item[fields.name] || '', - settings.preserveHTML - ) - ; - $input.append(''); - }); - module.observe.select(); - } - } - }, - - event: { - change: function() { - if(!internalChange) { - module.debug('Input changed, updating selection'); - module.set.selected(); - } - }, - focus: function() { - if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { - module.show(); - } - }, - blur: function(event) { - pageLostFocus = (document.activeElement === this); - if(!activated && !pageLostFocus) { - module.remove.activeLabel(); - module.hide(); - } - }, - mousedown: function() { - if(module.is.searchSelection()) { - // prevent menu hiding on immediate re-focus - willRefocus = true; - } - else { - // prevents focus callback from occurring on mousedown - activated = true; - } - }, - mouseup: function() { - if(module.is.searchSelection()) { - // prevent menu hiding on immediate re-focus - willRefocus = false; - } - else { - activated = false; - } - }, - click: function(event) { - var - $target = $(event.target) - ; - // focus search - if($target.is($module)) { - if(!module.is.focusedOnSearch()) { - module.focusSearch(); - } - else { - module.show(); - } - } - }, - search: { - focus: function(event) { - activated = true; - if(module.is.multiple()) { - module.remove.activeLabel(); - } - if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { - module.search(); - } - }, - blur: function(event) { - pageLostFocus = (document.activeElement === this); - if(module.is.searchSelection() && !willRefocus) { - if(!itemActivated && !pageLostFocus) { - if(settings.forceSelection) { - module.forceSelection(); - } else if(!settings.allowAdditions){ - module.remove.searchTerm(); - } - module.hide(); - } - } - willRefocus = false; - } - }, - clearIcon: { - click: function(event) { - module.clear(); - if(module.is.searchSelection()) { - module.remove.searchTerm(); - } - module.hide(); - event.stopPropagation(); - } - }, - icon: { - click: function(event) { - iconClicked=true; - if(module.has.search()) { - if(!module.is.active()) { - if(settings.showOnFocus){ - module.focusSearch(); - } else { - module.toggle(); - } - } else { - module.blurSearch(); - } - } else { - module.toggle(); - } - } - }, - text: { - focus: function(event) { - activated = true; - module.focusSearch(); - } - }, - input: function(event) { - if(module.is.multiple() || module.is.searchSelection()) { - module.set.filtered(); - } - clearTimeout(module.timer); - module.timer = setTimeout(module.search, settings.delay.search); - }, - label: { - click: function(event) { - var - $label = $(this), - $labels = $module.find(selector.label), - $activeLabels = $labels.filter('.' + className.active), - $nextActive = $label.nextAll('.' + className.active), - $prevActive = $label.prevAll('.' + className.active), - $range = ($nextActive.length > 0) - ? $label.nextUntil($nextActive).add($activeLabels).add($label) - : $label.prevUntil($prevActive).add($activeLabels).add($label) - ; - if(event.shiftKey) { - $activeLabels.removeClass(className.active); - $range.addClass(className.active); - } - else if(event.ctrlKey) { - $label.toggleClass(className.active); - } - else { - $activeLabels.removeClass(className.active); - $label.addClass(className.active); - } - settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); - } - }, - remove: { - click: function() { - var - $label = $(this).parent() - ; - if( $label.hasClass(className.active) ) { - // remove all selected labels - module.remove.activeLabels(); - } - else { - // remove this label only - module.remove.activeLabels( $label ); - } - } - }, - test: { - toggle: function(event) { - var - toggleBehavior = (module.is.multiple()) - ? module.show - : module.toggle - ; - if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { - return; - } - if( module.determine.eventOnElement(event, toggleBehavior) ) { - event.preventDefault(); - } - }, - touch: function(event) { - module.determine.eventOnElement(event, function() { - if(event.type == 'touchstart') { - module.timer = setTimeout(function() { - module.hide(); - }, settings.delay.touch); - } - else if(event.type == 'touchmove') { - clearTimeout(module.timer); - } - }); - event.stopPropagation(); - }, - hide: function(event) { - if(module.determine.eventInModule(event, module.hide)){ - if(element.id && $(event.target).attr('for') === element.id){ - event.preventDefault(); - } - } - } - }, - select: { - mutation: function(mutations) { - module.debug(' removing selected option', removedValue); - newValue = module.remove.arrayValue(removedValue, values); - module.remove.optionValue(removedValue); - } - else { - module.verbose('Removing from delimited values', removedValue); - newValue = module.remove.arrayValue(removedValue, values); - newValue = newValue.join(settings.delimiter); - } - if(settings.fireOnInit === false && module.is.initialLoad()) { - module.verbose('No callback on initial load', settings.onRemove); - } - else { - settings.onRemove.call(element, removedValue, removedText, $removedItem); - } - module.set.value(newValue, removedText, $removedItem); - module.check.maxSelections(); - }, - arrayValue: function(removedValue, values) { - if( !Array.isArray(values) ) { - values = [values]; - } - values = $.grep(values, function(value){ - return (removedValue != value); - }); - module.verbose('Removed value from delimited string', removedValue, values); - return values; - }, - label: function(value, shouldAnimate) { - var - $labels = $module.find(selector.label), - $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') - ; - module.verbose('Removing label', $removedLabel); - $removedLabel.remove(); - }, - activeLabels: function($activeLabels) { - $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); - module.verbose('Removing active label selections', $activeLabels); - module.remove.labels($activeLabels); - }, - labels: function($labels) { - $labels = $labels || $module.find(selector.label); - module.verbose('Removing labels', $labels); - $labels - .each(function(){ - var - $label = $(this), - value = $label.data(metadata.value), - stringValue = (value !== undefined) - ? String(value) - : value, - isUserValue = module.is.userValue(stringValue) - ; - if(settings.onLabelRemove.call($label, value) === false) { - module.debug('Label remove callback cancelled removal'); - return; - } - module.remove.message(); - if(isUserValue) { - module.remove.value(stringValue); - module.remove.label(stringValue); - } - else { - // selected will also remove label - module.remove.selected(stringValue); - } - }) - ; - }, - tabbable: function() { - if( module.is.searchSelection() ) { - module.debug('Searchable dropdown initialized'); - $search - .removeAttr('tabindex') - ; - $menu - .removeAttr('tabindex') - ; - } - else { - module.debug('Simple selection dropdown initialized'); - $module - .removeAttr('tabindex') - ; - $menu - .removeAttr('tabindex') - ; - } - }, - diacritics: function(text) { - return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; - } - }, - - has: { - menuSearch: function() { - return (module.has.search() && $search.closest($menu).length > 0); - }, - clearItem: function() { - return ($clear.length > 0); - }, - search: function() { - return ($search.length > 0); - }, - sizer: function() { - return ($sizer.length > 0); - }, - selectInput: function() { - return ( $input.is('select') ); - }, - minCharacters: function(searchTerm) { - if(settings.minCharacters && !iconClicked) { - searchTerm = (searchTerm !== undefined) - ? String(searchTerm) - : String(module.get.query()) - ; - return (searchTerm.length >= settings.minCharacters); - } - iconClicked=false; - return true; - }, - firstLetter: function($item, letter) { - var - text, - firstLetter - ; - if(!$item || $item.length === 0 || typeof letter !== 'string') { - return false; - } - text = module.get.choiceText($item, false); - letter = letter.toLowerCase(); - firstLetter = String(text).charAt(0).toLowerCase(); - return (letter == firstLetter); - }, - input: function() { - return ($input.length > 0); - }, - items: function() { - return ($item.length > 0); - }, - menu: function() { - return ($menu.length > 0); - }, - message: function() { - return ($menu.children(selector.message).length !== 0); - }, - label: function(value) { - var - escapedValue = module.escape.value(value), - $labels = $module.find(selector.label) - ; - if(settings.ignoreCase) { - escapedValue = escapedValue.toLowerCase(); - } - return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); - }, - maxSelections: function() { - return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); - }, - allResultsFiltered: function() { - var - $normalResults = $item.not(selector.addition) - ; - return ($normalResults.filter(selector.unselectable).length === $normalResults.length); - }, - userSuggestion: function() { - return ($menu.children(selector.addition).length > 0); - }, - query: function() { - return (module.get.query() !== ''); - }, - value: function(value) { - return (settings.ignoreCase) - ? module.has.valueIgnoringCase(value) - : module.has.valueMatchingCase(value) - ; - }, - valueMatchingCase: function(value) { - var - values = module.get.values(), - hasValue = Array.isArray(values) - ? values && ($.inArray(value, values) !== -1) - : (values == value) - ; - return (hasValue) - ? true - : false - ; - }, - valueIgnoringCase: function(value) { - var - values = module.get.values(), - hasValue = false - ; - if(!Array.isArray(values)) { - values = [values]; - } - $.each(values, function(index, existingValue) { - if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { - hasValue = true; - return false; - } - }); - return hasValue; - } - }, - - is: { - active: function() { - return $module.hasClass(className.active); - }, - animatingInward: function() { - return $menu.transition('is inward'); - }, - animatingOutward: function() { - return $menu.transition('is outward'); - }, - bubbledLabelClick: function(event) { - return $(event.target).is('select, input') && $module.closest('label').length > 0; - }, - bubbledIconClick: function(event) { - return $(event.target).closest($icon).length > 0; - }, - alreadySetup: function() { - return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); - }, - animating: function($subMenu) { - return ($subMenu) - ? $subMenu.transition && $subMenu.transition('is animating') - : $menu.transition && $menu.transition('is animating') - ; - }, - leftward: function($subMenu) { - var $selectedMenu = $subMenu || $menu; - return $selectedMenu.hasClass(className.leftward); - }, - clearable: function() { - return ($module.hasClass(className.clearable) || settings.clearable); - }, - disabled: function() { - return $module.hasClass(className.disabled); - }, - focused: function() { - return (document.activeElement === $module[0]); - }, - focusedOnSearch: function() { - return (document.activeElement === $search[0]); - }, - allFiltered: function() { - return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); - }, - hidden: function($subMenu) { - return !module.is.visible($subMenu); - }, - initialLoad: function() { - return initialLoad; - }, - inObject: function(needle, object) { - var - found = false - ; - $.each(object, function(index, property) { - if(property == needle) { - found = true; - return true; - } - }); - return found; - }, - multiple: function() { - return $module.hasClass(className.multiple); - }, - remote: function() { - return settings.apiSettings && module.can.useAPI(); - }, - single: function() { - return !module.is.multiple(); - }, - selectMutation: function(mutations) { - var - selectChanged = false - ; - $.each(mutations, function(index, mutation) { - if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { - selectChanged = true; - return false; - } - }); - return selectChanged; - }, - search: function() { - return $module.hasClass(className.search); - }, - searchSelection: function() { - return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); - }, - selection: function() { - return $module.hasClass(className.selection); - }, - userValue: function(value) { - return ($.inArray(value, module.get.userValues()) !== -1); - }, - upward: function($menu) { - var $element = $menu || $module; - return $element.hasClass(className.upward); - }, - visible: function($subMenu) { - return ($subMenu) - ? $subMenu.hasClass(className.visible) - : $menu.hasClass(className.visible) - ; - }, - verticallyScrollableContext: function() { - var - overflowY = ($context.get(0) !== window) - ? $context.css('overflow-y') - : false - ; - return (overflowY == 'auto' || overflowY == 'scroll'); - }, - horizontallyScrollableContext: function() { - var - overflowX = ($context.get(0) !== window) - ? $context.css('overflow-X') - : false - ; - return (overflowX == 'auto' || overflowX == 'scroll'); - } - }, - - can: { - activate: function($item) { - if(settings.useLabels) { - return true; - } - if(!module.has.maxSelections()) { - return true; - } - if(module.has.maxSelections() && $item.hasClass(className.active)) { - return true; - } - return false; - }, - openDownward: function($subMenu) { - var - $currentMenu = $subMenu || $menu, - canOpenDownward = true, - onScreen = {}, - calculations - ; - $currentMenu - .addClass(className.loading) - ; - calculations = { - context: { - offset : ($context.get(0) === window) - ? { top: 0, left: 0} - : $context.offset(), - scrollTop : $context.scrollTop(), - height : $context.outerHeight() - }, - menu : { - offset: $currentMenu.offset(), - height: $currentMenu.outerHeight() - } - }; - if(module.is.verticallyScrollableContext()) { - calculations.menu.offset.top += calculations.context.scrollTop; - } - onScreen = { - above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, - below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height - }; - if(onScreen.below) { - module.verbose('Dropdown can fit in context downward', onScreen); - canOpenDownward = true; - } - else if(!onScreen.below && !onScreen.above) { - module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); - canOpenDownward = true; - } - else { - module.verbose('Dropdown cannot fit below, opening upward', onScreen); - canOpenDownward = false; - } - $currentMenu.removeClass(className.loading); - return canOpenDownward; - }, - openRightward: function($subMenu) { - var - $currentMenu = $subMenu || $menu, - canOpenRightward = true, - isOffscreenRight = false, - calculations - ; - $currentMenu - .addClass(className.loading) - ; - calculations = { - context: { - offset : ($context.get(0) === window) - ? { top: 0, left: 0} - : $context.offset(), - scrollLeft : $context.scrollLeft(), - width : $context.outerWidth() - }, - menu: { - offset : $currentMenu.offset(), - width : $currentMenu.outerWidth() - } - }; - if(module.is.horizontallyScrollableContext()) { - calculations.menu.offset.left += calculations.context.scrollLeft; - } - isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); - if(isOffscreenRight) { - module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); - canOpenRightward = false; - } - $currentMenu.removeClass(className.loading); - return canOpenRightward; - }, - click: function() { - return (hasTouch || settings.on == 'click'); - }, - extendSelect: function() { - return settings.allowAdditions || settings.apiSettings; - }, - show: function() { - return !module.is.disabled() && (module.has.items() || module.has.message()); - }, - useAPI: function() { - return $.fn.api !== undefined; - } - }, - - animate: { - show: function(callback, $subMenu) { - var - $currentMenu = $subMenu || $menu, - start = ($subMenu) - ? function() {} - : function() { - module.hideSubMenus(); - module.hideOthers(); - module.set.active(); - }, - transition - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.verbose('Doing menu show animation', $currentMenu); - module.set.direction($subMenu); - transition = module.get.transition($subMenu); - if( module.is.selection() ) { - module.set.scrollPosition(module.get.selectedItem(), true); - } - if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { - if(transition == 'none') { - start(); - $currentMenu.transition('show'); - callback.call(element); - } - else if($.fn.transition !== undefined && $module.transition('is supported')) { - $currentMenu - .transition({ - animation : transition + ' in', - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - queue : true, - onStart : start, - onComplete : function() { - callback.call(element); - } - }) - ; - } - else { - module.error(error.noTransition, transition); - } - } - }, - hide: function(callback, $subMenu) { - var - $currentMenu = $subMenu || $menu, - start = ($subMenu) - ? function() {} - : function() { - if( module.can.click() ) { - module.unbind.intent(); - } - module.remove.active(); - }, - transition = module.get.transition($subMenu) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { - module.verbose('Doing menu hide animation', $currentMenu); - - if(transition == 'none') { - start(); - $currentMenu.transition('hide'); - callback.call(element); - } - else if($.fn.transition !== undefined && $module.transition('is supported')) { - $currentMenu - .transition({ - animation : transition + ' out', - duration : settings.duration, - debug : settings.debug, - verbose : settings.verbose, - queue : false, - onStart : start, - onComplete : function() { - callback.call(element); - } - }) - ; - } - else { - module.error(error.transition); - } - } - } - }, - - hideAndClear: function() { - module.remove.searchTerm(); - if( module.has.maxSelections() ) { - return; - } - if(module.has.search()) { - module.hide(function() { - module.remove.filteredItem(); - }); - } - else { - module.hide(); - } - }, - - delay: { - show: function() { - module.verbose('Delaying show event to ensure user intent'); - clearTimeout(module.timer); - module.timer = setTimeout(module.show, settings.delay.show); - }, - hide: function() { - module.verbose('Delaying hide event to ensure user intent'); - clearTimeout(module.timer); - module.timer = setTimeout(module.hide, settings.delay.hide); - } - }, - - escape: { - value: function(value) { - var - multipleValues = Array.isArray(value), - stringValue = (typeof value === 'string'), - isUnparsable = (!stringValue && !multipleValues), - hasQuotes = (stringValue && value.search(regExp.quote) !== -1), - values = [] - ; - if(isUnparsable || !hasQuotes) { - return value; - } - module.debug('Encoding quote values for use in select', value); - if(multipleValues) { - $.each(value, function(index, value){ - values.push(value.replace(regExp.quote, '"')); - }); - return values; - } - return value.replace(regExp.quote, '"'); - }, - string: function(text) { - text = String(text); - return text.replace(regExp.escape, '\\$&'); - }, - htmlEntities: function(string) { - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - } - }, - - setting: function(name, value) { - module.debug('Changing setting', name, value); - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - if($.isPlainObject(settings[name])) { - $.extend(true, settings[name], value); - } - else { - settings[name] = value; - } - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - module.error(error.method, query); - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - return (returnedValue !== undefined) - ? returnedValue - : $allModules - ; -}; - -$.fn.dropdown.settings = { - - silent : false, - debug : false, - verbose : false, - performance : true, - - on : 'click', // what event should show menu action on item selection - action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) - - values : false, // specify values to use for dropdown - - clearable : false, // whether the value of the dropdown can be cleared - - apiSettings : false, - selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used - minCharacters : 0, // Minimum characters required to trigger API call - - filterRemoteData : false, // Whether API results should be filtered after being returned for query term - saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh - - throttle : 200, // How long to wait after last user input to search remotely - - context : window, // Context to use when determining if on screen - direction : 'auto', // Whether dropdown should always open in one direction - keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing - - match : 'both', // what to match against with search selection (both, text, or label) - fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) - ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) - hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) - - placeholder : 'auto', // whether to convert blank the values will be delimited with this character - - showOnFocus : true, // show menu on focus - allowReselection : false, // whether current value should trigger callbacks when reselected - allowTab : true, // add tabindex to element - allowCategorySelection : false, // allow elements with sub-menus to be selected - - fireOnInit : false, // Whether callbacks should fire when initializing dropdown values - - transition : 'auto', // auto transition will slide down or up based on direction - duration : 200, // duration of transition - - glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width - - headerDivider : true, // whether option headers should have an additional divider line underneath when converted from requires multiple property to be set to correctly preserve multiple values', - method : 'The method you called is not defined.', - noAPI : 'The API module is required to load resources remotely', - noStorage : 'Saving remote data requires session storage', - noTransition : 'This module requires ui transitions ', - noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including as a polyfill.' - }, - - regExp : { - escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, - quote : /"/g - }, - - metadata : { - defaultText : 'defaultText', - defaultValue : 'defaultValue', - placeholderText : 'placeholder', - text : 'text', - value : 'value' - }, - - // property names for remote query - fields: { - remoteValues : 'results', // grouping for api results - values : 'values', // grouping for all dropdown values - disabled : 'disabled', // whether value should be disabled - name : 'name', // displayed dropdown text - value : 'value', // actual dropdown value - text : 'text', // displayed text when selected - type : 'type', // type of dropdown element - image : 'image', // optional image path - imageClass : 'imageClass', // optional individual class for image - icon : 'icon', // optional icon name - iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) - class : 'class', // optional individual class for item/header - divider : 'divider' // optional divider append for group headers - }, - - keys : { - backspace : 8, - delimiter : 188, // comma - deleteKey : 46, - enter : 13, - escape : 27, - pageUp : 33, - pageDown : 34, - leftArrow : 37, - upArrow : 38, - rightArrow : 39, - downArrow : 40 - }, - - selector : { - addition : '.addition', - divider : '.divider, .header', - dropdown : '.ui.dropdown', - hidden : '.hidden', - icon : '> .dropdown.icon', - input : '> input[type="hidden"], > select', - item : '.item', - label : '> .label', - remove : '> .label > .delete.icon', - siblingLabel : '.label', - menu : '.menu', - message : '.message', - menuIcon : '.dropdown.icon', - search : 'input.search, .menu > .search > input, .menu input.search', - sizer : '> input.sizer', - text : '> .text:not(.icon)', - unselectable : '.disabled, .filtered', - clearIcon : '> .remove.icon' - }, - - className : { - active : 'active', - addition : 'addition', - animating : 'animating', - disabled : 'disabled', - empty : 'empty', - dropdown : 'ui dropdown', - filtered : 'filtered', - hidden : 'hidden transition', - icon : 'icon', - image : 'image', - item : 'item', - label : 'ui label', - loading : 'loading', - menu : 'menu', - message : 'message', - multiple : 'multiple', - placeholder : 'default', - sizer : 'sizer', - search : 'search', - selected : 'selected', - selection : 'selection', - upward : 'upward', - leftward : 'left', - visible : 'visible', - clearable : 'clearable', - noselection : 'noselection', - delete : 'delete', - header : 'header', - divider : 'divider', - groupIcon : '', - unfilterable : 'unfilterable' - } - -}; - -/* Templates */ -$.fn.dropdown.settings.templates = { - deQuote: function(string) { - return String(string).replace(/"/g,""); - }, - escape: function(string, preserveHTML) { - if (preserveHTML){ - return string; - } - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - }, - // generates dropdown from select values - dropdown: function(select, fields, preserveHTML, className) { - var - placeholder = select.placeholder || false, - html = '', - escape = $.fn.dropdown.settings.templates.escape - ; - html += ''; - if(placeholder) { - html += '
' + escape(placeholder,preserveHTML) + '
'; - } - else { - html += '
'; - } - html += '
'; - html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); - html += '
'; - return html; - }, - - // generates just menu from select - menu: function(response, fields, preserveHTML, className) { - var - values = response[fields.values] || [], - html = '', - escape = $.fn.dropdown.settings.templates.escape, - deQuote = $.fn.dropdown.settings.templates.deQuote - ; - $.each(values, function(index, option) { - var - itemType = (option[fields.type]) - ? option[fields.type] - : 'item' - ; - - if( itemType === 'item' ) { - var - maybeText = (option[fields.text]) - ? ' data-text="' + deQuote(option[fields.text]) + '"' - : '', - maybeDisabled = (option[fields.disabled]) - ? className.disabled+' ' - : '' - ; - html += '
'; - if(option[fields.image]) { - html += ''; - } - if(option[fields.icon]) { - html += ''; - } - html += escape(option[fields.name] || '', preserveHTML); - html += '
'; - } else if (itemType === 'header') { - var groupName = escape(option[fields.name] || '', preserveHTML), - groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon - ; - if(groupName !== '' || groupIcon !== '') { - html += '
'; - if (groupIcon !== '') { - html += ''; - } - html += groupName; - html += '
'; - } - if(option[fields.divider]){ - html += '
'; - } - } - }); - return html; - }, - - // generates label for multiselect - label: function(value, text, preserveHTML, className) { - var - escape = $.fn.dropdown.settings.templates.escape; - return escape(text,preserveHTML) + ''; - }, - - - // generates messages like "No results" - message: function(message) { - return message; - }, - - // generates user addition to selection menu - addition: function(choice) { - return choice; - } - -}; - -})( jQuery, window, document ); diff --git a/web_src/js/vendor/semanticdropdown.js b/web_src/js/vendor/semanticdropdown.js new file mode 100644 index 0000000000..c9144adf53 --- /dev/null +++ b/web_src/js/vendor/semanticdropdown.js @@ -0,0 +1,4300 @@ +/*! + * # Fomantic-UI - Dropdown + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +/* + * Copyright 2019 The Gitea Authors + * Released under the MIT license + * http://opensource.org/licenses/MIT + * This version has been modified by Gitea to improve accessibility. + */ + +;(function ($, window, document, undefined) { + +'use strict'; + +$.isFunction = $.isFunction || function(obj) { + return typeof obj === "function" && typeof obj.nodeType !== "number"; +}; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.dropdown = function(parameters) { + var + $allModules = $(this), + $document = $(document), + + moduleSelector = $allModules.selector || '', + + hasTouch = ('ontouchstart' in document.documentElement), + clickEvent = hasTouch + ? 'touchstart' + : 'click', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + lastAriaID = 1, + returnedValue + ; + + $allModules + .each(function(elementIndex) { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.dropdown.settings, parameters) + : $.extend({}, $.fn.dropdown.settings), + + className = settings.className, + message = settings.message, + fields = settings.fields, + keys = settings.keys, + metadata = settings.metadata, + namespace = settings.namespace, + regExp = settings.regExp, + selector = settings.selector, + error = settings.error, + templates = settings.templates, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + $module = $(this), + $context = $(settings.context), + $text = $module.find(selector.text), + $search = $module.find(selector.search), + $sizer = $module.find(selector.sizer), + $input = $module.find(selector.input), + $icon = $module.find(selector.icon), + $clear = $module.find(selector.clearIcon), + + $combo = ($module.prev().find(selector.text).length > 0) + ? $module.prev().find(selector.text) + : $module.prev(), + + $menu = $module.children(selector.menu), + $item = $menu.find(selector.item), + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), + + activated = false, + itemActivated = false, + internalChange = false, + iconClicked = false, + element = this, + instance = $module.data(moduleNamespace), + + selectActionActive, + initialLoad, + pageLostFocus, + willRefocus, + elementNamespace, + id, + selectObserver, + menuObserver, + module + ; + + module = { + + initialize: function() { + module.debug('Initializing dropdown', settings); + + if( module.is.alreadySetup() ) { + module.setup.reference(); + } + else { + if (settings.ignoreDiacritics && !String.prototype.normalize) { + settings.ignoreDiacritics = false; + module.error(error.noNormalize, element); + } + + module.setup.layout(); + + if(settings.values) { + module.change.values(settings.values); + } + + module.refreshData(); + + module.save.defaults(); + module.restore.selected(); + + module.create.id(); + module.bind.events(); + + module.observeChanges(); + module.instantiate(); + + module.aria.setup(); + } + + }, + + instantiate: function() { + module.verbose('Storing instance of dropdown', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + destroy: function() { + module.verbose('Destroying previous dropdown', $module); + module.remove.tabbable(); + module.remove.active(); + $menu.transition('stop all'); + $menu.removeClass(className.visible).addClass(className.hidden); + $module + .off(eventNamespace) + .removeData(moduleNamespace) + ; + $menu + .off(eventNamespace) + ; + $document + .off(elementNamespace) + ; + module.disconnect.menuObserver(); + module.disconnect.selectObserver(); + }, + + observeChanges: function() { + if('MutationObserver' in window) { + selectObserver = new MutationObserver(module.event.select.mutation); + menuObserver = new MutationObserver(module.event.menu.mutation); + module.debug('Setting up mutation observer', selectObserver, menuObserver); + module.observe.select(); + module.observe.menu(); + } + }, + + disconnect: { + menuObserver: function() { + if(menuObserver) { + menuObserver.disconnect(); + } + }, + selectObserver: function() { + if(selectObserver) { + selectObserver.disconnect(); + } + } + }, + observe: { + select: function() { + if(module.has.input() && selectObserver) { + selectObserver.observe($module[0], { + childList : true, + subtree : true + }); + } + }, + menu: function() { + if(module.has.menu() && menuObserver) { + menuObserver.observe($menu[0], { + childList : true, + subtree : true + }); + } + } + }, + + create: { + id: function() { + id = (Math.random().toString(16) + '000000000').substr(2, 8); + elementNamespace = '.' + id; + module.verbose('Creating unique id for element', id); + }, + userChoice: function(values) { + var + $userChoices, + $userChoice, + isUserValue, + html + ; + values = values || module.get.userValues(); + if(!values) { + return false; + } + values = Array.isArray(values) + ? values + : [values] + ; + $.each(values, function(index, value) { + if(module.get.item(value) === false) { + html = settings.templates.addition( module.add.variables(message.addResult, value) ); + $userChoice = $('
') + .html(html) + .attr('data-' + metadata.value, value) + .attr('data-' + metadata.text, value) + .addClass(className.addition) + .addClass(className.item) + ; + if(settings.hideAdditions) { + $userChoice.addClass(className.hidden); + } + $userChoices = ($userChoices === undefined) + ? $userChoice + : $userChoices.add($userChoice) + ; + module.verbose('Creating user choices for value', value, $userChoice); + } + }); + return $userChoices; + }, + userLabels: function(value) { + var + userValues = module.get.userValues() + ; + if(userValues) { + module.debug('Adding user labels', userValues); + $.each(userValues, function(index, value) { + module.verbose('Adding custom user value'); + module.add.label(value, value); + }); + } + }, + menu: function() { + $menu = $('
') + .addClass(className.menu) + .appendTo($module) + ; + }, + sizer: function() { + $sizer = $('') + .addClass(className.sizer) + .insertAfter($search) + ; + } + }, + + search: function(query) { + query = (query !== undefined) + ? query + : module.get.query() + ; + module.verbose('Searching for query', query); + if(module.has.minCharacters(query)) { + module.filter(query); + } + else { + module.hide(null,true); + } + }, + + select: { + firstUnfiltered: function() { + module.verbose('Selecting first non-filtered element'); + module.remove.selectedItem(); + $item + .not(selector.unselectable) + .not(selector.addition + selector.hidden) + .eq(0) + .addClass(className.selected) + ; + }, + nextAvailable: function($selected) { + $selected = $selected.eq(0); + var + $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), + $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), + hasNext = ($nextAvailable.length > 0) + ; + if(hasNext) { + module.verbose('Moving selection to', $nextAvailable); + $nextAvailable.addClass(className.selected); + } + else { + module.verbose('Moving selection to', $prevAvailable); + $prevAvailable.addClass(className.selected); + } + } + }, + + aria: { + setup: function() { + var role = module.aria.guessRole(); + if( role !== 'menu' ) { + return; + } + $module.attr('aria-busy', 'true'); + $module.attr('role', 'menu'); + $module.attr('aria-haspopup', 'menu'); + $module.attr('aria-expanded', 'false'); + $menu.find('.divider').attr('role', 'separator'); + $item.attr('role', 'menuitem'); + $item.each(function (index, item) { + if( !item.id ) { + item.id = module.aria.nextID('menuitem'); + } + }); + $text = $module + .find('> .text') + .eq(0) + ; + if( $module.data('content') ) { + $text.attr('aria-hidden'); + $module.attr('aria-label', $module.data('content')); + } + else { + $text.attr('id', module.aria.nextID('menutext')); + $module.attr('aria-labelledby', $text.attr('id')); + } + $module.attr('aria-busy', 'false'); + }, + nextID: function(prefix) { + var nextID; + do { + nextID = prefix + '_' + lastAriaID++; + } while( document.getElementById(nextID) ); + return nextID; + }, + setExpanded: function(expanded) { + if( $module.attr('aria-haspopup') ) { + $module.attr('aria-expanded', expanded); + } + }, + refreshDescendant: function() { + if( $module.attr('aria-haspopup') !== 'menu' ) { + return; + } + var + $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), + $activeItem = $menu.children('.' + className.active).eq(0), + $selectedItem = ($currentlySelected.length > 0) + ? $currentlySelected + : $activeItem + ; + if( $selectedItem ) { + $module.attr('aria-activedescendant', $selectedItem.attr('id')); + } + else { + module.aria.removeDescendant(); + } + }, + removeDescendant: function() { + if( $module.attr('aria-haspopup') == 'menu' ) { + $module.removeAttr('aria-activedescendant'); + } + }, + guessRole: function() { + var + isIcon = $module.hasClass('icon'), + hasSearch = module.has.search(), + hasInput = ($input.length > 0), + isMultiple = module.is.multiple() + ; + if ( !isIcon && !hasSearch && !hasInput && !isMultiple ) { + return 'menu'; + } + return 'unknown'; + } + }, + + setup: { + api: function() { + var + apiSettings = { + debug : settings.debug, + urlData : { + value : module.get.value(), + query : module.get.query() + }, + on : false + } + ; + module.verbose('First request, initializing API'); + $module + .api(apiSettings) + ; + }, + layout: function() { + if( $module.is('select') ) { + module.setup.select(); + module.setup.returnedObject(); + } + if( !module.has.menu() ) { + module.create.menu(); + } + if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { + module.verbose('Adding clear icon'); + $clear = $('') + .addClass('remove icon') + .insertBefore($text) + ; + } + if( module.is.search() && !module.has.search() ) { + module.verbose('Adding search input'); + $search = $('') + .addClass(className.search) + .prop('autocomplete', 'off') + .insertBefore($text) + ; + } + if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { + module.create.sizer(); + } + if(settings.allowTab) { + module.set.tabbable(); + } + $item.attr('tabindex', '-1'); + }, + select: function() { + var + selectValues = module.get.selectValues() + ; + module.debug('Dropdown initialized on a select', selectValues); + if( $module.is('select') ) { + $input = $module; + } + // see if select is placed correctly already + if($input.parent(selector.dropdown).length > 0) { + module.debug('UI dropdown already exists. Creating dropdown menu only'); + $module = $input.closest(selector.dropdown); + if( !module.has.menu() ) { + module.create.menu(); + } + $menu = $module.children(selector.menu); + module.setup.menu(selectValues); + } + else { + module.debug('Creating entire dropdown from select'); + $module = $('
') + .attr('class', $input.attr('class') ) + .addClass(className.selection) + .addClass(className.dropdown) + .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) + .insertBefore($input) + ; + if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { + module.error(error.missingMultiple); + $input.prop('multiple', true); + } + if($input.is('[multiple]')) { + module.set.multiple(); + } + if ($input.prop('disabled')) { + module.debug('Disabling dropdown'); + $module.addClass(className.disabled); + } + $input + .removeAttr('required') + .removeAttr('class') + .detach() + .prependTo($module) + ; + } + module.refresh(); + }, + menu: function(values) { + $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); + }, + reference: function() { + module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); + // replace module reference + $module = $module.parent(selector.dropdown); + instance = $module.data(moduleNamespace); + element = $module.get(0); + module.refresh(); + module.setup.returnedObject(); + }, + returnedObject: function() { + var + $firstModules = $allModules.slice(0, elementIndex), + $lastModules = $allModules.slice(elementIndex + 1) + ; + // adjust all modules to use correct reference + $allModules = $firstModules.add($module).add($lastModules); + } + }, + + refresh: function() { + module.refreshSelectors(); + module.refreshData(); + }, + + refreshItems: function() { + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); + }, + + refreshSelectors: function() { + module.verbose('Refreshing selector cache'); + $text = $module.find(selector.text); + $search = $module.find(selector.search); + $input = $module.find(selector.input); + $icon = $module.find(selector.icon); + $combo = ($module.prev().find(selector.text).length > 0) + ? $module.prev().find(selector.text) + : $module.prev() + ; + $menu = $module.children(selector.menu); + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); + }, + + refreshData: function() { + module.verbose('Refreshing cached metadata'); + $item + .removeData(metadata.text) + .removeData(metadata.value) + ; + }, + + clearData: function() { + module.verbose('Clearing metadata'); + $item + .removeData(metadata.text) + .removeData(metadata.value) + ; + $module + .removeData(metadata.defaultText) + .removeData(metadata.defaultValue) + .removeData(metadata.placeholderText) + ; + }, + + toggle: function() { + module.verbose('Toggling menu visibility'); + if( !module.is.active() ) { + module.show(); + } + else { + module.hide(); + } + }, + + show: function(callback, preventFocus) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if(!module.can.show() && module.is.remote()) { + module.debug('No API results retrieved, searching before show'); + module.queryRemote(module.get.query(), module.show); + } + if( module.can.show() && !module.is.active() ) { + module.debug('Showing dropdown'); + if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { + module.remove.message(); + } + if(module.is.allFiltered()) { + return true; + } + if(settings.onShow.call(element) !== false) { + module.aria.setExpanded(true); + module.aria.refreshDescendant(); + module.animate.show(function() { + if( module.can.click() ) { + module.bind.intent(); + } + if(module.has.search() && !preventFocus) { + module.focusSearch(); + } + module.set.visible(); + callback.call(element); + }); + } + } + }, + + hide: function(callback, preventBlur) { + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( module.is.active() && !module.is.animatingOutward() ) { + module.debug('Hiding dropdown'); + if(settings.onHide.call(element) !== false) { + module.aria.setExpanded(false); + module.aria.removeDescendant(); + module.animate.hide(function() { + module.remove.visible(); + // hidding search focus + if ( module.is.focusedOnSearch() && preventBlur !== true ) { + $search.blur(); + } + callback.call(element); + }); + } + } else if( module.can.click() ) { + module.unbind.intent(); + } + }, + + hideOthers: function() { + module.verbose('Finding other dropdowns to hide'); + $allModules + .not($module) + .has(selector.menu + '.' + className.visible) + .dropdown('hide') + ; + }, + + hideMenu: function() { + module.verbose('Hiding menu instantaneously'); + module.remove.active(); + module.remove.visible(); + $menu.transition('hide'); + }, + + hideSubMenus: function() { + var + $subMenus = $menu.children(selector.item).find(selector.menu) + ; + module.verbose('Hiding sub menus', $subMenus); + $subMenus.transition('hide'); + }, + + bind: { + events: function() { + module.bind.keyboardEvents(); + module.bind.inputEvents(); + module.bind.mouseEvents(); + }, + keyboardEvents: function() { + module.verbose('Binding keyboard events'); + $module + .on('keydown' + eventNamespace, module.event.keydown) + ; + if( module.has.search() ) { + $module + .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) + ; + } + if( module.is.multiple() ) { + $document + .on('keydown' + elementNamespace, module.event.document.keydown) + ; + } + }, + inputEvents: function() { + module.verbose('Binding input change events'); + $module + .on('change' + eventNamespace, selector.input, module.event.change) + ; + }, + mouseEvents: function() { + module.verbose('Binding mouse events'); + if(module.is.multiple()) { + $module + .on(clickEvent + eventNamespace, selector.label, module.event.label.click) + .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) + ; + } + if( module.is.searchSelection() ) { + $module + .on('mousedown' + eventNamespace, module.event.mousedown) + .on('mouseup' + eventNamespace, module.event.mouseup) + .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) + .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) + .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) + .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) + .on('focus' + eventNamespace, selector.search, module.event.search.focus) + .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) + .on('blur' + eventNamespace, selector.search, module.event.search.blur) + .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) + ; + if(module.is.multiple()) { + $module + .on(clickEvent + eventNamespace, module.event.click) + ; + } + } + else { + if(settings.on == 'click') { + $module + .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) + .on(clickEvent + eventNamespace, module.event.test.toggle) + ; + } + else if(settings.on == 'hover') { + $module + .on('mouseenter' + eventNamespace, module.delay.show) + .on('mouseleave' + eventNamespace, module.delay.hide) + ; + } + else { + $module + .on(settings.on + eventNamespace, module.toggle) + ; + } + $module + .on('mousedown' + eventNamespace, module.event.mousedown) + .on('mouseup' + eventNamespace, module.event.mouseup) + .on('focus' + eventNamespace, module.event.focus) + .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) + ; + if(module.has.menuSearch() ) { + $module + .on('blur' + eventNamespace, selector.search, module.event.search.blur) + ; + } + else { + $module + .on('blur' + eventNamespace, module.event.blur) + ; + } + } + $menu + .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) + .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) + .on('click' + eventNamespace, selector.item, module.event.item.click) + ; + }, + intent: function() { + module.verbose('Binding hide intent event to document'); + if(hasTouch) { + $document + .on('touchstart' + elementNamespace, module.event.test.touch) + .on('touchmove' + elementNamespace, module.event.test.touch) + ; + } + $document + .on(clickEvent + elementNamespace, module.event.test.hide) + ; + } + }, + + unbind: { + intent: function() { + module.verbose('Removing hide intent event from document'); + if(hasTouch) { + $document + .off('touchstart' + elementNamespace) + .off('touchmove' + elementNamespace) + ; + } + $document + .off(clickEvent + elementNamespace) + ; + } + }, + + filter: function(query) { + var + searchTerm = (query !== undefined) + ? query + : module.get.query(), + afterFiltered = function() { + if(module.is.multiple()) { + module.filterActive(); + } + if(query || (!query && module.get.activeItem().length == 0)) { + module.select.firstUnfiltered(); + } + if( module.has.allResultsFiltered() ) { + if( settings.onNoResults.call(element, searchTerm) ) { + if(settings.allowAdditions) { + if(settings.hideAdditions) { + module.verbose('User addition with no menu, setting empty style'); + module.set.empty(); + module.hideMenu(); + } + } + else { + module.verbose('All items filtered, showing message', searchTerm); + module.add.message(message.noResults); + } + } + else { + module.verbose('All items filtered, hiding dropdown', searchTerm); + module.hideMenu(); + } + } + else { + module.remove.empty(); + module.remove.message(); + } + if(settings.allowAdditions) { + module.add.userSuggestion(module.escape.htmlEntities(query)); + } + if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { + module.show(); + } + } + ; + if(settings.useLabels && module.has.maxSelections()) { + return; + } + if(settings.apiSettings) { + if( module.can.useAPI() ) { + module.queryRemote(searchTerm, function() { + if(settings.filterRemoteData) { + module.filterItems(searchTerm); + } + var preSelected = $input.val(); + if(!Array.isArray(preSelected)) { + preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; + } + $.each(preSelected,function(index,value){ + $item.filter('[data-value="'+value+'"]') + .addClass(className.filtered) + ; + }); + afterFiltered(); + }); + } + else { + module.error(error.noAPI); + } + } + else { + module.filterItems(searchTerm); + afterFiltered(); + } + }, + + queryRemote: function(query, callback) { + var + apiSettings = { + errorDuration : false, + cache : 'local', + throttle : settings.throttle, + urlData : { + query: query + }, + onError: function() { + module.add.message(message.serverError); + callback(); + }, + onFailure: function() { + module.add.message(message.serverError); + callback(); + }, + onSuccess : function(response) { + var + values = response[fields.remoteValues] + ; + if (!Array.isArray(values)){ + values = []; + } + module.remove.message(); + module.setup.menu({ + values: values + }); + + if(values.length===0 && !settings.allowAdditions) { + module.add.message(message.noResults); + } + callback(); + } + } + ; + if( !$module.api('get request') ) { + module.setup.api(); + } + apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); + $module + .api('setting', apiSettings) + .api('query') + ; + }, + + filterItems: function(query) { + var + searchTerm = module.remove.diacritics(query !== undefined + ? query + : module.get.query() + ), + results = null, + escapedTerm = module.escape.string(searchTerm), + regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', + beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) + ; + // avoid loop if we're matching nothing + if( module.has.query() ) { + results = []; + + module.verbose('Searching for matching values', searchTerm); + $item + .each(function(){ + var + $choice = $(this), + text, + value + ; + if($choice.hasClass(className.unfilterable)) { + results.push(this); + return true; + } + if(settings.match === 'both' || settings.match === 'text') { + text = module.remove.diacritics(String(module.get.choiceText($choice, false))); + if(text.search(beginsWithRegExp) !== -1) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { + results.push(this); + return true; + } + } + if(settings.match === 'both' || settings.match === 'value') { + value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); + if(value.search(beginsWithRegExp) !== -1) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { + results.push(this); + return true; + } + else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { + results.push(this); + return true; + } + } + }) + ; + } + module.debug('Showing only matched items', searchTerm); + module.remove.filteredItem(); + if(results) { + $item + .not(results) + .addClass(className.filtered) + ; + } + + if(!module.has.query()) { + $divider + .removeClass(className.hidden); + } else if(settings.hideDividers === true) { + $divider + .addClass(className.hidden); + } else if(settings.hideDividers === 'empty') { + $divider + .removeClass(className.hidden) + .filter(function() { + // First find the last divider in this divider group + // Dividers which are direct siblings are considered a group + var lastDivider = $(this).nextUntil(selector.item); + + return (lastDivider.length ? lastDivider : $(this)) + // Count all non-filtered items until the next divider (or end of the dropdown) + .nextUntil(selector.divider) + .filter(selector.item + ":not(." + className.filtered + ")") + // Hide divider if no items are found + .length === 0; + }) + .addClass(className.hidden); + } + }, + + fuzzySearch: function(query, term) { + var + termLength = term.length, + queryLength = query.length + ; + query = (settings.ignoreSearchCase ? query.toLowerCase() : query); + term = (settings.ignoreSearchCase ? term.toLowerCase() : term); + if(queryLength > termLength) { + return false; + } + if(queryLength === termLength) { + return (query === term); + } + search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { + var + queryCharacter = query.charCodeAt(characterIndex) + ; + while(nextCharacterIndex < termLength) { + if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { + continue search; + } + } + return false; + } + return true; + }, + exactSearch: function (query, term) { + query = (settings.ignoreSearchCase ? query.toLowerCase() : query); + term = (settings.ignoreSearchCase ? term.toLowerCase() : term); + return term.indexOf(query) > -1; + + }, + filterActive: function() { + if(settings.useLabels) { + $item.filter('.' + className.active) + .addClass(className.filtered) + ; + } + }, + + focusSearch: function(skipHandler) { + if( module.has.search() && !module.is.focusedOnSearch() ) { + if(skipHandler) { + $module.off('focus' + eventNamespace, selector.search); + $search.focus(); + $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); + } + else { + $search.focus(); + } + } + }, + + blurSearch: function() { + if( module.has.search() ) { + $search.blur(); + } + }, + + forceSelection: function() { + var + $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), + $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), + $selectedItem = ($currentlySelected.length > 0) + ? $currentlySelected + : $activeItem, + hasSelected = ($selectedItem.length > 0) + ; + if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { + module.debug('Forcing partial selection to selected item', $selectedItem); + $selectedItem[0].click(); + return; + } + else { + module.remove.searchTerm(); + } + }, + + change: { + values: function(values) { + if(!settings.allowAdditions) { + module.clear(); + } + module.debug('Creating dropdown with specified values', values); + module.setup.menu({values: values}); + $.each(values, function(index, item) { + if(item.selected == true) { + module.debug('Setting initial selection to', item[fields.value]); + module.set.selected(item[fields.value]); + if(!module.is.multiple()) { + return false; + } + } + }); + + if(module.has.selectInput()) { + module.disconnect.selectObserver(); + $input.html(''); + $input.append(''); + $.each(values, function(index, item) { + var + value = settings.templates.deQuote(item[fields.value]), + name = settings.templates.escape( + item[fields.name] || '', + settings.preserveHTML + ) + ; + $input.append(''); + }); + module.observe.select(); + } + } + }, + + event: { + change: function() { + if(!internalChange) { + module.debug('Input changed, updating selection'); + module.set.selected(); + } + }, + focus: function() { + if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { + module.show(); + } + }, + blur: function(event) { + pageLostFocus = (document.activeElement === this); + if(!activated && !pageLostFocus) { + module.remove.activeLabel(); + module.hide(); + } + }, + mousedown: function() { + if(module.is.searchSelection()) { + // prevent menu hiding on immediate re-focus + willRefocus = true; + } + else { + // prevents focus callback from occurring on mousedown + activated = true; + } + }, + mouseup: function() { + if(module.is.searchSelection()) { + // prevent menu hiding on immediate re-focus + willRefocus = false; + } + else { + activated = false; + } + }, + click: function(event) { + var + $target = $(event.target) + ; + // focus search + if($target.is($module)) { + if(!module.is.focusedOnSearch()) { + module.focusSearch(); + } + else { + module.show(); + } + } + }, + search: { + focus: function(event) { + activated = true; + if(module.is.multiple()) { + module.remove.activeLabel(); + } + if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { + module.search(); + } + }, + blur: function(event) { + pageLostFocus = (document.activeElement === this); + if(module.is.searchSelection() && !willRefocus) { + if(!itemActivated && !pageLostFocus) { + if(settings.forceSelection) { + module.forceSelection(); + } else if(!settings.allowAdditions){ + module.remove.searchTerm(); + } + module.hide(); + } + } + willRefocus = false; + } + }, + clearIcon: { + click: function(event) { + module.clear(); + if(module.is.searchSelection()) { + module.remove.searchTerm(); + } + module.hide(); + event.stopPropagation(); + } + }, + icon: { + click: function(event) { + iconClicked=true; + if(module.has.search()) { + if(!module.is.active()) { + if(settings.showOnFocus){ + module.focusSearch(); + } else { + module.toggle(); + } + } else { + module.blurSearch(); + } + } else { + module.toggle(); + } + } + }, + text: { + focus: function(event) { + activated = true; + module.focusSearch(); + } + }, + input: function(event) { + if(module.is.multiple() || module.is.searchSelection()) { + module.set.filtered(); + } + clearTimeout(module.timer); + module.timer = setTimeout(module.search, settings.delay.search); + }, + label: { + click: function(event) { + var + $label = $(this), + $labels = $module.find(selector.label), + $activeLabels = $labels.filter('.' + className.active), + $nextActive = $label.nextAll('.' + className.active), + $prevActive = $label.prevAll('.' + className.active), + $range = ($nextActive.length > 0) + ? $label.nextUntil($nextActive).add($activeLabels).add($label) + : $label.prevUntil($prevActive).add($activeLabels).add($label) + ; + if(event.shiftKey) { + $activeLabels.removeClass(className.active); + $range.addClass(className.active); + } + else if(event.ctrlKey) { + $label.toggleClass(className.active); + } + else { + $activeLabels.removeClass(className.active); + $label.addClass(className.active); + } + settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); + } + }, + remove: { + click: function() { + var + $label = $(this).parent() + ; + if( $label.hasClass(className.active) ) { + // remove all selected labels + module.remove.activeLabels(); + } + else { + // remove this label only + module.remove.activeLabels( $label ); + } + } + }, + test: { + toggle: function(event) { + var + toggleBehavior = (module.is.multiple()) + ? module.show + : module.toggle + ; + if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { + return; + } + if( module.determine.eventOnElement(event, toggleBehavior) ) { + event.preventDefault(); + } + }, + touch: function(event) { + module.determine.eventOnElement(event, function() { + if(event.type == 'touchstart') { + module.timer = setTimeout(function() { + module.hide(); + }, settings.delay.touch); + } + else if(event.type == 'touchmove') { + clearTimeout(module.timer); + } + }); + event.stopPropagation(); + }, + hide: function(event) { + if(module.determine.eventInModule(event, module.hide)){ + if(element.id && $(event.target).attr('for') === element.id){ + event.preventDefault(); + } + } + } + }, + select: { + mutation: function(mutations) { + module.debug(' removing selected option', removedValue); + newValue = module.remove.arrayValue(removedValue, values); + module.remove.optionValue(removedValue); + } + else { + module.verbose('Removing from delimited values', removedValue); + newValue = module.remove.arrayValue(removedValue, values); + newValue = newValue.join(settings.delimiter); + } + if(settings.fireOnInit === false && module.is.initialLoad()) { + module.verbose('No callback on initial load', settings.onRemove); + } + else { + settings.onRemove.call(element, removedValue, removedText, $removedItem); + } + module.set.value(newValue, removedText, $removedItem); + module.check.maxSelections(); + }, + arrayValue: function(removedValue, values) { + if( !Array.isArray(values) ) { + values = [values]; + } + values = $.grep(values, function(value){ + return (removedValue != value); + }); + module.verbose('Removed value from delimited string', removedValue, values); + return values; + }, + label: function(value, shouldAnimate) { + var + $labels = $module.find(selector.label), + $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') + ; + module.verbose('Removing label', $removedLabel); + $removedLabel.remove(); + }, + activeLabels: function($activeLabels) { + $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); + module.verbose('Removing active label selections', $activeLabels); + module.remove.labels($activeLabels); + }, + labels: function($labels) { + $labels = $labels || $module.find(selector.label); + module.verbose('Removing labels', $labels); + $labels + .each(function(){ + var + $label = $(this), + value = $label.data(metadata.value), + stringValue = (value !== undefined) + ? String(value) + : value, + isUserValue = module.is.userValue(stringValue) + ; + if(settings.onLabelRemove.call($label, value) === false) { + module.debug('Label remove callback cancelled removal'); + return; + } + module.remove.message(); + if(isUserValue) { + module.remove.value(stringValue); + module.remove.label(stringValue); + } + else { + // selected will also remove label + module.remove.selected(stringValue); + } + }) + ; + }, + tabbable: function() { + if( module.is.searchSelection() ) { + module.debug('Searchable dropdown initialized'); + $search + .removeAttr('tabindex') + ; + $menu + .removeAttr('tabindex') + ; + } + else { + module.debug('Simple selection dropdown initialized'); + $module + .removeAttr('tabindex') + ; + $menu + .removeAttr('tabindex') + ; + } + }, + diacritics: function(text) { + return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; + } + }, + + has: { + menuSearch: function() { + return (module.has.search() && $search.closest($menu).length > 0); + }, + clearItem: function() { + return ($clear.length > 0); + }, + search: function() { + return ($search.length > 0); + }, + sizer: function() { + return ($sizer.length > 0); + }, + selectInput: function() { + return ( $input.is('select') ); + }, + minCharacters: function(searchTerm) { + if(settings.minCharacters && !iconClicked) { + searchTerm = (searchTerm !== undefined) + ? String(searchTerm) + : String(module.get.query()) + ; + return (searchTerm.length >= settings.minCharacters); + } + iconClicked=false; + return true; + }, + firstLetter: function($item, letter) { + var + text, + firstLetter + ; + if(!$item || $item.length === 0 || typeof letter !== 'string') { + return false; + } + text = module.get.choiceText($item, false); + letter = letter.toLowerCase(); + firstLetter = String(text).charAt(0).toLowerCase(); + return (letter == firstLetter); + }, + input: function() { + return ($input.length > 0); + }, + items: function() { + return ($item.length > 0); + }, + menu: function() { + return ($menu.length > 0); + }, + message: function() { + return ($menu.children(selector.message).length !== 0); + }, + label: function(value) { + var + escapedValue = module.escape.value(value), + $labels = $module.find(selector.label) + ; + if(settings.ignoreCase) { + escapedValue = escapedValue.toLowerCase(); + } + return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); + }, + maxSelections: function() { + return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); + }, + allResultsFiltered: function() { + var + $normalResults = $item.not(selector.addition) + ; + return ($normalResults.filter(selector.unselectable).length === $normalResults.length); + }, + userSuggestion: function() { + return ($menu.children(selector.addition).length > 0); + }, + query: function() { + return (module.get.query() !== ''); + }, + value: function(value) { + return (settings.ignoreCase) + ? module.has.valueIgnoringCase(value) + : module.has.valueMatchingCase(value) + ; + }, + valueMatchingCase: function(value) { + var + values = module.get.values(), + hasValue = Array.isArray(values) + ? values && ($.inArray(value, values) !== -1) + : (values == value) + ; + return (hasValue) + ? true + : false + ; + }, + valueIgnoringCase: function(value) { + var + values = module.get.values(), + hasValue = false + ; + if(!Array.isArray(values)) { + values = [values]; + } + $.each(values, function(index, existingValue) { + if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { + hasValue = true; + return false; + } + }); + return hasValue; + } + }, + + is: { + active: function() { + return $module.hasClass(className.active); + }, + animatingInward: function() { + return $menu.transition('is inward'); + }, + animatingOutward: function() { + return $menu.transition('is outward'); + }, + bubbledLabelClick: function(event) { + return $(event.target).is('select, input') && $module.closest('label').length > 0; + }, + bubbledIconClick: function(event) { + return $(event.target).closest($icon).length > 0; + }, + alreadySetup: function() { + return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); + }, + animating: function($subMenu) { + return ($subMenu) + ? $subMenu.transition && $subMenu.transition('is animating') + : $menu.transition && $menu.transition('is animating') + ; + }, + leftward: function($subMenu) { + var $selectedMenu = $subMenu || $menu; + return $selectedMenu.hasClass(className.leftward); + }, + clearable: function() { + return ($module.hasClass(className.clearable) || settings.clearable); + }, + disabled: function() { + return $module.hasClass(className.disabled); + }, + focused: function() { + return (document.activeElement === $module[0]); + }, + focusedOnSearch: function() { + return (document.activeElement === $search[0]); + }, + allFiltered: function() { + return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); + }, + hidden: function($subMenu) { + return !module.is.visible($subMenu); + }, + initialLoad: function() { + return initialLoad; + }, + inObject: function(needle, object) { + var + found = false + ; + $.each(object, function(index, property) { + if(property == needle) { + found = true; + return true; + } + }); + return found; + }, + multiple: function() { + return $module.hasClass(className.multiple); + }, + remote: function() { + return settings.apiSettings && module.can.useAPI(); + }, + single: function() { + return !module.is.multiple(); + }, + selectMutation: function(mutations) { + var + selectChanged = false + ; + $.each(mutations, function(index, mutation) { + if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { + selectChanged = true; + return false; + } + }); + return selectChanged; + }, + search: function() { + return $module.hasClass(className.search); + }, + searchSelection: function() { + return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); + }, + selection: function() { + return $module.hasClass(className.selection); + }, + userValue: function(value) { + return ($.inArray(value, module.get.userValues()) !== -1); + }, + upward: function($menu) { + var $element = $menu || $module; + return $element.hasClass(className.upward); + }, + visible: function($subMenu) { + return ($subMenu) + ? $subMenu.hasClass(className.visible) + : $menu.hasClass(className.visible) + ; + }, + verticallyScrollableContext: function() { + var + overflowY = ($context.get(0) !== window) + ? $context.css('overflow-y') + : false + ; + return (overflowY == 'auto' || overflowY == 'scroll'); + }, + horizontallyScrollableContext: function() { + var + overflowX = ($context.get(0) !== window) + ? $context.css('overflow-X') + : false + ; + return (overflowX == 'auto' || overflowX == 'scroll'); + } + }, + + can: { + activate: function($item) { + if(settings.useLabels) { + return true; + } + if(!module.has.maxSelections()) { + return true; + } + if(module.has.maxSelections() && $item.hasClass(className.active)) { + return true; + } + return false; + }, + openDownward: function($subMenu) { + var + $currentMenu = $subMenu || $menu, + canOpenDownward = true, + onScreen = {}, + calculations + ; + $currentMenu + .addClass(className.loading) + ; + calculations = { + context: { + offset : ($context.get(0) === window) + ? { top: 0, left: 0} + : $context.offset(), + scrollTop : $context.scrollTop(), + height : $context.outerHeight() + }, + menu : { + offset: $currentMenu.offset(), + height: $currentMenu.outerHeight() + } + }; + if(module.is.verticallyScrollableContext()) { + calculations.menu.offset.top += calculations.context.scrollTop; + } + onScreen = { + above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, + below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height + }; + if(onScreen.below) { + module.verbose('Dropdown can fit in context downward', onScreen); + canOpenDownward = true; + } + else if(!onScreen.below && !onScreen.above) { + module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); + canOpenDownward = true; + } + else { + module.verbose('Dropdown cannot fit below, opening upward', onScreen); + canOpenDownward = false; + } + $currentMenu.removeClass(className.loading); + return canOpenDownward; + }, + openRightward: function($subMenu) { + var + $currentMenu = $subMenu || $menu, + canOpenRightward = true, + isOffscreenRight = false, + calculations + ; + $currentMenu + .addClass(className.loading) + ; + calculations = { + context: { + offset : ($context.get(0) === window) + ? { top: 0, left: 0} + : $context.offset(), + scrollLeft : $context.scrollLeft(), + width : $context.outerWidth() + }, + menu: { + offset : $currentMenu.offset(), + width : $currentMenu.outerWidth() + } + }; + if(module.is.horizontallyScrollableContext()) { + calculations.menu.offset.left += calculations.context.scrollLeft; + } + isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); + if(isOffscreenRight) { + module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); + canOpenRightward = false; + } + $currentMenu.removeClass(className.loading); + return canOpenRightward; + }, + click: function() { + return (hasTouch || settings.on == 'click'); + }, + extendSelect: function() { + return settings.allowAdditions || settings.apiSettings; + }, + show: function() { + return !module.is.disabled() && (module.has.items() || module.has.message()); + }, + useAPI: function() { + return $.fn.api !== undefined; + } + }, + + animate: { + show: function(callback, $subMenu) { + var + $currentMenu = $subMenu || $menu, + start = ($subMenu) + ? function() {} + : function() { + module.hideSubMenus(); + module.hideOthers(); + module.set.active(); + }, + transition + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + module.verbose('Doing menu show animation', $currentMenu); + module.set.direction($subMenu); + transition = module.get.transition($subMenu); + if( module.is.selection() ) { + module.set.scrollPosition(module.get.selectedItem(), true); + } + if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { + if(transition == 'none') { + start(); + $currentMenu.transition('show'); + callback.call(element); + } + else if($.fn.transition !== undefined && $module.transition('is supported')) { + $currentMenu + .transition({ + animation : transition + ' in', + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration, + queue : true, + onStart : start, + onComplete : function() { + callback.call(element); + } + }) + ; + } + else { + module.error(error.noTransition, transition); + } + } + }, + hide: function(callback, $subMenu) { + var + $currentMenu = $subMenu || $menu, + start = ($subMenu) + ? function() {} + : function() { + if( module.can.click() ) { + module.unbind.intent(); + } + module.remove.active(); + }, + transition = module.get.transition($subMenu) + ; + callback = $.isFunction(callback) + ? callback + : function(){} + ; + if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { + module.verbose('Doing menu hide animation', $currentMenu); + + if(transition == 'none') { + start(); + $currentMenu.transition('hide'); + callback.call(element); + } + else if($.fn.transition !== undefined && $module.transition('is supported')) { + $currentMenu + .transition({ + animation : transition + ' out', + duration : settings.duration, + debug : settings.debug, + verbose : settings.verbose, + queue : false, + onStart : start, + onComplete : function() { + callback.call(element); + } + }) + ; + } + else { + module.error(error.transition); + } + } + } + }, + + hideAndClear: function() { + module.remove.searchTerm(); + if( module.has.maxSelections() ) { + return; + } + if(module.has.search()) { + module.hide(function() { + module.remove.filteredItem(); + }); + } + else { + module.hide(); + } + }, + + delay: { + show: function() { + module.verbose('Delaying show event to ensure user intent'); + clearTimeout(module.timer); + module.timer = setTimeout(module.show, settings.delay.show); + }, + hide: function() { + module.verbose('Delaying hide event to ensure user intent'); + clearTimeout(module.timer); + module.timer = setTimeout(module.hide, settings.delay.hide); + } + }, + + escape: { + value: function(value) { + var + multipleValues = Array.isArray(value), + stringValue = (typeof value === 'string'), + isUnparsable = (!stringValue && !multipleValues), + hasQuotes = (stringValue && value.search(regExp.quote) !== -1), + values = [] + ; + if(isUnparsable || !hasQuotes) { + return value; + } + module.debug('Encoding quote values for use in select', value); + if(multipleValues) { + $.each(value, function(index, value){ + values.push(value.replace(regExp.quote, '"')); + }); + return values; + } + return value.replace(regExp.quote, '"'); + }, + string: function(text) { + text = String(text); + return text.replace(regExp.escape, '\\$&'); + }, + htmlEntities: function(string) { + var + badChars = /[<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); + return string.replace(badChars, escapedChar); + } + return string; + } + }, + + setting: function(name, value) { + module.debug('Changing setting', name, value); + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + if($.isPlainObject(settings[name])) { + $.extend(true, settings[name], value); + } + else { + settings[name] = value; + } + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + module.error(error.method, query); + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if(Array.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + }) + ; + return (returnedValue !== undefined) + ? returnedValue + : $allModules + ; +}; + +$.fn.dropdown.settings = { + + silent : false, + debug : false, + verbose : false, + performance : true, + + on : 'click', // what event should show menu action on item selection + action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) + + values : false, // specify values to use for dropdown + + clearable : false, // whether the value of the dropdown can be cleared + + apiSettings : false, + selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used + minCharacters : 0, // Minimum characters required to trigger API call + + filterRemoteData : false, // Whether API results should be filtered after being returned for query term + saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh + + throttle : 200, // How long to wait after last user input to search remotely + + context : window, // Context to use when determining if on screen + direction : 'auto', // Whether dropdown should always open in one direction + keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing + + match : 'both', // what to match against with search selection (both, text, or label) + fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) + ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) + hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) + + placeholder : 'auto', // whether to convert blank the values will be delimited with this character + + showOnFocus : true, // show menu on focus + allowReselection : false, // whether current value should trigger callbacks when reselected + allowTab : true, // add tabindex to element + allowCategorySelection : false, // allow elements with sub-menus to be selected + + fireOnInit : false, // Whether callbacks should fire when initializing dropdown values + + transition : 'auto', // auto transition will slide down or up based on direction + duration : 200, // duration of transition + + glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width + + headerDivider : true, // whether option headers should have an additional divider line underneath when converted from requires multiple property to be set to correctly preserve multiple values', + method : 'The method you called is not defined.', + noAPI : 'The API module is required to load resources remotely', + noStorage : 'Saving remote data requires session storage', + noTransition : 'This module requires ui transitions ', + noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including as a polyfill.' + }, + + regExp : { + escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, + quote : /"/g + }, + + metadata : { + defaultText : 'defaultText', + defaultValue : 'defaultValue', + placeholderText : 'placeholder', + text : 'text', + value : 'value' + }, + + // property names for remote query + fields: { + remoteValues : 'results', // grouping for api results + values : 'values', // grouping for all dropdown values + disabled : 'disabled', // whether value should be disabled + name : 'name', // displayed dropdown text + value : 'value', // actual dropdown value + text : 'text', // displayed text when selected + type : 'type', // type of dropdown element + image : 'image', // optional image path + imageClass : 'imageClass', // optional individual class for image + icon : 'icon', // optional icon name + iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) + class : 'class', // optional individual class for item/header + divider : 'divider' // optional divider append for group headers + }, + + keys : { + backspace : 8, + delimiter : 188, // comma + deleteKey : 46, + enter : 13, + escape : 27, + pageUp : 33, + pageDown : 34, + leftArrow : 37, + upArrow : 38, + rightArrow : 39, + downArrow : 40 + }, + + selector : { + addition : '.addition', + divider : '.divider, .header', + dropdown : '.ui.dropdown', + hidden : '.hidden', + icon : '> .dropdown.icon', + input : '> input[type="hidden"], > select', + item : '.item', + label : '> .label', + remove : '> .label > .delete.icon', + siblingLabel : '.label', + menu : '.menu', + message : '.message', + menuIcon : '.dropdown.icon', + search : 'input.search, .menu > .search > input, .menu input.search', + sizer : '> input.sizer', + text : '> .text:not(.icon)', + unselectable : '.disabled, .filtered', + clearIcon : '> .remove.icon' + }, + + className : { + active : 'active', + addition : 'addition', + animating : 'animating', + disabled : 'disabled', + empty : 'empty', + dropdown : 'ui dropdown', + filtered : 'filtered', + hidden : 'hidden transition', + icon : 'icon', + image : 'image', + item : 'item', + label : 'ui label', + loading : 'loading', + menu : 'menu', + message : 'message', + multiple : 'multiple', + placeholder : 'default', + sizer : 'sizer', + search : 'search', + selected : 'selected', + selection : 'selection', + upward : 'upward', + leftward : 'left', + visible : 'visible', + clearable : 'clearable', + noselection : 'noselection', + delete : 'delete', + header : 'header', + divider : 'divider', + groupIcon : '', + unfilterable : 'unfilterable' + } + +}; + +/* Templates */ +$.fn.dropdown.settings.templates = { + deQuote: function(string) { + return String(string).replace(/"/g,""); + }, + escape: function(string, preserveHTML) { + if (preserveHTML){ + return string; + } + var + badChars = /[<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); + return string.replace(badChars, escapedChar); + } + return string; + }, + // generates dropdown from select values + dropdown: function(select, fields, preserveHTML, className) { + var + placeholder = select.placeholder || false, + html = '', + escape = $.fn.dropdown.settings.templates.escape + ; + html += ''; + if(placeholder) { + html += '
' + escape(placeholder,preserveHTML) + '
'; + } + else { + html += '
'; + } + html += '
'; + html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); + html += '
'; + return html; + }, + + // generates just menu from select + menu: function(response, fields, preserveHTML, className) { + var + values = response[fields.values] || [], + html = '', + escape = $.fn.dropdown.settings.templates.escape, + deQuote = $.fn.dropdown.settings.templates.deQuote + ; + $.each(values, function(index, option) { + var + itemType = (option[fields.type]) + ? option[fields.type] + : 'item' + ; + + if( itemType === 'item' ) { + var + maybeText = (option[fields.text]) + ? ' data-text="' + deQuote(option[fields.text]) + '"' + : '', + maybeDisabled = (option[fields.disabled]) + ? className.disabled+' ' + : '' + ; + html += '
'; + if(option[fields.image]) { + html += ''; + } + if(option[fields.icon]) { + html += ''; + } + html += escape(option[fields.name] || '', preserveHTML); + html += '
'; + } else if (itemType === 'header') { + var groupName = escape(option[fields.name] || '', preserveHTML), + groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon + ; + if(groupName !== '' || groupIcon !== '') { + html += '
'; + if (groupIcon !== '') { + html += ''; + } + html += groupName; + html += '
'; + } + if(option[fields.divider]){ + html += '
'; + } + } + }); + return html; + }, + + // generates label for multiselect + label: function(value, text, preserveHTML, className) { + var + escape = $.fn.dropdown.settings.templates.escape; + return escape(text,preserveHTML) + ''; + }, + + + // generates messages like "No results" + message: function(message) { + return message; + }, + + // generates user addition to selection menu + addition: function(choice) { + return choice; + } + +}; + +})( jQuery, window, document ); -- cgit v1.2.3