Merge "Horizon Checkboxes are now themeable."
This commit is contained in:
commit
721c963b9d
@ -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/help_panel";
|
||||
|
@ -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