Reputation: 8967
I started using JQuery a while ago to do small stuff, but increasingly JQuery has become part of the overall workflow and it's starting to get messy:
UI messages spread all over the code, hover image buttons found in different places, and a few other code smells. Things like this:
$("#button").hover(function(){
$(this).attr("src",ROOT_PATH + "images/button_01b.png");
$(this).css("margin", "-4px 0 0 0");
}, function(){
$(this).attr("src",ROOT_PATH + "images/button_01.png");
$(this).css("margin", "0 0 0 0");
});
Of course, when I want, say, to test different buttons, I have to search every instance of the button's path and replace it. Similar problem for the messages.
I also have a problem with keeping some configuration variables synched with the server code: I use a configuration file in PHP to set a few site-wide settings like the path to the root folder and the visitor's language.
In PHP, I simply call a configuration file which figures out the right paths and languages, but the JQuery files have no access to these variables.
I can think of a few solutions, like outputting the variables in PHP to HTML hidden divs and then reading the content of these divs with JQuery, but it feels like a bad hack and besides, I imagine this problem has been solved many times over.
What is the standard solution to deal with this?
Upvotes: 2
Views: 468
Reputation: 1733
After reviewing your code, I have adapted and restructured my answer. I tried to custom-fit it to your needs while keeping it as generic as possible so also other viewers could use it in future.
Easy things first. For my site-wide, server-shared configurations, I usually use something like this:
<script>
<!--[CDATA[
var ROOT_URL = "<?php echo $config["root_url"] ?>";
]]-->
</script>
This of course is useful only for simple configurations that do not require frequent updates.
A more elegant approach to retrieving site-wide configurations that even allows for more complex values - for example such that contain double quotes - would be to json_encode
them and let jQuery decode the JSON again. This would also allow you to easily update these configurations. To avoid enclosing your JSON string in further double quotes resulting in possible breaks, why not make use of the fact that browsers generally do not parse whatever is in between <script>
tags?
<script type="text/json" id="config">
<?php echo json_encode($config) ?>
</script>
Is totally valid as it will not be interpreted by the JavaScript engine and is generally ignored by the browser. Your script can then access it like this:
var config = $.parseJSON($('#config').html());
I learned this little trick while learning to work with WebGL. Although it is generally recommended to wrap script content in CDATA, in this case it would be more difficult to extract the contents and I consider it to be rather old-fashioned, tbh.
You clearly don't know the true power of jQuery. Calls like these
$('#status').html("<h2>Thanks! We will get back to you as soon as possible.</h2>")
.css('margin', '-300px 0 0 250px')
.css('background', '#C9DDFC')
.css('width', '500px')
.css('border', '2px solid black')
.css('position', 'relative')
.css('z-index', '100')
;
function userNameOk( userName ) {
var queryType = "checkUsrName";
var usrName = userName;
var usrNameOk = false;
$.ajax
({
async: false,
url: ROOT_PATH +'handlers/buyer_data_handler.php',
type: 'POST',
data: 'usrName=' + usrName + '&queryType=' + queryType,
success: function(result)
{
var obj = jQuery.parseJSON(result);
if (obj.usrNameOk == "yes")
{
usrNameOk = true;
}
else
{
usrNameOk = false;
alert(obj.msg);
}
}
});
return usrNameOk;
}
Can be done simply like this:
$('#status').html("Bla").css({
margin : '-300px 0 0 250px',
background : '#c9ddfc',
width : 500, // jQuery doesn't even need 'px' for some styles! But don't stringify them...
border : '2px solid black',
position : 'relative',
zIndex : 100 // Camel case for attributes with dashes.
});
function userNameOk( userName ) {
var usrNameOk;
$.ajax({
async : false,
url : ROOT_PATH + 'handlers/buyer_data_handler.php',
type : 'POST',
data : {
usrName : userName,
queryType : 'checkUsrName'
},
dataType : 'json',
success : function ( result ) {
usrNameOk = result.usrNameOk == 'yes'; // !! makes sure you get a boolean value.
if( !usrNameOk ) {
alert(result.msg);
}
}
});
}
Note that jQuery.ajax
accepts a plain JS object as data
parameter and also allows you to specify the expected server response type. If you tell jQuery that you expect JSON, it will automatically parse it into an object - unless it is malformed, but in that case, your approach would error, too.
If I don't state this, the community would. But it is generally discouraged to use synchronous AJAX requests - the A stands for Asynchronous in the first place - for the simple fact that it will block the website from performing any other operation until it has completed. Your audience will not approve if your server can't keep up with all the requests or if their own connection is very slow. In fact, at first they probably won't even realize it's a request that causing the trouble since the standard user has no idea what's going on behind the scenes.
It is better to use a "loading image" - I think synchronous requests would even block that animation - together with that callback of yours. That's what it is primarily intended for.
These are just very few examples of what jQuery enables you to do. Investing time in learning how to work with jQuery really is worth it.
Shift repetitive algorithms such as popping up a modal box or a defined sequence of animations to some additional functions. This is especially helpful if you have a single, very long function.
Even though parts of that function may only be needed once, it still helps a great bit to split it up into several sub-functions. Several small functions are easier to understand when called in a larger function than one huge function of dozens of lines of code.
It might even be a good idea to have modular development code - i.e. load multiple JS files on your test site but when deploying, merge them and use a minifier.
A last great tip for structuring your code: Generalize it!
While this is the beginning of your function:
$("#regError").html(""); // reset error message
var referer = $("#referer").val();
var clientIP = $("#clientIP").val();
var lastName = $("#lastName").val();
var firstName = $("#firstName").val();
var username = $("#newUsrName").val();
var password = $("#usrPass").val();
var confirmPass = $("#confirmUsrPass").val();
var email = $("#usrEmail").val();
var website = $("#usrWebsite").val();
var errors = "";
var error_msg = "";
if (userNameOk(username))
{
$("#newUsrName").css("border", "2px solid green");
$("#available").html(" OK");
}
else
{
$("#newUsrName").css("border", "2px solid red");
$("#newUsrName").focus();
}
if (lastName.length <3)
{
$("#lastName").css("border", "2px solid red");
errors++;
}
else
{
$("#lastName").css("border", "2px solid green");
}
if (firstName.length <3)
{
$("#firstName").css("border", "2px solid red");
errors++;
}
else
{
$("#firstName").css("border", "2px solid green");
}
if (password.length <6)
{
$("#usrPass").css("border", "2px solid red");
$("#confirmUsrPass").css("border", "2px solid red");
error_msg += "The password must be at least 6 characters long<br>";
errors++;
}
else if (confirmPass != password)
{
$("#usrPass").css("border", "2px solid red");
$("#confirmUsrPass").css("border", "2px solid red");
error_msg += "The confirmation password doesn't match the password.<br>";
errors++;
}
else
{
$("#usrPass").css("border", "2px solid green");
$("#confirmUsrPass").css("border", "2px solid green");
}
if (!isValidEmail(email))
{
$("#usrEmail").css("border", "2px solid red");
error_msg += "A valid email is needed to validate your registration.<br>";
errors++;
}
else
{
$("#usrEmail").css("border", "2px solid green");
}
if (errors > 0)
{
$("#regError").html(error_msg).css("color", "red");
return false;
}
it doesn't only qualify for other structuring tips, but also for generalization. This clearly is a validation algorithm. First of all, I thought I should let you know that HTML5 specs define a browser native validation implementation. Thus by using HTML5 attributes, in many cases you could spare almost all of these lines of code...
Have a look at Form Attributes over at W3Schools!
However, not every browser already implements this feature. So it would indeed make sense to use Modernizr to detect browser support and if necessary lazy-load a plugin or so.
Then, your code could look like this instead:
function validate( form ) {
var valid = true;
$('input[required][name], textarea[required][name]', form).each(function( index, elem ) {
var $elem = $(elem),
val = $elem.val(),
pattern = $elem.attr('pattern').
regex = null;
// Validation pattern provided?
if( typeof pattern !== 'undefined' && pattern !== false ) {
regex = new RegExp(pattern);
}
// Validation through pattern
// For minimum length, see https://stackoverflow.com/questions/10281962/is-it-minlength-in-html5
if( regex ) {
if( !regex.match(val) ) {
valid = false;
}
}
else {
if( val.trim().length === 0 ) {
valid = false;
}
}
if( !valid ) {
notifyRequired(elem);
}
});
return valid;
}
This is only a simple example, but could be used at different places - i.e. all of your forms. This little script makes use of few HTML5 attributes even though they may not be supported yet. Oh, while I'm at it, have a look at SO this question.
As a matter of fact, generic code like this could be used in some sort of core library that you share among all your website projects and thus spares you repetitive work.
Edit: Since you asked, it actually is fairly easy to adapt support for custom treatment by, for example, using an event listener. I have written a simple jsFiddle for demonstration.
Every event object accepts a return value, including native events. jQuery's Event object provides the result
property which is automatically the returned value of the last event listener. If undefined, simply do the standard testing. Otherwise somehow use the result. My example simply sets valid
to false if necessary.
The most relevant part included in the above validate
function looks like this:
var jqEvt = $.Event('validate');
$(this).trigger(jqEvt);
if( typeof jqEvt.result !== 'undefined' ) {
if( !jqEvt.result ) {
valid = false;
}
}
else {
// All the other stuff...
}
We must explicitly use the factory or constructor in order to be able to use the result value of our handler(s).
At the beginning of a function, declare all your variables. This not only helps in debugging your code. Due to the nature of JS, when not in strict mode, all undeclared variables will be used regardless. However, when you then later in your code "re-declare" them, they will be re-initialized to the value undefined
. If you don't already know, read some about undefined
- it is quite special.
This is especially useful when pausing development of your code or collaborating as it shows you which variables not to use further down the code.
It is not required to declare each and every variable with var
. You can separate declarations with ,
.
// Change all this...
var title = $('#select_title').val();
var lastName = $('#lastName').val();
var firstName = $('#firstName').val();
var company = $('#company').val();
var email = $('#email').val();
var phone = $('#phone').val();
var fullName = $('#fullName').val();
var instructions = $("#comments").val();
var QuoteData = "SourceLang=" + source_lang +
"&TargetLangs=" + target_langs +
"&Wordcount=" + wordcount +
"&FilesToTranslate=" + filesToTranslate +
"&title=" + title +
"&lastName=" + lastName +
"&firstName=" + firstName +
"&Company=" + company +
"&Email=" + email +
"&Phone=" + phone +
"&fullName=" + fullName +
"&instructions=" + instructions;
// to that:
var title = $('#select_title').val(),
lastName = $('#lastName').val(),
firstName = $('#firstName').val(),
company = $('#company').val(),
email = $('#email').val(),
phone = $('#phone').val(),
fullName = $('#fullName').val(),
instructions = $("#comments").val(),
QuoteData = "SourceLang=" + source_lang +
"&TargetLangs=" + target_langs +
"&Wordcount=" + wordcount +
"&FilesToTranslate=" + filesToTranslate +
"&title=" + title +
"&lastName=" + lastName +
"&firstName=" + firstName +
"&Company=" + company +
"&Email=" + email +
"&Phone=" + phone +
"&fullName=" + fullName +
"&instructions=" + instructions;
I find that even removing those few additional var
s makes the code slightly cleaner. Also this is subject to the tip above. Since this is all a set-up for the data sent via a request to the server, you could also simply do this:
$.ajax({
url : 'foo.bar',
data : {
SourceLang : source_lang,
TargetLangs : target_langs,
Wordcount : wordcount,
FilesToTranslate : filesToTranslate,
// ... and so forth
}
});
As a matter of fact, you could even write another function that generates a name : value map of all input elements of your form. Something like this, which again could be used in different forms.
function getFields( form ) {
var result = {};
// Short hand for $(form).find('input textarea')
$('input[name] textarea[name]', form).each(function(index, elem) {
var name = $(elem).attr('name'),
val = $(elem).val(),
tmp;
if( name.indexOf('[]') > -1 ) {
name = name.substring(0, name.indexOf('[]'));
if( name in result ) {
result[name].push(val);
}
else {
result[name] = [val];
}
}
else {
result[name] = val;
}
});
return result;
}
Group lines of codes that relate to each other with blank lines and comment them. Spares you to first understand what your code does before you can move on - again. Comments aren't only useful when collaborating, but also for yourself.
Also pay attention not to use unnecessary variables like in your first two functions. The less code that needs understanding, the better.
function login( userName, password ) {
var usrName = userName; // <-- totally
var pass = password; // <-- redundant
}
On a side note, always remember: As global as necessary, as local as possible. Though this is more important for other programming languages such as Java and C++, it would explain why a lot of JS developers, including me, like to enclose their entire code in an anonymous function like this:
(function(w, d, $){
// ... your code
})(window, document, jQuery);
I've heard some particular benefits from using w
and d
instead of window
and document
like this, but I can't quite recall. However, using jQuery
like this does have a notable advantage: should you happen to use another library overwriting $
, you can still make use of it referring to jQuery
without interfering with other code. For example, this is very useful when developing Joomla frontend, which ships with Mootools.
The following tips are biased and meant to assist in the maintenance of large projects.
In my opinion, a rather professional tool is the Google Closure Compiler, though I do suspect the abbreviation to be an immtiation of the GNU Compiler Compilation. It has rather less to do with cleaning up your code, but can help to keep a clear overview over large projects.
The Closure Compiler allows you to not only compress JavaScript, but also to compile different JavaScript files into a single one. There are three different modes in which you can operate the Closure Compiler. If not running in advanced mode, I'd use it only for automatically minifying multiple source files into one.
I'll briefly explain why I like to use the Closure Compiler in advanced mode for larger projects.
Regarding the annotations, the Closure Compiler allows tagging an object as enum
if the values are primitives or strings. Variables tagged const
won't be variables in the minified code. Every reference to that particular constant will be replaced with the actual value for efficiency. It offers many, many more useful annotations, like the @protected
annotation which will only grant access to this particular variable or method to other variables and methods within the same file, comparable to 'package accessibility' in Java.
Now using the Google Closure Compiler really isn't an easy thing and I really don't recommend it for smaller projects as there are way better solutions. It can be quite tedious to go about, especially trying to get it to do what exactly you want it to do. But once you configured it right, it really comes in handy.
These three languages are all variations of the CSS language and allow you to write a clear and tidy CSS dialect which will be compiled into (hopefully) valid CSS. While SCSS and LESS are quite similar, SASS is an alternative dialect of SCSS. However, I use LESS because SCSS and SASS require Ruby to be installed - and I simply don't want that. Another nice feature of LESS is that it automatically compiles the stylesheets if any change is detected and outputs the result to a specified file.
All these dialects allow some nifty "programming". They allow different additional programmatic elements to a certain degree. These elements though will be calculated and statically included in the document. Something like 90% - 5px
will (maybe, haven't tried) only work if one of the parent elements has a static width set. But as far as I've understood, the units must match. 5em - 5px
thus is not possible.
However, the reason why I really recommend these 'dialects' is because of selector nesting. Something that would look like this in CSS
#content .buttons .button {
background: green;
}
#content .buttons .button.active {
background: pink;
}
#content .buttons .button.active:hover {
background: purple;
}
Can be written as
#content .buttons .button {
background: green;
&.active {
background: pink;
}
&.active:hover {
background: purple;
}
}
Where &
references the selector you are currently inside of. Not only does this possibly shorten down your CSS - or rather SCSS/SASS/LESS - but also makes it easier to understand and maintain.
Well, not quite. This is just a portion of what you could really do not only to write cleaner but also more efficient code. If I were to write all that down, I could actually hold a seminar and demand money for that. And since I'm not paid and already spent more than two hours for all this, I think I definitely should put this to an end. I have to take care of my own stuff nonetheless.
Upvotes: 3