I started to work with Quill, and I need to save the changes made by the user in the document, and if possible, composing them, so I don't need to store operation by operation.
To accomplish this, I am monitoring the 'text-change' event, and every operation is stored in the database of my application. From time to time (every minute), I compose the changes made in the document with a previous document state and execute a diff between the result of this composition and the previous document state, storing the result of the diff, and deleting the previous operations, because they are in the diff result.
To get the previous document state, initially I use the original document delta. Then, when a diff is stored, I just compose the original document delta with the diff's that exist in the database. For example:
Original document delta: {"ops":[{"insert":"Evaluation Only. Created with Aspose.Words. Copyright 2003-2018 Aspose Pty Ltd.","attributes":{"size":"16px","font":"Calibri","bold":true,"color":"#FF0000"}},{"insert":"\n","attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"}},{"insert":"Test","attributes":{"size":"14.67px","font":"Calibri","color":"#000000"}},{"insert":"s","attributes":{"size":"14.67px","font":"Calibri","color":"#000000"}},{"insert":"\n","attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"}}],"page_setup":{"left_margin":"113.4px","top_margin":"94.47px","right_margin":"113.4px","bottom_margin":"94.47px"}}
First change: {"ops":[{"delete":80}]}
Second change: {"ops":[{"retain":5},{"insert":"\n","attributes":{"spacing_before":"0px","spacing_after":"10.67px","text_indent":"0px","line_spacing":"17.27px"}}]}
Third change: {"ops":[{"retain":6},{"insert":"A","attributes":{"color":"#000000"}}]}
The code I am using is shown below:
var diffs = result.diffs;
var deltas = result.deltas;
var lastComposedDelta = null;
for (var i = 0; i < diffs.length; i++) {
var currentDelta = newDelta(diffs[i].Value);
if (lastComposedDelta == null) {
lastComposedDelta = currentDelta;
} else {
lastComposedDelta = lastComposedDelta.compose(currentDelta);
}
}
var composedDeltas = lastComposedDelta;
for (var i = 0; i < deltas.length; i++) {
var currentDelta = newDelta(deltas[i].Value);
if (composedDeltas == null) {
composedDeltas = currentDelta;
} else {
composedDeltas = composedDeltas.compose(currentDelta);
}
}
var diffDelta = composedDeltas;
if (lastComposedDelta != null) {
diffDelta = lastComposedDelta.diff(composedDeltas);
}
The result of this diff is: {"ops":[{"delete":80},{"retain":5},{"retain":1,"attributes":{"paragraph":null,"indent":null}},{"attributes":{"color":"#000000"},"insert":"A"},{"attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"},"insert":"\n"}]}
The problem I encountered is when the user inserts a new line and indent it, for example. The delta of such operations are:
New line: {"ops":[{"retain":8},{"insert":"\n"}]}
Indent: {"ops":[{"retain":9},{"retain":1,"attributes":{"indent":1}}]}
Then, when I try to diff the document, with the code above, it gives me the error:
Uncaught Error: diff() called with non-document
Value of "lastComposedDelta": {"ops":[{"insert":"Tests","attributes":{"size":"14.67px","font":"Calibri","color":"#000000"}},{"insert":"\n","attributes":{"spacing_before":"0px","spacing_after":"10.67px","text_indent":"0px","line_spacing":"17.27px"}},{"attributes":{"color":"#000000"},"insert":"A"},{"attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"},"insert":"\n"},{"delete":80},{"retain":5},{"retain":1,"attributes":{"paragraph":null,"indent":null}},{"insert":"A","attributes":{"color":"#000000"}},{"insert":"\n","attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"}}]}
Value of "composedDeltas":
{"ops":[{"insert":"Tests","attributes":{"size":"14.67px","font":"Calibri","color":"#000000"}},{"insert":"\n","attributes":{"spacing_before":"0px","spacing_after":"10.67px","text_indent":"0px","line_spacing":"17.27px"}},{"insert":"A","attributes":{"color":"#000000"}},{"insert":"\n","attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"}},{"insert":"\n"},{"delete":80},{"retain":1,"attributes":{"indent":1}},{"retain":4},{"retain":1,"attributes":{"paragraph":null,"indent":null}},{"insert":"A","attributes":{"color":"#000000"}},{"insert":"\n","attributes":{"paragraph":true,"spacing_before":"0px","spacing_after":"10.67px","indent":0,"text_indent":"0px","line_spacing":"17.27px"}}]}
I dig a little, and found out that the error is caused because there is a "retain" operation on the deltas used to diff, and it is not processed. So, I want to know if there is a solution for this, because I am unsure if the code I've made is the right way to do this (storing diffs of a document).
If you don't need each individual operation, you can just update the document on the text-change event like so:
quill.on('text-change', () => {
// By the time we hit the 'text-change' event,
// quill.getContents() will return the updated
// content of the document
const currentOps = quill.getContents();
updateDatabase(currentOps);
});
function updateDatabase(currentOps) {
// Do whatever you need to do with the current ops
// to store them. No need at all to store the diffs.
}
So, I discovered the problem with the diff function. It was because, when I initialized the editor, I was using the function updateContents to set the delta I had in the database to the editor. Quill always initialize the editor with a blank line. By calling the updateContents, it was composing the blank line with the text coming from my database. Then, when the user was changing the text, the delta from the editor was different from the delta in the database.
To fix this, I changed the function that was loading the content from the database to setContents. This way, the deltas from the editor and database matched.
I want to ask, how to make this script directly display the total of numbers on my blog post without clicked, And will change if browser is refreshed.
` http://jsfiddle.net/BenedictLewis/xmPgR/
There the problem is, your getNumber() function is executing only after click. There is no way it is not added to on load part.
One simple way is call the function getNumbers() after declaration as i did:
var link = document.getElementById('getNumber'); // Gets the link
link.onclick = getNumber; // Runs the function on click
function getNumber() {
var minNumber = 0; // The minimum number you want
var maxNumber = 100; // The maximum number you want
var randomnumber = Math.floor(Math.random() * (maxNumber + 1) + minNumber); // Generates random number
$('#myNumber').html(randomnumber); // Sets content of <div> to number
return false; // Returns false just to tidy everything up
}
getNumber(); // here i called the method, it runs on load of the script
//and works as you wanted
I'm trying to make a click handler that calls a function; and that function gets a string and basically slices the last character and adds it to the front, and each time you click again it should add the last letter to the front.
It seem so easy at first that I thought I could just do it using array methods.
function scrollString() {
var defaultString = "Learning to Code Javascript Rocks!";
var clickCount = 0;
if (clickCount === 0) {
var stringArray = defaultString.split("");
var lastChar = stringArray.pop();
stringArray.unshift(lastChar);
var newString = stringArray.join('');
clickCount++;
} else {
var newArray = newString.split("");
var newLastChar = newArray.pop();
newArray.unshift(newLastChar);
var newerString = newArray.join("");
clickCount++;
}
document.getElementById('Result').innerHTML = (clickCount === 1) ? newString : newerString;
}
$('#button').on('click', scrollString);
Right now it only works the first time I click, and developer tools says newArray is undefined; also the clickCount stops incrementing. I do not know if it's an issue of scope, or should I take a whole different approach to the problem?
Every time you click you are actually reseting the string. Check the scope!
var str = "Learning to Code Javascript Rocks!";
var button = document.getElementById("button");
var output = document.getElementById("output");
output.innerHTML = str;
button.addEventListener("click", function(e){
str = str.charAt(str.length - 1) + str.substring(0, str.length - 1);
output.innerHTML = str;
});
button{
display: block;
margin: 25px 0;
}
<button id="button">Click Me!</button>
<label id="output"></label>
It is, in fact, a scoping issue. Your counter in inside the function, so each time the function is called, it gets set to 0. If you want a counter that is outside of the scope, and actually keeps a proper count, you will need to abstract it from the function.
If you want to keep it simple, even just moving clickCount above the function should work.
I do not know if it's an issue of scope
Yes, it is an issue of scope, more than one actually.
How?
As pointed out by #thesublimeobject, the counter is inside the function and hence gets reinitialized every time a click event occurs.
Even if you put the counter outside the function, you will still face another scope issue. In the else part of the function, you are manipulation a variable (newString) you initialized inside the if snippet. Since, the if snippet didn't run this time, it will throw the error undefined. (again a scope issue)
A fine approach would be:
take the counter and the defaultString outside the function. If the defaultString gets a value dynamically rather than what you showed in your code, extract its value on page load or any other event like change, etc. rather than passing it inside the function.
Do not assign a new string the result of your manipulation. Instead, assign it to defaultString. This way you probably won't need an if-else loop and a newLastChar to take care of newer results.
Manipulate the assignment to the element accordingly.
You can use Javascript closure functionality.
var scrollString = (function() {
var defaultString = "Learning to Code Javascript Rocks!";
return function() {
// convert the string into array, so that you can use the splice method
defaultString = defaultString.split('');
// get last element
var lastElm = defaultString.splice(defaultString.length - 1, defaultString.length)[0];
// insert last element at start
defaultString.splice(0, 0, lastElm);
// again join the string to make it string
defaultString = defaultString.join('');
document.getElementById('Result').innerHTML = defaultString;
return defaultString;
}
})();
Using this you don't need to declare any variable globally, or any counter element.
To understand Javascript Closures, please refer this:
http://www.w3schools.com/js/js_function_closures.asp
Okay, I hope you don't all facepalm when you see this - I'm still finding my way around javascript.
I am putting together an RSVP form for a wedding website.
I want the guests to be able to add their names to the RSVP form, but only have as many fields showing as required. To this end, after each name field, there is a link to click, which will, when clicked, show a name field for the next guest.
The code below works... but I am sure it can be tidier.
I have tried to insert a for() loop into the code in several different ways, I can see that the for() loop increments correctly to the last value - but when it does so, it leaves only the last addEventListener in place. I can only assume, that I should be using a different kind of loop - or a different approach entirely.
How should I tidy up the following?
<script>
function showNextGuest(i) {
document.getElementsByTagName(\'fieldset\')[i].style.display = \'block\';
}
function initiateShowNextGuest() {
document.getElementsByTagName('fieldset')[0].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(1);},false);
document.getElementsByTagName('fieldset')[1].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(2);},false);
document.getElementsByTagName('fieldset')[2].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(3);},false);
document.getElementsByTagName('fieldset')[3].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(4);},false);
document.getElementsByTagName('fieldset')[4].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(5);},false);
}
window.onload = initiateShowNextGuest();
</script>
Your intuition is right - a for loop could indeed simplify it and so could a query selector:
var fieldsSet = document.querySelectorAll("fieldset"); // get all the field sets
var fieldss = [].slice.call(asSet); // convert the html selection to a JS array.
fields.map(function(field){
return field.querySelector("a"); // get the first link for the field
}).forEach(function(link, i){
// bind the event with the right index.
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
This can be shortened to:
var links = document.querySelectorAll("fieldset a:first-of-type");
[].forEach.call(links, function(link, i){
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
function nextGuest () {
for(var i = 0; i < 5; i++){
document.getElementsByTagName('fieldset')[i]
.getElementsByTagName('a')[0]
.addEventListener('click',function(){
showNextGuest(parseInt(i + 1));
}, false);
}
}
Benjamin's answer above is the best given, so I have accepted it.
Nevertheless, for the sake of completeness, I wanted to show the (simpler, if less elegant) solution I used in the end, so that future readers can compare and contrast between the code in the question and the code below:
<script>
var initiateShowNextGuest = [];
function showNextGuest(j) {
document.getElementsByTagName('fieldset')[j].style.display = 'block';
}
function initiateShowNextGuestFunction(i) {
return function() {
var j = i + 1;
document.getElementsByTagName('fieldset')[i].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(j);},false);
};
}
function initiateShowNextGuests() {
for (var i = 0; i < 5; i++) {
initiateShowNextGuest[i] = initiateShowNextGuestFunction(i);
initiateShowNextGuest[i]();
}
}
window.onload = initiateShowNextGuests();
</script>
In summary, the function initiateShowNextGuests() loops through (and then executes) initiateShowNextGuestFunction(i) 5 times, setting up the 5 anonymous functions which are manually written out in the code in the original question, while avoiding the closure-loop problem.
I am trying to create clickable divs that will set a variable once they are clicked.
For example,
If my program had to do with playing with balls.
When a user inputs a name, and what kind of ball they want (lets say name= Johnny and Ball=BasketBall) and then clicks "Create a Ball", My program will create a div called "Ball0". The "Ball0" div will display Johnny's name. When a user Clicks on his name it will say "Johnny has a BasketBall.
Here is how I am trying to do this code
var ballRefrence = -1;
function createBall() {
ballRefrence +=1;
var myElement = document.createElement('div');
myElement.id = 'individualBall'+ballRefrence;
ballArea.appendChild(myElement);//ball area is where I want to list the divs
myElement.style.width = "550px";
myElement.innerHTML= userName+" has bought a ball!";
createEventListner();
}
function createEventListner() {
var iBall = document.getElementById("individualBall"+ballRefrence);
iball.addEventListener("click",showBall,false);
}
function playCaption() {
//I am not sure how to pull the individual up here.
//Since I am using the "BallRefrence"variable
//it will always be the last person clicked..
//(Basically I cannot say
//alert.(ballRefrence);
}
I guess what I am trying to say is this,
I have my "BallRefrence" that goes up by 1 each time.
I store all the items in an array. (so first person will go to index 0, second will go to index 1...ect)
I am having problems refrencing the element, because I am not sure how to pull the corrisponding numbe from the div.
Example:
After 3 clicks of "create ball" my html will look something like this.
<div id = "individualBall0></div>
<div id = "individualBall1></div>
<div id = "individualBall2></div>
Since each individual div will match up to the element in the array I want, How will tell my program
"When you click individualBall0, I want ball[0].."
or
"when you click indivdualBall2, I want ball[2]"
I hope this makes more sense.
One solution that doesn't really follow your updated requirements (but I believe does something simpler) is on JSFiddle. This stores the username and ball type in a local closure related to the event listener for that DIV. The code is fairly simple, and it removes some clutter from the global namespace, revealing only createBall:
var createBall = (function() {
var ballReference = -1;
var ballArea = document.getElementById('ballArea');
var showBall = function(userName, ballType) {
return function(event) {
alert(userName + ' has a ' + ballType);
};
};
return function() {
++ballReference;
var userName = document.getElementById('name').value;
var ballType = document.getElementById('ball').value;
var myElement = document.createElement('div');
myElement.id = 'individualBall'+ballReference;
ballArea.appendChild(myElement);
myElement.innerHTML= userName+" has bought a ball!";
myElement.addEventListener("click",showBall(userName, ballType),false);
};
}());
To perform your updated requirements, it should be pretty easy. Your event handler will run in the context of your element, so something like this:
this.id.substring('individualBall'.length) //=> '0', '1', etc
should return a string representing the index of the div. You can use that directly for indexing various arrays, or, if that makes you squeamish, turn it into an integer with parseInt, the Number constructor, or the unary + operator.
But note that the suggestions from Bergi, jfriend00, and RobG are all good. There are many good ways of going about this.