Calculate on the Fly While Still Allowing Input - Javascript API - javascript

I have a Mysql table called invoice_detais and I am using a PHP code generator PHPRUNER to retrieve, add, and edit data from my database. In the page that is used for adding a new record in invoice_details, I set the following Javascript event:
var ctrlPrice = Runner.getControl(pageid, 'Price');
var ctrlQuantity = Runner.getControl(pageid, 'Quantity');
var ctrlTotal = Runner.getControl(pageid, 'Total');
function func() {
ctrlTotal.setValue(Number(ctrlPrice.getValue()) - Number(ctrlQuantity.getValue()));
};
ctrlPrice.on('keyup', func);
ctrlQuantity.on('change', func);
The quantity is calculated on fly, but I can not edit the field, and sometimes I need it to be inserted manually without calculation. I mean to set this field to the auto-calculated value but also allow free input.
Please help.

Related

How Stop Formula from Using Updated Data? (Google Sheets)

I have a vlookup formula looking for names and returning the average for that person. I record these averages throughout the week, and identify them as W1, W2, W3, etc. in different columns. The problem is, the data I import only has ONE average on it, meaning that if I import it, it will override what I had already put in a few days ago using the same report, just an older version.
My question is, is there a way for me to stop the formula for W1 from updating when the data it is calling on changes? Essentially, to freeze the values? That way I don't have to keep adding new tabs to import the new data in an effort to save the history. The data is robust, and I am gathering more info than just averages, so I need the whole thing.
I wouldn't mind scripting something too that would help solve my issue, I just need some guidance.
Edit: thoughts on scripting a menu button that would copy, paste values only of a selected range that I could trigger right before I update the data?
I want to be able to select a range in my sheet, select the menu "Save Info", have it copy what I selected, and paste it right back where it already was. This will remove the formulas that would otherwise cause my values to change upon the new data import, and leave my history intact.
The following is what I have come up with so far, but I receive this error:
"TypeError: sourceSheet.getDataRange is not a function".
function saveInfo(){
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = spreadSheet.getActiveSheet
var sourceRange = sourceSheet.getDataRange();
var targetSheet = spreadSheet.getActiveSheet();
sourceRange.copyTo(targetSheet.getActiveRange());
}
function updateMenu() {
SpreadsheetApp.getActiveSpreadsheet().updateMenu('Save Info', generateMenu())
};
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().addMenu('Save Info', generateMenu());
};
function generateMenu() {
var entries = [{
name: "Save Data Before Update",
functionName: "saveInfo"
}];
return entries;
}
Any ideas??
Thanks!
Here you go:
function saveInfo() {
var range = SpreadsheetApp.getActiveSheet().getActiveRange();
var values = range.getDisplayValues();
range.setValues(values);
}
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Save Info')
.addItem('Save Data Before Update', 'saveInfo')
.addToUi();
}
The script converts data inside selected cells into a plain text.
But actually you don't need any script. You can just select the cells, press Ctrl+C and Ctrl+Shift+V.

Storing (or passing) an Element/TableCell reference during a server-client round-trip

I'm trying to make a modal popup that does some work on a given table cell within a Google Doc using HTMLService and GAS.
The popup works and in general I can pass data from the server script to the client script and back. The piece of data I can't seem to pass is which table cell the cursor was at when the popup was opened.
Approach 1: If I pass it directly to client and back, the reference is broken, because it's null by the time it gets to the client. I can pass the cell's contents just fine in the exact same context, so strings work, it's just the fact that it's a cell reference. (Makes sense enough.)
Approach 2: If I store the TableCell reference in a global variable on the server side, I get Cannot call method "getText" of undefined--the reference is undefined after the first round trip. (I guess the server script gets reloaded entirely in that time.)
Approach 3: If I use CacheService.getUserCache() on the client side, when I try to get my cached TableCell object, it seems disconnected from the one I had set, because I get the heretofore unGoogleable error Cannot find function getText in object TableCell.
// server, before popup is opened:
cache.put('cell_currently_being_edited', active_doc.getCursor().getElement().getParent());
// popup client calls server function like so:
google.script.run.withSuccessHandler(load_content).get_starting_content();
// server, where the above error occurs:
function get_starting_content() {
var cell_currently_being_edited = cache.get('cell_currently_being_edited');
return cell_currently_being_edited.getText();
}
If cells had some sort of fixed ID value I could pass, that could work...
I would do it based on the text in the cell, but I hope that's not the only option, because cell text in general (and yes, in this specific context too) may not be unique, so after the round trip I may end up replacing text in some other cell than the cursor was in when the user activates the popup.
Solution:
You could use the cell index as unique identifier to pass the RC string back and forth between client and server as string values are legal.
Use Properties/Cache Service to store RC Index information as string for future retrieval in Server, If needed.
Sample Script:
function getRCTIndexOfTable(tableCell) {
var tableRow = tableCell.getParentRow();
var table = tableRow.getParentTable();
var column = tableRow.getChildIndex(tableCell);
var row = table.getChildIndex(tableRow);
var body = table.getParent().asBody();
var tableId = body.getChildIndex(table);
return 'R' + row + 'C' + column + 'T' + tableId;
}
function main() {
var active_doc = DocumentApp.getActiveDocument();
var activeTableCell = active_doc
.getCursor()
.getElement()
.getParent()
.asTableCell();
Logger.log(getRCTIndexOfTable(activeTableCell));
}
References:
TableCell
TableCell from RCIndex
Legal Values
PropertiesService
CacheService
Since the other answer (even after modifying the code) focused on the "saving" half of the problem, I thought I would share code that includes how to load the reference afterwards to complete the round trip:
// save
var table_cell = active_doc.getCursor().getElement().getParent();
var table_row = table_cell.getParentRow();
var table = table_row.getParentTable();
var column_index = table_row.getChildIndex(table_cell);
var table_index = active_doc.getBody().getChildIndex(table);
var row_index = table.getChildIndex(table_row);
cache.put('table_currently_being_edited', table_index);
cache.put('row_currently_being_edited', row_index);
cache.put('column_currently_being_edited', column_index);
// load
function get_cell_currently_being_edited() {
var table_index = cache.get('table_currently_being_edited');
var row_index = cache.get('row_currently_being_edited');
var column_index = cache.get('column_currently_being_edited');
return active_doc.getBody().getChild(table_index).getCell(row_index, column_index);
}

Refresh to repeat control not working normal after deleting 1 row

I have a field named "selectedTime" in a document, this fields stores the selected timings added by user.Adding times is working perfect.This is back-end.
Now I will explain this issue of selecting date from front end.I have given a button add to add times.The custom control of date-time gets added to repeat control on click of Add button.Even if I check in document it shows the list of selected times.Even this works fine.
Now if I want to delete a selected time from repeat control randomly, it deleted that particular record from document, but on the page the last record of the repeat gets disappears,
I was assuming that this is the issue with partial refresh of repeat control,I have even tried that but no result.Full refresh breaks the page.
java script code for the delete button
`var doc:NotesDocument = database.getDocumentByUNID(context.getUrlParameter("refId"))
var selectedTimes:java.util.Vector = doc.getItemValue("selectedTimes");
if(selectedTimes != null){
var sdtString = getComponent("inputHidden1").getValue();
if(selectedTimes.contains(sdtString))
selectedTimes.remove(sdtString);
doc.replaceItemValue("selectedTimes",selectedTimes);
doc.save();
};
var url:XSPUrl = context.getUrl();
view.postScript("window.refresh('"+url+"')");`
I know it is difficult to understand what i want to explain but any suggestion on this will be appreciated.
Even if anybody have any idea to delete the a field values of a documents,In my case field name is "selectedTimes" and the values are added times in repeat control, Please share.
Edit 1:
//Repeat Control
var doc:NotesDocument = database.getDocumentByUNID(context.getUrlParameter("refId"))
var selectedTimes:java.util.Vector = doc.getItemValue("selectedTimes");
return selectedTimes;
Another try could be link the repeat with a viewScope instead of the document:
1) In the event beforeLoadPage/afterLoadPage: Get the value from the document, and put it in a viewScope variable:
// beforeLoadPage event:
// ... get the doc
viewScope.selectedTimes = doc.getItemValue("selectedTimes");
2) In the repeat control, use the viewScope:
<xp:repeat value="#{viewScope.selectedTimes}"...
3) When an update is done, update both the viewScope and the document:
//...update the View Scope variable and get the document:
doc.replaceItemValue("selectedTimes", viewScope.selectedTimes);
This could be a hint if the document would be added as DataSource:
Do you have the document included in the XPage as a DataSource? In that case, try to get and update the NotesXspDocument instead of the Document from the DB:
XPage:
<xp:this.data>
<xp:dominoDocument var="xspDocument"
action="editDocument"
documentId="#{param.unid}">
</xp:dominoDocument>
</xp:this.data>
SSJS code: work directly with the XspDocument
var selectedTimes:java.util.Vector = xspDocument.getItemValue("selectedTimes");
...
xspDocument.replaceItemValue("selectedTimes", selectedTimes);
This could be a hint if the value would not be removed from the document:
In sdtString you are getting a String value:
var sdtString = getComponent("inputHidden1").getValue();
If you have the time values stored as NotesDateTimes, you will get this type of value inside the Vector and the remove method won't find the String and nothing will be removed.
// In a Vector<NotesDateTime> the String cannot be found:
selectedTimes.remove(sdtString);
Be sure you remove the same type of value you get in the Vector

Task "Regarding" field not populating from subgrid in CRM 2013

I am using CRM 2013 on-premise with UR1 installed
I have a custom entity with a subgrid on it looking at related "tasks" which looks like this:
Whenever I create a task from the subgrid using the "+" button in the top right hand corner of the subgrid; the "Regarding" field of the newly created task remains blank. When it should be populated by a lookup to the record it was created from.
I have javascript on the task entity which checks the "Regarding" field to check what kind of entity it was created from (if it was created from one) and gets certain field values from the calling entity to populate fields on the task.
Since the "Regarding" field is never filled the Javascript never fires - and the fields do not populate.
When the record is saved, if the regarding field is blank (I have not manually filled it in) - it will eventually be populated by the correct record about 10 - 15 seconds later if you refresh the page. Then the correct fields will be populated and the user is able to edit the option set values and save again. This is not ideal for the user as they would like it to be one fluid action.
Is there any way around this problem?
EDIT for future browsers of this question:
Found a partial work around. If you use an "Activity" subgrid rather than a "Task" subgrid the field will populate. This has a drawback though as you cannot edit the "Activity" subgrid's view to show "Task" specific fields.
Ran into this same issue. The way I got around it was to add a look-up to the custom entity on the form (we put this on a hidden tab). When the Task gets created from the custom entity the look-up will be populated. You can then use that look-up to grab the values that you need to populate, including the regarding field. Not the most elegant, but it works.
I also ran into this problem and went with a pure JS approach to resolving. On load of the task form, call populateRegarding().
This works because even though the regarding lookup doesn't populate by default, the query string parameters include _CreateFromType and _CreateFromId values.
This works in 2015, didn't test on earlier versions. Note that it is unsupported.
function populateRegarding() {
var regarding = Xrm.Page.getAttribute("regardingobjectid"),
createFromType = Xrm.Page.context.getQueryStringParameters()._CreateFromType,
createFromId = Xrm.Page.context.getQueryStringParameters()._CreateFromId;
if (!createFromId || !createFromType ||
!regarding || regarding.getValue() !== null) {
return;
}
var entityLogicalName = getEntityLogicalNameFromObjectTypeCode(createFromType);
regarding.setValue([{
id: createFromId,
entityType: entityLogicalName,
name: "Hardcoded Name" // TODO: retrieve name dynamically
}]);
}
// This method uses an undocumented object and is therefore unsupported.
// You could implement a supported version of this function by querying for
// metadata, but that would be very expensive.
function getEntityLogicalNameFromObjectTypeCode(otc) {
var map = Mscrm.EntityPropUtil.EntityTypeName2CodeMap,
logicalName;
otc = Number(otc); // convert string to number
for (logicalName in map) {
if (!map.hasOwnProperty(logicalName)) { continue; }
if (map[logicalName] === otc) {
return logicalName;
}
}
}

Trying to clean out form inputs with jQuery so I can add it back into the form

I have a pretty simple HTML form where users can enter in information about a person. Below that form is a button which allows them to 'add more'. When clicked, the 'person' form is copied and appended to the page.
The way I used to do this was to take my HTML file, copy out the relevant section (the part that gets 'added more') and then save it into a variable in the Javascript. This became rather annoying when I had to make changes to the form as I would then have to make the same changes to the Javascript variable.
My new method is to create the variable dynamically in Javascript. When the page loads, I use jQuery to grab out the 'add more' part of the code and cache the HTML into a variable. Then when the 'add more' button is clicked, I append that cached HTML to the page.
The problem is with form inputs. The server-side code autofills the form with the user's data from the database. I want to cache that HTML data with no form inputs...
My current function looks like this:
function getHTML($obj, clean)
{
if (clean)
{
var $html = $obj.clone();
$html.find('input').each(function() { $(this)[0].value = ''; });
}
else
{
var $html = $obj;
}
var html = $html.wrap('<div></div>').parent()[0].innerHTML;
$html.unwrap();
return html;
}
It doesn't work. I'm also unsure if this is the best approach to solving the problem.
Any ideas?
I don't know why this wouldn't work. I can't see how the function is being called, or what is being passed to it.
I guess one thing I'd do differently would be to create a .clone() whether or not you're "cleaning" the inputs. Then you're not wrapping and unwrapping an element that is in the DOM. Just use the if() statement to decide whether or not to clean it.
Something like this:
function getHTML($obj, clean) {
var $clone = $obj.clone();
if (clean) {
$clone.find('input').each(function() { this.value = ''; });
}
return $clone.wrap('<div></div>').parent()[0].innerHTML;
}
Or a little more jQuery and less code:
function getHTML($obj) {
return $obj.clone().find('input').val('').end().wrap('<div/>').parent().html();
}
A little less efficient, but if it only runs once at the page load, then perhaps not a concern.
Or if it is going to be made into a jQuery object eventually anyway, why not just return that?
function getHTML($obj) {
return $obj.clone().find('input').val('').end();
}
Now you've returned a cleaned clone of the original that is ready to be inserted whenever you want.
EDIT:
Can't figure out right now why we can't get a new string.
Here's a function that will return the DOM elements. Beyond that, I'm stumped!
function getHTML($obj, clean) {
var $clone = $obj.clone();
if (clean) {
$clone.find('input').each(function() {
this.value = '';
});
}
return $clone.get(); // Return Array of DOM Elements
}
EDIT: Works now.
I ditched most of the jQuery, and used .setAttribute("value","") instead of this.value.
Give it a try:
function getHTML($obj, clean) {
var clone = $obj[0].cloneNode(true);
var inputs = clone.getElementsByTagName('input');
console.log(inputs);
for(var i = 0, len = inputs.length; i < len; i++) {
inputs[i].setAttribute('value','');
}
return $('<div></div>').append(clone)[0].innerHTML;
}
I would wrap the part of the form that needs to be cloned in a <fieldset>:
<form id="my_form">
<fieldset id="clone_1">
<input name="field_1_1">
<input name="field_2_1">
<input name="field_3_1">
</fieldset>
</form>
Add one more
Then for the jQuery script:
$("#fieldset_clone").click(function(event) {
// Get the number of current clones and set the new count ...
var cloneCount = parseInt($("fieldset[id^=clone_]").size());
var newCloneCount = cloneCount++;
// ... then create new clone based on the first fieldset ...
var newClone = $("#clone_1").clone();
// .. and do the cleanup, make sure it has
// unique IDs and name for server-side parsing
newClone.attr('id', 'clone_' + newCloneCount);
newClone.find("input[id^=clone_]").each(function() {
$(this).val('').attr('name', ($(this).attr('name').substr(0,7)) + newCloneCount);
});
// .. and finally insert it after the last fieldset
newClone.insertAfter("#clone_" + cloneCount);
event.preventDefault();
});
This would not only clone and clean the set of input fields, but it would also set new ID's and names so once the form is posted, their values would not be overwritten by the last set.
Also, in case you want to add the option of removing sets as well (one might add too many by mistake, or whatever other reason), having them wrapped in a <fieldset> that has an unique ID will help in accessing it and doing a .remove() on it.
Hope this helps.

Categories