Horizon Checkboxes are now themeable.
Horizon checkboxes were using a standard checkbox. Unfortunately, this type of checkbox is only customizable through Chrome, and even then, its not completely flexible. The default checkboxes have now been altered to allow for a highly customized experience through the use of CSS pseudo elements and Icon Fonts. This allows the color, size and unselected and selected states of the checkbox to be customized. The 'default' theme uses the standard Font Awesome checked and unchecked icons. The 'material' now uses the Material Design checkbox design. It was also noticed (and fixed) that the help-icon on the forms were not the same color as its corresponding text. Partially-Implements: blueprint horizon-theme-css-reorg Change-Id: I52602357d831a5e978fe6916b37b0cde9edb2b9b
This commit is contained in:
parent
dcc838128e
commit
259973dd06
@ -209,6 +209,7 @@ full use of the Bootstrap theme architecture.
|
||||
* Login_
|
||||
* Tabs_
|
||||
* Alerts_
|
||||
* Checkboxes_
|
||||
|
||||
Step 1
|
||||
------
|
||||
@ -321,6 +322,13 @@ Alerts
|
||||
Alerts use the basic Bootstrap brand colors. See **Colors** section of your
|
||||
variables file for specifics.
|
||||
|
||||
Checkboxes
|
||||
----------
|
||||
|
||||
Horizon uses icon fonts to represent checkboxes. In order to customize
|
||||
this, you simply need to override the standard scss. For an example of
|
||||
this, see themes/material/static/horizon/components/_checkboxes.scss
|
||||
|
||||
Bootswatch and Material Design
|
||||
------------------------------
|
||||
|
||||
|
@ -33,6 +33,8 @@ from horizon.forms.fields import IPv4 # noqa
|
||||
from horizon.forms.fields import IPv6 # noqa
|
||||
from horizon.forms.fields import MultiIPField # noqa
|
||||
from horizon.forms.fields import SelectWidget # noqa
|
||||
from horizon.forms.fields import ThemableCheckboxInput # noqa
|
||||
from horizon.forms.fields import ThemableCheckboxSelectMultiple # noqa
|
||||
from horizon.forms.views import ModalFormMixin # noqa
|
||||
from horizon.forms.views import ModalFormView # noqa
|
||||
|
||||
@ -45,6 +47,8 @@ __all__ = [
|
||||
"ModalFormMixin",
|
||||
"DynamicTypedChoiceField",
|
||||
"DynamicChoiceField",
|
||||
"ThemableCheckboxInput",
|
||||
"ThemableCheckboxSelectMultiple",
|
||||
"IPField",
|
||||
"IPv4",
|
||||
"IPv6",
|
||||
|
@ -16,6 +16,7 @@ import re
|
||||
|
||||
import netaddr
|
||||
import six
|
||||
import uuid
|
||||
|
||||
from django.core.exceptions import ValidationError # noqa
|
||||
from django.core import urlresolvers
|
||||
@ -253,3 +254,44 @@ class DynamicChoiceField(fields.ChoiceField):
|
||||
class DynamicTypedChoiceField(DynamicChoiceField, fields.TypedChoiceField):
|
||||
"""Simple mix of ``DynamicChoiceField`` and ``TypedChoiceField``."""
|
||||
pass
|
||||
|
||||
|
||||
class ThemableCheckboxInput(widgets.CheckboxInput):
|
||||
"""A subclass of the ``Checkbox`` widget which renders extra markup to
|
||||
allow a custom checkbox experience.
|
||||
"""
|
||||
def render(self, name, value, attrs=None):
|
||||
label_for = attrs.get('id', '')
|
||||
|
||||
if not label_for:
|
||||
attrs['id'] = uuid.uuid4()
|
||||
label_for = attrs['id']
|
||||
|
||||
return html.format_html(
|
||||
u'<div class="themable-checkbox">{}<label for="{}"></label></div>',
|
||||
super(ThemableCheckboxInput, self).render(name, value, attrs),
|
||||
label_for
|
||||
)
|
||||
|
||||
|
||||
class ThemableCheckboxChoiceInput(widgets.CheckboxChoiceInput):
|
||||
def render(self, name=None, value=None, attrs=None, choices=()):
|
||||
if self.id_for_label:
|
||||
label_for = html.format_html(' for="{}"', self.id_for_label)
|
||||
else:
|
||||
label_for = ''
|
||||
attrs = dict(self.attrs, **attrs) if attrs else self.attrs
|
||||
return html.format_html(
|
||||
u'<div class="themable-checkbox">{}<label{}>' +
|
||||
u'<span>{}</span></label></div>',
|
||||
self.tag(attrs), label_for, self.choice_label
|
||||
)
|
||||
|
||||
|
||||
class ThemableCheckboxFieldRenderer(widgets.CheckboxFieldRenderer):
|
||||
choice_input_class = ThemableCheckboxChoiceInput
|
||||
|
||||
|
||||
class ThemableCheckboxSelectMultiple(widgets.CheckboxSelectMultiple):
|
||||
renderer = ThemableCheckboxFieldRenderer
|
||||
_empty_value = []
|
||||
|
@ -89,9 +89,12 @@ horizon.datatables = {
|
||||
|
||||
// Only replace row if the html content has changed
|
||||
if($new_row.html() !== $row.html()) {
|
||||
if($row.find('.table-row-multi-select:checkbox').is(':checked')) {
|
||||
|
||||
// Directly accessing the checked property of the element
|
||||
// is MUCH faster than using jQuery's helper method
|
||||
if($row.find('.table-row-multi-select')[0].checked) {
|
||||
// Preserve the checkbox if it's already clicked
|
||||
$new_row.find('.table-row-multi-select:checkbox').prop('checked', true);
|
||||
$new_row.find('.table-row-multi-select').prop('checked', true);
|
||||
}
|
||||
$row.replaceWith($new_row);
|
||||
// Reset tablesorter's data cache.
|
||||
@ -151,37 +154,50 @@ horizon.datatables = {
|
||||
});
|
||||
},
|
||||
|
||||
validate_button: function ($form) {
|
||||
validate_button: function ($form, disable_button) {
|
||||
// Enable or disable table batch action buttons based on row selection.
|
||||
$form = $form || $(".table_wrapper > form");
|
||||
$form.each(function () {
|
||||
var $this = $(this);
|
||||
var checkboxes = $this.find(".table-row-multi-select:checkbox");
|
||||
var action_buttons = $this.find('.table_actions button[data-batch-action="true"]');
|
||||
action_buttons.toggleClass("disabled", !checkboxes.filter(":checked").length);
|
||||
var $action_buttons = $this.find('.table_actions button[data-batch-action="true"]');
|
||||
if (typeof disable_button == undefined) {
|
||||
disable_button = $this.find(".table-row-multi-select").filter(":checked").length > 0;
|
||||
}
|
||||
$action_buttons.toggleClass("disabled", disable_button);
|
||||
});
|
||||
},
|
||||
|
||||
initialize_checkboxes_behavior: function() {
|
||||
// Bind the "select all" checkbox action.
|
||||
$('div.table_wrapper, #modal_wrapper').on('click', 'table thead .multi_select_column .table-row-multi-select:checkbox', function() {
|
||||
var $this = $(this),
|
||||
$table = $this.closest('table'),
|
||||
is_checked = $this.prop('checked'),
|
||||
checkboxes = $table.find('tbody .table-row-multi-select:visible:checkbox');
|
||||
checkboxes.prop('checked', is_checked);
|
||||
});
|
||||
// Change "select all" checkbox behavior while any checkbox is checked/unchecked.
|
||||
$("div.table_wrapper, #modal_wrapper").on("click", 'table tbody .table-row-multi-select:checkbox', function () {
|
||||
var $table = $(this).closest('table');
|
||||
var $multi_select_checkbox = $table.find('thead .multi_select_column .table-row-multi-select:checkbox');
|
||||
var any_unchecked = $table.find("tbody .table-row-multi-select:checkbox").not(":checked");
|
||||
$multi_select_checkbox.prop('checked', any_unchecked.length === 0);
|
||||
});
|
||||
// Enable/disable table batch action buttons when row selection changes.
|
||||
$("div.table_wrapper, #modal_wrapper").on("click", '.table-row-multi-select:checkbox', function () {
|
||||
horizon.datatables.validate_button($(this).closest("form"));
|
||||
});
|
||||
$('.table_wrapper, #modal_wrapper')
|
||||
.on('change', '.table-row-multi-select', function() {
|
||||
var $this = $(this);
|
||||
var $table = $this.closest('table');
|
||||
var is_checked = $this.prop('checked');
|
||||
|
||||
if ($this.hasClass('multi-select-header')) {
|
||||
|
||||
// Only select / deselect the visible rows
|
||||
$table.find('tbody tr:visible .table-row-multi-select')
|
||||
.prop('checked', is_checked);
|
||||
|
||||
} else {
|
||||
|
||||
// Find the master checkbox
|
||||
var $multi_select_checkbox = $table.find('.multi-select-header');
|
||||
|
||||
// Determine if there are any unchecked checkboxes in the table
|
||||
var $checkboxes = $table.find('tbody .table-row-multi-select');
|
||||
var not_checked = $checkboxes.not(':checked').length;
|
||||
is_checked = $checkboxes.length != not_checked;
|
||||
|
||||
// If there are none, then check the master checkbox
|
||||
$multi_select_checkbox.prop('checked', not_checked == 0);
|
||||
}
|
||||
|
||||
// Pass in whether it should be visible, no point in doing this twice
|
||||
horizon.datatables.validate_button($this.closest('form'), !is_checked);
|
||||
});
|
||||
},
|
||||
|
||||
initialize_table_tooltips: function() {
|
||||
@ -229,7 +245,7 @@ horizon.datatables.confirm = function (action) {
|
||||
var actions_div = $(action).closest("div");
|
||||
if(actions_div.hasClass("table_actions") || actions_div.hasClass("table_actions_menu")) {
|
||||
// One or more checkboxes selected
|
||||
$("#"+closest_table_id+" tr[data-display]").has(".table-row-multi-select:checkbox:checked").each(function() {
|
||||
$("#"+closest_table_id+" tr[data-display]").has(".table-row-multi-select:checked").each(function() {
|
||||
name_array.push(" \"" + $(this).attr("data-display") + "\"");
|
||||
});
|
||||
name_array.join(", ");
|
||||
@ -480,11 +496,30 @@ horizon.datatables.set_table_sorting = function (parent) {
|
||||
});
|
||||
};
|
||||
|
||||
horizon.datatables.add_table_checkboxes = function(parent) {
|
||||
$(parent).find('table thead .multi_select_column').each(function(index, thead) {
|
||||
if (!$(thead).find('.table-row-multi-select:checkbox').length &&
|
||||
$(thead).parents('table').find('tbody .table-row-multi-select:checkbox').length) {
|
||||
$(thead).append('<input type="checkbox" class="table-row-multi-select">');
|
||||
horizon.datatables.add_table_checkboxes = function($parent) {
|
||||
$($parent).find('table thead .multi_select_column').each(function() {
|
||||
var $thead = $(this);
|
||||
if (!$thead.find('.table-row-multi-select').length &&
|
||||
$thead.parents('table').find('tbody .table-row-multi-select').length) {
|
||||
|
||||
// Build up the themable checkbox
|
||||
var $container = $(document.createElement('div'))
|
||||
.addClass('themable-checkbox');
|
||||
|
||||
// Create the input checkbox
|
||||
var $input = $(document.createElement('input'))
|
||||
.attr('type', 'checkbox')
|
||||
.addClass('table-row-multi-select multi-select-header')
|
||||
.uniqueId()
|
||||
.appendTo($container);
|
||||
|
||||
// Create the label
|
||||
$(document.createElement('label'))
|
||||
.attr('for', $input.attr('id'))
|
||||
.appendTo($container);
|
||||
|
||||
// Append to the thead last, for speed
|
||||
$thead.append($container);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -576,10 +611,11 @@ horizon.addInitFunction(horizon.datatables.init = function() {
|
||||
horizon.datatables.initialize_table_tooltips();
|
||||
|
||||
// Trigger run-once setup scripts for tables.
|
||||
horizon.datatables.add_table_checkboxes($('body'));
|
||||
horizon.datatables.set_table_sorting($('body'));
|
||||
horizon.datatables.set_table_query_filter($('body'));
|
||||
horizon.datatables.set_table_fixed_filter($('body'));
|
||||
var $body = $('body');
|
||||
horizon.datatables.add_table_checkboxes($body);
|
||||
horizon.datatables.set_table_sorting($body);
|
||||
horizon.datatables.set_table_query_filter($body);
|
||||
horizon.datatables.set_table_fixed_filter($body);
|
||||
horizon.datatables.disable_actions_on_submit();
|
||||
|
||||
// Also apply on tables in modal views.
|
||||
|
@ -37,6 +37,7 @@ import six
|
||||
|
||||
from horizon import conf
|
||||
from horizon import exceptions
|
||||
from horizon.forms import ThemableCheckboxInput
|
||||
from horizon import messages
|
||||
from horizon.tables.actions import FilterAction # noqa
|
||||
from horizon.tables.actions import LinkAction # noqa
|
||||
@ -672,7 +673,7 @@ class Cell(html.HTMLElement):
|
||||
if column.auto == "multi_select":
|
||||
data = ""
|
||||
if row.can_be_selected(datum):
|
||||
widget = forms.CheckboxInput(check_test=lambda value: False)
|
||||
widget = ThemableCheckboxInput(check_test=lambda value: False)
|
||||
# Convert value to string to avoid accidental type conversion
|
||||
data = widget.render('object_ids',
|
||||
six.text_type(table.get_object_id(datum)),
|
||||
|
@ -3,27 +3,7 @@
|
||||
<div class="form-group{% if field.errors %} has-error{% endif %} {{ field.css_classes }}">
|
||||
{% if field|is_checkbox %}
|
||||
<div class="{{ classes.single_value }}">
|
||||
<div class="checkbox">
|
||||
{% if field.auto_id %}
|
||||
<label
|
||||
{% if field.field.required and form.required_css_class %}
|
||||
class="{{ form.required_css_class }}"
|
||||
{% endif %}>
|
||||
{{ field }}
|
||||
<span>{{ field.label }}</span>
|
||||
{% if field.field.required %}{% include "horizon/common/_form_field_required.html" %}{% endif %}
|
||||
{% if field.help_text %}
|
||||
<span class="help-icon" data-toggle="tooltip"
|
||||
data-placement="top" title="{{ field.help_text|safe }}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<span class="help-block {{ form.error_css_class }}">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% include 'horizon/common/fields/_themable_checkbox.html' %}
|
||||
</div>
|
||||
{% elif field|is_radio %}
|
||||
{% if field.auto_id %}
|
||||
|
@ -3,7 +3,13 @@
|
||||
<div class="form-group{% if field.errors %} has-error{% endif %} {{ field.css_classes }}">
|
||||
<label class="control-label col-sm-3 {% if field.field.required %}{{ form.required_css_class }}{% endif %}" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||
<div class="col-sm-9 {{ classes.value }} {{ field|wrapper_classes }}">
|
||||
{{ field|add_bootstrap_class }}
|
||||
{% if field|is_checkbox %}
|
||||
{% with is_vertical=1 %}
|
||||
{% include 'horizon/common/fields/_themable_checkbox.html' %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
{{ field|add_bootstrap_class }}
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span>
|
||||
{% empty %}
|
||||
|
@ -0,0 +1,24 @@
|
||||
<div class="themable-checkbox">
|
||||
{% if field.auto_id %}
|
||||
{{ field }}
|
||||
<label
|
||||
{% if field.field.required and form.required_css_class %}
|
||||
class="{{ form.required_css_class }}"
|
||||
{% endif %}
|
||||
for="{{ field.auto_id }}">
|
||||
{% if not is_vertical %}
|
||||
<span>{{ field.label }}</span>
|
||||
{% endif %}
|
||||
{% if field.field.required %}{% include "horizon/common/_form_field_required.html" %}{% endif %}
|
||||
{% if field.help_text %}
|
||||
<span class="help-icon" data-toggle="tooltip"
|
||||
data-placement="top" title="{{ field.help_text|safe }}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<span class="help-block {{ form.error_css_class }}">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
@ -13,6 +13,7 @@
|
||||
import django.forms
|
||||
from django import template as django_template
|
||||
|
||||
|
||||
register = django_template.Library()
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
window.WEBROOT = '{{ WEBROOT }}';
|
||||
</script>
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jquery/jquery.js' type='text/javascript' charset="utf-8"></script>
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jquery-ui/ui/jquery-ui.js' type='text/javascript' charset="utf-8"></script>
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jquery/jquery-migrate.js' type='text/javascript' charset="utf-8"></script>
|
||||
<script src="{{ STATIC_URL }}horizon/lib/jquery/jquery.tablesorter.js"></script>
|
||||
<script src="{{ STATIC_URL }}horizon/lib/angular/angular.js" type="text/javascript" charset="utf-8"></script>
|
||||
@ -123,4 +124,4 @@
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -338,10 +338,10 @@ class GetUserHomeTests(BaseHorizonTests):
|
||||
conf.HORIZON_CONFIG._setup()
|
||||
|
||||
def test_using_callable(self):
|
||||
def fancy_user_fnc(user):
|
||||
def themable_user_fnc(user):
|
||||
return user.username.upper()
|
||||
|
||||
settings.HORIZON_CONFIG['user_home'] = fancy_user_fnc
|
||||
settings.HORIZON_CONFIG['user_home'] = themable_user_fnc
|
||||
conf.HORIZON_CONFIG._setup()
|
||||
|
||||
self.assertEqual(self.test_user.username.upper(),
|
||||
|
@ -37,7 +37,7 @@ class LazyLoadedTabsTests(test.SeleniumTestCase):
|
||||
table_selector = 'div.tab-content > div#{0} > div.table_wrapper'.format(
|
||||
tab_id)
|
||||
button_selector = 'button#lazy_puppies__action_delete'
|
||||
checkbox_selector = 'td.multi_select_column > input[type=checkbox]'
|
||||
checkbox_selector = 'td.multi_select_column input[type=checkbox]'
|
||||
select_all_selector = 'th.multi_select_column input[type=checkbox]'
|
||||
|
||||
def setUp(self):
|
||||
|
@ -527,6 +527,10 @@
|
||||
<input type="checkbox"> Checkbox
|
||||
</label>
|
||||
</div>
|
||||
<div class="themable-checkbox">
|
||||
<input type="checkbox" id="themable-checkbox">
|
||||
<label for="themable-checkbox" translate>Checkbox</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -338,7 +338,7 @@ class AddRouterToFirewall(RouterInsertionFormBase):
|
||||
router_ids = forms.MultipleChoiceField(
|
||||
label=_("Add Routers"),
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
widget=forms.ThemableCheckboxSelectMultiple(),
|
||||
help_text=_("Add selected router(s) to the firewall."))
|
||||
|
||||
failure_url = 'horizon:project:firewalls:index'
|
||||
@ -363,7 +363,7 @@ class RemoveRouterFromFirewall(RouterInsertionFormBase):
|
||||
router_ids = forms.MultipleChoiceField(
|
||||
label=_("Associated Routers"),
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
widget=forms.ThemableCheckboxSelectMultiple(),
|
||||
help_text=_("Unselect the router(s) to be removed from firewall."))
|
||||
|
||||
failure_url = 'horizon:project:firewalls:index'
|
||||
|
@ -164,7 +164,7 @@ class SelectRulesAction(workflows.Action):
|
||||
rule = forms.MultipleChoiceField(
|
||||
label=_("Rules"),
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
widget=forms.ThemableCheckboxSelectMultiple(),
|
||||
help_text=_("Create a policy with selected rules."))
|
||||
|
||||
class Meta(object):
|
||||
@ -206,7 +206,7 @@ class SelectRoutersAction(workflows.Action):
|
||||
router = forms.MultipleChoiceField(
|
||||
label=_("Routers"),
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
widget=forms.ThemableCheckboxSelectMultiple(),
|
||||
help_text=_("Create a firewall with selected routers."))
|
||||
|
||||
class Meta(object):
|
||||
|
@ -1617,11 +1617,11 @@ class InstanceTests(helpers.TestCase):
|
||||
else:
|
||||
self.assertNotContains(res, boot_from_image_field_label)
|
||||
|
||||
checked_label = '<label for="id_network_0"><input checked="checked"'
|
||||
checked_box = '<input checked="checked" id="id_network_0"'
|
||||
if only_one_network:
|
||||
self.assertContains(res, checked_label)
|
||||
self.assertContains(res, checked_box)
|
||||
else:
|
||||
self.assertNotContains(res, checked_label)
|
||||
self.assertNotContains(res, checked_box)
|
||||
|
||||
disk_config_field_label = 'Disk Partition'
|
||||
if disk_config:
|
||||
|
@ -551,12 +551,13 @@ class SetAccessControlsAction(workflows.Action):
|
||||
label=_("Confirm Admin Password"),
|
||||
required=False,
|
||||
widget=forms.PasswordInput(render_value=False))
|
||||
groups = forms.MultipleChoiceField(label=_("Security Groups"),
|
||||
required=False,
|
||||
initial=["default"],
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
help_text=_("Launch instance in these "
|
||||
"security groups."))
|
||||
groups = forms.MultipleChoiceField(
|
||||
label=_("Security Groups"),
|
||||
required=False,
|
||||
initial=["default"],
|
||||
widget=forms.ThemableCheckboxSelectMultiple(),
|
||||
help_text=_("Launch instance in these "
|
||||
"security groups."))
|
||||
|
||||
class Meta(object):
|
||||
name = _("Access & Security")
|
||||
@ -701,14 +702,15 @@ class PostCreationStep(workflows.Step):
|
||||
|
||||
|
||||
class SetNetworkAction(workflows.Action):
|
||||
network = forms.MultipleChoiceField(label=_("Networks"),
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
error_messages={
|
||||
'required': _(
|
||||
"At least one network must"
|
||||
" be specified.")},
|
||||
help_text=_("Launch instance with"
|
||||
" these networks"))
|
||||
network = forms.MultipleChoiceField(
|
||||
label=_("Networks"),
|
||||
widget=forms.ThemableCheckboxSelectMultiple(),
|
||||
error_messages={
|
||||
'required': _(
|
||||
"At least one network must"
|
||||
" be specified.")},
|
||||
help_text=_("Launch instance with"
|
||||
" these networks"))
|
||||
if api.neutron.is_port_profiles_supported():
|
||||
widget = None
|
||||
else:
|
||||
|
@ -0,0 +1,38 @@
|
||||
@import '/horizon/lib/font-awesome/scss/variables';
|
||||
@import '/horizon/lib/font-awesome/scss/mixins';
|
||||
|
||||
//
|
||||
// Checkboxes
|
||||
// This will ONLY work when the label's 'for' attribute
|
||||
// shares the input[type=checkbox]'s 'id' value
|
||||
// --------------------------------------------------
|
||||
|
||||
.themable-checkbox {
|
||||
|
||||
// Hide the real checkbox
|
||||
input[type=checkbox] {
|
||||
display:none;
|
||||
|
||||
// The checkbox - Unchecked
|
||||
& + label {
|
||||
margin-bottom: 0; // remove the Bootstrap margin
|
||||
|
||||
&:before {
|
||||
@include fa-icon();
|
||||
content: $fa-var-square-o;
|
||||
width: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& > span {
|
||||
padding-left: $padding-small-vertical;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
// The checkbox - Checked
|
||||
&:checked + label:before {
|
||||
content: $fa-var-check-square-o;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
// Dashboard Components
|
||||
@import "components/bar_charts";
|
||||
@import "components/charts";
|
||||
@import "components/checkboxes";
|
||||
@import "components/datepicker";
|
||||
@import "components/forms";
|
||||
@import "components/inline_edit";
|
||||
|
@ -9,6 +9,8 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
from selenium.webdriver.common import by
|
||||
@ -94,17 +96,31 @@ class CheckBoxMixin(object):
|
||||
if self.is_marked():
|
||||
self.element.click()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Themable checkboxes use a <label> with font-awesome icon as a
|
||||
control element while the <input> widget is hidden. Still the name
|
||||
needs to be extracted from <label>. Attribute "for" is used for that,
|
||||
since it mirrors <input> "id" which in turn is derived from <input>'s
|
||||
name.
|
||||
"""
|
||||
for_attribute = self.element.get_attribute('for')
|
||||
indirect_name = re.search(r'id_(.*)', for_attribute)
|
||||
return (indirect_name and indirect_name.group(1)) or None
|
||||
|
||||
class CheckBoxFormFieldRegion(BaseFormFieldRegion, CheckBoxMixin):
|
||||
|
||||
class CheckBoxFormFieldRegion(CheckBoxMixin, BaseFormFieldRegion):
|
||||
"""Checkbox field."""
|
||||
|
||||
_element_locator_str_suffix = 'label > input[type=checkbox]'
|
||||
_element_locator_str_suffix = \
|
||||
'.themable-checkbox input[type=checkbox] + label'
|
||||
|
||||
|
||||
class ProjectPageCheckBoxFormFieldRegion(BaseFormFieldRegion, CheckBoxMixin):
|
||||
class ProjectPageCheckBoxFormFieldRegion(CheckBoxMixin, BaseFormFieldRegion):
|
||||
"""Checkbox field for Project-page."""
|
||||
|
||||
_element_locator_str_suffix = 'div > input[type=checkbox]'
|
||||
_element_locator_str_suffix = \
|
||||
'div > .themable-checkbox input[type=checkbox] + label'
|
||||
|
||||
|
||||
class ChooseFileFormFieldRegion(BaseFormFieldRegion):
|
||||
|
@ -24,7 +24,10 @@ class RowRegion(baseregion.BaseRegion):
|
||||
"""Classic table row."""
|
||||
|
||||
_cell_locator = (by.By.CSS_SELECTOR, 'td.%s' % NORMAL_COLUMN_CLASS)
|
||||
_row_checkbox_locator = (by.By.CSS_SELECTOR, 'td > input')
|
||||
_row_checkbox_locator = (
|
||||
by.By.CSS_SELECTOR,
|
||||
'td .themable-checkbox [type="checkbox"] + label'
|
||||
)
|
||||
|
||||
def __init__(self, driver, conf, src_elem, column_names):
|
||||
self.column_names = column_names
|
||||
|
@ -36,6 +36,10 @@ form label {
|
||||
}
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
// Note (hurgleburgler) : the combination of display: table-cell, width: 100%
|
||||
// and max-width has strange consequences in CSS. This is required to make sure
|
||||
// that the width does not stretch beyond 100%. Please see the answer to
|
||||
|
@ -1,6 +1,7 @@
|
||||
@import "icons";
|
||||
@import "components/context_selection";
|
||||
@import "components/messages";
|
||||
@import "components/checkboxes";
|
||||
@import "components/hamburger";
|
||||
@import "components/magic_search";
|
||||
@import "components/navbar";
|
||||
|
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Custom Material Checkboxes
|
||||
// --------------------------------------------------
|
||||
|
||||
@import "/horizon/lib/mdi/scss/icons";
|
||||
.themable-checkbox {
|
||||
|
||||
input[type=checkbox] {
|
||||
|
||||
// The checkbox - Unchecked
|
||||
& + label {
|
||||
@extend .mdi-checkbox-blank-outline;
|
||||
|
||||
&:before {
|
||||
@extend .mdi;
|
||||
font-size: $font-size-h5;
|
||||
}
|
||||
}
|
||||
|
||||
// The checkbox - Checked
|
||||
&:checked + label {
|
||||
@extend .mdi-checkbox-marked;
|
||||
|
||||
&:before {
|
||||
color: $brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user