* multicheck control * [checkboxes] replace in domain settings * [checkboxes] use in setup_wizard domains * [multicheck] minor * [multicheck] set and get value as an array * [multicheck] make controls.less * basic view control with value as array object * load active domains * selenium helper: multicheck * [multicheck] make field if not present * [multicheck] append control in an HTML fieldversion-14
@@ -2,58 +2,62 @@ | |||
// For license information, please see license.txt | |||
frappe.ui.form.on('Domain Settings', { | |||
onload: function(frm) { | |||
let domains = $('<div class="domain-editor">') | |||
.appendTo(frm.fields_dict.domains_html.wrapper); | |||
if(!frm.domain_editor) { | |||
frm.domain_editor = new frappe.DomainsEditor(domains, frm); | |||
before_load: function(frm) { | |||
if(!frm.domains_multicheck) { | |||
frm.domains_multicheck = frappe.ui.form.make_control({ | |||
parent: frm.fields_dict.domains_html.$wrapper, | |||
df: { | |||
fieldname: "domains_multicheck", | |||
fieldtype: "MultiCheck", | |||
get_data: () => { | |||
let active_domains = (frm.doc.active_domains || []).map(row => row.domain); | |||
return frappe.boot.all_domains.map(domain => { | |||
return { | |||
label: domain, | |||
value: domain, | |||
checked: active_domains.includes(domain) | |||
}; | |||
}); | |||
} | |||
}, | |||
render_input: true | |||
}); | |||
frm.domains_multicheck.refresh_input(); | |||
} | |||
frm.domain_editor.show(); | |||
}, | |||
validate: function(frm) { | |||
if(frm.domain_editor) { | |||
frm.domain_editor.set_items_in_table(); | |||
} | |||
frm.trigger('set_options_in_table'); | |||
}, | |||
}); | |||
frappe.DomainsEditor = frappe.CheckboxEditor.extend({ | |||
init: function(wrapper, frm) { | |||
var opts = {}; | |||
$.extend(opts, { | |||
wrapper: wrapper, | |||
frm: frm, | |||
field_mapper: { | |||
child_table_field: "active_domains", | |||
item_field: "domain", | |||
cdt: "Has Domain" | |||
}, | |||
attribute: 'data-domain', | |||
checkbox_selector: false, | |||
get_items: this.get_all_domains, | |||
editor_template: this.get_template() | |||
set_options_in_table: function(frm) { | |||
let selected_options = frm.domains_multicheck.get_value(); | |||
let unselected_options = frm.domains_multicheck.options | |||
.map(option => option.value) | |||
.filter(value => { | |||
return !selected_options.includes(value); | |||
}); | |||
let map = {}, list = []; | |||
(frm.doc.active_domains || []).map(row => { | |||
map[row.domain] = row.name; | |||
list.push(row.domain); | |||
}); | |||
this._super(opts); | |||
}, | |||
unselected_options.map(option => { | |||
if(list.includes(option)) { | |||
frappe.model.clear_doc("Has Domain", map[option]); | |||
} | |||
}); | |||
get_template: function() { | |||
return ` | |||
<div class="checkbox" data-domain="{{item}}"> | |||
<label> | |||
<input type="checkbox"> | |||
<span class="label-area small">{{ __(item) }}</span> | |||
</label> | |||
</div> | |||
`; | |||
}, | |||
selected_options.map(option => { | |||
if(!list.includes(option)) { | |||
frappe.model.clear_doc("Has Domain", map[option]); | |||
let row = frappe.model.add_child(frm.doc, "Has Domain", "active_domains"); | |||
row.domain = option; | |||
} | |||
}); | |||
get_all_domains: function() { | |||
// return all the domains available in the system | |||
this.items = frappe.boot.all_domains; | |||
this.render_items(); | |||
}, | |||
}); | |||
refresh_field('active_domains'); | |||
} | |||
}); |
@@ -18,7 +18,7 @@ | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "domains", | |||
"fieldname": "active_domains_sb", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
@@ -27,7 +27,7 @@ | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Domains", | |||
"label": "Active Domains", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
@@ -57,7 +57,7 @@ | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Domains", | |||
"label": "Domains HTML", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
@@ -95,7 +95,7 @@ | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -114,7 +114,7 @@ | |||
"issingle": 1, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2017-05-12 17:01:18.615000", | |||
"modified": "2017-12-05 17:36:46.842134", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Domain Settings", | |||
@@ -7,6 +7,7 @@ frappe.setup = { | |||
events: {}, | |||
data: {}, | |||
utils: {}, | |||
domains: [], | |||
on: function(event, fn) { | |||
if(!frappe.setup.events[event]) { | |||
@@ -26,7 +27,8 @@ frappe.setup = { | |||
} | |||
frappe.pages['setup-wizard'].on_page_load = function(wrapper) { | |||
var requires = (frappe.boot.setup_wizard_requires || []); | |||
let requires = (frappe.boot.setup_wizard_requires || []); | |||
frappe.require(requires, function() { | |||
frappe.call({ | |||
@@ -216,10 +218,10 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||
get_setup_slides_filtered_by_domain() { | |||
var filtered_slides = []; | |||
frappe.setup.slides.forEach(function(slide) { | |||
if(frappe.setup.domain) { | |||
var domains = slide.domains; | |||
if (domains.indexOf('all') !== -1 || | |||
domains.indexOf(frappe.setup.domain.toLowerCase()) !== -1) { | |||
if(frappe.setup.domains) { | |||
let active_domains = frappe.setup.domains; | |||
if (!slide.domains || | |||
slide.domains.filter(d => active_domains.includes(d)).length > 0) { | |||
filtered_slides.push(slide); | |||
} | |||
} else { | |||
@@ -311,7 +313,6 @@ frappe.setup.slides_settings = [ | |||
{ | |||
// Welcome (language) slide | |||
name: "welcome", | |||
domains: ["all"], | |||
title: __("Hello!"), | |||
icon: "fa fa-world", | |||
// help: __("Let's prepare the system for first use."), | |||
@@ -344,7 +345,6 @@ frappe.setup.slides_settings = [ | |||
{ | |||
// Region slide | |||
name: 'region', | |||
domains: ["all"], | |||
title: __("Select Your Region"), | |||
icon: "fa fa-flag", | |||
// help: __("Select your Country, Time Zone and Currency"), | |||
@@ -376,7 +376,6 @@ frappe.setup.slides_settings = [ | |||
{ | |||
// Profile slide | |||
name: 'user', | |||
domains: ["all"], | |||
title: __("The First User: You"), | |||
icon: "fa fa-user", | |||
fields: [ | |||
@@ -55,7 +55,8 @@ | |||
"public/js/frappe/form/controls/autocomplete.js", | |||
"public/js/frappe/form/controls/barcode.js", | |||
"public/js/frappe/form/controls/geolocation.js", | |||
"public/js/frappe/form/controls/multiselect.js" | |||
"public/js/frappe/form/controls/multiselect.js", | |||
"public/js/frappe/form/controls/multicheck.js" | |||
], | |||
"js/dialog.min.js": [ | |||
"public/js/frappe/dom.js", | |||
@@ -120,7 +121,8 @@ | |||
"public/css/desktop.css", | |||
"public/css/form.css", | |||
"public/css/mobile.css", | |||
"public/css/kanban.css" | |||
"public/css/kanban.css", | |||
"public/css/controls.css" | |||
], | |||
"css/frappe-rtl.css": [ | |||
"public/css/bootstrap-rtl.css", | |||
@@ -168,7 +170,6 @@ | |||
"public/js/frappe/socketio_client.js", | |||
"public/js/frappe/router.js", | |||
"public/js/frappe/defaults.js", | |||
"public/js/frappe/checkbox_editor.js", | |||
"public/js/frappe/roles_editor.js", | |||
"public/js/lib/microtemplate.js", | |||
@@ -0,0 +1,6 @@ | |||
.unit-checkbox { | |||
color: #36414c; | |||
} | |||
.frappe-control .select-all { | |||
margin-right: 5px; | |||
} |
@@ -1,140 +0,0 @@ | |||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
// opts: | |||
// frm | |||
// wrapper | |||
// get_items | |||
// add_btn_label | |||
// remove_btn_label | |||
// field_mapper: | |||
// cdt | |||
// child_table_field | |||
// item_field | |||
// attribute | |||
frappe.CheckboxEditor = Class.extend({ | |||
init: function(opts) { | |||
$.extend(this, opts); | |||
this.doctype = this.field_mapper.cdt; | |||
this.fieldname = this.field_mapper.child_table_field; | |||
this.item_fieldname = this.field_mapper.item_field; | |||
$(this.wrapper).html('<div class="help">' + __("Loading") + '...</div>'); | |||
if(this.get_items) { | |||
this.get_items(); | |||
} | |||
}, | |||
render_items: function(callback) { | |||
let me = this; | |||
$(this.wrapper).empty(); | |||
if(this.checkbox_selector) { | |||
let toolbar = $('<p><button class="btn btn-default btn-add btn-sm" style="margin-right: 5px;"></button>\ | |||
<button class="btn btn-sm btn-default btn-remove"></button></p>').appendTo($(this.wrapper)); | |||
toolbar.find(".btn-add") | |||
.html(__(this.add_btn_label)) | |||
.on("click", function() { | |||
$(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { | |||
if(!$(check).is(":checked")) { | |||
check.checked = true; | |||
} | |||
}); | |||
}); | |||
toolbar.find(".btn-remove") | |||
.html(__(this.remove_btn_label)) | |||
.on("click", function() { | |||
$(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { | |||
if($(check).is(":checked")) { | |||
check.checked = false; | |||
} | |||
}); | |||
}); | |||
} | |||
$.each(this.items, function(i, item) { | |||
$(me.wrapper).append(frappe.render(me.editor_template, {'item': item})); | |||
}); | |||
$(this.wrapper).find('input[type="checkbox"]').change(function() { | |||
if(me.fieldname && me.doctype && me.item_field) { | |||
me.set_items_in_table(); | |||
me.frm.dirty(); | |||
} | |||
}); | |||
callback && callback() | |||
}, | |||
show: function() { | |||
let me = this; | |||
// uncheck all items | |||
$(this.wrapper).find('input[type="checkbox"]') | |||
.each(function(i, checkbox) { checkbox.checked = false; }); | |||
// set user items as checked | |||
$.each((me.frm.doc[this.fieldname] || []), function(i, row) { | |||
let selector = repl('[%(attribute)s="%(value)s"] input[type="checkbox"]', { | |||
attribute: me.attribute, | |||
value: row[me.item_fieldname] | |||
}); | |||
let checkbox = $(me.wrapper) | |||
.find(selector).get(0); | |||
if(checkbox) checkbox.checked = true; | |||
}); | |||
}, | |||
get_selected_unselected_items: function() { | |||
let checked_items = []; | |||
let unchecked_items = []; | |||
let selector = repl('[%(attribute)s]', { attribute: this.attribute }); | |||
let me = this; | |||
$(this.wrapper).find(selector).each(function() { | |||
if($(this).find('input[type="checkbox"]:checked').length) { | |||
checked_items.push($(this).attr(me.attribute)); | |||
} else { | |||
unchecked_items.push($(this).attr(me.attribute)); | |||
} | |||
}); | |||
return { | |||
checked_items: checked_items, | |||
unchecked_items: unchecked_items | |||
} | |||
}, | |||
set_items_in_table: function() { | |||
let opts = this.get_selected_unselected_items(); | |||
let existing_items_map = {}; | |||
let existing_items_list = []; | |||
let me = this; | |||
$.each(me.frm.doc[this.fieldname] || [], function(i, row) { | |||
existing_items_map[row[me.item_fieldname]] = row.name; | |||
existing_items_list.push(row[me.item_fieldname]); | |||
}); | |||
// remove unchecked items | |||
$.each(opts.unchecked_items, function(i, item) { | |||
if(existing_items_list.indexOf(item)!=-1) { | |||
frappe.model.clear_doc(me.doctype, existing_items_map[item]); | |||
} | |||
}); | |||
// add new items that are checked | |||
$.each(opts.checked_items, function(i, item) { | |||
if(existing_items_list.indexOf(item)==-1) { | |||
let row = frappe.model.add_child(me.frm.doc, me.doctype, me.fieldname); | |||
row[me.item_fieldname] = item; | |||
} | |||
}); | |||
refresh_field(this.fieldname); | |||
} | |||
}); |
@@ -38,6 +38,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||
} else { | |||
this.label_area = this.label_span = this.$wrapper.find("label").get(0); | |||
this.input_area = this.$wrapper.find(".control-input").get(0); | |||
this.$input_wrapper = this.$wrapper.find(".control-input-wrapper"); | |||
// keep a separate display area to rendered formatted values | |||
// like links, currencies, HTMLs etc. | |||
this.disp_area = this.$wrapper.find(".control-value").get(0); | |||
@@ -0,0 +1,139 @@ | |||
frappe.ui.form.ControlMultiCheck = frappe.ui.form.Control.extend({ | |||
// UI: multiple checkboxes | |||
// Value: Array of values | |||
// Options: Array of label/value/checked option objects | |||
make() { | |||
this._super(); | |||
// this.$label = $(`<label class="control-label">${this.df.label}</label>`).appendTo(this.wrapper); | |||
this.$load_state = $('<div class="load-state text-muted small">' + __("Loading") + '...</div>'); | |||
this.$select_buttons = this.get_select_buttons().appendTo(this.wrapper); | |||
this.$load_state.appendTo(this.wrapper); | |||
this.$checkbox_area = $('<div class="checkbox-options"></div>').appendTo(this.wrapper); | |||
this.set_options(); | |||
this.bind_checkboxes(); | |||
}, | |||
refresh_input() { | |||
this.options.map(option => option.value).forEach(value => { | |||
$(this.wrapper) | |||
.find(`:checkbox[data-unit=${value}]`) | |||
.prop("checked", this.selected_options.includes(value)); | |||
}); | |||
}, | |||
set_options() { | |||
this.$load_state.show(); | |||
this.$select_buttons.hide(); | |||
this.parse_df_options(); | |||
if(this.df.get_data) { | |||
if(typeof this.df.get_data().then == 'function') { | |||
this.df.get_data().then(results => { | |||
this.options = results; | |||
this.make_checkboxes(); | |||
}); | |||
} else { | |||
this.options = this.df.get_data(); | |||
this.make_checkboxes(); | |||
} | |||
} else { | |||
this.make_checkboxes(); | |||
} | |||
}, | |||
parse_df_options() { | |||
if(Array.isArray(this.df.options)) { | |||
this.options = this.df.options; | |||
} else if(this.df.options && this.df.options.length>0 && frappe.utils.is_json(this.df.options)) { | |||
let args = JSON.parse(this.df.options); | |||
if(Array.isArray(args)) { | |||
this.options = args; | |||
} else if(Array.isArray(args.options)) { | |||
if(args.select_all) { | |||
this.select_all = true; | |||
} | |||
this.options = args.options; | |||
} | |||
} else { | |||
this.options = []; | |||
} | |||
}, | |||
make_checkboxes() { | |||
this.set_checked_options(); | |||
this.$load_state.hide(); | |||
this.$checkbox_area.empty(); | |||
this.options.forEach(option => { | |||
this.get_checkbox_element(option).appendTo(this.$checkbox_area); | |||
}); | |||
if(this.select_all) { | |||
this.setup_select_all(); | |||
} | |||
}, | |||
bind_checkboxes() { | |||
$(this.wrapper).on('change', ':checkbox', e => { | |||
const $checkbox = $(e.target); | |||
const option_name = $checkbox.attr("data-unit"); | |||
if($checkbox.is(':checked')) { | |||
this.selected_options.push(option_name); | |||
} else { | |||
let index = this.selected_options.indexOf(option_name); | |||
this.selected_options.splice(index, 1); | |||
} | |||
this.df.on_change && this.df.on_change(); | |||
}); | |||
}, | |||
set_checked_options() { | |||
this.selected_options = this.options | |||
.filter(o => o.checked) | |||
.map(o => o.value); | |||
}, | |||
setup_select_all() { | |||
this.$select_buttons.show(); | |||
let select_all = (deselect=false) => { | |||
$(this.wrapper).find(`:checkbox`).prop("checked", deselect).trigger('click'); | |||
}; | |||
this.$select_buttons.find('.select-all').on('click', () => { | |||
select_all(); | |||
}); | |||
this.$select_buttons.find('.deselect-all').on('click', () => { | |||
select_all(true); | |||
}); | |||
}, | |||
get_value() { | |||
return this.selected_options; | |||
}, | |||
get_checked_options() { | |||
return this.get_value(); | |||
}, | |||
get_unchecked_options() { | |||
return this.options.map(o => o.value) | |||
.filter(value => !this.selected_options.includes(value)); | |||
}, | |||
get_checkbox_element(option) { | |||
return $(` | |||
<div class="checkbox unit-checkbox"> | |||
<label> | |||
<input type="checkbox" data-unit="${option.value}"> | |||
</input> | |||
<span class="label-area small" data-unit="${option.value}">${__(option.label)}</span> | |||
</label> | |||
</div>`); | |||
}, | |||
get_select_buttons() { | |||
return $(`<div><button class="btn btn-xs btn-default select-all"> | |||
${__("Select All")}</button> | |||
<button class="btn btn-xs btn-default deselect-all"> | |||
${__("Unselect All")}</button></div>`); | |||
} | |||
}); |
@@ -239,6 +239,26 @@ frappe.ui.form.Layout = Class.extend({ | |||
}); | |||
}, | |||
refresh_fields: function(fields) { | |||
let fieldnames = fields.map((field) => { | |||
if(field.fieldname) return field.fieldname; | |||
}); | |||
this.fields_list.map(fieldobj => { | |||
if(fieldnames.includes(fieldobj.df.fieldname)) { | |||
fieldobj.refresh(); | |||
if(fieldobj.df["default"]) { | |||
fieldobj.set_input(fieldobj.df["default"]); | |||
} | |||
} | |||
}); | |||
}, | |||
add_fields: function(fields) { | |||
this.render(fields); | |||
this.refresh_fields(fields); | |||
}, | |||
refresh_section_collapse: function() { | |||
if(!this.doc) return; | |||
@@ -41,6 +41,14 @@ frappe.utils = { | |||
is_md: function() { | |||
return $(document).width() < 1199 && $(document).width() >= 991; | |||
}, | |||
is_json: function(str) { | |||
try { | |||
JSON.parse(str); | |||
} catch (e) { | |||
return false; | |||
} | |||
return true; | |||
}, | |||
strip_whitespace: function(html) { | |||
return (html || "").replace(/<p>\s*<\/p>/g, "").replace(/<br>(\s*<br>\s*)+/g, "<br><br>"); | |||
}, | |||
@@ -41,24 +41,6 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ | |||
} | |||
}, | |||
add_fields: function(fields) { | |||
this.render(fields); | |||
this.refresh_fields(fields); | |||
}, | |||
refresh_fields: function(fields) { | |||
let fieldnames = fields.map((field) => { | |||
if(field.fieldname) return field.fieldname; | |||
}); | |||
this.fields_list.map(fieldobj => { | |||
if(fieldnames.includes(fieldobj.df.fieldname)) { | |||
fieldobj.refresh(); | |||
if(fieldobj.df["default"]) { | |||
fieldobj.set_input(fieldobj.df["default"]); | |||
} | |||
} | |||
}); | |||
}, | |||
first_button: false, | |||
focus_on_first_input: function() { | |||
if(this.no_focus) return; | |||
@@ -194,6 +194,10 @@ frappe.ui.Slide = class Slide { | |||
return this.form.get_field(fieldname); | |||
} | |||
get_value(fieldname) { | |||
return this.form.get_value(fieldname); | |||
} | |||
destroy() { | |||
this.$body.remove(); | |||
} | |||
@@ -0,0 +1,7 @@ | |||
.unit-checkbox{ | |||
color: #36414c; | |||
} | |||
.frappe-control .select-all { | |||
margin-right: 5px; | |||
} |
@@ -93,6 +93,13 @@ class TestDriver(object): | |||
time.sleep(0.2) | |||
elem.send_keys(text) | |||
def set_multicheck(self, fieldname, values): | |||
for value in values: | |||
path = '//div[@data-fieldname="{0}"]//span[@data-unit="{1}"]'.format(fieldname, value) | |||
elem = self.wait_for(xpath=path) | |||
time.sleep(0.2) | |||
elem.click() | |||
def set_text_editor(self, fieldname, text): | |||
elem = self.wait_for(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) | |||
time.sleep(0.2) | |||