Restrictions in file types in FineUploader 3.7.0 - fine-uploader

I am using the option "allowedExtensions" without any problem but there is a situation where I have to permit any type of extension but two.
Is there a simple way to do that? I didn't find an option like 'restrictedExtensions' to do that in the code.
Thanks

From the docs:
The validate and validateBatch events are thrown/called before the default Fine Uploader validators (defined in the options) execute.
Also, if your validation event handler returns false, then Fine Uploader will register that file as invalid and not submit it.
Here's some code you could try in your validate event handler. It has not been tested yet so YMMV.
var notAllowedExts = ['pptx', 'xlsx', 'docx'];
/* ... */
onValidate: function (fileOrBlobData) {
var valid = true;
var fileName = fileOrBlobData.name || '';
qq.each(notAllowedExts, function(idx, notAllowedExt) {
var extRegex = new RegExp('\\.' + notAllowedExt + "$", 'i');
if (fileName.match(extRegex) != null) {
valid = false;
return false;
}
});
return valid;
}
/* ... */

Related

getting XMP File does not have a constructor error through ExtendScript

I am using In Design CC 2019, on my Mac OS. When I am trying to get XMP data for my .indd (InDesign document) using ExtendScript.
I am currently getting the error like this:
XMPFile Does not have a constructor.
Below is my script.
// load XMP Library
function loadXMPLibrary(){
if ( ExternalObject.AdobeXMPScript){
try{ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');}
catch (e){alert('Unable to load the AdobeXMPScript library!'); return false;}
}
return true;
}
var myFile= app.activeDocument.fullName;
// check library and file
if(loadXMPLibrary() && myFile != null){
xmpFile = new XMPFile(myFile.fsName, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_UPDATE);
var myXmp = xmpFile.getXMP();
}
if(myXmp){
$.writeln ('sucess')
}
There's an issue with your codes logic, you need to make the following change:
Add the Logical NOT operator (i.e. !) to the condition specified for your if statement in the body of your loadXMPLibrary function.
function loadXMPLibrary(){
if (!ExternalObject.AdobeXMPScript) { // <--- Change to this
// ^
try {ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');}
catch (e){alert('Unable to load the AdobeXMPScript library!'); return false;}
}
return true;
}
You need to add this because currently your if statement checks whether the condition is truthy, i.e. it checks whether ExternalObject.AdobeXMPScript is true. This will always remain false, until the AdobeXMPScript library has been loaded, therefore you're code that actually loads the library never gets executed.
Revised script:
For clarity here is the complete revised script:
// load XMP Library
function loadXMPLibrary() {
if (!ExternalObject.AdobeXMPScript) {
try{ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');}
catch (e){alert('Unable to load the AdobeXMPScript library!'); return false;}
}
return true;
}
var myFile= app.activeDocument.fullName;
// check library and file
if (loadXMPLibrary() && myFile !== null) {
xmpFile = new XMPFile(myFile.fsName, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_UPDATE);
var myXmp = xmpFile.getXMP();
}
if (myXmp){
$.writeln ('success')
}

How to create repeating form section in ServiceNow

I'm new to ServiceNow and going to start create a new form. One of the requirment is to create a repeating form section (below image is the design of the component).
May I know any default component in ServiceNow or we need to create a custom widget for this?
There is no OOB way to do this.
The way that I've solved the problem in the past, was as follows:
Create a single variable or set of variables representing the data you want to capture
Create a UI Macro/button "Add"
When clicked, that button should trigger a Script which will add the data from the fields into a JSON object which is then used to populate an HTML element with some friendly-looking representation of the data.
Here are some swatches of code I saved to do that, but keep in mind that I had to do this like a year ago so it's not super fresh.
"Add" button:
<?xml version="1.0" encoding="utf-8" ?>
<!--
UI Page
Name: http_addServerButton_loadBalancer
Category: General
Direct: false
Note that this will change slightly in name, and in the argument being passed, depending on whether the button is for the http, https, or TCP
sections.
-->
<j:jelly trim="false"
xmlns:j="jelly:core"
xmlns:g="glide"
xmlns:j2="null"
xmlns:g2="null" >
<script language="javascript"
src="addServerButtonClicked.jsdbx" />
<button name="add"
onclick="addServerButtonClicked('http')" >Add
</button >
</j:jelly >
UI Script that handled the add actions:
/*
UI Script
Name: addServerButtonClicked
Category: General
Direct: false
*/
/***********BEGIN***********/
// todo: Create onSubmit validation. Make sure users don't submit a form with existing server name/port data filled out but not added to the JSON.
// todo: Change add new button name to "add server".
/**
* This function is called when the "Add Server" button is clicked
*/
function addServerButtonClicked(protocol) {
g_form.hideFieldMsg('server_name_' + protocol, true);
g_form.hideFieldMsg('server_port_' + protocol, true);
//todo: validate the server AND port are filled out for the specific protocol selected, using "'server_name_' + protocol" and "'server_port_' + protocol"
//todo: If not BOTH populated, then add field mesage and tell the user that they are a bad user and should feel bad about their life choices.
var isFormValid = validateForm(protocol);
if (isFormValid) {
var fieldName = 'server_name_' + protocol;
g_form.getReference(fieldName, function(gr) {
//cheap way to combine relevant objects from differing scope without writing a gigantic anonymous inline function.
buildDataObject(gr, protocol);
});
} else {
alert('form invalid'); // todo: remove
// todo: throw some error or something
}
}
/**
* This function is called whenever a new server is added to the request. It parses the existing JSON data into an object, and then goes about adding on to it.
* #param serverGR {GlideRecord} - The GlideRecord object returned from the asynchronous nonHireATSCandidatesEncodedQuery that's run when add button is clicked. This param is auto-populated.
*/
function buildDataObject(serverGR, protocol) {
if (!serverGR) {
console.error('No valid server was found.');
}
// Grab the value of the JSON Data field
var existingJsonData = g_form.getValue('json_data');
// If the JSON Data field already contains some existing JSON data, use that as the starting
// point for our object. Otherwise (if this is the first entry), declare a new object.
var dataObject = existingJsonData ? JSON.parse(existingJsonData) : {
http: {},
https: {},
tcp: {}
};
//todo: write and call a function to get the protocol header info crap and append it to the object.
// Set the "serverName" property to either the server's name, or (if no name exists for this server),
// its' IP address of (if no name OR IP address exists for this server), its' sys_id.
var requestedFor = g_form.getValue('requested_for');
var serverSysId = serverGR.getValue('sys_id');
var serverIP = serverGR.getValue('ip_address');
var serverName = serverGR.getValue('name') ? serverGR.getValue('name') : serverGR.getValue('ip_address') ? serverGR.getValue('ip_address') : serverSysId = serverGR.getValue('sys_id');
var serverPort;
var tcpIPPort;
var lbMethod;
var persistence;
var monitorRequest;
var monitorResponse;
//Okay, yeah, so I could've used one line to set each of these vars, and used syntax like "'server_port_' + protocol.toLowerCase()".
//But instead of that, I did it this way. Why? Because I thought for a minute at 2AM that this would help future-me, in the event that
//we ever had stupid variable names to compete with. Sooo... behold, the pointless switch-case block.
switch (protocol.toLowerCase()) {
case 'http':
serverPort = g_form.getValue('server_port_http');
tcpIPPort = g_form.getValue('tcp_ip_http');
lbMethod = g_form.getValue('lb_method_http');
persistence = g_form.getValue('persistence_http');
monitorRequest = g_form.getValue('monitor_request_http');
monitorResponse = g_form.getValue('monitor_response_http');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_http', '');
g_form.setValue('server_name_http', '');
break;
case 'https':
serverPort = g_form.getValue('server_port_https');
tcpIPPort = g_form.getValue('tcp_ip_https');
lbMethod = g_form.getValue('lb_method_https');
persistence = g_form.getValue('persistence_https');
monitorRequest = g_form.getValue('monitor_request_https');
monitorResponse = g_form.getValue('monitor_response_https');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_https', '');
g_form.setValue('server_name_https', '');
break;
case 'tcp':
serverPort = g_form.getValue('server_port_tcp');
tcpIPPort = g_form.getValue('tcp_ip_tcp');
lbMethod = g_form.getValue('lb_method_tcp');
persistence = g_form.getValue('persistence_tcp');
monitorRequest = g_form.getValue('monitor_request_tcp');
monitorResponse = g_form.getValue('monitor_response_tcp');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_tcp', '');
g_form.setValue('server_name_tcp', '');
break;
}
if (!serverIP || !serverSysId || !serverPort || !serverName) {
// return; //Halt execution, since we don't have some of the data we need.
// todo: re-enable the above line after testing. Need to figure out how to handle errors, and what constitutes an error.
console.error('Not able to get one of these important values: [IP, Sys ID, Port, Server]')
}
// Populate the data object.
// Using bracket-notation here, in order to use a variable name as the object property name.
dataObject[protocol][serverSysId] = {};
dataObject[protocol][serverSysId].name = serverName;
dataObject[protocol][serverSysId].port = serverPort;
dataObject[protocol][serverSysId].sysid = serverSysId;
dataObject[protocol][serverSysId].ip = serverIP;
console.log(dataObject);
var dataSummary = '';
/*
This bit's pretty complex.
For each "prot" (protocol) in the outermost object,
check if the object corresponding to that protocol is truthy (not empty).
If it isn't empty, insert a header (H3) for that protocol/section.
Then, for each "prop" (server element) in that protocol object, print out some details about it.
*/
for (var prot in dataObject) {
if (!isObjEmpty(dataObject[prot]) && dataObject.hasOwnProperty(prot) && prot !== 'requestor' && prot !== 'generalInfo') {
dataSummary += '<h3>' + prot.toUpperCase() + '</h3>';
for (var prop in dataObject[prot]) {
if (dataObject[prot].hasOwnProperty(prop) && prop !== 'protocolDetails') {
dataSummary += '<b>Server name</b>: ' + dataObject[prot][prop].name + '<br />Server Sys ID: ' + dataObject[prot][prop].sysid + '<br />IP: ' + dataObject[prot][prop].ip + '<br />Port: ' + dataObject[prot][prop].port + '<br /><br />';
}
}
}
}
dataObject = populateObjectMeta(dataObject);
g_form.setValue('request_summary', dataSummary);
g_form.setValue('json_data', JSON.stringify(dataObject));
g_form.setVisible('request_summary', true);
}
function populateObjectMeta(dataObject) {
var i;
//Get boolean values for the three check-boxes representing whether the user wants HTTP, HTTPS, or TCP.
var httpSelected = isThisTrueOrWhat(g_form.getValue('http_select'));
var httpsSelected = isThisTrueOrWhat(g_form.getValue('https_select'));
var tcpSelected = isThisTrueOrWhat(g_form.getValue('tcp_select')); //todo: use these to populate more metadata in the relevant object
//Populate requestor details
dataObject.requestor = {};
dataObject.requestor.name = g_form.getReference('requested_by').getValue('name');
dataObject.requestor.sysID = g_form.getValue('requested_by');
dataObject.requestor.email = g_form.getValue('email');
//Populate general load balancing details
dataObject.generalInfo = {};
dataObject.generalInfo.dnsHost = g_form.getValue('dns_host');
dataObject.generalInfo.domainName = g_form.getValue('domain_name');
dataObject.generalInfo.application = isThisTrueOrWhat(g_form.getValue('application_not_found')) ? g_form.getValue('application_name_text') : g_form.getReference('application_name_ref').getValue('name');
dataObject.generalInfo.lifeCycle = g_form.getValue('lifecycle');
dataObject.generalInfo.site = g_form.getReference('site').getValue('name');
//Populate protocol details for HTTP, HTTPS, and TCP.
var selectedProtocols = getSelectedProtocols();
console.log(selectedProtocols);
for (i = 0; i < selectedProtocols.length; i++) {
console.log('Adding data to dataObj with selected protocol ' + [selectedProtocols[i]]);
dataObject[selectedProtocols[i]].protocolDetails = {};
dataObject[selectedProtocols[i]].protocolDetails.tcpIPPort = g_form.getValue('tcp_ip_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.lbMethod = g_form.getValue('lb_method_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.persistence = g_form.getValue('persistence_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.monitorRequest = g_form.getValue('monitor_request_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.monitorResponse = g_form.getValue('monitor_response_' + selectedProtocols[i]);
}
return dataObject;
}
function getSelectedProtocols() {
var selectedProtocols = [];
var httpSelected = isThisTrueOrWhat(g_form.getValue('http_select'));
var httpsSelected = isThisTrueOrWhat(g_form.getValue('https_select'));
var tcpSelected = isThisTrueOrWhat(g_form.getValue('tcp_select'));
if (httpSelected) {
selectedProtocols.push('http');
}
if (httpsSelected) {
selectedProtocols.push('https');
}
if (tcpSelected) {
selectedProtocols.push('tcp');
}
return selectedProtocols;
}
function validateForm(protocol) {
var port;
switch (protocol.toLowerCase()) {
case 'http':
port = g_form.getValue('server_port_http');
break;
case 'https':
port = g_form.getValue('server_port_https');
break;
case 'tcp':
port = g_form.getValue('server_port_tcp');
break;
}
//todo: validate port, and a bunch of other stuff.
return true;
}
/**
* Adds one object to another, nesting the child object into the parent.
* this is to get around javascript's immutable handling of objects.
* #param name {String} - The name of the property of the parent object, in which to nest the child object. <br />For example, if the name parameter is set to "pickles" then "parent.pickles" will return the child object.
* #param child {Object} - The object that should be nested within the parent object.
* #param [parent={}] {Object} - The parent object in which to nest the child object. If the parent object is not specified, then the child object is simple nested into an otherwise empty object, which is then returned.
* #returns {Object} - A new object consisting of the parent (or an otherwise empty object) with the child object nested within it.
* #example
* //sets myNewObject to a copy of originalObject, which now also contains the original (yet un-linked) version of itself as a child, under the property name "original"
* var myNewObject = addObjToObj("original", originalObj, originalObj);
*/
function addObjToObj(name, child, parent) {
if (!parent) {
parent = {};
}
parent[name] = child;
return parent;
}
function isObjEmpty(o) {
for (var p in o) {
if (o.hasOwnProperty(p)) {
return false;
}
}
return true;
}
function isThisTrueOrWhat(b) {
return ((typeof b == 'string') ? (b.toLowerCase() == 'true') : (b == true)); //all this just to properly return a bool in JS. THERE'S GOT TO BE A BETTER WAY!
}
Just create a UI page with the functionality that you want to have.
Ex- clicking on new button opens up the repeated form view.
Get the inputs from the filled form as list of objects, send them over to the client-callable script include(GlideAjax) that handles creation.

Timeline Schedule Pages events - Servicenow

I'm starting to work using servicenow, and I've an issue with TimeLines and the doubleClick events.
I configured the schedule page and ScriptInclude (code as pseudo-code):
Schedule Page
glideTimeline.setReadOnly(true); glideTimeline.showLeftPane(true);
glideTimeline.registerEvent("getItems", "MyTimelineScriptInclude");
function doubleClickCustomFunction(evt) {
try {
alert('double click: ' + "evt: " + evt + ', target: ' + target );
action.setRedirectURL( 'my_application.do?sys_id=' + target );
catch (exception) {
gs.log(exception);
}
},
MyTimelineScriptInclude
var MyTimelineScriptInclude = Class.create();
MyTimelineScriptInclude.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {
_getTickets: function(){
tickets = foo();
return tickets;
}
getItems: function() {
try {
var ticket_list = this._getTickets();
for (var ticket in ticket_list) {
this._representTicket(ticket_list[ticket].sys_id);
}
} catch(exception) {
this._debugLog(exception, "getItemsException");
}
},
_representTicket: function(sys_id) {
// ticket Object;
ticket_object = getTicket(sys_id);
var timelineItem = new TimelineItem('my_application' , ticket_object.sys_id);
_representSpans( timelineItem , ticket_object );
this.add(timelineItem);
},
_representSpans: function( timelineItem , ticket_object ) {
var timelineItemSpan1 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan1.setTimeSpan( ticket_object.startDateTime1.getNumericValue() , ticket_object.endDateTime1.getNumericValue() );
timelineItemSpan1.setSpanText(ticket_object.spanText);
timelineItemSpan1.setSpanColor(ticket_object.spanColor);
timelineItemSpan1.setTooltip(ticket_object.spanTooltip);
var timelineItemSpan2 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan2.setTimeSpan( ticket_object.startDateTime2.getNumericValue() , ticket_object.endDateTime2.getNumericValue() );
timelineItemSpan2.setSpanText(ticket_object.spanText);
timelineItemSpan2.setSpanColor(ticket_object.spanColor);
timelineItemSpan2.setTooltip(ticket_object.spanTooltip);
var timelineItemSpan3 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan3.setTimeSpan( ticket_object.startDateTime2.getNumericValue() , ticket_object.endDateTime2.getNumericValue() );
timelineItemSpan3.setSpanText(ticket_object.spanText);
timelineItemSpan3.setSpanColor(ticket_object.spanColor);
timelineItemSpan3.setTooltip(ticket_object.spanTooltip);
},
});
The problem is when I double click on a timeline row, it triggers the doubleClickCustomFunction, but, it isn't able to get any evt data, so, It doesn't performs the redirection.
Best regards
 
Schedule Pages in ServiceNow use client-side script, so if the doubleClickCustomFunction is part of the schedule page client script, the server-side calls (action.setRedirect and gs.log) will fail.
The default double click function contains the following parameters: event, this, strRecordSysID, strUserSysID
I haven't used a custom doubleclick override, so I'm not sure if these parameters are automatically available. However, this is the case for other custom overrides written within the script include, such as elementMoveX
Other than that, you might try calling window.event within the function if it is a part of the client script

jqGrid loses selections when request is cancelled

I have a requirement to allow a user to cancel a jqGrid (Version 4.4.1) paging or sorting operation if they have any selections that they do not want to lose.
I initially attempted to handle this in the beforeRequest event handler but when I call the selarrrow function an empty array is always returned as the selections appear to have already been cleared.
I then tried adding onPaging and onSorting event handlers where the selections were available via the selarrrow function, however, when I return 'stop' from these functions to cancel the request (as specified in the jqGrid documentation) the selections still appear to have been cleared. Note the rows still appear selected in the grid but selarrrow returns an empty array.
I'm guessing this is a jqGrid defect but does anyone know if it has been fixed in a more recent release or if there is a configuration workaround?
I think that there are just a bug in usage of onPaging. If the method return "stop" the selection still cleared. The reason is the order of the lines in the code fragment
clearVals = function(onpaging){
var ret;
if ($.isFunction(ts.p.onPaging) ) { ret = ts.p.onPaging.call(ts,onpaging); }
ts.p.selrow = null;
if(ts.p.multiselect) {ts.p.selarrrow =[]; setHeadCheckBox( false );}
ts.p.savedRow = [];
if(ret==='stop') {return false;}
return true;
};
The correct code should be
clearVals = function(onpaging){
var ret;
if ($.isFunction(ts.p.onPaging) ) { ret = ts.p.onPaging.call(ts,onpaging); }
if(ret==='stop') {return false;}
ts.p.selrow = null;
if(ts.p.multiselect) {ts.p.selarrrow =[]; setHeadCheckBox( false );}
ts.p.savedRow = [];
return true;
};
You can move the line 2045 (with if(ret==='stop') {return false;}) of jquery.jqGrid.src.js of the version 4.5.2 (which corresponds the line 1902 in version 4.4.1) after the 2041 (the line 1898 in version 4.4.1) (after if ($.isFunction(ts.p.onPaging) ) { ret = ts.p.onPaging.call(ts,onpaging); }) to fix the bug.
The usage of onSortCol seems be correct and if the callback returns "stop" string the selection should stay unchanged.
UPDATED: I posted the corresponding bud fix as pull request. It's merged today (see here) to the main code of jqGrid. So the next version (>4.5.2) should not have more the problem which you described.
This is the workaround I have put in place for this problem (note: it requires Underscore.js) but interested in a cleaner solution if anyone has one:
var tempSelections,
myGrid = $("#mygrid"),
checkSelections = function() {
var selections = myGrid.jqGrid("getGridParam", "selarrrow");
if (selections && selections.length > 0) {
tempSelections = selections;
}
};
myGrid.jqGrid({
... //settings
multiselect: true,
onPaging: checkSelections,
onSortCol: checkSelections,
beforeRequest: function() {
if (tempSelections && tempSelections.length > 0) {
if (!confirm("Do you want to clear the selections on this page?")) {
_.forEach(tempSelections, function(selection) {
myGrid.jqGrid("setSelection", selection);
});
tempSelections = null;
return false;
}
tempSelections = null;
}
return true;
}
});

jqGrid - Inline edit - Detect dirty / changed cells

is there an example of using jqgrid's getChangedCells
method to determine if data has changed?
I grepped getChangedCells in the downloadable demos for
jqgrid, and could only find the function definition, not
example usages of getChangedCells.
What I want to do is save the edits that a user's
made if the user clicks on another row. But, I only
want to submit the save if the row is dirty.
Thanks in advance,
--Nate
There are no safe dirty flag on the row. You can use the fact that at the beginning of row editing (at the start of the inline editing mode) the method editRow add editable="1" attribute to the grid row (<tr> element). Later the methods saveRow and restoreRow changes the attribute value to editable="0". So the rows of the current page which was at least once in the inline editing mode will have the editable attribute. If the id of the table element is "list" you can find the edited rows with
$("#list tr[editable]")
The ids of the elements of the set are the rowids of the rows.
If you use paging in the grid you should be careful and save the ids of the edited rows on the current page before the changing of the page. The onPaging event would help you here.
In my opinion the best and the most safe way to do what you need is to use aftersavefunc parameter of the editRow or saveRow methods (probably you use directly only editRow). Inside of your aftersavefunc function you can save the id of the modified row in an array/map. This will solve your problem and will safe work.
Finally, I managed to bring a piece of code to detect what we want ;)
Hopefully any jqgrid gurus there (like Oleg), have enough time to review this code and improve it.
The example code will work for detect data changed in a grid with an editable field named "name". If you want to check for changed data in more columns, you have to add the variables after_edit and before_edit asociated with that columns.
To get the previous cell data inside the onSelectRow function, I don't used the getCell method because in the documentation says in red:
Do not use this method when you editing the row or
cell. This will return the cell content and not the
actuall value of the input element
By disgrace I could check that the documentation was right :(.
However the getCell function works properly with the current cell data.
And here is the code:
// Declare variables used for inline edit functionality.
var last_selected;
var before_edit_value;
var after_edit_value;
$('#grid-id').jqGrid({
...
onSelectRow: function(row_id){
if(row_id && row_id !== last_selected) {
/*
* Determine if the value was changed, if not there is no need to save to server.
*/
if (typeof(last_selected) != 'undefined') {
after_edit_value = $('#grid-id tr#' + last_selected + ' .name_column input').val();
}
if (before_edit_value != after_edit_value) {
/*
* Save row.
*/
$('#grid-id').jqGrid(
'saveRow',
last_selected,
function(response){
/* SuccessFunction: Do something with the server response */
return true;
},
'http://url.to.server-side.script.com/server-side-script.php',
{
additional_data: 'example: additional string',
});
}
else {
/*
* Restore the row.
*/
$('#grid-id').jqGrid('restoreRow', last_selected);
}
before_edit_value = $('#grid-id').jqGrid('getCell', row_id, 'name');
}
last_selected = row_id;
/*
* Edit row.
*/
$('#grid-id').jqGrid(
'editRow',
row_id,
true,
function() {/* OnEditFunction */},
function(response) {
/* SuccessFunction: Do something with the server response */
return true;
},
'http://url.to.server-side.script.com/server-side-script.php',
{
additional_data: 'example: additional string',
});
},
...
});
In one of my projects I did the following: before editing the row I remember row data in global variable and after editing is done just check if row data was changed. Something like this (edit mode activated by double click):
var beforeEditData;
function onGridDblClickRow(id) {
if (isRowEditable(id)) {
beforeEditData = grid.getRowData(id);
grid.editRow(id, true, null, null, 'clientArray', null, onRowAfterEdit);
...
}
}
function onRowAfterEdit(row) {
var data = grid.getRowData(row);
if (!isDataChanged(beforeEditData, data)) {
return; // No changes
}
... // Save data here
}
function isDataChanged(before, after){
... // Allows tricky logic for dirty data, e.g. one may trim spaces etc.
}
Using MVC4 and JQuery this is what I did
In the View
<script type="text/javascript">
var $grid = $("#Grid");
var lastSelection;
var datachanged = false;
function gridInitialised() {
var headers = $('th>div>:input');
for (var h = 0; h < headers.length; headers[h++].onclick = (function () { if (datachanged) { $grid.saveRow(lastSelection); datachanged = false; } }));
}
function editRow(id) {
if (id && id !== lastSelection) {
if (datachanged) { $grid.saveRow(lastSelection); datachanged = false; }
$grid.restoreRow(lastSelection);
$grid.editRow(id, true);
var inputs = $('#'+id+'>td>:input[class="editable"]');
for (var i = 0; i < inputs.length; inputs[i++].onchange = (function () { datachanged = true; }));
lastSelection = id;
}
}
</script>
#Html.Trirand().JQGrid(Model.Grid, "Grid")
in the Model
Grid.ClientSideEvents.RowSelect = "editRow";
Grid.ClientSideEvents.GridInitialized = "gridInitialised";
The gridInitialised code is to handle changes to the search filter.
Dave
As Oleg mentioned 5 (wow) years ago - I used the saveRow function and passed the flag as extraparam.
something like this, assuming your "model" or a hidden column IsDirty in my case:
onSelectRow: function(id) {
if (id && id !== lastgridsel) {
$("#myGrid").saveRow(lastgridsel, false, "clientArray", { IsDirty: "True" });
$("#myGrid").editRow(id, true, null, null, "clientArray");
lastgridsel = id;
}
},
and then loop through the rows on Save click (external button in my case), something along the lines of:
$("#gridSaveBtn").on("click", function() {
var batch = new Array();
var dataIds = $("#myGrid").jqGrid("getDataIDs");
for (var i = 0; i < dataIds.length; i++) {
try {
$("#myGrid").jqGrid("saveRow", dataIds[i], false, "clientArray");
//get row data
var data = $("#myGrid").jqGrid("getRowData", dataIds[i]);
if (data["IsDirty"] === "True") {
batch.push(data);
}
} catch (ex) {
alert(ex.Message);
$("#myGrid").jqGrid("restoreRow", dataIds[i]);
}
}
});

Resources