diff --git a/frappe/public/build.json b/frappe/public/build.json
index 600984f801..5ff2fb3784 100644
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -132,6 +132,7 @@
"public/js/frappe/form/save.js",
"public/js/frappe/form/script_manager.js",
"public/js/frappe/form/grid_form.html",
+ "public/js/frappe/form/grid_body.html",
"public/js/frappe/form/grid.js",
"public/js/frappe/form/linked_with.js",
"public/js/frappe/form/workflow.js",
diff --git a/frappe/public/css/form_grid.css b/frappe/public/css/form_grid.css
index 1ecf80e4c8..1545642ae0 100644
--- a/frappe/public/css/form_grid.css
+++ b/frappe/public/css/form_grid.css
@@ -77,6 +77,9 @@
font-size: 120%;
border-bottom: 1px solid #d1d8dd;
}
+.grid-footer {
+ background-color: #fff;
+}
.grid-footer-toolbar {
padding: 10px 15px;
border-top: 1px solid #d1d8dd;
diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js
index ecd4380325..505ec584d1 100644
--- a/frappe/public/js/frappe/form/control.js
+++ b/frappe/public/js/frappe/form/control.js
@@ -735,7 +735,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
this.frm.attachments.update_attachment(attachment);
} else {
this.set_input(this.fileobj.filename, this.dataurl);
- this.refresh();
+ //this.refresh();
}
},
});
diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js
index b516b6f10d..0415e86919 100644
--- a/frappe/public/js/frappe/form/grid.js
+++ b/frappe/public/js/frappe/form/grid.js
@@ -20,29 +20,16 @@ frappe.ui.form.Grid = Class.extend({
make: function() {
var me = this;
- this.wrapper = $('
')
+ this.wrapper = $(frappe.render_template("grid_body", {}))
.appendTo(this.parent)
.attr("data-fieldname", this.df.fieldname);
$(this.wrapper).find(".grid-add-row").click(function() {
me.add_new_row(null, null, true);
return false;
- })
+ });
+
+ this.setup_allow_bulk_edit();
},
make_head: function() {
@@ -144,21 +131,6 @@ frappe.ui.form.Grid = Class.extend({
me.frm.dirty();
}
});
-
- // $rows.sortable({
- // handle: ".data-row, .grid-form-heading",
- // helper: 'clone',
- // update: function(event, ui) {
- // me.frm.doc[me.df.fieldname] = [];
- // $rows.find(".grid-row").each(function(i, item) {
- // var doc = $(item).data("doc");
- // doc.idx = i + 1;
- // $(this).find(".row-index").html(i + 1);
- // me.frm.doc[me.df.fieldname].push(doc);
- // });
- // me.frm.dirty();
- // }
- // });
},
get_data: function() {
var data = this.frm.doc[this.df.fieldname] || [];
@@ -231,6 +203,68 @@ frappe.ui.form.Grid = Class.extend({
return false;
});
this.multiple_set = true;
+ },
+ setup_allow_bulk_edit: function() {
+ var me = this;
+ if(this.frm.get_docfield(this.df.fieldname).allow_bulk_edit) {
+ // download
+ me.setup_download();
+
+ // upload
+ $(this.wrapper).find(".grid-upload").removeClass("hide").on("click", function() {
+ frappe.prompt({fieldtype:"Attach", label:"Upload File"},
+ function(data) {
+ var data = frappe.utils.csv_to_array(frappe.upload.get_string(data.upload_file));
+ // row #2 contains fieldnames;
+ var fieldnames = data[2];
+
+ me.frm.clear_table(me.df.fieldname);
+ $.each(data, function(i, row) {
+ if(i > 4) {
+ var d = me.frm.add_child(me.df.fieldname);
+ $.each(row, function(ci, value) {
+ d[fieldnames[ci]] = value;
+ });
+ }
+ });
+
+ me.frm.refresh_field(me.df.fieldname);
+
+ }, __("Edit via Upload"), __("Update"));
+ return false;
+ });
+ }
+ },
+ setup_download: function() {
+ var me = this;
+ $(this.wrapper).find(".grid-download").removeClass("hide").on("click", function() {
+ var data = [];
+ data.push([__("Bulk Edit {0}", [me.df.label])]);
+ data.push([]);
+ data.push([]);
+ data.push([]);
+ data.push(["------"]);
+ $.each(frappe.get_meta(me.df.options).fields, function(i, df) {
+ if(frappe.model.is_value_type(me.df.fieldtype)) {
+ data[1].push(df.label);
+ data[2].push(df.fieldname);
+ data[3].push(df.description);
+ }
+ });
+
+ // add data
+ $.each(me.frm.doc[me.df.fieldname] || [], function(i, d) {
+ row = [];
+ $.each(data[2], function(i, fieldname) {
+ row.push(d[fieldname] || "");
+ });
+ data.push(row);
+ });
+
+ frappe.tools.downloadify(data, null, me.df.label);
+ return false;
+ });
+
}
});
diff --git a/frappe/public/js/frappe/form/grid_body.html b/frappe/public/js/frappe/form/grid_body.html
new file mode 100644
index 0000000000..55ebb86746
--- /dev/null
+++ b/frappe/public/js/frappe/form/grid_body.html
@@ -0,0 +1,28 @@
+
diff --git a/frappe/public/js/frappe/misc/tools.js b/frappe/public/js/frappe/misc/tools.js
index d564493c3b..0e25a96c41 100644
--- a/frappe/public/js/frappe/misc/tools.js
+++ b/frappe/public/js/frappe/misc/tools.js
@@ -3,7 +3,7 @@
frappe.provide("frappe.tools");
-frappe.tools.downloadify = function(data, roles, me) {
+frappe.tools.downloadify = function(data, roles, title) {
if(roles && roles.length && !has_common(roles, user_roles)) {
msgprint(__("Export not allowed. You need {0} role to export.", [frappe.utils.comma_or(roles)]));
return;
@@ -14,7 +14,7 @@ frappe.tools.downloadify = function(data, roles, me) {
var download_from_server = function() {
open_url_post("/", {
- args: { data: data, filename: me.title },
+ args: { data: data, filename: title },
cmd: "frappe.utils.csvutils.send_csv_to_client"
}, true);
}
@@ -33,7 +33,7 @@ frappe.tools.downloadify = function(data, roles, me) {
Downloadify.create(id ,{
filename: function(){
- return me.title + '.csv';
+ return title + '.csv';
},
data: _get_data,
swf: 'assets/frappe/js/lib/downloadify/downloadify.swf',
diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js
index c7ddbf79f4..76fcab67f5 100644
--- a/frappe/public/js/frappe/misc/utils.js
+++ b/frappe/public/js/frappe/misc/utils.js
@@ -320,4 +320,87 @@ frappe.utils = {
setTimeout(function() { callback(dataURL); }, 10 );
}
},
+
+ csv_to_array: function (strData, strDelimiter) {
+ // Check to see if the delimiter is defined. If not,
+ // then default to comma.
+ strDelimiter = (strDelimiter || ",");
+
+ // Create a regular expression to parse the CSV values.
+ var objPattern = new RegExp(
+ (
+ // Delimiters.
+ "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
+
+ // Quoted fields.
+ "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
+
+ // Standard fields.
+ "([^\"\\" + strDelimiter + "\\r\\n]*))"
+ ),
+ "gi"
+ );
+
+
+ // Create an array to hold our data. Give the array
+ // a default empty first row.
+ var arrData = [[]];
+
+ // Create an array to hold our individual pattern
+ // matching groups.
+ var arrMatches = null;
+
+
+ // Keep looping over the regular expression matches
+ // until we can no longer find a match.
+ while (arrMatches = objPattern.exec( strData )){
+
+ // Get the delimiter that was found.
+ var strMatchedDelimiter = arrMatches[ 1 ];
+
+ // Check to see if the given delimiter has a length
+ // (is not the start of string) and if it matches
+ // field delimiter. If id does not, then we know
+ // that this delimiter is a row delimiter.
+ if (
+ strMatchedDelimiter.length &&
+ strMatchedDelimiter !== strDelimiter
+ ){
+
+ // Since we have reached a new row of data,
+ // add an empty row to our data array.
+ arrData.push( [] );
+
+ }
+
+ var strMatchedValue;
+
+ // Now that we have our delimiter out of the way,
+ // let's check to see which kind of value we
+ // captured (quoted or unquoted).
+ if (arrMatches[ 2 ]){
+
+ // We found a quoted value. When we capture
+ // this value, unescape any double quotes.
+ strMatchedValue = arrMatches[ 2 ].replace(
+ new RegExp( "\"\"", "g" ),
+ "\""
+ );
+
+ } else {
+
+ // We found a non-quoted value.
+ strMatchedValue = arrMatches[ 3 ];
+
+ }
+
+
+ // Now that we have our value string, let's add
+ // it to the data array.
+ arrData[ arrData.length - 1 ].push( strMatchedValue );
+ }
+
+ // Return the parsed data.
+ return( arrData );
+ }
};
diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js
index 8d006fa42d..0e8ed04e64 100644
--- a/frappe/public/js/frappe/model/model.js
+++ b/frappe/public/js/frappe/model/model.js
@@ -34,6 +34,10 @@ $.extend(frappe.model, {
new_names: {},
events: {},
+ is_value_type: function(fieldtype) {
+ return frappe.model.no_value_type.indexOf(fieldtype)!==-1;
+ },
+
get_std_field: function(fieldname) {
var docfield = $.map([].concat(frappe.model.std_fields).concat(frappe.model.std_fields_table),
function(d) {
diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js
index 3b28fc5095..6b6ff48ef5 100644
--- a/frappe/public/js/frappe/ui/messages.js
+++ b/frappe/public/js/frappe/ui/messages.js
@@ -34,13 +34,13 @@ frappe.confirm = function(message, ifyes, ifno) {
return d;
}
-frappe.prompt = function(fields, callback) {
+frappe.prompt = function(fields, callback, title, primary_label) {
if(!$.isArray(fields)) fields = [fields];
var d = new frappe.ui.Dialog({
fields: fields,
- title: __("Enter Value"),
+ title: title || __("Enter Value"),
})
- d.set_primary_action(__("Submit"), function() {
+ d.set_primary_action(primary_label || __("Submit"), function() {
var values = d.get_values();
if(!values) {
return;
diff --git a/frappe/public/js/frappe/upload.js b/frappe/public/js/frappe/upload.js
index 2a09c9a3d8..7f68a75dc9 100644
--- a/frappe/public/js/frappe/upload.js
+++ b/frappe/public/js/frappe/upload.js
@@ -116,5 +116,16 @@ frappe.upload = {
freader.readAsDataURL(fileobj);
}
+ },
+ get_string: function(dataURI) {
+ // remove filename
+ var parts = dataURI.split(',');
+ if(parts[0].indexOf(":")===-1) {
+ var a = parts[2];
+ } else {
+ var a = parts[1];
+ }
+
+ return atob(a);
}
}
diff --git a/frappe/public/js/frappe/views/reports/grid_report.js b/frappe/public/js/frappe/views/reports/grid_report.js
index 8a5c2291a8..de3d5efcd7 100644
--- a/frappe/public/js/frappe/views/reports/grid_report.js
+++ b/frappe/public/js/frappe/views/reports/grid_report.js
@@ -444,7 +444,7 @@ frappe.views.GridReport = Class.extend({
},
export: function() {
frappe.tools.downloadify(frappe.slickgrid_tools.get_view_data(this.columns, this.dataView),
- ["Report Manager", "System Manager"], this);
+ ["Report Manager", "System Manager"], this.title);
return false;
},
apply_filters: function(item) {
@@ -915,7 +915,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({
return false;
});
- frappe.tools.downloadify(data, ["Report Manager", "System Manager"], me);
+ frappe.tools.downloadify(data, ["Report Manager", "System Manager"], me.title);
return false;
})
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index e6615f0fd5..30d294ec0c 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -622,7 +622,7 @@ frappe.views.QueryReport = Class.extend({
return [row.splice(1)];
});
this.title = this.report_name;
- frappe.tools.downloadify(result, null, this);
+ frappe.tools.downloadify(result, null, this.title);
return false;
}
})
diff --git a/frappe/public/js/legacy/clientscriptAPI.js b/frappe/public/js/legacy/clientscriptAPI.js
index d5ae8978bd..975a95c453 100644
--- a/frappe/public/js/legacy/clientscriptAPI.js
+++ b/frappe/public/js/legacy/clientscriptAPI.js
@@ -146,11 +146,15 @@ _f.Frm.prototype.field_map = function(fnames, fn) {
}
}
+_f.Frm.prototype.get_docfield = function(fieldname) {
+ return frappe.meta.get_docfield(this.doctype, fieldname, this.docname);
+}
+
_f.Frm.prototype.set_df_property = function(fieldname, property, value) {
- var field = frappe.meta.get_docfield(cur_frm.doctype, fieldname, cur_frm.docname)
+ var field = this.get_docfield(fieldname);
if(field) {
field[property] = value;
- cur_frm.refresh_field(fieldname);
+ this.refresh_field(fieldname);
};
}
diff --git a/frappe/public/less/form_grid.less b/frappe/public/less/form_grid.less
index a40cbb95c6..8d39b9f87d 100644
--- a/frappe/public/less/form_grid.less
+++ b/frappe/public/less/form_grid.less
@@ -91,6 +91,10 @@
border-bottom: 1px solid @border-color;
}
+.grid-footer {
+ background-color: #fff;
+}
+
.grid-footer-toolbar {
padding: 10px 15px;
border-top: 1px solid @border-color;