I need a way to call many DHTMLX attach*() functions with certain defaults already set. This is just one example. If I can figure this one example out then I can apply it to all others.
DHTMLX has many functions similar to this: dhtmlXCellObject.prototype.attachToolbar(), attachTabbar(), attachRibbon(), etc... But for every single toolbar in my app there are certain settings I want to automatically apply like iconSize and iconPath.
dhtmlXCellObject.prototype.attachTheBetterToolbar = function (conf) {
// https://docs.dhtmlx.com/api__dhtmlxlayout_attachtoolbar.html
// dhtmlXToolbarObject.prototype.attachToolbar.call(this, conf); This throws: Cannot read property 'call' of undefined
this.attachToolbar.call(this, conf);
// I want these two settings below on every single toolbar in our app but
// I only want to have to set them once in here. Then throughout my
// entire application, we will only use attachTheBetterToolbar...
// layout.cells('a').attachTheBetterToolbar()
// window.attachTheBetterToolbar()
// accordian.attachTheBetterToolbar()
// tabbar.tabs('a').attachTheBetterToolbar()
// etc...
this.setIconSize(18);
this.setIconsPath(c3.iconPath);
};
The above code doesn't work (errors with: this.setIconSize is not a function) but I think you'll get the idea of what I'm trying to attempt. I'm reading all sorts of articles on JavaScript extend, apply, call, inheritance, etc... I feel like I'm close but something just isn't clicking.
I thought the ".call()" part would cause inheritance to happen like described here: https://stackoverflow.com/a/16058530/3112803 (the Variation 1 - Mixin -> Inheritance example)
This does what I need...
dhtmlXCellObject.prototype.attachTheBetterToolbar = function (conf) {
var tb = this.attachToolbar(conf);
// this.setIconset("awesome");
tb.setIconSize(18);
tb.setIconsPath(c3.iconPath);
return tb;
};
I have two pages. One of them is dashboard with a lot of functionality. The second page is shared dashboard - the simple version of the first page.
The dashboard contains the view of the database (it can contain much other info, but the problem with this one). You can click on the filter button and modal window will be opened. So, simple version of the dashboard doesn't have this possibility. I'd like to add it, but I don't want to copy+past code from the full version of the dashboard because the code of this part is about two thousand lines. I'll add some primitive code example:
DashboardView = SomeAnotherView.extend({
initialize: function() {...},
events: {...} // huge objects of jQuery events,
render: function () {...},
... // 2k lines of functions for events
});
How can I use this View on another page? I tried to call a function from this view:
DashboardView.prototype.filterClicked(event);
But in this case event.curentTarget is null (it is necessary for this function), I also tried to send "this" to get the context, but it was failed.
Is there a possibility in Backbone.js to use one View for 2+ pages without any huge copy/past code?
Ideally if you have a simple version and full version of a view, you should have a "base view" (simple one) and the full version should extend the base view.
It'll look something like:
var SimpleDashbard = Backbone.view.extend({});
var Dashboard = SimpleDashbard.extend({});
In this way Dashboard will have access to the methods from SimpleDashbard.
Your situation sounds like you need to use a method from extended view in base view. Which is not a good idea. Ideally if it's shared you should move it to the base view/extract it into a utility method or service, and of course this involve re-writing this method to be reusable
If you have views that share a large amount of functionality, you could consider using the same View type, but opening it up to some configuration when instancing. For example:
var DashboardView = Backbone.View.extend({
initialize: function(options) {
this.allowFunctionX = (options && options.allowFunctionX);
this.allowFunctionY = (options && options.allowFunctionY);
},
// etc
functionX: function() {
if (!this.allowFunctionX) { return; }
// do the function...
},
functionY: function() {
if (!this.allowFunctionY) { return; }
// do the function...
},
});
Then on one page:
var firstDashView = new DashboardView({allowFunctionX: true});
and on another page:
var secondDashView = new DashboardView({allowFunctionY: true});
This may become not worth it if the functionality diverges too much (and there are likely better ways to configure than passing in a long list of booleans!). If your requirements are significantly different on your two pages, I feel like duplicating the code they both need is not a major sin.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
The Problem
This answer has been answered before but are old and not up to date. I have over 2000 lines of code in a single file, and as we all know this is bad practice, especially when i'm looking through code or adding new features. I want to better organize my code, for now and for the future.
I should mention that I'm building a tool (not a simple website) with lots of buttons, UI elements, drag, drops, action listeners/handlers and function in the global scope where several listeners may use the same function.
Example code
$('#button1').on('click', function(e){
// Determined action.
update_html();
});
... // Around 75 more of this
function update_html(){ .... }
...
More example code
Conclusion
I really need to organize this code for best use and not to repeat myself and be able to add new features and update old ones. I will be working on this by myself. Some selectors can be 100 lines of code others are 1. I have looked a bit at require.js and found it kinda repetitive, and actually writing more code than needed . I'm open to any possible solution that fit this criteria and link to resource / examples are always a plus.
Thanks.
I'll go over some simple things that may, or may not, help you. Some might be obvious, some might be extremely arcane.
Step 1: Compartmentalize your code
Separating your code into multiple, modular units is a very good first step. Round up what works "together" and put them in their own little encased unit. don't worry about the format for now, keep it inline. The structure is a later point.
So, suppose you have a page like this:
It would make sense to compartmentalize so that all the header-related event handlers/binders are in there, for ease of maintenance (and not having to sift through 1000 lines).
You can then use a tool such as Grunt to re-build your JS back to a single unit.
Step 1a: Dependency management
Use a library such as RequireJS or CommonJS to implement something called AMD. Asynchronous Module Loading allows you to explicitely state what your code depends on, which then allows you to offload the library-calling to the code. You can just literally say "This needs jQuery" and the AMD will load it, and execute your code when jQuery is available.
This also has a hidden gem: the library loading will be done the second the DOM is ready, not before. This no longer halts load-up of your page!
Step 2: Modularize
See the wireframe? I have two ad units. They'll most likely have shared event listeners.
Your task in this step is to identify the points of repetition in your code and to try to synthesise all this into modules. Modules, right now, will encompass everything. We'll split stuff as we go along.
The whole idea of this step is to go from step 1 and delete all the copy-pastas, to replace them with units that are loosely coupled. So, instead of having:
ad_unit1.js
$("#au1").click(function() { ... });
ad_unit2.js
$("#au2").click(function() { ... });
I will have:
ad_unit.js:
var AdUnit = function(elem) {
this.element = elem || new jQuery();
}
AdUnit.prototype.bindEvents = function() {
... Events go here
}
page.js:
var AUs = new AdUnit($("#au1,#au2"));
AUs.bindEvents();
Which allows you to compartmentalize between your events and your markup in addition to getting rid of repetition. This is a pretty decent step and we'll extend this further later on.
Step 3: Pick a framework!
If you'd like to modularize and reduce repetitions even further, there are a bunch of awesome frameworks around that implement MVC (Model - View - Controller) approaches. My favourite is Backbone/Spine, however, there's also Angular, Yii, ... The list goes on.
A Model represents your data.
A View represents your mark-up and all the events associated to it
A Controller represents your business logic - in other words, the controller tells the page what views to load and what models to use.
This will be a significant learning step, but the prize is worth it: it favours clean, modular code over spaghetti.
There are plenty of other things you can do, those are just guidelines and ideas.
Code-specific changes
Here are some specific improvements to your code:
$('.new_layer').click(function(){
dialog("Create new layer","Enter your layer name","_input", {
'OK' : function(){
var reply = $('.dialog_input').val();
if( reply != null && reply != "" ){
var name = "ln_"+reply.split(' ').join('_');
var parent = "";
if(selected_folder != "" ){
parent = selected_folder+" .content";
}
$R.find(".layer").clone()
.addClass(name).html(reply)
.appendTo("#layer_groups "+parent);
$R.find(".layers_group").clone()
.addClass(name).appendTo('#canvas '+selected_folder);
}
}
});
});
This is better written as:
$("body").on("click",".new_layer", function() {
dialog("Create new layer", "Enter your layer name", "_input", {
OK: function() {
// There must be a way to get the input from here using this, if it is a standard library. If you wrote your own, make the value retrievable using something other than a class selector (horrible performance + scoping +multiple instance issues)
// This is where the view comes into play. Instead of cloning, bind the rendering into a JS prototype, and instantiate it. It means that you only have to modify stuff in one place, you don't risk cloning events with it, and you can test your Layer stand-alone
var newLayer = new Layer();
newLayer
.setName(name)
.bindToGroup(parent);
}
});
});
Earlier in your code:
window.Layer = function() {
this.instance = $("<div>");
// Markup generated here
};
window.Layer.prototype = {
setName: function(newName) {
},
bindToGroup: function(parentNode) {
}
}
Suddenly, you have a way to create a standard layer from anywhere in your code without copy pasting. You're doing this in five different places. I've just saved you five copy-pastes.
One more:
// Ruleset wrapper for actions
var PageElements = function(ruleSet) {
ruleSet = ruleSet || [];
this.rules = [];
for (var i = 0; i < ruleSet.length; i++) {
if (ruleSet[i].target && ruleSet[i].action) {
this.rules.push(ruleSet[i]);
}
}
}
PageElements.prototype.run = function(elem) {
for (var i = 0; i < this.rules.length; i++) {
this.rules[i].action.apply(elem.find(this.rules.target));
}
}
var GlobalRules = new PageElements([
{
"target": ".draggable",
"action": function() { this.draggable({
cancel: "div#scrolling, .content",
containment: "document"
});
}
},
{
"target" :".resizable",
"action": function() {
this.resizable({
handles: "all",
zIndex: 0,
containment: "document"
});
}
}
]);
GlobalRules.run($("body"));
// If you need to add elements later on, you can just call GlobalRules.run(yourNewElement);
This is a very potent way to register rules if you have events that are not standard, or creation events. This is also seriously kick-ass when combined with a pub/sub notification system and when bound to an event you fire whenever you create elements. Fire'n'forget modular event binding!
Here is a simple way to split your current codebase into multiple files, using require.js.
I will show you how to split your code into two files. Adding more files will be straightforward after that.
Step 1) At the top of your code, create an App object (or whatever name you prefer, like MyGame):
var App = {}
Step 2) Convert all of your top-level variables and functions to belong to the App object.
Instead of:
var selected_layer = "";
You want:
App.selected_layer = "";
Instead of:
function getModified(){
...
}
You want:
App.getModified = function() {
}
Note that at this point your code will not work until you finish the next step.
Step 3) Convert all global variable and function references to go through App.
Change stuff like:
selected_layer = "."+classes[1];
to:
App.selected_layer = "."+classes[1];
and:
getModified()
to:
App.GetModified()
Step 4) Test Your code at this stage -- it should all work. You will probably get a few errors at first because you missed something, so fix those before moving on.
Step 5) Set up requirejs. I assume you have a web page, served from a web server, whose code is in:
www/page.html
and jquery in
www/js/jquery.js
If these paths are not exactly like this the below will not work and you'll have to modify the paths.
Download requirejs and put require.js in your www/js directory.
in your page.html, delete all script tags and insert a script tag like:
<script data-main="js/main" src="js/require.js"></script>
create www/js/main.js with content:
require.config({
"shim": {
'jquery': { exports: '$' }
}
})
require(['jquery', 'app']);
then put all the code you just fixed up in Steps 1-3 (whose only global variable should be App) in:
www/js/app.js
At the very top of that file, put:
require(['jquery'], function($) {
At the bottom put:
})
Then load page.html in your browser. Your app should work!
Step 6) Create another file
Here is where your work pays off, you can do this over and over.
Pull out some code from www/js/app.js that references $ and App.
e.g.
$('a').click(function() { App.foo() }
Put it in www/js/foo.js
At the very top of that file, put:
require(['jquery', 'app'], function($, App) {
At the bottom put:
})
Then change the last line of www/js/main.js to:
require(['jquery', 'app', 'foo']);
That's it! Do this every time you want to put code in its own file!
For your question and comments I'll assume you are not willing to port your code to a framework like Backbone, or use a loader library like Require. You just want a better way to orgainze the code that you already have, in the simplest way possible.
I understand it is annoying to scroll through 2000+ lines of code to find the section that you want to work on. The solution is to split your code in different files, one for each functionality. For example sidebar.js, canvas.js etc. Then you can join them together for production using Grunt, together with Usemin you can have something like this:
In your html:
<!-- build:js scripts/app.js -->
<script src="scripts/sidebar.js"></script>
<script src="scripts/canvas.js"></script>
<!-- endbuild -->
In your Gruntfile:
useminPrepare: {
html: 'app/index.html',
options: {
dest: 'dist'
}
},
usemin: {
html: ['dist/{,*/}*.html'],
css: ['dist/styles/{,*/}*.css'],
options: {
dirs: ['dist']
}
}
If you want to use Yeoman it will give you a boilerplate code for all this.
Then for each file itself, you need to make sure you follow best practices and that all the code and variables are all in that file, and don't depend on other files. This doesn't mean you can't call functions of one file from other, the point is to have variables and functions encapsulated. Something similar to namespacing. I'll assume you don't want to port all your code to be Object Oriented, but if you don't mind refactoring a bit, I'd recommend to add something equivalent with what is called a Module pattern. It looks something like this:
sidebar.js
var Sidebar = (function(){
// functions and vars here are private
var init = function(){
$("#sidebar #sortable").sortable({
forceHelperSize: true,
forcePlaceholderSize: true,
revert: true,
revert: 150,
placeholder: "highlight panel",
axis: "y",
tolerance: "pointer",
cancel: ".content"
}).disableSelection();
}
return {
// here your can put your "public" functions
init : init
}
})();
Then you can load this bit of code like this:
$(document).ready(function(){
Sidebar.init();
...
This will allow you to have a much more maintainable code without having to rewrite your code too much.
Use javascript MVC Framework in order to organize the javascript code in a standard way.
Best JavaScript MVC frameworks available are:
Backbone
Angular
CanJS
Ember
ReactJS
Selecting a JavaScript MVC framework required so many factors to consider. Read the following comparison article that will help you to select best framework based on the factors important for your project:
http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/
You can also use RequireJS with the framework to support Asynchrounous js file & module loading.
Look the below to get started on JS Module loading:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/
Categorize your code. This method is helping me a lot and does work with any js framework:
(function(){//HEADER: menu
//your code for your header
})();
(function(){//HEADER: location bar
//your code for your location
})();
(function(){//FOOTER
//your code for your footer
})();
(function(){//PANEL: interactive links. e.g:
var crr = null;
$('::section.panel a').addEvent('click', function(E){
if ( crr) {
crr.hide();
}
crr = this.show();
});
})();
In your preferred editor (the best is Komodo Edit) you may fold in by collapsing all entries and you will see only the titles:
(function(){//HEADER: menu_____________________________________
(function(){//HEADER: location bar_____________________________
(function(){//FOOTER___________________________________________
(function(){//PANEL: interactive links. e.g:___________________
I would suggest:
publisher/subscriber pattern for event management.
object orientation
namespacing
In your case Jessica, divide the interface into pages or screens. Pages or screens can be objects and extended from some parent classes. Manage the interactions among pages with a PageManager class.
I suggest that you use something like Backbone. Backbone is a RESTFUL supported javascript library. Ik makes your code cleaner and more readable and is powerful when used together with requirejs.
http://backbonejs.org/
http://requirejs.org/
Backbone isn't a real library. It is meant to give structure to your javascript code. It is able to include other libraries like jquery, jquery-ui, google-maps etc. Backbone is in my opinion the closest javascript approach to Object Oriented and Model View Controller structures.
Also regarding your workflow.. If you build your applications in PHP use the Laravel library. It'll work flawlessly with Backbone when used with the RESTfull principle. Below the link to Laravel Framework and a tutorial about building RESTfull APIs:
http://maxoffsky.com/code-blog/building-restful-api-in-laravel-start-here/
http://laravel.com/
Below is a tutorial from nettuts. Nettuts has a lot of High Quality tutorials:
http://net.tutsplus.com/tutorials/javascript-ajax/understanding-backbone-js-and-the-server/
Maybe its time for you start implementing a whole development workflow using such tools as yeoman http://yeoman.io/. This will help control all your dependencies, build process and if you wanted, automated testing. Its a lot of work to start with but once implemented will make future changes a lot easier.
I'm currently refactoring some Javascript code we have and amongst other things I've changed it to make use of the revealing module pattern. The code is looking much tidier and it works fine but I can't see the functions anymore in the outline view. I see the top level namespace var as a var but you can't expand it to see the functions within.
Lets say the code used to look like this:
function myFunc1() {}
function myFunc2() {}
In this case you see both functions in the outline view. But if you change it to this:
var myNamespace = function()
{
function myFunc1() {}
function myFunc2() {}
return {
name: "myNamespace",
myFunc1: myFunc1,
myFunc2: myFunc2
}
}();
Then the outline view just shows you the myNamespace var. I've tried looking but can't find a view that will actually show me the hierarchy correctly. Does anyone know of a way to view this or is it a case of eclipse not being able to do this?
Add:
/**
* #memberOf myNamespace
*/
before each function definition to restore the hierarchy.
You will find more interesting tags to document your code here:
How I Introduced JsDoc into a JavaScript project – and found my Eclipse Outline
one way is to call it as below.
Define it as it is, but do not self execute it.
Ensure the prototype is an empty object and then try calling it.
It works the same way, but will restore the outline and you don't need to add comments in front of every function.
var myNamespace = (function()
{
function myFunc1() {}
function myFunc2() {}
return {
name: "myNamespace",
myFunc1: myFunc1,
myFunc2: myFunc2
}
});
myNamespace.prototype = {};
myNamespace();
Not showing myFunc1() etc. in the outline appears to be a bug which is marked as fixed in 3.2. However it is not fixed in 4.2. It is certainly a huge pain when dealing with very large files of hundreds of functions, and only the var name shows up in the outline. I pray for it being fixed.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=236202
https://bugs.eclipse.org/bugs/show_bug.cgi?id=281374#c1
/**
* #memberOf myNamespace
*/
Did not work for me. When I add this above myFunc1(), it does not show it in the outline, even if I close and open the file.
Interestingly, 4 of my 20 or so functions do show up in the outline, but there is no difference between the ones which work and the ones which do not except the ones which work all have this.xxx in them (but if I add this.dummy; to invisible functions it does not help)
This semi works:
myNameSpace.prototype = {};
myNameSpace;
But then you cant call its functions thusly:
myNameSpace.myFunc1();
I'm working with eclipse/Kepler. Using the advice from above I managed to get the outline view. But proposals (Ctrl-space) didn't work. Some fn were visible, some not. No pattern to detect.
After reading http://usejsdoc.org/#JSDoc3_Tag_Dictionary I replaced all #memberOf by #memberof and now everything works as expected ('til the next problem arises ...)
I have file called common.js and it's included in each page of my site using <script />.
It will grow fast as my sites functionality will grow (I hope; I imagine). :)
Lets example I have a jQuery event:
$('#that').click(function() {
one_of_many_functions($(this));
}
For the moment, I have that one_of_many_functions() in common.js.
Is it somehow possible that JavaScript automatically loads file one_of_many_functions.js when such function is called, but it doesn't exist? Like auto-loader. :)
The second option I see is to do something like:
$('#that').click(function() {
include('one_of_many_functions');
one_of_many_functions($(this));
}
That not so automatically, but still - includes wanted file.
Is any of this possible? Thanks in an advice! :)
It is not possible to directly auto-load external javascripts on demand. It is, however, possible to implement a dynamic inclusion mechanism similar to the second route you mentioned.
There are some challenges though. When you "include" a new external script, you aren't going to be able to immediately use the included functionality, you'll have to wait until the script loads. This means that you'll have to fragment your code somewhat, which means that you'll have to make some decisions about what should just be included in the core vs. what can be included on demand.
You'll need to set up a central object that keeps track of which assets are already loaded. Here's a quick mockup of that:
var assets = {
assets: {},
include: function (asset_name, callback) {
if (typeof callback != 'function')
callback = function () { return false; };
if (typeof this.assets[asset_name] != 'undefined' )
return callback();
var html_doc = document.getElementsByTagName('head')[0];
var st = document.createElement('script');
st.setAttribute('language', 'javascript');
st.setAttribute('type', 'text/javascript');
st.setAttribute('src', asset_name);
st.onload = function () { assets._script_loaded(asset_name, callback); };
html_doc.appendChild(st);
},
_script_loaded: function (asset_name, callback) {
this.assets[asset_name] = true;
callback();
}
};
assets.inlude('myfile.js', function () {
/* do stuff that depends on myfile.js */
});
Sure it's possible -- but this can become painful to manage. In order to implement something like this, you're going to have to maintain an index of functions and their corresponding source file. As your project grows, this can be troublesome for a few reasons -- the 2 that stick out in my mind are:
A) You have the added responsibility of maintaining your index object/lookup mechanism so that your scripts know where to look when the function you're calling cannot be found.
B) This is one more thing that can go wrong when debugging your growing project.
I'm sure that someone else will mention this by the time I'm finished writing this, but your time would probably be better spent figuring out how to combine all of your code into a single .js file. The benefits to doing so are well-documented.
I have created something close to that a year ago. In fact, I have found this thread by search if that is something new on the field. You can see what I have created here: https://github.com/thiagomata/CanvasBox/blob/master/src/main/New.js
My project are, almost 100% OOP. So, I used this fact to focus my solution. I create this "Class" with the name "New" what is used to, first load and after instance the objects.
Here a example of someone using it:
var objSquare = New.Square(); // Square is loaded and after that instance is created
objSquare.x = objBox.width / 2;
objSquare.y = objBox.height / 2;
var objSomeExample = New.Stuff("some parameters can be sent too");
In this version I am not using some json with all js file position. The mapping is hardcore as you can see here:
New.prototype.arrMap = {
CanvasBox: "" + window.MAIN_PATH + "CanvasBox",
CanvasBoxBehavior: "" + window.MAIN_PATH + "CanvasBoxBehavior",
CanvasBoxButton: "" + window.MAIN_PATH + "CanvasBoxButton",
// (...)
};
But make this more automatic, using gulp or grunt is something what I am thinking to do, and it is not that hard.
This solution was created to be used into the project. So, the code may need some changes to be able to be used into any project. But may be a start.
Hope this helps.
As I said before, this still is a working progress. But I have created a more independent module what use gulp to keep it updated.
All the magic que be found in this links:
https://github.com/thiagomata/CanvasBox/blob/master/src/coffee/main/Instance.coffee
https://github.com/thiagomata/CanvasBox/blob/master/src/node/scripts.js
https://github.com/thiagomata/CanvasBox/blob/master/gulpfile.js
A special look should be in this lines of the Instance.coffee
###
# Create an instance of the object passing the argument
###
instaceObject = (->
ClassElement = (args) ->
window[args["0"]].apply this, args["1"]
->
ClassElement:: = (window[arguments["0"]])::
objElement = new ClassElement(arguments)
return objElement
)()
This lines allows me to initialize a instance of some object after load its file. As is used in the create method:
create:()->
#load()
return instaceObject(#packageName, arguments)