jQuery Refactoring - DRY - javascript

I have these two functions, inside of my main function. As you'll see the only difference between the two of them is in the middle with how they append/edit the html. I am thinking it would be nice to come up with two new functions, one that does the first half and the other that does the second half. I'm not sure if this is possible with jQuery, or even with JavaScript for that matter, as I don't know how to call these new functions inside of these functions, if that makes sense. Any help/guidance would be great!
Here is the first one
$('#save').click(function(){
var title = $('#title').val();
var tags = $('#tags').val();
var notes = $('#notes').val();
var myDate = new Date();
if (title.length < 1) {
$('.title-warn').show();
}
if (tags.length < 1) {
$('.tags-warn').show();
}
if (notes.length < 1) {
$('.notes-warn').show();
}
if (title.length >= 1 && tags.length >= 1 && notes.length >= 1) {
$allNotes.prepend('<li class="note"><div><h1>' + title + '</h1><div class="date"> <h2>'+ myDate.toDateString() +'</h2><span class="btn btn-edit">Edit</span></div><h3>' + tags + '</h3><p>' + notes + '</p></div></li>');
$allNotes.show();
$newNote.hide();
$('.title-warn').hide();
$('.tags-warn').hide();
$('.notes-warn').hide();
}
$('#title').val('');
$('#tags').val('');
$('#notes').val('');
$('#search').prop('disabled', false);
$('#search').attr("placeholder", "Search by title, tags, date, or even words/sentences in notes");
$('.btn-search').prop('disabled', false);
});
And now the second one
$('#edit').click(function(){
var title = $('#edit-title').val();
var tags = $('#edit-tags').val();
var notes = $('#edit-notes').val();
var myDate = new Date();
if (title.length < 1) {
$('.title-warn').show();
}
if (tags.length < 1) {
$('.tags-warn').show();
}
if (notes.length < 1) {
$('.notes-warn').show();
}
if (title.length >= 1 && tags.length >= 1 && notes.length >= 1) {
$('.edited-note').html('<div><h1>' + title + '</h1><div class="date"> <h2>'+ myDate.toDateString() +'</h2><span class="btn btn-edit">Edit</span></div><h3>' + tags + '</h3><p>' + notes + '</p></div>');
$('.allnotes').show();
$('.edit-note').hide();
$('.title-warn').hide();
$('.tags-warn').hide();
$('.notes-warn').hide();
}
$('#title').val('');
$('#tags').val('');
$('#notes').val('');
$('#search').prop('disabled', false);
$('#search').attr("placeholder", "Search by title, tags, date, or even words/sentences in notes");
$('.btn-search').prop('disabled', false);
$('.edited-note').removeClass('edited-note');
});
And of course if anyone has any suggestions on any other aspects of my code I am up for criticism!

You answered your own question! "As you'll see the only difference between the two of them is in the middle with how they append/edit the html." An initial pretty naive and mechanical attempt attempt at refactoring might look like something like this, just pulling out all the common code into shared functions:
function preHandler(title, tags, notes) {
if (title.length < 1) {
$('.title-warn').show();
}
if (tags.length < 1) {
$('.tags-warn').show();
}
if (notes.length < 1) {
$('.notes-warn').show();
}
return title.length >= 1 && tags.length >= 1 && notes.length >= 1;
}
function commonPost () {
$('#title').val('');
$('#tags').val('');
$('#notes').val('');
$('#search').prop('disabled', false);
$('#search').attr("placeholder", "Search by title, tags, date, or even words/sentences in notes");
$('.btn-search').prop('disabled', false);
}
$('#save').click(function(){
var title = $('#title').val();
var tags = $('#tags').val();
var notes = $('#notes').val();
var myDate = new Date();
if (preHandler(title, tags, notes)) {
$allNotes.prepend('<li class="note"><div><h1>' + title + '</h1><div class="date"> <h2>'+ myDate.toDateString() +'</h2><span class="btn btn-edit">Edit</span></div><h3>' + tags + '</h3><p>' + notes + '</p></div></li>');
$allNotes.show();
$newNote.hide();
$('.title-warn').hide();
$('.tags-warn').hide();
$('.notes-warn').hide();
}
commonPost();
});
$('#edit').click(function() {
var title = $('#edit-title').val();
var tags = $('#edit-tags').val();
var notes = $('#edit-notes').val();
var myDate = new Date();
if (preHandler(title, tags, notes)) {
$('.edited-note').html('<div><h1>' + title + '</h1><div class="date"> <h2>'+ myDate.toDateString() +'</h2><span class="btn btn-edit">Edit</span></div><h3>' + tags + '</h3><p>' + notes + '</p></div>');
$('.allnotes').show();
$('.edit-note').hide();
$('.title-warn').hide();
$('.tags-warn').hide();
$('.notes-warn').hide();
}
commonPost();
$('.edited-note').removeClass('edited-note');
});
It doesn't really win you that much when there are only two scenarios. But with more than two doing this sort of refactoring starts to reap rewards.
This first attempt could be refined (a lot). Perhaps nice folks will post. But this would be a good first attempt.

Generally I like creating some sort of a controller class which does all the UI work for each page in my application. This makes testing a whole lot easier because you can invoke your target methods in a very easy manner.
I also like prefer hiding my selectors behind easy to read variables, so if I were to read this file in a few weeks/months from now, I can quickly bring myself to speed by reading what the high-level logic is intending to do. (FYI in my answer below I haven't done this. I actually don't know what editTitle is supposed to represent. But here are few examples of how I would rename it as: TaskTitle, BlogTitle, etc.)
These are all guidelines really, but I wished I had always written my code like this. I would recommend you to take out some time and just read about javascript design patterns, programming idioms, etc.
var myApp = new app();
$(function () {
myApp.init();
});
var app = function () {
var editTitle = "#edit-title";
var editTitleWarning = ".title-warn";
var editTags = "#edit-tags";
var editTagsWarning = ".tags-warn";
var editNotes = "#edit-notes";
var editNotesWarning = ".notes-warn";
var saveButton = "#save";
var editButton = "#edit";
var updateUI = function (args) {
var isSave = args.data.isSave;
var title = $(editTitle).val();
var tags = $(editTags).val();
var notes = $(editNotes).val();
var myDate = new Date();
// (suggestion) add comment here
if (title.length < 1) {
$(editTitleWarning).show();
}
// (suggestion) add comment here
if (tags.length < 1) {
$(editTagsWarning).show();
}
// (suggestion) add comment here
if (notes.length < 1) {
$(editNotesWarning).show();
}
if (isSave) {
// add save append code here
} else {
// add edit html code here
}
// add remaining code here
};
this.init = function () {
$("body")
.on("click", saveButton, { isSave = true }, updateUI)
.on("click", editButton, { isSave = false }, updateUI);
};
}

Talking only about making the code DRY (versus about architecturing it properly in general), I'd probably rewrite the code as:
var handler = function(prefix, htmlHandler, removeEditedNoteCls) {
prefix = '#' + (prefix === false ? '' : (prefix + '-'));
var list = ['title', 'tags', 'notes'],
i = 0,
h = {
date: new Date
},
act = true;
for (; i < list.length; i++) {
h[list[i]] = $(prefix + list[i]).val();
if (h[list[i]].length < 1) {
$('.' + list[i] + '-warn').show();
act = false;
}
}
if (act) {
htmlHandler.call(this, h);
for (i = 0; i < list.length; i++) {
$('.' + list[i] + '-warn').hide();
}
}
for (i = 0; i < list.length; i++) {
$('#' + list[i]).val('');
}
$('#search').prop('disabled', false);
$('#search').attr("placeholder", "Search by title, tags, date, or even words/sentences in notes");
$('.btn-search').prop('disabled', false);
if (removeEditedNoteCls) {
$('.edited-note').removeClass('edited-note');
}
},
prepend = function(h) {
$allNotes.prepend('<li class="note"><div><h1>' + h.title + '</h1><div class="date"> <h2>' + h.date.toDateString() + '</h2><span class="btn btn-edit">Edit</span></div><h3>' + h.tags + '</h3><p>' + h.notes + '</p></div></li>');
$allNotes.show();
$newNote.hide();
},
replace = function(h) {
$('.edited-note').html('<div><h1>' + h.title + '</h1><div class="date"> <h2>' + h.date.toDateString() + '</h2><span class="btn btn-edit">Edit</span></div><h3>' + h.tags + '</h3><p>' + h.notes + '</p></div>');
$('.allnotes').show();
$('.edit-note').hide();
};
$('#save').click(function() {
handler('', prepend);
});
$('#edit').click(function() {
handler('edit', replace, true);
});
Basically, you:
Don't repeat var per earch variable declaration. Use commas to separate declarations under the same var. Although not a big deal with DRY but it makes code shorter and nicer.
Identify repeating/similar stuff and squash it into either:
Loops over arrays which contain the differences only. In your case it will be the array of IDs ['title', 'tags', 'notes']; OR
Methods accepting the differences as arguments. In your case both "edit" and "save" handlers are pretty similar, which should be the first signal to you to wrap them into a named method (handler in my example).

Related

How do i switch Javascript to Jquery code?

I have some javascript that I want to convert to jQuery...
How do we change javascript to jquery code?
Do we just change the document.getElementById > $?
Do we change document.querySelectorAll > $ too?
Does the function portion also need to be tweak?
Kindly see my code apprehend below:
// Home Page Gallery
let i = 0; // current slide
let j = 5; // total slides
const dots = document.querySelectorAll(".dot-container button");
const images = document.querySelectorAll(".image-container img");
function next() {
document.getElementById("content" + (i + 1)).classList.remove("active");
i = (j + i + 1) % j;
document.getElementById("content" + (i + 1)).classList.add("active");
indicator(i + 1);
}
function prev() {
document.getElementById("content" + (i + 1)).classList.remove("active");
i = (j + i - 1) % j;
document.getElementById("content" + (i + 1)).classList.add("active");
indicator(i + 1);
}
function indicator(num) {
dots.forEach(function (dot) {
dot.style.backgroundColor = "transparent";
});
document.querySelector(".dot-container button:nth-child(" + num + ")").style.backgroundColor = "#107e31";
}
function dot(index) {
images.forEach(function (image) {
image.classList.remove("active");
});
document.getElementById("content" + index).classList.add("active");
i = index - 1;
indicator(index);
}
// FAQ JS
let toggles = document.getElementsByClassName('toggle');
let contentDiv = document.getElementsByClassName('content');
let icons = document.getElementsByClassName('icon');
for(let i=0; i<toggles.length; i++){
toggles[i].addEventListener('click', ()=>{
if( parseInt(contentDiv[i].style.height) != contentDiv[i].scrollHeight){
contentDiv[i].style.height = contentDiv[i].scrollHeight + "px";
toggles[i].style.color = "#0084e9";
icons[i].classList.remove('fa-plus');
icons[i].classList.add('fa-minus');
}
else{
contentDiv[i].style.height = "0px";
toggles[i].style.color = "#111130";
icons[i].classList.remove('fa-minus');
icons[i].classList.add('fa-plus');
}
for(let j=0; j<contentDiv.length; j++){
if(j!==i){
contentDiv[j].style.height = "0px";
toggles[j].style.color = "#111130";
icons[j].classList.remove('fa-minus');
icons[j].classList.add('fa-plus');
}
}
});
}
When using $() with selectors, it's very similar to document.querySelectorAll().
So, if you wanted to query for a specific ID, you'd need to use a pound symbol # in front of the ID:
$('#some-id')
Really though, there's no reason I can think of these days to use jQuery. Additionally, you could probably remove the need for any of this JavaScript by simply using anchor fragments/hash and the :target selector. Your URLs could be like somepage.html#slide-1.

Javascript Regex optimization

I have two JQuery/Javascript functions that highlights all string occurences in a web page, and then narrows these occurences one by one when clicking on the next/prev buttons.
Here is the current JS code for the next and previous buttons:
var outputHtmlContent;
var posScroll = 0;
// Trigger on search next occurence's button
$('#nextSearchOutputButton').on("click", function (e) {
e.preventDefault();
var searchString = "Command Name";
$("body").highlight(searchString);
$(".highlight").css({ backgroundColor: "#E0FFFF" });
var regex = /Command Name/g, result, indices = [];
var i = 0;
outputHtmlContent = $("#output").html();
if (posScroll == outputHtmlContent.match(/Command Name/g).length - 1) posScroll = 0;
else
posScroll++;
while ((result = regex.exec(outputHtmlContent))) {
if (posScroll == i) {
var index = result.index;
outputHtmlContent = outputHtmlContent.substring(0, index) + "<span class='highlight'>" + outputHtmlContent.substring(index, index + searchString.length) + "</span>" + outputHtmlContent.substring(index + searchString.length);
$("#output").html(outputHtmlContent);
document.getElementById("commandNumber" + posScroll).scrollIntoView();
return;
}
i++;
}
})
// Trigger on search previous occurence's button
$('#prevSearchOutputButton').on("click", function (e) {
e.preventDefault();
var searchString = "Command Name";
$("body").highlight(searchString);
$(".highlight").css({ backgroundColor: "#E0FFFF" });
var regex = /Command Name/g, result, indices = [];
var i = 0;
outputHtmlContent = $("#output").html();
if (posScroll == 0) posScroll = outputHtmlContent.match(/Command Name/g).length - 1;
else
posScroll--;
while ((result = regex.exec(outputHtmlContent)) && i <= posScroll) {
if (posScroll == i) {
var index = result.index;
outputHtmlContent = outputHtmlContent.substring(0, index) + "<span class='highlight'>" + outputHtmlContent.substring(index, index + searchString.length) + "</span>" + outputHtmlContent.substring(index + searchString.length);
$("#output").html(outputHtmlContent);
document.getElementById("commandNumber" + posScroll).scrollIntoView();
return;
}
i++;
}
})
The two functions work well, but I have a very critical issue concerning the time spent when executing the two functions, this time estimated to 10 seconds for IE when moving from one occurence to an other and is estimated to 4 seconds for FF and chrome.
I googled and found that the Regex performance issue is a classic topic. I found this nice atricle http://infrequently.org/2007/02/regexs-are-slow/.
I would rather remove completely this feature than offering it with such a low quality issue. Yet, I want to stick on it because it is very helpful for my user. Can anyone help improve the Regex performance for my functions ?

Jquery- Dynamic added fields - prevent the last Item to be removed

I have a form field that is being duplicated / removed dynamically
See the fiddle here : http://jsfiddle.net/obmerk99/y2d4c/6/
In this feature, I also need to increment the NAME of the field ( and ID etc.. ) like so :
o99_brsa_settings[brsa_dash_wdgt_content][0]
o99_brsa_settings[brsa_dash_wdgt_content][1]
o99_brsa_settings[brsa_dash_wdgt_content][2] ...
It is working, but the problem is that when I add / remove fields , when it gets to the last ( actually first one ) , it will give me "undefined" and will not add anymore fields .
To see the problem you will need to "play" a bit with add/remove .
I believe the main problem is how to keep all of those on the same array level if we have [0] and [0][2]
I am far from a JS guru, and this code was somehow assembled from various sources. But I am kind of stuck right now, So any help will be appreciated .
Try this way:
$(function () {
$(".addScnt").on("click", function () {
var i = checkNumberOfElements();
var scntDiv = $("div[id^='widget_dup']:last");
var prevDiv = scntDiv.clone();
var newname = $(prevDiv).find("textarea").attr("name").substring(0, $(prevDiv).find("textarea").attr('name').indexOf(']'));
prevDiv.find('textarea').attr('name', newname + "][" + i + "]");
prevDiv.find('textarea').attr('id', newname + "][" + i + "]");
prevDiv.find('label').attr('for', newname + "][" + i + "]");
prevDiv.attr('id', $(prevDiv).attr('id') + "_" + i);
$(scntDiv).after(prevDiv);
});
$(document).on("click", ".remScnt", function (event) {
var i = checkNumberOfElements();
if (i <= 1) {
return false;
} else {
var target = $(event.currentTarget);
target.parent("div").remove();
}
});
});
function checkNumberOfElements() {
// Number of textareas
var i = $("textarea[name^='o99_brsa_settings[brsa_dash_wdgt_content]']").length;
// Number of divs
// var i = $("div[id^='widget_dup']").length;
if (typeof i === undefined) {
return 0;
} else {
return i;
}
}
Demo: http://jsfiddle.net/y2d4c/7/

Using values from Array to execute function in jQuery

I have a page that contains several divs. The divs have the same postfixes but different prefixes. What I want to do is loop through them and if one of them contain a 0 hide the description div.
The divs look like this:
<div id="first_desc">First</div><div id="first_note1">0</div>
<div id="second_desc">Second</div><div id="second_note1"></div>
<div id="third_desc">Third</div><div id="third_note1"></div>
My script is this:
$(document).ready(function() {
// Variables
var firstdiv = 'first';
var seconddiv = 'second';
var thirddiv = 'third';
// Array
var myArray = new Array(firstdiv, seconddiv, thirddiv);
for(var x = 0; x < myArray.length; x++) {
if('$("#' + myArray[x] + '_note1").text() === $.trim("0")') {
'$("#' + myArray[x] + '_desc").hide()';
}
}
});
So far this code is not working. Can anyone help me? Thanks.
You have some extra ' in there.
if($("#" + myArray[x] + "_note1").text() === $.trim("0")) {
$("#" + myArray[x] + "_desc").hide();

Long string pagination done right

I want to split a long text into smaller chunks, that will act as pages.
var longText = document.getElementById('content').innerHTML;
for (i=0; i<10; i++) {
var page = longText.substring(i*100,(i+1)*100);
document.write(page + "<br /><hr />");
}
See it here on jsfiddle.
This code splits the text, but in a stupid way, cutting also words in half.
It would be far better, for example, creating substrings ending at the last space in a certain number of characters (count 100 characters, then go back to the last space).
How would you achieve it?
Second shot
Third shot
I would use:
function paginate(longText, pageSize) {
var parts = longText.split(/[ \n\t]/g);
if (parts.length == 0)
return [];
var pages = [parts.unshift()];
for (var i = 0; i < parts.length; i += 1) {
var part = parts[i];
if (part.length + pages[pages.length - 1].length < pageSize) {
pages[pages.length - 1] += " " + part;
} else {
pages.push(part);
}
}
return parts;
}
For those looking for a working answer:
<div id="long-text">Lorem ipsum [...]</div>
<script>
var splitter = function(id) {
var longText = document.getElementById(id).innerHTML;
var pageLenght = 200;
var charsDone = 0;
var htmlBefore = "<p>";
var htmlAfter = "</p>";
while (charsDone <= longText.length && (pageLenght+charsDone)<longText.length) {
var pageBox = longText.substr(lastSpace,pageLenght);
var lastSpace = charsDone + pageBox.lastIndexOf(" ");
var page = longText.substring(charsDone,lastSpace);
document.write(htmlBefore + page + htmlAfter);
charsDone = lastSpace;
}
document.write(longText.substr(lastSpace,pageLenght));
}
splitter("#long-text");
You can easily use arrays instead of writing to document.
You will also want to set your html to your needs, do it in htmlBefore and htmlAfter.
See it in action here.

Categories

Resources