Open invenio-developers opened 10 years ago
Comment by lnielsen-cern Thursday May 22, 2014 at 11:57 GMT
See previous discussion as well http://invenio-software.org/wiki/Talk/JSFrameworks
Comment by kneczaj Thursday May 22, 2014 at 12:13 GMT
For me https://github.com/airbnb/javascript looks nice. It maybe over detailed, but I like especially a small part about jQuery selectors which I already use.
Beside the content of the instruction above I think would be useful to have:
Comment by kneczaj Thursday May 22, 2014 at 12:14 GMT
I think such a document is especially needed for summer students who will come, and write code in their way
Comment by kneczaj Thursday May 22, 2014 at 12:29 GMT
Ok, maybe everybody will write here their propositions, and we can discuss/vote them at some meeting later.
Comment by kneczaj Thursday May 22, 2014 at 12:35 GMT
I like naming jQuery selectors with $ sign at the beginning. Also it's important to first save a selector to a variable, and name it somehow, and then use the variable instead of repeating the query to get the selector. It's exactly what is here: https://github.com/airbnb/javascript#jquery Adavantages are:
Comment by drjova Thursday May 22, 2014 at 12:40 GMT
It would be nice if we use a template engine (ex. hogan) to serve html
Comment by greut Thursday May 22, 2014 at 12:40 GMT
@kneczaj And then you start using Angular.js and your world falls apart. Big no-no for $variables
.
Comment by kneczaj Thursday May 22, 2014 at 12:45 GMT
jQuery events
I propose not to put big functions directly inside events functions like:
$this.on('click', function(event) { /* here */ });
more than two-three lines of code. When the function is more complicated let's make it externally with proper arguments passed to it (not just jQuery event) and then trigger the function inside the event binding like:
function runQuery(a, b, c) {
}
$this.on('click', function(event) {
runQuery(event.data.a, event.data.b, event.data.c);
});
Advantages:
There is one issue with it. If you want to run it inside a class, you don't have 'this' inside the event so if runQuery() function above is a class method then such a pattern is useful:
var that = this;
$this.on('click', function(event) {
that.runQuery(event.data.a, event.data.b, event.data.c);
});
Comment by kneczaj Thursday May 22, 2014 at 12:49 GMT
@greut I haven't met with Angular.js in invenio yet, I also haven't used it by myself, so I am not so familiar with the topic.
@drjova +1 for hogan - btw would be nice to have voting feature here ;)
Comment by greut Thursday May 22, 2014 at 12:52 GMT
$this
!?! really?Anyway, all these points must be brought during code reviews, arguing in the void has close to no values.
Comment by kneczaj Thursday May 22, 2014 at 12:56 GMT
@greut It's just a random example, but if $this is a jQuery selector made by:
var $this = $(this);
it makes sense for me
Comment by jalavik Thursday May 22, 2014 at 13:51 GMT
Agree with @kneczaj that big inline functions sometimes should be avoided. If the function is big, chances are that parts of it can be split into more functions and re-used elsewhere.
@drjova +1 for hogan and https://github.com/airbnb/javascript looks reasonable at first glance.
Anyway, as we are expanding on the amount of JS being written in Invenio I suggest we land on some JSFramework, as @lnielsen-cern mentioned, to get more stuff and guidelines for free.
Comment by greut Friday May 23, 2014 at 07:28 GMT
I want to quote the co-author of Twitter's Flight, the slide 7 is brilliant.
- Don't let best practices do your thinking for you
- Keep it simple and human readable so it's easier to fix
Angus Croll, https://speakerdeck.com/anguscroll/stop-being-perfect
Another good one: https://speakerdeck.com/anguscroll/how-we-learned-to-stop-worrying-and-love-javascript
Comment by jirikuncar Wednesday May 28, 2014 at 14:19 GMT
We should seriously discuss usage of AngularJS or similar framework (see also https://github.com/jirikuncar/invenio/pull/207#issuecomment-44408651).
Comment by greut Wednesday May 28, 2014 at 16:33 GMT
And a javascript code loader/dependency like AMD (require.js, ...) or CommonJS (browserify, ...).
For the record, I'll be playing with ReactJS + ... + require.js for my throw-away prototype. I'm new to them and believe in do-ocracy.
Comment by kneczaj Friday Jun 06, 2014 at 16:27 GMT
This is applicable for python as well.
It is always makes code more clear to avoid making nested conditional statements.
To achieve this try to return from a function as fast as possible, and make functions to compute the results of the nested code inside if() statement.
For example instead:
function f(a) {
if (a) {
b = a.getSth()
if (b) {
c = b.getSth()
if (c) {
/* 20 lines of code here */
}
}
}
}
It is much cleaner to write:
function f(a) {
if (!a)
return;
b = a.getSth()
if (!b)
return;
c = b.getSth()
if (!c)
return;
/* 20 lines of code here */
}
This is applicable also to for
loops using continue
statement instead of return
.
Instead:
for (var i=0; i<6; i++) {
if (a[i].bool) {
b = a.getSth()
if (b) {
c = b.getSth()
if (c) {
/* 20 lines of code here */
}
}
}
}
It is much cleaner to write:
for (var i=0; i<6; i++) {
if (!a[i].bool)
continue;
b = a.getSth()
if (!b)
continue;
c = b.getSth()
if (!c)
continue;
/* 20 lines of code here */
}
jQuery Selectors
It is much cleaner and understandable for a third person when jQuery selectors are not strings with 20+ characters.
Try to save intermediate queries in variables, and then make sub-queries on them using find()
, parent()
, parents()
, children()
functions.
Avoid storing partial selectors in strings and then combining strings to get the desired one.
This is a good example of what I mean to avoid:
selector_prefix = '#'+options.prefix+options.sep+options.index_suffix+options.sep;
/* later */
var input = root.find(selector_prefix+field);
The above for sure works, but it is not understandable without debugging and very error prone when someone changes the page structure.
Regarding the number of spaces (2 or 4), do we have an agreement?
In the last discussion about jinja code style we agreed on 2, although I don't think we agreed on JavaScript. I see some new files like https://github.com/inveniosoftware/invenio/blob/pu/invenio/base/static/js/build.js or https://github.com/inveniosoftware/invenio/blob/pu/invenio/base/static/js/translate.js are using 4 spaces.
It would be good to have a common agreement as we were planning on using the grunt task jsbeautifier and we would like to use the same standard as Invenio - https://github.com/beautify-web/js-beautify#options
It is a matter of taste but it seems like the JS community is going more for 2 spaces http://atroche.org/post/30994290348/javascript-indentation so if you agree we can go for that option.
There was no consensus on 2 vs 4 in #1768, except that you shouldn't use 1,3,5,6,7 or 8 spaces, and that you should be consistent through out the file.
You can put a .jshintrc
file in your module and impose how you expect it to be formatted (https://github.com/greut/cds-demosite/commit/3fc93c40c19e0e276951aba81007b29edc81d1d3). The current state of the JS code is more problematic than its formatting.
Also we may consider chai instead of QUnit, the syntax it's more familiar, it supports language chains and more. Examples from the website:
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members');
QUnit
QUnit.assert.contains = function( needle, haystack, message ) {
var actual = haystack.indexOf(needle) > -1;
QUnit.push(actual, actual, needle, message);
};
QUnit.test("retrieving object keys", function( assert ) {
var arrayKeys = keys( [1, 2, 3] );
assert.contains( "3", arrayKeys, "Array keys" );
});
Chai
describe('Test Array', function() {
it('should not fail', function(done) {
assert.include([ 1, 2, 3 ], 3, 'array contains value 3');
});
});
A python friendly library/test runner, Jasmine:
It appears that this RFC didn't reach consensus, so no decision was made. Let me try to rekindle the discussion with some organization and some proposals.
The discussion started from code style, then touched code quality, the use of frameworks, testing, and documentation. I think these are very different problems, which require different discussions.
ag onclick
this will require A LOT of work.Related to these, and in particular to the first two, is the question of having some kind of task runner for JavaScript tasks, like Grunt or Gulp. Should I open an RFC for this?
In summary I propose to:
To follow up on yesterday's question, no, JSCS has no "autoformatting" functionality, but it's been actively worked on, so it should be here soon™: https://github.com/jscs-dev/node-jscs/issues/516.
JSCS just added the --fix
option as of 1.12 (on most of the whitespace rules). We need some better docs on it and other things though.
You guys can start with using a preset (I see airbnb was mentioned in the OP) and then nulling out any rules you don't think or adding more that you want. Also feel free to create issues/PRs for new rules/options that could be added in!
Awesome. Thanks for correcting me @hzoo!
So, let's get back to our discussion. I'll take the following code in Invenio (from https://github.com/inveniosoftware/invenio/blob/ad39db02238330f419bb73d40d708df059d05cf8/invenio/base/static/js/invenio.js#L43-L59) and rewrite it using 3 different presets: AirBnB, Crockford, jQuery.
$('[data-menu="click"]').each(function (index) {
var that = this,
$menu = $(this).parent(),
$li = $menu.parent(),
message_update = function(obj) {
$(that).unbind('click');
$.ajax({
url: $(that).attr('data-menu-source')
}).done(function (data) {
$menu.parent().append(data);
});
};
$(that).on('click', message_update);
$li.addClass('dropdown');
$menu.attr('data-toggle', "dropdown");
$menu.dropdown();
});
$('[data-menu="click"]').each(function(index) {
var _this = this;
var $menu = $(this).parent();
var $li = $menu.parent();
var messageUpdate = function(obj) {
$(_this).unbind('click');
$.ajax({
url: $(_this).attr('data-menu-source')
}).done(function(data) {
$menu.parent().append(data);
});
};
$(_this).on('click', messageUpdate);
$li.addClass('dropdown');
$menu.attr('data-toggle', 'dropdown');
$menu.dropdown();
});
$('[data-menu="click"]').each(function (index) {
var that = this,
$menu = $(this).parent(),
$li = $menu.parent(),
message_update = function (obj) {
$(that).unbind('click');
$.ajax({
url: $(that).attr('data-menu-source')
}).done(function (data) {
$menu.parent().append(data);
});
};
$(that).on('click', message_update);
$li.addClass('dropdown');
$menu.attr('data-toggle', "dropdown");
$menu.dropdown();
});
$( '[data-menu="click"]' ).each( function( index ) {
var that = this,
$menu = $( this ).parent(),
$li = $menu.parent(),
messageUpdate = function( obj ) {
$( that ).unbind( "click" );
$.ajax( {
url: $( that ).attr( "data-menu-source" )
} ).done( function( data ) {
$menu.parent().append( data );
} );
};
$( that ).on( "click", messageUpdate );
$li.addClass( "dropdown" );
$menu.attr( "data-toggle", "dropdown" );
$menu.dropdown();
} );
Personally, I'd go with number 1.
Here's also an estimate of the effort needed (obtained running jscs -p PRESET invenio/modules/*/static
):
I would like 2. Crockford with 2 space indentation but I'm also fine with 1. AirBnB with possibility to join var
definitions.
var foo = 1,
bar = 'baz',
fb = function() {
return 1;
};
1 seems good to me. Agree with Jiri that I would allow join var definitions (as long as they are properly aligned with each other)
I give my vote to 1. AriBnB and as mentioned above - with the possibility to have var definitions joined.
BTW, in regards to proposal 5:
Start a new RFC about a JavaScript task runner
What use cases do you see @jacquerie for js task runner in Invenio
in this moment ?
As a wrapper for the tooling we introduce in this and several other RFCs, for programmer convenience. For example, grunt jscs
might check the code style in all Invenio, using the right paths and excluding files which we don't need to check, without having to remember a long jscs
command line.
Another use case I see is pipelining these commands, so that a programmer only needs to type grunt lint
and she runs jshint
, jscs
and jsonlint
.
It's highly preferable (in my opinion a must) that any linting is integrated with inveniosoftware/kwalitee so that there's only one tool for checking Invenio source code. 1) It's easier for developers (just one tool to run on your commits) 2) Kwalitee is integrated with GitHub so PR's can be checked automatically.
Grunt could potentially be integrated with Kwalitee, so just wanted to emphasise that integration with kwalitee has to be thought of when taking a decision.
I would propose to extend inveniomanage
and kwalitee
to provide necessary aliases/pipelining/quality checks and avoid adding new tools (grunt
).
You could also use npm to hold scripts if not grunt/gulp. So in package.json (example from jscs itself)
"scripts": {
"lint": "jshint . && node bin/jscs lib test bin publish"
}
I'l leave this thread now xd
:+1:
Ok, I think the consensus is against having a task runner, so I'll drop that proposal.
@jacquerie We could add it as a suggestion to the invenio kwalitee tool as @lnielsen suggested. Sounds like a nice compromise.
Issue by jirikuncar Thursday May 22, 2014 at 11:54 GMT Originally opened as https://github.com/jirikuncar/invenio/issues/262
Idea
It'd be nice to use the standard way for JavaScript.
Proposals
Testing
onclick
, nohref=javascript:
. The second would be tests, QUnit is super simple and nice. Extra pain points: jslint, jsfmtRelated