Avoiding data corruption if column state is saved, new column is defined in server and data is saved in edit - json

Answer in
How to replace remapColums with remapColumnsByName in free jqgrid
contains code to save and restore jqgrid column order.
It contains method to restore columns state:
var restoreColumnState = function (colModel) {
var colItem, i, l = colModel.length, colStates, cmName,
columnsState = getObjectFromLocalStorage(myColumnStateName);
if (columnsState) {
colStates = columnsState.colStates;
for (i = 0; i < l; i++) {
colItem = colModel[i];
cmName = colItem.name;
if (cmName !== "rn" && cmName !== "cb" && cmName !== "subgrid") {
colModel[i] = $.extend(true, {}, colModel[i], colStates[cmName]);
}
}
}
return columnsState;
};
This method causes invalid data posting from inline edit if new column is defined in server side.
jqgrid is populated from remote json data array. In this array columns must be the same as in column state.
If columns state is saved and new column is added to jqgrid in server code,
colStates[cmName] value is undefined.
This code causes new column to be added to end of jqgrid columns. However, in json data array it appears in the column as defined in server.
On inline edit, if row is saved, wrong values are assigned to form fields and invalid values are passed to server.
I tried to fix it adding colStates[cmName] !== undefined check:
if (cmName !== "rn" && cmName !== "cb" && cmName !== "subgrid" && colStates[cmName] !== undefined) {
but problem persists.
How to fix this that if new column is added to jqgrid colmodel in server, restoring column state allows to save correct data?
New column which is not found in saved columns list should appear in the same relative position as it is defined in colmodel. Column order shoudl corrspond to remote data from server.
Update
ColModel is defined in Razor view in variable cm
<script>
var
$grid,
myColumnsState,
isColState,
myColumnStateName;
$(function () {
var cm= #Html.Raw(Model.GetColModel());
$grid = $("#grid");
myColumnStateName = #Model.ColumnStateName();
myColumnsState = restoreColumnState(cm, myColumnStateName);
isColState = typeof (myColumnsState) !== 'undefined' && myColumnsState !== null;
$grid.jqGrid({
page: isColState ? myColumnsState.page : 1,
sortname: isColState ? myColumnsState.sortname : "",
sortorder: isColState ? myColumnsState.sortorder : "",
....
</script>

I know the problem very good! One need to implement some kind of validating checks of the previously saved state of the grid before the usage. The deepness of checks could depend on the exact requirements of your application and from the information which one knows exactly. The most opened and unclear thing: should one make some correction/fixing of the previously saved state or should one discard the state on the first small error? The answer on the question depends on the project where jqGrid are used. Deep fixing could include fixing of sorting parameter and modifying previously saved filter. Another example: the state could include ids of selected rows, but the fixing of the part of the state could be bad idea in the common case. One loading of the data could imply one setting of selected rows, but loading of another data (unfiltered for example) could do have the rows and the rows should be do selected. There are no best choice in the case, all depends on the exact project requirements. In any way the implementation of the state validation/fixing isn't a simple code.
Only because of the complexity of the problems of validation of previously saved state and the existence of different scenarios of validation I didn't implemented such feature in free jqGrid. Any good implementation needs time and the resulting code will be not simple. It will have some options for some typical scenarios. I would like to implement the feature in the future, but I just didn't found the time for the implementation, because I have to do my main job to earn money for my family and I still try to help other people in the community who have small, but important, for the person, problems with jqGrid of free jqGrid.

Related

Increasing Value of State Variable in ForEach loop for Progress Bar in React

I'm trying to figure out a way to build a progress bar with React. I have a forEach loop that iterates through an array of about 7,000 indexes. Each time I validate a row, I want to update a state variable with percentage completion (and render this on the page live). I've tried iterating through these indexes, and updating my state variable (hoping to update the page) in the loop but I'm realizing that will not work. I obviously can't do this with a normal variable as it will reset when the component re-renders. Can anyone give me some insight on this topic?
Thanks.
Here is a code snippet from what I'm looking at:
parsedAssets.forEach(asset => {
newAssetValidated = validateBulkUpload(asset, parsedAssets, assetList, accountLogged, jobSites);
!newAssetValidated.reject_err ? validatedAssetList.accepted.push(newAssetValidated) : validatedAssetList.rejected.push(newAssetValidated);
setStateAssets({ ...stateAssets, validatedAssetList });
});
}
So essentially, as each asset is either accepted or rejected we add it to "stateAssets", and I'm hoping to build the progress bar from the length of the combined arrays that are getting set in stateAssets. However, when the forEach loop is completed, only the last validated asset is getting set due to it not updating until the forEach loop is completed.
Personally I can't imagine such a heavy validation, that you need progress-bar, but anyway.
First solution is to separate validation itself from state update for progress-bar into separate "threads". But since JS is single threaded, you may use some tricks with setTimeout or setInterval functionality. It may be very tricky, and in general not recommended practice with React.
Another way is - to set the work into queue & process 1 item at a time.
As an example I would do it something like this:
function ComponentWithProgress({parsedAssets, setParsedAssets}) {
const [validatedAssetList, setValidatedAssetList] = useState([])
const [progress, setProgress] = useState(0)
const [toDo, setToDo] = useState([])
if(parsedAssets && parsedAssets.length>0) {
setToDo(parsedAssets)
// clear parsedAssets in parent component to: [], false, null ...
// so you put it into toDo only once
setParsedAssets([])
}
if(toDo.length > 0) {
const asset = toDo[0]
const newToDo = toDo.slice(1) // All but 0th element
const newAssetValidated = validateBulkUpload(asset);
setValidatedAssetList([ ...validatedAssetList, newAssetValidated ]);
setToDo(newToDo)
setProgress( newToDo.length / ( validatedAssetList.length + newToDo.length ) * 100 )
}
// ... Render here
// If you need only accepted
const accepted = validatedAssetList.filter(v => !v.reject_err)
}
This example maybe not work for you as is, because you didn't showed us the context, but the main idea is here.

Is there a way of searching div element by class in GAS? [duplicate]

Is there a simple method to locate an XML node by its attribute in Google Apps Script? Here's an XML snippet:
<hd:components>
<hd:text name="ADM Custom admissions TE">
<hd:prompt>Admission</hd:prompt>
<hd:columnWidth widthType="minimum" minWidth="100"/>
</hd:text>
<hd:text name="ADM Insufficient heat end date TE">
<hd:prompt>To</hd:prompt>
</hd:text>
<hd:text name="ADM Insufficient heat start date TE">
<hd:prompt>From</hd:prompt>
</hd:text>
<hd:text name="ADM Third party payment period TE">
<hd:defMergeProps unansweredText="__________"/>
<hd:prompt>When (date or period)?</hd:prompt>
</hd:text>
For purposes of the XML file I'm trying to parse, the "name" attribute is a unique identifier, while what GAS thinks is the "name" for purposes of the XmlService.Element.getChild(name) method ("text" for each node shown in this snippet) is a non-unique classifier for the type of node. I'd like to be able to write a function to retrieve a specific node from this XML file with only the name attribute. XMLPath notation in other languages has this capability using the [# notation. Is there a way to do it in GAS, or do I need to write a function that walks through the XML until it finds a node with the right name attribute, or store it in some different type of data structure for fast searching if the XML file is sufficiently large?
Here's the snippet I started writing: it's fine if there's no built-in function, I just wondered if there was a better/faster way to do this. My function isn't so efficient, and I wondered if the XmlService had a more efficient internal data structure it's using to speed up searching. My approach is just to loop through all of the element's children until there's a match.
function getComponentFromXML(xml,name) {
for (var i = 0; i < xml.length; i++) {
var x = xml[i];
var xname = x.getAttribute('name').getValue();
if (xname == name) {
return getComponentAttributes(x);
}
}
}
There is no built-in search, so the only way is to read the list of elements looking for the one with the desired value of attribute 'name'. If elements is an array of elements to search through, you can do
var searchResults = elements.filter(function (e) {
return e.getAttribute('name') && e.getAttribute('name').getValue() == searchString;
});
(Both checks are needed to avoid an error when there is no 'name' attribute at all.)
How to obtain such an array elements may depend on XML document. If, as in your example, the elements to search are the immediate children of the root element, then
var doc = XmlService.parse(xmlString);
var elements = doc.getRootElement().getChildren();
would be a quick and easy way to do this.
In general, to get all elements without recursion, the getDescendants method can be used. It returns an array of Content object, which can be filtered down to Element objects:
var elements = doc.getDescendants().filter(function (c) {
return c.getType() == XmlService.ContentTypes.ELEMENT;
}).map(function (c) {
return c.asElement();
});

Node-red - need a multi-input function for a number value

So I'm just getting to grips with node-red and I need to create a conditional global function.
I have two separate global.payloads set to a number value of either 0 or 1.
What I need to happen now is, if global.payload is equal to value 1 then follow this flow, if it is equal to value 0 then follow this one.
I'm just a little confused with the syntax for the function statement. Any help gratefully appreciated.
Since you haven't accepted the current answer, thought I'd give this a try.
I think this is what you need to handle inputs from two separate global contexts. I'm simulating them here with two separate inject nodes to demonstrate:
The checkconf inject node emits a 1 or a 0. Same for the meshstatus node. Substitute your real inputs for those inject nodes. The real work is done inside the function:
var c = context.get('c') || 0; // initialize variables
var m = context.get('m') || 0;
if (msg.topic == "checkconf") // update context based on topic of input
{
c = {payload: msg.payload};
context.set("c", c); // save last value in local context
}
if (msg.topic == 'meshstatus') // same here
{
m = {payload: msg.payload};
context.set('m', m); // save last value in local context
}
// now do the test to see if both inputs are triggered...
if (m.payload == 1) // check last value of meshstatus first
{
if (c.payload == 1) // now check last value of checkconf
return {topic:'value', payload: "YES"};
}
else
return {topic:'value', payload: "NO"};
Be sure to set the "topic" property of whatever you use as inputs so the if statements can discriminate between the two input. Good luck!
You can use the Switch node to do this, rather than a Function node.

how to update html page only when database changes

JS, which returns the first name:
$.ajax({
url: 'exm1.php',
type: 'get',
dataType: 'json',
success: function(data){
if( t<data.length){
for(var i=0;i<data.length;i++){
$("#output").html(data[i].fn);
}
}
},
error: function() {
// TODO: do error handling here
console.log('An error has occurred while fetching lat/lon.');
}
});
There are two things which I would like to sort out first:
It always overwrites the output div tag. I don't know how I can prevent this
setInterval should only run when there is a change in the database, or maybe when data.length changes, is there any way I could store the previous value of data.length and then compare with new data.length
If #output contains the first name, then you can target and save it into a variable on load
Example:
var firstname = $('#output').html();
And then all you have to do is compare the AJAX output with the variable.
Example:
if ( firstname != data )
$('#output').html(firstname);
To prevent overwriting your div tag, use the following.
$("#output").append(data[i].fn);
I don't understand what you mean by setinterval, please clarify in your code.
You can keep the html inside of the div by using this (vanilla javascript)
var inTheOutPutDiv = document.getElementById("output");
inputTheOutPutDiv.html = inputTheOutPutDiv.html + data; // whatever
// or use inputTheOutPutDiv.html+= data;
but sense you are using Jquery you can use append
$("#output").append(data);
You can instantiate a variable outside of your function that holds the length of your database query (scope). Or you can return a database value that says if the info has been updated (a value of 1 if changed, a value of 0 if the same). Then use a conditional statement to perform an action on the response. You have many options.
Unfortunately you can't call setInterval only when the data changes, but you can run something at a set interval and only update the #output html if the result is different.
The reason #output is getting overwritten is because calling $.html("...") will replace the existing html with the argument. I'd recommend simply putting what you want to remain static on the page in a different div.
<div id='oldOutput'>
Static Text Here: firstName?
<span id='output'></span>
</div>
Now your ajax return will overwrite the element and your static text will remain. However, you still have an issue in that every iteration of your loop will remove the string from before. If you want each loop to render cleanly, i'd recommend something along these lines:
success: function(data){
if( t<data.length){
// if you want to flush the #output each time
$("#output").html("");
for(var i=0;i<data.length;i++){
$("#output").append(data[i].fn);
// or you could put each entry in a div for later use
// $("#output").append("<div id='div_"+ i +"'>" + data[i].fn + "</div>");
}
}
}
In this case (you're flushing the div each time) then if the data is the same you shouldn't notice it update. If you don't flush the div then the data will keep appending on to the end.
If this is what you want, but only when there is new data, then you can count the number of child divs in the #output span - if you wrap each item from the output in a div as I have above, you should be able to compare this to data.length:
success: function(data){
if (t<data.length){
// gets direction descendants of #output that are divs,
// and counts the length of the array returned
var numItems = $("#output > div").length;
if(numItems != data.length){
loop goes here..
}
}
This will sort of work, it will only update when the number of items returned is different to the number of items you already have. However, if your script is returning a set of data and you need to filter it to see what's old and what's new, it's slightly different. You'll need to do include some sort of identifier (other than the data itself) to embed into the to check if it exists later.
if ( $("[data-id=" + data[i].identifier +"]").length > 0 ) {
// already exists, do nothing
} else {
$("#output").append("<div data-id='"+ data[i].identifier +"' etc...
}

Is there a way I can check to see if the "go to page based on answer" is checked in Google Forms

When using .setChoices() It looks for an array of created choices. If it is a multiple choice question it can be formatted either createChoice(value) or createChoice(value, navigationType).
When looking at a choice you put in something like: var cPage = chkItem.getChoices()[j].getGotoPage();
This will produce either the page object or a Null value.
The problem is when a question is set to "go to page based on answer" and they have not set a page for every entry, and left the default to continue it also reads it (in my case cPage), as null. This means if I want to go through each choice to capture it, modify it, then push it back out to the question, it my ending Array that I push out consists of both Null and Objects, which produces an error.
My workaround for questions without pages is for the script to forcibly change the question to handle page navigation but set everyone to CONTINUE.
I would like to find a way to check if the question has "go to page based on answer" checked and if not then be able to create choices using just the value.
Figured it out: I need to check the first choice if it has a PageNavigationType(). If it does then the box is checked. If null then it is not checked. I have not written the code to fully test this but the theory should work. (I was checking against just the goToPage type which is why it didn't work before. )
var choicecount = chkItem.getChoices().length-1;
var hasNavType = chkItem.getChoices()[0].getPageNavigationType(); //if null
for (var j = 0; j <= choicecount; ++j) {
var cValue = chkItem.getChoices()[j].getValue();
var cPage = chkItem.getChoices()[j].getGotoPage();
var cNav = chkItem.getChoices()[j].getPageNavigationType();
// Logger.log(j+" "+cPage);
if (cValue != reValues[0]){
if (hasNavType == null){
newChoices.push(chkItem.createChoice(cValue));
} else {
if (cPage == null){
newChoices.push(chkItem.createChoice(cValue,cNav));
} else {
newChoices.push(chkItem.createChoice(cValue,cPage));
}
}
}
}