Просмотр исходного кода

[Multicheck control] (#4529)

* 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 field
version-14
Prateeksha Singh 7 лет назад
committed by Nabin Hait
Родитель
Сommit
d1aa5c60b4
15 измененных файлов: 1546 добавлений и 1508 удалений
  1. +1289
    -1289
      frappe/core/doctype/docfield/docfield.json
  2. +49
    -45
      frappe/core/doctype/domain_settings/domain_settings.js
  3. +5
    -5
      frappe/core/doctype/domain_settings/domain_settings.json
  4. +7
    -8
      frappe/desk/page/setup_wizard/setup_wizard.js
  5. +4
    -3
      frappe/public/build.json
  6. +6
    -0
      frappe/public/css/controls.css
  7. +0
    -140
      frappe/public/js/frappe/checkbox_editor.js
  8. +1
    -0
      frappe/public/js/frappe/form/controls/base_input.js
  9. +139
    -0
      frappe/public/js/frappe/form/controls/multicheck.js
  10. +20
    -0
      frappe/public/js/frappe/form/layout.js
  11. +8
    -0
      frappe/public/js/frappe/misc/utils.js
  12. +0
    -18
      frappe/public/js/frappe/ui/field_group.js
  13. +4
    -0
      frappe/public/js/frappe/ui/slides.js
  14. +7
    -0
      frappe/public/less/controls.less
  15. +7
    -0
      frappe/utils/selenium_testdriver.py

+ 1289
- 1289
frappe/core/doctype/docfield/docfield.json
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 49
- 45
frappe/core/doctype/domain_settings/domain_settings.js Просмотреть файл

@@ -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');
}
});

+ 5
- 5
frappe/core/doctype/domain_settings/domain_settings.json Просмотреть файл

@@ -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
- 8
frappe/desk/page/setup_wizard/setup_wizard.js Просмотреть файл

@@ -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: [


+ 4
- 3
frappe/public/build.json Просмотреть файл

@@ -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",



+ 6
- 0
frappe/public/css/controls.css Просмотреть файл

@@ -0,0 +1,6 @@
.unit-checkbox {
color: #36414c;
}
.frappe-control .select-all {
margin-right: 5px;
}

+ 0
- 140
frappe/public/js/frappe/checkbox_editor.js Просмотреть файл

@@ -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);
}
});

+ 1
- 0
frappe/public/js/frappe/form/controls/base_input.js Просмотреть файл

@@ -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);


+ 139
- 0
frappe/public/js/frappe/form/controls/multicheck.js Просмотреть файл

@@ -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>`);
}
});

+ 20
- 0
frappe/public/js/frappe/form/layout.js Просмотреть файл

@@ -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;



+ 8
- 0
frappe/public/js/frappe/misc/utils.js Просмотреть файл

@@ -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>");
},


+ 0
- 18
frappe/public/js/frappe/ui/field_group.js Просмотреть файл

@@ -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;


+ 4
- 0
frappe/public/js/frappe/ui/slides.js Просмотреть файл

@@ -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();
}


+ 7
- 0
frappe/public/less/controls.less Просмотреть файл

@@ -0,0 +1,7 @@
.unit-checkbox{
color: #36414c;
}

.frappe-control .select-all {
margin-right: 5px;
}

+ 7
- 0
frappe/utils/selenium_testdriver.py Просмотреть файл

@@ -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)


Загрузка…
Отмена
Сохранить