Replay Javascript range styling to identical document in new session - javascript

I am trying to record user selections and replay them in a new session at a later point in time. I cannot figure out how to re-create the range(s) the user has selected in the new session.
Specifically, I display (inside a div with a known ID) a blob of HTML. The user makes a selection and clicks a button. This allows me to capture whatever information I want about the range. I am using Rangy to get the range information and capture the start and end nodes and offsets, the raw HTML, and the text property. I also style the selection at this point, which obviously changes the DOM. The user may make/store multiple selections. When they click 'save' I upload all the selection data to the server and store it.
At a later point in time, a different user looks at the same blob of HTML (also inside a div with a known ID). The are presented with the document with all user-applied highlighting shown.
Obviously in a new session the DOM objects are different, so storing the actual node data is meaningless. I also tried converting the path to the node as an XPath but I can't find a good way to then re-apply the XPath to find the target node in the new session. I thought of counting elements to find the start or end element, but the count obviously changes if later selections come before previous selections and this will throw off the retrieval without some hairy logic.
Is there a good way to remember and re-find a node in a DOM that could change slightly (and in predictable ways)?

If you are using Rangy 1.3 (still in beta at the time of this writing), you can preserve highlightings between sessions easily.
First initialize Rangy when your page loads:
var highlighter;
var highlightClass = "someCssClassName";
$(document).ready(function() {
highlighter = rangy.createHighlighter();
highlighter.addClassApplier(rangy.createCssClassApplier(highlightClass, {
ignoreWhiteSpace: true,
tagNames: ["span", "a"]
}));
});
To highlight the text that is selected with a :
highlighter.highlightSelection(highlightClass);
To serialize all the highlighted text in the current document:
var data = highlighter.serialize();
storeDataToExternalSourceAndReloadPage(data);
To later (or in another session) deserialize the data object and highlight the current document:
var data = loadDataFromExternalSource();
highlighter.deserialize(data);
Easy! Thanks Tim for an amazingly helpful library!

Related

How to access an entered Interactive Grid column value in a Javascript dynamic action on the Change event in order to ensure uniqueness

I am trying to prevent duplicate items from being entered in an Interactive Grid in Oracle Apex 20.2. I do get a unique constraint error when this happens, but this is for a barcode scanning stock control app and the unique constraint error only happens when saving after scanning a room with lots of objects. It is then very difficult to find the duplicate field. You also cannot use sort, since that wants to refresh the page and looses all your scanned items. I cannot presort because I want the last scanned item on top.
I was able to add Javascript on page load that creates an array with all the barcodes. I then check this array when scanning and do not add new Interactive Grid rows when a duplicate barcode is going to be added to the array.
In addition to this I need to add the same for when an Interactive Grid row is manually entered. For this I wanted to add a Javascript dynamic action on the barcode column in the Interactive Grid, in order to once again check the global array for uniqueness. However I have several issues: I cannot figure out how the get the entered barcode value in the change dynamic action Javascript, sometimes it shows the previous changed value (might be this bug although I am in 20.2) and the Change event also seems to fire twice when hitting enter after entering a value (once for the new row (this time my code works unlike when hitting Tab) and once for the next row below). The last one seems bad, since then it will try to check existing values (the next row) and give errors that should not happen; however I do not see a more appropriate event like On Row Submit. Not sure if there is a way to check whether the value changed on the Change event.
The code I currently have I got from here. I am assuming this means Oracle Apex does not have a standard way of getting an Interactive Grid column value in a Javascript dynamic action. Not sure if this has changed in 20.2 or 21. The code I have is:
console.log($(this.triggeringElement));
var grid = apex.region('LINES').widget().interactiveGrid('getViews', 'grid');
var model = grid.model;
var selectedRow = grid.view$.grid('getSelection');
var id = $(selectedRow[0][0]).data('id');
var record = model.getRecord(id);
let bcode = model.getValue(record, 'BARCODE');
console.log(id);
console.log(record);
console.log($(selectedRow[0][0]));
console.log(bcode);
if(barcodes.includes(bcode)) {
apex.message.showErrors([{
type: "error",
location: "page",
message: "The entered barcode is already in the list.",
unsafe: false
}]);
}
When I console.log(record) I can see values that I enter into the barcode column, but I do not know how to walk the object tree in order to retrieve the value out of the record. I do not understand the object it shows me in the console log. It does not seem to correlate with the dot access traversals that others are doing in the above code. I can see the record array at the top, but for that the barcode column shows the previous value; below that it does however show the newly entered value as the third (2) index, but I do not know how to write Javascript to access that.
If I can access the previous and new value from the record object I could check for changes and also compare the new value against the global array. How do I access these values in the record object or is there a better way of achieving my goal? bcode prints the previous value, so I guess I already have that if that is not a bug.

How to implement collaborative rich text editing with Google Drive Realtime API?

I'm developing a web application which uses the Google Drive Realtime API. The API is used to store the document's data and to synchronize all modifications between the active collaborators.
Now I want to add support for text boxes with rich text (only some basics like bold, underline and links) to this application. A text box should enable collaborative text editing similar to Google Docs. I searched and experimented for some days now but I can't find a proper solution in how to exchange the data or how to build a suitable data model that would work with the Drive Realtime API.
There are several ways that one can think of like exchanging HTML (or a similar markup) within a CollaborativeSting. But that wont work because it would probably break the markup sooner or later.
Another (probably much better) starting point is to use a more abstract data model as the Quill editor does. (I would like to use this editor later if it's possible, but that's no must have.)
The rich text model for "Hello! Here is a link." looks like this:
var doc = [
{ insert: "Hello!", attributes: { bold: true } },
{ insert: " Here is a " },
{ insert: "link.", attributes: { href: 'http://example.org' } }
];
I could transform the upper document example into "Hello! That's a link." with these instructions:
var operation = [
{ retain: 7 },
{ insert: "That's", attributes: { italic: true } },
{ delete: 1 }
];
But saving this model into a CollaborativeList seems to be no solution as well, if more collaborators are typing or formatting at the same time. Especially because I can't influence server side behavior.
Can someone think of a suitable model or data exchange process that would work with rich text? It does not have to be the best solution (if there is something in between). Exchanging plain text is unbelievable simple with this API but rich text seems to be impossible to me.
Thanks for any help!
Update
I'm able to precise my question with the new information Sheryl Simon provided me below. By using IndexReferences I’m now able to isolate the plain text from the format information.
I have added some code that saves the local text selection of the user (that can be a single position or a range) and restores it after a text change. This works fine. I could also add support for multiple text selections of a single user - because every user is only able to change the own selection(s).
But I can’t figure out a model where several users can simultaneously add and remove ranges of e.g. bold text. If I use a CollaborativeList for bold text with several [start, end] arrays in it, I'll get a broken data set if two users set an overlapping range at the same time or if two users want to edit the same range at the same time (by removing and reinserting ranges or by moving the range markers of an existing range).
The following is a bit of pseudo code. All indexes are stored as IndexReferences:
Model:
[ User1: makeBold([8,20])
[ 0, 10] => removeValue([0,10]), removeValue([15,36]), push([0,36])
[15, 36]
[77, 82] User2: removeBold([0,5])
] => removeValue([0,10]), push([6,10])
If both users start on the data set that is shown on the left and the actions of the first user are applied first, the second user is not able to remove [0,10] anymore (because it had been replaced) so the text stays bold and pushing [6,10] into the list leads into duplicate data. How can I prevent these problems?
Check out IndexReferences. This is what those are designed for. You basically track a marker for the start and end of the region that should be bold, italic, etc. The IndexReference will automatically move around if text is added before or within the region so it should behave logically.

Javascript/jQuery/html: How to automatize data retrieval from page source, when 'hard' refresh is involved?

IMPORTANT NOTE: I have totally rephrased/edited my original question, so please do not consider the first answer as irrelevant.
Here is the situation:
There is this web page of publications database, where the user selects different filters in order to search for certain articles, which meet specific criteria. In some cases, like when selecting Group of people or a particular person, the filtering is achieved from drop lists. When the user selects a group (from the 'Group' filter), then the corresponding list of people's names appear at the 'Person' selection drop list filter. Also when the value in the 'Group' drop list changes, the page is reloaded with the new drop list for the 'Person' filter. My aim is to retrieve and save the content of the 'Person' filter for each value in the 'Group' filter. And I would like to do so by using Javascript code in the Console window. However, I don't want to manually change the value of the 'Group' filter and then retrieve the 'Person' contents, but, in other words, to build an automated process within the Console window (this is the only way I know) to pick up the data I require.
Here is my problem:
Considering that my approach is not completely crazy, that's what I try to do at the Safari's Console (please let me know if there is another way):
// Since the jQuery is not loaded in the resources I call it by this:
var script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
// I locate the 'Group' Select List and I store the values of its options by this:
$(document).ready(function(){
//Cycle through all the groups
var abtOptValues = new Array();
$("[name='abt']>option.[value!='-1']").each(function(l){
abtOptValues[l] = $(this).attr("value");
});
// Now, with the values from the 'Group' Select List in an array I want to cycle through
// each one of them and get the respected values of the 'People' Select List. So I try this:
for (var abtOptValue in abtOptValues)
{
$("[name='abt']").val(abtOptValues[abtOptValue]); //set the option in
// the 'Group' List
var persOptValue = new Array();
$("[name='pers']>option.[value!='-1']").each(function(i){
persOptValue[i] = $(this).attr("value");
});
console.log(persOptValue);
}
});
However, as Mike accurately comments below, this is not a proper approach, while the javascript in the console runs, the javascript, which controls the contents of Select Lists, will not run. Only after the Console's script has completed the script for the controls will pick the current value selected for the 'Group' List and will print the corresponding to this option content of the 'people' list that many times as the number of options in the 'Group' List.
If hopefully the above makes sense, my question would be what is the strategy to follow in order to achieve such an automation, as a viewer of the page in my browser, without having access to the page resources?
If I would try to diagrammatically describe the order of the actions I would need to take place that would be:
page is loaded -> jQuery library is loaded -> options of 'Group'
List collected -> set the first option in 'Group' List -> page
is reloaded -> jQuery library is loaded -> options of 'People'
List (corresponding to the 1st 'Group' option) collected -> set the
second option in 'Group List -> jQuery library is loaded ->
options of 'People' List (corresponding to the 2nd 'Group' option)
collected -> set the third option in 'Group' List and so on...
Thank you in advance.
PS: Please mention (although you have already speculated) that I'm pretty much novice in this area of html and jQuery.
EDIT: After discussing with a friend on this topic, he explained me a few things, which I will try to reassemble here, as far as I could understand, but please state the way you would do it, if you wish.
So firstly, one important parameter is the fact that I don't have access to the server and to the page source, so what I'm asking for is to 'hack' the content that has already arrived in my browser, in order to retrieve the data I need. Which is possible, as long as I don't need to refresh the page. And a refresh takes place when, for example a new value in a Select List is selected and the page automatically refreshes to update the content of the children drop Lists. Secondly, I need to automate the process and be able to have typed the code in the console, to press enter once and let the process I described above to continue until to its completion.
The friend suggests that is nearly impossible, unless an external application would take the management role of starting and stoping the javascript in the console, takes notice of when the page has refreshed and controlling the communication among each other, as well. He underlined that the normal way in general would be to have access to the server, where I could ask to have returned in my browser only the data I desire. I am looking forward to your answers.
If you want an alert to show only once, change your for loop from
for (HLValue in HLValues)
{
alert(LLValues);
// **Here is my problem**
}
to
var vals = []; // Create a new empty array
for (HLValue in HLValues)
{
vals.push(HLValue); // Add the item value to your array
}
var joinedVals = vals.join("\n"); // Join the item values with a newline between them
alert(joinedVals); // Alert just once with the joined item values
To include JQuery on each page refresh, add the following line in your <head>:
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
This loads JQuery straight from their site without you having to download it and reference it on your machine.

Count SCEditor characters and output below?

I am using the real simple wysiwyg editor SCEditor for my website.
I am wandering how exactly I can get the current amount of characters in it's textarea and then output them below it?
I asked on the github but it seems not many people use it and the answer didn't make much sense to me, can someone clear it up for me?
The person replied with this:
var numOfCharacters = $textArea.data("sceditor").val().length;
Where: "$textArea" is a variable with a reference for the textarea DOM
element wrapped in a jQuery object.
I have no idea what that means but I'm sure some of you will.
I want to output the length just to some text below the textarea.
you need learn something about jQuery.data .
jQuery.data Store arbitrary data associated with the specified element. read more
jQuery plugins like SCeditor write their associated datas in jQuery.data of element.
for accessing this datas and management, they set their name (like 'sceditor') to it.
when you call $textArea.data("sceditor") jQuery return datas that sceditior stored in element for you.
when you call $textArea.data("sceditor").val().length you are requesting to get val(). it is text of current editor page for $textArea element and length return length of it's text.

How to save 4 drop down list selections to a cookie, and set drops if cookie present

So I am using jQuery and have setup the jquery cookie plugin.
I have 4 drop down lists on my page, and I want to save the user's selections in a cookie, so when they come back to the page I automatically pre-select their previous selections.
I added a class to all my drop downs "ddl-cookie", and I was just thinking if I could somehow loop through all the drop down lists using the class, and save the selection and also loop to set the selections when the user returns to the page.
$(".ddl-cookie").each(function() {
});
It seems that given a cookie name, I can save a single key/value in the cookie.
So I'm guessing the only way for me to do this would be to have a comma separated list of drop down list names and values (selection value)?
You are correct. Cookies are intended to store a single piece of data, so the most common way to handle this is to serialize your data into an easy to retrieve format. That format is up to you, but you might use something like:
field_1=value1&field_2=value&...
You might want to also encode this data--remember that cookies are transferred as part of the request header. The pseudo code would go something like this:
// Store the data, using your own defined methods
data = serialize_data(data);
data = encode_data(data);
cookie = data;
// Retrieve the data using your own defined methods
data = cookie;
data = unencode_data(data)
data = deserialize_data(data)

Categories