Can I optimize these functions? - javascript

I have 3 functions (they are minimalized so they might be difficult to read) listed below - i0, t0, and is.
is() and t0() both pull data from the DOM with this line
var c=document.forms[a].elements;
would it be better to pull the data from the DOM in i0(), and then pass it to is() and t0()?
That way I would only pull data from the DOM once, but then I would need an extra variable to store it in an pass it to the two functions.
i0():
function i0()
{
if(t0())
{
var a=is('f0');
s0('bi0.php',a,s2);
}
}
t0:
function t0()
{
var a=document.forms['f0'].elements;
a1="Please enter your credentials";
a2="That email is not registered";
a3="Incorrect credentials - Reset your password?";
if(c0(a,a1,'fb1')&&c2(a[1],a2,'fb1')&&c3(a[2],a3,'fb1'))
{
return 1;
}
else
{
return 0;
}
}
is():
function is(a)
{
var b='';
var c=document.forms[a].elements;
for(i=0;i<c.length;i++)
{
if(c[i].name)
{
if(c[i].type=='checkbox'&&c[i].checked==false)
{
b+=c[i].name+"=NULL&";
}
else
{
b+=c[i].name+"="+c[i].value+"&";
}
}
}
b=b.slice(0,-1);
return b;
}

function i0(a){
t0() && (a=is('f0'), s0('bi0.php', a, s2)); // just so I can use the comma like this
}
// or
function i0(){
t0() && s0('bio.php', is('f0'), s2);
}
function t0(){
var a = document.forms['f0'].elements,
a1 = "Please enter your credentials",
a2 = "That email is not registered",
a3 = "Incorrect credentials - Reset your password?";
return +( c0(a,a1,'fb1') && c2(a[1],a2,'fb1') && c3(a[2],a3,'fb1') );
}
function is(a){
var b = '',
c = document.forms[a].elements;
for( var i=0, l=c.length; i<l; i++ ){
c[i].name
? c[i].type == 'checkbox' && !c[i].checked && b += c[i].name + '=NULL&'
: b += c[i].name + '=' + c[i].value + '&';
}
return ( b = b.slice(0, -1) );
}
to answer your actual question, yes doing a single select on document.forms['f0'].elements will make things slightly faster in some browsers, but it's a micro-optimization that I suspect will only be faster in old browsers (IE6) due to the hash-lookup.

You can change your for loop like this to make it faster, albeit a slight optimization
(Comparison to 0 is faster than comparing to other numbers):
for(i = c.length;i > 0;--i)
{
if(c[i].name)
{
if(c[i].type=='checkbox'&&c[i].checked==false)
{
b+=c[i].name+"=NULL&";
}
else
{
b+=c[i].name+"="+c[i].value+"&";
}
}
}

I assume you are talking about optimize in time.
Long way: Everything can be optimized.
Short way: Any optimization in this kind of code will be extremely low
Anyway, function is() is very similar to JQuery serialize call and it has been optimizated. Have you considered to use it?

Related

Text operations: Detect replacement from clipboard

General Info
Working on my own implementation of the operational transformation algorithm. For those that don't know what this is: When multiple users work on the same document at the same time, this algorithm attempts to preserve each users intention and make sure all users end up with the same document.
The problem
To begin, I need a proper way of detecting text operations. Like insert and delete. Obviously I need to know exactly at which position this is happening so each operation can be correctly transformed by the server to preserve the intention of other users.
My code so far is doing a pretty decent job at this. But it gets in trouble when selecting a text range and replacing it with another. I rely on the input event for this, and it seems to be unable to detect both delete and insert operations at the same time. When doing this, it detects a delete operation on the selected text. But it does not detect the insert operation of the text pasted in from the clipboard.
My question is: How can I solve this issue?
My code (so far)
let txtArea = {};
let cursorPos = {};
let clientDoc = ""; // Shadow DOC
document.addEventListener("DOMContentLoaded", function(event){
txtArea = document.getElementById("test");
clientDoc = txtArea.value;
txtArea.addEventListener("input", function(){ handleInput(); });
txtArea.addEventListener("click", function(){ handleSelect(); });
});
/* Gets cursor position / selected text range */
function handleSelect(){
cursorPos = getCursorPos(txtArea);
}
/* Check whether the operation is insert or delete */
function handleInput(){
if(txtArea.value > clientDoc){
handleOperation("insert");
} else {
handleOperation("delete");
}
}
/* Checks text difference to know exactly what happened */
function handleOperation(operation){
let lines = "";
if(operation === "insert"){
lines = getDifference(clientDoc, txtArea.value);
} else if(operation === "delete"){
lines = getDifference(txtArea.value, clientDoc);
}
const obj = {
operation: operation,
lines: lines,
position: cursorPos
};
clientDoc = txtArea.value;
console.log(obj);
}
/* Simple function to get difference between 2 strings */
function getDifference(a, b)
{
let i = 0;
let j = 0;
let result = "";
while (j < b.length)
{
if (a[i] != b[j] || i == a.length){
result += b[j];
} else {
i++;
}
j++;
}
return result;
}
/* Function to get cursor position / selection range */
function getCursorPos(input) {
if ("selectionStart" in input && document.activeElement == input) {
return {
start: input.selectionStart,
end: input.selectionEnd
};
}
else if (input.createTextRange) {
var sel = document.selection.createRange();
if (sel.parentElement() === input) {
var rng = input.createTextRange();
rng.moveToBookmark(sel.getBookmark());
for (var len = 0;
rng.compareEndPoints("EndToStart", rng) > 0;
rng.moveEnd("character", -1)) {
len++;
}
rng.setEndPoint("StartToStart", input.createTextRange());
for (var pos = { start: 0, end: len };
rng.compareEndPoints("EndToStart", rng) > 0;
rng.moveEnd("character", -1)) {
pos.start++;
pos.end++;
}
return pos;
}
}
return -1;
}
#test {
width: 600px;
height: 400px;
}
<textarea id="test">test</textarea>
Managed to solve the problem myself, though not entirely sure if it's the best solution. I've used comments within the code to explain how I solved it:
function handleOperation(operation){
let lines = "";
if(operation === "insert"){
lines = getDifference(clientDoc, txtArea.value);
} else if(operation === "delete"){
lines = getDifference(txtArea.value, clientDoc);
}
// This handles situations where text is being selected and replaced
if(operation === "delete"){
// Create temporary shadow doc with the delete operation finished
const tempDoc = clientDoc.substr(0, cursorPos.start) + clientDoc.substr(cursorPos.end);
// In case the tempDoc is different from the actual textarea value, we know for sure we missed an insert operation
if(tempDoc !== txtArea.value){
let foo = "";
if(tempDoc.length > txtArea.value.length){
foo = getDifference(txtArea.value, tempDoc);
} else {
foo = getDifference(tempDoc, txtArea.value);
}
console.log("char(s) replaced detected: "+foo);
}
} else if(operation === "insert"){
// No need for a temporary shadow doc. Insert will always add length to our shadow doc. So if anything is replaced,
// the actual textarea length will never match
if(clientDoc.length + lines.length !== txtArea.value.length){
let foo = "";
if(clientDoc.length > txtArea.value.length){
foo = getDifference(txtArea.value, clientDoc);
} else {
foo = getDifference(clientDoc, txtArea.value);
}
console.log("char(s) removed detected: "+foo);
}
}
const obj = {
operation: operation,
lines: lines,
position: cursorPos
};
// Update our shadow doc
clientDoc = txtArea.value;
// Debugging
console.log(obj);
}
I'm still very much open to better solutions / tips / advise if you can give it to me.

Combining Javascript Validation Functions

Alright I need help combining the two JavaScript Functions... I have tried multiple times and am not coming up with any luck. There almost identical functions except the fact that I change one number so that it thinks there different textboxes. I tried putting a variable in its place but then it always only validates to the ending number of the loop. Please show me how I may be able to combine these two functions. (Its my only work around and I can not find any examples similar to mine)
First:
<script type="text/javascript">
var QnoText = ['abc_1']; // add IDs here for questions with optional text input
function doSubmit_1() {
var ids_1 = '';
flag_1 = true;
for (i=0; i<QnoText.length; i++) {
CkStatus = document.getElementById(QnoText[i]).checked;
ids_1 = QnoText[i]+'Certificate_1';
if (CkStatus && document.getElementById(ids_1).value == '') {
alert('Please enter certificate number 1.');
document.getElementById(ids_1).focus();
flag_1 = false;
alert('return flag_1');
}
}
return flag_1;
}
</script>
Second:
<script type="text/javascript">
var QnoText = ['abc_2']; // add IDs here for questions with optional text input
function doSubmit_2() {
var ids_2 = '';
flag_2 = true;
for (i=0; i<QnoText.length; i++) {
CkStatus = document.getElementById(QnoText[i]).checked;
ids_2 = QnoText[i]+'Certificate_2';
if (CkStatus && document.getElementById(ids_2).value == '') {
alert('Please enter certificate number 2.');
document.getElementById(ids_2).focus();
flag_2 = false;
alert('return flag_2');
}
}
return flag_2;
}
</script>
You can pass a parameter in your function with the number of the textbox, like this:
var QnoText = ['abc_2']; // add IDs here for questions with optional text input
function doSubmit(n) {
var ids = '';
flag = true;
for (i=0; i<QnoText.length; i++) {
CkStatus = document.getElementById(QnoText[i]).checked;
ids = QnoText[i]+'Certificate_' + n;
if (CkStatus && document.getElementById(ids).value == '') {
alert('Please enter certificate number ' + n + '.');
document.getElementById(ids).focus();
flag = false;
alert('return flag_' + n);
}
}
return flag;
}
doSubmit(1); // for your submit 1
doSubmit(2); // for your submit 2
Is this what you wanted? because is not very clear. If is not feel free to explain.

how to attach click function to multiple divs without ID

I have a fade in function im trying to understand better. It works fine when I set up the
My question is if I have 8 links that already have the separate ID and class names how can I attach this function to each clickable link?
Is there a function to getElementbyClass or something and then just add the class to all my links?
here is my javascript:
var done = true,
fading_div = document.getElementById('fading_div'),
fade_in_button = document.getElementById('fade_in'),
fade_out_button = document.getElementById('fade_out');
function function_opacity(opacity_value) {
fading_div.style.opacity = opacity_value / 100;
fading_div.style.filter = 'alpha(opacity=' + opacity_value + ')';
}
function function_fade_out(opacity_value) {
function_opacity(opacity_value);
if (opacity_value == 1) {
fading_div.style.display = 'none';
done = true;
}
}
function function_fade_in(opacity_value) {
function_opacity(opacity_value);
if (opacity_value == 1) {
fading_div.style.display = 'block';
}
if (opacity_value == 100) {
done = true;
}
}
// fade in button
fade_in_button.onclick = function () {
if (done && fading_div.style.opacity !== '1') {
done = false;
for (var i = 1; i <= 100; i++) {
setTimeout((function (x) {
return function () {
function_fade_in(x)
};
})(i), i * 10);
}
}
};
// fade out button
fade_out_button.onclick = function () {
if (done && fading_div.style.opacity !== '0') {
done = false;
for (var i = 1; i <= 100; i++) {
setTimeout((function (x) {
return function () {
function_fade_out(x)
};
})(100 - i), i * 10);
}
}
};
Correcting the answer from BLiu1:
var fadeDivs = document.getElementsByClassName('fade');
for (var i=0, i<fadeDivs.length, i++){
// do stuff to all fade-divs by accessing them with "fadeDivs[i].something"
}
Have you considered using a javascript library like jQuery to manage this. They have some extensive, very easy to use "selectors" that allow you to easily get access to elements in the DOM and animate them with things like "fade ins" and "slides", etc. If you need more animations there are tons of plugins available for this. It also helps to deal with browser compatibility challenges too.
If you want to rely on pure JavaScript, you can use the document.getElementsByClassName() function defined here, but that function is only defined in IE9 and above as well as Safari, Chrome, FF, and Opera.
As said in the comments, there is a getElementsByClassName() method. Here is how you would use it.
for(var i=0; i<document.getElementsByClassName("fade").length; i++ ){
/*apply fade in function*/
}
I'm not sure whether getElementsByClassName() can detect one class name at a time. You might need regex for that.

jQuery "keyup" crashing page when checking 'Word Count'

I have a word counter running on a DIV and after typing in a few words, the page crashes. The browser continues to work (par scrolling) and no errors are showing in Chrome's console. Not sure where I'm going wrong...
It all started when I passed "wordCount(q);" in "keyup". I only passed it there as it would split-out "NaN" instead of a number to countdown from.
JS:
wordCount();
$('#group_3_1').click(function(){
var spliced = 200;
wordCount(spliced);
}) ;
$('#group_3_2').click(function(){
var spliced = 600;
wordCount(spliced);
}) ;
function wordCount(q) {
var content_text = $('.message1').text(),
char_count = content_text.length;
if (char_count != 0)
var word_count = q - content_text.replace(/[^\w ]/g, "").split(/\s+/).length;
$('.word_count').html(word_count + " words remaining...");
$('.message1').keyup(function() {
wordCount(q);
});
try
{
if (new Number( word_count ) < 0) {
$(".word_count").attr("id","bad");
}
else {
$(".word_count").attr("id","good");
}
} catch (error)
{
//
}
};
HTML:
<input type="checkbox" name="entry.3.group" value="1/6" class="size1" id="group_3_1">
<input type="checkbox" name="entry.3.group" value="1/4" class="size1" id="group_3_2">
<div id="entry.8.single" class="message1" style="height: 400px; overflow-y:scroll; overflow-x:hidden;" contenteditable="true"> </div>
<span class="word_count" id="good"></span>
Thanks in advanced!
This is causing an infinite loop if (new Number(word_count) < 0) {.
Your code is a mess altogether. Just study and start with more basic concepts and start over. If you want to describe your project to me in a comment, I would be glad to show you a good, clean, readable approach.
Update:
Part of having a good architecture in your code is to keep different parts of your logic separate. No part of your code should know about or use anything that isn't directly relevant to it. Notice in my word counter that anything it does it immediately relevant to its word-counter-ness. Does a word counter care about what happens with the count? Nope. It just counts and sends the result away (wherever you tell it to, via the callback function). This isn't the only approach, but I just wanted to give you an idea of how to approach things more sensefully.
Live demo here (click).
/* what am I creating? A word counter.
* How do I want to use it?
* -Call a function, passing in an element and a callback function
* -Bind the word counter to that element
* -When the word count changes, pass the new count to the callback function
*/
window.onload = function() {
var countDiv = document.getElementById('count');
wordCounter.bind(countDiv, displayCount);
//you can pass in whatever function you want. I made one called displayCount, for example
};
var wordCounter = {
current : 0,
bind : function(elem, callback) {
this.ensureEditable(elem);
this.handleIfChanged(elem, callback);
var that = this;
elem.addEventListener('keyup', function(e) {
that.handleIfChanged(elem, callback);
});
},
handleIfChanged : function(elem, callback) {
var count = this.countWords(elem);
if (count !== this.current) {
this.current = count;
callback(count);
}
},
countWords : function(elem) {
var text = elem.textContent;
var words = text.match(/(\w+\b)/g);
return (words) ? words.length : 0;
},
ensureEditable : function(elem) {
if (
elem.getAttribute('contenteditable') !== 'true' &&
elem.nodeName !== 'TEXTAREA' &&
elem.nodeName !== 'INPUT'
) {
elem.setAttribute('contenteditable', true);
}
}
};
var display = document.getElementById('display');
function displayCount(count) {
//this function is called every time the word count changes
//do whatever you want...the word counter doesn't care.
display.textContent = 'Word count is: '+count;
}
I would do probably something like this
http://jsfiddle.net/6WW7Z/2/
var wordsLimit = 50;
$('#group_3_1').click(function () {
wordsLimit = 200;
wordCount();
});
$('#group_3_2').click(function () {
wordsLimit = 600;
wordCount();
});
$('.message1').keydown(function () {
wordCount();
});
function wordCount() {
var text = $('.message1').text(),
textLength = text.length,
wordsCount = 0,
wordsRemaining = wordsLimit;
if(textLength > 0) {
wordsCount = text.replace(/[^\w ]/g, '').split(/\s+/).length;
wordsRemaining = wordsRemaining - wordsCount;
}
$('.word_count')
.html(wordsRemaining + " words remaining...")
.attr('id', (parseInt(wordsRemaining) < 0 ? 'bad' : 'good'));
};
wordCount();
It's not perfect and complete but it may show you direction how to do this. You should use change event on checkboxes to change wordsLimit if checked/unchecked. For styling valid/invalid words remaining message use classes rather than ids.
I think you should use radio in place of checkboxes because you can limit 200 or 600 only at a time.
Try this like,
wordCount();
$('input[name="entry.3.group"]').click(function () {
wordCount();
$('.word_count').html($(this).data('val') + " words remaining...");
});
$('.message1').keyup(function () {
wordCount();
});
function wordCount() {
var q = $('input[name="entry.3.group"]:checked').data('val');
var content_text = $('.message1').text(),
char_count = content_text.length;
if (char_count != 0) var word_count = q - content_text.replace(/[^\w ]/g, "").split(/\s+/).length;
$('.word_count').html(word_count + " words remaining...");
try {
if (Number(word_count) < 0) {
$(".word_count").attr("id", "bad");
} else {
$(".word_count").attr("id", "good");
}
} catch (error) {
//
}
};
Also you can add if your span has bad id then key up should return false;
See Demo

javascript if statement to do-while

I am trying to get this snip of code to work in a while loop instead of an if.
I need each instant of num_# to not be displayed if the printLoad_# val is empty. So if printLoad_1 value = nothing, the Num_1 would not be displayed, and then the printLoad_2 would check to see if its num_2 is empty and so on.
The problem I am having is the function stops before checking each section.
Im not sure if a do-while will work.
$(document).ready(function() {
if(document.getElementById("printLoad_1").value == "")
{
document.getElementById('num_1').style.display = 'none';
}
if(document.getElementById("printLoad_2").value == "")
{
document.getElementById('num_2').style.display = 'none';
}
if(document.getElementById("printLoad_3").value == "")
{
document.getElementById('num_3').style.display = 'none';
}
if(document.getElementById("printLoad_4").value == "")
{
document.getElementById('num_4').style.display = 'none';
}
if(document.getElementById("printLoad_5").value == "")
{
document.getElementById('num_5').style.display = 'none';
}
});
Have you considered just compositing the strings instead of this verbose construction? Something like this:
for( var i=1; i<=5; i++ ) {
if( document.getElementById('printLoad_'+i).value === '' ) {
document.getElementById('num_'+i).style.display = 'none';
}
}
Assuming you're using jQuery and that your elements are in order, I'd forget about ID's. If you use common classes, say .printload and .num, then you can easily target elements by index like:
$('.printload').each(function(i){
if (!this.value) $('.num').eq(i).hide();
});
if you have a variable amount of printLoad and num:
var i = 1,
pl=document.getElementById('printLoad_'+i),
num = document.getElementById('num_'+i);
while(pl !== null && num !== null){
if(pl.value === ""){
num.style.display = 'none';
}
i++;
pl=document.getElementById('printLoad_'+i),
num = document.getElementById('num_'+i);
}
Here is a slight modification of Ethan's answer that "works dynamically". I've also updated it to use jQuery. This could be handled cleaner if CSS classes and a hierarchy relationship were used, but that would affect how the DOM needed to be generated ..
for (var i = 1; /* break */; i++) {
var printEl = $('#printLoad_' + i)
if (printEl.length) {
if (!printEl.val()) {
$('#num_' + i).css({display: 'none'})
}
} else {
// No #printLoad_N, guess we're done looking
break
}
}
Per #elclanr 's answer:
Use common classes, and target elements by index, it'll be much simpler.
Set your "printLoad" elements to class='printLoad' and your "num" elements to class='num'. Then...
for (i=0;i<document.getElementByClass('printLoad').length;i++)
{
if (document.getElementByClass('printLoad')[i].value == "")
{
document.getElementByClass('num')[i].style.display='none';
}
}

Categories