问题
My javascript skills are fairly basic, I can work with jquery etc. but when it comes to building a site like pivotaltracker I wouldn't know where to begin!
Was hoping someone could help break down their javascript architecture and explain at a high level how they went about designing their js frameowork to make a gmail like design where it is purely javascript driven (at least I think it is).
Things like:
- layout wise, is there a single div container that loads the different panels?
- Does it keep a browser copy of all the stories, and use javascript templating to build the html?
- how are the various objects designed
- I think this is a major one, how are the events wired up, is it a global event that bubbles up?
I think the interesting thing is that there are allot of DOM elements on a page with all the user stories grouped together etc., so they must have done some cool performance techniques especially around events etc.
回答1:
I think your question is more about understanding MVC (model-view-controller) patterns in javascript. I think you should update your question to reflect that.
Something like 'Help understanding MVC patterns in javascript'.
It's hard to distil the concept of what that looks like in javscript without providing a demo use case with examples and a detailed walk through of the code. I know that is essentially what you have asked for, but I think that is out of the remit of StackOverflow.
MVC patterns are fairly familiar and widely used in server side frameworks, for Example.
- PHP has CodeIgnighter
- Ruby has Rails
- Python has Django
- Java has Spring
- Plus many, many more variations for each language.
The MVC pattern is closely linked to the concept of OOP ( object oriented programming ). While it is not fundamental for a language to be object oriented in order to follow the MVC pattern. Many MVC frameworks tend to be built following OOP methodologies to the extent the language will allow.
This is one of the reasons I think the MVC concept is less prevalent in front-end development. For a long time Javascript has been a fairly mis-understood as a language. As a result it is only fairly recently that people have been applying the principles of OOP into javscript.
The improvement in browser conformance and libraries like JQuery has had a lot to do with this. Having the ability to focus less on the frustrations of inconsistencies in the DOM, Has allowed people to realize the core aspects of the language itself.
( Many people believed and still do, that browser inconsistencies are a fault of the JavaScript language, not the browser vendors implementation of the DOM. Which is the root cause behind the mis-understanding of Javascript. )
With that little rant out of the way, I will have a crack at giving you a really high level interpretation of MVC.
In MVC frameworks the creation of Models, Views, Controllers and how they interact is predefined. They do this to keep project clean and following the same structure throughout. The benefits of this are..
It's easier for new developers coming to the project to understand what's going on.
The more time you spend working in the framework, the more familiar you will become with the api's. So it speeds up development time.
The common structure and api's make it easier for you and others to maintain the codebase.
To understand how they do this in javscript you need to understand how constructor functions, prototypes and objects work. These are some of the fundamentals of the core JavaScript language and eloquent JavaScript is a good place to get started.
To start I don't think the term MVC is quite in the right order to aid visualising the flow of the internal processes. Whether this is intentional or not I don't know, I guess different people perceive things differently, but it seems to me that MVC is just easier to say and sounds better.
I prefer to think of it as CVM.
The key point of MVC frameworks is the separation of logic.
CONTROLLER >> The controller, is the functional part of the application, each controller deals with a specific aspect of user interaction. It then manages how that interaction should be handled by passing changes to the models and views, based on the input it received.
MODEL >> The Model is all about data. It only has one job, Model the data. So the Model would normally take data and validate or change it's representation. The Model also takes care of the CRUD operations ( Create, Read, Update, Delete ). You normally have a separate model for the different types of data running through your app. e.g. Users, Comments, Posts.
VIEW >> The View is the visual representation of the operation. It takes data from the model and generates the visual output. While the view generates the visual output, it is common that the view itself does not do the job of rendering it. It simply returns the visual representation to the controller for rendering. Views are not associated with whole pages, each view represents a different visual aspect of the application e.g. Sign in Dialog, New Comment etc.
By separating out different parts of an application like this. Many of the parts become interchangeable and re-usable by different controllers.
In backend MVC framework the user interaction they respond to is normally a page request. So the controllers listen to requests coming from the client. They use the url and query params to work out which controller is responsible for dealing with that request.
e.g. http://myapp.com/users/ >> user Controller
The controller may then use any subsequent part of the url to define what models and views it should use to respond.
e.g. http://myapp.com/users/new/ >> user Controller renders the newUser View
Server side MVC frameworks use URL fragments to respond to user interaction, because they don't have access to the user interaction directly ( e.g. the server can't respond directly to a mouse click ). So it is more by force than choice that server side applications work this way.
In Javscript however we do have that luxury. We can add event handlers to parts of interface and respond directly to user interaction. This pattern is familiar to virtually every JavaScript user.
e.g. ( using jQuery )
// Create and event handler
$('.myButton').bind('click', function(event){
// Do some work when this event is fired.
});
It just so happens however that this ability to micro manage user interaction, is an inefficient approach in JavaScript intensive applications ( also known as single page web apps ). You end up with spaghetti code and duplication of functionality. As this approach tends to lead to someone encapsulating all the functionality into the function dealing with the interaction.
e.g.
$('myButton').bind('click', function(event){
var self = $(this);
event.preventDefault();
$.ajax({
url: self.attr('href'),
context: self.parents('.ResponseContainer'),
success: function(data){
self.addClass('.workDone');
for( key in data ){
$(this).append('<li>'+data[key]+'</li>')
};
}
});
});
So JavaScripts ability to deal directly with interaction, actually becomes a disadvantage. Having a global object such as the URL to respond to, makes modelling and separating parts of the application much easier to handle and conceptualise.
In theory you could create your own global object to store the application state and monitor for changes in your Controllers. However for most applications this is an unnecessary pursuit, it turns out that the URL object is both simple and highly effective for this operation. Because the URL contains a form of state in it's fragments, people can jump straight to specific parts of your application. If you implement your own object to do the job of the URL, the application would not have any knowledge of state prior to it's load. Any state at runtime would also be lost as soon as the page is closed. So the URL provides an excellent mechanism for persistent and transferable state ( as the URL can be shared ).
Therefore in most JavaScript MVC frameworks they use the URL over directly handling events. This presents some problems however, in order to change the URL a link has to be clicked. The browsers default behaviour is to send a request to the server for the new page and re render the entire page.
This is obviously not what we want to happen. So in order to prevent this MVC frameworks use a couple methods to alter the browsers default behaviour. The first mechanism is to prevent default on all link clicks.
e.g.
$('a').bind('click', function(event){
event.preventDefault();
});
// This prevents any link clicks from firing the browsers default action
// of making a request to the server and reloading the page.
In order to change the URL we have to update the window.location object to point to the URL contained in the the links href attribute. However just changing the window.location will still cause the page to be reloaded. In order to over come this we actually change the url to use the hash fragments e.g. http://myapp.com/#/users. When the browser see's a hash in the URL it does not reload the page. Historically the hash was used to navigate to a section of content within the existing page.
Hash updates also go into the browsing history, allowing you to navigate using the browsers back and forward button.
e.g.
$('a').bind('click', function(event){
event.preventDefault();
var el = $(event.currentTarget);
window.location.hash = el.attr('href');
});
// A real use case would be much more complex than this.
// This code assumes you have a link structured like
// <a href="/new/user">Sign up</a>
A separate function will monitor for changes in the hash fragment. This maybe in the form of a setInterval() on the location.hash that compares the previous fragment to the current one, or a custom event fired by the above function.
In order to allow the controllers to respond to the correct URL ( also referred to as Routes ), typically naming conventions on objects or methods are used.
e.g.
//Create your controller to listen to '/user' fragments
var users = new Controller('/users');
// function to run on '/user' fragment changes
users.On = function(reqParams){
// do some work to respond to http://myapp.com/#/users;
};
// create a Controller as a method of users, to respond to '/users/new'
users.new = new Controller('/new');
// function to run on '/user/new' fragment changes
users.new.On = function(reqParams){
// do some work to respond to http://myapp.com/#/users/new
};
I'm not going to go into more detail, MVC frameworks provide different ways to implement and structure your application. Also because JavaScript does have the ability to directly respond to user interaction, that power shouldn't be completely ignored. So in some JavaScript MVC frameworks they slightly taint the pure MVC concept, to allow for deeper interaction control.
I came across this video tutorial by Ben Nadel exploring the MVC concept in single page web apps. It an extremely detailed walk through of how to structure an app. And also gives some great JavaScript authoring tips.
- http://www.bennadel.com/blog/1730-Building-Single-Page-Applications-Using-jQuery-And-ColdFusion-With-Ben-Nadel-Video-Presentation-.htm
Some Javascript MVC Frameworks
http://documentcloud.github.com/backbone/
http://maccman.github.com/spine/
http://www.bennadel.com/resources/projects/cormvc/demo/index.htm#/ by Ben Nadel as a result of the presentation ( might be the best choice after watching the video ).
http://javascriptmvc.com/
http://knockoutjs.com/
An overview of a few of the frameworks mentioned above.
- http://www.protectedmethod.com/blog/4d49b82942ba7864ce00000d/knockout_vs_javascriptmvc_vs_backbone
And don't forget to read eloquent JavaScript if you haven't already
- http://eloquentjavascript.net/
I hope this is enough info for you to get started.
回答2:
Pivotal Tracker UI (and js) is very similar to Google Wave (Wave in the Box) Wave protocol specification So I think it has following architecture.
Main page consists of html and js loader. Html is simple - just a div with no content. Loader runs when the page is loaded, just like that
$(document).ready(function(){
$("#main_holder").buildPage("home"); // jquery example
});
This function runs 2 tasks:
- load data (through AJAX e.g.)
- build UI with loaded data
Loading data is clear operation. Building UI is more complex. UI builds with simple controls - widgets (or some sort of widgets). Each widget has a code to build itself, and initialize event handlers. Every loaded widget is registered in a loader (or mediator), so it can access to other widgets data through the loader.
For building html for each widget templates is used (some kind of JSP templates). Example of template
<li class="task_<%=id%> <%= readOnly ? 'readonly' : '' %>">
<% if (!readOnly) { %>
<input type="checkbox" name="task[complete_<%=id%>]" value="1" <%= complete ? "checked='checked'" : "" %>/>
<div style="display:none"><textarea class="autoresize expand17-10000" name="task[description_<%=id%>]"><%= description %></textarea></div>
<% } else { %>
<div><%= position %>.</div>
<% } %>
<label <%= complete ? "class='completed'" : "" %>><%= Element.formatText(description) %></label>
<% if (!readOnly) { %>
<ul class="actions">
<li class="edit"><a href="#" title="Edit Task"><img src="<%= edit_img_src %>" /></a></li>
<li class="delete"><a href="#" title="Delete Task"><img src="<%= delete_img_src %>" /></a></li>
</ul>
<% } %>
</li>
Template compiles by the template engine and becomes a pure html code.
Event handlers is not global. Every widget create event handlers by itself. If it's a global event, which need to be fired on each widget, then the loader (mediator) fires it by calling trigger method (for jquery) on each widget registered in it's list.
Various objects designed as a associative arrays. Like
org.pivotal.model.vo.VariousObjectVO = new Class({
/**
*
* @param {Long} id
* @param {String} name
* @param {Map<String, String>} fields
*
*/
initialize: function(){
},
id: null,
name: "",
fields: {}
});
So you can keep any count of fields with any count of values.
Hope it helps.
Regards, Sergey
回答3:
Well, it certainly is a good app and looks daunting initially. However, if you break it down to components (such header, body, footer, child widgets), it becomes easy to tackle them one by one.
From what I see, it is built with various "widgets". Of all, let me pick the dashboard page and show you how to go about designing it.
1. Layout
From what it looks like, they have 3 column layout. You can opt for a fixed layout or a fluid layout as per your needs.
If you look at pivotal, they have a fluid layout for the dashboard as the panels resize when you resize the browser.
On initial page load, I would render three empty panels with a loading sign. Then fill them up via ajax calls with data.
- You can either go with server side rendering (and geeting entire HTML
back to client
- Or, just get the data back from server and bind them on client side using client side templates (preferred as it avoids roundtrip of markup tags)
2. Client Templating
The idea is that you get your data via Ajax calls and then use a client side templating engine to bind your data with the template markup to produce the desired output markup.
Pseudo Code to load a widget:
1. Getdata // $.ajax() or any other way
2. Bind data to template (using underscore templates or other templating engines)
3. Append the HTML to the panels
In my experience, I 've found Underscore.js templates extremely easy and fast (I recommend them over jQuery templates)
The HTML template and their corresponding script would make up a widget
.
You could benefit from designing these widgets as jQuery plugins. On top of that, if you add inheritance model to those plugins, you can have extensible plugins. Here's a nice approach that has worked very well for me: attaching a class to a jQuery object
3. Object Design
Short answer - Base it on your View Model. The JSON object that you send to the client should be a subset of your view model containing only relevant data that is needed to draw the widgets and enable interaction (keys, ids etc) via events.
4. Event management
For event management, the way I would go is:
- each widget is self contained. In the sense that it is agnostic of other widgets on the page or its parent.
- The parent subscribes to events on child widgets.
- 2 widgets don't talk to each other.
- If one needs to change based on some event in another, then the page becomes the broker.
- Page listens to events from first widget and fires calls onto second widget, causing it to react to the change.
- Data is bubbled up from widget 1 to page, page to widget 2.
- Widgets listen to DOM events (such as click, mouseover etc). They catch the events, process them (extract data, massage data etc) and
publish
them.
You can use jQuery custom events for this, but for optimum performance, use this jQUery plugin created by Dojo's author Peter Higgins: pubsub js
5. Other suggestions
- Use a javascript MVC framework such as Backbone.js.
- Use jQuery plugins but be wary of their performance. jQuery UI, jScrollPane are excellent plugins that can easily build the panels you see on pivotal tracker
As you can see, this is a very wide topic and you can go as deep as you wish in each of these sections. If you have any questions, let me know and I'll try to explain them.
回答4:
I create my javascript apps using a event bus that is responsible for the logic. There the business rules, server interaction, validation and so on get plugged in. Also visual elements retrieve their data through this bus. The visual elements get designed using MVC independent from each other. If code gets shared, it gets a plugin (I use jQuery on the very botom of the application). There is also a manager that is responsible for finding and displaying components. It gets its commands through the event bus.
I like this design because it is very flexible and perfectly fits to the language javascript that is designed for event handling.
来源:https://stackoverflow.com/questions/6718501/help-learning-from-pivotaltrackers-javascript-looking-for-a-high-level-breakdo