diff --git a/public/js/icinga/componentLoader.js b/public/js/icinga/componentLoader.js deleted file mode 100644 index d4d958559..000000000 --- a/public/js/icinga/componentLoader.js +++ /dev/null @@ -1,172 +0,0 @@ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -/** - * A module to load and manage frontend components - * - */ -define(['jquery', 'logging', 'icinga/componentRegistry'], function ($, log, registry) { - 'use strict'; - - var ComponentLoader = function() { - - /** - * Load the component with the given type and attach it to the target - * - * @param {String} cmpType The component type to load '/' - * @param {HTMLElement} target The targeted dom node - * @param {function} fin The called when the component was successfully loaded - * @param {function} err The error-callback - */ - var loadComponent = function(cmpType, target, fin, err) { - requirejs( - ['components/' + cmpType], - function (Cmp) { - var cmp; - try { - cmp = new Cmp(target); - - } catch (e) { - log.emergency('Error in component "' + cmpType + '" : "' + e + '"'); - err(e); - return; - } - if (fin) { - fin(cmp); - } - }, - function (ex) { - if (!ex) { - return; - } - log.emergency('Component "' + cmpType + '" could not be loaded.', ex); - if (err) { - err(ex); - } - } - ); - }; - - /** - * Load all new components and remove components that were removed from - * the DOM from the internal registry - * - * @param {function} fin Called when the loading is completed - */ - this.load = function(fin) { - - /* - * Count the amount of pending callbacks to make sure everything is loaded - * when calling the garbage collection. - */ - var pendingFns = 1; - - var finalize = function() { - pendingFns--; - /* - * Only return when all components are loaded - */ - if (pendingFns === 0) { - registry.removeInactive(); - if (fin) { - fin(); - } - } - }; - - registry.markAllInactive(); - - $('[data-icinga-component]') - .each(function(index, el) { - var type = $(el).attr('data-icinga-component'); - pendingFns++; - - if (!el.id || !registry.getById(el.id)) { - loadComponent( - type, - el, - function(cmp) { - var id = registry.add(cmp, type); - registry.markActive(id); - el.id = id; - finalize(); - }, - finalize - ); - } else { - registry.markActive(el.id); - finalize(); - } - }); - finalize(); - }; - - /** - * Get the id of the given component, if one is assigned - * - * @param {*} component The component of which the id should be retrieved - * - * @returns {String|null} The id of the component, or null - */ - this.getId = function(component) { - return registry.getId(component); - }; - - /** - * Get the component that is assigned to the given id - * - * @param {String} id The id of the component - * - * @returns {*} The component or null - */ - this.getById = function(id) { - return registry.getById(id); - }; - - /** - * Get all components that match the given type - * - * @param {String} type The component type in the form '/' - * - * @returns {*|Array} The components or an empty array - */ - this.getByType = function(type) { - return registry.getByType(type); - }; - - /** - * Get all components - * - * @returns {*|Array} The components or an empty array - */ - this.getComponents = function() { - return registry.getComponents(); - }; - }; - return new ComponentLoader(); -}); diff --git a/public/js/icinga/componentRegistry.js b/public/js/icinga/componentRegistry.js deleted file mode 100644 index 65b9b153d..000000000 --- a/public/js/icinga/componentRegistry.js +++ /dev/null @@ -1,158 +0,0 @@ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -/** - * A component registry that maps components to unique IDs and keeps track - * of component types to allow easy querying - * - */ -define(['jquery'], function($) { - "use strict"; - - var ComponentRegistry = function() { - var self = this; - - /** - * Map ids to components - */ - var components = {}; - - /** - * Generate a new component id - */ - var createId = (function() { - var id = 0; - return function() { - return 'icinga-component-' + id++; - }; - })(); - - /** - * Get the id of the given component, if one is assigned - * - * @param {*} component The component of which the id should be retrieved - * - * @returns {String|null} The id of the component, or null - */ - this.getId = function(cmp) { - var id = null; - $.each(components, function(key, value) { - if (value && value.cmp === cmp) { - id = key; - } - }); - return id; - }; - - /** - * Get the component that is assigned to the given id - * - * @param {String} id The id of the component - * - * @returns {*} The component or null - */ - this.getById = function(id) { - return components[id] && components[id].cmp; - }; - - /** - * Get all components that match the given type - * - * @param {String} type The component type in the form '/' - * - * @returns {*|Array} The components or an empty array - */ - this.getByType = function(type) { - return $.map(components, function(entry) { - return entry.type === type ? entry.cmp : null; - }); - }; - - /** - * Get all components - * - * @returns {*|Array} The components or an empty array - */ - this.getComponents = function() { - return $.map(components, function(entry) { - return entry.cmp; - }); - }; - - /** - * Add the given component to the registry and return the assigned id - * - * @param {*} cmp The component to add - * @param {String} id The optional id that should be assigned to that component - * @param {String} type The component type to load '/' - * - * @returns {*|Array} - */ - this.add = function(cmp, type) { - var id = createId(); - components[id] = { - cmp: cmp, - type: type, - active: true - }; - return id; - }; - - /** - * Mark all components inactive - */ - this.markAllInactive = function() { - $.each(components,function(index, el){ - if (el && el.active) { - el.active = false; - } - }); - }; - - /** - * Mark the component with the given id as active - */ - this.markActive = function(id) { - if (components[id]) { - components[id].active = true; - } - }; - - /** - * Let the garbage collection remove all inactive components - */ - this.removeInactive = function() { - $.each(components, function(key,value) { - if (!value || !value.active) { - delete components[key]; - } - }); - }; - }; - return new ComponentRegistry(); -}); diff --git a/public/js/icinga/components/ajaxPostSubmitForm.js b/public/js/icinga/components/ajaxPostSubmitForm.js deleted file mode 100644 index c0170a3d3..000000000 --- a/public/js/icinga/components/ajaxPostSubmitForm.js +++ /dev/null @@ -1,84 +0,0 @@ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} -/*global Icinga:false define:false require:false base_url:false console:false */ - -/** - * Icinga app/ajaxPostSubmitForm component. - * - * This component converts generic post forms into ajax - * submit forms. - */ -define(['components/app/container', 'jquery'], function(Container, $) { - "use strict"; - - /**; - * Handler for ajax post submit - * - * @param {Event} e - */ - var submitHandler = function(e) { - e.preventDefault(); - - - var form = $(this); - var url = form.attr('action'); - var submit = form.find('button[type="submit"]', 'input[type="submit"]'); - var data = form.serialize(); - - // Submit name is missing for valid submission - if (data) { - data += '&'; - } - data += submit.attr('name') + '=1'; - - $.ajax({ - url: url, - type: 'POST', - data: data, - beforeSend: function() { - submit.attr('disabled', true); - } - }).done(function() { - var c = new Container(form); - c.refresh(); - }).error(function() { - submit.removeAttr('disabled'); - }); - return false; - }; - - /** - * The component bootstrap - * - * @param {Element} targetElement - */ - return function(targetForm) { - var form = $(targetForm); - form.submit(submitHandler); - }; -}); \ No newline at end of file diff --git a/public/js/icinga/components/container.js b/public/js/icinga/components/container.js deleted file mode 100644 index 743206d4b..000000000 --- a/public/js/icinga/components/container.js +++ /dev/null @@ -1,509 +0,0 @@ -/*global Icinga:false, Modernizr: false, document: false, History: false, define:false require:false base_url:false console:false */ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITemplate', 'icinga/util/url'], - function($, logger, componentLoader, URI, Tpl, urlMgr) { - 'use strict'; - - var Icinga; - - /** - * Enumeration of possible container types - * - * @type {{GENERIC: string, MAIN: string, DETAIL: string}} - */ - var CONTAINER_TYPES = { - 'GENERIC' : 'generic', - 'MAIN' : 'icingamain', - 'DETAIL': 'icingadetail' - }; - - /** - * Static reference to the main container, populated on the first 'getMainContainer' call - * - * @type {Container} - */ - var mainContainer = null; - - /** - * Static reference to the detail container, populated on the first getDetailContainer call - * - * @type {Container} - */ - var detailContainer = null; - - /** - * Contains currently pending requests - * - * @type {jqAJAX} - */ - var pendingDetailRequest = null; - - /** - * Cancel the pending request, if one exists - */ - var cancelPendingRequest = function() { - if (pendingDetailRequest) { - pendingDetailRequest.abort(); - } - }; - - /** - * A handler for accessing icinga containers, i.e. the #icingamain, #icingadetail containers and specific 'app/container' - * components. - * - * This component can be constructed with every object as the parameter and will provide access to the nearest - * container (which could be the applied object itself, if it is a container) wrapping this object. - * - * The windows url should always be modified with this implementation, so an objects context should point to a - * new URL, call new Container('#myObject').updateContainerHref('/my/url') - * - * This requirejs module also registers a global handler catching all links of the main container and rendering - * their content to the main container, in case you don't want to extend the container with additional handlers. - * - * @param {HTMLElement, jQuery, String} target A jQuery resultset, dom element or matcher string - */ - var Container = function(target) { - - - /** - * Return the container that is at the nearest location to this element, or the element itself if it is a container - * - * Containers are either the icingamain and icingadetail ids or components tagged as app/container - * - * @param {String, jQuery, HTMLElement} target The node to use as the starting point - * - * @returns {HTMLElement|null} The nearest container found or null if target is no container - * and no container is above target - */ - this.findNearestContainer = function(target) { - target = $(target); - if (target.attr('data-icinga-component') === 'app/container' || - target.attr('id') === 'icingamain' || target.attr('id') === 'icingadetail') { - return target; - } - return target.parents('[data-icinga-component="app/container"], #icingamain, #icingadetail')[0]; - }; - - /** - * Find the container responsible for target and determine it's type - * - * @param {HTMLElement, jQuery, String} target A jQuery resultset, dom element or matcher string - */ - this.construct = function(target) { - this.containerDom = $(this.findNearestContainer(target)); - this.containerType = CONTAINER_TYPES.GENERIC; - - if (this.containerDom.attr('id') === CONTAINER_TYPES.MAIN) { - this.containerType = CONTAINER_TYPES.MAIN; - } else if (this.containerDom.attr('id') === CONTAINER_TYPES.DETAIL) { - this.containerType = CONTAINER_TYPES.DETAIL; - } else { - this.containerType = CONTAINER_TYPES.GENERIC; - } - - if (this.containerDom.data('loadIndicator') !== true) { - this.installDefaultLoadIndicator(); - this.containerDom.data('loadIndicator', true); - } - }; - - - /** - * Returns the window without the hostname - * - * @returns {string} path with query, search and hash - */ - var getWindowLocationWithoutHost = function() { - return window.location.pathname + window.location.search + window.location.hash; - }; - - /** - * Create default load mask - * - * @private - */ - var createDefaultLoadIndicator = function() { - if (this.containerDom.find('div.load-indicator').length === 0) { - var content = '
' + - '
' + - '
Loading
' + - '
'; - $(this.containerDom).append(content); - } - }; - - /** - * Remove default load mask - * - * @private - */ - var destroyDefaultLoadIndicator = function() { - this.containerDom.find('div.load-indicator').remove(); - }; - - - /** - * Load the provided url, stop all pending requests for this container and call replaceDom for the returned html - * - * This method relaods the page if a 401 (Authorization required) header is encountered - * - * @param {String, URI} url The Url to load or and URI.js object encapsulating it - */ - this.updateFromUrl = function(url) { - - if (this.containerType === CONTAINER_TYPES.DETAIL) { - urlMgr.setDetailUrl(url); - } else { - urlMgr.setMainUrl(url); - - } - }; - - this.replaceDomAsync = function(url) { - urlMgr.syncWithUrl(); - if (urlMgr.detailUrl === '') { - this.hideDetail(); - } - cancelPendingRequest(); - var loadingTimeout = window.setTimeout( - (function(containerDom) { - return function() { - containerDom.trigger('showLoadIndicator'); - } - })(this.containerDom), - 500 - ); - pendingDetailRequest = $.ajax({ - 'url': url, - 'data': { - 'render': 'detail' - }, - 'context': this - }) - .done(function (response) { - pendingDetailRequest = null; - this.replaceDom($(response)); - }) - .fail(function (response, reason) { - if (reason === 'abort') { - return; - } - if (response.statusCode().status === 401) { - var error = JSON.parse(response.responseText); - window.location.replace( - URI(error.redirectTo).search({ - redirect: URI(urlMgr.getUrl()).resource().replace(new RegExp('^' + window.base_url), '') - }) - ); - return; - } - var errorReason; - if (response.statusCode.toString()[0] === '4') { - errorReason = 'The Requested View Couldn\'t Be Found
'; - } else { - errorReason = response.responseText; - } - this.replaceDom(response.responseText); - }) - .always(function () { - window.clearTimeout(loadingTimeout); - this.containerDom.trigger('hideLoadIndicator'); - }); - }; - - this.getUrl = function() { - if (this.containerType === CONTAINER_TYPES.DETAIL) { - return urlMgr.detailUrl; - } else { - return urlMgr.mainUrl; - } - - }; - - /** - * Remove all dom nodes from this container and replace them with the ones from domNodes - * - * Triggers the custom "updated" event and causes a rescan for components on the DOM nodes - * - * If keepLayout is given, the detail panel won't be expanded if this is an update for the detail panel, - * otherwise it will be automatically shown. - * - * @param {String, jQuery, HTMLElement, Array} domNodes Any valid representation of the Dom nodes to insert - * @param {boolean} keepLayout Whether to keep the layout untouched, even if detail - * is updated end collapsed - * - * @see registerOnUpdate - */ - this.replaceDom = function(domNodes, keepLayout) { - this.containerDom.trigger('showLoadIndicator'); - this.containerDom.empty().append(domNodes); - this.containerDom.trigger('updated', [domNodes]); - this.containerDom.trigger('hideLoadIndicator'); - componentLoader.load(); - if (!keepLayout) { - if (this.containerType === CONTAINER_TYPES.DETAIL) { - this.showDetail(); - } - } - }; - - /** - * Register a method to be called when this container is updated - * - * @param {function} fn The function to call when the container is updated - */ - this.registerOnUpdate = function(fn) { - this.containerDom.on('updated', fn); - }; - - /** - * Register a method to show a load indicator - * - * @param {function} fn The function to register - */ - this.registerOnShowLoadIndicator = function(fn) { - this.containerDom.on('showLoadIndicator', fn); - }; - - /** - * Register a method when load indicator should be removed - * - * @param {function} fn The function to register - */ - this.registerOnHideLoadIndicator = function(fn) { - this.containerDom.on('hideLoadIndicator', fn); - }; - - /** - * Install default load indicator - */ - this.installDefaultLoadIndicator = function() { - this.registerOnShowLoadIndicator($.proxy(createDefaultLoadIndicator, this)); - this.registerOnHideLoadIndicator($.proxy(destroyDefaultLoadIndicator, this)); - }; - - /** - * Remove default load indicator - */ - this.removeDefaultLoadIndicator = function() { - this.containerDom.off('showLoadIndicator'); - this.containerDom.off('hideLoadIndicator'); - }; - - this.onLinkClick = function(ev, target) { - if ($.trim($(target).attr('href')) === '#') { - return true; - } - var url = URI($(target).attr('href')); - - var explicitTarget = $(target).attr('data-icinga-target'); - - var isHash = ('#' + url.fragment() === url.href()); - if (isHash) { - explicitTarget = this.containerType === CONTAINER_TYPES.MAIN ? 'main' : 'detail'; - } - if (explicitTarget) { - - urlMgr[{ - 'main' : 'setMainUrl', - 'detail' : 'setDetailUrl', - 'self' : 'setUrl' - }[explicitTarget]](url.href()); - - } else if (this.containerType === CONTAINER_TYPES.MAIN) { - urlMgr.setDetailUrl(url.href()); - } else { - urlMgr.setMainUrl(url.href()); - } - - - ev.preventDefault(); - ev.stopPropagation(); - return false; - - }; - - this.setUrl = function(url) { - if (typeof url === 'string') { - url = URI(url); - } - console.log(url); - if (this.containerType === CONTAINER_TYPES.MAIN) { - urlMgr.setMainUrl(url.href()); - } else { - urlMgr.setDetailUrl(url.href()); - } - }; - - this.refresh = function() { - if (this.containerType === CONTAINER_TYPES.MAIN) { - Container.getMainContainer().replaceDomAsync(urlMgr.mainUrl); - } else { - Container.getDetailContainer().replaceDomAsync(urlMgr.detailUrl); - } - }; - - this.construct(target); - }; - - /** - * Static method for detecting whether the given link is external or only browserside (hash links) - * - * @param {String} link The link to test for being site-related - * - * @returns {boolean} True when the link should be executed with the browsers normal behaviour, false - * when the link should be catched and processed internally - */ - Container.isExternalLink = function(link) { - return (/^\/\//).test(URI(link).relativeTo(window.location.href).href()); - }; - - - - /** - * Return the page's detail container (which is always there) - * - * @returns {Container} The detail container of the page - */ - Container.getDetailContainer = function() { - detailContainer = detailContainer || new Container('#icingadetail'); - if(!jQuery.contains(document.body, mainContainer)) { - detailContainer = new Container('#icingadetail'); - } - return detailContainer; - }; - - /** - * Return the page's main container (which is always there) - * - * @returns {Container} The main container of the page - */ - Container.getMainContainer = function() { - mainContainer = mainContainer || new Container('#icingamain'); - if(!jQuery.contains(document.body, mainContainer)) { - mainContainer = new Container('#icingamain'); - } - return mainContainer; - }; - - /** - * Expand the detail container and shrinken the main container - * - * Available as a static method on the Container object or as an instance method - */ - Container.prototype.showDetail = Container.showDetail = function() { - $('#icingadetailClose').removeClass('hidden'); - var mainDom = Container.getMainContainer().containerDom, - detailDom = Container.getDetailContainer().containerDom; - - if (detailDom.find('*').length === 0) { - var mainHeight = $(window).height(); - detailDom.append('
'); - } - - mainDom.removeClass(); - detailDom.removeClass(); - - mainDom.addClass('col-xs-pull-12 col-sm-pull-12 col-md-pull-12 col-lg-7'); - detailDom.addClass('col-xs-push-12 col-sm-push-12 col-md-push-12 col-lg-5'); - $(window).trigger('layoutchange'); - }; - - /** - * Hide the detail container and expand the main container - * - * Also updates the Url by removing the detail part - * - * Available as a static method on the Container object or as an instance method - */ - Container.prototype.hideDetail = Container.hideDetail = function() { - $('#icingadetailClose').addClass('hidden'); - cancelPendingRequest(); - urlMgr.setDetailUrl(''); - var mainDom = Container.getMainContainer().containerDom, - detailDom = Container.getDetailContainer().containerDom; - - mainDom.removeClass(); - detailDom.removeClass(); - mainDom.addClass('col-md-12'); - detailDom.addClass('hidden-md'); - mainDom.addClass('col-lg-12'); - detailDom.addClass('hidden-lg'); - mainDom.addClass('col-xs-12'); - detailDom.addClass('hidden-xs'); - mainDom.addClass('col-sm-12'); - detailDom.addClass('hidden-sm'); - detailDom.removeAttr('data-icinga-href'); - $(window).trigger('layoutchange'); - }; - /** - * Injects the icinga object into the Container class - * - * This can't be done via requirejs as we would end up in circular references - * - * @param {Icinga} icingaObj The Icinga object to use for reloading - */ - Container.setIcinga = function(icingaObj) { - Icinga = icingaObj; - }; - - $('body').on('click', '*[data-icinga-component="app/container"], #icingamain, #icingadetail', function(ev) { - var targetEl = ev.target || ev.toElement || ev.relatedTarget; - - if (targetEl.tagName.toLowerCase() !== 'a') { - targetEl = $(targetEl).parents('a')[0]; - if (!targetEl) { - return true; - } - } - return (new Container(targetEl)).onLinkClick(ev, targetEl); - - }); - - $(window).on('hashchange', (function() { - urlMgr.syncWithUrl(); - if (urlMgr.detailUrl) { - Container.getDetailContainer().replaceDomAsync(urlMgr.detailUrl); - } else { - Container.hideDetail(); - } - })); - - - if (urlMgr.detailUrl) { - Container.getDetailContainer().replaceDomAsync(urlMgr.detailUrl); - } - - $('#icingadetailClose').click(function(){ - detailContainer.hideDetail(); - }); - - return Container; -}); diff --git a/public/js/icinga/components/dashboard.js b/public/js/icinga/components/dashboard.js deleted file mode 100644 index 758bccbc0..000000000 --- a/public/js/icinga/components/dashboard.js +++ /dev/null @@ -1,87 +0,0 @@ -/*global Icinga:false, document: false, define:false require:false base_url:false console:false */ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -/** - * Dashboard container, uses freetile for layout - * - */ -define(['jquery', 'logging', 'URIjs/URI', 'icinga/componentLoader', 'icinga/util/url'], - function($, log, URI, components, urlMgr) { - 'use strict'; - return function(parent) { - this.dom = $(parent); - var dashboardContainer = this.dom.parent('div'); - dashboardContainer.freetile(); - this.container = this.dom.children('.container'); - this.dashboardUrl = this.dom.attr('data-icinga-url'); - var reloadTimeout = null; - - /** - * Refresh the container content and layout - */ - this.refresh = function () { - $.ajax({ - url: this.dashboardUrl, - context: this - }) - .done(function (response) { - this.container.empty(); - this.container.html(response); - components.load(); - }) - .fail(function (response, reason) { - if (response.statusCode().status === 401) { - var error = JSON.parse(response.responseText); - window.location.replace( - URI(error.redirectTo).search({ - redirect: URI(urlMgr.getUrl()).resource().replace(new RegExp('^' + window.base_url), '') - }) - ); - return; - } - this.container.html(response.responseText); - }) - .always(function () { - dashboardContainer.freetile('layout'); - $(window).on('layoutchange', function() { - dashboardContainer.freetile('layout'); - }); - this.triggerRefresh(); - }) - }; - - this.triggerRefresh = function() { - if (reloadTimeout) { - clearTimeout(reloadTimeout); - } - setTimeout(this.refresh.bind(this), 10000); - }; - this.refresh(); - }; -}); diff --git a/public/js/icinga/components/datetime.js b/public/js/icinga/components/datetime.js deleted file mode 100644 index a0f065aee..000000000 --- a/public/js/icinga/components/datetime.js +++ /dev/null @@ -1,52 +0,0 @@ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} -/*global Icinga:false, document: false, define:false require:false base_url:false console:false */ - -/** - * Ensures that our date/time controls will work on every browser (natively or javascript based) - */ -define(['jquery', 'datetimepicker'], function($) { - "use strict"; - - var DateTimePicker = function(target) { - $(target).datetimepicker({ - format: 'yyyy-mm-dd hh:ii:ss', - minuteStep: 10, - autoclose: true, - todayBtn: true, - todayHighlight: true - }); - - $(target).parent().find('a').click(function(e) { - e.preventDefault(); - $(target).datetimepicker('show'); - }); - }; - - return DateTimePicker; -}); diff --git a/public/js/icinga/components/ellipsisText.js b/public/js/icinga/components/ellipsisText.js deleted file mode 100644 index d59bf0083..000000000 --- a/public/js/icinga/components/ellipsisText.js +++ /dev/null @@ -1,100 +0,0 @@ -/*global Icinga:false, Modernizr: false, document: false, History: false, define:false require:false base_url:false console:false */ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -/** - * Icinga app/ellipsisText component - * - * This component adds ellipsis with expand functionality - * to content. - * - * Example: - * - *
- * 
- *   
- *     A very long example text
- *    
- * 
- * 
- */ -define(['jquery'], - function($, logger, componentLoader, URI) { - "use strict"; - - /** - * Test if a css3 ellipsis is avtive - * - * @param {Element} element - * @returns {boolean} - */ - var activeEllipsis = function(element) { - return (element.offsetWidth < element.scrollWidth); - }; - - /** - * Add classes to element to create a css3 ellipsis - * - * Parent elements width is used to calculate containing width - * and set target element width to a fixed one. - * - * @param {Element} target - * @constructor - */ - var EllipsisText = function(target) { - var parentWidth = $(target).parent().width(); - - $(target).width(parentWidth) - .css('overflow', 'hidden') - .css('text-overflow', 'ellipsis') - .css('display', 'block') - .css('white-space', 'nowrap'); - - if (activeEllipsis(target)) { - $(target).wrap('') - .css('cursor', 'pointer'); - - $(target).parent() - .attr('data-icinga-ellipsistext', 'true') - .attr('data-content', $(target).html()) - .popover({ - trigger : 'manual', - html : true, - placement : 'auto' - }) - .click(function(e) { - e.stopImmediatePropagation(); - $('a[data-icinga-ellipsistext=\'true\']').popover('hide'); - $(e.currentTarget).popover('toggle'); - }); - } - }; - - return EllipsisText; - } -); \ No newline at end of file diff --git a/public/js/icinga/components/form.js b/public/js/icinga/components/form.js deleted file mode 100644 index 2253c7bd8..000000000 --- a/public/js/icinga/components/form.js +++ /dev/null @@ -1,140 +0,0 @@ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} -/*global Icinga:false define:false require:false base_url:false console:false */ - -/** - * Icinga app/form component. - * - * This component makes sure a user has to confirm when trying to discard unsaved changes - * by leaving the current page. It also implements the code for autosubmitting fields having the - * 'data-icinga-form-autosubmit' component. - */ -define(['jquery'], function($) { - "use strict"; - - /** - * The attribute name marking forms as being modified - * - * @type {string} - */ - var ATTR_MODIFIED = 'data-icinga-form-modified'; - - - - /** - * Takes a form and returns an overloaded jQuery object - * - * The returned object is the jQuery matcher with the following additional methods: - * - * - isModified: Return true when the form is marked as modified - * - setModificationFlag: Mark this form as being modified - * - clearModificationFlag: Clear the modification mark - * - * @param targetForm - * @returns {jQuery} - */ - var getFormObject = function(targetForm) { - var form = $(targetForm); - /** - * Return true when the form is marked as modified - * - * @returns {boolean} True when the form has the @see ATTR_MODIFIED attribute set to 'true', otherwise false - */ - form.isModified = function() { - return form.attr(ATTR_MODIFIED) === 'true' || - form.attr(ATTR_MODIFIED) === '1'; - }; - - /** - * Mark this form as being modified - */ - form.setModificationFlag = function() { - form.attr(ATTR_MODIFIED, true); - }; - - /** - * Clear the modification flag on this form - */ - form.clearModificationFlag = function() { - form.attr(ATTR_MODIFIED, false); - }; - return form; - }; - - /** - * Register event handler for detecting form modifications. - * - * This handler takes care of autosubmit form fields causing submissions on change and - * makes sure the modification flag on the form is set when changes occur. - * - * @param {jQuery} form A form object returned from @see getFormObject() - */ - var registerFormEventHandler = function(form) { - form.change(function(changed) { - if ($(changed.target).attr('data-icinga-form-autosubmit')) { - form.clearModificationFlag(); - form.submit(); - } else { - form.setModificationFlag(); - } - }); - // submissions should clear the modification flag - form.submit(function() { - form.clearModificationFlag(); - }); - }; - - /** - * Register an eventhandler that triggers a confirmation message when the user tries to leave a modified form - * - * @param {jQuery} form A form object returned from @see getFormObject() - */ - var registerLeaveConfirmationHandler = function(form) { - - $(window).on('beforeunload', function() { - if (form.isModified()) { - return 'All unsaved changes will be lost when leaving this page'; - } - }); - }; - - - /** - * The component bootstrap - */ - return function(targetForm) { - var form = getFormObject(targetForm); - - - registerFormEventHandler(form); - registerLeaveConfirmationHandler(form); - - // Remove DOM level onchange, we registered proper jQuery listeners for them - $('[data-icinga-form-autosubmit]').removeAttr('onchange'); - }; -}); \ No newline at end of file diff --git a/public/js/icinga/components/mainDetailGrid.js b/public/js/icinga/components/mainDetailGrid.js deleted file mode 100644 index 859514d49..000000000 --- a/public/js/icinga/components/mainDetailGrid.js +++ /dev/null @@ -1,337 +0,0 @@ -/*global Icinga:false, document: false, define:false require:false base_url:false console:false */ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} -define(['components/app/container', 'jquery', 'logging', 'URIjs/URI', 'URIjs/URITemplate', 'icinga/util/url', 'icinga/selection/selectable', 'icinga/selection/multiSelection'], -function(Container, $, logger, URI, tpl, urlMgr, Selectable, TableMultiSelection) { - "use strict"; - - /** - * Master/Detail grid component handling history, link behaviour, selection (@TODO 3788) and updates of - * grids - * - * @param {HTMLElement} The outer element to apply the behaviour on - */ - return function(gridDomNode) { - - /** - * Reference to the outer container of this component - * - * @type {*|HTMLElement} - */ - gridDomNode = $(gridDomNode); - - /** - * A container component to use for updating URLs and content - * - * @type {Container} - */ - this.container = null; - - /** - * The node wrapping the table and pagination - * - * @type {jQuery} - */ - var contentNode; - - /** - * jQuery matcher result of the form components wrapping the controls - * - * @type {jQuery} - */ - var controlForms; - - /** - * Handles multi-selection - * - * @type {TableMultiSelection} - */ - var selection; - - /** - * Defines how row clicks are handled. Can either be 'none', 'single' or 'multi' - * - * @type {string} - */ - var selectionMode; - - /** - * Detect and select control forms for this table and return them - * - * Form controls are either all forms underneath the of the component, but not underneath the table - * or in a dom node explicitly tagged with the 'data-icinga-actiongrid-controls' attribute - * - * @param {jQuery|null} domContext The context to use as the root node for matching, if null - * the component node given in the constructor is used - * - * @returns {jQuery} A selector result with all forms modifying this grid - */ - var determineControlForms = function(domContext) { - domContext = domContext || gridDomNode; - var controls = $('[data-icinga-grid-controls]', domContext); - if (controls.length > 0) { - return $('form', controls); - } else { - return $('form', domContext).filter(function () { - return $(this).parentsUntil(domContext, 'table').length === 0; - }); - } - }; - - /** - * Detect and select the dom of all tables displaying content for this mainDetailGrid component - * - * The table can either explicitly tagged with the 'data-icinga-grid-content' attribute, if not every table - * underneath the components root dom will be used - * - * @param {jQuery|null} domContext The context to use as the root node for matching, if null - * the component node given in the constructor is used - * - * @returns {jQuery} A selector result with all tables displaying information in the - * grid - */ - var determineContentTable = function(domContext) { - domContext = domContext || gridDomNode; - var maindetail = $('[data-icinga-grid-content]', domContext); - if (maindetail.length > 0) { - return maindetail; - } else { - return $('table', domContext); - } - }; - - /** - * Show a 'hand' to indicate that the row is selectable, - * when hovering. - */ - this.showMousePointerOnRow = function(domContext) { - domContext = domContext || contentNode; - $('tbody tr', domContext).css('cursor' ,'pointer'); - }; - - /** - * Activate a hover effect on all table rows, to indicate that - * this table row is clickable. - * - * @param domContext - */ - this.activateRowHovering = function(domContext) { - domContext = domContext || contentNode; - //$(domContext).addClass('table-hover'); - $('tbody tr', domContext).hover( - function(e) { - $(this).addClass('hover'); - e.preventDefault(); - e.stopPropagation(); - }, - function(e) { - $(this).removeClass('hover'); - e.preventDefault(); - e.stopPropagation(); - } - ); - }; - - /** - * Register the row links of tables using the first link found in the table (no matter if visible or not) - * - * Row level links can only be realized via JavaScript, so every row should provide additional links for - * Users that don't have javascript enabled - * - * @param {jQuery|null} domContext The rootnode to use for selecting rows or null to use contentNode - */ - this.registerTableLinks = function(domContext) { - domContext = domContext || contentNode; - $('tbody tr button[type=submit], tbody tr submit', domContext).click(function(ev) { - ev.stopPropagation(); - return true; - }); - $('tbody tr', domContext).click(function(ev) { - var targetEl = ev.target || ev.toElement || ev.relatedTarget, - a = $(targetEl).closest('a'); - ev.preventDefault(); - ev.stopPropagation(); - - var nodeNames = []; - nodeNames.push($(targetEl).prop('nodeName').toLowerCase()); - nodeNames.push($(targetEl).parent().prop('nodeName').toLowerCase()); - if (a.length) { - // test if the URL is on the current server, if not open it directly - if (Container.isExternalLink(a.attr('href'))) { - return true; - } - } - - var selected = new Selectable(this); - switch (selectionMode) { - case 'multi': - if (ev.ctrlKey || ev.metaKey) { - selection.toggle(selected); - } else if (ev.shiftKey) { - selection.add(selected); - } else { - var oldState = selected.isActive(); - if (!oldState) { - selection.clear(); - selection.add(selected); - } - } - break; - - case 'single': - oldState = selected.isActive(); - if (!oldState) { - selection.clear(); - selection.add(selected); - } - break; - - default: - // don't open the link - return; - } - var url = URI($('a', this).attr('href')); - if (targetEl.tagName.toLowerCase() === 'a') { - url = URI($(targetEl).attr('href')); - } - var segments = url.segment(); - if (selection.size() === 0) { - // don't open anything - urlMgr.setDetailUrl(''); - return false; - } else if (selection.size() > 1 && segments.length > 3) { - // open detail view for multiple objects - segments[2] = 'multi'; - url.pathname('/' + segments.join('/')); - url.search('?'); - url.setSearch(selection.toQuery()); - } - urlMgr.setDetailUrl(url); - return false; - }); - - /* - * Clear the URL when deselected or when a wildcard is used - */ - $(window).on('hashchange', function(){ - if ( - !location.hash || - location.hash.match(/(host=%2A)|(service=%2A)/) - ) { - selection.clear(); - } - }); - }; - - /** - * Register submit handler for the form controls (sorting, filtering, etc). Reloading happens in the - * current container - */ - this.registerControls = function() { - controlForms.on('submit', function(evt) { - var container = (new Container(this)); - var form = $(this); - var url = container.getUrl(); - - if (url.indexOf('?') >= 0) { - url += '&'; - } else { - url += '?'; - } - url += form.serialize(); - container.setUrl(url); - - evt.preventDefault(); - evt.stopPropagation(); - return false; - - }); - $('.pagination li a, a.filter-badge', contentNode.parent()).on('click', function(ev) { - - var container = (new Container(this)); - - // Detail will be removed when main pagination changes - if (container.containerType === 'icingamain') { - urlMgr.setMainUrl(URI($(this).attr('href'))); - urlMgr.setDetailUrl(''); - } else { - urlMgr.setDetailUrl(URI($(this).attr('href'))); - } - - ev.preventDefault(); - ev.stopPropagation(); - return false; - }); - }; - - /** - * Create a new TableMultiSelection, attach it to the content node, and use the - * current detail url to restore the selection state - */ - this.initSelection = function() { - var detail = urlMgr.getDetailUrl(); - if (typeof detail !== 'string') { - detail = detail[0] || ''; - } - selection = new TableMultiSelection(contentNode,new URI(detail)); - }; - - /** - * Init all objects responsible for selection handling - * - * - Indicate selection by showing active and hovered rows - * - Handle click-events according to the selection mode - * - Create and follow links according to the row content - */ - this.initRowSelection = function() { - selectionMode = gridDomNode.data('icinga-grid-selection-type'); - if (selectionMode === 'multi' || selectionMode === 'single') { - // indicate selectable rows - this.showMousePointerOnRow(); - this.activateRowHovering(); - this.initSelection(); - } - this.registerTableLinks(); - }; - - /** - * Create this component, setup listeners and behaviour - */ - this.construct = function(target) { - this.container = new Container(target); - this.container.removeDefaultLoadIndicator(); - controlForms = determineControlForms(); - contentNode = determineContentTable(); - this.initRowSelection(); - this.registerControls(); - }; - if (typeof $(gridDomNode).attr('id') === 'undefined') { - this.construct(gridDomNode); - } - }; -}); diff --git a/public/js/icinga/components/semanticsearch.js b/public/js/icinga/components/semanticsearch.js deleted file mode 100644 index 51ec98f01..000000000 --- a/public/js/icinga/components/semanticsearch.js +++ /dev/null @@ -1,231 +0,0 @@ -/*global Icinga:false, document: false, define:false require:false base_url:false console:false */ -// {{{ICINGA_LICENSE_HEADER}}} -/** - * This file is part of Icinga Web 2. - * - * Icinga Web 2 - Head for multiple monitoring backends. - * Copyright (C) 2013 Icinga Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * @copyright 2013 Icinga Development Team - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -/** - * Ensures that our date/time controls will work on every browser (natively or javascript based) - */ -define(['jquery', 'logging', 'URIjs/URI', 'components/app/container'], function($, log, URI, Container) { - 'use strict'; - - return function(inputDOM) { - this.inputDom = $(inputDOM); - this.domain = this.inputDom.attr('data-icinga-filter-domain'); - this.module = this.inputDom.attr('data-icinga-filter-module'); - this.form = $(this.inputDom.parents('form').first()); - this.formUrl = URI(this.form.attr('action')); - - this.lastQueuedEvent = null; - this.pendingRequest = null; - - /** - * Register the input listener - */ - this.construct = function() { - this.registerControlListener(); - }; - - /** - * Request new proposals for the given input box - */ - this.getProposal = function() { - var text = $.trim(this.inputDom.val()); - - if (this.pendingRequest) { - this.pendingRequest.abort(); - } - this.pendingRequest = $.ajax(this.getRequestParams(text)) - .done(this.showProposals.bind(this)) - .fail(this.showError.bind(this)); - }; - - /** - * Apply a selected proposal to the text box - * - * String parts encapsulated in {} are parts that already exist in the input - * - * @param token The selected token - */ - this.applySelectedProposal = function(token) { - var currentText = $.trim(this.inputDom.val()); - - var substr = token.match(/^(\{.*\})/); - if (substr !== null) { - token = token.substr(substr[0].length); - } else { - token = ' ' + token; - } - - currentText += token; - this.inputDom.val(currentText); - this.inputDom.popover('hide'); - this.inputDom.focus(); - }; - - /** - * Display an error in the box if the request failed - * - * @param {Object} error The error response - * @param {String} state The HTTP state as a string - */ - this.showError = function(error, state) { - if (!error.message || state === 'abort') { - return; - } - this.inputDom.popover('destroy').popover({ - content: '
' + error.message + '
', - html: true, - trigger: 'manual' - }).popover('show'); - }; - - /** - * Return an Object containing the request information for the given query - * - * @param query - * @returns {{data: {cache: number, query: *, filter_domain: (*|Function|Function), filter_module: Function}, headers: {Accept: string}, url: *}} - */ - this.getRequestParams = function(query) { - return { - data: { - 'cache' : (new Date()).getTime(), - 'query' : query, - 'filter_domain' : this.domain, - 'filter_module' : this.module - }, - headers: { - 'Accept': 'application/json' - }, - url: this.formUrl - }; - }; - - /** - * Callback that renders the proposal list after retrieving it from the server - * - * @param {Object} response The jquery response object inheritn XHttpResponse Attributes - */ - this.showProposals = function(response) { - - if (!response || !response.proposals || response.proposals.length === 0) { - this.inputDom.popover('destroy'); - return; - } - - if (response.valid) { - this.inputDom.parent('div').removeClass('has-error').addClass('has-success'); - } else { - this.inputDom.parent('div').removeClass('has-success').addClass('has-error'); - } - var list = $('