summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Vobornik <pvoborni@redhat.com>2013-03-22 17:53:04 +0100
committerPetr Vobornik <pvoborni@redhat.com>2013-03-29 17:12:20 +0100
commit04325fbb4c64ee4aef6d8c9adf0ff95b8b653101 (patch)
treeea424198852f81e1b08695f93a65b2ec343b5052
parent5f26d2c6dbe878518963b5d8f9159ed3fcc71d58 (diff)
downloadfreeipa-04325fbb4c64ee4aef6d8c9adf0ff95b8b653101.tar.gz
freeipa-04325fbb4c64ee4aef6d8c9adf0ff95b8b653101.tar.xz
freeipa-04325fbb4c64ee4aef6d8c9adf0ff95b8b653101.zip
Nestable checkbox/radio widget
New component: option_widget_base. It's not a regular widget but it share some of its characteristics. It should extend regular widget or it can be nested in itself alone. checkbox_widget, checkboxes_widget, radio_widget were modified to use it. Built as a prerequisite for: https://fedorahosted.org/freeipa/ticket/3404
-rw-r--r--install/ui/ipa.css15
-rw-r--r--install/ui/src/freeipa/aci.js15
-rw-r--r--install/ui/src/freeipa/rule.js3
-rw-r--r--install/ui/src/freeipa/widget.js500
4 files changed, 390 insertions, 143 deletions
diff --git a/install/ui/ipa.css b/install/ui/ipa.css
index 71cad4206..3e443d54e 100644
--- a/install/ui/ipa.css
+++ b/install/ui/ipa.css
@@ -1294,6 +1294,21 @@ table.scrollable tbody {
width: 400px;
}
+.option_widget {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.option_widget.nested {
+ padding-left: 40px;
+}
+
+.option_widget.inline,
+.option_widget.inline > li {
+ display: inline;
+}
+
.combobox-widget-input {
display: inline-block;
position: relative;
diff --git a/install/ui/src/freeipa/aci.js b/install/ui/src/freeipa/aci.js
index 383848ec6..b6825d136 100644
--- a/install/ui/src/freeipa/aci.js
+++ b/install/ui/src/freeipa/aci.js
@@ -477,7 +477,7 @@ IPA.attributes_widget = function(spec) {
'class': 'aci-attribute-table-container'
}).appendTo(container);
- that.table = $('<table/>', {
+ that.$node = that.table = $('<table/>', {
id:id,
'class':'search-table aci-attribute-table scrollable'
}).
@@ -520,9 +520,12 @@ IPA.attributes_widget = function(spec) {
var tr = $('<tr/>').appendTo(tbody);
var td = $('<td/>').appendTo(tr);
+ var name = that.get_input_name();
+ var id = that.option_next_id + name;
td.append($('<input/>',{
+ id: id,
type: 'checkbox',
- name: that.name,
+ name: name,
value: value,
'class': 'aci-attribute',
change: function() {
@@ -531,8 +534,10 @@ IPA.attributes_widget = function(spec) {
}));
td = $('<td/>').appendTo(tr);
td.append($('<label/>',{
- text: value
+ text: value,
+ 'for': id
}));
+ that.new_option_id();
}
};
@@ -553,7 +558,7 @@ IPA.attributes_widget = function(spec) {
that.populate(that.object_type);
that.append();
- that.checkboxes_update(values);
+ that.owb_update(values);
};
that.populate = function(object_type) {
@@ -567,6 +572,7 @@ IPA.attributes_widget = function(spec) {
var aciattrs = metadata.aciattrs;
+ that.options = that.prepare_options(aciattrs);
that.create_options(aciattrs);
};
@@ -585,6 +591,7 @@ IPA.attributes_widget = function(spec) {
}
if (unmatched.length > 0 && !that.skip_unmatched) {
+ that.options.push.apply(that.options, that.prepare_options(unmatched));
that.create_options(unmatched);
}
};
diff --git a/install/ui/src/freeipa/rule.js b/install/ui/src/freeipa/rule.js
index 888fd4508..436e934ca 100644
--- a/install/ui/src/freeipa/rule.js
+++ b/install/ui/src/freeipa/rule.js
@@ -43,7 +43,8 @@ IPA.rule_details_widget = function(spec) {
that.enable_radio.value_changed.attach(that.on_enable_radio_changed);
};
- that.on_enable_radio_changed = function(value) {
+ that.on_enable_radio_changed = function() {
+ var value = that.enable_radio.save();
if(value.length > 0) {
var enabled = ('' === value[0]);
for (var i=0; i<that.tables.length; i++) {
diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index 5ce767e7e..44b8611a3 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -21,7 +21,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-define(['./ipa', './jquery'], function(IPA, $) {
+define(['dojo/_base/array', './ipa', './jquery'], function(array, IPA, $) {
IPA.checkbox_column_width = 22;
IPA.required_indicator = '*';
@@ -616,147 +616,395 @@ IPA.multivalued_widget = function(spec) {
return that;
};
-IPA.checkbox_widget = function (spec) {
+/**
+ * Widget base for checkboxes and radios. Doesn't handle dirty state's but
+ * its supposed to be nestable.
+ *
+ * Nesting rules:
+ * 1. parent should be checked when one of its child is checked
+ * Consequences:
+ * * childs get unchecked when parent gets unchecked
+ * * parent will be checked when child is checked even when input
+ * values don't contain parent's value.
+ * 2. parent can be configured not to include it's value when children are
+ * checked
+ * 3. each subtree containing a checked input has to return at least one value
+ * on save()
+ * 4. each option has to have unique value
+ *
+ * Has subset of widget interface - overrides the values in widget
+ * * save(): get values
+ * * update(values): set values
+ * * value_changed: event when change happens
+ * * create: creates HTML
+ */
+IPA.option_widget_base = function(spec, that) {
spec = spec || {};
- var that = IPA.input_widget(spec);
+ // when that is specified, this constructor behaves like a mixin
+ that = that || {};
- // default value
- that.checked = spec.checked || false;
+ // classic properties
+ that.name = spec.name;
+ that.label = spec.label;
+ that.tooltip = spec.tooltip;
+ that.value_changed = IPA.observer();
+ that.default_value = spec.default_value || null;
- that.create = function(container) {
+ /**
+ * Jquery reference to current node
+ */
+ that.$node = null;
- that.widget_create(container);
+ /**
+ * Type of rendered inputs: ['checkbox', 'radio']
+ */
+ that.input_type = spec.input_type || 'checkbox';
- container.addClass('checkbox-widget');
+ /**
+ * CSS class for container
+ */
+ that.css_class = spec.css_class || '';
- that.input = $('<input/>', {
- type: 'checkbox',
- name: that.name,
- checked: that.checked,
- title: that.tooltip,
- change: function() {
- that.value_changed.notify([that.save()], that);
- }
- }).appendTo(container);
+ /**
+ * If it's nested widget
+ */
+ that.nested = !!spec.nested;
- if (that.undo) {
- that.create_undo(container);
+ /**
+ * How items should be rendered.
+ *
+ * values: ['inline', 'vertical']
+ */
+ that.layout = spec.layout || 'vertical';
+
+ // private properties
+ that._child_widgets = [];
+ that._input_name = null;
+ that._selector = null;
+ that._option_next_id = 0;
+
+
+ /**
+ * Normalizes options spec
+ */
+ that.prepare_options = function(options) {
+
+ var ret = [];
+
+ if (!options) return options;
+
+ for (var i=0; i<options.length; i++) {
+ ret.push(that.prepare_option(options[i]));
}
- that.create_error_link(container);
+ return ret;
};
- that.save = function() {
- var value = that.input.is(':checked');
- return [value];
+ /**
+ * Option has following interface: {
+ * label: String
+ * value: String
+ * nested: Boolean
+ * widget: Widget
+ * combine_values: Boolean, default true. Whether to include this value
+ * if some of its children is specified
+ * }
+ */
+ that.prepare_option = function(spec) {
+
+ var option = spec;
+
+ if (!option) throw {
+ error: 'Invalid option specified',
+ option: option
+ };
+
+ if (typeof option === 'string') {
+ option = {
+ label: option,
+ value: option
+ };
+ } else {
+ if (option.type || option.factory) {
+ var factory = option.factory || IPA.widget_factories[option.type];
+ if (typeof factory !== 'function') throw {
+ error: 'Invalid factory',
+ factory: factory
+ };
+ option.nested = true;
+ option.widget = factory(option);
+ option.widget.value_changed.attach(that.on_input_change);
+
+ that._child_widgets.push(option.widget);
+ }
+ }
+
+ option.combine_values = option.combine_values === undefined ? true :
+ !!option.combine_values;
+
+ return option;
};
- that.update = function(values) {
- var value;
+ that.add_option = function(option, suppress_update) {
+ that.options.push(that.prepare_option(option));
+ if (!suppress_update) that.update_dom();
+ };
+
+ that.update_dom = function() {
- if (values && values.length) {
- value = values[0];
+ if (that.$node) {
+ var values = that.save();
+ var container = that.$node.parent();
+ that.create(container);
+ that.update(values);
}
+ };
- if (typeof value !== 'boolean') {
- // use default value
- value = that.checked;
+ that.create_options = function(container) {
+ for (var i=0; i<that.options.length; i++) {
+ var option_container = that.create_option_container();
+ var option = that.options[i];
+ that.create_option(option, option_container);
+ option_container.appendTo(container);
}
+ };
- that.input.prop('checked', value);
+ that.create_option_container = function() {
+ return $('<li/>');
};
- that.clear = function() {
- that.input.prop('checked', false);
+ that._create_option = function(option, container) {
+ var input_name = that.get_input_name();
+ var id = that._option_next_id + input_name;
+
+ $('<input/>', {
+ id: id,
+ type: that.input_type,
+ name: input_name,
+ value: option.value,
+ title: option.tooltip || that.tooltip,
+ change: that.on_input_change
+ }).appendTo(container);
+
+ $('<label/>', {
+ text: option.label,
+ title: option.tooltip || that.tooltip,
+ 'for': id
+ }).appendTo(container);
+
+ that.new_option_id();
};
- that.checkbox_save = that.save;
+ that.create_option = function(option, container) {
- return that;
-};
+ that._create_option(option, container);
-IPA.checkboxes_widget = function (spec) {
+ if (option.widget) {
+ option.widget.create(container);
+ }
+ };
- spec = spec || {};
+ that.new_option_id = function() {
+ that._option_next_id++;
+ };
- var that = IPA.input_widget(spec);
+ that.get_input_name = function() {
- that.options = spec.options || [];
- that.direction = spec.direction || 'vertical';
+ if (!that._input_name) {
+ var name = that.name;
+ if (that.input_type === 'radio') {
+ name = IPA.html_util.get_next_id(name);
+ }
+ that._input_name = name;
+ that._selector = 'input[name="'+name+'"]';
+ }
+ return that._input_name;
+ };
that.create = function(container) {
+ that.destroy();
+ that.create_options(that.$node);
+ var css_class = [that.css_class, 'option_widget', that.layout,
+ that.nested ? 'nested': ''].join(' ');
- that.widget_create(container);
+ that.$node = $('<ul/>', { 'class': css_class });
+ that.create_options(that.$node);
- container.addClass('checkboxes-widget');
+ if (container) that.$node.appendTo(container);
+ };
+
+ that.destroy = function() {
+ if (that.$node) {
+ that.$node.empty();
+ that.$node.remove();
+ }
- var vertical = that.direction === 'vertical';
+ for (var i=0; i< that._child_widgets.length; i++) {
+ that._child_widgets[i].destroy();
+ }
+ };
+ that.get_option = function(value) {
for (var i=0; i<that.options.length; i++) {
var option = that.options[i];
- $('<input/>', {
- type: 'checkbox',
- name: that.name,
- value: option.value,
- title: that.tooltip
- }).appendTo(container);
+ if (option.value === value) return option;
+ }
+ return null;
+ };
- $('<label/>', {
- text: option.label,
- title: that.tooltip
- }).appendTo(container);
+ /**
+ * Get values for specific option and its children.
+ *
+ * If option is not specified, gets values of all options and children.
+ */
+ that.get_values = function(option) {
- if (vertical) {
- $('<br/>').appendTo(container);
+ var values = [];
+ if (option) {
+ values.push(option.value);
+ if (option.widget) {
+ values.push.apply(values, option.widget.get_values());
+ }
+ } else {
+ for (var i=0; i<that.options.length; i++) {
+ var vals = that.get_values(that.options[i]);
+ values.push.apply(values, vals);
}
}
- if (that.undo) {
- that.create_undo(container);
- }
+ return values;
+ };
- var input = $('input[name="'+that.name+'"]', that.container);
- input.change(function() {
- that.value_changed.notify([that.save()], that);
- });
+ that.on_input_change = function(e) {
- that.create_error_link(container);
+ // uncheck child widgets on uncheck of parent option
+ if (that._child_widgets.length > 0) {
+
+ var parents_selected = [];
+
+ $(that._selector+':checked', that.container).each(function() {
+ var value = $(this).val();
+ var option = that.get_option(value);
+ if (option && option.nested) {
+ parents_selected.push(value);
+ }
+ });
+
+ for (var i=0; i<that.options.length; i++) {
+
+ var option = that.options[i];
+
+ if (option.nested) {
+ var selected = parents_selected.indexOf(option.value) > -1;
+ option.widget.set_enabled(selected);
+ }
+ }
+ }
+ that.value_changed.notify([], that);
};
that.save = function() {
+
var values = [];
- $('input[name="'+that.name+'"]:checked', that.container).each(function() {
- values.push($(this).val());
- });
+ if (that.$node) {
+
+ $(that._selector+':checked', that.container).each(function() {
+ var value = $(this).val();
+ var child_values = [];
+ var option = that.get_option(value);
+
+ if (option.widget) {
+ child_values = option.widget.save();
+ values.push.apply(values, child_values);
+ }
+
+ // don't use value if cannot be combined with children's value
+ if (!(child_values.length > 0 && !option.combine_values)) {
+ values.push(value);
+ }
+ });
+ }
return values;
};
that.update = function(values) {
- var inputs = $('input[name="'+that.name+'"]', that.container);
- inputs.prop('checked', false);
- for (var j=0; values && j<values.length; j++) {
- var value = values[j];
- var input = $('input[name="'+that.name+'"][value="'+value+'"]', that.container);
- if (!input.length) continue;
- input.prop('checked', true);
+ var check = function(selector, uncheck) {
+ $(selector, that.$node).prop('checked', !uncheck);
+ };
+
+ if (that.$node) {
+
+ // uncheck all inputs
+ check(that._selector, true /*uncheck*/);
+
+ if (values && values.length > 0) {
+
+ // check the option when option or some of its child should be
+ // checked
+ for (var i=0; i<that.options.length; i++) {
+ var option = that.options[i];
+ var opt_vals = that.get_values(option);
+ var has_opt = array.some(values, function(val) {
+ return array.indexOf(opt_vals, val) > -1;
+ });
+
+ if (has_opt) {
+ check(that._selector+'[value="'+ option.value +'"]');
+ }
+ if (option.widget) {
+ option.widget.set_enabled(has_opt);
+ }
+ }
+ } else {
+ // select default if none specified
+ if (that.default_value !== null) {
+ check(that._selector+'[value="'+that.default_value+'"]');
+ } else {
+ // otherwise select empty
+ check(that._selector+'[value=""]');
+ }
+ }
+
+ for (var j=0; j<that._child_widgets.length; j++) {
+ that._child_widgets[j].update(values);
+ }
}
};
- that.clear = function() {
- $('input[name="'+that.name+'"]').prop('checked', false);
+ that.set_enabled = function(enabled) {
+
+ $(that._selector, that.container).prop('disabled', !enabled);
+ if (!enabled) that.clear();
+ for (var i=0; i<that._child_widgets.length;i++){
+ that._child_widgets[i].set_enabled(enabled);
+ }
};
- that.add_option = function(option) {
- that.options.push(option);
+
+ that.clear = function() {
+
+ $(that._selector, that.$node).prop('checked', false);
+
+ if (that.default_value) {
+ $(that._selector+'[value="'+that.default_value+'"]', that.$node).
+ prop('checked', true);
+ }
+
+ for (var i=0; i<that._child_widgets.length; i++) {
+ that._child_widgets[i].clear();
+ }
};
- // methods that should be invoked by subclasses
- that.checkboxes_update = that.update;
+ that.options = that.prepare_options(spec.options || []);
+
+ that.owb_create = that.create;
+ that.owb_save = that.save;
+ that.owb_update = that.update;
return that;
};
@@ -764,89 +1012,65 @@ IPA.checkboxes_widget = function (spec) {
IPA.radio_widget = function(spec) {
spec = spec || {};
+ spec.input_type = spec.input_type || 'radio';
+ spec.layout = spec.layout || 'inline';
var that = IPA.input_widget(spec);
-
- that.default_value = spec.default_value;
- that.options = spec.options;
+ IPA.option_widget_base(spec, that);
that.create = function(container) {
-
that.widget_create(container);
-
+ that.owb_create(container);
container.addClass('radio-widget');
- var name = IPA.html_util.get_next_id(that.name+'-');
- that.selector = 'input[name="'+name+'"]';
-
- for (var i=0; i<that.options.length; i++) {
- var option = that.options[i];
-
- var id = name+'-'+i;
-
- $('<input/>', {
- id: id,
- type: 'radio',
- name: name,
- value: option.value
- }).appendTo(container);
-
- $('<label/>', {
- text: option.label,
- 'for': id
- }).appendTo(container);
- }
-
if (that.undo) {
that.create_undo(container);
}
- var input = $(that.selector, that.container);
- input.change(function() {
- that.value_changed.notify([that.save()], that);
- });
-
that.create_error_link(container);
};
+ return that;
+};
- that.save = function() {
- var input = $(that.selector+':checked', that.container);
- if (!input.length) return [];
- return [input.val()];
- };
+IPA.checkbox_widget = function (spec) {
- that.update = function(values) {
+ var checked = 'checked';
- $(that.selector, that.container).each(function() {
- var input = this;
- input.checked = false;
- });
+ spec = spec || {};
+ spec.input_type = spec.input_type || 'checkbox';
- var value = values && values.length ? values[0] : '';
- var input = $(that.selector+'[value="'+value+'"]', that.container);
- if (input.length) {
- input.prop('checked', true);
- } else if (that.default_value) {
- input = $(that.selector+'[value="'+that.default_value+'"]', that.container);
- input.prop('checked', true);
- }
+ if (!spec.options) {
+ spec.options = [ { value: checked, label: '' } ];
+ }
+
+ if (spec.checked) spec.default_value = spec.checked;
+
+ var that = IPA.radio_widget(spec);
- that.value_changed.notify([that.save()], that);
+ that.save = function() {
+ var values = that.owb_save();
+ return [values.length > 0];
};
- that.clear = function() {
- $(that.selector, that.container).prop('checked', false);
+ that.update = function(values) {
+ var value = values ? values[0] : '';
- if (that.default_value) {
- var input = $(that.selector+'[value="'+that.default_value+'"]', that.container);
- input.prop('checked', true);
+ if (typeof value !== 'boolean') {
+ value = that.default_value || '';
+ } else {
+ value = value ? checked : '';
}
+ that.owb_update([value]);
};
- // methods that should be invoked by subclasses
- that.radio_create = that.create;
- that.radio_save = that.save;
+ return that;
+};
+IPA.checkboxes_widget = function (spec) {
+ spec = spec || {};
+ spec.input_type = spec.input_type || 'checkbox';
+ spec.layout = spec.layout || 'vertical';
+ var that = IPA.radio_widget(spec);
return that;
};