Horizon selects are now themable: Table Actions
Horizon was using a standard select input. Unfortunately, this type of input is only customizable to a small extent. Also, a bug was noted. Things were being marked as btn-groups that were not button groups. This was fixed. Co-Authored-By: Ryan Peters <rjpeter2@gmail.com> Co-Authored-By: Matthew Wood <woodm1979@gmail.com> Co-Authored-By: Brian Tully <brian.tully@hp.com> Change-Id: I048f001bf71c5d9a8d13451b7e5a892122f481c8 Partially-implements: blueprint horizon-theme-css-reorg
This commit is contained in:
parent
929d1ed50d
commit
00b842e989
@ -239,11 +239,112 @@ horizon.forms.init_examples = function (el) {
|
|||||||
$el.find("#create_image_form input#id_copy_from").attr("placeholder", "http://example.com/image.iso");
|
$el.find("#create_image_form input#id_copy_from").attr("placeholder", "http://example.com/image.iso");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
horizon.forms.init_themable_select = function ($elem) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// If not specified, find them all
|
||||||
|
$elem = $elem || $('body');
|
||||||
|
|
||||||
|
// If a jQuery object isn't passed in ... make it one
|
||||||
|
$elem = $elem instanceof jQuery ? $elem : $($elem);
|
||||||
|
|
||||||
|
// Pass in a container OR the themable select itself
|
||||||
|
$elem = $elem.hasClass('themable-select') ? $elem : $elem.find('.themable-select');
|
||||||
|
|
||||||
|
// Update the select value if dropdown value changes
|
||||||
|
$elem.on('click', 'li a', function () {
|
||||||
|
var $this = $(this);
|
||||||
|
var $container = $this.closest('.themable-select');
|
||||||
|
var value = $this.data('selectValue');
|
||||||
|
|
||||||
|
// Find select ... if we've searched for it before, then its cached on 'data-select'
|
||||||
|
var $select = $container.data('mySelect');
|
||||||
|
if (!$select) {
|
||||||
|
$select = $container.find('select');
|
||||||
|
$container.data('mySelect', $select);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the select if necessary
|
||||||
|
if($select.val() !== value) {
|
||||||
|
$select.val(value).change();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$elem.find('li a[title]').tooltip();
|
||||||
|
|
||||||
|
// We need to rebuild the dropdown if the Select html ever
|
||||||
|
// changes via javascript. Mutation Observers are DOM change
|
||||||
|
// listeners. http://stackoverflow.com/a/11546242
|
||||||
|
MutationObserver = window.MutationObserver || window.WebKitMutationObserver; // eslint-disable-line no-native-reassign
|
||||||
|
|
||||||
|
var $targets = $elem.find('select');
|
||||||
|
for (var ii = 0; ii < $targets.length; ii++) {
|
||||||
|
var observer = new MutationObserver(function (mutations) { // eslint-disable-line no-loop-func
|
||||||
|
|
||||||
|
// Will return many mutations for a select box changing,
|
||||||
|
// we just need the target of one.
|
||||||
|
var $select = $(mutations[0].target).closest('select');
|
||||||
|
var $options = $select.find('option');
|
||||||
|
var list = [];
|
||||||
|
|
||||||
|
for (var jj = 0; jj < $options.length; jj++) {
|
||||||
|
|
||||||
|
// Build new list item and anchor tag.
|
||||||
|
var $list_item = $(document.createElement('li'))
|
||||||
|
.attr('data-original-index', jj)
|
||||||
|
.attr('select-value', $options[jj].attr('value'));
|
||||||
|
|
||||||
|
var $anchor = $(document.createElement('a'));
|
||||||
|
|
||||||
|
// Append option text to anchor, then to list item.
|
||||||
|
$anchor.text($($options[jj]).text()).appendTo($list_item);
|
||||||
|
list[jj] = $list_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new list to the dropdown.
|
||||||
|
$select.siblings('.dropdown-menu').html(list).change();
|
||||||
|
});
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: true
|
||||||
|
};
|
||||||
|
|
||||||
|
observer.observe($targets[ii], config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the dropdown if select value changes
|
||||||
|
$elem.children('select').on('change', function () {
|
||||||
|
var $this = $(this);
|
||||||
|
var thisVal = $this.val();
|
||||||
|
var thisLabel = $this.find('option[value="' + thisVal + '"]').text();
|
||||||
|
|
||||||
|
// Go find the title element
|
||||||
|
var $title = $this.parents('.themable-select').find('.dropdown-title');
|
||||||
|
|
||||||
|
// Set dropdown title to first option if the select menu is unset
|
||||||
|
if (thisLabel === null || thisLabel.length === 0) {
|
||||||
|
thisLabel = $this.find('option').first().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the dropdown-title if necessary.
|
||||||
|
if (thisLabel !== $title.text()) {
|
||||||
|
$title.text(thisLabel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
horizon.addInitFunction(horizon.forms.init = function () {
|
horizon.addInitFunction(horizon.forms.init = function () {
|
||||||
horizon.forms.handle_submit($('body'));
|
var $body = $('body');
|
||||||
|
horizon.forms.handle_submit($body);
|
||||||
horizon.modals.addModalInitFunction(horizon.forms.handle_submit);
|
horizon.modals.addModalInitFunction(horizon.forms.handle_submit);
|
||||||
|
|
||||||
horizon.forms.init_examples($("body"));
|
horizon.forms.init_themable_select();
|
||||||
|
horizon.modals.addModalInitFunction(horizon.forms.init_themable_select);
|
||||||
|
|
||||||
|
horizon.forms.init_examples($body);
|
||||||
horizon.modals.addModalInitFunction(horizon.forms.init_examples);
|
horizon.modals.addModalInitFunction(horizon.forms.init_examples);
|
||||||
|
|
||||||
horizon.forms.handle_snapshot_source();
|
horizon.forms.handle_snapshot_source();
|
||||||
@ -255,14 +356,14 @@ horizon.addInitFunction(horizon.forms.init = function () {
|
|||||||
horizon.forms.handle_subnet_subnetpool();
|
horizon.forms.handle_subnet_subnetpool();
|
||||||
|
|
||||||
if (!horizon.conf.disable_password_reveal) {
|
if (!horizon.conf.disable_password_reveal) {
|
||||||
horizon.forms.add_password_fields_reveal_buttons($("body"));
|
horizon.forms.add_password_fields_reveal_buttons($body);
|
||||||
horizon.modals.addModalInitFunction(
|
horizon.modals.addModalInitFunction(
|
||||||
horizon.forms.add_password_fields_reveal_buttons);
|
horizon.forms.add_password_fields_reveal_buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind event handlers to confirm dangerous actions.
|
// Bind event handlers to confirm dangerous actions.
|
||||||
// Stops angular form buttons from triggering this event
|
// Stops angular form buttons from triggering this event
|
||||||
$("body").on("click", "form button:not([ng-click]).btn-danger", function (evt) {
|
$body.on("click", "form button:not([ng-click]).btn-danger", function (evt) {
|
||||||
horizon.datatables.confirm(this);
|
horizon.datatables.confirm(this);
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
});
|
});
|
||||||
@ -278,10 +379,13 @@ horizon.addInitFunction(horizon.forms.init = function () {
|
|||||||
$switchables = $fieldset.find('select.switchable');
|
$switchables = $fieldset.find('select.switchable');
|
||||||
|
|
||||||
$switchables.each(function (index, switchable) {
|
$switchables.each(function (index, switchable) {
|
||||||
var $switchable = $(switchable),
|
var $switchable = $(switchable);
|
||||||
slug = $switchable.data('slug'),
|
var slug = $switchable.data('slug');
|
||||||
visible = $switchable.is(':visible'),
|
var isThemable = $switchable.parent('.themable-select').length > 0;
|
||||||
val = $switchable.val();
|
var visible = isThemable
|
||||||
|
? $switchable.siblings('.dropdown-toggle').is(':visible')
|
||||||
|
: $switchable.is(':visible');
|
||||||
|
var val = $switchable.val();
|
||||||
|
|
||||||
function handle_switched_field(index, input){
|
function handle_switched_field(index, input){
|
||||||
var $input = $(input),
|
var $input = $(input),
|
||||||
|
@ -523,6 +523,12 @@ class FilterAction(BaseAction):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_select_options(self):
|
||||||
|
"""Provide the value and string for the template to render.
|
||||||
|
"""
|
||||||
|
if self.filter_choices:
|
||||||
|
return [x[:2] for x in self.filter_choices]
|
||||||
|
|
||||||
|
|
||||||
class NameFilterAction(FilterAction):
|
class NameFilterAction(FilterAction):
|
||||||
"""A filter action for name property."""
|
"""A filter action for name property."""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% load i18n %}
|
{% load i18n bootstrap %}
|
||||||
<div class="table_actions clearfix">
|
<div class="table_actions clearfix">
|
||||||
{% block table_filter %}
|
{% block table_filter %}
|
||||||
{% if filter.filter_type == 'fixed' %}
|
{% if filter.filter_type == 'fixed' %}
|
||||||
@ -24,11 +24,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% elif filter.filter_type == 'server' %}
|
{% elif filter.filter_type == 'server' %}
|
||||||
<div class="table_search">
|
<div class="table_search">
|
||||||
<select name="{{ filter.get_param_name }}_field" class="form-control">
|
{% with name=filter.get_param_name|add:'_field' options=filter.get_select_options value=filter.filter_field %}
|
||||||
{% for choice in filter.filter_choices %}
|
{% include 'horizon/common/fields/_themable_select.html' %}
|
||||||
<option value="{{ choice.0 }}" {% if choice.0 == filter.filter_field %} selected{% endif %}>{{ choice.1 }}</option>
|
{% endwith %}
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<input class="form-control" value="{{ filter.filter_string|default:'' }}" type="text" name="{{ filter.get_param_name }}" />
|
<input class="form-control" value="{{ filter.filter_string|default:'' }}" type="text" name="{{ filter.get_param_name }}" />
|
||||||
<button type="submit" class="btn btn-default {{ filter.get_final_css|safe }}" {{ filter.attr_string_nc|safe }}>{% trans "Filter" %}</button>
|
<button type="submit" class="btn btn-default {{ filter.get_final_css|safe }}" {{ filter.attr_string_nc|safe }}>{% trans "Filter" %}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -48,7 +46,7 @@
|
|||||||
If additional actions are defined, scoop them into the actions dropdown menu
|
If additional actions are defined, scoop them into the actions dropdown menu
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% if table_actions_menu|length > 0 %}
|
{% if table_actions_menu|length > 0 %}
|
||||||
<div class="btn-group table_actions_menu">
|
<div class="table_actions_menu">
|
||||||
<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#">
|
<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
{% if table_actions_buttons|length > 0 %}
|
{% if table_actions_buttons|length > 0 %}
|
||||||
{% trans "More Actions" %}
|
{% trans "More Actions" %}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
{% load horizon %}
|
||||||
|
|
||||||
|
{% minifyspace %}
|
||||||
|
<div class="themable-select dropdown" xmlns="http://www.w3.org/1999/html">
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle"
|
||||||
|
data-toggle="dropdown"{% if value %}
|
||||||
|
title="{{ value }}" {% endif %}
|
||||||
|
aria-expanded="false">
|
||||||
|
<span class="dropdown-title">
|
||||||
|
{% if initial_value %}
|
||||||
|
{{ initial_value }}
|
||||||
|
{% elif value %}
|
||||||
|
{% for option in options %}
|
||||||
|
{% if option.0 == value %}
|
||||||
|
{{ option.1 }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{{ options.0.1 }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="fa fa-caret-down"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for option in options %}
|
||||||
|
<li data-original-index="{{ forloop.counter0 }}">
|
||||||
|
<a data-select-value="{{ option.0 }}"
|
||||||
|
{% if option.2 %}
|
||||||
|
{{ option.2|safe|default:'' }}
|
||||||
|
{% endif %}>{{ option.1 }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<select
|
||||||
|
{% if id %}
|
||||||
|
id="{{ id }}"{% endif %}
|
||||||
|
{% if name %}
|
||||||
|
name="{{ name }}"
|
||||||
|
{% endif %}
|
||||||
|
{% for k,v in select_attrs.items %}
|
||||||
|
{% if k == 'class' %}
|
||||||
|
class="form-control {{ v }}"
|
||||||
|
{% else %}
|
||||||
|
{{ k|safe }}="{{ v }}"
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
>
|
||||||
|
{% for option in options %}
|
||||||
|
<option value="{{ option.0 }}"
|
||||||
|
{% if option.0 == value %}
|
||||||
|
selected="selected"
|
||||||
|
{% endif %}
|
||||||
|
{% if option.2 %}
|
||||||
|
{{ option.2|safe }}
|
||||||
|
{% endif %}>
|
||||||
|
{{ option.1 }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% endminifyspace %}
|
@ -34,6 +34,20 @@
|
|||||||
@include box-shadow(none);
|
@include box-shadow(none);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-primary {
|
||||||
|
color: $brand-primary;
|
||||||
|
}
|
||||||
|
&.btn-danger {
|
||||||
|
color: $brand-danger;
|
||||||
|
}
|
||||||
|
&.btn-warning {
|
||||||
|
color: $brand-warning;
|
||||||
|
}
|
||||||
|
&.btn-info {
|
||||||
|
color: $brand-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@include dropdown-button('default', $dropdown-link-color, $dropdown-link-hover-bg);
|
@include dropdown-button('default', $dropdown-link-color, $dropdown-link-hover-bg);
|
||||||
@include dropdown-button('primary', $btn-primary-bg, $btn-primary-bg, $btn-primary-color);
|
@include dropdown-button('primary', $btn-primary-bg, $btn-primary-bg, $btn-primary-color);
|
||||||
@include dropdown-button('info', $btn-info-bg, $btn-info-bg, $btn-info-color);
|
@include dropdown-button('info', $btn-info-bg, $btn-info-bg, $btn-info-color);
|
||||||
@ -41,6 +55,12 @@
|
|||||||
@include dropdown-button('danger', $btn-danger-bg, $btn-danger-bg, $btn-danger-color);
|
@include dropdown-button('danger', $btn-danger-bg, $btn-danger-bg, $btn-danger-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table_search .themable-select,
|
||||||
|
.table_actions_menu {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.table_actions {
|
.table_actions {
|
||||||
float: right;
|
float: right;
|
||||||
@extend .form-inline;
|
@extend .form-inline;
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Themable Selects
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
|
.themable-select {
|
||||||
|
.dropdown-title {
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@
|
|||||||
@import "components/resource_browser";
|
@import "components/resource_browser";
|
||||||
@import "components/resource_topology";
|
@import "components/resource_topology";
|
||||||
@import "components/selection_menu";
|
@import "components/selection_menu";
|
||||||
|
@import "components/selects";
|
||||||
@import "components/sidebar";
|
@import "components/sidebar";
|
||||||
@import "components/tables";
|
@import "components/tables";
|
||||||
@import "components/transfer_tables";
|
@import "components/transfer_tables";
|
||||||
|
@ -57,7 +57,7 @@ class TableRegion(baseregion.BaseRegion):
|
|||||||
_search_button_locator = (by.By.CSS_SELECTOR,
|
_search_button_locator = (by.By.CSS_SELECTOR,
|
||||||
'div.table_search > button')
|
'div.table_search > button')
|
||||||
_search_option_locator = (by.By.CSS_SELECTOR,
|
_search_option_locator = (by.By.CSS_SELECTOR,
|
||||||
'div.table_search select.form-control')
|
'div.table_search > .themable-select')
|
||||||
marker_name = 'marker'
|
marker_name = 'marker'
|
||||||
prev_marker_name = 'prev_marker'
|
prev_marker_name = 'prev_marker'
|
||||||
|
|
||||||
@ -72,6 +72,10 @@ class TableRegion(baseregion.BaseRegion):
|
|||||||
def _prev_locator(self):
|
def _prev_locator(self):
|
||||||
return by.By.CSS_SELECTOR, 'a[href^="?%s"]' % self.prev_marker_name
|
return by.By.CSS_SELECTOR, 'a[href^="?%s"]' % self.prev_marker_name
|
||||||
|
|
||||||
|
def _search_menu_value_locator(self, value):
|
||||||
|
return (by.By.CSS_SELECTOR,
|
||||||
|
'ul.dropdown-menu a[data-select-value="%s"]' % value)
|
||||||
|
|
||||||
def __init__(self, driver, conf):
|
def __init__(self, driver, conf):
|
||||||
self._default_src_locator = self._table_locator(self.__class__.name)
|
self._default_src_locator = self._table_locator(self.__class__.name)
|
||||||
super(TableRegion, self).__init__(driver, conf)
|
super(TableRegion, self).__init__(driver, conf)
|
||||||
@ -105,8 +109,10 @@ class TableRegion(baseregion.BaseRegion):
|
|||||||
self._click_search_btn()
|
self._click_search_btn()
|
||||||
|
|
||||||
def set_filter_value(self, value):
|
def set_filter_value(self, value):
|
||||||
srch_option = self._get_element(*self._search_option_locator)
|
search_menu = self._get_element(*self._search_option_locator)
|
||||||
return self._select_dropdown_by_value(value, srch_option)
|
search_menu.click()
|
||||||
|
item_locator = self._search_menu_value_locator(value)
|
||||||
|
search_menu.find_element(*item_locator).click()
|
||||||
|
|
||||||
def get_row(self, column_name, text, exact_match=True):
|
def get_row(self, column_name, text, exact_match=True):
|
||||||
"""Get row that contains specified text in specified column.
|
"""Get row that contains specified text in specified column.
|
||||||
|
Loading…
Reference in New Issue
Block a user