I've been struggling lately with understanding the best way to organize jQuery code. I asked another question earlier and I don't think I was specific enough (found in this question here).
My problem is that the richer you make an application, the quicker your client side gets out of control. Consider this situation...
//Let's start some jQuery
$(function() {
var container = $("#inputContainer");
//Okay let's list text fields that can be updated
for(var i=0; i < 5; i++) {
//okay let's add an event for when a field changes
$("<input/>").change(function() {
//okay something changed, let's update the server
$.ajax({
success:function(data) {
//Okay - no problem from the server... let's update
//the bindings on our input fields
$.each(container.children(), function(j,w) {
//YIKES!! We're deep in here now!!
$(w).unbind().change(function() {
//Then insanity starts...
}); // end some function
}); //end some loop
} // what was this again?
}); //ending something... not sure anymore
}).appendTo(container); //input added to the page... logic WAY split apart
}; //the first loop - whew! almost out!
}); //The start of the code!!
Now this situation isn't too far from impossible. I'm not saying this is the right way to do it, but it's not uncommon to find yourself several levels down into a jQuery command and starting to wonder how much more logic can add before the screen begins to melt.
My question is how are people managing this or organizing to limit the complexity of their code?
I listed how I'm doing it in my other post...
Just want to add to what was mentioned previously that this:
$.each(container.children(), function(j,w) {
$(w).unbind().change(function() { ... });
});
can be optimized to:
container.children().unbind().change(function() { ... });
It's all about chaining, a great way to simplify your code.
So far, I do it like this:
// initial description of this code block
$(function() {
var container = $("#inputContainer");
for(var i=0; i < 5; i++) {
$("<input/>").changed(inputChanged).appendTo(container);
};
function inputChanged() {
$.ajax({
success: inputChanged_onSuccess
});
}
function inputChanged_onSuccess(data) {
$.each(container.children(), function(j,w) {
$(w).unbind().changed(function() {
//replace the insanity with another refactored function
});
});
}
});
In JavaScript, functions are first-class objects and can thus be used as variables.
Well, for one, having a good IDE that understands javascript can help tremendously, even if just to identify matching demarcations (braces, parens, etc).
If your code starts to really get that complex, consider making your own static object to organize the mess - you don't have to work so hard to keep everything anonymous.
var aCustomObject = {
container: $("#inputContainer"),
initialize: function()
{
for(var i=0; i < 5; i++)
{
$("<input/>").changed( aCustomObject.changeHandler );
}
},
changeHandler: function( event )
{
$.ajax( {success: aCustomObject.ajaxSuccessHandler} );
},
ajaxSuccessHandler: function( data )
{
$.each( aCustomObject.container.children(), aCustomObject.updateBindings )
},
updateBindings: function( j, w )
{
$(w).unbind().changed( function(){} );
}
}
aCustomObject.initialize();
In my opinion the method described by BaileyP is what I use to start off with then I normally abstract everything into more re-usable chunks, especially when some functionality expands to the point where it's easier to abstract it into a plugin then have it specific to one site.
As long as you keep the large blocks of code in a seperate file and coded nicely you can then end up with some really clean syntax.
// Page specific code
jQuery(function() {
for(var i = 0; i < 5; i++) {
$("<input/>").bindWithServer("#inputContainer");
}
});
// Nicely abstracted code
jQuery.fn.bindWithServer = function(container) {
this.change(function() {
jQuery.ajax({
url: 'http://example.com/',
success: function() { jQuery(container).unbindChildren(); }
});
});
}
jQuery.fn.unbindChildren = function() {
this.children().each(function() {
jQuery(this).unbind().change(function() {});
});
}
Somebody wrote a post on the similar topic.
jQuery Code Does not have to be Ugly
For instance, the author, Steve Wellens, suggests to not use anonymous functions, as it makes code harder to read. Instead, push the function reference into the jQuery methods, like so:
$(document).ready(DocReady);
function DocReady()
{
AssignClickToToggleButtons();
ColorCodeTextBoxes();
}
Another takeaway from the article is to assign a jQuery object to a concrete variable, which makes the code look cleaner, less dependent on the actual jQuery object, and easier to tell what a certain line of code is doing:
function ColorCodeTextBoxes()
{
var TextBoxes = $(":text.DataEntry");
TextBoxes.each(function()
{
if (this.value == "")
this.style.backgroundColor = "yellow";
else
this.style.backgroundColor = "White";
});
}
Stick some of the anon functions into global scope functions (or your own "namespace" object), especially the re-used functions, and it begins to look less like what you posted. Kind of like what you linked to.
I described my approach in your other post. Short form:
do not mix javascript and HTML
use classes (basically start to see your application as a collection of widgets)
only have a single $(document).ready(...) block
send jQuery instances into your classes (instead of using plugins)
Use http://coffeescript.com/ ;)
$ ->
container = $ '#inputContainer'
for i in [0...5]
$('<input/>').change ->
$.ajax success: (data) ->
for w in container.children()
$(w).unbind().change ->
alert 'duh'
Related
I'm trying to use infinite-scroll to lazy load images. I'm getting the following error when it's called though:
TypeError: undefined is not a function
at handler (http://onfilm.us/ng-infinite-scroll.js:31:34)
Here's a very watered down look of what I have thus far.
function tagsController($scope) {
$scope.handleClick = function(tags) {
// Parse Tags
$scope.finished_tags = parsed_data;
};
$scope.$emit( 'handleEmit', { tags = $scope.finished_tags; });
};
function imagesController($scope,$http) {
var rows_per = 5;
$scope.$on('handleBroadcast', function(event, args) {
// Sort the images here, put them in matrix
// Example: matrix[row_number] = { picture1, picture2, picture3 }
$scope.data = matrix;
$scope.loadMore();
};
$scope.loadMore() = function() {
var last = $scope.images.length;
for ( var i = 0; i < rows_per; i++ ) {
$scope.images[last + i] = new Array();
$scope.images[last + i] = $scope.data[last + i].slice( 0 );
}
}
}
The rough idea is that the page loads the first time (w/ no tags) and get images from a PHP script. All of them. They are stored, and loadMore() is called which will populate $scope.images with 5 rows of images. It does, and they are loaded.
The line in that script is accessing $window.height and $window.scrollup. I'm still pretty green w/ Javascript, so feel free to lambast me if I'm doing something horribly wrong.
This is the broken version I'm testing with:
http://onfilm.us/test.html
Here is a version before the lazy loading was implemented, if seeing how the tags work will help. I don't think that's the issue here though.
http://onfilm.us/image_index.html
EDIT: I do think this is a problem w/ the ng-infinite-scroll.js script. The error is on line 31 (of version 1.0.0). It's telling me:
TypeError: undefined is not a function
It doesn't like $window apparently.
My JS Kung Fu is not really equipped to say why. YOu can see a literal copy/paste job from the simple demo here (with the error) onfilm.us/scroll2.html
By refering your site, It appears at first instance that your HTML-markup is not appropriate. You should move infinite-scroll to the parent of ng-repeat directive so that it will not make overlapping calls for each row generated. Please visit http://binarymuse.github.io/ngInfiniteScroll/demo_basic.html
I have an array of hooks in jQuery that are executed before I load data into a grid. In one case, however, I want to remove the hook, then add it back for later. Whatever I'm doing is not working just right... it's probably a syntax error because I'm still somewhat new to jQuery. Any help would be appreciated, thanks!
Current code:
var preLoad = this.opts.hooks.preLoad.pop();
//stuff happens
//now I want to add the preLoad hook back
this.opts.hooks.preLoad.push(function(report) { preLoad(report); });
EDIT
It turns out the issue lies elsewhere in the code. However, I'd still like to know how best to accomplish this.
You access it the same way as any other variable stored in any other array.
this.opts.hooks.preLoad[0](myReport)
Can you not just add the function you removed like this?
var preLoad = this.opts.hooks.preLoad.pop();
//stuff happens
//now I want to add the preLoad hook back
this.opts.hooks.preLoad.push(preLoad);
And are you sure it's always the last one in the array that you want to remove?
It probably has to do with the fact that you are "canning" the argument "report" when you push the function back on the stack.
Try doing it like that:
var preLoad = this.opts.hooks.preLoad.pop();
//stuff happens
//now I want to add the preLoad hook back
this.opts.hooks.preLoad.push(preLoad);
I've tested it here http://jsfiddle.net/fWRez/
The example you gave has nothing to do with jQuery and is pure Javascript. Also, beware that what you are doing in your example is... not right. Consider this :
var ReportManager {
...
replace: function(report) {
var preLoad = this.opts.hooks.preLoad.pop();
//stuff happens
//now I want to add the preLoad hook back
this.opts.hooks.preLoad.push(function(report) { preLoad(report); });
}
}
If you execute this :
replace(null);
replace({foo:'bar'});
replace(null);
Your this.opts.hooks.preLoad array will look like this :
Array(
0: function(report) { return function(report) { return function(report) { ... } } }
)
Because you are pushing the function wrapped into itself every time you execute your code. I'm not sure why you need to pop and push it back in again, but this just look odd.
Also, Javascript is a very flexible language; which mean that you can do many weird stuff, like
"hello".concat(" world"); // -> 'hello world'
0.toString(); // -> '0'
(function(a) { return a; })("foo"); // -> 'foo'
(function() { return false; })() || (function() { return true; })(); // -> true (executes both functions)
(function(i) { return [i*2,i*3,i*4]; })(2)[1]; // -> 6
$('selector')[0]; // ...
// etc.
I want to create an object that can parse a certain filetype. I've looked at some of the files in the File API and I want my object to work about the same. So basically, what I want is this:
A function, called CustomFileParser. I want to be able to use it as the following:
var customFileParser = new CustomFileParser();
customFileParser.parsed = paresed;
customFileParser.progress = progress;
customFileParser.parse(file);
function parsed(event){
//The file is loaded, you can do stuff with it here.
}
function progess(event){
//The file load has progressed, you can do stuff with it here.
}
So I was thinking on how to define this object, but I'm not sure how to define these events and how I should do this.
function customFileParser(){
this.parse = function(){
//Do stuff here and trigger event when it's done...
}
}
However, I'm not sure how to define these events, and how I can do this. Anyone can give me a hand?
Javscript is prototype-based OOP language, not class-based like most other popular languages. Therefore, the OOP constructs are a bit different from what you might be used to. You should ignore most websites that try to implement class-based inheritance in JS, since that's not how the language is meant to be used.
The reason people are doing it because they are used to the class-based system and are usually not even aware that are alternatives to that, so instead of trying to learn the correct way, they try to implement the way that they are more familiar with, which usually results in loads and loads of hacks or external libraries that are essentially unnecessary.
Just use the prototype.
function CustomFileParser(onParsed, onProgress) {
// constructor
this.onParsed = onParsed;
this.onProgress = onProgress;
};
CustomFileParser.prototype.parse = function(file) {
// parse the file here
var event = { foo: 'bar' };
this.onProgress(event);
// finish parsing
this.onParsed(event);
};
And you can use it like so
function parsed(event) {
alert(event);
}
function progress(event) {
alert(event);
}
var customFileParser = new CustomFileParser(parsed, progress);
var file = ''; // pseudo-file
customFileParser.parse(file);
From what it sounds to me i think you need your program to look like this
function customFileParser( onparse , progress){
this.onparse = onparse;
this.progressStatus = 0;
this.progress = progress;
this.parser = function (chunk)
}
this.parse = function(){
// Do stuff of parsing
// Determine how much data is it
// Now make a function that parses a bit of data in every run
// Keep on calling the function till the data is getting parsed
// THat function should also increase the percentage it think this can be done via setTimeout.
// After every run of the semi parser function call the progress via something like
this.parser();
if(progressStatus <100){
this.progress(this.progressStatus);
}else{
this.parsed();
}
}
}
and u can create instance of that object like
var dark = new customFileParser( function () { // this tells what to
do what parsed is complete } , function (status) { // this tells what
to do with the progress status } ) ;
using the method i suggested. you can actually define different methods for all the instances of the object you have !
I'm sorry if this question has been asked before, but I'm not even sure what search terms to use to find the answer and when I try to search I never get anything specific to this question.
I'm using Javascript and I am wondering if it is possible to do something like this:
find(x); // find a document (for example)
find.inFolder(y); // find a folder's documents (for example)
In other words, can I have a function that can also be used as an object/class? I know I could run find() once and return a hash so that find.inFolder() would work, but I'm hoping there's a way where I could continue to call find().
Can it be done with prototype? (my "prototype" knowledge is very limited)
function find() {}
find.prototype.inFolder = function() {}
Can it be done inside a hash? [I know this code doesn't work]
var find = {
() : function() {},
inFolder : function() {}
}
To push it even further, is there a way to have the results of .inFolder() be sent to the find() function this way:
find().inFolder();
I know you might say that I don't understand the concept of javascript, and you'd be mostly correct, but I've seen people do some pretty amazing stuff with JS so I thought I'd ask the pros out there.
Thanks in advance for any help.
What you're describing is a Fluent interface (if you want something to search for). You could accomplish something like what you're trying to achieve like this:
var find = function() {
this.inFolder = function() {
return this; // Although to stop chaining, you could return nothing here.
};
return this;
};
find().inFolder(); // .inFolder().inFolder()...
This is a great pattern, especially when leveraged in projects like jQuery:
$("#element").find(".child_element").first();
Each call returns a jQuery object with .find(), .first() and many other functions, which lets you write intuitive and fluid code.
I kind of liked your find().inFolder() example, so here's an expanded version:
var find = function(file) {
this.folders = {
"Documents": ["Foo.txt", "Bar.txt"],
"Downloads": ["File.exe"],
"Misc": ["Picture.jpg"]
};
this.file = file;
this.inFolder = function(folder) {
var files = this.folders[folder];
return files.indexOf(this.file) >= 0;
};
return this;
};
alert(find("Foo.txt").inFolder("Documents")); // True
alert(find("File.exe").inFolder("Downloads")); // True
alert(find("Picture.jpg").inFolder("Downloads")); // False
http://jsfiddle.net/andrewwhitaker/TCdTd/
You can assign, a function to a member of another function:
find = function(x) { .... }
find.inFolder = function(y) { ... }
jsFiddle.
I'm not sure I understand the question however.
right now i am at a point where i feel that i need to improve my javascript skills because i already see that what i want to realize will get quite complex. I've iterrated over the same fragment of code now 4 times and i am still not sure if it's the best way.
The task:
A user of a webpage can add different forms to a webpage which i call modules. Each form provides different user inputs and needs to be handled differently. Forms/Modules of the same type can be added to the list of forms as the user likes.
My current solution:
To make the code more readable and seperate functions i use namespaced objects. The first object holds general tasks and refers to the individual forms via a map which holds several arrays where each contains the id of a form and the reference to the object which holds all the functions which need to be performed especially for that kind of form.
The structure looks more or less similar to this:
var module_handler = {
_map : [], /* Map {reference_to_obj, id} */
init: function(){
var module = example_module; /* Predefined for this example */
this.create(module);
},
create: function(module) {
//Store reference to obj id in map
this._map.push([module,id = this.createID()]);
module.create(id);
},
createID: function(id) {
//Recursive function to find an available id
},
remove: function(id) {
//Remove from map
var idx = this._map.indexOf(id);
if(idx!=-1) this._map.splice(idx, 1);
//Remove from DOM
$('#'+id+'').remove();
}
}
var example_module = {
create: function(id) {
//Insert html
$('#'+id+' > .module_edit_inner').replaceWith("<some html>");
}
}
Now comes my question ;-)
Is the idea with the map needed?
I mean: Isn't there something more elegant like:
var moduleXYZ = new example_module(id)
which copies the object and refers only to that form.... Something more logical and making speed improvements?? The main issue is that right now i need to traverse the DOM each time if i call for example "example_module.create() or later on any other function. With this structure i cant refer to the form like with something like "this"???
Do you see any improvements at this point??? This would help me very much!!! Really i am just scared to go the wrong way now looking at all the stuff i will put on top of this ;-)
Thank You!
I think you're looking for prototype:
=========
function exampleModule(id)
{
this.id = id;
}
exampleModule.prototype.create = function()
{
}
=========
var module1 = new exampleModule(123);
module1.create();
var module2 = new exampleModule(456);
module2.create();