* [enhance] web form pagination frappe/shishuvan#3 * [minor] add label in web form grid * [minor] select module for exporting web form * [fix] more fixes, #2057version-14
@@ -1,2 +1,5 @@ | |||||
- Search for help from within the app. Click on "Help" | - Search for help from within the app. Click on "Help" | ||||
- Send a popup to all users on login for a new Note by checking on "Notify users with a popup when they log in" | |||||
- Send a popup to all users on login for a new Note by checking on "Notify users with a popup when they log in" | |||||
- Updates to Web Form | |||||
- Add grids (child tables) | |||||
- Add page breaks (for long forms) |
@@ -200,6 +200,13 @@ def export_fixtures(context): | |||||
def import_doc(context, path, force=False): | def import_doc(context, path, force=False): | ||||
"Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported" | "Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported" | ||||
from frappe.core.page.data_import_tool import data_import_tool | from frappe.core.page.data_import_tool import data_import_tool | ||||
if not os.path.exists(path): | |||||
path = os.path.join('..', path) | |||||
if not os.path.exists(path): | |||||
print 'Invalid path {0}'.format(path) | |||||
sys.exit(1) | |||||
for site in context.sites: | for site in context.sites: | ||||
try: | try: | ||||
frappe.init(site=site) | frappe.init(site=site) | ||||
@@ -222,6 +229,12 @@ def import_csv(context, path, only_insert=False, submit_after_import=False, igno | |||||
from frappe.utils.csvutils import read_csv_content | from frappe.utils.csvutils import read_csv_content | ||||
site = get_site(context) | site = get_site(context) | ||||
if not os.path.exists(path): | |||||
path = os.path.join('..', path) | |||||
if not os.path.exists(path): | |||||
print 'Invalid path {0}'.format(path) | |||||
sys.exit(1) | |||||
with open(path, 'r') as csvfile: | with open(path, 'r') as csvfile: | ||||
content = read_csv_content(csvfile.read()) | content = read_csv_content(csvfile.read()) | ||||
@@ -440,6 +440,7 @@ select.form-control { | |||||
} | } | ||||
.form-headline { | .form-headline { | ||||
padding: 0px 15px; | padding: 0px 15px; | ||||
margin: 0px; | |||||
} | } | ||||
.form-headline .alert { | .form-headline .alert { | ||||
font-size: 12px; | font-size: 12px; | ||||
@@ -153,10 +153,9 @@ frappe.ui.to_do_dialog = function(opts){ | |||||
var dialog = new frappe.ui.Dialog({ | var dialog = new frappe.ui.Dialog({ | ||||
title: __('Add to To Do'), | title: __('Add to To Do'), | ||||
fields: [ | fields: [ | ||||
{fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0}, | |||||
{fieldtype: 'Section Break'}, | |||||
{fieldtype: 'Link', fieldname:'assign_to', options:'User', | {fieldtype: 'Link', fieldname:'assign_to', options:'User', | ||||
label:__("Assign To"), reqd:true, filters: {'user_type': 'System User'}}, | label:__("Assign To"), reqd:true, filters: {'user_type': 'System User'}}, | ||||
{fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0}, | |||||
{fieldtype:'Small Text', fieldname:'description', label:__("Comment"), reqd:true}, | {fieldtype:'Small Text', fieldname:'description', label:__("Comment"), reqd:true}, | ||||
{fieldtype: 'Section Break'}, | {fieldtype: 'Section Break'}, | ||||
{fieldtype: 'Column Break'}, | {fieldtype: 'Column Break'}, | ||||
@@ -7,11 +7,13 @@ | |||||
<div class="small form-clickable-section grid-footer"> | <div class="small form-clickable-section grid-footer"> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="col-sm-6 grid-buttons"> | <div class="col-sm-6 grid-buttons"> | ||||
<a href="#" class="grid-add-multiple-rows btn btn-xs btn-default hide" | |||||
<button type="reset" | |||||
class="grid-add-multiple-rows btn btn-xs btn-default hide" | |||||
style="margin-right: 10px;"> | style="margin-right: 10px;"> | ||||
{%= __("Add multiple rows") %}</a> | {%= __("Add multiple rows") %}</a> | ||||
<a href="#" class="btn btn-xs btn-default grid-add-row"> | |||||
{%= __("Add new row") %}</a> | |||||
<!-- hack to allow firefox include this in tabs --> | |||||
<button type="reset" class="btn btn-xs btn-default grid-add-row"> | |||||
{%= __("Add new row") %}</button> | |||||
</div> | </div> | ||||
<div class="col-sm-6 text-right"> | <div class="col-sm-6 text-right"> | ||||
<a href="#" class="grid-download btn btn-xs btn-default hide" | <a href="#" class="grid-download btn btn-xs btn-default hide" | ||||
@@ -555,6 +555,7 @@ select.form-control { | |||||
.form-headline { | .form-headline { | ||||
padding: 0px 15px; | padding: 0px 15px; | ||||
margin: 0px; | |||||
} | } | ||||
.form-headline .alert { | .form-headline .alert { | ||||
@@ -15,7 +15,7 @@ | |||||
<a href="{{ cancel_url or pathname }}" class="btn btn-default btn-sm"> | <a href="{{ cancel_url or pathname }}" class="btn btn-default btn-sm"> | ||||
{{ _("Cancel") }}</a> | {{ _("Cancel") }}</a> | ||||
<button type="submit" class="btn btn-primary btn-sm btn-form-submit"> | <button type="submit" class="btn btn-primary btn-sm btn-form-submit"> | ||||
{{ _("Submit") if frappe.form_dict.new else _("Update") }}</button> | |||||
{{ _("Submit") if frappe.form_dict.new else _("Save") }}</button> | |||||
{% elif is_list %} | {% elif is_list %} | ||||
<div style="padding-bottom: 15px;"> | <div style="padding-bottom: 15px;"> | ||||
<a href="/{{ pathname }}{{ delimeter }}new=1" class="btn btn-primary btn-new btn-sm"> | <a href="/{{ pathname }}{{ delimeter }}new=1" class="btn btn-primary btn-new btn-sm"> | ||||
@@ -93,6 +93,18 @@ | |||||
value="{{ value(field, _doc) }}"> | value="{{ value(field, _doc) }}"> | ||||
{{ help(field) }} | {{ help(field) }} | ||||
</div> | </div> | ||||
{% elif field.fieldtype=="Link" %} | |||||
<div class="form-group"> | |||||
{% if with_label %}{{ label(field) }}{% endif %} | |||||
<select class="form-control" {{ properties(field) }}> | |||||
{% for option in ([{"name":""}] + frappe.get_all(field.options)) -%} | |||||
<option value="{{ option.name }}" | |||||
{{ 'selected="selected"' if value(field, _doc)==option.name else '' }}> | |||||
{{ option.name }}</option> | |||||
{%- endfor %} | |||||
</select> | |||||
{{ help(field) }} | |||||
</div> | |||||
{% elif field.fieldtype=="Select" %} | {% elif field.fieldtype=="Select" %} | ||||
<div class="form-group"> | <div class="form-group"> | ||||
{% if with_label %}{{ label(field) }}{% endif %} | {% if with_label %}{{ label(field) }}{% endif %} | ||||
@@ -136,8 +148,8 @@ | |||||
{% if with_label %} | {% if with_label %} | ||||
<label> | <label> | ||||
<input type="checkbox" id="{{ field.fieldname }}" | <input type="checkbox" id="{{ field.fieldname }}" | ||||
name="{{ field.fieldname }}" | |||||
{{ doc and doc.get(field.fieldname) and 'checked' or '' }}> | |||||
name="{{ field.fieldname }}" data-doctype="{{ field.parent }}" | |||||
{{ _doc and _doc.get(field.fieldname) and 'checked' or '' }}> | |||||
{{ _(field.label) }} | {{ _(field.label) }} | ||||
</label> | </label> | ||||
{% endif %} | {% endif %} | ||||
@@ -152,7 +164,7 @@ | |||||
{% if d != None %}data-name="{{ d.name }}" data-child-row=1{% endif %}> | {% if d != None %}data-name="{{ d.name }}" data-child-row=1{% endif %}> | ||||
{% for df in frappe.get_meta(field.options).fields %} | {% for df in frappe.get_meta(field.options).fields %} | ||||
{% if df.in_list_view %} | {% if df.in_list_view %} | ||||
<{{ 'th' if d==None else 'td' }} style="width: {{ (field.columns or 2) * 8.3333 }}%;"> | |||||
<{{ 'th' if d==None else 'td' }} style="width: {{ (df.columns or 2) * 8.3333 }}%;"> | |||||
{% if d!=None %} | {% if d!=None %} | ||||
{{ render_field(df, d, False) }} | {{ render_field(df, d, False) }} | ||||
{% else %} | {% else %} | ||||
@@ -170,6 +182,7 @@ | |||||
{% endmacro %} | {% endmacro %} | ||||
{% macro render_table(field) %} | {% macro render_table(field) %} | ||||
{{ label(field) }} | |||||
<div class='web-form-grid' | <div class='web-form-grid' | ||||
data-fieldname='{{ field.fieldname }}' data-doctype='{{ field.options }}'> | data-fieldname='{{ field.fieldname }}' data-doctype='{{ field.options }}'> | ||||
<table class='table table-bordered'> | <table class='table table-bordered'> | ||||
@@ -192,6 +205,15 @@ | |||||
data-fieldname='{{ field.fieldname }}'>{{ _("Add Row") }}</button></p> | data-fieldname='{{ field.fieldname }}'>{{ _("Add Row") }}</button></p> | ||||
{% endmacro %} | {% endmacro %} | ||||
{% if layout|len > 1 %} | |||||
<div class="text-center slide-progress text-extra-muted"> | |||||
{% for page in layout %} | |||||
<i data-idx="{{ loop.index }}" class="icon-fixed-width | |||||
{% if loop.index==1 %}icon-circle{% else %}icon-circle-blank{% endif %}"></i> | |||||
{% endfor %} | |||||
</div> | |||||
{% endif %} | |||||
<div class="form-message text-muted hide"></div> | <div class="form-message text-muted hide"></div> | ||||
<form role="form" | <form role="form" | ||||
data-web-form="{{ name }}" data-owner="{{ doc.owner }}"> | data-web-form="{{ name }}" data-owner="{{ doc.owner }}"> | ||||
@@ -210,24 +232,50 @@ | |||||
data-doctype="{{ doc_type }}"> | data-doctype="{{ doc_type }}"> | ||||
{%- endif %} | {%- endif %} | ||||
{% for section in layout %} | |||||
{% if section.label %} | |||||
<h5 class='uppercase'>{{ _(section.label) }}</h5> | |||||
{% endif %} | |||||
<div class="row"> | |||||
{% for column in section.columns %} | |||||
<div class="col-sm-{{ (12 / (section.columns|len))|int }}"> | |||||
{% for field in column %} | |||||
{% if field.fieldtype=='Table' %} | |||||
{{ render_table(field) }} | |||||
{% else %} | |||||
{{ render_field(field, doc) }} | |||||
{% endif %} | |||||
{% endfor %} | |||||
</div> | |||||
{% endfor %} | |||||
{% for page in layout %} | |||||
<div class="web-form-page{% if loop.index > 1 %} hidden{% endif %}" | |||||
data-idx="{{ loop.index }}"> | |||||
<h2>{{ page.label }}</h2> | |||||
{% for section in page.sections %} | |||||
<div class="section"> | |||||
{% if section.label %} | |||||
<h5 class='uppercase'>{{ _(section.label) }}</h5> | |||||
{% endif %} | |||||
<div class="row"> | |||||
{% for column in section.columns %} | |||||
<div class="col-sm-{{ (12 / (section.columns|len))|int }}"> | |||||
{% for field in column %} | |||||
{% if field.fieldtype=='Table' %} | |||||
{{ render_table(field) }} | |||||
{% else %} | |||||
{{ render_field(field, doc) }} | |||||
{% endif %} | |||||
{% endfor %} | |||||
</div> | |||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
{% endfor %} | |||||
<!-- page footer buttons --> | |||||
<div class='text-right'> | |||||
<br> | |||||
{% if loop.index > 1 %} | |||||
<button class='btn btn-sm btn-default btn-change-section' | |||||
data-idx="{{ loop.index - 1 }}"> | |||||
{{ _("Previous") }}</button> | |||||
{% endif %} | |||||
{% if loop.index == layout|len %} | |||||
<button type="submit" class="btn btn-primary btn-sm btn-form-submit"> | |||||
{{ _("Submit") if frappe.form_dict.new else _("Update") }}</button> | |||||
{% elif layout|len > 1 %} | |||||
<button class="btn btn-primary btn-sm btn-change-section" | |||||
data-idx="{{ loop.index + 1 }}"> | |||||
{{ _("Next") }}</button> | |||||
{% endif %} | |||||
</div> | </div> | ||||
</div> | |||||
{% endfor %} | {% endfor %} | ||||
</form> | </form> | ||||
{% if allow_comments and not frappe.form_dict.new -%} | {% if allow_comments and not frappe.form_dict.new -%} | ||||
<div class="comments"> | <div class="comments"> | ||||
@@ -283,6 +331,29 @@ frappe.ready(function() { | |||||
return false; | return false; | ||||
}); | }); | ||||
// change section | |||||
$('.btn-change-section, .slide-progress .icon-fixed-width').on('click', function() { | |||||
var idx = $(this).attr('data-idx'); | |||||
show_slide(idx); | |||||
}); | |||||
show_slide = function(idx) { | |||||
// hide all sections | |||||
$('.web-form-page').addClass('hidden'); | |||||
// slide-progress | |||||
$('.slide-progress .icon-fixed-width.icon-circle') | |||||
.removeClass('icon-circle').addClass('icon-circle-blank'); | |||||
$('.slide-progress .icon-fixed-width[data-idx="'+idx+'"]') | |||||
.removeClass('icon-circle-blank').addClass('icon-circle'); | |||||
// un hide target section | |||||
$('.web-form-page[data-idx="'+idx+'"]') | |||||
.removeClass('hidden') | |||||
.find(':input:first').focus(); | |||||
} | |||||
// add row | // add row | ||||
$('.btn-add-row').on('click', function() { | $('.btn-add-row').on('click', function() { | ||||
var fieldname = $(this).attr('data-fieldname'); | var fieldname = $(this).attr('data-fieldname'); | ||||
@@ -343,7 +414,7 @@ frappe.ready(function() { | |||||
if(input_type==="file") { | if(input_type==="file") { | ||||
var val = $input.get(0).filedata; | var val = $input.get(0).filedata; | ||||
} else if(input_type==="checkbox") { | } else if(input_type==="checkbox") { | ||||
var val = $input.is(":checked") ? 1 : 0; | |||||
var val = $input.prop("checked") ? 1 : 0; | |||||
} else if($input.attr("data-fieldtype")==="Date") { | } else if($input.attr("data-fieldtype")==="Date") { | ||||
var val = $.datepicker.formatDate("yy-mm-dd", | var val = $.datepicker.formatDate("yy-mm-dd", | ||||
$input.datepicker('getDate')); | $input.datepicker('getDate')); | ||||
@@ -496,6 +567,24 @@ input, select { | |||||
.web-form-grid-row .form-group { | .web-form-grid-row .form-group { | ||||
margin: 0px; | margin: 0px; | ||||
} | } | ||||
.slide-progress { | |||||
/*border-top: 1px solid #d1d8dd;*/ | |||||
margin-top: -7px; | |||||
padding: 15px 0px; | |||||
} | |||||
.slide-progress .icon-fixed-width { | |||||
vertical-align: middle; | |||||
margin-right: 7px; | |||||
cursor: pointer; | |||||
} | |||||
.slide-progress .icon-circle-blank { | |||||
font-size: 12px; | |||||
} | |||||
.slide-progress .icon-circle { | |||||
font-size: 14px; | |||||
} | |||||
{% if style is defined %}{{ style }}{% endif %} | {% if style is defined %}{{ style }}{% endif %} | ||||
</style> | </style> | ||||
{% endblock %} | {% endblock %} |
@@ -95,7 +95,7 @@ | |||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "module", | "fieldname": "module", | ||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"hidden": 1, | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
@@ -618,7 +618,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2016-09-13 13:08:13.720070", | |||||
"modified": "2016-09-20 07:06:10.839930", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Web Form", | "name": "Web Form", | ||||
@@ -26,27 +26,17 @@ class WebForm(WebsiteGenerator): | |||||
def validate(self): | def validate(self): | ||||
super(WebForm, self).validate() | super(WebForm, self).validate() | ||||
self.module = frappe.db.get_value('DocType', self.doc_type, 'module') | |||||
if not self.module: | |||||
self.module = frappe.db.get_value('DocType', self.doc_type, 'module') | |||||
if (not (frappe.flags.in_install or frappe.flags.in_patch or frappe.flags.in_test or frappe.flags.in_fixtures) | if (not (frappe.flags.in_install or frappe.flags.in_patch or frappe.flags.in_test or frappe.flags.in_fixtures) | ||||
and self.is_standard and not frappe.conf.developer_mode): | and self.is_standard and not frappe.conf.developer_mode): | ||||
frappe.throw(_("You need to be in developer mode to edit a Standard Web Form")) | frappe.throw(_("You need to be in developer mode to edit a Standard Web Form")) | ||||
def reset_field_parent_and_convert_links_to_selects(self): | |||||
def reset_field_parent(self): | |||||
'''Convert link fields to select with names as options''' | '''Convert link fields to select with names as options''' | ||||
for df in self.web_form_fields: | for df in self.web_form_fields: | ||||
df.parent = self.doc_type | df.parent = self.doc_type | ||||
if df.fieldtype == "Link": | |||||
options = [d.name for d in frappe.get_all(df.options)] | |||||
df.fieldtype = "Select" | |||||
if len(options)==1: | |||||
df.options = options[0] | |||||
df.default = options[0] | |||||
df.hidden = 1 | |||||
else: | |||||
df.options = "\n".join([""] + options) | |||||
def use_meta_fields(self): | def use_meta_fields(self): | ||||
'''Override default properties for standard web forms''' | '''Override default properties for standard web forms''' | ||||
@@ -78,7 +68,7 @@ class WebForm(WebsiteGenerator): | |||||
from frappe.modules import get_module_path | from frappe.modules import get_module_path | ||||
# json | # json | ||||
export_to_files(record_list=[['Web Form', self.name]]) | |||||
export_to_files(record_list=[['Web Form', self.name]], record_module=self.module) | |||||
# write files | # write files | ||||
path = os.path.join(get_module_path(self.module), 'web_form', scrub(self.name), scrub(self.name)) | path = os.path.join(get_module_path(self.module), 'web_form', scrub(self.name), scrub(self.name)) | ||||
@@ -120,7 +110,7 @@ def get_context(context): | |||||
if frappe.form_dict.name and not has_web_form_permission(self.doc_type, frappe.form_dict.name): | if frappe.form_dict.name and not has_web_form_permission(self.doc_type, frappe.form_dict.name): | ||||
frappe.throw(_("You don't have the permissions to access this document"), frappe.PermissionError) | frappe.throw(_("You don't have the permissions to access this document"), frappe.PermissionError) | ||||
self.reset_field_parent_and_convert_links_to_selects() | |||||
self.reset_field_parent() | |||||
if self.is_standard: | if self.is_standard: | ||||
self.use_meta_fields() | self.use_meta_fields() | ||||
@@ -167,7 +157,7 @@ def get_context(context): | |||||
"<br>").replace("'", "\'") | "<br>").replace("'", "\'") | ||||
self.add_custom_context_and_script(context) | self.add_custom_context_and_script(context) | ||||
def add_custom_context_and_script(self, context): | def add_custom_context_and_script(self, context): | ||||
'''Update context from module if standard and append script''' | '''Update context from module if standard and append script''' | ||||
if self.is_standard: | if self.is_standard: | ||||
@@ -193,21 +183,54 @@ def get_context(context): | |||||
def get_layout(self): | def get_layout(self): | ||||
layout = [] | layout = [] | ||||
for df in self.web_form_fields: | |||||
if not layout: | |||||
layout.append({'columns': []}) | |||||
def add_page(df=None): | |||||
new_page = {'sections': []} | |||||
layout.append(new_page) | |||||
if df and df.fieldtype=='Page Break': | |||||
new_page['label'] = df.label | |||||
if df.fieldtype=="Section Break": | |||||
layout.append({'label': df.label, 'columns': [] }) | |||||
return new_page | |||||
if not layout[-1]['columns']: | |||||
layout[-1]['columns'].append([]) | |||||
def add_section(df=None): | |||||
new_section = {'columns': []} | |||||
layout[-1]['sections'].append(new_section) | |||||
if df and df.fieldtype=='Section Break': | |||||
new_section['label'] = df.label | |||||
if df.fieldtype=="Column Break" or not layout[-1]['columns']: | |||||
layout[-1]['columns'].append([]) | |||||
return new_section | |||||
def add_column(df=None): | |||||
new_col = [] | |||||
layout[-1]['sections'][-1]['columns'].append(new_col) | |||||
return new_col | |||||
page, section, column = None, None, None | |||||
for df in self.web_form_fields: | |||||
if df.fieldtype not in ("Section Break", "Column Break"): | |||||
layout[-1]['columns'][-1].append(df) | |||||
# breaks | |||||
if df.fieldtype=='Page Break': | |||||
page = add_page(df) | |||||
section, column = None, None | |||||
if df.fieldtype=='Section Break': | |||||
section = add_section(df) | |||||
column = None | |||||
if df.fieldtype=='Column Break': | |||||
column = add_column(df) | |||||
# input | |||||
if df.fieldtype not in ('Section Break', 'Column Break', 'Page Break'): | |||||
if not page: | |||||
page = add_page() | |||||
section, column = None, None | |||||
if not section: | |||||
section = add_section() | |||||
column = None | |||||
if not column: | |||||
column = add_column() | |||||
column.append(df) | |||||
return layout | return layout | ||||
@@ -25,7 +25,7 @@ | |||||
"label": "Fieldtype", | "label": "Fieldtype", | ||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "Attach\nCheck\nData\nDate\nDatetime\nHTML\nLink\nSelect\nText\nTable\nSection Break\nColumn Break", | |||||
"options": "Attach\nCheck\nData\nDate\nDatetime\nHTML\nLink\nSelect\nText\nTable\nSection Break\nColumn Break\nPage Break", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
@@ -319,7 +319,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2016-09-13 12:39:31.889290", | |||||
"modified": "2016-09-19 08:02:36.033335", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Web Form Field", | "name": "Web Form Field", | ||||