Sharepoint Multiline Values on Load - javascript

I am trying to capture all of the fields of a SharePoint form using JavaScript during the initial load of the edit form. Somehow, only the first two multi-line fields are being captured properly. When I change the order of the fields this works. For example:
Field 1
Field 2
Field 3
Fields 1 & 2 are captured.
If I change the order they appear:
Field 2
Field 3
Field 1
Fields 2 & 3 are captured.
This doesn't appear to be affected by the code itself, but something to do with loading in SharePoint. But here is the code anyways:
$(window).load(function()
{
if(document.readyState === 'complete')
{
getFields();
getValues();
var myStatement = $("textarea[Title='Problem Statement']").closest("span").find("iframe[Title='Rich Text Editor']").contents().text();
var myScope = $("textarea[Title='Scope']").closest("span").find("iframe[Title='Rich Text Editor']").contents().text();
}
});
I am also using SPUtility to capture values and am having difficult getting support elsewhere.
SPUtility code:
scope = SPUtility.GetSPField('Scope');
scopeVal = scope.GetValue();
All of the fields are being captured per the above code in getFields() and getValues() called in the above code during window.load.
Any help or advice is appreciated.

Every field is embedded in a <td> that contains the following HTML comment:
<!-- FieldName="Field1"
FieldInternalName="Field1"
FieldType="SPFieldNote"
-->
While you can't explicitly query the DOM for this comment, you can query to get all the fields on the form and then loop through them, checking their innerHTML property to see if they contain the desired comment string.
By this means, you can determine the exact parent <td> element for any desired field. This allows you to be more precise than by using jQuery's .closest() function.
The other thing to keep in mind is that enhanced rich text, rich text, and plain text multiline fields are each rendered differently, and will thus require different query selector parameters to obtain their values.
The following example code gets the values of multiline fields with internal names of Field1, Field2, or Field3, and stores the values in an object.
var richTextFieldNames = ["Field1","Field2","Field3"];
var richTextFieldValues = {};
var fieldRows = document.querySelectorAll(".ms-formtable td.ms-formbody");
for(var i = 0, len = richTextFieldNames.length; i<len; i++){
currentFieldName = richTextFieldNames[i];
for(var j = 0, jlen = fieldRows.length; j<jlen; j++){
var currentRow = fieldRows[j];
if(currentRow.innerHTML.indexOf("FieldInternalName=\""+currentFieldName+"\"") > -1){
var element = currentRow.querySelector(".ms-rtestate-field[role=\"textbox\"]"); // enhanced rich text
if(!element){
var iframe = currentRow.querySelector("iframe[title=\"Rich Text Editor\"]");
if(iframe){
element = iframe.contentDocument.querySelector("body"); // rich text
}else{
element = currentRow.querySelector("textarea"); // plain text
}
}
richTextFieldValues[currentFieldName] = element.innerText ? element.innerText : element.textContent;
break;
}
}
}
The values can then be accessed as properties of the richTextFieldValues object by using array or dot notation, e.g. var myScope = richTextFieldValues["Scope"]; or richTextFieldValues.Scope; (assuming Scope is the internal name of a multi-line field).

Related

I used js to create my command syntax, now how can I use it?

I have a Google Sheet with .gs script that is successfully generating dynamicnewRichTextValue() parameters which are meant to be injected into a Sheet cell that will contain multiple lines of text each with their own URL. I do not know all of the parameters in advance (might be one text and one link, or two each, or more) which is why I am dynamically generating the parameters.
Let's say the end-state should be this (in this case there are only two line items, but there could be more or less:
var RichTextValue=SpreadsheetApp.newRichTextValue()
.setText("mailto:fred#abcdef.com,mailto:jim#abcdef.com")
.setLinkUrl(0,6,"mailto:fred#abcdef.com")
.setLinkUrl(7,19,"mailto:jim#abcdef.com")
.build();
In my script I don't know how many "setText" parameters or "setLinkUrl" statements I will need to generate, so I am doing it dynamically.
This is simple to handle for "setText" because I can just pass a single variable constructed during an earlier loop that builds the "setText" parameters. Let's call that variable setTextContent, and it works like this:
var RichTextValue=SpreadsheetApp.newRichTextValue()
.setText(setTextContent)
So up to this point, everything is great. The problem is that I have another variable that generates the URL portion of the newrichtextvalue() parameters up to the ".build();" statement. So let's call that variable setUrlContent and it is built in an earlier loop and contains the string for the rest of the statement:
.setLinkURL(0,22,"mailto:fred#abcdef.com").setLinkURL(23,44,"mailto:jim#abcdef.com")
I am stumped trying to figure out how to attach it to the earlier bit. I feel like this is something simple I am forgetting. But I can't find it after much research. How do I hook up setUrlContent to the code above so that the command executes? I want to attach the bits above and get back to assigning it all to a variable I can put into a cell:
var emailCell=SpreadsheetApp.newRichTextValue()
.setText("mailto:fred#abcdef.com,mailto:jim#abcdef.com") // I can dynamically create up to here
.setLinkUrl(0,6,"mailto:fred#abcdef.com") // ...but these last couple lines are
.setLinkUrl(7,19,"mailto:jim#abcdef.com") // stuck in a string variable.
.build();
sheet.getRange(1,1,1,1).setRichTextValue(emailCell)
Thanks!
I believe your goal and situation as follows.
You want to use your script by dynamically changing the number of emails.
Modification points:
When your following script is run, I think that the links are reflected to mailto and fred#abcdef..
var emailCell=SpreadsheetApp.newRichTextValue()
.setText("mailto:fred#abcdef.com,mailto:jim#abcdef.com")
.setLinkUrl(0,6,"mailto:fred#abcdef.com")
.setLinkUrl(7,19,"mailto:jim#abcdef.com")
.build();
sheet.getRange(1,1,1,1).setRichTextValue(emailCell)
I thought that you might have wanted the linked email addresses like below.
fred#abcdef.com has the link of mailto:fred#abcdef.com.
jim#abcdef.com has the link of mailto:jim#abcdef.com.
In this answer, I would like to propose the modified script for above direction.
Modified script:
var inputText = "mailto:fred#abcdef.com,mailto:jim#abcdef.com"; // This is your sample text value.
var ar = inputText.split(",").map(e => {
var v = e.trim();
return [v.split(":")[1], v];
});
var text = ar.map(([e]) => e).join(",");
var emailCell = SpreadsheetApp.newRichTextValue().setText(text);
var start = 0;
ar.forEach(([t, u], i) => {
var len = t.length;
emailCell.setLinkUrl(start, start + len, u);
start += len + 1;
});
SpreadsheetApp.getActiveSheet().getRange(1,1,1,1).setRichTextValue(emailCell.build());
In this modification, inputText is splitted to the hyperlink and the text (for example, when your sample value is used, it's fred#abcdef.com and mailto:fred#abcdef.com.), and the text including the hyperlink are put to the cell.
In this case, for example, even when var inputText = "mailto:fred#abcdef.com,mailto:jim#abcdef.com" is modified to var inputText = "mailto:fred#abcdef.com" and var inputText = "mailto:fred#abcdef.com,mailto:jim#abcdef.com,mailto:sample#abcdef.com", each hyperlink are reflected to each text.
Note:
When you want to the hyperlink of mailto:fred#abcdef.com to the text of mailto:fred#abcdef.com, you can also use the following modified script.
var inputText = "mailto:fred#abcdef.com,mailto:jim#abcdef.com"; // This is your sample text value.
var ar = inputText.split(",").map(e => e.trim());
var emailCell = SpreadsheetApp.newRichTextValue().setText(inputText);
var start = 0;
ar.forEach((t, i) => {
var len = t.length;
emailCell.setLinkUrl(start, start + len, t);
start += len + 1;
});
SpreadsheetApp.getActiveSheet().getRange(1,1,1,1).setRichTextValue(emailCell.build());
References:
newRichTextValue()
Class RichTextValueBuilder
Class RichTextValue

How to add IF/ELSE function in data validation in-cell dropdown function?

I'm trying to create an in-cell data validation drop down menu (in google spreadsheet) by using If/Else function.
Actually, I've 2 range of lists from which I only want to show one decided by a variable cell.
It sounds like this: If A1 is ABC then show range 1 in-cell dropdown & if A1 is XYZ then show range 2 in-cell dropdown.
Here's the code. Let me know what I'm doing wrong.
function dataval (){
var cell = SpreadsheetApp.getActive().getRange('B12');
if (cell=="Price") {
var cell = SpreadsheetApp.getActive().getRange('C12');
var range = SpreadsheetApp.getActive().getRange('D12:D19');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(range).build();
cell.setDataValidation(rule);
};
if (cell=="Technical") {
var cell = SpreadsheetApp.getActive().getRange('C12');
var range = SpreadsheetApp.getActive().getRange('E12:E19');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(range).build();
cell.setDataValidation(rule);
};
};
P.S.: I'm new to coding & this is my first ever ever post on SOF.
A problem with your code is the comparison cell=="Price". The variable cell is an object of class Range, not a string. To get the value it contains, you need cell.getValue(), so the comparison would be cell.getValue() == "Price".
You should also handle the event of the content of B12 changing. A simple way is to put the data validation code in onEdit trigger, and check whether the edit was to B12: e.g.,
if (e.range.getA1Notation() == 'B12') {
// change data validation
}
If the goal was simply to have a rule that forces inputs to be in one of two ranges, depending on another cell, that can be done without a script, using a custom formula with if and index. But that wouldn't provide a dropdown of values.

contenteditable not working on dynamically generated elements

I am dynamically creating an unordered list and adding items to it on a click of a button. I append this to a section that has contenteditable attribute set true. However, I do not see it working. I did set the contenteditable attribute to true even for the list but I guess it is supposed to inherit that from the section it is appended to. Here is the code of what I am doing.
// create text input
var categoryInput = document.createElement('input')
// create button to add the text entered to a list
var btnAddToList = document.createElement('input');
btnAddToList.type ="button";
//create a section to add a list to
var section = document.createElement('section');
var ul=document.createElement('ul');
section.appendChild(ul);
section.contenteditable = "true";
ul.contenteditable = "true";
//create an event handler to add to the list
if (btnAddToList.addEventListener) { btnAddToList.addEventListener('click', function () { addToList(ul, categoryInput.value);});
} else if (btnAddToList.attachEvent) {
btnAddToList.addEvent('click', function () { addToList(ul, categoryInput.value);});
Here is the function I call
function addToList(unorderedlist, inputText) {
if(inputText.length == 0) {
alert("Add Text");
return;
}
var listitem = document.createElement('li');
var listvalue = document.createTextNode(inputText);
listitem.appendChild(listvalue);
unorderedlist.appendChild(listitem);
}
What am I doing wrong or not doing? Any help appreciated. Thanks
The property is contentEditable (note upper-case 'E'), not contenteditable.
section.contentEditable = "true";
You need to set the attribute, not the property:
section.setAttribute('contenteditable', 'true');
Instead of
section.contenteditable = "true";
Some more info here and here (in the context of jQuery, but covers the topic splendidly nonetheless).
My current understanding of the difference is that attributes are the things you can set through markup (id, class, contenteditable, etc.), whereas properties are the properties of the actual javascript objects representing the DOM nodes. As the linked article mentions, the two are often kept in sync by the browser, but not always.
Edit:
As Tim Down states in his answer, while the above works (setting the attribute), the actual problem is that the name of the property is cased wrong. It should be
section.contentEditable = "true"; //Note the upper case 'E'
The reason setting the attribute works, is that attributes are case-insensitive.

jQuery navigating XML parent child nodes and selecting appropriate attributes from each?

I am creating a templating system which can be interpreted at client side with Javascript to construct a fill in the blanks form e.g. for a letter to a customer etc.
I have the template constructed and the logic set out in pseudo code, however my unfamiliarity with jQuery I could use some direction to get me started.
The basic idea is there is a markup in my text node that denotes a field e.g. ${prologue} this is then added to an array called "fields" which will then be used to search for corresponding node names in the xml.
XML
<?xml version="1.0" encoding="UTF-8"?>
<message>
<text>${Prologue} - Dear ${Title} ${Surname}. This is a message from FUBAR. An engineer called but was unable to gain access, a new appointment has been made for ${ProductName} with order number ${VOLNumber}, on ${AppointmentDate} between ${AppointmentSlot}.
Please ensure you are available at your premises for the engineer. If this is not convenient, go to fubar.com or call 124125121515 before 12:00 noon the day before your appointment. Please refer to your order confirmation for details on what will happen on the day. ${Epilogue} - Free text field for advisor input<
</text>
<inputTypes>
<textBox type="text" fixed="n" size="100" alt="Enter a value">
<Prologue size="200" value="BT ENG Appt Reschedule 254159" alt="Prologue field"></Prologue>
<Surname value="Hoskins"></Surname>
<ProductName value=""></ProductName>
<VOLNumber size="8" value="" ></VOLNumber>
<Epilogue value=""></Epilogue>
</textBox>
<date type="datePicker" fixed="n" size="8" alt="Select a suitable appointment date">
<AppointmentDate></AppointmentDate>
</date>
<select type="select" >
<Title alt="Select the customers title">
<values>
<Mr selected="true">Mr</Mr>
<Miss>Miss</Miss>
<Mrs>Mrs</Mrs>
<Dr>Dr</Dr>
<Sir>Sir</Sir>
</values>
</Title>
<AppointmentSlot alt="Select the appointment slot">
<values>
<Morning>9:30am - 12:00pm</Morning>
<Afternoon>1:00pm - 5:00pm</Afternoon>
<Evening>6:00pm - 9:00pm</Evening>
</values>
</AppointmentSlot>
</select>
</inputTypes>
</message>
Pseudocode
Get list of tags from text node and build array called "fields"
For each item in "fields" array:
Find node in xml that equals array item's name
Get attributes of that node
Jump to parent node
Get attributes of parent node
If attributes of parent node != child node then ignore
Else add the parent attributes to the result
Build html for field using all the data gathered from above
Addendums
Is this logic ok, is it possible to start at the parent of the node and navigate downwards instead?
Also with regards to inheritence could we get the parent attributes and if the child attributes are different then add them to the result? What about if the number of attributes in the parent does not equal the number in the child?
Please do not provide fully coded solutions, just a little teasers to get me started.
Here is what I have so far which is extracting the tags from text node
//get value of node "text" in xml
var start = $(xml).find("text").text().indexOf('$');
var end = $(xml).find("text").text().indexOf('}');
var tag = "";
var inputType;
// find all tags and add them to a tag array
while (start >= 0)
{
//console.log("Reach In Loop " + start)
tag = theLetter.slice(start + 2, end);
tagArray.push(tag);
tagReplaceArray.push(theLetter.slice(start, end + 1));
start = theLetter.indexOf('$', start + 1);
end = theLetter.indexOf('}', end + 1);
}
Any other recommendations or links to similar problems would be welcome.
Thankyou!
I am using a similar technique to do html templating.
Instead of working with elements, I find it easier to work with a string and then convert it to html. In your case with jQuery, you could do something similar:
Have your xml as a string:
var xmlString='<?xml version="1.0" encoding="UTF-8"?><message><text>${Prologue} - Dear ${Title} ${Surname}... ';
Iterate through the string to do the replacements with a regex ($1 is the captured placeholder, for example Surname):
xmlString.replace(/$\{([^}]+)}/g,function($0,$1)...}
Convert to nodes if needed:
var xml=$(xmlString);
The benefits of the regex:
faster (just a string, you're not walking the DOM)
global replace (for example if Surname appears several times), just loop through your object properties once
simple regex /${([^}]+)}/ to target the placeholder
Get list of tags from text node and build array called "fields"
To create the array I would rather user regular expression, this is one of the best use for it (in my opinion) because we are indeed searching for a pattern :
var reg = /\$\{(\w+)\}/gm;
var i = 0;
var fields = new Array();
while ( (m = reg.exec(txt)) !== null)
{
fields[i++] = m[1];
}
For each item in "fields" array
jQuery offers some utility functions :
To iterate through your fields you could do this : $.each(fields, function(index, value){});
Navigating through the nodes and retrieving the values
Just use the jQuery function like you are already doing.
Building the HTML
I would create templates objects for each types you would take in charge (in this example : Text, Select)
Then using said templates you could replace the tokens with the HTML of your templates.
Displaying the HTML
Last step would be to parse the result string and append it at the right place:
var ResultForm = $.parseHTML(txt);
$("#DisplayDiv").append(ResultForm);
Conclusion
Like you asked, I did not prepare anything that works right out of the box, I hope it will help you prepare your own answer. (And then I hope you will share it with the community)
This is just a framework to get you going, like you asked.
first concept is using a regex to just find all matches of ${ }. it returns an array like ["${one}","${t w 0 }","${ three}"].
second concept is a htmlGenerator json object mapping "inputTypes-->childname" to a function responsible for the html print out.
third is not to forget about natural javascript. .localname will give you the xml element's name, and node.attributes should give you a namedNodeMap back (remember not to perform natural javascript against the jquery object, make sure you're referencing the node element jQuery found for you).
the actual flow is simple.
find all the '${}'tokens and store the result in an array.
find all the tokens in the xml document and using their parents info, store the html in an map of {"${one}":"<input type='text' .../>","${two}":"<select><option value='hello'>world!</option></select>" ...}
iterate through the map and replace every token in the source text with the html you want.
javascript
var $xmlDoc = $(xml); //store the xml document
var tokenSource =$xmlDoc.find("message text").text();
var tokenizer=/${[^}]+/g; //used to find replacement locations
var htmlGenerators = {
"textBox":function(name,$elementParent){
//default javascript .attributes returns a namedNodeMap, I think jquery can handle it, otherwise parse the .attributes return into an array or json obj first.
var parentAttributes = ($elementParent[0] && $elementParent.attributes)?$elementParent.attributes:null;
//this may be not enough null check work, but you get the idea
var specificAttributes =$elementParent.find(name)[0].attributes;
var combinedAttributes = {};
if(parentAttributes && specificAttributes){
//extend or overwrite the contents of the first obj with contents from 2nd, then 3rd, ... then nth [$.extend()](http://api.jquery.com/jQuery.extend/)
$.extend(combinedAttributes,parentAttributes,specificAttributes);
}
return $("<input>",combinedAttributes);
},
"date":function(name,$elementParent){
//whatever you want to do for a 'date' text input
},
"select":function(name,$elementParent){
//put in a default select box implementation, obviously you'll need to copy options attributes too in addition to their value / visible value.
}
};
var html={};
var tokens = tokenSource.match(tokenizer); //pull out each ${elementKey}
for(index in tokens){
var elementKey = tokens[index].replace("${","").replace("}"),"");//chomp${,}
var $elementParent = $xmlDoc.find(elementKey).parent();//we need parent attributes. javascript .localname should have the element name of your xml node, in this case "textBox","date" or "select". might need a [0].localname....
var elementFunction = ($elementParent.localname)?htmlGenerators[elementParent.localname]:null; //lookup the html generator function
if(elementFunction != null){ //make sure we found one
html[tokens[index]] = elementFunction(elementKey,elementParent);//store the result
}
}
for(index in html){
//for every html result, replace it's token
tokenSource = tokenSource.replace(index,html[index]);
}

Building an xml string with the form elements in javascript

I'm working in building an xml string with the element values in a HTML form with java script. I'm able to get all the values of the form and build an xml easily with the below code. But the problem is there are a few repeating groups which makes the process hard. Means that, the repeating groups are FIELD SETS and for example if there is a field set named xxxRepeatingGroup and yyyRepeatingGroup, the xml should be as follows.
<xxxRepeatingGroup>
<xxx>
all the element values inside the field set with the name of the element as tag name and the value in between the tags here
</xxx>
<xxx>
all the element values inside the next field set(the repeating thing with different values) here
</xxx>
</xxxRepeatingGroup>
<yyyRepeatingGroup>
<yyy>
all the element values inside the field set with the name of the element as tag name and the value in between the tags here
</yyy>
<yyy>
all the element values inside the next field set(the repeating thing with different values) here
</yyy>
</yyyRepeatingGroup>
I'm not able to find a logic to add the <xxxRepeatingGroup>, <yyyRepeatingGroup>, <xxx> and <yyy> tags and I will not be able to hard code these tags as I need to use the same method in all the places.
Below is my java script function:
function getElementnames() {
var msg = "";
for (j=0;j < document.forms.mf.elements.length;j++) {
if (document.forms.mf.elements[j].type != 'button' && document.forms.mf.elements[j].type != 'submit' && document.forms.mf.elements[j].type == undefined )
{
msg += startElement(document.forms.mf.elements[j].name ) + document.forms.mf.elements[j].value + endElement(document.forms.mf.elements[j].name );
}
}
alert(msg);
}
function startElement(startname) {
var startname="<"+ startname +">";
return startname;
}
function endElement(endname) {
var endname ="</"+ endname +">";
return endname;
}
Please help...
I found out a way to do this by adding the element which I require as the tag name with type hidden and place it where ever I need the repeating group tags.

Categories

Resources