From d08a332a85c0013125936f2c98fed9be352eccab Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 30 Dec 2021 14:50:33 +0530 Subject: [PATCH 01/30] feat: Phone Field Control Type --- frappe/boot.py | 7 + frappe/core/doctype/docfield/docfield.json | 1085 +++++++++-------- .../doctype/custom_field/custom_field.json | 4 +- .../customize_form_field.json | 4 +- frappe/database/mariadb/database.py | 3 +- frappe/database/postgres/database.py | 3 +- frappe/model/__init__.py | 3 +- frappe/model/base_document.py | 12 + frappe/model/meta.py | 3 + .../public/js/frappe/form/controls/control.js | 1 + .../public/js/frappe/form/controls/phone.js | 159 +++ .../js/frappe/phone_picker/phone_picker.js | 92 ++ frappe/public/js/frappe/utils/utils.js | 13 + frappe/public/scss/common/controls.scss | 1 + frappe/public/scss/common/phone_picker.scss | 119 ++ 15 files changed, 960 insertions(+), 549 deletions(-) create mode 100644 frappe/public/js/frappe/form/controls/phone.js create mode 100644 frappe/public/js/frappe/phone_picker/phone_picker.js create mode 100644 frappe/public/scss/common/phone_picker.scss diff --git a/frappe/boot.py b/frappe/boot.py index cf2b914436..026c0d6f16 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -58,6 +58,7 @@ def get_bootinfo(): bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1}) bootinfo.navbar_settings = get_navbar_settings() bootinfo.notification_settings = get_notification_settings() + get_country_codes(bootinfo) # ipinfo if frappe.session.data.get('ipinfo'): @@ -324,3 +325,9 @@ def get_desk_settings(): def get_notification_settings(): return frappe.get_cached_doc('Notification Settings', frappe.session.user) + +def get_country_codes(bootinfo): + country_codes = { + "United States": {"isd":"+1","code":"us" }, + "India": {"isd":"+91","code":"in" }} + bootinfo.country_codes = frappe._dict(country_codes) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 6910d615d3..56c3ff6037 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -1,543 +1,544 @@ { - "actions": [], - "autoname": "hash", - "creation": "2013-02-22 01:27:33", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label_and_type", - "label", - "fieldtype", - "fieldname", - "precision", - "length", - "non_negative", - "hide_days", - "hide_seconds", - "reqd", - "search_index", - "column_break_18", - "options", - "defaults_section", - "default", - "column_break_6", - "fetch_from", - "fetch_if_empty", - "visibility_section", - "hidden", - "bold", - "allow_in_quick_entry", - "translatable", - "print_hide", - "print_hide_if_no_value", - "report_hide", - "column_break_28", - "depends_on", - "collapsible", - "collapsible_depends_on", - "hide_border", - "list__search_settings_section", - "in_list_view", - "in_standard_filter", - "in_preview", - "column_break_35", - "in_filter", - "in_global_search", - "permissions", - "read_only", - "allow_on_submit", - "ignore_user_permissions", - "allow_bulk_edit", - "column_break_13", - "permlevel", - "ignore_xss_filter", - "constraints_section", - "unique", - "no_copy", - "set_only_once", - "remember_last_selected_value", - "column_break_38", - "mandatory_depends_on", - "read_only_depends_on", - "display", - "print_width", - "width", - "max_height", - "columns", - "column_break_22", - "description", - "oldfieldname", - "oldfieldtype" - ], - "fields": [{ - "fieldname": "label_and_type", - "fieldtype": "Section Break" - }, - { - "bold": 1, - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "oldfieldname": "label", - "oldfieldtype": "Data", - "print_width": "163", - "search_index": 1, - "width": "163" - }, - { - "bold": 1, - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Type", - "oldfieldname": "fieldtype", - "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", - "reqd": 1, - "search_index": 1 - }, - { - "bold": 1, - "fieldname": "fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Name", - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "search_index": 1 - }, - { - "default": "0", - "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", - "fieldname": "reqd", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Mandatory", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", - "description": "Set non-standard precision for a Float or Currency field", - "fieldname": "precision", - "fieldtype": "Select", - "label": "Precision", - "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9", - "print_hide": 1 - }, - { - "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", - "fieldname": "length", - "fieldtype": "Int", - "label": "Length" - }, - { - "default": "0", - "fieldname": "search_index", - "fieldtype": "Check", - "label": "Index", - "oldfieldname": "search_index", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View", - "print_width": "70px", - "width": "70px" - }, - { - "default": "0", - "fieldname": "in_standard_filter", - "fieldtype": "Check", - "label": "In List Filter" - }, - { - "default": "0", - "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", - "fieldname": "in_global_search", - "fieldtype": "Check", - "label": "In Global Search" - }, - { - "default": "0", - "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", - "fieldname": "in_preview", - "fieldtype": "Check", - "label": "In Preview" - }, - { - "default": "0", - "fieldname": "allow_in_quick_entry", - "fieldtype": "Check", - "label": "Allow in Quick Entry" - }, - { - "default": "0", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold" - }, - { - "default": "0", - "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fieldname": "translatable", - "fieldtype": "Check", - "label": "Translatable" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype===\"Section Break\"", - "fieldname": "collapsible", - "fieldtype": "Check", - "label": "Collapsible", - "length": 255 - }, - { - "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", - "fieldname": "collapsible_depends_on", - "fieldtype": "Code", - "label": "Collapsible Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", - "fieldname": "options", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text" - }, - { - "fieldname": "default", - "fieldtype": "Small Text", - "label": "Default", - "max_height": "3rem", - "oldfieldname": "default", - "oldfieldtype": "Text" - }, - { - "fieldname": "fetch_from", - "fieldtype": "Small Text", - "label": "Fetch From" - }, - { - "default": "0", - "fieldname": "fetch_if_empty", - "fieldtype": "Check", - "label": "Fetch only if value is not set" - }, - { - "fieldname": "permissions", - "fieldtype": "Section Break", - "label": "Permissions" - }, - { - "fieldname": "depends_on", - "fieldtype": "Code", - "label": "Display Depends On (JS)", - "length": 255, - "max_height": "3rem", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", - "options": "JS" - }, - { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "label": "Hidden", - "oldfieldname": "hidden", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique" - }, - { - "default": "0", - "fieldname": "set_only_once", - "fieldtype": "Check", - "label": "Set only once" - }, - { - "default": "0", - "depends_on": "eval: doc.fieldtype == \"Table\"", - "fieldname": "allow_bulk_edit", - "fieldtype": "Check", - "label": "Allow Bulk Edit" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "label": "Perm Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions" - }, - { - "default": "0", - "depends_on": "eval: parent.is_submittable", - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "depends_on": "eval:(doc.fieldtype == 'Link')", - "fieldname": "remember_last_selected_value", - "fieldtype": "Check", - "label": "Remember Last Selected Value" - }, - { - "default": "0", - "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", - "fieldname": "ignore_xss_filter", - "fieldtype": "Check", - "label": "Ignore XSS Filter" - }, - { - "fieldname": "display", - "fieldtype": "Section Break", - "label": "Display" - }, - { - "default": "0", - "fieldname": "in_filter", - "fieldtype": "Check", - "label": "In Filter", - "oldfieldname": "in_filter", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "no_copy", - "fieldtype": "Check", - "label": "No Copy", - "oldfieldname": "no_copy", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "print_hide", - "fieldtype": "Check", - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", - "fieldname": "print_hide_if_no_value", - "fieldtype": "Check", - "label": "Print Hide If No Value" - }, - { - "fieldname": "print_width", - "fieldtype": "Data", - "label": "Print Width", - "length": 10 - }, - { - "fieldname": "width", - "fieldtype": "Data", - "label": "Width", - "length": 10, - "oldfieldname": "width", - "oldfieldtype": "Data", - "print_width": "50px", - "width": "50px" - }, - { - "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", - "fieldname": "columns", - "fieldtype": "Int", - "label": "Columns" - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "print_width": "300px", - "width": "300px" - }, - { - "fieldname": "oldfieldname", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldname", - "oldfieldtype": "Data" - }, - { - "fieldname": "oldfieldtype", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldtype", - "oldfieldtype": "Data" - }, - { - "fieldname": "mandatory_depends_on", - "fieldtype": "Code", - "label": "Mandatory Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "read_only_depends_on", - "fieldtype": "Code", - "label": "Read Only Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "column_break_38", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_days", - "fieldtype": "Check", - "label": "Hide Days" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_seconds", - "fieldtype": "Check", - "label": "Hide Seconds" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Section Break'", - "fieldname": "hide_border", - "fieldtype": "Check", - "label": "Hide Border" - }, - { - "default": "0", - "depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)", - "fieldname": "non_negative", - "fieldtype": "Check", - "label": "Non Negative" - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "defaults_section", - "fieldtype": "Section Break", - "label": "Defaults", - "max_height": "2rem" - }, - { - "fieldname": "visibility_section", - "fieldtype": "Section Break", - "label": "Visibility" - }, - { - "fieldname": "column_break_28", - "fieldtype": "Column Break" - }, - { - "fieldname": "constraints_section", - "fieldtype": "Section Break", - "label": "Constraints" - }, - { - "fieldname": "max_height", - "fieldtype": "Data", - "label": "Max Height", - "length": 10 - }, - { - "fieldname": "list__search_settings_section", - "fieldtype": "Section Break", - "label": "List / Search Settings" - }, - { - "fieldname": "column_break_35", - "fieldtype": "Column Break" - } - ], - "idx": 1, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-09-04 19:41:23.684094", - "modified_by": "Administrator", - "module": "Core", - "name": "DocField", - "naming_rule": "Random", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "ASC" -} \ No newline at end of file + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:27:33", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "label_and_type", + "label", + "fieldtype", + "fieldname", + "precision", + "length", + "non_negative", + "hide_days", + "hide_seconds", + "reqd", + "search_index", + "column_break_18", + "options", + "defaults_section", + "default", + "column_break_6", + "fetch_from", + "fetch_if_empty", + "visibility_section", + "hidden", + "bold", + "allow_in_quick_entry", + "translatable", + "print_hide", + "print_hide_if_no_value", + "report_hide", + "column_break_28", + "depends_on", + "collapsible", + "collapsible_depends_on", + "hide_border", + "list__search_settings_section", + "in_list_view", + "in_standard_filter", + "in_preview", + "column_break_35", + "in_filter", + "in_global_search", + "permissions", + "read_only", + "allow_on_submit", + "ignore_user_permissions", + "allow_bulk_edit", + "column_break_13", + "permlevel", + "ignore_xss_filter", + "constraints_section", + "unique", + "no_copy", + "set_only_once", + "remember_last_selected_value", + "column_break_38", + "mandatory_depends_on", + "read_only_depends_on", + "display", + "print_width", + "width", + "max_height", + "columns", + "column_break_22", + "description", + "oldfieldname", + "oldfieldtype" + ], + "fields": [ + { + "fieldname": "label_and_type", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "oldfieldname": "label", + "oldfieldtype": "Data", + "print_width": "163", + "search_index": 1, + "width": "163" + }, + { + "bold": 1, + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "oldfieldname": "fieldtype", + "oldfieldtype": "Select", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", + "reqd": 1, + "search_index": 1 + }, + { + "bold": 1, + "fieldname": "fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "search_index": 1 + }, + { + "default": "0", + "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", + "fieldname": "reqd", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9", + "print_hide": 1 + }, + { + "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", + "fieldname": "length", + "fieldtype": "Int", + "label": "Length" + }, + { + "default": "0", + "fieldname": "search_index", + "fieldtype": "Check", + "label": "Index", + "oldfieldname": "search_index", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View", + "print_width": "70px", + "width": "70px" + }, + { + "default": "0", + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "label": "In List Filter" + }, + { + "default": "0", + "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", + "fieldname": "in_global_search", + "fieldtype": "Check", + "label": "In Global Search" + }, + { + "default": "0", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", + "fieldname": "in_preview", + "fieldtype": "Check", + "label": "In Preview" + }, + { + "default": "0", + "fieldname": "allow_in_quick_entry", + "fieldtype": "Check", + "label": "Allow in Quick Entry" + }, + { + "default": "0", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold" + }, + { + "default": "0", + "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", + "fieldname": "translatable", + "fieldtype": "Check", + "label": "Translatable" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype===\"Section Break\"", + "fieldname": "collapsible", + "fieldtype": "Check", + "label": "Collapsible", + "length": 255 + }, + { + "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", + "fieldname": "collapsible_depends_on", + "fieldtype": "Code", + "label": "Collapsible Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", + "fieldname": "options", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text" + }, + { + "fieldname": "default", + "fieldtype": "Small Text", + "label": "Default", + "max_height": "3rem", + "oldfieldname": "default", + "oldfieldtype": "Text" + }, + { + "fieldname": "fetch_from", + "fieldtype": "Small Text", + "label": "Fetch From" + }, + { + "default": "0", + "fieldname": "fetch_if_empty", + "fieldtype": "Check", + "label": "Fetch only if value is not set" + }, + { + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions" + }, + { + "fieldname": "depends_on", + "fieldtype": "Code", + "label": "Display Depends On (JS)", + "length": 255, + "max_height": "3rem", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", + "options": "JS" + }, + { + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique" + }, + { + "default": "0", + "fieldname": "set_only_once", + "fieldtype": "Check", + "label": "Set only once" + }, + { + "default": "0", + "depends_on": "eval: doc.fieldtype == \"Table\"", + "fieldname": "allow_bulk_edit", + "fieldtype": "Check", + "label": "Allow Bulk Edit" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "label": "Perm Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions" + }, + { + "default": "0", + "depends_on": "eval: parent.is_submittable", + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "depends_on": "eval:(doc.fieldtype == 'Link')", + "fieldname": "remember_last_selected_value", + "fieldtype": "Check", + "label": "Remember Last Selected Value" + }, + { + "default": "0", + "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", + "fieldname": "ignore_xss_filter", + "fieldtype": "Check", + "label": "Ignore XSS Filter" + }, + { + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display" + }, + { + "default": "0", + "fieldname": "in_filter", + "fieldtype": "Check", + "label": "In Filter", + "oldfieldname": "in_filter", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "no_copy", + "fieldtype": "Check", + "label": "No Copy", + "oldfieldname": "no_copy", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "print_hide", + "fieldtype": "Check", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", + "fieldname": "print_hide_if_no_value", + "fieldtype": "Check", + "label": "Print Hide If No Value" + }, + { + "fieldname": "print_width", + "fieldtype": "Data", + "label": "Print Width", + "length": 10 + }, + { + "fieldname": "width", + "fieldtype": "Data", + "label": "Width", + "length": 10, + "oldfieldname": "width", + "oldfieldtype": "Data", + "print_width": "50px", + "width": "50px" + }, + { + "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", + "fieldname": "columns", + "fieldtype": "Int", + "label": "Columns" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" + }, + { + "fieldname": "oldfieldname", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldname", + "oldfieldtype": "Data" + }, + { + "fieldname": "oldfieldtype", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldtype", + "oldfieldtype": "Data" + }, + { + "fieldname": "mandatory_depends_on", + "fieldtype": "Code", + "label": "Mandatory Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "read_only_depends_on", + "fieldtype": "Code", + "label": "Read Only Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_days", + "fieldtype": "Check", + "label": "Hide Days" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_seconds", + "fieldtype": "Check", + "label": "Hide Seconds" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" + }, + { + "default": "0", + "depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)", + "fieldname": "non_negative", + "fieldtype": "Check", + "label": "Non Negative" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "defaults_section", + "fieldtype": "Section Break", + "label": "Defaults", + "max_height": "2rem" + }, + { + "fieldname": "visibility_section", + "fieldtype": "Section Break", + "label": "Visibility" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "constraints_section", + "fieldtype": "Section Break", + "label": "Constraints" + }, + { + "fieldname": "max_height", + "fieldtype": "Data", + "label": "Max Height", + "length": 10 + }, + { + "fieldname": "list__search_settings_section", + "fieldtype": "Section Break", + "label": "List / Search Settings" + }, + { + "fieldname": "column_break_35", + "fieldtype": "Column Break" + } + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-12-26 23:39:38.341443", + "modified_by": "Administrator", + "module": "Core", + "name": "DocField", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC" +} diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 235f11aad8..516daf3521 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -120,7 +120,7 @@ "label": "Field Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", "reqd": 1 }, { @@ -455,4 +455,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 986b99a7af..971bc51f96 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -82,7 +82,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nTab Break", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nTab Break", "reqd": 1, "search_index": 1 }, @@ -436,4 +436,4 @@ "permissions": [], "sort_field": "modified", "sort_order": "ASC" -} \ No newline at end of file +} diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 2f6d640743..5e838db842 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -52,7 +52,8 @@ class MariaDBDatabase(Database): 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), 'Duration': ('decimal', '21,9'), - 'Icon': ('varchar', self.VARCHAR_LEN) + 'Icon': ('varchar', self.VARCHAR_LEN), + 'Phone': ('varchar', self.VARCHAR_LEN) } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index bfa5515111..71b5918d4a 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -62,7 +62,8 @@ class PostgresDatabase(Database): 'Barcode': ('text', ''), 'Geolocation': ('text', ''), 'Duration': ('decimal', '21,9'), - 'Icon': ('varchar', self.VARCHAR_LEN) + 'Icon': ('varchar', self.VARCHAR_LEN), + 'Phone': ('varchar', self.VARCHAR_LEN) } def get_connection(self): diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index b460db29a7..00486d3d87 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -35,7 +35,8 @@ data_fieldtypes = ( 'Barcode', 'Geolocation', 'Duration', - 'Icon' + 'Icon', + 'Phone' ) no_value_fields = ( diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 1826cca9a3..ac87e564ac 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -11,6 +11,7 @@ from frappe.model import display_fieldtypes from frappe.utils import (cint, flt, now, cstr, strip_html, sanitize_html, sanitize_email, cast_fieldtype) from frappe.utils.html_utils import unescape_html +import phonenumbers as ph max_positive_value = { 'smallint': 2 ** 15, @@ -652,6 +653,17 @@ class BaseDocument(object): from frappe.core.doctype.user.user import STANDARD_USERS # data_field options defined in frappe.model.data_field_options + for phone_field in self.meta.get_phone_fields(): + phone = self.get(phone_field.fieldname) + try: + phone = ph.parse(phone) + except Exception as e: + if e.error_type == 1: + frappe.throw(_("The entered value is not a phone number."), title="Invalid Number") + frappe.throw(_("Please select a country code."), title = _("Country Code Required")) + if not ph.is_valid_number(phone): + frappe.throw('This is not a valid phone number', title = "Invalid Number") + for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) data_field_options = data_field.get("options") diff --git a/frappe/model/meta.py b/frappe/model/meta.py index cd0d8e0f3a..7430c35946 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -130,6 +130,9 @@ class Meta(Document): def get_data_fields(self): return self.get("fields", {"fieldtype": "Data"}) + def get_phone_fields(self): + return self.get("fields", {"fieldtype": "Phone"}) + def get_dynamic_link_fields(self): if not hasattr(self, '_dynamic_link_fields'): self._dynamic_link_fields = self.get("fields", {"fieldtype": "Dynamic Link"}) diff --git a/frappe/public/js/frappe/form/controls/control.js b/frappe/public/js/frappe/form/controls/control.js index bd04938e35..578ddd3276 100644 --- a/frappe/public/js/frappe/form/controls/control.js +++ b/frappe/public/js/frappe/form/controls/control.js @@ -40,6 +40,7 @@ import './multiselect_list'; import './rating'; import './duration'; import './icon'; +import './phone' frappe.ui.form.make_control = function (opts) { var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, ""); diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js new file mode 100644 index 0000000000..c00c966539 --- /dev/null +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -0,0 +1,159 @@ + +import Picker from '../../phone_picker/phone_picker'; + +frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlData { + + make_input() { + super.make_input(); + this.make_icon_input(); + this.input_events(); + } + + input_events() { + // Replaces code when selected and removes previously selected. + this.picker.on_change = (country) => { + const country_code = frappe.boot.country_codes[country].code; + const country_isd = frappe.boot.country_codes[country].isd; + this.selected_icon.find('use').attr('href', '#'+country_code) + this.$icon = this.selected_icon.find('svg'); + if (this.$icon.hasClass('icon-sm')) { + this.$icon.removeClass('icon-sm'); + this.selected_icon.find('svg').addClass('flag-md') + } + if (!this.$isd.length) { + this.selected_icon.append($(` ${country_isd}`)) + } else { + this.$isd.text(country_isd) + } + // this.selected_icon.text('+' + this.get_country(country)) + if(this.$input.val()) { + this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()) + } + }; + + this.$wrapper.find('.selected-phone').on('click', (e) => { + this.$wrapper.popover('toggle'); + e.stopPropagation(); + + $('body').on('click.phone-popover', (ev) => { + if (!$(ev.target).parents().is('.popover')) { + this.$wrapper.popover('hide'); + } + }); + $(window).on('hashchange.phone-popover', () => { + this.$wrapper.popover('hide'); + }); + }); + } + + make_icon_input() { + let picker_wrapper = $('
'); + this.picker = new Picker({ + parent: picker_wrapper, + countries: frappe.boot.country_codes + }); + + this.$wrapper.popover({ + trigger: 'manual', + offset: `${-this.$wrapper.width() / 4.5}, 5`, + boundary: 'viewport', + placement: 'bottom', + template: ` +
+
+
+
+ `, + content: () => picker_wrapper, + html: true + }).on('show.bs.popover', () => { + setTimeout(() => { + this.picker.refresh(); + }, 10); + }).on('hidden.bs.popover', () => { + $('body').off('click.phone-popover'); + $(window).off('hashchange.phone-popover'); + }); + + // Default icon when nothing is selected. + this.selected_icon = this.$wrapper.find('.selected-phone'); + let input_value = this.get_input_value() + if (!this.selected_icon.length) { + this.selected_icon = $(`
${frappe.utils.icon("down", "sm")}
`); + this.selected_icon.insertAfter(this.$input); + this.selected_icon.append($(``)) + this.$isd = this.selected_icon.find('.country'); + if(input_value && input_value.split("-").length == 2) { + this.$isd.text(this.value.split("-")[0]) + } + } + } + + refresh() { + super.refresh(); + + // Previously opened doc values get fetched. + if(!this.value && this.frm.is_new()) { + this.$input.val(""); + this.$wrapper.find('.country').text("") + this.selected_icon.find('use').attr('href', '#icon-down') + this.flag = this.selected_icon.find('svg'); + let has_flag = this.flag.hasClass('flag-md'); + if (has_flag) { + this.flag.toggleClass('flag-md'); + this.flag.toggleClass('icon-sm'); + } + } + + if(this.value && this.value.split("-").length == 2) { + let isd = this.value.split("-")[0]; + let country_data = frappe.boot.country_codes; + + for (const country in country_data) { + if (Object.values(country_data[country]).includes(isd)) { + let code = country_data[country].code; + this.change_flag(code); + } + } + this.picker.set_country(isd); + this.picker.refresh(); + if (this.picker.country && this.picker.country !== this.$isd.text()) { + this.$isd.length && this.$isd.text(isd) + } + } + } + + + set_formatted_input(value) { + if(value && value.includes('-')) { + this.set_model_value(value) + } else if(this.$isd.text().trim() && this.value) { + let code_number = this.$isd.text() + '-' + value; + this.set_model_value(code_number) + } + this.$input && value && this.$input.val(value.split("-").pop()) + } + + reset_icon() { + + } + + change_flag(country_code) { + this.selected_icon.find('use').attr('href', '#'+country_code) + this.$icon = this.selected_icon.find('svg'); + if (this.$icon.hasClass('icon-sm')) { + this.$icon.removeClass('icon-sm'); + this.selected_icon.find('svg').addClass('flag-md') + } + } + + get_country(country=null) { + const country_codes = frappe.boot.country_codes; + return country_codes[country].isd; + } + get_country_flag(country) { + const country_codes = frappe.boot.country_codes; + let code = country_codes[country].code; + return frappe.utils.flag(code, "md") + } +}; diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js new file mode 100644 index 0000000000..12285cca7e --- /dev/null +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -0,0 +1,92 @@ +class Picker { + constructor(opts) { + this.parent = opts.parent; + this.width = opts.width; + this.height = opts.height; + this.country = opts.country; + opts.country && this.set_country(opts.country); + this.countries = opts.countries; + this.setup_picker(); + } + + refresh() { + this.update_icon_selected(true); + } + + setup_picker() { + this.icon_picker_wrapper = $(` +
+
+ + ${frappe.utils.icon('search', "sm")} +
+
+
+
+
+ `); + this.parent.append(this.icon_picker_wrapper); + this.icon_wrapper = this.icon_picker_wrapper.find('.phones'); + this.search_input = this.icon_picker_wrapper.find('.search-phones > input'); + this.refresh(); + this.setup_icons(); + } + + setup_icons() { + + Object.entries(this.countries).forEach(([country, info]) => { + let $country = $(`
${frappe.utils.flag(info.code, "md")}${country}
`); + this.icon_wrapper.append($country); + const set_values = () => { + this.set_country(country); + this.update_icon_selected(); + }; + $country.on('click', () => { + set_values(); + }); + $country.hover(() => { + $country.toggleClass("bg-gray-100"); + }); + this.search_input.keydown((e) => { + const key_code = e.keyCode; + if ([13, 32].includes(key_code)) { + e.preventDefault(); + set_values(); + } + }); + this.search_input.keyup((e) => { + e.preventDefault(); + this.filter_icons(); + }); + + this.search_input.on('search', () => { + this.filter_icons(); + }); + }); + } + + filter_icons() { + let value = this.search_input.val(); + if (!value) { + this.icon_wrapper.find(".phone-wrapper").removeClass('hidden'); + } else { + this.icon_wrapper.find(".phone-wrapper").addClass('hidden'); + this.icon_wrapper.find(`.phone-wrapper[id*='${value}']`).removeClass('hidden'); + } + } + + update_icon_selected(silent) { + !silent && this.on_change && this.on_change(this.get_country()); + } + + set_country(country) { + this.country = country || ''; + } + + get_country() { + if (!this.country) return frappe.utils.icon("down", "sm") + return this.country; + } +} + +export default Picker; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 2baff996c6..8b3dc80ce6 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1156,6 +1156,19 @@ Object.assign(frappe.utils, { `; }, + flag(icon_name, size="sm", icon_class="", icon_style="", svg_class="") { + let size_class = ""; + + if (typeof size == "object") { + icon_style += ` width: ${size.width}; height: ${size.height}`; + } else { + size_class = `flag-${size}`; + } + return ` + + `; + }, + make_chart(wrapper, custom_options={}) { let chart_args = { type: 'bar', diff --git a/frappe/public/scss/common/controls.scss b/frappe/public/scss/common/controls.scss index 954916c911..15fd493096 100644 --- a/frappe/public/scss/common/controls.scss +++ b/frappe/public/scss/common/controls.scss @@ -2,6 +2,7 @@ @import "color_picker"; @import "icon_picker"; @import "datepicker"; +@import "phone_picker"; // password .form-control[data-fieldtype="Password"] { diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss new file mode 100644 index 0000000000..74a61cbbe8 --- /dev/null +++ b/frappe/public/scss/common/phone_picker.scss @@ -0,0 +1,119 @@ +.phone-picker { + font-size: var(--text-xs); + color: var(--text-muted); + --phone-picker-width: 210px; + width: var(--phone-picker-width); + .phones { + margin-top: 10px; + display: flex; + flex-wrap: wrap; + overflow-y: scroll; + max-height: 210px; + cursor: pointer; + + /* Hide scrollbar for IE, Edge and Firefox */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + + /* Hide scrollbar for Chrome, Safari and Opera */ + &::-webkit-scrollbar { + display: none; + } + + .phone-wrapper { + display: flex; + width: 210px; + height: 30px; + text-align: center; + align-items: center; + border-radius: 0.375rem; + padding: 0.5rem; + + .country { + display: flex; + margin-left: 0.6rem; + flex-grow: 1; + } + } + } + + .search-phones { + position: relative; + + input[type='search'] { + height: inherit; + padding-left: 30px; + } + + .search-phone { + position: absolute; + top: 7px; + left: 7px; + } + } +} +.phone-picker-popover { + left: -20px !important; + .picker-arrow { + left: 15px !important; + } +} +.frappe-control[data-fieldtype='Phone'] + { + input { + padding-left: 70px; + } + .selected-phone { + display: flex; + cursor: pointer; + width: 52px; + height: 18px; + border-radius: 5px; + position: absolute; + top: calc(50% + 2.6px); + left: 8px; + content: ' '; + + .country { + display: flex; + margin-left: 0.6rem; + flex-grow: 1; + } + + } + .like-disabled-input { + .phone-value { + padding-left: 25px; + } + .selected-phone { + top: 20%; + cursor: default; + } + } +} + +.data-row.row { + .selected-phone { + top: calc(50% - 11px); + z-index: 2; + } +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgba(244,245,246,var(--tw-bg-opacity)); +} + +.dt-cell__content { + .selected-phone { + display: contents; + } +} + +.dt-cell__edit, .filter-field { + .selected-phone { + top: 5px !important; + } +} + + From 5cc1da476dcac0952c24aeafbc7d68e57d768ebc Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 30 Dec 2021 14:41:54 +0530 Subject: [PATCH 02/30] chore: added svg file for country flags --- frappe/public/icons/timeless/flags.svg | 18341 +++++++++++++++++++++++ frappe/www/app.html | 1 + 2 files changed, 18342 insertions(+) create mode 100644 frappe/public/icons/timeless/flags.svg diff --git a/frappe/public/icons/timeless/flags.svg b/frappe/public/icons/timeless/flags.svg new file mode 100644 index 0000000000..d57fda3d0e --- /dev/null +++ b/frappe/public/icons/timeless/flags.svg @@ -0,0 +1,18341 @@ + + diff --git a/frappe/www/app.html b/frappe/www/app.html index 68a6dc8e86..8fcaf581e2 100644 --- a/frappe/www/app.html +++ b/frappe/www/app.html @@ -26,6 +26,7 @@ {% include "public/icons/timeless/symbol-defs.svg" %} + {% include "public/icons/timeless/flags.svg" %}
From aa84930dac68776f013a2c442f80c242d72a880d Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 30 Dec 2021 15:12:54 +0530 Subject: [PATCH 03/30] fix: made the search for country case insensitive --- frappe/public/js/frappe/phone_picker/phone_picker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index 12285cca7e..0d9910e9a2 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -35,7 +35,7 @@ class Picker { setup_icons() { Object.entries(this.countries).forEach(([country, info]) => { - let $country = $(`
${frappe.utils.flag(info.code, "md")}${country}
`); + let $country = $(`
${frappe.utils.flag(info.code, "md")}${country}
`); this.icon_wrapper.append($country); const set_values = () => { this.set_country(country); @@ -49,7 +49,7 @@ class Picker { }); this.search_input.keydown((e) => { const key_code = e.keyCode; - if ([13, 32].includes(key_code)) { + if ([13].includes(key_code)) { e.preventDefault(); set_values(); } @@ -71,7 +71,7 @@ class Picker { this.icon_wrapper.find(".phone-wrapper").removeClass('hidden'); } else { this.icon_wrapper.find(".phone-wrapper").addClass('hidden'); - this.icon_wrapper.find(`.phone-wrapper[id*='${value}']`).removeClass('hidden'); + this.icon_wrapper.find(`.phone-wrapper[id*='${value.toLowerCase()}']`).removeClass('hidden'); } } From 3521cc97f137a981df07c3ac3683a2fcd83c04fd Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 3 Jan 2022 10:09:28 +0530 Subject: [PATCH 04/30] fix: exclude flags in icon picker --- frappe/public/js/frappe/form/controls/icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/icon.js b/frappe/public/js/frappe/form/controls/icon.js index 7ab2e11f24..000731fa52 100644 --- a/frappe/public/js/frappe/form/controls/icon.js +++ b/frappe/public/js/frappe/form/controls/icon.js @@ -11,7 +11,7 @@ frappe.ui.form.ControlIcon = class ControlIcon extends frappe.ui.form.ControlDat get_all_icons() { frappe.symbols = []; $("#frappe-symbols > symbol[id]").each(function() { - frappe.symbols.push(this.id.replace('icon-', '')); + this.id.includes('icon-') && frappe.symbols.push(this.id.replace('icon-', '')); }); } From 6f8a53cdfdedddf7d31298c559458396bd8e9d43 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 3 Jan 2022 10:11:44 +0530 Subject: [PATCH 05/30] fix(ui): added isd code in the picker --- .../js/frappe/phone_picker/phone_picker.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index 0d9910e9a2..9e60acc257 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -14,7 +14,7 @@ class Picker { } setup_picker() { - this.icon_picker_wrapper = $(` + this.phone_picker_wrapper = $(`
@@ -25,18 +25,19 @@ class Picker {
`); - this.parent.append(this.icon_picker_wrapper); - this.icon_wrapper = this.icon_picker_wrapper.find('.phones'); - this.search_input = this.icon_picker_wrapper.find('.search-phones > input'); + this.parent.append(this.phone_picker_wrapper); + this.phone_wrapper = this.phone_picker_wrapper.find('.phones'); + this.search_input = this.phone_picker_wrapper.find('.search-phones > input'); this.refresh(); - this.setup_icons(); + this.setup_countries(); } - setup_icons() { + setup_countries() { Object.entries(this.countries).forEach(([country, info]) => { - let $country = $(`
${frappe.utils.flag(info.code, "md")}${country}
`); - this.icon_wrapper.append($country); + let $country = $(`
${frappe.utils.flag(info.code, "md")} + ${country} (${info.isd})
`); + this.phone_wrapper.append($country); const set_values = () => { this.set_country(country); this.update_icon_selected(); @@ -68,10 +69,10 @@ class Picker { filter_icons() { let value = this.search_input.val(); if (!value) { - this.icon_wrapper.find(".phone-wrapper").removeClass('hidden'); + this.phone_wrapper.find(".phone-wrapper").removeClass('hidden'); } else { - this.icon_wrapper.find(".phone-wrapper").addClass('hidden'); - this.icon_wrapper.find(`.phone-wrapper[id*='${value.toLowerCase()}']`).removeClass('hidden'); + this.phone_wrapper.find(".phone-wrapper").addClass('hidden'); + this.phone_wrapper.find(`.phone-wrapper[id*='${value.toLowerCase()}']`).removeClass('hidden'); } } From eb314f2e959baf5f0fee3eafba8063c17eda6bf2 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 3 Jan 2022 13:18:09 +0530 Subject: [PATCH 06/30] chore: updated country_info.json with iso codes --- frappe/boot.py | 5 +- frappe/geo/country_info.json | 738 +++++++++++++++++++++++------------ 2 files changed, 493 insertions(+), 250 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 026c0d6f16..4b764dabfc 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -17,6 +17,7 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p from frappe.model.base_document import get_controller from frappe.social.doctype.post.post import frequently_visited_links from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings, get_app_logo +from frappe.geo.country_info import get_all def get_bootinfo(): """build and return boot info""" @@ -327,7 +328,5 @@ def get_notification_settings(): return frappe.get_cached_doc('Notification Settings', frappe.session.user) def get_country_codes(bootinfo): - country_codes = { - "United States": {"isd":"+1","code":"us" }, - "India": {"isd":"+91","code":"in" }} + country_codes = get_all() bootinfo.country_codes = frappe._dict(country_codes) diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json index 7ffdf0a8bf..72e3e0ba24 100644 --- a/frappe/geo/country_info.json +++ b/frappe/geo/country_info.json @@ -8,7 +8,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Kabul" - ] + ], + "iso": "+93" }, "Albania": { "code": "al", @@ -20,7 +21,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Tirane" - ] + ], + "iso": "+355" }, "Algeria": { "code": "dz", @@ -32,11 +34,13 @@ "number_format": "#,###.##", "timezones": [ "Africa/Algiers" - ] + ], + "iso": "+213" }, "American Samoa": { "code": "as", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+1684" }, "Andorra": { "code": "ad", @@ -48,7 +52,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Andorra" - ] + ], + "iso": "+376" }, "Angola": { "code": "ao", @@ -60,7 +65,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Luanda" - ] + ], + "iso": "+244" }, "Anguilla": { "code": "ai", @@ -72,7 +78,8 @@ "number_format": "#,###.##", "timezones": [ "America/Anguilla" - ] + ], + "iso": "+1264" }, "Antarctica": { "code": "aq", @@ -88,7 +95,8 @@ "Antarctica/Rothera", "Antarctica/Syowa", "Antarctica/Vostok" - ] + ], + "iso": "+672" }, "Antigua and Barbuda": { "code": "ag", @@ -100,7 +108,8 @@ "number_format": "#,###.##", "timezones": [ "America/Antigua" - ] + ], + "iso": "+1268" }, "Argentina": { "code": "ar", @@ -123,7 +132,8 @@ "America/Argentina/San_Luis", "America/Argentina/Tucuman", "America/Argentina/Ushuaia" - ] + ], + "iso": "+54" }, "Armenia": { "code": "am", @@ -135,7 +145,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Yerevan" - ] + ], + "iso": "+374" }, "Aruba": { "code": "aw", @@ -147,7 +158,8 @@ "number_format": "#,###.##", "timezones": [ "America/Aruba" - ] + ], + "iso": "+297" }, "Australia": { "code": "au", @@ -170,7 +182,8 @@ "Australia/Melbourne", "Australia/Perth", "Australia/Sydney" - ] + ], + "iso": "+61" }, "Austria": { "code": "at", @@ -182,7 +195,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Vienna" - ] + ], + "iso": "+43" }, "Azerbaijan": { "code": "az", @@ -192,7 +206,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Baku" - ] + ], + "iso": "+994" }, "Bahamas": { "code": "bs", @@ -201,7 +216,8 @@ "number_format": "#,###.##", "timezones": [ "America/Nassau" - ] + ], + "iso": "+1242" }, "Bahrain": { "code": "bh", @@ -213,7 +229,8 @@ "number_format": "#,###.###", "timezones": [ "Asia/Bahrain" - ] + ], + "iso": "+973" }, "Bangladesh": { "code": "bd", @@ -225,7 +242,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Dhaka" - ] + ], + "iso": "+880" }, "Barbados": { "code": "bb", @@ -237,7 +255,8 @@ "number_format": "#,###.##", "timezones": [ "America/Barbados" - ] + ], + "iso": "+1246" }, "Belarus": { "code": "by", @@ -247,7 +266,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Minsk" - ] + ], + "iso": "+375" }, "Belgium": { "code": "be", @@ -259,7 +279,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Brussels" - ] + ], + "iso": "+32" }, "Belize": { "code": "bz", @@ -272,7 +293,8 @@ "number_format": "#,###.##", "timezones": [ "America/Belize" - ] + ], + "iso": "+501" }, "Benin": { "code": "bj", @@ -284,7 +306,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Porto-Novo" - ] + ], + "iso": "+229" }, "Bermuda": { "code": "bm", @@ -296,7 +319,8 @@ "number_format": "#,###.##", "timezones": [ "Atlantic/Bermuda" - ] + ], + "iso": "+1441" }, "Bhutan": { "code": "bt", @@ -308,13 +332,15 @@ "number_format": "#,###.##", "timezones": [ "Asia/Thimphu" - ] + ], + "iso": "+975" }, "Bolivia, Plurinational State of": { "code": "bo", "currency": "BOB", "currency_name": "Boliviano", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+591" }, "Bonaire, Sint Eustatius and Saba": { "code": "bq", @@ -329,7 +355,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Sarajevo" - ] + ], + "iso": "+387" }, "Botswana": { "code": "bw", @@ -341,11 +368,13 @@ "number_format": "#,###.##", "timezones": [ "Africa/Gaborone" - ] + ], + "iso": "+267" }, "Bouvet Island": { "code": "bv", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+47" }, "Brazil": { "code": "br", @@ -372,7 +401,8 @@ "America/Rio_Branco", "America/Santarem", "America/Sao_Paulo" - ] + ], + "iso": "+55" }, "British Indian Ocean Territory": { "code": "io", @@ -382,7 +412,8 @@ "number_format": "#,###.##", "timezones": [ "Indian/Chagos" - ] + ], + "iso": "+246" }, "Brunei Darussalam": { "code": "bn", @@ -391,7 +422,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Brunei" - ] + ], + "iso": "+673" }, "Bulgaria": { "code": "bg", @@ -403,7 +435,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Sofia" - ] + ], + "iso": "+359" }, "Burkina Faso": { "code": "bf", @@ -415,7 +448,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Ouagadougou" - ] + ], + "iso": "+226" }, "Burundi": { "code": "bi", @@ -427,7 +461,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Bujumbura" - ] + ], + "iso": "+257" }, "Cambodia": { "code": "kh", @@ -439,7 +474,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Phnom_Penh" - ] + ], + "iso": "+855" }, "Cameroon": { "code": "cm", @@ -451,7 +487,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Douala" - ] + ], + "iso": "+237" }, "Canada": { "code": "ca", @@ -491,7 +528,8 @@ "America/Whitehorse", "America/Winnipeg", "America/Yellowknife" - ] + ], + "iso": "+1" }, "Cape Verde": { "code": "cv", @@ -503,7 +541,8 @@ "number_format": "#,###.##", "timezones": [ "Atlantic/Cape_Verde" - ] + ], + "iso": "+238" }, "Cayman Islands": { "code": "ky", @@ -515,7 +554,8 @@ "number_format": "#,###.##", "timezones": [ "America/Cayman" - ] + ], + "iso": "+ 345" }, "Central African Republic": { "code": "cf", @@ -527,7 +567,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Bangui" - ] + ], + "iso": "+236" }, "Chad": { "code": "td", @@ -539,7 +580,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Ndjamena" - ] + ], + "iso": "+235" }, "Chile": { "code": "cl", @@ -552,7 +594,8 @@ "timezones": [ "America/Santiago", "Pacific/Easter" - ] + ], + "iso": "+56" }, "China": { "code": "cn", @@ -568,14 +611,16 @@ "Asia/Kashgar", "Asia/Shanghai", "Asia/Urumqi" - ] + ], + "iso": "+86" }, "Christmas Island": { "code": "cx", "number_format": "#,###.##", "timezones": [ "Indian/Christmas" - ] + ], + "iso": "+61" }, "Cocos (Keeling) Islands": { "code": "cc", @@ -585,7 +630,8 @@ "number_format": "#,###.##", "timezones": [ "Indian/Cocos" - ] + ], + "iso": "+61" }, "Colombia": { "code": "co", @@ -597,7 +643,8 @@ "number_format": "#.###,##", "timezones": [ "America/Bogota" - ] + ], + "iso": "+57" }, "Comoros": { "code": "km", @@ -609,7 +656,8 @@ "number_format": "#,###.##", "timezones": [ "Indian/Comoro" - ] + ], + "iso": "+269" }, "Congo": { "code": "cg", @@ -618,7 +666,8 @@ "currency_name": "Central African CFA Franc", "currency_symbol": "FCFA", "currency_fraction": "Centime", - "currency_fraction_units": 100 + "currency_fraction_units": 100, + "iso": "+242" }, "Congo, The Democratic Republic of the": { "code": "cd", @@ -627,7 +676,8 @@ "currency_name": "Congolese franc", "currency_symbol": "FC", "currency_fraction": "Centime", - "currency_fraction_units": 100 + "currency_fraction_units": 100, + "iso": "+243" }, "Cook Islands": { "code": "ck", @@ -637,7 +687,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Rarotonga" - ] + ], + "iso": "+682" }, "Costa Rica": { "code": "cr", @@ -649,7 +700,8 @@ "number_format": "#.###,##", "timezones": [ "America/Costa_Rica" - ] + ], + "iso": "+506" }, "Croatia": { "code": "hr", @@ -661,7 +713,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Zagreb" - ] + ], + "iso": "+385" }, "Cuba": { "code": "cu", @@ -673,7 +726,8 @@ "number_format": "#,###.##", "timezones": [ "America/Havana" - ] + ], + "iso": "+53" }, "Cura\u00e7ao": { "code": "cw", @@ -692,7 +746,8 @@ "number_format": "#.###,##", "timezones": [ "Asia/Nicosia" - ] + ], + "iso": "+357" }, "Czech Republic": { "code": "cz", @@ -704,7 +759,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Prague" - ] + ], + "iso": "+420" }, "Denmark": { "code": "dk", @@ -716,7 +772,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Copenhagen" - ] + ], + "iso": "+45" }, "Djibouti": { "code": "dj", @@ -728,7 +785,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Djibouti" - ] + ], + "iso": "+253" }, "Dominica": { "code": "dm", @@ -740,7 +798,8 @@ "number_format": "#,###.##", "timezones": [ "America/Dominica" - ] + ], + "iso": "+1767" }, "Dominican Republic": { "code": "do", @@ -752,7 +811,8 @@ "number_format": "#,###.##", "timezones": [ "America/Santo_Domingo" - ] + ], + "iso": "+1849" }, "Ecuador": { "code": "ec", @@ -763,7 +823,8 @@ "timezones": [ "America/Guayaquil", "Pacific/Galapagos" - ] + ], + "iso": "+593" }, "Egypt": { "code": "eg", @@ -775,7 +836,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Cairo" - ] + ], + "iso": "+20" }, "El Salvador": { "code": "sv", @@ -789,7 +851,8 @@ "number_format": "#,###.##", "timezones": [ "America/El_Salvador" - ] + ], + "iso": "+503" }, "Equatorial Guinea": { "code": "gq", @@ -801,7 +864,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Malabo" - ] + ], + "iso": "+240" }, "Eritrea": { "code": "er", @@ -813,7 +877,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Asmara" - ] + ], + "iso": "+291" }, "Estonia": { "code": "ee", @@ -825,7 +890,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Tallinn" - ] + ], + "iso": "+372" }, "Ethiopia": { "code": "et", @@ -837,13 +903,15 @@ "number_format": "#,###.##", "timezones": [ "Africa/Addis_Ababa" - ] + ], + "iso": "+251" }, "Falkland Islands (Malvinas)": { "code": "fk", "currency": "FKP", "currency_name": "Falkland Islands Pound", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+500" }, "Faroe Islands": { "code": "fo", @@ -853,7 +921,8 @@ "number_format": "#,###.##", "timezones": [ "Atlantic/Faroe" - ] + ], + "iso": "+298" }, "Fiji": { "code": "fj", @@ -865,7 +934,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Fiji" - ] + ], + "iso": "+679" }, "Finland": { "code": "fi", @@ -877,7 +947,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Helsinki" - ] + ], + "iso": "+358" }, "France": { "code": "fr", @@ -890,14 +961,16 @@ "date_format": "dd/mm/yyyy", "timezones": [ "Europe/Paris" - ] + ], + "iso": "+33" }, "French Guiana": { "code": "gf", "number_format": "#,###.##", "timezones": [ "America/Cayenne" - ] + ], + "iso": "+594" }, "French Polynesia": { "code": "pf", @@ -909,11 +982,13 @@ "Pacific/Gambier", "Pacific/Marquesas", "Pacific/Tahiti" - ] + ], + "iso": "+689" }, "French Southern Territories": { "code": "tf", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+262" }, "Gabon": { "code": "ga", @@ -925,7 +1000,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Libreville" - ] + ], + "iso": "+241" }, "Gambia": { "code": "gm", @@ -934,7 +1010,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Banjul" - ] + ], + "iso": "+220" }, "Georgia": { "code": "ge", @@ -944,7 +1021,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Tbilisi" - ] + ], + "iso": "+995" }, "Germany": { "code": "de", @@ -956,7 +1034,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Berlin" - ] + ], + "iso": "+49" }, "Ghana": { "code": "gh", @@ -967,7 +1046,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Accra" - ] + ], + "iso": "+233" }, "Gibraltar": { "code": "gi", @@ -979,7 +1059,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Gibraltar" - ] + ], + "iso": "+350" }, "Greece": { "code": "gr", @@ -991,7 +1072,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Athens" - ] + ], + "iso": "+30" }, "Greenland": { "code": "gl", @@ -1001,7 +1083,8 @@ "America/Godthab", "America/Scoresbysund", "America/Thule" - ] + ], + "iso": "+299" }, "Grenada": { "code": "gd", @@ -1013,21 +1096,24 @@ "number_format": "#,###.##", "timezones": [ "America/Grenada" - ] + ], + "iso": "+1473" }, "Guadeloupe": { "code": "gp", "number_format": "#,###.##", "timezones": [ "America/Guadeloupe" - ] + ], + "iso": "+590" }, "Guam": { "code": "gu", "number_format": "#,###.##", "timezones": [ "Pacific/Guam" - ] + ], + "iso": "+1671" }, "Guatemala": { "code": "gt", @@ -1039,7 +1125,8 @@ "number_format": "#,###.##", "timezones": [ "America/Guatemala" - ] + ], + "iso": "+502" }, "Guernsey": { "code": "gg", @@ -1049,7 +1136,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/London" - ] + ], + "iso": "+44" }, "Guinea": { "code": "gn", @@ -1061,7 +1149,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Conakry" - ] + ], + "iso": "+224" }, "Guinea-Bissau": { "code": "gw", @@ -1073,7 +1162,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Bissau" - ] + ], + "iso": "+245" }, "Guyana": { "code": "gy", @@ -1085,7 +1175,8 @@ "number_format": "#,###.##", "timezones": [ "America/Guyana" - ] + ], + "iso": "+592" }, "Haiti": { "code": "ht", @@ -1098,15 +1189,18 @@ "timezones": [ "America/Guatemala", "America/Port-au-Prince" - ] + ], + "iso": "+509" }, "Heard Island and McDonald Islands": { "code": "hm", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+0" }, "Holy See (Vatican City State)": { "code": "va", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+379" }, "Honduras": { "code": "hn", @@ -1118,7 +1212,8 @@ "number_format": "#,###.##", "timezones": [ "America/Tegucigalpa" - ] + ], + "iso": "+504" }, "Hong Kong": { "code": "hk", @@ -1130,7 +1225,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Hong_Kong" - ] + ], + "iso": "+852" }, "Hungary": { "code": "hu", @@ -1143,7 +1239,8 @@ "number_format": "#.###", "timezones": [ "Europe/Budapest" - ] + ], + "iso": "+36" }, "Iceland": { "code": "is", @@ -1155,7 +1252,8 @@ "number_format": "#.###", "timezones": [ "Atlantic/Reykjavik" - ] + ], + "iso": "+354" }, "India": { "code": "in", @@ -1167,7 +1265,8 @@ "number_format": "#,##,###.##", "timezones": [ "Asia/Kolkata" - ] + ], + "iso": "+91" }, "Indonesia": { "code": "id", @@ -1182,7 +1281,8 @@ "Asia/Jayapura", "Asia/Makassar", "Asia/Pontianak" - ] + ], + "iso": "+62" }, "Iran": { "code": "ir", @@ -1192,7 +1292,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Tehran" - ] + ], + "iso": "+98" }, "Iraq": { "code": "iq", @@ -1204,7 +1305,8 @@ "number_format": "#,###.###", "timezones": [ "Asia/Baghdad" - ] + ], + "iso": "+964" }, "Ireland": { "code": "ie", @@ -1216,7 +1318,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Dublin" - ] + ], + "iso": "+353" }, "Isle of Man": { "code": "im", @@ -1226,7 +1329,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/London" - ] + ], + "iso": "+44" }, "Israel": { "code": "il", @@ -1238,7 +1342,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Jerusalem" - ] + ], + "iso": "+972" }, "Italy": { "code": "it", @@ -1251,7 +1356,8 @@ "date_format": "dd/mm/yyyy", "timezones": [ "Europe/Rome" - ] + ], + "iso": "+39" }, "Ivory Coast": { "code": "ci", @@ -1263,7 +1369,8 @@ "number_format": "#,###.##", "timeszones": [ "Africa/Abidjan" - ] + ], + "iso": "+225" }, "Jamaica": { "code": "jm", @@ -1275,7 +1382,8 @@ "number_format": "#,###.##", "timezones": [ "America/Jamaica" - ] + ], + "iso": "+1876" }, "Japan": { "code": "jp", @@ -1287,7 +1395,8 @@ "number_format": "#,###", "timezones": [ "Asia/Tokyo" - ] + ], + "iso": "+81" }, "Jersey": { "code": "je", @@ -1297,7 +1406,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/London" - ] + ], + "iso": "+44" }, "Jordan": { "code": "jo", @@ -1309,7 +1419,8 @@ "number_format": "#,###.###", "timezones": [ "Asia/Amman" - ] + ], + "iso": "+962" }, "Kazakhstan": { "code": "kz", @@ -1325,7 +1436,8 @@ "Asia/Aqtobe", "Asia/Oral", "Asia/Qyzylorda" - ] + ], + "iso": "+7" }, "Kenya": { "code": "ke", @@ -1337,7 +1449,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Nairobi" - ] + ], + "iso": "+254" }, "Kiribati": { "code": "ki", @@ -1349,19 +1462,22 @@ "Pacific/Enderbury", "Pacific/Kiritimati", "Pacific/Tarawa" - ] + ], + "iso": "+686" }, "Korea, Democratic Peoples Republic of": { "code": "kp", "currency": "KPW", "currency_name": "North Korean Won", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+850" }, "Korea, Republic of": { "code": "kr", "currency": "KRW", "currency_name": "Won", - "number_format": "#,###" + "number_format": "#,###", + "iso": "+82" }, "Kuwait": { "code": "kw", @@ -1373,7 +1489,8 @@ "number_format": "#,###.###", "timezones": [ "Asia/Kuwait" - ] + ], + "iso": "+965" }, "Kyrgyzstan": { "code": "kg", @@ -1385,16 +1502,18 @@ "number_format": "#,###.##", "timezones": [ "Asia/Bishkek" - ] + ], + "iso": "+996" }, "Lao Peoples Democratic Republic": { "code": "la", "currency": "LAK", "currency_name": "Kip", "number_format": "#,###.##", - "timezones":[ - "Asia/Vientiane" - ] + "timezones": [ + "Asia/Vientiane" + ], + "iso": "+856" }, "Latvia": { "code": "lv", @@ -1406,7 +1525,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Riga" - ] + ], + "iso": "+371" }, "Lebanon": { "code": "lb", @@ -1418,7 +1538,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Beirut" - ] + ], + "iso": "+961" }, "Lesotho": { "code": "ls", @@ -1430,7 +1551,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Maseru" - ] + ], + "iso": "+266" }, "Liberia": { "code": "lr", @@ -1442,7 +1564,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Monrovia" - ] + ], + "iso": "+231" }, "Libya": { "code": "ly", @@ -1454,7 +1577,8 @@ "number_format": "#,###.###", "timezones": [ "Africa/Tripoli" - ] + ], + "iso": "+218" }, "Liechtenstein": { "code": "li", @@ -1464,7 +1588,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Vaduz" - ] + ], + "iso": "+423" }, "Lithuania": { "code": "lt", @@ -1477,7 +1602,8 @@ "number_format": "# ###,##", "timezones": [ "Europe/Vilnius" - ] + ], + "iso": "+370" }, "Luxembourg": { "code": "lu", @@ -1489,13 +1615,15 @@ "number_format": "#,###.##", "timezones": [ "Europe/Luxembourg" - ] + ], + "iso": "+352" }, "Macao": { "code": "mo", "currency": "MOP", "currency_name": "Pataca", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+853" }, "Macedonia": { "code": "mk", @@ -1504,7 +1632,8 @@ "currency_fraction_units": 100, "currency_name": "Denar", "currency_symbol": "\u0434\u0435\u043d", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+389" }, "Madagascar": { "code": "mg", @@ -1514,7 +1643,8 @@ "number_format": "#,###.##", "timezones": [ "Indian/Antananarivo" - ] + ], + "iso": "+261" }, "Malawi": { "code": "mw", @@ -1526,7 +1656,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Blantyre" - ] + ], + "iso": "+265" }, "Malaysia": { "code": "my", @@ -1539,7 +1670,8 @@ "timezones": [ "Asia/Kuala_Lumpur", "Asia/Kuching" - ] + ], + "iso": "+60" }, "Maldives": { "code": "mv", @@ -1551,7 +1683,8 @@ "number_format": "#,###.##", "timezones": [ "Indian/Maldives" - ] + ], + "iso": "+960" }, "Mali": { "code": "ml", @@ -1563,7 +1696,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Bamako" - ] + ], + "iso": "+223" }, "Malta": { "code": "mt", @@ -1575,7 +1709,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Malta" - ] + ], + "iso": "+356" }, "Marshall Islands": { "code": "mh", @@ -1586,14 +1721,16 @@ "timezones": [ "Pacific/Kwajalein", "Pacific/Majuro" - ] + ], + "iso": "+692" }, "Martinique": { "code": "mq", "number_format": "#,###.##", "timezones": [ "America/Martinique" - ] + ], + "iso": "+596" }, "Mauritania": { "code": "mr", @@ -1605,7 +1742,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Nouakchott" - ] + ], + "iso": "+222" }, "Mauritius": { "code": "mu", @@ -1617,14 +1755,16 @@ "number_format": "#,###", "timezones": [ "Indian/Mauritius" - ] + ], + "iso": "+230" }, "Mayotte": { "code": "yt", "number_format": "#,###.##", "timezones": [ "Indian/Mayotte" - ] + ], + "iso": "+262" }, "Mexico": { "code": "mx", @@ -1647,17 +1787,20 @@ "America/Ojinaga", "America/Santa_Isabel", "America/Tijuana" - ] + ], + "iso": "+52" }, "Micronesia, Federated States of": { "code": "fm", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+691" }, "Moldova, Republic of": { "code": "md", "currency": "MDL", "currency_name": "Moldovan Leu", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+373" }, "Monaco": { "code": "mc", @@ -1669,7 +1812,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Monaco" - ] + ], + "iso": "+377" }, "Mongolia": { "code": "mn", @@ -1684,7 +1828,8 @@ "Asia/Choibalsan", "Asia/Hovd", "Asia/Ulaanbaatar" - ] + ], + "iso": "+976" }, "Montenegro": { "code": "me", @@ -1696,7 +1841,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Belgrade" - ] + ], + "iso": "+382" }, "Montserrat": { "code": "ms", @@ -1708,7 +1854,8 @@ "number_format": "#,###.##", "timezones": [ "America/Montserrat" - ] + ], + "iso": "+1664" }, "Morocco": { "code": "ma", @@ -1720,7 +1867,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Casablanca" - ] + ], + "iso": "+212" }, "Mozambique": { "code": "mz", @@ -1731,7 +1879,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Maputo" - ] + ], + "iso": "+258" }, "Myanmar": { "code": "mm", @@ -1740,7 +1889,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Rangoon" - ] + ], + "iso": "+95" }, "Namibia": { "code": "na", @@ -1752,7 +1902,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Windhoek" - ] + ], + "iso": "+264" }, "Nauru": { "code": "nr", @@ -1762,7 +1913,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Nauru" - ] + ], + "iso": "+674" }, "Nepal": { "code": "np", @@ -1774,7 +1926,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Kathmandu" - ] + ], + "iso": "+977" }, "Netherlands": { "code": "nl", @@ -1786,7 +1939,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Amsterdam" - ] + ], + "iso": "+31" }, "New Caledonia": { "code": "nc", @@ -1796,7 +1950,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Noumea" - ] + ], + "iso": "+687" }, "New Zealand": { "code": "nz", @@ -1809,7 +1964,8 @@ "timezones": [ "Pacific/Auckland", "Pacific/Chatham" - ] + ], + "iso": "+64" }, "Nicaragua": { "code": "ni", @@ -1821,7 +1977,8 @@ "number_format": "#,###.##", "timezones": [ "America/Managua" - ] + ], + "iso": "+505" }, "Niger": { "code": "ne", @@ -1833,7 +1990,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Niamey" - ] + ], + "iso": "+227" }, "Nigeria": { "code": "ng", @@ -1845,7 +2003,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Lagos" - ] + ], + "iso": "+234" }, "Niue": { "code": "nu", @@ -1855,21 +2014,24 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Niue" - ] + ], + "iso": "+683" }, "Norfolk Island": { "code": "nf", "number_format": "#,###.##", "timezones": [ "Pacific/Norfolk" - ] + ], + "iso": "+672" }, "Northern Mariana Islands": { "code": "mp", "number_format": "#,###.##", "timezones": [ "Pacific/Saipan" - ] + ], + "iso": "+1670" }, "Norway": { "code": "no", @@ -1881,7 +2043,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Oslo" - ] + ], + "iso": "+47" }, "Oman": { "code": "om", @@ -1893,7 +2056,8 @@ "number_format": "#,###.###", "timezones": [ "Asia/Muscat" - ] + ], + "iso": "+968" }, "Pakistan": { "code": "pk", @@ -1905,7 +2069,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Karachi" - ] + ], + "iso": "+92" }, "Palau": { "code": "pw", @@ -1916,11 +2081,13 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Palau" - ] + ], + "iso": "+680" }, "Palestinian Territory, Occupied": { "code": "ps", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+970" }, "Panama": { "code": "pa", @@ -1930,7 +2097,8 @@ "number_format": "#,###.##", "timezones": [ "America/Panama" - ] + ], + "iso": "+507" }, "Papua New Guinea": { "code": "pg", @@ -1942,7 +2110,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Port_Moresby" - ] + ], + "iso": "+675" }, "Paraguay": { "code": "py", @@ -1954,7 +2123,8 @@ "number_format": "#,###.##", "timezones": [ "America/Asuncion" - ] + ], + "iso": "+595" }, "Peru": { "code": "pe", @@ -1966,7 +2136,8 @@ "number_format": "#,###.##", "timezones": [ "America/Lima" - ] + ], + "iso": "+51" }, "Philippines": { "code": "ph", @@ -1979,14 +2150,16 @@ "number_format": "#,###.##", "timezones": [ "Asia/Manila" - ] + ], + "iso": "+63" }, "Pitcairn": { "code": "pn", "number_format": "#,###.##", "timezones": [ "Pacific/Pitcairn" - ] + ], + "iso": "+64" }, "Poland": { "code": "pl", @@ -1997,7 +2170,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Warsaw" - ] + ], + "iso": "+48" }, "Portugal": { "code": "pt", @@ -2011,14 +2185,16 @@ "Atlantic/Azores", "Atlantic/Madeira", "Europe/Lisbon" - ] + ], + "iso": "+351" }, "Puerto Rico": { "code": "pr", "number_format": "#,###.##", "timezones": [ "America/Puerto_Rico" - ] + ], + "iso": "+1939" }, "Qatar": { "code": "qa", @@ -2030,7 +2206,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Qatar" - ] + ], + "iso": "+974" }, "Romania": { "code": "ro", @@ -2042,13 +2219,15 @@ "number_format": "#,###.##", "timezones": [ "Europe/Bucharest" - ] + ], + "iso": "+40" }, "Russian Federation": { "code": "ru", "currency": "RUB", "currency_name": "Russian Ruble", - "number_format": "#.###,##" + "number_format": "#.###,##", + "iso": "+7" }, "Rwanda": { "code": "rw", @@ -2060,21 +2239,25 @@ "number_format": "#,###.##", "timezones": [ "Africa/Kigali" - ] + ], + "iso": "+250" }, "R\u00e9union": { "code": "re", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+262" }, "Saint Barth\u00e9lemy": { "code": "bl", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+590" }, "Saint Helena, Ascension and Tristan da Cunha": { "code": "sh", "currency": "SHP", "currency_name": "Saint Helena Pound", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+290" }, "Saint Kitts and Nevis": { "code": "kn", @@ -2086,7 +2269,8 @@ "number_format": "#,###.##", "timezones": [ "America/St_Kitts" - ] + ], + "iso": "+1869" }, "Saint Lucia": { "code": "lc", @@ -2098,15 +2282,18 @@ "number_format": "#,###.##", "timezones": [ "America/St_Lucia" - ] + ], + "iso": "+1758" }, "Saint Martin (French part)": { "code": "mf", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+590" }, "Saint Pierre and Miquelon": { "code": "pm", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+508" }, "Saint Vincent and the Grenadines": { "code": "vc", @@ -2118,7 +2305,8 @@ "number_format": "#,###.##", "timezones": [ "America/St_Vincent" - ] + ], + "iso": "+1784" }, "Samoa": { "code": "ws", @@ -2130,7 +2318,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Apia" - ] + ], + "iso": "+685" }, "San Marino": { "code": "sm", @@ -2142,13 +2331,15 @@ "number_format": "#,###.##", "timezones": [ "Europe/Rome" - ] + ], + "iso": "+378" }, "Sao Tome and Principe": { "code": "st", "currency": "STD", "currency_name": "Dobra", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+239" }, "Saudi Arabia": { "code": "sa", @@ -2160,7 +2351,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Riyadh" - ] + ], + "iso": "+966" }, "Senegal": { "code": "sn", @@ -2172,7 +2364,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Dakar" - ] + ], + "iso": "+221" }, "Serbia": { "code": "rs", @@ -2184,7 +2377,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Belgrade" - ] + ], + "iso": "+381" }, "Seychelles": { "code": "sc", @@ -2196,7 +2390,8 @@ "number_format": "#,###.##", "timezones": [ "Indian/Mahe" - ] + ], + "iso": "+248" }, "Sierra Leone": { "code": "sl", @@ -2208,7 +2403,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Freetown" - ] + ], + "iso": "+232" }, "Singapore": { "code": "sg", @@ -2220,7 +2416,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Singapore" - ] + ], + "iso": "+65" }, "Sint Maarten (Dutch part)": { "code": "sx", @@ -2236,7 +2433,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Bratislava" - ] + ], + "iso": "+421" }, "Slovenia": { "code": "si", @@ -2248,7 +2446,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Belgrade" - ] + ], + "iso": "+386" }, "Solomon Islands": { "code": "sb", @@ -2260,7 +2459,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Guadalcanal" - ] + ], + "iso": "+677" }, "Somalia": { "code": "so", @@ -2272,7 +2472,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Mogadishu" - ] + ], + "iso": "+252" }, "South Africa": { "code": "za", @@ -2285,14 +2486,16 @@ "number_format": "# ###.##", "timezones": [ "Africa/Johannesburg" - ] + ], + "iso": "+27" }, "South Georgia and the South Sandwich Islands": { "code": "gs", "currency_fraction": "Penny", "currency_fraction_units": 100, "currency_symbol": "\u00a3", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+500" }, "South Sudan": { "code": "ss", @@ -2302,7 +2505,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Juba" - ] + ], + "iso": "+211" }, "Spain": { "code": "es", @@ -2316,7 +2520,8 @@ "Africa/Ceuta", "Atlantic/Canary", "Europe/Madrid" - ] + ], + "iso": "+34" }, "Sri Lanka": { "code": "lk", @@ -2328,7 +2533,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Colombo" - ] + ], + "iso": "+94" }, "Sudan": { "code": "sd", @@ -2338,7 +2544,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Khartoum" - ] + ], + "iso": "+249" }, "Suriname": { "code": "sr", @@ -2349,11 +2556,13 @@ "number_format": "#,###.##", "timezones": [ "America/Paramaribo" - ] + ], + "iso": "+597" }, "Svalbard and Jan Mayen": { "code": "sj", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+47" }, "Swaziland": { "code": "sz", @@ -2365,7 +2574,8 @@ "number_format": "#, ###.##", "timezones": [ "Africa/Mbabane" - ] + ], + "iso": "+268" }, "Sweden": { "code": "se", @@ -2377,7 +2587,8 @@ "number_format": "#.###,##", "timezones": [ "Europe/Stockholm" - ] + ], + "iso": "+46" }, "Switzerland": { "code": "ch", @@ -2390,19 +2601,22 @@ "number_format": "#'###.##", "timezones": [ "Europe/Zurich" - ] + ], + "iso": "+41" }, "Syria": { "code": "sy", "currency": "SYP", "currency_name": "Syrian Pound", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+963" }, "Taiwan": { "code": "tw", "currency": "TWD", "date_format": "yyyy-mm-dd", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+886" }, "Tajikistan": { "code": "tj", @@ -2412,13 +2626,15 @@ "number_format": "#,###.##", "timezones": [ "Asia/Dushanbe" - ] + ], + "iso": "+992" }, "Tanzania": { "code": "tz", "currency": "TZS", "currency_name": "Tanzanian Shilling", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+255" }, "Thailand": { "code": "th", @@ -2430,11 +2646,13 @@ "number_format": "#,###.##", "timezones": [ "Asia/Bangkok" - ] + ], + "iso": "+66" }, "Timor-Leste": { "code": "tl", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+670" }, "Togo": { "code": "tg", @@ -2446,14 +2664,16 @@ "number_format": "#,###.##", "timezones": [ "Africa/Lome" - ] + ], + "iso": "+228" }, "Tokelau": { "code": "tk", "number_format": "#,###.##", "timezones": [ "Pacific/Fakaofo" - ] + ], + "iso": "+690" }, "Tonga": { "code": "to", @@ -2465,7 +2685,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Tongatapu" - ] + ], + "iso": "+676" }, "Trinidad and Tobago": { "code": "tt", @@ -2477,7 +2698,8 @@ "number_format": "#,###.##", "timezones": [ "America/Port_of_Spain" - ] + ], + "iso": "+1868" }, "Tunisia": { "code": "tn", @@ -2489,7 +2711,8 @@ "number_format": "#,###.###", "timezones": [ "Africa/Tunis" - ] + ], + "iso": "+216" }, "Turkey": { "code": "tr", @@ -2500,7 +2723,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/Istanbul" - ] + ], + "iso": "+90" }, "Turkmenistan": { "code": "tm", @@ -2512,14 +2736,16 @@ "number_format": "#,###.##", "timezones": [ "Asia/Ashgabat" - ] + ], + "iso": "+993" }, "Turks and Caicos Islands": { "code": "tc", "currency_fraction": "Cent", "currency_fraction_units": 100, "currency_symbol": "$", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+1649" }, "Tuvalu": { "code": "tv", @@ -2529,7 +2755,8 @@ "number_format": "#,###.##", "timezones": [ "Pacific/Funafuti" - ] + ], + "iso": "+688" }, "Uganda": { "code": "ug", @@ -2541,7 +2768,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Kampala" - ] + ], + "iso": "+256" }, "Ukraine": { "code": "ua", @@ -2556,7 +2784,8 @@ "Europe/Simferopol", "Europe/Uzhgorod", "Europe/Zaporozhye" - ] + ], + "iso": "+380" }, "United Arab Emirates": { "code": "ae", @@ -2568,7 +2797,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Dubai" - ] + ], + "iso": "+971" }, "United Kingdom": { "code": "gb", @@ -2580,7 +2810,8 @@ "number_format": "#,###.##", "timezones": [ "Europe/London" - ] + ], + "iso": "+44" }, "United States": { "code": "us", @@ -2623,7 +2854,8 @@ "America/Sitka", "America/Yakutat", "Pacific/Honolulu" - ] + ], + "iso": "+1" }, "United States Minor Outlying Islands": { "code": "um", @@ -2639,7 +2871,8 @@ "number_format": "#.###,##", "timezones": [ "America/Montevideo" - ] + ], + "iso": "+598" }, "Uzbekistan": { "code": "uz", @@ -2652,7 +2885,8 @@ "timezones": [ "Asia/Samarkand", "Asia/Tashkent" - ] + ], + "iso": "+998" }, "Vanuatu": { "code": "vu", @@ -2664,7 +2898,8 @@ "number_format": "#,###", "timezones": [ "Pacific/Efate" - ] + ], + "iso": "+678" }, "Venezuela, Bolivarian Republic of": { "code": "ve", @@ -2672,28 +2907,33 @@ "currency": "VEF", "currency_symbol": "Bs.", "currency_fraction": "Centimos", - "currency_fraction_units": 100 + "currency_fraction_units": 100, + "iso": "+58" }, "Vietnam": { "code": "vn", "currency": "VND", "currency_name": "Dong", - "number_format": "#.###" + "number_format": "#.###", + "iso": "+84" }, "Virgin Islands, British": { "code": "vg", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+1284" }, "Virgin Islands, U.S.": { "code": "vi", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+1340" }, "Wallis and Futuna": { "code": "wf", "currency_fraction": "Centime", "currency_fraction_units": 100, "currency_symbol": "Fr", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+681" }, "Western Sahara": { "code": "eh", @@ -2713,7 +2953,8 @@ "number_format": "#,###.##", "timezones": [ "Asia/Aden" - ] + ], + "iso": "+967" }, "Zambia": { "code": "zm", @@ -2725,7 +2966,8 @@ "number_format": "#,###.##", "timezones": [ "Africa/Lusaka" - ] + ], + "iso": "+260" }, "Zimbabwe": { "code": "zw", @@ -2737,10 +2979,12 @@ "number_format": "# ###.##", "timezones": [ "Africa/Harare" - ] + ], + "iso": "+263" }, "\u00c5land Islands": { "code": "ax", - "number_format": "#,###.##" + "number_format": "#,###.##", + "iso": "+358" } -} +} \ No newline at end of file From 596a7c28278515ae7263f11b7c5109b84a186025 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 3 Jan 2022 13:19:33 +0530 Subject: [PATCH 07/30] fix(ui): inconsistent flag size in the field --- frappe/public/js/frappe/form/controls/phone.js | 9 ++------- frappe/public/js/frappe/phone_picker/phone_picker.js | 6 ++++-- frappe/public/scss/common/phone_picker.scss | 11 ++++++----- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index c00c966539..c5191ee224 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -13,7 +13,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD // Replaces code when selected and removes previously selected. this.picker.on_change = (country) => { const country_code = frappe.boot.country_codes[country].code; - const country_isd = frappe.boot.country_codes[country].isd; + const country_isd = frappe.boot.country_codes[country].iso; this.selected_icon.find('use').attr('href', '#'+country_code) this.$icon = this.selected_icon.find('svg'); if (this.$icon.hasClass('icon-sm')) { @@ -25,7 +25,6 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD } else { this.$isd.text(country_isd) } - // this.selected_icon.text('+' + this.get_country(country)) if(this.$input.val()) { this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()) } @@ -134,10 +133,6 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.$input && value && this.$input.val(value.split("-").pop()) } - reset_icon() { - - } - change_flag(country_code) { this.selected_icon.find('use').attr('href', '#'+country_code) this.$icon = this.selected_icon.find('svg'); @@ -149,7 +144,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD get_country(country=null) { const country_codes = frappe.boot.country_codes; - return country_codes[country].isd; + return country_codes[country].iso; } get_country_flag(country) { const country_codes = frappe.boot.country_codes; diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index 9e60acc257..f12ddf46ec 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -33,10 +33,12 @@ class Picker { } setup_countries() { - Object.entries(this.countries).forEach(([country, info]) => { + if (!info.iso) { + return + } let $country = $(`
${frappe.utils.flag(info.code, "md")} - ${country} (${info.isd})
`); + ${country} (${info.iso})
`); this.phone_wrapper.append($country); const set_values = () => { this.set_country(country); diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index 74a61cbbe8..a80d9aa83e 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -1,7 +1,7 @@ .phone-picker { font-size: var(--text-xs); color: var(--text-muted); - --phone-picker-width: 210px; + --phone-picker-width: 250px; width: var(--phone-picker-width); .phones { margin-top: 10px; @@ -22,7 +22,7 @@ .phone-wrapper { display: flex; - width: 210px; + width: 250px; height: 30px; text-align: center; align-items: center; @@ -61,13 +61,13 @@ .frappe-control[data-fieldtype='Phone'] { input { - padding-left: 70px; + padding-left: 80px; } .selected-phone { display: flex; cursor: pointer; - width: 52px; - height: 18px; + width: 66px; + height: 20px; border-radius: 5px; position: absolute; top: calc(50% + 2.6px); @@ -77,6 +77,7 @@ .country { display: flex; margin-left: 0.6rem; + align-items: flex-end; flex-grow: 1; } From 934d34084ef11bc3737d6ef55c1ab06219c8d529 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 3 Jan 2022 15:07:03 +0530 Subject: [PATCH 08/30] fix(ui): increased width of popover --- frappe/public/scss/common/phone_picker.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index a80d9aa83e..631090f145 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -1,7 +1,7 @@ .phone-picker { font-size: var(--text-xs); color: var(--text-muted); - --phone-picker-width: 250px; + --phone-picker-width: 290px; width: var(--phone-picker-width); .phones { margin-top: 10px; @@ -22,7 +22,7 @@ .phone-wrapper { display: flex; - width: 250px; + width: 290px; height: 30px; text-align: center; align-items: center; @@ -33,6 +33,7 @@ display: flex; margin-left: 0.6rem; flex-grow: 1; + width: 290px; } } } @@ -53,6 +54,7 @@ } } .phone-picker-popover { + max-width: 325px; left: -20px !important; .picker-arrow { left: 15px !important; From 3010d537bbe0a8c2b41a08581f59867904074511 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 3 Jan 2022 16:19:14 +0530 Subject: [PATCH 09/30] test: UI test for Phone Control --- cypress/integration/control_phone.js | 57 +++++++++++++++++++ .../public/js/frappe/form/controls/phone.js | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 cypress/integration/control_phone.js diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js new file mode 100644 index 0000000000..684e0d7d99 --- /dev/null +++ b/cypress/integration/control_phone.js @@ -0,0 +1,57 @@ +context('Control Phone', () => { + before(() => { + cy.login(); + cy.visit('/app/website'); + }); + + function get_dialog_with_phone(s) { + return cy.dialog({ + title: 'Phone', + fields: [{ + 'fieldname': 'phone', + 'fieldtype': 'Phone', + }] + }); + } + + it('should set flag and data', () => { + get_dialog_with_phone().as('dialog'); + cy.get('.selected-phone').click(); + cy.get('.phone-picker .phone-wrapper[id="afghanistan"]').click(); + cy.get('.phone-picker .phone-wrapper[id="india"]').click(); + cy.get('.selected-phone .country').should('have.text', '+91') + cy.get('.selected-phone > .icon > use').should('have.attr', 'href').and('include', '#in') + + let phone_number = '9312672712' + cy.get('.selected-phone').click().first(); + cy.get('.frappe-control[data-fieldname=phone] input') + .first() + .click(); + cy.get('.frappe-control[data-fieldname=phone]') + .findByRole('textbox') + .first() + .type(phone_number); + + cy.get('.frappe-control[data-fieldname=phone] input').first().should('have.value', phone_number); + cy.get('.frappe-control[data-fieldname=phone] input').first().blur(); + cy.get('@dialog').then(dialog => { + let value = dialog.fields_dict.phone.value; + expect(value).to.equal('+91-' + phone_number); + }); + }); + + it('case insensitive search for country and clear search', () => { + let search_text = 'india'; + cy.get('.selected-phone').click().first(); + cy.get('.phone-picker').findByRole('searchbox').click().type(search_text); + cy.get('.phone-section .phone-wrapper:not(.hidden)').then(i => { + cy.get(`.phone-section .phone-wrapper[id*='${search_text.toLowerCase()}']`).then(countries => { + expect(i.length).to.equal(countries.length); + }); + }); + + cy.get('.phone-picker').findByRole('searchbox').clear().blur(); + cy.get('.phone-section .phone-wrapper').should('not.have.class', 'hidden'); + }); + +}); diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index c5191ee224..98764bc76c 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -92,7 +92,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD super.refresh(); // Previously opened doc values get fetched. - if(!this.value && this.frm.is_new()) { + if(!this.value) { this.$input.val(""); this.$wrapper.find('.country').text("") this.selected_icon.find('use').attr('href', '#icon-down') From 849dadf6e12e415c8a292b6efb3a585d3ccd9ecd Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 5 Jan 2022 16:55:10 +0530 Subject: [PATCH 10/30] chore: updated requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 671f6ced11..5a985985e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -75,3 +75,5 @@ zxcvbn-python~=4.4.24 tenacity~=8.0.1 cairocffi==1.2.0 WeasyPrint==52.5 +phonenumbers==8.12.40 + From 224c91f92ce3b9b541e0e0f6f5929cdc0c4b23ff Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 7 Jan 2022 12:14:07 +0530 Subject: [PATCH 11/30] refactor: using flagcdn instead of flags.svg --- frappe/public/icons/timeless/flags.svg | 18341 ---------------- .../public/js/frappe/form/controls/phone.js | 81 +- .../js/frappe/phone_picker/phone_picker.js | 5 +- frappe/public/js/frappe/utils/utils.js | 6 +- frappe/public/scss/common/phone_picker.scss | 8 + frappe/www/app.html | 1 - 6 files changed, 57 insertions(+), 18385 deletions(-) delete mode 100644 frappe/public/icons/timeless/flags.svg diff --git a/frappe/public/icons/timeless/flags.svg b/frappe/public/icons/timeless/flags.svg deleted file mode 100644 index d57fda3d0e..0000000000 --- a/frappe/public/icons/timeless/flags.svg +++ /dev/null @@ -1,18341 +0,0 @@ - - diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 98764bc76c..bf5a865d02 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -14,19 +14,22 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.picker.on_change = (country) => { const country_code = frappe.boot.country_codes[country].code; const country_isd = frappe.boot.country_codes[country].iso; - this.selected_icon.find('use').attr('href', '#'+country_code) + this.change_flag(country_code); this.$icon = this.selected_icon.find('svg'); - if (this.$icon.hasClass('icon-sm')) { - this.$icon.removeClass('icon-sm'); - this.selected_icon.find('svg').addClass('flag-md') + this.$flag = this.selected_icon.find('img'); + if (!this.$icon.hasClass('hide')){ + this.$icon.toggleClass('hide'); + } + if (!this.$flag.length) { + this.selected_icon.prepend(this.get_country_flag(country)); } if (!this.$isd.length) { - this.selected_icon.append($(` ${country_isd}`)) + this.selected_icon.append($(` ${country_isd}`)); } else { - this.$isd.text(country_isd) + this.$isd.text(country_isd); } if(this.$input.val()) { - this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()) + this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()); } }; @@ -76,14 +79,14 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD // Default icon when nothing is selected. this.selected_icon = this.$wrapper.find('.selected-phone'); - let input_value = this.get_input_value() + let input_value = this.get_input_value(); if (!this.selected_icon.length) { this.selected_icon = $(`
${frappe.utils.icon("down", "sm")}
`); this.selected_icon.insertAfter(this.$input); - this.selected_icon.append($(``)) + this.selected_icon.append($(``)); this.$isd = this.selected_icon.find('.country'); if(input_value && input_value.split("-").length == 2) { - this.$isd.text(this.value.split("-")[0]) + this.$isd.text(this.value.split("-")[0]); } } } @@ -94,26 +97,15 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD // Previously opened doc values get fetched. if(!this.value) { this.$input.val(""); - this.$wrapper.find('.country').text("") - this.selected_icon.find('use').attr('href', '#icon-down') - this.flag = this.selected_icon.find('svg'); - let has_flag = this.flag.hasClass('flag-md'); - if (has_flag) { - this.flag.toggleClass('flag-md'); - this.flag.toggleClass('icon-sm'); + this.$wrapper.find('.country').text(""); + if (this.selected_icon.find('svg').hasClass('hide')) { + this.selected_icon.find('svg').toggleClass('hide'); + this.selected_icon.find('img').addClass('hide'); } } - if(this.value && this.value.split("-").length == 2) { let isd = this.value.split("-")[0]; - let country_data = frappe.boot.country_codes; - - for (const country in country_data) { - if (Object.values(country_data[country]).includes(isd)) { - let code = country_data[country].code; - this.change_flag(code); - } - } + this.get_country_code_and_change_flag(isd); this.picker.set_country(isd); this.picker.refresh(); if (this.picker.country && this.picker.country !== this.$isd.text()) { @@ -125,30 +117,45 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD set_formatted_input(value) { if(value && value.includes('-')) { - this.set_model_value(value) + this.set_model_value(value); + this.$input.val(value.split("-").pop()); } else if(this.$isd.text().trim() && this.value) { let code_number = this.$isd.text() + '-' + value; - this.set_model_value(code_number) + this.set_model_value(code_number); } - this.$input && value && this.$input.val(value.split("-").pop()) } change_flag(country_code) { - this.selected_icon.find('use').attr('href', '#'+country_code) - this.$icon = this.selected_icon.find('svg'); - if (this.$icon.hasClass('icon-sm')) { - this.$icon.removeClass('icon-sm'); - this.selected_icon.find('svg').addClass('flag-md') - } + this.selected_icon.find('img').attr('src', 'https://flagcdn.com/h20/'+country_code+'.png') + this.$icon = this.selected_icon.find('img'); + // this.$icon.hasClass('hide') && this.$icon.toggleClass('hide'); } - get_country(country=null) { + // country_code for India is 'in' + get_country_code_and_change_flag(isd) { + let country_data = frappe.boot.country_codes; + let flag = this.selected_icon.find('img'); + for (const country in country_data) { + if (Object.values(country_data[country]).includes(isd)) { + let code = country_data[country].code; + flag = this.selected_icon.find('img'); + if (!flag.length) { + this.selected_icon.prepend(this.get_country_flag(country)); + this.selected_icon.find('svg').addClass('hide'); + } + else { + this.change_flag(code); + } + } + } + } + get_country(country) { const country_codes = frappe.boot.country_codes; return country_codes[country].iso; } get_country_flag(country) { const country_codes = frappe.boot.country_codes; let code = country_codes[country].code; - return frappe.utils.flag(code, "md") + return frappe.utils.flag(code); } }; diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index f12ddf46ec..7270fe63a1 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -37,7 +37,7 @@ class Picker { if (!info.iso) { return } - let $country = $(`
${frappe.utils.flag(info.code, "md")} + let $country = $(`
${frappe.utils.flag(info.code)} ${country} (${info.iso})
`); this.phone_wrapper.append($country); const set_values = () => { @@ -49,7 +49,7 @@ class Picker { }); $country.hover(() => { $country.toggleClass("bg-gray-100"); - }); + }); this.search_input.keydown((e) => { const key_code = e.keyCode; if ([13].includes(key_code)) { @@ -87,7 +87,6 @@ class Picker { } get_country() { - if (!this.country) return frappe.utils.icon("down", "sm") return this.country; } } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 8b3dc80ce6..db21adf886 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1164,9 +1164,9 @@ Object.assign(frappe.utils, { } else { size_class = `flag-${size}`; } - return ` - - `; + return `` }, make_chart(wrapper, custom_options={}) { diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index 631090f145..cc3387ecb0 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -29,6 +29,9 @@ border-radius: 0.375rem; padding: 0.5rem; + img { + height: 15px; + } .country { display: flex; margin-left: 0.6rem; @@ -75,6 +78,7 @@ top: calc(50% + 2.6px); left: 8px; content: ' '; + align-items: center; .country { display: flex; @@ -83,6 +87,10 @@ flex-grow: 1; } + img { + height: 15px; + } + } .like-disabled-input { .phone-value { diff --git a/frappe/www/app.html b/frappe/www/app.html index 8fcaf581e2..68a6dc8e86 100644 --- a/frappe/www/app.html +++ b/frappe/www/app.html @@ -26,7 +26,6 @@ {% include "public/icons/timeless/symbol-defs.svg" %} - {% include "public/icons/timeless/flags.svg" %}
From b3076c67b8b477112a0a57a198e95f67049da193 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 7 Jan 2022 12:18:33 +0530 Subject: [PATCH 12/30] chore: renamed iso to isd --- frappe/geo/country_info.json | 490 +++++++++--------- .../public/js/frappe/form/controls/phone.js | 4 +- .../js/frappe/phone_picker/phone_picker.js | 4 +- 3 files changed, 249 insertions(+), 249 deletions(-) diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json index 72e3e0ba24..e8b8bed1c7 100644 --- a/frappe/geo/country_info.json +++ b/frappe/geo/country_info.json @@ -9,7 +9,7 @@ "timezones": [ "Asia/Kabul" ], - "iso": "+93" + "isd": "+93" }, "Albania": { "code": "al", @@ -22,7 +22,7 @@ "timezones": [ "Europe/Tirane" ], - "iso": "+355" + "isd": "+355" }, "Algeria": { "code": "dz", @@ -35,12 +35,12 @@ "timezones": [ "Africa/Algiers" ], - "iso": "+213" + "isd": "+213" }, "American Samoa": { "code": "as", "number_format": "#,###.##", - "iso": "+1684" + "isd": "+1684" }, "Andorra": { "code": "ad", @@ -53,7 +53,7 @@ "timezones": [ "Europe/Andorra" ], - "iso": "+376" + "isd": "+376" }, "Angola": { "code": "ao", @@ -66,7 +66,7 @@ "timezones": [ "Africa/Luanda" ], - "iso": "+244" + "isd": "+244" }, "Anguilla": { "code": "ai", @@ -79,7 +79,7 @@ "timezones": [ "America/Anguilla" ], - "iso": "+1264" + "isd": "+1264" }, "Antarctica": { "code": "aq", @@ -96,7 +96,7 @@ "Antarctica/Syowa", "Antarctica/Vostok" ], - "iso": "+672" + "isd": "+672" }, "Antigua and Barbuda": { "code": "ag", @@ -109,7 +109,7 @@ "timezones": [ "America/Antigua" ], - "iso": "+1268" + "isd": "+1268" }, "Argentina": { "code": "ar", @@ -133,7 +133,7 @@ "America/Argentina/Tucuman", "America/Argentina/Ushuaia" ], - "iso": "+54" + "isd": "+54" }, "Armenia": { "code": "am", @@ -146,7 +146,7 @@ "timezones": [ "Asia/Yerevan" ], - "iso": "+374" + "isd": "+374" }, "Aruba": { "code": "aw", @@ -159,7 +159,7 @@ "timezones": [ "America/Aruba" ], - "iso": "+297" + "isd": "+297" }, "Australia": { "code": "au", @@ -183,7 +183,7 @@ "Australia/Perth", "Australia/Sydney" ], - "iso": "+61" + "isd": "+61" }, "Austria": { "code": "at", @@ -196,7 +196,7 @@ "timezones": [ "Europe/Vienna" ], - "iso": "+43" + "isd": "+43" }, "Azerbaijan": { "code": "az", @@ -207,7 +207,7 @@ "timezones": [ "Asia/Baku" ], - "iso": "+994" + "isd": "+994" }, "Bahamas": { "code": "bs", @@ -217,7 +217,7 @@ "timezones": [ "America/Nassau" ], - "iso": "+1242" + "isd": "+1242" }, "Bahrain": { "code": "bh", @@ -230,7 +230,7 @@ "timezones": [ "Asia/Bahrain" ], - "iso": "+973" + "isd": "+973" }, "Bangladesh": { "code": "bd", @@ -243,7 +243,7 @@ "timezones": [ "Asia/Dhaka" ], - "iso": "+880" + "isd": "+880" }, "Barbados": { "code": "bb", @@ -256,7 +256,7 @@ "timezones": [ "America/Barbados" ], - "iso": "+1246" + "isd": "+1246" }, "Belarus": { "code": "by", @@ -267,7 +267,7 @@ "timezones": [ "Europe/Minsk" ], - "iso": "+375" + "isd": "+375" }, "Belgium": { "code": "be", @@ -280,7 +280,7 @@ "timezones": [ "Europe/Brussels" ], - "iso": "+32" + "isd": "+32" }, "Belize": { "code": "bz", @@ -294,7 +294,7 @@ "timezones": [ "America/Belize" ], - "iso": "+501" + "isd": "+501" }, "Benin": { "code": "bj", @@ -307,7 +307,7 @@ "timezones": [ "Africa/Porto-Novo" ], - "iso": "+229" + "isd": "+229" }, "Bermuda": { "code": "bm", @@ -320,7 +320,7 @@ "timezones": [ "Atlantic/Bermuda" ], - "iso": "+1441" + "isd": "+1441" }, "Bhutan": { "code": "bt", @@ -333,14 +333,14 @@ "timezones": [ "Asia/Thimphu" ], - "iso": "+975" + "isd": "+975" }, "Bolivia, Plurinational State of": { "code": "bo", "currency": "BOB", "currency_name": "Boliviano", "number_format": "#,###.##", - "iso": "+591" + "isd": "+591" }, "Bonaire, Sint Eustatius and Saba": { "code": "bq", @@ -356,7 +356,7 @@ "timezones": [ "Europe/Sarajevo" ], - "iso": "+387" + "isd": "+387" }, "Botswana": { "code": "bw", @@ -369,12 +369,12 @@ "timezones": [ "Africa/Gaborone" ], - "iso": "+267" + "isd": "+267" }, "Bouvet Island": { "code": "bv", "number_format": "#,###.##", - "iso": "+47" + "isd": "+47" }, "Brazil": { "code": "br", @@ -402,7 +402,7 @@ "America/Santarem", "America/Sao_Paulo" ], - "iso": "+55" + "isd": "+55" }, "British Indian Ocean Territory": { "code": "io", @@ -413,7 +413,7 @@ "timezones": [ "Indian/Chagos" ], - "iso": "+246" + "isd": "+246" }, "Brunei Darussalam": { "code": "bn", @@ -423,7 +423,7 @@ "timezones": [ "Asia/Brunei" ], - "iso": "+673" + "isd": "+673" }, "Bulgaria": { "code": "bg", @@ -436,7 +436,7 @@ "timezones": [ "Europe/Sofia" ], - "iso": "+359" + "isd": "+359" }, "Burkina Faso": { "code": "bf", @@ -449,7 +449,7 @@ "timezones": [ "Africa/Ouagadougou" ], - "iso": "+226" + "isd": "+226" }, "Burundi": { "code": "bi", @@ -462,7 +462,7 @@ "timezones": [ "Africa/Bujumbura" ], - "iso": "+257" + "isd": "+257" }, "Cambodia": { "code": "kh", @@ -475,7 +475,7 @@ "timezones": [ "Asia/Phnom_Penh" ], - "iso": "+855" + "isd": "+855" }, "Cameroon": { "code": "cm", @@ -488,7 +488,7 @@ "timezones": [ "Africa/Douala" ], - "iso": "+237" + "isd": "+237" }, "Canada": { "code": "ca", @@ -529,7 +529,7 @@ "America/Winnipeg", "America/Yellowknife" ], - "iso": "+1" + "isd": "+1" }, "Cape Verde": { "code": "cv", @@ -542,7 +542,7 @@ "timezones": [ "Atlantic/Cape_Verde" ], - "iso": "+238" + "isd": "+238" }, "Cayman Islands": { "code": "ky", @@ -555,7 +555,7 @@ "timezones": [ "America/Cayman" ], - "iso": "+ 345" + "isd": "+ 345" }, "Central African Republic": { "code": "cf", @@ -568,7 +568,7 @@ "timezones": [ "Africa/Bangui" ], - "iso": "+236" + "isd": "+236" }, "Chad": { "code": "td", @@ -581,7 +581,7 @@ "timezones": [ "Africa/Ndjamena" ], - "iso": "+235" + "isd": "+235" }, "Chile": { "code": "cl", @@ -595,7 +595,7 @@ "America/Santiago", "Pacific/Easter" ], - "iso": "+56" + "isd": "+56" }, "China": { "code": "cn", @@ -612,7 +612,7 @@ "Asia/Shanghai", "Asia/Urumqi" ], - "iso": "+86" + "isd": "+86" }, "Christmas Island": { "code": "cx", @@ -620,7 +620,7 @@ "timezones": [ "Indian/Christmas" ], - "iso": "+61" + "isd": "+61" }, "Cocos (Keeling) Islands": { "code": "cc", @@ -631,7 +631,7 @@ "timezones": [ "Indian/Cocos" ], - "iso": "+61" + "isd": "+61" }, "Colombia": { "code": "co", @@ -644,7 +644,7 @@ "timezones": [ "America/Bogota" ], - "iso": "+57" + "isd": "+57" }, "Comoros": { "code": "km", @@ -657,7 +657,7 @@ "timezones": [ "Indian/Comoro" ], - "iso": "+269" + "isd": "+269" }, "Congo": { "code": "cg", @@ -667,7 +667,7 @@ "currency_symbol": "FCFA", "currency_fraction": "Centime", "currency_fraction_units": 100, - "iso": "+242" + "isd": "+242" }, "Congo, The Democratic Republic of the": { "code": "cd", @@ -677,7 +677,7 @@ "currency_symbol": "FC", "currency_fraction": "Centime", "currency_fraction_units": 100, - "iso": "+243" + "isd": "+243" }, "Cook Islands": { "code": "ck", @@ -688,7 +688,7 @@ "timezones": [ "Pacific/Rarotonga" ], - "iso": "+682" + "isd": "+682" }, "Costa Rica": { "code": "cr", @@ -701,7 +701,7 @@ "timezones": [ "America/Costa_Rica" ], - "iso": "+506" + "isd": "+506" }, "Croatia": { "code": "hr", @@ -714,7 +714,7 @@ "timezones": [ "Europe/Zagreb" ], - "iso": "+385" + "isd": "+385" }, "Cuba": { "code": "cu", @@ -727,7 +727,7 @@ "timezones": [ "America/Havana" ], - "iso": "+53" + "isd": "+53" }, "Cura\u00e7ao": { "code": "cw", @@ -747,7 +747,7 @@ "timezones": [ "Asia/Nicosia" ], - "iso": "+357" + "isd": "+357" }, "Czech Republic": { "code": "cz", @@ -760,7 +760,7 @@ "timezones": [ "Europe/Prague" ], - "iso": "+420" + "isd": "+420" }, "Denmark": { "code": "dk", @@ -773,7 +773,7 @@ "timezones": [ "Europe/Copenhagen" ], - "iso": "+45" + "isd": "+45" }, "Djibouti": { "code": "dj", @@ -786,7 +786,7 @@ "timezones": [ "Africa/Djibouti" ], - "iso": "+253" + "isd": "+253" }, "Dominica": { "code": "dm", @@ -799,7 +799,7 @@ "timezones": [ "America/Dominica" ], - "iso": "+1767" + "isd": "+1767" }, "Dominican Republic": { "code": "do", @@ -812,7 +812,7 @@ "timezones": [ "America/Santo_Domingo" ], - "iso": "+1849" + "isd": "+1849" }, "Ecuador": { "code": "ec", @@ -824,7 +824,7 @@ "America/Guayaquil", "Pacific/Galapagos" ], - "iso": "+593" + "isd": "+593" }, "Egypt": { "code": "eg", @@ -837,7 +837,7 @@ "timezones": [ "Africa/Cairo" ], - "iso": "+20" + "isd": "+20" }, "El Salvador": { "code": "sv", @@ -852,7 +852,7 @@ "timezones": [ "America/El_Salvador" ], - "iso": "+503" + "isd": "+503" }, "Equatorial Guinea": { "code": "gq", @@ -865,7 +865,7 @@ "timezones": [ "Africa/Malabo" ], - "iso": "+240" + "isd": "+240" }, "Eritrea": { "code": "er", @@ -878,7 +878,7 @@ "timezones": [ "Africa/Asmara" ], - "iso": "+291" + "isd": "+291" }, "Estonia": { "code": "ee", @@ -891,7 +891,7 @@ "timezones": [ "Europe/Tallinn" ], - "iso": "+372" + "isd": "+372" }, "Ethiopia": { "code": "et", @@ -904,14 +904,14 @@ "timezones": [ "Africa/Addis_Ababa" ], - "iso": "+251" + "isd": "+251" }, "Falkland Islands (Malvinas)": { "code": "fk", "currency": "FKP", "currency_name": "Falkland Islands Pound", "number_format": "#,###.##", - "iso": "+500" + "isd": "+500" }, "Faroe Islands": { "code": "fo", @@ -922,7 +922,7 @@ "timezones": [ "Atlantic/Faroe" ], - "iso": "+298" + "isd": "+298" }, "Fiji": { "code": "fj", @@ -935,7 +935,7 @@ "timezones": [ "Pacific/Fiji" ], - "iso": "+679" + "isd": "+679" }, "Finland": { "code": "fi", @@ -948,7 +948,7 @@ "timezones": [ "Europe/Helsinki" ], - "iso": "+358" + "isd": "+358" }, "France": { "code": "fr", @@ -962,7 +962,7 @@ "timezones": [ "Europe/Paris" ], - "iso": "+33" + "isd": "+33" }, "French Guiana": { "code": "gf", @@ -970,7 +970,7 @@ "timezones": [ "America/Cayenne" ], - "iso": "+594" + "isd": "+594" }, "French Polynesia": { "code": "pf", @@ -983,12 +983,12 @@ "Pacific/Marquesas", "Pacific/Tahiti" ], - "iso": "+689" + "isd": "+689" }, "French Southern Territories": { "code": "tf", "number_format": "#,###.##", - "iso": "+262" + "isd": "+262" }, "Gabon": { "code": "ga", @@ -1001,7 +1001,7 @@ "timezones": [ "Africa/Libreville" ], - "iso": "+241" + "isd": "+241" }, "Gambia": { "code": "gm", @@ -1011,7 +1011,7 @@ "timezones": [ "Africa/Banjul" ], - "iso": "+220" + "isd": "+220" }, "Georgia": { "code": "ge", @@ -1022,7 +1022,7 @@ "timezones": [ "Asia/Tbilisi" ], - "iso": "+995" + "isd": "+995" }, "Germany": { "code": "de", @@ -1035,7 +1035,7 @@ "timezones": [ "Europe/Berlin" ], - "iso": "+49" + "isd": "+49" }, "Ghana": { "code": "gh", @@ -1047,7 +1047,7 @@ "timezones": [ "Africa/Accra" ], - "iso": "+233" + "isd": "+233" }, "Gibraltar": { "code": "gi", @@ -1060,7 +1060,7 @@ "timezones": [ "Europe/Gibraltar" ], - "iso": "+350" + "isd": "+350" }, "Greece": { "code": "gr", @@ -1073,7 +1073,7 @@ "timezones": [ "Europe/Athens" ], - "iso": "+30" + "isd": "+30" }, "Greenland": { "code": "gl", @@ -1084,7 +1084,7 @@ "America/Scoresbysund", "America/Thule" ], - "iso": "+299" + "isd": "+299" }, "Grenada": { "code": "gd", @@ -1097,7 +1097,7 @@ "timezones": [ "America/Grenada" ], - "iso": "+1473" + "isd": "+1473" }, "Guadeloupe": { "code": "gp", @@ -1105,7 +1105,7 @@ "timezones": [ "America/Guadeloupe" ], - "iso": "+590" + "isd": "+590" }, "Guam": { "code": "gu", @@ -1113,7 +1113,7 @@ "timezones": [ "Pacific/Guam" ], - "iso": "+1671" + "isd": "+1671" }, "Guatemala": { "code": "gt", @@ -1126,7 +1126,7 @@ "timezones": [ "America/Guatemala" ], - "iso": "+502" + "isd": "+502" }, "Guernsey": { "code": "gg", @@ -1137,7 +1137,7 @@ "timezones": [ "Europe/London" ], - "iso": "+44" + "isd": "+44" }, "Guinea": { "code": "gn", @@ -1150,7 +1150,7 @@ "timezones": [ "Africa/Conakry" ], - "iso": "+224" + "isd": "+224" }, "Guinea-Bissau": { "code": "gw", @@ -1163,7 +1163,7 @@ "timezones": [ "Africa/Bissau" ], - "iso": "+245" + "isd": "+245" }, "Guyana": { "code": "gy", @@ -1176,7 +1176,7 @@ "timezones": [ "America/Guyana" ], - "iso": "+592" + "isd": "+592" }, "Haiti": { "code": "ht", @@ -1190,17 +1190,17 @@ "America/Guatemala", "America/Port-au-Prince" ], - "iso": "+509" + "isd": "+509" }, "Heard Island and McDonald Islands": { "code": "hm", "number_format": "#,###.##", - "iso": "+0" + "isd": "+0" }, "Holy See (Vatican City State)": { "code": "va", "number_format": "#,###.##", - "iso": "+379" + "isd": "+379" }, "Honduras": { "code": "hn", @@ -1213,7 +1213,7 @@ "timezones": [ "America/Tegucigalpa" ], - "iso": "+504" + "isd": "+504" }, "Hong Kong": { "code": "hk", @@ -1226,7 +1226,7 @@ "timezones": [ "Asia/Hong_Kong" ], - "iso": "+852" + "isd": "+852" }, "Hungary": { "code": "hu", @@ -1240,7 +1240,7 @@ "timezones": [ "Europe/Budapest" ], - "iso": "+36" + "isd": "+36" }, "Iceland": { "code": "is", @@ -1253,7 +1253,7 @@ "timezones": [ "Atlantic/Reykjavik" ], - "iso": "+354" + "isd": "+354" }, "India": { "code": "in", @@ -1266,7 +1266,7 @@ "timezones": [ "Asia/Kolkata" ], - "iso": "+91" + "isd": "+91" }, "Indonesia": { "code": "id", @@ -1282,7 +1282,7 @@ "Asia/Makassar", "Asia/Pontianak" ], - "iso": "+62" + "isd": "+62" }, "Iran": { "code": "ir", @@ -1293,7 +1293,7 @@ "timezones": [ "Asia/Tehran" ], - "iso": "+98" + "isd": "+98" }, "Iraq": { "code": "iq", @@ -1306,7 +1306,7 @@ "timezones": [ "Asia/Baghdad" ], - "iso": "+964" + "isd": "+964" }, "Ireland": { "code": "ie", @@ -1319,7 +1319,7 @@ "timezones": [ "Europe/Dublin" ], - "iso": "+353" + "isd": "+353" }, "Isle of Man": { "code": "im", @@ -1330,7 +1330,7 @@ "timezones": [ "Europe/London" ], - "iso": "+44" + "isd": "+44" }, "Israel": { "code": "il", @@ -1343,7 +1343,7 @@ "timezones": [ "Asia/Jerusalem" ], - "iso": "+972" + "isd": "+972" }, "Italy": { "code": "it", @@ -1357,7 +1357,7 @@ "timezones": [ "Europe/Rome" ], - "iso": "+39" + "isd": "+39" }, "Ivory Coast": { "code": "ci", @@ -1370,7 +1370,7 @@ "timeszones": [ "Africa/Abidjan" ], - "iso": "+225" + "isd": "+225" }, "Jamaica": { "code": "jm", @@ -1383,7 +1383,7 @@ "timezones": [ "America/Jamaica" ], - "iso": "+1876" + "isd": "+1876" }, "Japan": { "code": "jp", @@ -1396,7 +1396,7 @@ "timezones": [ "Asia/Tokyo" ], - "iso": "+81" + "isd": "+81" }, "Jersey": { "code": "je", @@ -1407,7 +1407,7 @@ "timezones": [ "Europe/London" ], - "iso": "+44" + "isd": "+44" }, "Jordan": { "code": "jo", @@ -1420,7 +1420,7 @@ "timezones": [ "Asia/Amman" ], - "iso": "+962" + "isd": "+962" }, "Kazakhstan": { "code": "kz", @@ -1437,7 +1437,7 @@ "Asia/Oral", "Asia/Qyzylorda" ], - "iso": "+7" + "isd": "+7" }, "Kenya": { "code": "ke", @@ -1450,7 +1450,7 @@ "timezones": [ "Africa/Nairobi" ], - "iso": "+254" + "isd": "+254" }, "Kiribati": { "code": "ki", @@ -1463,21 +1463,21 @@ "Pacific/Kiritimati", "Pacific/Tarawa" ], - "iso": "+686" + "isd": "+686" }, "Korea, Democratic Peoples Republic of": { "code": "kp", "currency": "KPW", "currency_name": "North Korean Won", "number_format": "#,###.##", - "iso": "+850" + "isd": "+850" }, "Korea, Republic of": { "code": "kr", "currency": "KRW", "currency_name": "Won", "number_format": "#,###", - "iso": "+82" + "isd": "+82" }, "Kuwait": { "code": "kw", @@ -1490,7 +1490,7 @@ "timezones": [ "Asia/Kuwait" ], - "iso": "+965" + "isd": "+965" }, "Kyrgyzstan": { "code": "kg", @@ -1503,7 +1503,7 @@ "timezones": [ "Asia/Bishkek" ], - "iso": "+996" + "isd": "+996" }, "Lao Peoples Democratic Republic": { "code": "la", @@ -1513,7 +1513,7 @@ "timezones": [ "Asia/Vientiane" ], - "iso": "+856" + "isd": "+856" }, "Latvia": { "code": "lv", @@ -1526,7 +1526,7 @@ "timezones": [ "Europe/Riga" ], - "iso": "+371" + "isd": "+371" }, "Lebanon": { "code": "lb", @@ -1539,7 +1539,7 @@ "timezones": [ "Asia/Beirut" ], - "iso": "+961" + "isd": "+961" }, "Lesotho": { "code": "ls", @@ -1552,7 +1552,7 @@ "timezones": [ "Africa/Maseru" ], - "iso": "+266" + "isd": "+266" }, "Liberia": { "code": "lr", @@ -1565,7 +1565,7 @@ "timezones": [ "Africa/Monrovia" ], - "iso": "+231" + "isd": "+231" }, "Libya": { "code": "ly", @@ -1578,7 +1578,7 @@ "timezones": [ "Africa/Tripoli" ], - "iso": "+218" + "isd": "+218" }, "Liechtenstein": { "code": "li", @@ -1589,7 +1589,7 @@ "timezones": [ "Europe/Vaduz" ], - "iso": "+423" + "isd": "+423" }, "Lithuania": { "code": "lt", @@ -1603,7 +1603,7 @@ "timezones": [ "Europe/Vilnius" ], - "iso": "+370" + "isd": "+370" }, "Luxembourg": { "code": "lu", @@ -1616,14 +1616,14 @@ "timezones": [ "Europe/Luxembourg" ], - "iso": "+352" + "isd": "+352" }, "Macao": { "code": "mo", "currency": "MOP", "currency_name": "Pataca", "number_format": "#,###.##", - "iso": "+853" + "isd": "+853" }, "Macedonia": { "code": "mk", @@ -1633,7 +1633,7 @@ "currency_name": "Denar", "currency_symbol": "\u0434\u0435\u043d", "number_format": "#,###.##", - "iso": "+389" + "isd": "+389" }, "Madagascar": { "code": "mg", @@ -1644,7 +1644,7 @@ "timezones": [ "Indian/Antananarivo" ], - "iso": "+261" + "isd": "+261" }, "Malawi": { "code": "mw", @@ -1657,7 +1657,7 @@ "timezones": [ "Africa/Blantyre" ], - "iso": "+265" + "isd": "+265" }, "Malaysia": { "code": "my", @@ -1671,7 +1671,7 @@ "Asia/Kuala_Lumpur", "Asia/Kuching" ], - "iso": "+60" + "isd": "+60" }, "Maldives": { "code": "mv", @@ -1684,7 +1684,7 @@ "timezones": [ "Indian/Maldives" ], - "iso": "+960" + "isd": "+960" }, "Mali": { "code": "ml", @@ -1697,7 +1697,7 @@ "timezones": [ "Africa/Bamako" ], - "iso": "+223" + "isd": "+223" }, "Malta": { "code": "mt", @@ -1710,7 +1710,7 @@ "timezones": [ "Europe/Malta" ], - "iso": "+356" + "isd": "+356" }, "Marshall Islands": { "code": "mh", @@ -1722,7 +1722,7 @@ "Pacific/Kwajalein", "Pacific/Majuro" ], - "iso": "+692" + "isd": "+692" }, "Martinique": { "code": "mq", @@ -1730,7 +1730,7 @@ "timezones": [ "America/Martinique" ], - "iso": "+596" + "isd": "+596" }, "Mauritania": { "code": "mr", @@ -1743,7 +1743,7 @@ "timezones": [ "Africa/Nouakchott" ], - "iso": "+222" + "isd": "+222" }, "Mauritius": { "code": "mu", @@ -1756,7 +1756,7 @@ "timezones": [ "Indian/Mauritius" ], - "iso": "+230" + "isd": "+230" }, "Mayotte": { "code": "yt", @@ -1764,7 +1764,7 @@ "timezones": [ "Indian/Mayotte" ], - "iso": "+262" + "isd": "+262" }, "Mexico": { "code": "mx", @@ -1788,19 +1788,19 @@ "America/Santa_Isabel", "America/Tijuana" ], - "iso": "+52" + "isd": "+52" }, "Micronesia, Federated States of": { "code": "fm", "number_format": "#,###.##", - "iso": "+691" + "isd": "+691" }, "Moldova, Republic of": { "code": "md", "currency": "MDL", "currency_name": "Moldovan Leu", "number_format": "#,###.##", - "iso": "+373" + "isd": "+373" }, "Monaco": { "code": "mc", @@ -1813,7 +1813,7 @@ "timezones": [ "Europe/Monaco" ], - "iso": "+377" + "isd": "+377" }, "Mongolia": { "code": "mn", @@ -1829,7 +1829,7 @@ "Asia/Hovd", "Asia/Ulaanbaatar" ], - "iso": "+976" + "isd": "+976" }, "Montenegro": { "code": "me", @@ -1842,7 +1842,7 @@ "timezones": [ "Europe/Belgrade" ], - "iso": "+382" + "isd": "+382" }, "Montserrat": { "code": "ms", @@ -1855,7 +1855,7 @@ "timezones": [ "America/Montserrat" ], - "iso": "+1664" + "isd": "+1664" }, "Morocco": { "code": "ma", @@ -1868,7 +1868,7 @@ "timezones": [ "Africa/Casablanca" ], - "iso": "+212" + "isd": "+212" }, "Mozambique": { "code": "mz", @@ -1880,7 +1880,7 @@ "timezones": [ "Africa/Maputo" ], - "iso": "+258" + "isd": "+258" }, "Myanmar": { "code": "mm", @@ -1890,7 +1890,7 @@ "timezones": [ "Asia/Rangoon" ], - "iso": "+95" + "isd": "+95" }, "Namibia": { "code": "na", @@ -1903,7 +1903,7 @@ "timezones": [ "Africa/Windhoek" ], - "iso": "+264" + "isd": "+264" }, "Nauru": { "code": "nr", @@ -1914,7 +1914,7 @@ "timezones": [ "Pacific/Nauru" ], - "iso": "+674" + "isd": "+674" }, "Nepal": { "code": "np", @@ -1927,7 +1927,7 @@ "timezones": [ "Asia/Kathmandu" ], - "iso": "+977" + "isd": "+977" }, "Netherlands": { "code": "nl", @@ -1940,7 +1940,7 @@ "timezones": [ "Europe/Amsterdam" ], - "iso": "+31" + "isd": "+31" }, "New Caledonia": { "code": "nc", @@ -1951,7 +1951,7 @@ "timezones": [ "Pacific/Noumea" ], - "iso": "+687" + "isd": "+687" }, "New Zealand": { "code": "nz", @@ -1965,7 +1965,7 @@ "Pacific/Auckland", "Pacific/Chatham" ], - "iso": "+64" + "isd": "+64" }, "Nicaragua": { "code": "ni", @@ -1978,7 +1978,7 @@ "timezones": [ "America/Managua" ], - "iso": "+505" + "isd": "+505" }, "Niger": { "code": "ne", @@ -1991,7 +1991,7 @@ "timezones": [ "Africa/Niamey" ], - "iso": "+227" + "isd": "+227" }, "Nigeria": { "code": "ng", @@ -2004,7 +2004,7 @@ "timezones": [ "Africa/Lagos" ], - "iso": "+234" + "isd": "+234" }, "Niue": { "code": "nu", @@ -2015,7 +2015,7 @@ "timezones": [ "Pacific/Niue" ], - "iso": "+683" + "isd": "+683" }, "Norfolk Island": { "code": "nf", @@ -2023,7 +2023,7 @@ "timezones": [ "Pacific/Norfolk" ], - "iso": "+672" + "isd": "+672" }, "Northern Mariana Islands": { "code": "mp", @@ -2031,7 +2031,7 @@ "timezones": [ "Pacific/Saipan" ], - "iso": "+1670" + "isd": "+1670" }, "Norway": { "code": "no", @@ -2044,7 +2044,7 @@ "timezones": [ "Europe/Oslo" ], - "iso": "+47" + "isd": "+47" }, "Oman": { "code": "om", @@ -2057,7 +2057,7 @@ "timezones": [ "Asia/Muscat" ], - "iso": "+968" + "isd": "+968" }, "Pakistan": { "code": "pk", @@ -2070,7 +2070,7 @@ "timezones": [ "Asia/Karachi" ], - "iso": "+92" + "isd": "+92" }, "Palau": { "code": "pw", @@ -2082,12 +2082,12 @@ "timezones": [ "Pacific/Palau" ], - "iso": "+680" + "isd": "+680" }, "Palestinian Territory, Occupied": { "code": "ps", "number_format": "#,###.##", - "iso": "+970" + "isd": "+970" }, "Panama": { "code": "pa", @@ -2098,7 +2098,7 @@ "timezones": [ "America/Panama" ], - "iso": "+507" + "isd": "+507" }, "Papua New Guinea": { "code": "pg", @@ -2111,7 +2111,7 @@ "timezones": [ "Pacific/Port_Moresby" ], - "iso": "+675" + "isd": "+675" }, "Paraguay": { "code": "py", @@ -2124,7 +2124,7 @@ "timezones": [ "America/Asuncion" ], - "iso": "+595" + "isd": "+595" }, "Peru": { "code": "pe", @@ -2137,7 +2137,7 @@ "timezones": [ "America/Lima" ], - "iso": "+51" + "isd": "+51" }, "Philippines": { "code": "ph", @@ -2151,7 +2151,7 @@ "timezones": [ "Asia/Manila" ], - "iso": "+63" + "isd": "+63" }, "Pitcairn": { "code": "pn", @@ -2159,7 +2159,7 @@ "timezones": [ "Pacific/Pitcairn" ], - "iso": "+64" + "isd": "+64" }, "Poland": { "code": "pl", @@ -2171,7 +2171,7 @@ "timezones": [ "Europe/Warsaw" ], - "iso": "+48" + "isd": "+48" }, "Portugal": { "code": "pt", @@ -2186,7 +2186,7 @@ "Atlantic/Madeira", "Europe/Lisbon" ], - "iso": "+351" + "isd": "+351" }, "Puerto Rico": { "code": "pr", @@ -2194,7 +2194,7 @@ "timezones": [ "America/Puerto_Rico" ], - "iso": "+1939" + "isd": "+1939" }, "Qatar": { "code": "qa", @@ -2207,7 +2207,7 @@ "timezones": [ "Asia/Qatar" ], - "iso": "+974" + "isd": "+974" }, "Romania": { "code": "ro", @@ -2220,14 +2220,14 @@ "timezones": [ "Europe/Bucharest" ], - "iso": "+40" + "isd": "+40" }, "Russian Federation": { "code": "ru", "currency": "RUB", "currency_name": "Russian Ruble", "number_format": "#.###,##", - "iso": "+7" + "isd": "+7" }, "Rwanda": { "code": "rw", @@ -2240,24 +2240,24 @@ "timezones": [ "Africa/Kigali" ], - "iso": "+250" + "isd": "+250" }, "R\u00e9union": { "code": "re", "number_format": "#,###.##", - "iso": "+262" + "isd": "+262" }, "Saint Barth\u00e9lemy": { "code": "bl", "number_format": "#,###.##", - "iso": "+590" + "isd": "+590" }, "Saint Helena, Ascension and Tristan da Cunha": { "code": "sh", "currency": "SHP", "currency_name": "Saint Helena Pound", "number_format": "#,###.##", - "iso": "+290" + "isd": "+290" }, "Saint Kitts and Nevis": { "code": "kn", @@ -2270,7 +2270,7 @@ "timezones": [ "America/St_Kitts" ], - "iso": "+1869" + "isd": "+1869" }, "Saint Lucia": { "code": "lc", @@ -2283,17 +2283,17 @@ "timezones": [ "America/St_Lucia" ], - "iso": "+1758" + "isd": "+1758" }, "Saint Martin (French part)": { "code": "mf", "number_format": "#,###.##", - "iso": "+590" + "isd": "+590" }, "Saint Pierre and Miquelon": { "code": "pm", "number_format": "#,###.##", - "iso": "+508" + "isd": "+508" }, "Saint Vincent and the Grenadines": { "code": "vc", @@ -2306,7 +2306,7 @@ "timezones": [ "America/St_Vincent" ], - "iso": "+1784" + "isd": "+1784" }, "Samoa": { "code": "ws", @@ -2319,7 +2319,7 @@ "timezones": [ "Pacific/Apia" ], - "iso": "+685" + "isd": "+685" }, "San Marino": { "code": "sm", @@ -2332,14 +2332,14 @@ "timezones": [ "Europe/Rome" ], - "iso": "+378" + "isd": "+378" }, "Sao Tome and Principe": { "code": "st", "currency": "STD", "currency_name": "Dobra", "number_format": "#,###.##", - "iso": "+239" + "isd": "+239" }, "Saudi Arabia": { "code": "sa", @@ -2352,7 +2352,7 @@ "timezones": [ "Asia/Riyadh" ], - "iso": "+966" + "isd": "+966" }, "Senegal": { "code": "sn", @@ -2365,7 +2365,7 @@ "timezones": [ "Africa/Dakar" ], - "iso": "+221" + "isd": "+221" }, "Serbia": { "code": "rs", @@ -2378,7 +2378,7 @@ "timezones": [ "Europe/Belgrade" ], - "iso": "+381" + "isd": "+381" }, "Seychelles": { "code": "sc", @@ -2391,7 +2391,7 @@ "timezones": [ "Indian/Mahe" ], - "iso": "+248" + "isd": "+248" }, "Sierra Leone": { "code": "sl", @@ -2404,7 +2404,7 @@ "timezones": [ "Africa/Freetown" ], - "iso": "+232" + "isd": "+232" }, "Singapore": { "code": "sg", @@ -2417,7 +2417,7 @@ "timezones": [ "Asia/Singapore" ], - "iso": "+65" + "isd": "+65" }, "Sint Maarten (Dutch part)": { "code": "sx", @@ -2434,7 +2434,7 @@ "timezones": [ "Europe/Bratislava" ], - "iso": "+421" + "isd": "+421" }, "Slovenia": { "code": "si", @@ -2447,7 +2447,7 @@ "timezones": [ "Europe/Belgrade" ], - "iso": "+386" + "isd": "+386" }, "Solomon Islands": { "code": "sb", @@ -2460,7 +2460,7 @@ "timezones": [ "Pacific/Guadalcanal" ], - "iso": "+677" + "isd": "+677" }, "Somalia": { "code": "so", @@ -2473,7 +2473,7 @@ "timezones": [ "Africa/Mogadishu" ], - "iso": "+252" + "isd": "+252" }, "South Africa": { "code": "za", @@ -2487,7 +2487,7 @@ "timezones": [ "Africa/Johannesburg" ], - "iso": "+27" + "isd": "+27" }, "South Georgia and the South Sandwich Islands": { "code": "gs", @@ -2495,7 +2495,7 @@ "currency_fraction_units": 100, "currency_symbol": "\u00a3", "number_format": "#,###.##", - "iso": "+500" + "isd": "+500" }, "South Sudan": { "code": "ss", @@ -2506,7 +2506,7 @@ "timezones": [ "Africa/Juba" ], - "iso": "+211" + "isd": "+211" }, "Spain": { "code": "es", @@ -2521,7 +2521,7 @@ "Atlantic/Canary", "Europe/Madrid" ], - "iso": "+34" + "isd": "+34" }, "Sri Lanka": { "code": "lk", @@ -2534,7 +2534,7 @@ "timezones": [ "Asia/Colombo" ], - "iso": "+94" + "isd": "+94" }, "Sudan": { "code": "sd", @@ -2545,7 +2545,7 @@ "timezones": [ "Africa/Khartoum" ], - "iso": "+249" + "isd": "+249" }, "Suriname": { "code": "sr", @@ -2557,12 +2557,12 @@ "timezones": [ "America/Paramaribo" ], - "iso": "+597" + "isd": "+597" }, "Svalbard and Jan Mayen": { "code": "sj", "number_format": "#,###.##", - "iso": "+47" + "isd": "+47" }, "Swaziland": { "code": "sz", @@ -2575,7 +2575,7 @@ "timezones": [ "Africa/Mbabane" ], - "iso": "+268" + "isd": "+268" }, "Sweden": { "code": "se", @@ -2588,7 +2588,7 @@ "timezones": [ "Europe/Stockholm" ], - "iso": "+46" + "isd": "+46" }, "Switzerland": { "code": "ch", @@ -2602,21 +2602,21 @@ "timezones": [ "Europe/Zurich" ], - "iso": "+41" + "isd": "+41" }, "Syria": { "code": "sy", "currency": "SYP", "currency_name": "Syrian Pound", "number_format": "#,###.##", - "iso": "+963" + "isd": "+963" }, "Taiwan": { "code": "tw", "currency": "TWD", "date_format": "yyyy-mm-dd", "number_format": "#,###.##", - "iso": "+886" + "isd": "+886" }, "Tajikistan": { "code": "tj", @@ -2627,14 +2627,14 @@ "timezones": [ "Asia/Dushanbe" ], - "iso": "+992" + "isd": "+992" }, "Tanzania": { "code": "tz", "currency": "TZS", "currency_name": "Tanzanian Shilling", "number_format": "#,###.##", - "iso": "+255" + "isd": "+255" }, "Thailand": { "code": "th", @@ -2647,12 +2647,12 @@ "timezones": [ "Asia/Bangkok" ], - "iso": "+66" + "isd": "+66" }, "Timor-Leste": { "code": "tl", "number_format": "#,###.##", - "iso": "+670" + "isd": "+670" }, "Togo": { "code": "tg", @@ -2665,7 +2665,7 @@ "timezones": [ "Africa/Lome" ], - "iso": "+228" + "isd": "+228" }, "Tokelau": { "code": "tk", @@ -2673,7 +2673,7 @@ "timezones": [ "Pacific/Fakaofo" ], - "iso": "+690" + "isd": "+690" }, "Tonga": { "code": "to", @@ -2686,7 +2686,7 @@ "timezones": [ "Pacific/Tongatapu" ], - "iso": "+676" + "isd": "+676" }, "Trinidad and Tobago": { "code": "tt", @@ -2699,7 +2699,7 @@ "timezones": [ "America/Port_of_Spain" ], - "iso": "+1868" + "isd": "+1868" }, "Tunisia": { "code": "tn", @@ -2712,7 +2712,7 @@ "timezones": [ "Africa/Tunis" ], - "iso": "+216" + "isd": "+216" }, "Turkey": { "code": "tr", @@ -2724,7 +2724,7 @@ "timezones": [ "Europe/Istanbul" ], - "iso": "+90" + "isd": "+90" }, "Turkmenistan": { "code": "tm", @@ -2737,7 +2737,7 @@ "timezones": [ "Asia/Ashgabat" ], - "iso": "+993" + "isd": "+993" }, "Turks and Caicos Islands": { "code": "tc", @@ -2745,7 +2745,7 @@ "currency_fraction_units": 100, "currency_symbol": "$", "number_format": "#,###.##", - "iso": "+1649" + "isd": "+1649" }, "Tuvalu": { "code": "tv", @@ -2756,7 +2756,7 @@ "timezones": [ "Pacific/Funafuti" ], - "iso": "+688" + "isd": "+688" }, "Uganda": { "code": "ug", @@ -2769,7 +2769,7 @@ "timezones": [ "Africa/Kampala" ], - "iso": "+256" + "isd": "+256" }, "Ukraine": { "code": "ua", @@ -2785,7 +2785,7 @@ "Europe/Uzhgorod", "Europe/Zaporozhye" ], - "iso": "+380" + "isd": "+380" }, "United Arab Emirates": { "code": "ae", @@ -2798,7 +2798,7 @@ "timezones": [ "Asia/Dubai" ], - "iso": "+971" + "isd": "+971" }, "United Kingdom": { "code": "gb", @@ -2811,7 +2811,7 @@ "timezones": [ "Europe/London" ], - "iso": "+44" + "isd": "+44" }, "United States": { "code": "us", @@ -2855,7 +2855,7 @@ "America/Yakutat", "Pacific/Honolulu" ], - "iso": "+1" + "isd": "+1" }, "United States Minor Outlying Islands": { "code": "um", @@ -2872,7 +2872,7 @@ "timezones": [ "America/Montevideo" ], - "iso": "+598" + "isd": "+598" }, "Uzbekistan": { "code": "uz", @@ -2886,7 +2886,7 @@ "Asia/Samarkand", "Asia/Tashkent" ], - "iso": "+998" + "isd": "+998" }, "Vanuatu": { "code": "vu", @@ -2899,7 +2899,7 @@ "timezones": [ "Pacific/Efate" ], - "iso": "+678" + "isd": "+678" }, "Venezuela, Bolivarian Republic of": { "code": "ve", @@ -2908,24 +2908,24 @@ "currency_symbol": "Bs.", "currency_fraction": "Centimos", "currency_fraction_units": 100, - "iso": "+58" + "isd": "+58" }, "Vietnam": { "code": "vn", "currency": "VND", "currency_name": "Dong", "number_format": "#.###", - "iso": "+84" + "isd": "+84" }, "Virgin Islands, British": { "code": "vg", "number_format": "#,###.##", - "iso": "+1284" + "isd": "+1284" }, "Virgin Islands, U.S.": { "code": "vi", "number_format": "#,###.##", - "iso": "+1340" + "isd": "+1340" }, "Wallis and Futuna": { "code": "wf", @@ -2933,7 +2933,7 @@ "currency_fraction_units": 100, "currency_symbol": "Fr", "number_format": "#,###.##", - "iso": "+681" + "isd": "+681" }, "Western Sahara": { "code": "eh", @@ -2954,7 +2954,7 @@ "timezones": [ "Asia/Aden" ], - "iso": "+967" + "isd": "+967" }, "Zambia": { "code": "zm", @@ -2967,7 +2967,7 @@ "timezones": [ "Africa/Lusaka" ], - "iso": "+260" + "isd": "+260" }, "Zimbabwe": { "code": "zw", @@ -2980,11 +2980,11 @@ "timezones": [ "Africa/Harare" ], - "iso": "+263" + "isd": "+263" }, "\u00c5land Islands": { "code": "ax", "number_format": "#,###.##", - "iso": "+358" + "isd": "+358" } -} \ No newline at end of file +} diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index bf5a865d02..308957ccb5 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -13,7 +13,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD // Replaces code when selected and removes previously selected. this.picker.on_change = (country) => { const country_code = frappe.boot.country_codes[country].code; - const country_isd = frappe.boot.country_codes[country].iso; + const country_isd = frappe.boot.country_codes[country].isd; this.change_flag(country_code); this.$icon = this.selected_icon.find('svg'); this.$flag = this.selected_icon.find('img'); @@ -151,7 +151,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD } get_country(country) { const country_codes = frappe.boot.country_codes; - return country_codes[country].iso; + return country_codes[country].isd; } get_country_flag(country) { const country_codes = frappe.boot.country_codes; diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index 7270fe63a1..34241be915 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -34,11 +34,11 @@ class Picker { setup_countries() { Object.entries(this.countries).forEach(([country, info]) => { - if (!info.iso) { + if (!info.isd) { return } let $country = $(`
${frappe.utils.flag(info.code)} - ${country} (${info.iso})
`); + ${country} (${info.isd})
`); this.phone_wrapper.append($country); const set_values = () => { this.set_country(country); From ad26e277182a245a5a30b32f3a88b59efb9d199e Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 7 Jan 2022 12:51:42 +0530 Subject: [PATCH 13/30] fix: updated test --- cypress/integration/control_phone.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 684e0d7d99..f59ebd2667 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -4,7 +4,7 @@ context('Control Phone', () => { cy.visit('/app/website'); }); - function get_dialog_with_phone(s) { + function get_dialog_with_phone() { return cy.dialog({ title: 'Phone', fields: [{ @@ -19,10 +19,10 @@ context('Control Phone', () => { cy.get('.selected-phone').click(); cy.get('.phone-picker .phone-wrapper[id="afghanistan"]').click(); cy.get('.phone-picker .phone-wrapper[id="india"]').click(); - cy.get('.selected-phone .country').should('have.text', '+91') - cy.get('.selected-phone > .icon > use').should('have.attr', 'href').and('include', '#in') + cy.get('.selected-phone .country').should('have.text', '+91'); + cy.get('.selected-phone > img').should('have.attr', 'src').and('include', '/in.png'); - let phone_number = '9312672712' + let phone_number = '9312672712'; cy.get('.selected-phone').click().first(); cy.get('.frappe-control[data-fieldname=phone] input') .first() From 938d12b14258fd5fbdb51ed0d2351397d3159df1 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 7 Jan 2022 12:52:09 +0530 Subject: [PATCH 14/30] fix: sider --- frappe/database/mariadb/database.py | 4 ++-- frappe/database/postgres/database.py | 4 ++-- frappe/public/js/frappe/form/controls/control.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 5e838db842..9e0e8e1b51 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -52,8 +52,8 @@ class MariaDBDatabase(Database): 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), 'Duration': ('decimal', '21,9'), - 'Icon': ('varchar', self.VARCHAR_LEN), - 'Phone': ('varchar', self.VARCHAR_LEN) + 'Icon': ('varchar', self.VARCHAR_LEN), + 'Phone': ('varchar', self.VARCHAR_LEN) } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 71b5918d4a..3ad3c10869 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -62,8 +62,8 @@ class PostgresDatabase(Database): 'Barcode': ('text', ''), 'Geolocation': ('text', ''), 'Duration': ('decimal', '21,9'), - 'Icon': ('varchar', self.VARCHAR_LEN), - 'Phone': ('varchar', self.VARCHAR_LEN) + 'Icon': ('varchar', self.VARCHAR_LEN), + 'Phone': ('varchar', self.VARCHAR_LEN) } def get_connection(self): diff --git a/frappe/public/js/frappe/form/controls/control.js b/frappe/public/js/frappe/form/controls/control.js index 578ddd3276..dad3ed1bec 100644 --- a/frappe/public/js/frappe/form/controls/control.js +++ b/frappe/public/js/frappe/form/controls/control.js @@ -40,7 +40,7 @@ import './multiselect_list'; import './rating'; import './duration'; import './icon'; -import './phone' +import './phone'; frappe.ui.form.make_control = function (opts) { var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, ""); From b45e5dda63df0bc95341033a5d847cf6f2c27155 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 7 Jan 2022 13:40:03 +0530 Subject: [PATCH 15/30] refactor: moved phone_validation to frappe.utils --- frappe/model/base_document.py | 10 +--------- frappe/public/js/frappe/form/controls/phone.js | 2 +- frappe/utils/__init__.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index ac87e564ac..b4f1910bbc 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -11,7 +11,6 @@ from frappe.model import display_fieldtypes from frappe.utils import (cint, flt, now, cstr, strip_html, sanitize_html, sanitize_email, cast_fieldtype) from frappe.utils.html_utils import unescape_html -import phonenumbers as ph max_positive_value = { 'smallint': 2 ** 15, @@ -655,14 +654,7 @@ class BaseDocument(object): # data_field options defined in frappe.model.data_field_options for phone_field in self.meta.get_phone_fields(): phone = self.get(phone_field.fieldname) - try: - phone = ph.parse(phone) - except Exception as e: - if e.error_type == 1: - frappe.throw(_("The entered value is not a phone number."), title="Invalid Number") - frappe.throw(_("Please select a country code."), title = _("Country Code Required")) - if not ph.is_valid_number(phone): - frappe.throw('This is not a valid phone number', title = "Invalid Number") + frappe.utils.validate_phone_number_with_isd(phone, throw=True) for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 308957ccb5..492b75f8c3 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -128,7 +128,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD change_flag(country_code) { this.selected_icon.find('img').attr('src', 'https://flagcdn.com/h20/'+country_code+'.png') this.$icon = this.selected_icon.find('img'); - // this.$icon.hasClass('hide') && this.$icon.toggleClass('hide'); + this.$icon.hasClass('hide') && this.$icon.toggleClass('hide'); } // country_code for India is 'in' diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 1b4cdc7922..add55fc238 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -17,6 +17,7 @@ from typing import Generator, Iterable from urllib.parse import quote, urlparse from werkzeug.test import Client from redis.exceptions import ConnectionError +import phonenumbers as ph import frappe # utility functions like cint, int, flt, etc. @@ -71,6 +72,21 @@ def extract_email_id(email): email_id = email_id.decode("utf-8", "ignore") return email_id +def validate_phone_number_with_isd(phone, throw=False): + if not phone: + return + try: + phone = ph.parse(phone) + except Exception as e: + if e.error_type == 1: + frappe.throw(frappe._("The entered value is not a phone number."), frappe.InvalidPhoneNumberError, + title=frappe._("Invalid Number")) + frappe.throw(frappe._("Please select a country code."), frappe.InvalidPhoneNumberError, + title = frappe._("Country Code Required")) + if not ph.is_valid_number(phone): + frappe.throw(frappe._("This is not a valid phone number"), frappe.InvalidPhoneNumberError, + title = frappe._("Invalid Number")) + def validate_phone_number(phone_number, throw=False): """Returns True if valid phone number""" if not phone_number: From 8d00c4ff7611363ba8b94bd6f70cc68c58f60cf7 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 7 Jan 2022 14:17:56 +0530 Subject: [PATCH 16/30] fix: sider and styling --- cypress/integration/control_phone.js | 2 +- frappe/database/mariadb/database.py | 2 +- .../public/js/frappe/form/controls/phone.js | 21 ++++--- .../js/frappe/phone_picker/phone_picker.js | 58 +++++++++---------- frappe/public/js/frappe/utils/utils.js | 13 +---- frappe/public/scss/common/phone_picker.scss | 2 +- 6 files changed, 45 insertions(+), 53 deletions(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index f59ebd2667..952e31a9eb 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -20,7 +20,7 @@ context('Control Phone', () => { cy.get('.phone-picker .phone-wrapper[id="afghanistan"]').click(); cy.get('.phone-picker .phone-wrapper[id="india"]').click(); cy.get('.selected-phone .country').should('have.text', '+91'); - cy.get('.selected-phone > img').should('have.attr', 'src').and('include', '/in.png'); + cy.get('.selected-phone > img').should('have.attr', 'src').and('include', '/in.svg'); let phone_number = '9312672712'; cy.get('.selected-phone').click().first(); diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 21d5a9962c..fde86a7876 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -52,7 +52,7 @@ class MariaDBDatabase(Database): 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), 'Duration': ('decimal', '21,9'), - 'Icon': ('varchar', self.VARCHAR_LEN), + 'Icon': ('varchar', self.VARCHAR_LEN), 'Phone': ('varchar', self.VARCHAR_LEN) } diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 492b75f8c3..7144b31f1e 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -17,7 +17,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.change_flag(country_code); this.$icon = this.selected_icon.find('svg'); this.$flag = this.selected_icon.find('img'); - if (!this.$icon.hasClass('hide')){ + if (!this.$icon.hasClass('hide')) { this.$icon.toggleClass('hide'); } if (!this.$flag.length) { @@ -28,7 +28,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD } else { this.$isd.text(country_isd); } - if(this.$input.val()) { + if (this.$input.val()) { this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()); } }; @@ -85,7 +85,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.selected_icon.insertAfter(this.$input); this.selected_icon.append($(``)); this.$isd = this.selected_icon.find('.country'); - if(input_value && input_value.split("-").length == 2) { + if (input_value && input_value.split("-").length == 2) { this.$isd.text(this.value.split("-")[0]); } } @@ -95,7 +95,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD super.refresh(); // Previously opened doc values get fetched. - if(!this.value) { + if (!this.value) { this.$input.val(""); this.$wrapper.find('.country').text(""); if (this.selected_icon.find('svg').hasClass('hide')) { @@ -103,30 +103,30 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.selected_icon.find('img').addClass('hide'); } } - if(this.value && this.value.split("-").length == 2) { + if (this.value && this.value.split("-").length == 2) { let isd = this.value.split("-")[0]; this.get_country_code_and_change_flag(isd); this.picker.set_country(isd); this.picker.refresh(); if (this.picker.country && this.picker.country !== this.$isd.text()) { - this.$isd.length && this.$isd.text(isd) + this.$isd.length && this.$isd.text(isd); } } } set_formatted_input(value) { - if(value && value.includes('-')) { + if (value && value.includes('-')) { this.set_model_value(value); this.$input.val(value.split("-").pop()); - } else if(this.$isd.text().trim() && this.value) { + } else if (this.$isd.text().trim() && this.value) { let code_number = this.$isd.text() + '-' + value; this.set_model_value(code_number); } } change_flag(country_code) { - this.selected_icon.find('img').attr('src', 'https://flagcdn.com/h20/'+country_code+'.png') + this.selected_icon.find('img').attr('src', 'https://flagcdn.com/'+country_code+'.svg'); this.$icon = this.selected_icon.find('img'); this.$icon.hasClass('hide') && this.$icon.toggleClass('hide'); } @@ -142,8 +142,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD if (!flag.length) { this.selected_icon.prepend(this.get_country_flag(country)); this.selected_icon.find('svg').addClass('hide'); - } - else { + } else { this.change_flag(code); } } diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index 34241be915..cbc5df4b12 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -34,38 +34,38 @@ class Picker { setup_countries() { Object.entries(this.countries).forEach(([country, info]) => { - if (!info.isd) { - return - } - let $country = $(`
${frappe.utils.flag(info.code)} - ${country} (${info.isd})
`); - this.phone_wrapper.append($country); - const set_values = () => { - this.set_country(country); - this.update_icon_selected(); - }; - $country.on('click', () => { - set_values(); - }); - $country.hover(() => { - $country.toggleClass("bg-gray-100"); - }); - this.search_input.keydown((e) => { - const key_code = e.keyCode; - if ([13].includes(key_code)) { - e.preventDefault(); - set_values(); + if (!info.isd) { + return; } - }); - this.search_input.keyup((e) => { - e.preventDefault(); - this.filter_icons(); - }); + let $country = $(`
${frappe.utils.flag(info.code)} + ${country} (${info.isd})
`); + this.phone_wrapper.append($country); + const set_values = () => { + this.set_country(country); + this.update_icon_selected(); + }; + $country.on('click', () => { + set_values(); + }); + $country.hover(() => { + $country.toggleClass("bg-gray-100"); + }); + this.search_input.keydown((e) => { + const key_code = e.keyCode; + if ([13].includes(key_code)) { + e.preventDefault(); + set_values(); + } + }); + this.search_input.keyup((e) => { + e.preventDefault(); + this.filter_icons(); + }); - this.search_input.on('search', () => { - this.filter_icons(); + this.search_input.on('search', () => { + this.filter_icons(); + }); }); - }); } filter_icons() { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index c7739c82a1..3cde43f529 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1164,17 +1164,10 @@ Object.assign(frappe.utils, { `; }, - flag(icon_name, size="sm", icon_class="", icon_style="", svg_class="") { - let size_class = ""; - - if (typeof size == "object") { - icon_style += ` width: ${size.width}; height: ${size.height}`; - } else { - size_class = `flag-${size}`; - } + flag(country_code) { return `` + src="https://flagcdn.com/${country_code}.svg" + width="20" height="15">`; }, make_chart(wrapper, custom_options={}) { diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index cc3387ecb0..dea364d157 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -75,7 +75,7 @@ height: 20px; border-radius: 5px; position: absolute; - top: calc(50% + 2.6px); + top: calc(50% + 2px); left: 8px; content: ' '; align-items: center; From d4bf210a033659a957892f978823dd04fc2c5e0f Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 22 Mar 2022 15:06:42 +0530 Subject: [PATCH 17/30] style(ui): dynamic spacing between isd code and phone number --- frappe/public/js/frappe/form/controls/phone.js | 8 ++++++++ frappe/public/scss/common/phone_picker.scss | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 7144b31f1e..060d41f328 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -17,6 +17,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.change_flag(country_code); this.$icon = this.selected_icon.find('svg'); this.$flag = this.selected_icon.find('img'); + if (!this.$icon.hasClass('hide')) { this.$icon.toggleClass('hide'); } @@ -111,6 +112,13 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD if (this.picker.country && this.picker.country !== this.$isd.text()) { this.$isd.length && this.$isd.text(isd); } + let len = this.$isd.text().length; + let diff = len - 3; + if (len > 3) { + this.$input.css("padding-left", 67 + (diff * 9)); + } else { + this.$input.css("padding-left", 67); + } } } diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index dea364d157..bbac895018 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -58,7 +58,7 @@ } .phone-picker-popover { max-width: 325px; - left: -20px !important; + left: 11px !important; .picker-arrow { left: 15px !important; } @@ -66,7 +66,7 @@ .frappe-control[data-fieldtype='Phone'] { input { - padding-left: 80px; + padding-left: 67px; } .selected-phone { display: flex; From 70d0aaf52d0580d56a04afbe2f43c5e51ea81e07 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 22 Mar 2022 15:52:59 +0530 Subject: [PATCH 18/30] test(ui): updated click events to be specific --- cypress/integration/control_phone.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 952e31a9eb..9bfce3059c 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -16,14 +16,14 @@ context('Control Phone', () => { it('should set flag and data', () => { get_dialog_with_phone().as('dialog'); - cy.get('.selected-phone').click(); + cy.get('.selected-phone > svg').click(); cy.get('.phone-picker .phone-wrapper[id="afghanistan"]').click(); cy.get('.phone-picker .phone-wrapper[id="india"]').click(); cy.get('.selected-phone .country').should('have.text', '+91'); cy.get('.selected-phone > img').should('have.attr', 'src').and('include', '/in.svg'); let phone_number = '9312672712'; - cy.get('.selected-phone').click().first(); + cy.get('.selected-phone > img').click().first(); cy.get('.frappe-control[data-fieldname=phone] input') .first() .click(); @@ -42,7 +42,7 @@ context('Control Phone', () => { it('case insensitive search for country and clear search', () => { let search_text = 'india'; - cy.get('.selected-phone').click().first(); + cy.get('.selected-phone > img').click().first(); cy.get('.phone-picker').findByRole('searchbox').click().type(search_text); cy.get('.phone-section .phone-wrapper:not(.hidden)').then(i => { cy.get(`.phone-section .phone-wrapper[id*='${search_text.toLowerCase()}']`).then(countries => { From 0cf945f5f1edeaea221ada08c0b07d86932c7471 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 28 Mar 2022 10:52:20 +0530 Subject: [PATCH 19/30] fix: alignment for flag in dialogs, padding for text area at initial state --- .../public/js/frappe/form/controls/phone.js | 22 ++++++++------ .../js/frappe/phone_picker/phone_picker.js | 4 +-- frappe/public/scss/common/phone_picker.scss | 29 +++++++++++++++---- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 060d41f328..e4252b762f 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -1,5 +1,5 @@ -import Picker from '../../phone_picker/phone_picker'; +import PhonePicker from '../../phone_picker/phone_picker'; frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlData { @@ -32,6 +32,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD if (this.$input.val()) { this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()); } + this.change_padding(); }; this.$wrapper.find('.selected-phone').on('click', (e) => { @@ -51,7 +52,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD make_icon_input() { let picker_wrapper = $('
'); - this.picker = new Picker({ + this.picker = new PhonePicker({ parent: picker_wrapper, countries: frappe.boot.country_codes }); @@ -103,6 +104,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.selected_icon.find('svg').toggleClass('hide'); this.selected_icon.find('img').addClass('hide'); } + this.$input.css("padding-left", 30); } if (this.value && this.value.split("-").length == 2) { let isd = this.value.split("-")[0]; @@ -112,13 +114,6 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD if (this.picker.country && this.picker.country !== this.$isd.text()) { this.$isd.length && this.$isd.text(isd); } - let len = this.$isd.text().length; - let diff = len - 3; - if (len > 3) { - this.$input.css("padding-left", 67 + (diff * 9)); - } else { - this.$input.css("padding-left", 67); - } } } @@ -165,4 +160,13 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD let code = country_codes[country].code; return frappe.utils.flag(code); } + change_padding() { + let len = this.$isd.text().length; + let diff = len - 3; + if (len > 3) { + this.$input.css("padding-left", 67 + (diff * 7)); + } else { + this.$input.css("padding-left", 67); + } + } }; diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index cbc5df4b12..c5c1437c12 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -1,4 +1,4 @@ -class Picker { +class PhonePicker { constructor(opts) { this.parent = opts.parent; this.width = opts.width; @@ -91,4 +91,4 @@ class Picker { } } -export default Picker; +export default PhonePicker; diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index bbac895018..7c9bbcf7e5 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -56,15 +56,22 @@ } } } + .phone-picker-popover { max-width: 325px; - left: 11px !important; + left: 29px !important; .picker-arrow { left: 15px !important; } + @media (max-width: 992px) { + max-width: 325px; + left: 48px !important; + } } + + .frappe-control[data-fieldtype='Phone'] - { +{ input { padding-left: 67px; } @@ -103,6 +110,18 @@ } } +.modal-body { + .frappe-control[data-fieldtype='Phone'] + { + input { + padding-left: 67px; + } + .selected-phone { + top: calc(50%); + } + } +} + .data-row.row { .selected-phone { top: calc(50% - 11px); @@ -111,8 +130,8 @@ } .bg-gray-100 { - --tw-bg-opacity: 1; - background-color: rgba(244,245,246,var(--tw-bg-opacity)); + --tw-bg-opacity: 1; + background-color: rgba(244,245,246,var(--tw-bg-opacity)); } .dt-cell__content { @@ -126,5 +145,3 @@ top: 5px !important; } } - - From 9dc123182737fa69a5982b47abc3222bae4549de Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 28 Mar 2022 12:27:49 +0530 Subject: [PATCH 20/30] refactor: code cleanup --- cypress/integration/control_phone.js | 11 ++--- .../public/js/frappe/form/controls/phone.js | 41 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 9bfce3059c..8d1e7c1d05 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -24,18 +24,19 @@ context('Control Phone', () => { let phone_number = '9312672712'; cy.get('.selected-phone > img').click().first(); - cy.get('.frappe-control[data-fieldname=phone] input') + cy.get_field("phone") .first() - .click(); + .click({multiple: true}); cy.get('.frappe-control[data-fieldname=phone]') .findByRole('textbox') .first() .type(phone_number); - cy.get('.frappe-control[data-fieldname=phone] input').first().should('have.value', phone_number); - cy.get('.frappe-control[data-fieldname=phone] input').first().blur(); + cy.get_field("phone").first().should('have.value', phone_number); + cy.get_field("phone").first().blur({force: true}); + cy.get('@dialog').then(dialog => { - let value = dialog.fields_dict.phone.value; + let value = dialog.get_value("phone"); expect(value).to.equal('+91-' + phone_number); }); }); diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index e4252b762f..98de47ff41 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -30,7 +30,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.$isd.text(country_isd); } if (this.$input.val()) { - this.set_formatted_input(this.get_country(country) +'-'+ this.$input.val()); + this.set_value(this.get_country(country) +'-'+ this.$input.val()); } this.change_padding(); }; @@ -95,9 +95,8 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD refresh() { super.refresh(); - - // Previously opened doc values get fetched. - if (!this.value) { + // Previously opened doc values showing up on a new doc + if (this.frm.doc.__islocal && !this.get_value()) { this.$input.val(""); this.$wrapper.find('.country').text(""); if (this.selected_icon.find('svg').hasClass('hide')) { @@ -106,7 +105,10 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD } this.$input.css("padding-left", 30); } - if (this.value && this.value.split("-").length == 2) { + } + + set_formatted_input(value) { + if (value && value.includes('-') && value.split('-').length == 2) { let isd = this.value.split("-")[0]; this.get_country_code_and_change_flag(isd); this.picker.set_country(isd); @@ -114,18 +116,17 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD if (this.picker.country && this.picker.country !== this.$isd.text()) { this.$isd.length && this.$isd.text(isd); } + this.change_padding(); + this.$input.val(value.split('-').pop()); + + } else if (this.$isd.text().trim() && this.value) { + let code_number = this.$isd.text() + '-' + value; + this.set_value(code_number); } } - - set_formatted_input(value) { - if (value && value.includes('-')) { - this.set_model_value(value); - this.$input.val(value.split("-").pop()); - } else if (this.$isd.text().trim() && this.value) { - let code_number = this.$isd.text() + '-' + value; - this.set_model_value(code_number); - } + get_value() { + return this.value; } change_flag(country_code) { @@ -162,11 +163,11 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD } change_padding() { let len = this.$isd.text().length; - let diff = len - 3; - if (len > 3) { - this.$input.css("padding-left", 67 + (diff * 7)); - } else { - this.$input.css("padding-left", 67); - } + let diff = len - 2; + if (len > 2) { + this.$input.css("padding-left", 60 + (diff * 7)); + } else { + this.$input.css("padding-left", 60); } + } }; From 2864c4322e348c68d264a4389862b2324144b455 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 28 Mar 2022 13:13:04 +0530 Subject: [PATCH 21/30] style: alignment of isd code for grid and dialog --- frappe/public/js/frappe/form/controls/phone.js | 3 ++- frappe/public/scss/common/phone_picker.scss | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 98de47ff41..92abd6e20c 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -96,7 +96,8 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD refresh() { super.refresh(); // Previously opened doc values showing up on a new doc - if (this.frm.doc.__islocal && !this.get_value()) { + + if (this.frm && this.frm.doc.__islocal && !this.get_value()) { this.$input.val(""); this.$wrapper.find('.country').text(""); if (this.selected_icon.find('svg').hasClass('hide')) { diff --git a/frappe/public/scss/common/phone_picker.scss b/frappe/public/scss/common/phone_picker.scss index 7c9bbcf7e5..f2131e8b84 100644 --- a/frappe/public/scss/common/phone_picker.scss +++ b/frappe/public/scss/common/phone_picker.scss @@ -73,12 +73,11 @@ .frappe-control[data-fieldtype='Phone'] { input { - padding-left: 67px; + padding-left: 30px; } .selected-phone { display: flex; cursor: pointer; - width: 66px; height: 20px; border-radius: 5px; position: absolute; @@ -86,6 +85,7 @@ left: 8px; content: ' '; align-items: center; + z-index: 1; .country { display: flex; @@ -113,18 +113,15 @@ .modal-body { .frappe-control[data-fieldtype='Phone'] { - input { - padding-left: 67px; - } .selected-phone { - top: calc(50%); + top: calc(50% - 0.5px); } } } .data-row.row { .selected-phone { - top: calc(50% - 11px); + top: calc(50% - 10.1px); z-index: 2; } } @@ -142,6 +139,6 @@ .dt-cell__edit, .filter-field { .selected-phone { - top: 5px !important; + top: 5.5px !important; } } From 949646222f8a1314ff10b2180353646620eea3bf Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 30 Mar 2022 14:19:39 +0530 Subject: [PATCH 22/30] test(ui): potential fix for failing test --- cypress/integration/control_phone.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 8d1e7c1d05..0d0812b983 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -30,9 +30,10 @@ context('Control Phone', () => { cy.get('.frappe-control[data-fieldname=phone]') .findByRole('textbox') .first() - .type(phone_number); + .type(phone_number, {force: true}); cy.get_field("phone").first().should('have.value', phone_number); + cy.wait(1000) cy.get_field("phone").first().blur({force: true}); cy.get('@dialog').then(dialog => { From 544d248323576f56b8d70073e132f9831af6e835 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 30 Mar 2022 18:34:25 +0530 Subject: [PATCH 23/30] test(ui): increasing coverage in tests --- cypress/fixtures/doctype_with_phone.js | 47 +++++++++++ cypress/integration/control_phone.js | 110 ++++++++++++++++++------- cypress/support/commands.js | 4 + 3 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 cypress/fixtures/doctype_with_phone.js diff --git a/cypress/fixtures/doctype_with_phone.js b/cypress/fixtures/doctype_with_phone.js new file mode 100644 index 0000000000..c62922ade2 --- /dev/null +++ b/cypress/fixtures/doctype_with_phone.js @@ -0,0 +1,47 @@ +export default { + name: "Doctype With Phone", + actions: [], + custom: 1, + is_submittable: 1, + autoname: "field:title", + creation: '2022-03-30 06:29:07.215072', + doctype: 'DocType', + engine: 'InnoDB', + fields: [ + + { + fieldname: 'title', + fieldtype: 'Data', + label: 'title', + unique: 1, + }, + { + fieldname: 'phone', + fieldtype: 'Phone', + label: 'Phone' + } + ], + links: [], + modified: '2019-03-30 14:40:53.127615', + modified_by: 'Administrator', + naming_rule: "By fieldname", + module: 'Custom', + owner: 'Administrator', + permissions: [ + { + create: 1, + delete: 1, + email: 1, + print: 1, + read: 1, + role: 'System Manager', + share: 1, + write: 1, + submit: 1, + cancel: 1 + } + ], + sort_field: 'modified', + sort_order: 'ASC', + track_changes: 1 +}; diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 0d0812b983..2e6d30daf8 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -1,59 +1,113 @@ -context('Control Phone', () => { +import doctype_with_phone from '../fixtures/doctype_with_phone'; + +context("Control Phone", () => { before(() => { cy.login(); - cy.visit('/app/website'); + cy.visit("/app/website"); }); function get_dialog_with_phone() { return cy.dialog({ - title: 'Phone', + title: "Phone", fields: [{ - 'fieldname': 'phone', - 'fieldtype': 'Phone', + "fieldname": "phone", + "fieldtype": "Phone", }] }); } - it('should set flag and data', () => { - get_dialog_with_phone().as('dialog'); - cy.get('.selected-phone > svg').click(); - cy.get('.phone-picker .phone-wrapper[id="afghanistan"]').click(); - cy.get('.phone-picker .phone-wrapper[id="india"]').click(); - cy.get('.selected-phone .country').should('have.text', '+91'); - cy.get('.selected-phone > img').should('have.attr', 'src').and('include', '/in.svg'); + it("should set flag and data", () => { + get_dialog_with_phone().as("dialog"); + cy.get(".selected-phone > svg").click(); + cy.get(".phone-picker .phone-wrapper[id='afghanistan']").click(); + cy.get(".phone-picker .phone-wrapper[id='india']").click(); + cy.get(".selected-phone .country").should("have.text", "+91"); + cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); - let phone_number = '9312672712'; - cy.get('.selected-phone > img').click().first(); + let phone_number = "9312672712"; + cy.get(".selected-phone > img").click().first(); cy.get_field("phone") .first() .click({multiple: true}); - cy.get('.frappe-control[data-fieldname=phone]') - .findByRole('textbox') + cy.get(".frappe-control[data-fieldname=phone]") + .findByRole("textbox") .first() .type(phone_number, {force: true}); - cy.get_field("phone").first().should('have.value', phone_number); - cy.wait(1000) + cy.get_field("phone").first().should("have.value", phone_number); + cy.wait(1000); cy.get_field("phone").first().blur({force: true}); - cy.get('@dialog').then(dialog => { + cy.get("@dialog").then(dialog => { let value = dialog.get_value("phone"); - expect(value).to.equal('+91-' + phone_number); + expect(value).to.equal("+91-" + phone_number); }); }); - it('case insensitive search for country and clear search', () => { - let search_text = 'india'; - cy.get('.selected-phone > img').click().first(); - cy.get('.phone-picker').findByRole('searchbox').click().type(search_text); - cy.get('.phone-section .phone-wrapper:not(.hidden)').then(i => { - cy.get(`.phone-section .phone-wrapper[id*='${search_text.toLowerCase()}']`).then(countries => { + it("case insensitive search for country and clear search", () => { + let search_text = "india"; + cy.get(".selected-phone > img").click().first(); + cy.get(".phone-picker").findByRole("searchbox").click().type(search_text); + cy.get(".phone-section .phone-wrapper:not(.hidden)").then(i => { + cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(countries => { expect(i.length).to.equal(countries.length); }); }); - cy.get('.phone-picker').findByRole('searchbox').clear().blur(); - cy.get('.phone-section .phone-wrapper').should('not.have.class', 'hidden'); + cy.get(".phone-picker").findByRole("searchbox").clear().blur(); + cy.get(".phone-section .phone-wrapper").should("not.have.class", "hidden"); }); + it("Already existing docs with phone field", () => { + cy.visit("/app/doctype"); + cy.insert_doc("DocType", doctype_with_phone, true); + cy.clear_cache(); + + // Creating custom doctype + cy.insert_doc("DocType", doctype_with_phone, true); + cy.visit("/app/doctype-with-phone"); + cy.click_listview_primary_button("Add Doctype With Phone"); + + //Adding a new entry for the created custom doctype + cy.fill_field("title", "Test Phone 1"); + cy.fill_field("phone", "+91-9823341234"); + cy.wait(500); + cy.get_field("phone").should("have.value", "9823341234"); + cy.click_doc_primary_button("Save"); + cy.wait(500); + cy.get_doc("Doctype With Phone", "Test Phone 1").then((doc) => { + let value = doc.data.phone; + expect(value).to.equal("+91-9823341234"); + }); + cy.go_to_list("Doctype With Phone"); + cy.click_listview_primary_button("Add Doctype With Phone"); + // Field should be empty on new doc + cy.get_field("phone").should("have.value", ""); + cy.get(".selected-phone .country").should("have.text", ""); + cy.fill_field("title", "Test Phone 2"); + cy.fill_field("phone", "+91-9823341291"); + cy.wait(500); + cy.get_field("phone").should("have.value", "9823341291"); + cy.click_doc_primary_button("Save"); + cy.wait(500); + cy.go_to_list("Doctype With Phone"); + cy.clear_cache(); + cy.click_listview_row_item(0); + cy.title().should("eq", "Test Phone 2"); + cy.get(".selected-phone .country").should("have.text", "+91"); + cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); + cy.get_field("phone").should("have.value", "9823341291"); + cy.go_to_list("Doctype With Phone"); + cy.click_listview_row_item(1); + cy.title().should("eq", "Test Phone 1"); + cy.get(".selected-phone .country").should("have.text", "+91"); + cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); + cy.get_field("phone").should("have.value", "9823341234"); + cy.get_doc("Doctype With Phone", "Test Phone 2").then((doc) => { + let value = doc.data.phone; + expect(value).to.equal("+91-9823341291"); + cy.remove_doc("Doctype With Phone", "Test Phone 1", true); + cy.remove_doc("Doctype With Phone", "Test Phone 2", true); + }); + }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 4f273af21f..7d9932198d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -325,6 +325,10 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => { cy.get('.primary-action').contains(btn_name).click({force: true}); }); +Cypress.Commands.add('click_doc_primary_button', (btn_name) => { + cy.get('.primary-action').contains(btn_name).click({force: true}); +}); + Cypress.Commands.add('click_timeline_action_btn', (btn_name) => { cy.get('.timeline-message-box .actions .action-btn').contains(btn_name).click(); }); From 7d49693a339efb4c03b6e35199efcab5e8d619d4 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 7 Apr 2022 15:04:26 +0530 Subject: [PATCH 24/30] refactor: updated error messages for invalid phone numbers --- frappe/utils/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 03877518d3..44debde273 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -80,15 +80,15 @@ def validate_phone_number_with_isd(phone, throw=False): if not phone: return try: - phone = ph.parse(phone) + phone_number = ph.parse(phone) except Exception as e: if e.error_type == 1: - frappe.throw(frappe._("The entered value is not a phone number."), frappe.InvalidPhoneNumberError, + frappe.throw(frappe._("{0} is not a valid Phone Number.").format(frappe.bold(phone)), frappe.InvalidPhoneNumberError, title=frappe._("Invalid Number")) - frappe.throw(frappe._("Please select a country code."), frappe.InvalidPhoneNumberError, + frappe.throw(frappe._("Please select a country code for the Phone Number {0}.").format(frappe.bold(phone)), frappe.InvalidPhoneNumberError, title = frappe._("Country Code Required")) - if not ph.is_valid_number(phone): - frappe.throw(frappe._("This is not a valid phone number"), frappe.InvalidPhoneNumberError, + if not ph.is_valid_number(phone_number): + frappe.throw(frappe._("{0} is not a valid Phone Number").format(frappe.bold(phone)), frappe.InvalidPhoneNumberError, title = frappe._("Invalid Number")) def validate_phone_number(phone_number, throw=False): From 2d3c1053a0fce13d05aace48a596fa9124478837 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 11 Apr 2022 15:25:27 +0530 Subject: [PATCH 25/30] refactor(ui): updated validation messages with fieldname --- frappe/model/base_document.py | 2 +- frappe/utils/__init__.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index ada5119995..b90dbed9ac 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -726,7 +726,7 @@ class BaseDocument(object): # data_field options defined in frappe.model.data_field_options for phone_field in self.meta.get_phone_fields(): phone = self.get(phone_field.fieldname) - frappe.utils.validate_phone_number_with_isd(phone, throw=True) + frappe.utils.validate_phone_number_with_isd(phone, phone_field.fieldname, throw=True) for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index e5a8ac45fb..c1c19a0bd4 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -76,20 +76,22 @@ def extract_email_id(email): email_id = email_id.decode("utf-8", "ignore") return email_id -def validate_phone_number_with_isd(phone, throw=False): +def validate_phone_number_with_isd(phone, fieldname, throw=False): + from frappe import _ if not phone: return try: phone_number = ph.parse(phone) except Exception as e: if e.error_type == 1: - frappe.throw(frappe._("{0} is not a valid Phone Number.").format(frappe.bold(phone)), frappe.InvalidPhoneNumberError, - title=frappe._("Invalid Number")) - frappe.throw(frappe._("Please select a country code for the Phone Number {0}.").format(frappe.bold(phone)), frappe.InvalidPhoneNumberError, - title = frappe._("Country Code Required")) + + frappe.throw(_("Phone Number {0} set in field {1} is not valid.").format(frappe.bold(phone), frappe.bold(fieldname)), frappe.InvalidPhoneNumberError, + title=_("Invalid Phone Number")) + frappe.throw(_("Please select a country code for field {1}.").format(frappe.bold(phone), frappe.bold(fieldname)), frappe.InvalidPhoneNumberError, + title = _("Country Code Required")) if not ph.is_valid_number(phone_number): - frappe.throw(frappe._("{0} is not a valid Phone Number").format(frappe.bold(phone)), frappe.InvalidPhoneNumberError, - title = frappe._("Invalid Number")) + frappe.throw(_("Phone Number {0} set in field {1} is not valid.").format(frappe.bold(phone), frappe.bold(fieldname)), frappe.InvalidPhoneNumberError, + title = _("Invalid Phone Number")) def validate_phone_number(phone_number, throw=False): """Returns True if valid phone number""" From 36ca84c9ff1a010abc6816688826e322c2b372f2 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 12 Apr 2022 21:06:19 +0530 Subject: [PATCH 26/30] test: suggested changes --- cypress/integration/control_phone.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 2e6d30daf8..1a12ddf2b4 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -88,8 +88,9 @@ context("Control Phone", () => { cy.fill_field("phone", "+91-9823341291"); cy.wait(500); cy.get_field("phone").should("have.value", "9823341291"); + cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form"); cy.click_doc_primary_button("Save"); - cy.wait(500); + cy.wait("@save_form"); cy.go_to_list("Doctype With Phone"); cy.clear_cache(); cy.click_listview_row_item(0); From 1637e3af087acf7292e7058952c735313194aebd Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 12 Apr 2022 21:06:59 +0530 Subject: [PATCH 27/30] fix: linting --- frappe/boot.py | 3 ++- frappe/utils/__init__.py | 32 +++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 59981b19cf..62122ed4e5 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -11,8 +11,8 @@ from frappe.core.doctype.navbar_settings.navbar_settings import get_app_logo, ge from frappe.desk.doctype.route_history.route_history import frequently_visited_links from frappe.desk.form.load import get_meta_bundle from frappe.email.inbox import get_email_accounts -from frappe.model.base_document import get_controller from frappe.geo.country_info import get_all +from frappe.model.base_document import get_controller from frappe.query_builder import DocType from frappe.query_builder.functions import Count from frappe.query_builder.terms import subqry @@ -390,6 +390,7 @@ def get_country_codes(bootinfo): country_codes = get_all() bootinfo.country_codes = frappe._dict(country_codes) + @frappe.whitelist() def get_link_title_doctypes(): dts = frappe.get_all("DocType", {"show_title_field_in_link": 1}) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 010171f7a1..e651af3ff5 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -17,8 +17,8 @@ from gzip import GzipFile from typing import Generator, Iterable from urllib.parse import quote, urlparse -from redis.exceptions import ConnectionError import phonenumbers as ph +from redis.exceptions import ConnectionError from werkzeug.test import Client import frappe @@ -82,8 +82,10 @@ def extract_email_id(email): email_id = email_id.decode("utf-8", "ignore") return email_id + def validate_phone_number_with_isd(phone, fieldname, throw=False): from frappe import _ + if not phone: return try: @@ -91,13 +93,29 @@ def validate_phone_number_with_isd(phone, fieldname, throw=False): except Exception as e: if e.error_type == 1: - frappe.throw(_("Phone Number {0} set in field {1} is not valid.").format(frappe.bold(phone), frappe.bold(fieldname)), frappe.InvalidPhoneNumberError, - title=_("Invalid Phone Number")) - frappe.throw(_("Please select a country code for field {1}.").format(frappe.bold(phone), frappe.bold(fieldname)), frappe.InvalidPhoneNumberError, - title = _("Country Code Required")) + frappe.throw( + _("Phone Number {0} set in field {1} is not valid.").format( + frappe.bold(phone), frappe.bold(fieldname) + ), + frappe.InvalidPhoneNumberError, + title=_("Invalid Phone Number"), + ) + frappe.throw( + _("Please select a country code for field {1}.").format( + frappe.bold(phone), frappe.bold(fieldname) + ), + frappe.InvalidPhoneNumberError, + title=_("Country Code Required"), + ) if not ph.is_valid_number(phone_number): - frappe.throw(_("Phone Number {0} set in field {1} is not valid.").format(frappe.bold(phone), frappe.bold(fieldname)), frappe.InvalidPhoneNumberError, - title = _("Invalid Phone Number")) + frappe.throw( + _("Phone Number {0} set in field {1} is not valid.").format( + frappe.bold(phone), frappe.bold(fieldname) + ), + frappe.InvalidPhoneNumberError, + title=_("Invalid Phone Number"), + ) + def validate_phone_number(phone_number, throw=False): """Returns True if valid phone number""" From 48cd1c2ad4e8aa1cbef320b0dca70f60673b0ed3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 14 Apr 2022 14:14:00 +0530 Subject: [PATCH 28/30] refactor: Fix naming and simpify code - Ability to clear code, by hiting backspace --- frappe/model/base_document.py | 2 +- .../public/js/frappe/form/controls/phone.js | 67 +++++++++++++------ .../js/frappe/phone_picker/phone_picker.js | 15 ++++- frappe/utils/__init__.py | 46 ++++++------- 4 files changed, 77 insertions(+), 53 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 71e829f61a..784c398030 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -769,7 +769,7 @@ class BaseDocument(object): # data_field options defined in frappe.model.data_field_options for phone_field in self.meta.get_phone_fields(): phone = self.get(phone_field.fieldname) - frappe.utils.validate_phone_number_with_isd(phone, phone_field.fieldname, throw=True) + frappe.utils.validate_phone_number_with_country_code(phone, phone_field.fieldname) for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) diff --git a/frappe/public/js/frappe/form/controls/phone.js b/frappe/public/js/frappe/form/controls/phone.js index 92abd6e20c..d67b449ac8 100644 --- a/frappe/public/js/frappe/form/controls/phone.js +++ b/frappe/public/js/frappe/form/controls/phone.js @@ -5,16 +5,28 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD make_input() { super.make_input(); - this.make_icon_input(); + this.setup_country_code_picker(); this.input_events(); } input_events() { + this.$input.keydown((e) => { + const key_code = e.keyCode; + if ([frappe.ui.keyCode.BACKSPACE].includes(key_code)) { + if (this.$input.val().length == 0) { + this.country_code_picker.reset(); + } + } + }); + // Replaces code when selected and removes previously selected. - this.picker.on_change = (country) => { + this.country_code_picker.on_change = (country) => { + if (!country) { + return this.reset_inputx(); + } const country_code = frappe.boot.country_codes[country].code; const country_isd = frappe.boot.country_codes[country].isd; - this.change_flag(country_code); + this.set_flag(country_code); this.$icon = this.selected_icon.find('svg'); this.$flag = this.selected_icon.find('img'); @@ -32,7 +44,10 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD if (this.$input.val()) { this.set_value(this.get_country(country) +'-'+ this.$input.val()); } - this.change_padding(); + this.update_padding(); + // hide popover and focus input + this.$wrapper.popover('hide'); + this.$input.focus(); }; this.$wrapper.find('.selected-phone').on('click', (e) => { @@ -50,9 +65,9 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD }); } - make_icon_input() { + setup_country_code_picker() { let picker_wrapper = $('
'); - this.picker = new PhonePicker({ + this.country_code_picker = new PhonePicker({ parent: picker_wrapper, countries: frappe.boot.country_codes }); @@ -72,7 +87,8 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD html: true }).on('show.bs.popover', () => { setTimeout(() => { - this.picker.refresh(); + this.country_code_picker.refresh(); + this.country_code_picker.search_input.focus(); }, 10); }).on('hidden.bs.popover', () => { $('body').off('click.phone-popover'); @@ -98,26 +114,30 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD // Previously opened doc values showing up on a new doc if (this.frm && this.frm.doc.__islocal && !this.get_value()) { - this.$input.val(""); - this.$wrapper.find('.country').text(""); - if (this.selected_icon.find('svg').hasClass('hide')) { - this.selected_icon.find('svg').toggleClass('hide'); - this.selected_icon.find('img').addClass('hide'); - } - this.$input.css("padding-left", 30); + this.reset_input(); } } + reset_input() { + this.$input.val(""); + this.$wrapper.find('.country').text(""); + if (this.selected_icon.find('svg').hasClass('hide')) { + this.selected_icon.find('svg').toggleClass('hide'); + this.selected_icon.find('img').addClass('hide'); + } + this.$input.css("padding-left", 30); + } + set_formatted_input(value) { if (value && value.includes('-') && value.split('-').length == 2) { let isd = this.value.split("-")[0]; this.get_country_code_and_change_flag(isd); - this.picker.set_country(isd); - this.picker.refresh(); - if (this.picker.country && this.picker.country !== this.$isd.text()) { + this.country_code_picker.set_country(isd); + this.country_code_picker.refresh(); + if (this.country_code_picker.country && this.country_code_picker.country !== this.$isd.text()) { this.$isd.length && this.$isd.text(isd); } - this.change_padding(); + this.update_padding(); this.$input.val(value.split('-').pop()); } else if (this.$isd.text().trim() && this.value) { @@ -130,8 +150,8 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD return this.value; } - change_flag(country_code) { - this.selected_icon.find('img').attr('src', 'https://flagcdn.com/'+country_code+'.svg'); + set_flag(country_code) { + this.selected_icon.find('img').attr('src', `https://flagcdn.com/${country_code}.svg`); this.$icon = this.selected_icon.find('img'); this.$icon.hasClass('hide') && this.$icon.toggleClass('hide'); } @@ -148,21 +168,24 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD this.selected_icon.prepend(this.get_country_flag(country)); this.selected_icon.find('svg').addClass('hide'); } else { - this.change_flag(code); + this.set_flag(code); } } } } + get_country(country) { const country_codes = frappe.boot.country_codes; return country_codes[country].isd; } + get_country_flag(country) { const country_codes = frappe.boot.country_codes; let code = country_codes[country].code; return frappe.utils.flag(code); } - change_padding() { + + update_padding() { let len = this.$isd.text().length; let diff = len - 2; if (len > 2) { diff --git a/frappe/public/js/frappe/phone_picker/phone_picker.js b/frappe/public/js/frappe/phone_picker/phone_picker.js index c5c1437c12..e96d34b991 100644 --- a/frappe/public/js/frappe/phone_picker/phone_picker.js +++ b/frappe/public/js/frappe/phone_picker/phone_picker.js @@ -17,7 +17,7 @@ class PhonePicker { this.phone_picker_wrapper = $(`
- + ${frappe.utils.icon('search', "sm")}
@@ -37,8 +37,12 @@ class PhonePicker { if (!info.isd) { return; } - let $country = $(`
${frappe.utils.flag(info.code)} - ${country} (${info.isd})
`); + let $country = $(` +
+ ${frappe.utils.flag(info.code)} + ${country} (${info.isd}) +
+ `); this.phone_wrapper.append($country); const set_values = () => { this.set_country(country); @@ -89,6 +93,11 @@ class PhonePicker { get_country() { return this.country; } + + reset() { + this.set_country(); + this.update_icon_selected(); + } } export default PhonePicker; diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index e651af3ff5..1ed46ecb64 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -17,7 +17,6 @@ from gzip import GzipFile from typing import Generator, Iterable from urllib.parse import quote, urlparse -import phonenumbers as ph from redis.exceptions import ConnectionError from werkzeug.test import Client @@ -83,37 +82,30 @@ def extract_email_id(email): return email_id -def validate_phone_number_with_isd(phone, fieldname, throw=False): +def validate_phone_number_with_country_code(phone_number, fieldname): + from phonenumbers import NumberParseException, is_valid_number, parse + from frappe import _ - if not phone: + if not phone_number: return try: - phone_number = ph.parse(phone) - except Exception as e: - if e.error_type == 1: - - frappe.throw( - _("Phone Number {0} set in field {1} is not valid.").format( - frappe.bold(phone), frappe.bold(fieldname) - ), - frappe.InvalidPhoneNumberError, - title=_("Invalid Phone Number"), - ) + if is_valid_number(parse(phone_number)): + return True + error_message = _("Phone Number {0} set in field {1} is not valid.") + error_title = _("Invalid Phone Number") + except NumberParseException as e: + if e.error_type == NumberParseException.INVALID_COUNTRY_CODE: + error_message = _("Please select a country code for field {1}.") + error_title = _("Country Code Required") + if e.error_type == NumberParseException.NOT_A_NUMBER: + error_message = _("Phone Number {0} set in field {1} is not valid.") + error_title = _("Invalid Phone Number") + finally: frappe.throw( - _("Please select a country code for field {1}.").format( - frappe.bold(phone), frappe.bold(fieldname) - ), - frappe.InvalidPhoneNumberError, - title=_("Country Code Required"), - ) - if not ph.is_valid_number(phone_number): - frappe.throw( - _("Phone Number {0} set in field {1} is not valid.").format( - frappe.bold(phone), frappe.bold(fieldname) - ), - frappe.InvalidPhoneNumberError, - title=_("Invalid Phone Number"), + error_message.format(frappe.bold(phone_number), frappe.bold(fieldname)), + title=error_title, + exc=frappe.InvalidPhoneNumberError, ) From a396afc7d471b117aca6c01d9e8c1439155c00c0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 14 Apr 2022 17:05:07 +0530 Subject: [PATCH 29/30] fix: Validation error --- cypress/integration/control_phone.js | 3 --- frappe/utils/__init__.py | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 1a12ddf2b4..03f01f5630 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -71,10 +71,8 @@ context("Control Phone", () => { //Adding a new entry for the created custom doctype cy.fill_field("title", "Test Phone 1"); cy.fill_field("phone", "+91-9823341234"); - cy.wait(500); cy.get_field("phone").should("have.value", "9823341234"); cy.click_doc_primary_button("Save"); - cy.wait(500); cy.get_doc("Doctype With Phone", "Test Phone 1").then((doc) => { let value = doc.data.phone; expect(value).to.equal("+91-9823341234"); @@ -86,7 +84,6 @@ context("Control Phone", () => { cy.get(".selected-phone .country").should("have.text", ""); cy.fill_field("title", "Test Phone 2"); cy.fill_field("phone", "+91-9823341291"); - cy.wait(500); cy.get_field("phone").should("have.value", "9823341291"); cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form"); cy.click_doc_primary_button("Save"); diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 1ed46ecb64..7907dc8cd4 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -89,24 +89,24 @@ def validate_phone_number_with_country_code(phone_number, fieldname): if not phone_number: return + + valid_number = False + error_message = _("Phone Number {0} set in field {1} is not valid.") + error_title = _("Invalid Phone Number") try: - if is_valid_number(parse(phone_number)): + if valid_number := is_valid_number(parse(phone_number)): return True - error_message = _("Phone Number {0} set in field {1} is not valid.") - error_title = _("Invalid Phone Number") except NumberParseException as e: if e.error_type == NumberParseException.INVALID_COUNTRY_CODE: error_message = _("Please select a country code for field {1}.") error_title = _("Country Code Required") - if e.error_type == NumberParseException.NOT_A_NUMBER: - error_message = _("Phone Number {0} set in field {1} is not valid.") - error_title = _("Invalid Phone Number") finally: - frappe.throw( - error_message.format(frappe.bold(phone_number), frappe.bold(fieldname)), - title=error_title, - exc=frappe.InvalidPhoneNumberError, - ) + if not valid_number: + frappe.throw( + error_message.format(frappe.bold(phone_number), frappe.bold(fieldname)), + title=error_title, + exc=frappe.InvalidPhoneNumberError, + ) def validate_phone_number(phone_number, throw=False): From a0e9b61e04770c5d1be782af18ac8157bd5ca225 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 14 Apr 2022 18:14:55 +0530 Subject: [PATCH 30/30] test: Simplify phone control test --- cypress/integration/control_phone.js | 37 ++++++---------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index 03f01f5630..5a26decdee 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -18,8 +18,9 @@ context("Control Phone", () => { it("should set flag and data", () => { get_dialog_with_phone().as("dialog"); - cy.get(".selected-phone > svg").click(); + cy.get(".selected-phone").click(); cy.get(".phone-picker .phone-wrapper[id='afghanistan']").click(); + cy.get(".selected-phone").click(); cy.get(".phone-picker .phone-wrapper[id='india']").click(); cy.get(".selected-phone .country").should("have.text", "+91"); cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); @@ -35,9 +36,8 @@ context("Control Phone", () => { .type(phone_number, {force: true}); cy.get_field("phone").first().should("have.value", phone_number); - cy.wait(1000); cy.get_field("phone").first().blur({force: true}); - + cy.wait(100); cy.get("@dialog").then(dialog => { let value = dialog.get_value("phone"); expect(value).to.equal("+91-" + phone_number); @@ -46,7 +46,7 @@ context("Control Phone", () => { it("case insensitive search for country and clear search", () => { let search_text = "india"; - cy.get(".selected-phone > img").click().first(); + cy.get(".selected-phone").click().first(); cy.get(".phone-picker").findByRole("searchbox").click().type(search_text); cy.get(".phone-section .phone-wrapper:not(.hidden)").then(i => { cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(countries => { @@ -58,7 +58,7 @@ context("Control Phone", () => { cy.get(".phone-section .phone-wrapper").should("not.have.class", "hidden"); }); - it("Already existing docs with phone field", () => { + it("existing document should render phone field with data", () => { cy.visit("/app/doctype"); cy.insert_doc("DocType", doctype_with_phone, true); cy.clear_cache(); @@ -68,7 +68,7 @@ context("Control Phone", () => { cy.visit("/app/doctype-with-phone"); cy.click_listview_primary_button("Add Doctype With Phone"); - //Adding a new entry for the created custom doctype + // create a record cy.fill_field("title", "Test Phone 1"); cy.fill_field("phone", "+91-9823341234"); cy.get_field("phone").should("have.value", "9823341234"); @@ -77,35 +77,14 @@ context("Control Phone", () => { let value = doc.data.phone; expect(value).to.equal("+91-9823341234"); }); - cy.go_to_list("Doctype With Phone"); - cy.click_listview_primary_button("Add Doctype With Phone"); - // Field should be empty on new doc - cy.get_field("phone").should("have.value", ""); - cy.get(".selected-phone .country").should("have.text", ""); - cy.fill_field("title", "Test Phone 2"); - cy.fill_field("phone", "+91-9823341291"); - cy.get_field("phone").should("have.value", "9823341291"); - cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form"); - cy.click_doc_primary_button("Save"); - cy.wait("@save_form"); + + // open the doc from list view cy.go_to_list("Doctype With Phone"); cy.clear_cache(); cy.click_listview_row_item(0); - cy.title().should("eq", "Test Phone 2"); - cy.get(".selected-phone .country").should("have.text", "+91"); - cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); - cy.get_field("phone").should("have.value", "9823341291"); - cy.go_to_list("Doctype With Phone"); - cy.click_listview_row_item(1); cy.title().should("eq", "Test Phone 1"); cy.get(".selected-phone .country").should("have.text", "+91"); cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); cy.get_field("phone").should("have.value", "9823341234"); - cy.get_doc("Doctype With Phone", "Test Phone 2").then((doc) => { - let value = doc.data.phone; - expect(value).to.equal("+91-9823341291"); - cy.remove_doc("Doctype With Phone", "Test Phone 1", true); - cy.remove_doc("Doctype With Phone", "Test Phone 2", true); - }); }); });