When to combine common functionality? - A public static object w/ initialization - javascript

This is a common occurence when I code...I see some code that looks kind of alike..and I know that it is obviously not good to have redundant functionality in my code.
However , is this absolute? 0 Redundancy? I have two functions below, which look kind of alike. ViewH.bookmark and ViewH.tweet.
I'm trying to decide if I should pull out the common functionality into a function called ViewH.mark().
EDIT
var ViewH = {
MARK:
{
FIELD: '|',
ROW: '||',
PASS: '<xx_p>',
FAIL: '<xx_f>'
},
return_string: '',
mark: function(passed_function, embeddedAml)
{
var return_string,
first_split,
element_count,
second_split;
return_string = '';
first_split = embeddedAml.split( ViewH.MARK.ROW );
for( element_count=0; element_count < first_split.length; element_count++)
{
second_split = first_split[element_count].split( ViewH.MARK.FIELD );
passed_function(second_split);
}
return ViewH.return_string;
},
bookmark: function ( embeddedAml )
{
ViewH.return_string='';
return ViewH.mark(ViewH.bookmark_inner, embeddedAml);
},
tweet: function ( embeddedAml )
{
ViewH.return_string='';
return ViewH.mark(ViewH.tweet_inner, embeddedAml);
},
portfolio: function ( embeddedAml )
{
ViewH.return_string='';
return ViewH.mark(ViewH.portfolio_inner, embeddedAml);
},
bookmark_inner: function ( second_split )
{
ViewH.return_string = ViewH.return_string
+ '<img name="bo_im" class="c" src="'
+ 'http://www.google.com/s2/favicons?domain='
+ second_split[0]
+ '" onerror="Arc.BookmarkError(this)"><a target="_blank" name="bookmark_link" class="b" href = "'
+ second_split[1]
+ '">'
+ second_split[2]
+ '</a>';
},
tweet_inner: function ( second_split )
{
ViewH.return_string = ViewH.return_string
+ '<div class="Bb2b"><img class="a" src="'
+ Constant.PICTURES + second_split[ 0 ]
+ '.jpg" alt=""/><a class="a" href="javascript:void(0)\">'
+ second_split[ 1 ]
+ ' posted '
+ ViewH.pretty( second_split[ 2 ],second_split[ 3 ] )
+ '</a><br/><p class="c">'
+ second_split[ 4 ]
+ '</p></div>';
},
portfolio_inner: function ( second_split )
{
if( ( second_split[ 1 ] === 'docx' ) || ( second_split[ 1 ] === 'xlsx' ) )
{
ViewH.return_string = ViewH.return_string
+ '<img name="bo_im" class="c" src="'
+ Constant.IMAGES + second_split[1]
+ '.ico"><a target="_blank" name="bookmark_link" class="b" href = "/'
+ Constant.ROOT
+ second_split[1]
+ '/'
+ second_split[0]
+ '.'
+ second_split[1]
+ '">'
+ second_split[0]
+ '.'
+ second_split[1]
+ '</a>';
}
else
{
ViewH.return_string=ViewH.return_string
+ '<simg name="bo_im" class="c" src="'
+ Constant.IMAGES
+ 'generic'
+ '.ico"><a target="_blank" name="bookmark_link" class="b" href = "'
+ Constant.TEXT
+ second_split[0]
+ '.txt">'
+ second_split[0]
+ '.'
+ second_split[1]
+ '</a>';
}
},

This is a great question, but there is no answer that will apply to all cases. It really is going to depend on what your code looks like. Redundancy is generally to be avoided but it is sometimes worse to over-engineer your code and try to make it fit into a box that it does not really fit into.
In your case you could definitely benefit from taking common code and pulling it into a common method. It looks like the only difference between your methods is the rendering part and it would be simple to pass a rendering function into your "mark" method.
Your "mark" method would look a bit like this:
mark: function(embeddedAml, renderer) {
var return_string,
first_split,
element_count,
second_split;
return_string = '';
first_split = embeddedAml.split( ViewH.MARK.ROW );
for( element_count=0; element_count < first_split.length; element_count++)
{
second_split = first_split[element_count].split( ViewH.MARK.FIELD );
return_string = return_string + renderer(second_split);
}
return return_string;
}
You would keep your bookmark and tweet methods but they would change as well:
bookmark: function (embeddedAml) {
return this.mark(embeddedAml, function(data) {
return '<img name="bo_im" class="c" src="' +
'http://www.google.com/s2/favicons?domain=' +
data[0] +
'" onerror="Arc.BookmarkError(this)"><a target="_blank" name="bookmark_link" class="b" href = "' +
data[1] + '">' +
data[2] + '</a>'
});
}
Now your rendering code (the only code that was different) is controlled independently, but the code that overlapped is in a common place and if it changes you only have to update it in one place.

Generally, yes.
One deciding factor is whether the code is similar coincidentally, or because it performs a similar task. If the latter is true, should you change the functionality of one in the future (particularly that part of the functionality which is shared), will you also want to change the functionality in the other? If so, that makes your decision easy - merge the code where you can.
Even if the code is similar coincidentally, it may still make sense to create a generic library function that cleans up your code.

I would definitely attempt to combine them. You'll notice that the body of the for loop is the only thing that's different between the two. Here's one approach (most of ViewH elided):
var ViewH = {
bookmark: function(embeddedAml) {
return ViewH.combinedFunc(embeddedAml, function(parts) {
return '<img name="bo_im" class="c" src="' +
'http://www.google.com/s2/favicons?domain=' +
parts[0] +
'" onerror="Arc.BookmarkError(this)"><a target="_blank" name="bookmark_link" class="b" href = "' +
parts[1] + '">' +
parts[2] + '</a>';
});
},
combinedFunc: function (embeddedAml, handler) {
var return_string,
first_split,
element_count,
second_split;
return_string = '';
first_split = embeddedAml.split(ViewH.MARK.ROW);
for(element_count=0; element_count < first_split.length; element_count++) {
second_split = first_split[element_count].split(ViewH.MARK.FIELD);
return_string = return_string + handler(second_split);
}
return return_string;
},
}
You could easily do the same thing for tweet. Clearly, you'll want to name the function something better than combinedFunc, but you'll need to choose that name based on context.

Related

How to run an if statement inside of an append statement

I'm trying to run an if statement inside of an append but it seems to come out as true regardless of what I expect the result to be. The problematic code is on lines 13 and 14, Code:
I've tried declaring it using the typical method of...
if(xxx != "false") but this broke the entire page and no JS ran.
$.getJSON("https://raw.githubusercontent.com/Fazyyy/Fazyyy.github.io/master/data/product.json", function(posts) {
for(var post = 0; post < posts.length; post++) {
$(".posts").append("<li " +
"data-price='"+ posts[post].price + "'" +
"data-reviews='"+ posts[post].reviews + "'" +
"data-name='"+ posts[post].name + "'" +
"data-saving='"+ posts[post].was_price + "'" +
">" +
"<div class='spacer'>" +
"<img src='./img/" + posts[post].img +".jpg' />" +
"<h2>" + posts[post].name + "</h2>" +
"<p>" + "£<span class='price'>" + posts[post].price + "</span></p>" +
( posts[post].was_price != "false" ? "<p class='red'>£<span class='strike'>" + posts[post].was_price + "</span></p>" : '<p></p>') +
( posts[post].reviews != "false" ? "<p class='reviews'>" + posts[post].reviews + "% reviews score" : '<p></p>') +
"<p><a href='#' class='basket'>Add To Basket</a></p>" + "</div>" + "</li>"
);
}
});
Quick answer is don't.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
you should abstract your problems following the DRY principles 'dont repeat yourself' and focus on making it easy to read and everything just has one responsibility.
you can see in my example that it almost looks like it would in html but in your example you have to struggle to parse the logic.
Another few things to note was you were doing something != 'false' where you had to do != instead of !== because you were comparing 'false' to false. read up on type equality
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
$.getJSON("https://raw.githubusercontent.com/Fazyyy/Fazyyy.github.io/master/data/product.json", function(posts = []) {
// appending only after we've created all posts to stop appending multiple times
appendToPosts(posts.reduce((acc, post) => acc + createPost(post), ''))
});
function appendToPosts(html) {
$(".posts").append(html);
}
function createPost(post) {
return `<li
data-price='${post.price}'
data-reviews='${post.reviews}'
data-name='${post.name}'
data-saving='${post.was_price}'>
<div class='spacer'>
<img src='./img/${post.img}.jpg' />
<h2>${post.name}</h2>
<p>£<span class='price'>${post.price}</span></p>
${hasPreviousPrice(post)}
${hasReviews(post)}
</div>
</li>`;
}
function hasPreviousPrice(post) {
return post.was_price ?
`<p class='red'>£<span class='strike'>${post.was_price}</span></p>` :
``
}
function hasReviews(post) {
return post.reviews ?
`<p class='reviews'>${post.reviews}% reviews score` :
`<p><a href='#' class='basket'>Add To Basket</a></p>`
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="posts"></div>
Here's another approach. This one does not append multiple times, as it's a more expensive operation.
const productTmpl = ({was_price, reviews, price, name, img}) => {
const wasPriceNode = was_price
? `<p class="red">£<span class="strike">${was_price}</span></p>`
: "<p></p>"
const reviewsNode = reviews
? `<p class="reviews">${reviews}% reviews score</p>`
: "<p></p>"
return `
<li data-price="${price}"
data-reviews="${reviews}"
data-name="${name}"
data-saving="${was_price}"
>
<div class="spacer'">
<img src="./img/${img}.jpg"/>
<h2>${name}</h2>
<p>£<span class="price">${price}</span></p>
${wasPriceNode}
${reviewsNode}
<p>
Add To Basket
</p>
</div>
</li>
`;
}
$.getJSON(
"https://raw.githubusercontent.com/Fazyyy/Fazyyy.github.io/master/data/product.json",
products => {
const listItems = products.reduce((acc, cur) => acc + productTmpl(cur), "");
$(".posts").html(listItems);
}
);

Multiple of the similar "Select Option" ID , Dynamic Javascript Code

1) I need to find a way to have multiple of id="select-repo" because every single "add another item" is the same. "Add Another Item" is suppose to add another row of html with that id="select-repo" for now it's just a empty textbox.
Generate Functions on the go? Dynamic Functions? I can easily loop the Select box but not the "$('#select-repo').selectize({" function i believe.
2) After number 1 is solved, I need to find a way to know which row of data to update after an option has been selected.
3) Is this easier to get done with VUE.JS, since i'm using laravel , integration should be easier with Vue.JS
What is your advice, I was told to use stuff like ReactJS / styled components? is there anyway to not switch framework to just get this done?
Please Advice.
HTML Code
<td><select id="select-repo" class="repositories"></select></td>
JS Code
<script>
//<select id="select-repo"></select>
$('#select-repo').selectize({
valueField: 'url',
labelField: 'name',
searchField: 'name',
options: [],
create: false,
render: {
option: function(item, escape) {
return '<div>' +
'<span class="title">' +
'<span class="name"><i class="icon ' + (item.fork ? 'fork' : 'source') + '"></i>' + escape(item.name) + '</span>' +
'<span class="by">' + escape(item.username) + '</span>' +
'</span>' +
'<span class="description">' + escape(item.description) + '</span>' +
'<ul class="meta">' +
(item.language ? '<li class="language">' + escape(item.language) + '</li>' : '') +
'<li class="watchers"><span>' + escape(item.watchers) + '</span> watchers</li>' +
'<li class="forks"><span>' + escape(item.forks) + '</span> forks</li>' +
'</ul>' +
'</div>';
}
},
score: function(search) {
var score = this.getScoreFunction(search);
return function(item) {
return score(item) * (1 + Math.min(item.watchers / 100, 1));
};
},
load: function(query, callback) {
if (!query.length) return callback();
$.ajax({
url: 'https://api.github.com/legacy/repos/search/' + encodeURIComponent(query),
type: 'GET',
error: function() {
callback();
},
success: function(res) {
callback(res.repositories.slice(0, 10));
}
});
},
onChange: function(value) {
alert(value);
}
});
</script>
Since id can not be the same, you can define a global variable like index to memo the count as part of id;
Like to dynamically add select with id "select-repo"+index; e.g. select-repo1, select-repo999
Here's an example:
var index = 1;
function addSelect(){
$('#somewhere').append('<select id="select-repo'+index+'">');
$('select-rep'+index).selectize(){
....
};
index++;
}
And you can easily get the select index by parse Id string.

Issue trying to escape a string in javascript plugin

I'm trying to use a Bootstrap Tab plugin for CKEditor, and it works well. But, when i use a single quote on content (doesn't matter if the string is "'", or " or '), it broke up.
I guess is just a escape issue. I tried with escape() and encodeURI(), but fails.
Error: SyntaxError: missing ) after argument list;
Source:
x)PRE3015 Pino 1/2'' 15mm Ponta Balistica Aço 3,1 15
The piece of plugin code for tabs is:
data: function() {
var bootstrapTab_d = new Date();
var bootstrapTab_id = bootstrapTab_d.getTime();
var bootstrapTab_item = bootstrapTab_contents = '';
for (var bootstrapTab_i = 0; bootstrapTab_i <= this.data.bootstrapTab_total; bootstrapTab_i++) {
eval("bootstrapTab_title = this.data.bootstrapTab_item" + bootstrapTab_i);
bootstrapTab_title = bootstrapTab_title != undefined ? bootstrapTab_title : '';
eval("bootstrapTab_content = this.data.bootstrapTab_content" + bootstrapTab_i);
bootstrapTab_content = bootstrapTab_content != undefined ? bootstrapTab_content : '';
eval("bootstrapTab_itemClass = this.data.bootstrapTab_itemClass" + bootstrapTab_i);
bootstrapTab_itemClass = bootstrapTab_itemClass != undefined ? bootstrapTab_itemClass : '';
eval("bootstrapTab_contentClass = this.data.bootstrapTab_contentClass" + bootstrapTab_i);
bootstrapTab_contentClass = bootstrapTab_contentClass != undefined ? bootstrapTab_contentClass : '';
if (bootstrapTab_title) {
bootstrapTab_item += '<li role="presentation" class="' + bootstrapTab_itemClass + '">' + bootstrapTab_title + '</li>';
bootstrapTab_contents += '<div role="tabpanel" class="' + bootstrapTab_contentClass + '" id="tab' + bootstrapTab_id + '_' + (bootstrapTab_i + 1) + '">' + bootstrapTab_content + '</div>'
}
}
this.element.setAttribute('id', 'collapse' + bootstrapTab_id);
this.element.$.innerHTML = '<div role="tabpanel"><ul class="nav nav-tabs" role="tablist">' + bootstrapTab_item + '</ul><div class="tab-content">' + bootstrapTab_contents + '</div></div>'
}
I'd refactor the code, and not use eval!
eval is evil http://blogs.msdn.com/b/ericlippert/archive/2003/11/01/53329.aspx
instead of
eval("bootstrapTab_title = this.data.bootstrapTab_item" + bootstrapTab_i);
use bootstrapTab_item as an array and do this
bootstrapTab_title = this.data.bootstrapTab_item[bootstrapTab_i]
really don't know if the error is there, but I think the path to find the error is refactor to eliminate those eval, that could be the source of your problem.

Add div to javascript code

Trying to add a div around some javascript code.
Here's the code I'm trying to modify:
slider.controlNavScaffold = $('<ol class="'+ namespace + 'control-nav ' + namespace + type + '"></ol>');
if (slider.pagingCount > 1) {
for (var i = 0; i < slider.pagingCount; i++) {
slide = slider.slides.eq(i);
item = (slider.vars.controlNav === "thumbnails") ? '<img src="' + slide.attr( 'data-thumb' ) + '"/>' : '<a>' + j + '</a>';
if ( 'thumbnails' === slider.vars.controlNav && true === slider.vars.thumbCaptions ) {
var captn = slide.attr( 'data-thumbcaption' );
if ( '' != captn && undefined != captn ) item += '<span class="' + namespace + 'caption">' + captn + '</span>';
}
slider.controlNavScaffold.append('<li>' + item + '</li>');
j++;
}
}
Here's the resulted outcome when you add <div class="container"> before the <ol> and closing </div> tag after </ol> in the code above...as you can see the list closes before list items make it inside:
<div class="container"><ol class="flex-control-nav flex-control-paging"></ol><li><a>1</a></li><li><a>2</a></li><li><a>3</a></li><li><a>4</a></li></div>
Here's what I'm trying to output.
<div class="container"><ol class="flex-control-nav flex-control-paging"><li><a class="">1</a></li><li><a class="flex-active">2</a></li><li><a>3</a></li><li><a>4</a></li></ol></div>
Code that isn't working:
slider.controlNavScaffold = $('<div class="container"><ol class="'+ namespace + 'control-nav ' + namespace + type + '"></ol></div>');
if (slider.pagingCount > 1) {
for (var i = 0; i < slider.pagingCount; i++) {
slide = slider.slides.eq(i);
item = (slider.vars.controlNav === "thumbnails") ? '<img src="' + slide.attr( 'data-thumb' ) + '"/>' : '<a>' + j + '</a>';
if ( 'thumbnails' === slider.vars.controlNav && true === slider.vars.thumbCaptions ) {
var captn = slide.attr( 'data-thumbcaption' );
if ( '' != captn && undefined != captn ) item += '<span class="' + namespace + 'caption">' + captn + '</span>';
}
slider.controlNavScaffold.append('<li>' + item + '</li>');
j++;
}
}
You didn't post it, but I suspect that you changed that first line to
slider.controlNavScaffold = $('<div><ol class="'+ namespace + 'control-nav ' + namespace + type + '"></ol></div>');
That will cause the code to do exactly what you describe, because the .append() calls will append to the outer element (the <div>).
Instead, leave that first line alone, and then at the end — after the <li> elements have been added — add this:
slider.controlNavScaffold.wrap( $('<div/>', { "class": "container" }) );
After that, in order to have things work properly when you actually add the stuff to the DOM, you'll want to find the parent of the <ol> and make sure that that's what you add:
slider.controlNavWrapper = slider.controlNavScaffold.parent();
(or however you want to keep track of it).

streamlining a series of javascript functions

Is there a better, more concise way to do this:
function getTweets(){
$.getJSON("http://search.twitter.com/search.json?callback=?&q=superfad",
function(data){
tweetsLoaded = true;
$.each(data.results, function(i,item){
var textPlain = item.text;
var textLinked = linkify(textPlain);
var textHashed = hashify(textLinked);
var textListed = listify(textHashed);
function linkify(tweet){
return tweet.replace(/(http:\/\/[^\s]*)/g, "<a class='twtr-link' target=\"_blank\" href=\"$1\">$1</a>");
}
function hashify(tweet){
return tweet.replace(/(^|\s+)#(\w+)/gi, function(m, before, hash) {
return before + '<a target="_blank" class="twtr-hashtag" href="http://twitter.com/search?q=%23' + hash + '">#' + hash + '</a>';
});
}
function listify(tweet) {
return tweet.replace(/\B[#@]([a-zA-Z0-9_]{1,20})/g, function(m, username) {
return '<a target="_blank" class="twtr-atreply" href="http://twitter.com/intent/user?screen_name=' + username + '">#' + username + '</a>';
});
}
$("#twitter_results").append('<li class="twitter"><img class="twitter_img" src="' + item.profile_image_url + '"/>'+ textListed + '</li>');
});
});
} //end getTweets
// define this globally
function stuffify(match, group1, group2) {
switch (group1 || group2) {
case 'http':
return '<a class="twtr-link" target="_blank" href="' + match + '">' + match + '</a>';
case '#':
return '<a class="twtr-hashtag" target="_blank" href="http://twitter.com/search?q=' + encodeURIComponent(match) + '">#' + match + '</a>'
case '#':
return '<a class="twtr-atreply" target="_blank" href="http://twitter.com/intent/user?screen_name=' + encodeURIComponent(match) + '">#' + match + '</a>';
default:
return match;
}
}
function(data){
tweetsLoaded = true;
var interestingParts = /(http):\/\/\S+|(#|#)[^\s.,!?;^()\[\]<>{}]+/g;
$.each(data.results, function(i,item) {
var newText = item.text.replace(interestingParts, stuffify);
$("#twitter_results").append('<li class="twitter"><img class="twitter_img" src="' + item.profile_image_url + '"/>'+ newText + '</li>');
});
}
I would realign the code such that the text manipulation functions (linkify, hashify, listify) are not inside of the each function, nor are they even inside of getTweets itself. If it's in getTweets, then every time you call that function they have to be redefined. Worse, inside of the each where you had them, those functions are redefined for every item in the returned tweet-set.
Also, there is no need to store the return of each of those functions in it's own var since you only use them once afterward and aren't performing any checks before using them. Just nest the function calls.
Finally, since you're calling append over and over on the same element (inside the each) I pre-queried that element instead of querying for it every time the each function is executed
Those changes, along with some things I do for my own performance preferences are exhibited in the code sample pasted below.
There are other things I would do, however I won't show them here--you can see it in my posted answer at Trouble Converting jQuery Script to Plugin -- a question about making a jQuery plugin which does the same ting as your code). You should store the formatted tweets in an array rather than append each as you get it. Having built that array, you should then combine it into one string and call append once with that string. Making this a jQuery plugin would also be nice for you as it would not require you modify the code to change the targeted DOM element. Looking into use of String.prototype.link would be good as well.
(demo: http://jsfiddle.net/JAAulde/fQ3Lp/2/ )
var getTweets = ( function()
{
/* Privatized text manipulation functions */
var linkify = function( tweet )
{
return tweet.replace( /(http:\/\/[^\s]*)/g, "<a class='twtr-link' target=\"_blank\" href=\"$1\">$1</a>" );
};
var hashify = function( tweet )
{
return tweet.replace( /(^|\s+)#(\w+)/gi, function(m, before, hash)
{
return before + '<a target="_blank" class="twtr-hashtag" href="http://twitter.com/search?q=%23' + hash + '">#' + hash + '</a>';
} );
};
var listify = function( tweet )
{
return tweet.replace(/\B[#@]([a-zA-Z0-9_]{1,20})/g, function(m, username)
{
return '<a target="_blank" class="twtr-atreply" href="http://twitter.com/intent/user?screen_name=' + username + '">#' + username + '</a>';
} );
};
var $twitterResultTarget = $( "#twitter_results" );
/* The actual function which is stored in `getTweets` */
return function()
{
$.getJSON( "http://search.twitter.com/search.json?callback=?&q=superfad", function( data )
{
tweetsLoaded = true;
$.each( data.results, function( i, item )
{
$twitterResultTarget
.append( [
'<li class="twitter"><img class="twitter_img" src="',
item.profile_image_url,
'"/>',
listify( hashify( linkify( item.text ) ) ),
'</li>'
].join( '' ) );
} );
} );
}
}() );
Try to avoid using anonymous functions, its always less confusing if they all have names and are declared outside of any other functions.
I'd probably write in in the jQuery plugin style, and use split as opposed to replace with HTML in callbacks:
(function($){
function getTweets(q) {
var
$set = this,
prefixes = {
'h': 'h',
'#': 'http://twitter.com/search?q=%23',
'#': 'http://twitter.com/intent/user?screen_name='
},
classes = {
'h': 'twtr-link',
'#': 'twtr-hashtag',
'#': 'twtr-atreply'
};
$.getJSON(
"http://search.twitter.com/search.json?callback=?&q=" + encodeURIComponent(q),
function(data){
$.each(data.results, function(i, item){
var
$li = $('<li class="twitter"></li>')
.append('<img class="twitter_img" src="' + item.profile_image_url + '"/> ');
$.each(
item.text.split(
/(\s+)|(https?:\/\/[^\s]*)|(#\w+)|(#[a-zA-Z0-9_]{1,20})/g
),
function(i, chunk) {
if (/^(https?:\/\/|#|#)/.test(chunk)) {
$('<a target="_blank"></a>')
.addClass(classes[chunk[0]])
.attr('href', prefixes[chunk[0]] + chunk.substr(1))
.text(chunk)
.appendTo($li);
} else if (chunk) {
$li.append(document.createTextNode(chunk));
}
}
);
$li.appendTo($set);
});
}
);
}
$.fn.getTweets = getTweets;
})(jQuery);
Then call it like this:
$('#twitter_results').getTweets('superfad');
My $0.02

Categories

Resources