Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
todomvc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Eugene Shen
todomvc
Commits
b7cc462a
Commit
b7cc462a
authored
Sep 06, 2012
by
Derick Bailey
Committed by
Sindre Sorhus
Oct 09, 2012
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-worked @jsoverson's Marionette implementation to use Marionette's modules
parent
2a923887
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
4435 additions
and
0 deletions
+4435
-0
labs/architecture-examples/backbone_marionette_modules/.jshintignore
...ecture-examples/backbone_marionette_modules/.jshintignore
+4
-0
labs/architecture-examples/backbone_marionette_modules/css/custom.css
...cture-examples/backbone_marionette_modules/css/custom.css
+10
-0
labs/architecture-examples/backbone_marionette_modules/index.html
...hitecture-examples/backbone_marionette_modules/index.html
+105
-0
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.Layout.js
...examples/backbone_marionette_modules/js/TodoMVC.Layout.js
+84
-0
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.TodoList.Views.js
.../backbone_marionette_modules/js/TodoMVC.TodoList.Views.js
+116
-0
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.TodoList.js
...amples/backbone_marionette_modules/js/TodoMVC.TodoList.js
+80
-0
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.Todos.js
...-examples/backbone_marionette_modules/js/TodoMVC.Todos.js
+53
-0
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.js
...ecture-examples/backbone_marionette_modules/js/TodoMVC.js
+11
-0
labs/architecture-examples/backbone_marionette_modules/js/lib/backbone-localStorage.js
...ckbone_marionette_modules/js/lib/backbone-localStorage.js
+136
-0
labs/architecture-examples/backbone_marionette_modules/js/lib/backbone.js
...e-examples/backbone_marionette_modules/js/lib/backbone.js
+1431
-0
labs/architecture-examples/backbone_marionette_modules/js/lib/backbone.marionette.js
...backbone_marionette_modules/js/lib/backbone.marionette.js
+1346
-0
labs/architecture-examples/backbone_marionette_modules/js/lib/underscore.js
...examples/backbone_marionette_modules/js/lib/underscore.js
+1059
-0
No files found.
labs/architecture-examples/backbone_marionette_modules/.jshintignore
0 → 100644
View file @
b7cc462a
js/lib/underscore.js
js/lib/backbone.js
js/lib/backbone.marionette.js
js/lib/backbone-localStorage.js
labs/architecture-examples/backbone_marionette_modules/css/custom.css
0 → 100644
View file @
b7cc462a
#todoapp
.filter-active
#todo-list
.completed
{
display
:
none
}
#todoapp
.filter-completed
#todo-list
.active
{
display
:
none
}
#main
,
#footer
{
display
:
none
;
}
labs/architecture-examples/backbone_marionette_modules/index.html
0 → 100644
View file @
b7cc462a
<!doctype html>
<html
lang=
"en"
>
<head>
<meta
charset=
"utf-8"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge,chrome=1"
>
<title>
Marionette • TodoMVC
</title>
<link
rel=
"stylesheet"
href=
"../../../assets/base.css"
>
<link
rel=
"stylesheet"
href=
"css/custom.css"
>
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
<script
type=
"text/html"
id=
"template-footer"
>
<
span
id
=
"
todo-count
"
><
strong
><
/strong> items left</
span
>
<
ul
id
=
"
filters
"
>
<
li
>
<
a
href
=
"
#
"
>
All
<
/a
>
<
/li
>
<
li
>
<
a
href
=
"
#active
"
>
Active
<
/a
>
<
/li
>
<
li
>
<
a
href
=
"
#completed
"
>
Completed
<
/a
>
<
/li
>
<
/ul
>
<
button
id
=
"
clear-completed
"
>
Clear
completed
<
/button
>
</script>
<script
type=
"text/html"
id=
"template-header"
>
<
h1
>
todos
<
/h1
>
<
input
id
=
"
new-todo
"
placeholder
=
"
What needs to be done?
"
autofocus
>
</script>
<script
type=
"text/html"
id=
"template-todoItemView"
>
<
div
class
=
"
view
"
>
<
input
class
=
"
toggle
"
type
=
"
checkbox
"
<%
if
(
completed
)
{
%>
checked
<%
}
%>>
<
label
><%=
title
%><
/label
>
<
button
class
=
"
destroy
"
><
/button
>
<
/div
>
<
input
class
=
"
edit
"
value
=
"
<%= title %>
"
>
</script>
<script
type=
"text/html"
id=
"template-todoListCompositeView"
>
<
input
id
=
"
toggle-all
"
type
=
"
checkbox
"
>
<
label
for
=
"
toggle-all
"
>
Mark
all
as
complete
<
/label
>
<
ul
id
=
"
todo-list
"
><
/ul
>
</script>
<script>
// Google analytics
var
_gaq
=
_gaq
||
[];
_gaq
.
push
([
'
_setAccount
'
,
'
UA-22728809-1
'
]);
_gaq
.
push
([
'
_trackPageview
'
]);
(
function
()
{
var
ga
=
document
.
createElement
(
'
script
'
);
ga
.
type
=
'
text/javascript
'
;
ga
.
async
=
true
;
ga
.
src
=
(
'
https:
'
==
document
.
location
.
protocol
?
'
https://ssl
'
:
'
http://www
'
)
+
'
.google-analytics.com/ga.js
'
;
var
s
=
document
.
getElementsByTagName
(
'
script
'
)[
0
];
s
.
parentNode
.
insertBefore
(
ga
,
s
);
})();
</script>
</head>
<body>
<section
id=
"todoapp"
>
<header
id=
"header"
></header>
<section
id=
"main"
></section>
<footer
id=
"footer"
></footer>
</section>
<footer
id=
"info"
>
<p>
Double-click to edit a todo
</p>
<p>
Created by
<a
href=
"http://github.com/jsoverson"
>
Jarrod Overson
</a>
and
<a
href=
"http://github.com/derickbailey"
>
Derick Bailey
</a>
,
using
<a
href=
"http://marionettejs.com"
>
Backbone.Marionette
</a>
</p>
</footer>
<!-- vendor libraries -->
<script
src=
"../../../assets/base.js"
></script>
<script
src=
"../../../assets/jquery.min.js"
></script>
<script
src=
"js/lib/underscore.js"
></script>
<script
src=
"js/lib/backbone.js"
></script>
<script
src=
"js/lib/backbone-localStorage.js"
></script>
<script
src=
"js/lib/backbone.marionette.js"
></script>
<!-- application -->
<script
src=
"js/TodoMVC.js"
></script>
<script
src=
"js/TodoMVC.Todos.js"
></script>
<script
src=
"js/TodoMVC.Layout.js"
></script>
<script
src=
"js/TodoMVC.TodoList.Views.js"
></script>
<script
src=
"js/TodoMVC.TodoList.js"
></script>
<script>
$
(
function
(){
// Start the TodoMVC app (defined in js/TodoMVC.js)
TodoMVC
.
start
();
});
</script>
</body>
</html>
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.Layout.js
0 → 100644
View file @
b7cc462a
TodoMVC
.
module
(
"
Layout
"
,
function
(
Layout
,
App
,
Backbone
,
Marionette
,
$
,
_
){
// Layout Header View
// ------------------
Layout
.
Header
=
Backbone
.
Marionette
.
ItemView
.
extend
({
template
:
"
#template-header
"
,
// UI bindings create cached attributes that
// point to jQuery selected objects
ui
:
{
input
:
'
#new-todo
'
},
events
:
{
'
keypress #new-todo
'
:
'
onInputKeypress
'
},
onInputKeypress
:
function
(
evt
)
{
var
ENTER_KEY
=
13
;
var
todoText
=
this
.
ui
.
input
.
val
().
trim
();
if
(
evt
.
which
===
ENTER_KEY
&&
todoText
)
{
this
.
collection
.
create
({
title
:
todoText
});
this
.
ui
.
input
.
val
(
''
);
}
}
});
// Layout Footer View
// ------------------
Layout
.
Footer
=
Backbone
.
Marionette
.
Layout
.
extend
({
template
:
"
#template-footer
"
,
// UI bindings create cached attributes that
// point to jQuery selected objects
ui
:
{
count
:
'
#todo-count strong
'
,
filters
:
'
#filters a
'
},
events
:
{
'
click #clear-completed
'
:
'
onClearClick
'
},
initialize
:
function
()
{
this
.
bindTo
(
App
.
vent
,
'
todoList:filter
'
,
this
.
updateFilterSelection
,
this
);
this
.
bindTo
(
this
.
collection
,
'
all
'
,
this
.
updateCount
,
this
);
},
onRender
:
function
()
{
this
.
updateCount
();
},
updateCount
:
function
()
{
var
count
=
this
.
collection
.
getActive
().
length
;
this
.
ui
.
count
.
html
(
count
);
if
(
count
===
0
)
{
this
.
$el
.
parent
().
hide
();
}
else
{
this
.
$el
.
parent
().
show
();
}
},
updateFilterSelection
:
function
(
filter
)
{
this
.
ui
.
filters
.
removeClass
(
'
selected
'
)
.
filter
(
'
[href="#
'
+
filter
+
'
"]
'
)
.
addClass
(
'
selected
'
);
},
onClearClick
:
function
()
{
var
completed
=
this
.
collection
.
getCompleted
();
completed
.
forEach
(
function
destroy
(
todo
)
{
todo
.
destroy
();
});
}
});
});
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.TodoList.Views.js
0 → 100644
View file @
b7cc462a
TodoMVC
.
module
(
"
TodoList.Views
"
,
function
(
Views
,
App
,
Backbone
,
Marionette
,
$
,
_
){
// Todo List Item View
// -------------------
//
// Display an individual todo item, and respond to changes
// that are made to the item, including marking completed.
Views
.
ItemView
=
Marionette
.
ItemView
.
extend
({
tagName
:
'
li
'
,
template
:
"
#template-todoItemView
"
,
ui
:
{
edit
:
'
.edit
'
},
events
:
{
'
click .destroy
'
:
'
destroy
'
,
'
dblclick label
'
:
'
onEditClick
'
,
'
keypress .edit
'
:
'
onEditKeypress
'
,
'
click .toggle
'
:
'
toggle
'
},
initialize
:
function
()
{
this
.
bindTo
(
this
.
model
,
'
change
'
,
this
.
render
,
this
);
},
onRender
:
function
()
{
this
.
$el
.
removeClass
(
'
active completed
'
);
if
(
this
.
model
.
get
(
'
completed
'
))
this
.
$el
.
addClass
(
'
completed
'
);
else
this
.
$el
.
addClass
(
'
active
'
);
},
destroy
:
function
()
{
this
.
model
.
destroy
();
},
toggle
:
function
()
{
this
.
model
.
toggle
().
save
();
},
onEditClick
:
function
()
{
this
.
$el
.
addClass
(
'
editing
'
);
this
.
ui
.
edit
.
focus
();
},
onEditKeypress
:
function
(
evt
)
{
var
ENTER_KEY
=
13
;
var
todoText
=
this
.
ui
.
edit
.
val
().
trim
();
if
(
evt
.
which
===
ENTER_KEY
&&
todoText
)
{
this
.
model
.
set
(
'
title
'
,
todoText
).
save
();
this
.
$el
.
removeClass
(
'
editing
'
);
}
}
});
// Item List View
// --------------
//
// Controls the rendering of the list of items, including the
// filtering of activs vs completed items for display.
Views
.
ListView
=
Backbone
.
Marionette
.
CompositeView
.
extend
({
template
:
"
#template-todoListCompositeView
"
,
itemView
:
Views
.
ItemView
,
itemViewContainer
:
'
#todo-list
'
,
ui
:
{
toggle
:
'
#toggle-all
'
},
events
:
{
'
click #toggle-all
'
:
'
onToggleAllClick
'
},
initialize
:
function
()
{
this
.
bindTo
(
this
.
collection
,
'
all
'
,
this
.
update
,
this
);
},
onRender
:
function
()
{
this
.
update
();
},
update
:
function
()
{
function
reduceCompleted
(
left
,
right
)
{
return
left
&&
right
.
get
(
'
completed
'
);
}
var
allCompleted
=
this
.
collection
.
reduce
(
reduceCompleted
,
true
);
this
.
ui
.
toggle
.
prop
(
'
checked
'
,
allCompleted
);
if
(
this
.
collection
.
length
===
0
)
{
this
.
$el
.
parent
().
hide
();
}
else
{
this
.
$el
.
parent
().
show
();
}
},
onToggleAllClick
:
function
(
evt
)
{
var
isChecked
=
evt
.
currentTarget
.
checked
;
this
.
collection
.
each
(
function
(
todo
){
todo
.
save
({
'
completed
'
:
isChecked
});
});
}
});
// Application Event Handlers
// --------------------------
//
// Handler for filtering the list of items by showing and
// hiding through the use of various CSS classes
App
.
vent
.
on
(
'
todoList:filter
'
,
function
(
filter
)
{
filter
=
filter
||
'
all
'
;
$
(
'
#todoapp
'
).
attr
(
'
class
'
,
'
filter-
'
+
filter
);
});
});
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.TodoList.js
0 → 100644
View file @
b7cc462a
TodoMVC
.
module
(
"
TodoList
"
,
function
(
TodoList
,
App
,
Backbone
,
Marionette
,
$
,
_
){
// TodoList Router
// ---------------
//
// Handle routes to show the active vs complete todo items
TodoList
.
Router
=
Marionette
.
AppRouter
.
extend
({
appRoutes
:
{
"
*filter
"
:
"
filterItems
"
}
});
// TodoList Controller (Mediator)
// ------------------------------
//
// Control the workflow and logic that exists at the application
// level, above the implementation detail of views and models
TodoList
.
Controller
=
function
(){
this
.
todoList
=
new
App
.
Todos
.
TodoList
();
};
_
.
extend
(
TodoList
.
Controller
.
prototype
,
{
// Start the app by showing the appropriate views
// and fetching the list of todo items, if there are any
start
:
function
(){
this
.
showHeader
(
this
.
todoList
);
this
.
showFooter
(
this
.
todoList
);
this
.
showTodoList
(
this
.
todoList
);
this
.
todoList
.
fetch
();
},
showHeader
:
function
(
todoList
){
var
header
=
new
App
.
Layout
.
Header
({
collection
:
todoList
});
App
.
header
.
show
(
header
);
},
showFooter
:
function
(
todoList
){
var
footer
=
new
App
.
Layout
.
Footer
({
collection
:
todoList
});
App
.
footer
.
show
(
footer
);
},
showTodoList
:
function
(
todoList
){
App
.
main
.
show
(
new
TodoList
.
Views
.
ListView
({
collection
:
todoList
}));
},
// Set the filter to show complete or all items
filterItems
:
function
(
filter
){
App
.
vent
.
trigger
(
"
todoList:filter
"
,
filter
.
trim
()
||
""
);
}
});
// TodoList Initializer
// --------------------
//
// Get the TodoList up and running by initializing the mediator
// when the the application is started, pulling in all of the
// existing Todo items and displaying them.
TodoList
.
addInitializer
(
function
(){
var
controller
=
new
TodoList
.
Controller
();
new
TodoList
.
Router
({
controller
:
controller
});
controller
.
start
();
});
});
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.Todos.js
0 → 100644
View file @
b7cc462a
TodoMVC
.
module
(
"
Todos
"
,
function
(
Todos
,
App
,
Backbone
,
Marionette
,
$
,
_
){
// Todo Model
// ----------
Todos
.
Todo
=
Backbone
.
Model
.
extend
({
localStorage
:
new
Backbone
.
LocalStorage
(
'
todos-backbone
'
),
defaults
:
{
title
:
''
,
completed
:
false
,
created
:
0
},
initialize
:
function
()
{
if
(
this
.
isNew
())
this
.
set
(
'
created
'
,
Date
.
now
());
},
toggle
:
function
()
{
return
this
.
set
(
'
completed
'
,
!
this
.
isCompleted
());
},
isCompleted
:
function
()
{
return
this
.
get
(
'
completed
'
);
}
});
// Todo Collection
// ---------------
Todos
.
TodoList
=
Backbone
.
Collection
.
extend
({
model
:
Todos
.
Todo
,
localStorage
:
new
Backbone
.
LocalStorage
(
'
todos-backbone
'
),
getCompleted
:
function
()
{
return
this
.
filter
(
this
.
_isCompleted
);
},
getActive
:
function
()
{
return
this
.
reject
(
this
.
_isCompleted
);
},
comparator
:
function
(
todo
)
{
return
todo
.
get
(
'
created
'
);
},
_isCompleted
:
function
(
todo
){
return
todo
.
isCompleted
();
}
});
});
labs/architecture-examples/backbone_marionette_modules/js/TodoMVC.js
0 → 100644
View file @
b7cc462a
var
TodoMVC
=
new
Backbone
.
Marionette
.
Application
();
TodoMVC
.
addRegions
({
header
:
'
#header
'
,
main
:
'
#main
'
,
footer
:
'
#footer
'
});
TodoMVC
.
on
(
"
initialize:after
"
,
function
(){
Backbone
.
history
.
start
();
});
labs/architecture-examples/backbone_marionette_modules/js/lib/backbone-localStorage.js
0 → 100644
View file @
b7cc462a
/**
* Backbone localStorage Adapter
* https://github.com/jeromegn/Backbone.localStorage
*/
(
function
()
{
// A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that.
// Hold reference to Underscore.js and Backbone.js in the closure in order
// to make things work even if they are removed from the global namespace
var
_
=
this
.
_
;
var
Backbone
=
this
.
Backbone
;
// Generate four random hex digits.
function
S4
()
{
return
(((
1
+
Math
.
random
())
*
0x10000
)
|
0
).
toString
(
16
).
substring
(
1
);
};
// Generate a pseudo-GUID by concatenating random hexadecimal.
function
guid
()
{
return
(
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
());
};
// Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table.
// window.Store is deprectated, use Backbone.LocalStorage instead
Backbone
.
LocalStorage
=
window
.
Store
=
function
(
name
)
{
this
.
name
=
name
;
var
store
=
this
.
localStorage
().
getItem
(
this
.
name
);
this
.
records
=
(
store
&&
store
.
split
(
"
,
"
))
||
[];
};
_
.
extend
(
Backbone
.
LocalStorage
.
prototype
,
{
// Save the current state of the **Store** to *localStorage*.
save
:
function
()
{
this
.
localStorage
().
setItem
(
this
.
name
,
this
.
records
.
join
(
"
,
"
));
},
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create
:
function
(
model
)
{
if
(
!
model
.
id
)
{
model
.
id
=
guid
();
model
.
set
(
model
.
idAttribute
,
model
.
id
);
}
this
.
localStorage
().
setItem
(
this
.
name
+
"
-
"
+
model
.
id
,
JSON
.
stringify
(
model
));
this
.
records
.
push
(
model
.
id
.
toString
());
this
.
save
();
return
model
.
toJSON
();
},
// Update a model by replacing its copy in `this.data`.
update
:
function
(
model
)
{
this
.
localStorage
().
setItem
(
this
.
name
+
"
-
"
+
model
.
id
,
JSON
.
stringify
(
model
));
if
(
!
_
.
include
(
this
.
records
,
model
.
id
.
toString
()))
this
.
records
.
push
(
model
.
id
.
toString
());
this
.
save
();
return
model
.
toJSON
();
},
// Retrieve a model from `this.data` by id.
find
:
function
(
model
)
{
return
JSON
.
parse
(
this
.
localStorage
().
getItem
(
this
.
name
+
"
-
"
+
model
.
id
));
},
// Return the array of all models currently in storage.
findAll
:
function
()
{
return
_
(
this
.
records
).
chain
()
.
map
(
function
(
id
){
return
JSON
.
parse
(
this
.
localStorage
().
getItem
(
this
.
name
+
"
-
"
+
id
));},
this
)
.
compact
()
.
value
();
},
// Delete a model from `this.data`, returning it.
destroy
:
function
(
model
)
{
this
.
localStorage
().
removeItem
(
this
.
name
+
"
-
"
+
model
.
id
);
this
.
records
=
_
.
reject
(
this
.
records
,
function
(
record_id
){
return
record_id
==
model
.
id
.
toString
();});
this
.
save
();
return
model
;
},
localStorage
:
function
()
{
return
localStorage
;
}
});
// localSync delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`.
// window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
Backbone
.
LocalStorage
.
sync
=
window
.
Store
.
sync
=
Backbone
.
localSync
=
function
(
method
,
model
,
options
,
error
)
{
var
store
=
model
.
localStorage
||
model
.
collection
.
localStorage
;
// Backwards compatibility with Backbone <= 0.3.3
if
(
typeof
options
==
'
function
'
)
{
options
=
{
success
:
options
,
error
:
error
};
}
var
resp
;
switch
(
method
)
{
case
"
read
"
:
resp
=
model
.
id
!=
undefined
?
store
.
find
(
model
)
:
store
.
findAll
();
break
;
case
"
create
"
:
resp
=
store
.
create
(
model
);
break
;
case
"
update
"
:
resp
=
store
.
update
(
model
);
break
;
case
"
delete
"
:
resp
=
store
.
destroy
(
model
);
break
;
}
if
(
resp
)
{
options
.
success
(
resp
);
}
else
{
options
.
error
(
"
Record not found
"
);
}
};
Backbone
.
ajaxSync
=
Backbone
.
sync
;
Backbone
.
getSyncMethod
=
function
(
model
)
{
if
(
model
.
localStorage
||
(
model
.
collection
&&
model
.
collection
.
localStorage
))
{
return
Backbone
.
localSync
;
}
return
Backbone
.
ajaxSync
;
};
// Override 'Backbone.sync' to default to localSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone
.
sync
=
function
(
method
,
model
,
options
,
error
)
{
return
Backbone
.
getSyncMethod
(
model
).
apply
(
this
,
[
method
,
model
,
options
,
error
]);
};
})();
labs/architecture-examples/backbone_marionette_modules/js/lib/backbone.js
0 → 100644
View file @
b7cc462a
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(
function
(){
// Initial Setup
// -------------
// Save a reference to the global object (`window` in the browser, `global`
// on the server).
var
root
=
this
;
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var
previousBackbone
=
root
.
Backbone
;
// Create a local reference to slice/splice.
var
slice
=
Array
.
prototype
.
slice
;
var
splice
=
Array
.
prototype
.
splice
;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
var
Backbone
;
if
(
typeof
exports
!==
'
undefined
'
)
{
Backbone
=
exports
;
}
else
{
Backbone
=
root
.
Backbone
=
{};
}
// Current version of the library. Keep in sync with `package.json`.
Backbone
.
VERSION
=
'
0.9.2
'
;
// Require Underscore, if we're on the server, and it's not already present.
var
_
=
root
.
_
;
if
(
!
_
&&
(
typeof
require
!==
'
undefined
'
))
_
=
require
(
'
underscore
'
);
// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
var
$
=
root
.
jQuery
||
root
.
Zepto
||
root
.
ender
;
// Set the JavaScript library that will be used for DOM manipulation and
// Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
// Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
// alternate JavaScript library (or a mock library for testing your views
// outside of a browser).
Backbone
.
setDomLibrary
=
function
(
lib
)
{
$
=
lib
;
};
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object.
Backbone
.
noConflict
=
function
()
{
root
.
Backbone
=
previousBackbone
;
return
this
;
};
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
// set a `X-Http-Method-Override` header.
Backbone
.
emulateHTTP
=
false
;
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
// `application/json` requests ... will encode the body as
// `application/x-www-form-urlencoded` instead and will send the model in a
// form param named `model`.
Backbone
.
emulateJSON
=
false
;
// Backbone.Events
// -----------------
// Regular expression used to split event strings
var
eventSplitter
=
/
\s
+/
;
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback functions
// to an event; trigger`-ing an event fires all callbacks in succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
var
Events
=
Backbone
.
Events
=
{
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on
:
function
(
events
,
callback
,
context
)
{
var
calls
,
event
,
node
,
tail
,
list
;
if
(
!
callback
)
return
this
;
events
=
events
.
split
(
eventSplitter
);
calls
=
this
.
_callbacks
||
(
this
.
_callbacks
=
{});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while
(
event
=
events
.
shift
())
{
list
=
calls
[
event
];
node
=
list
?
list
.
tail
:
{};
node
.
next
=
tail
=
{};
node
.
context
=
context
;
node
.
callback
=
callback
;
calls
[
event
]
=
{
tail
:
tail
,
next
:
list
?
list
.
next
:
node
};
}
return
this
;
},
// Remove one or many callbacks. If `context` is null, removes all callbacks
// with that function. If `callback` is null, removes all callbacks for the
// event. If `events` is null, removes all bound callbacks for all events.
off
:
function
(
events
,
callback
,
context
)
{
var
event
,
calls
,
node
,
tail
,
cb
,
ctx
;
// No events, or removing *all* events.
if
(
!
(
calls
=
this
.
_callbacks
))
return
;
if
(
!
(
events
||
callback
||
context
))
{
delete
this
.
_callbacks
;
return
this
;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events
=
events
?
events
.
split
(
eventSplitter
)
:
_
.
keys
(
calls
);
while
(
event
=
events
.
shift
())
{
node
=
calls
[
event
];
delete
calls
[
event
];
if
(
!
node
||
!
(
callback
||
context
))
continue
;
// Create a new list, omitting the indicated callbacks.
tail
=
node
.
tail
;
while
((
node
=
node
.
next
)
!==
tail
)
{
cb
=
node
.
callback
;
ctx
=
node
.
context
;
if
((
callback
&&
cb
!==
callback
)
||
(
context
&&
ctx
!==
context
))
{
this
.
on
(
event
,
cb
,
ctx
);
}
}
}
return
this
;
},
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger
:
function
(
events
)
{
var
event
,
node
,
calls
,
tail
,
args
,
all
,
rest
;
if
(
!
(
calls
=
this
.
_callbacks
))
return
this
;
all
=
calls
.
all
;
events
=
events
.
split
(
eventSplitter
);
rest
=
slice
.
call
(
arguments
,
1
);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while
(
event
=
events
.
shift
())
{
if
(
node
=
calls
[
event
])
{
tail
=
node
.
tail
;
while
((
node
=
node
.
next
)
!==
tail
)
{
node
.
callback
.
apply
(
node
.
context
||
this
,
rest
);
}
}
if
(
node
=
all
)
{
tail
=
node
.
tail
;
args
=
[
event
].
concat
(
rest
);
while
((
node
=
node
.
next
)
!==
tail
)
{
node
.
callback
.
apply
(
node
.
context
||
this
,
args
);
}
}
}
return
this
;
}
};
// Aliases for backwards compatibility.
Events
.
bind
=
Events
.
on
;
Events
.
unbind
=
Events
.
off
;
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var
Model
=
Backbone
.
Model
=
function
(
attributes
,
options
)
{
var
defaults
;
attributes
||
(
attributes
=
{});
if
(
options
&&
options
.
parse
)
attributes
=
this
.
parse
(
attributes
);
if
(
defaults
=
getValue
(
this
,
'
defaults
'
))
{
attributes
=
_
.
extend
({},
defaults
,
attributes
);
}
if
(
options
&&
options
.
collection
)
this
.
collection
=
options
.
collection
;
this
.
attributes
=
{};
this
.
_escapedAttributes
=
{};
this
.
cid
=
_
.
uniqueId
(
'
c
'
);
this
.
changed
=
{};
this
.
_silent
=
{};
this
.
_pending
=
{};
this
.
set
(
attributes
,
{
silent
:
true
});
// Reset change tracking.
this
.
changed
=
{};
this
.
_silent
=
{};
this
.
_pending
=
{};
this
.
_previousAttributes
=
_
.
clone
(
this
.
attributes
);
this
.
initialize
.
apply
(
this
,
arguments
);
};
// Attach all inheritable methods to the Model prototype.
_
.
extend
(
Model
.
prototype
,
Events
,
{
// A hash of attributes whose current and previous value differ.
changed
:
null
,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent
:
null
,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending
:
null
,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute
:
'
id
'
,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
// Return a copy of the model's `attributes` object.
toJSON
:
function
(
options
)
{
return
_
.
clone
(
this
.
attributes
);
},
// Get the value of an attribute.
get
:
function
(
attr
)
{
return
this
.
attributes
[
attr
];
},
// Get the HTML-escaped value of an attribute.
escape
:
function
(
attr
)
{
var
html
;
if
(
html
=
this
.
_escapedAttributes
[
attr
])
return
html
;
var
val
=
this
.
get
(
attr
);
return
this
.
_escapedAttributes
[
attr
]
=
_
.
escape
(
val
==
null
?
''
:
''
+
val
);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has
:
function
(
attr
)
{
return
this
.
get
(
attr
)
!=
null
;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set
:
function
(
key
,
value
,
options
)
{
var
attrs
,
attr
,
val
;
// Handle both `"key", value` and `{key: value}` -style arguments.
if
(
_
.
isObject
(
key
)
||
key
==
null
)
{
attrs
=
key
;
options
=
value
;
}
else
{
attrs
=
{};
attrs
[
key
]
=
value
;
}
// Extract attributes and options.
options
||
(
options
=
{});
if
(
!
attrs
)
return
this
;
if
(
attrs
instanceof
Model
)
attrs
=
attrs
.
attributes
;
if
(
options
.
unset
)
for
(
attr
in
attrs
)
attrs
[
attr
]
=
void
0
;
// Run validation.
if
(
!
this
.
_validate
(
attrs
,
options
))
return
false
;
// Check for changes of `id`.
if
(
this
.
idAttribute
in
attrs
)
this
.
id
=
attrs
[
this
.
idAttribute
];
var
changes
=
options
.
changes
=
{};
var
now
=
this
.
attributes
;
var
escaped
=
this
.
_escapedAttributes
;
var
prev
=
this
.
_previousAttributes
||
{};
// For each `set` attribute...
for
(
attr
in
attrs
)
{
val
=
attrs
[
attr
];
// If the new and current value differ, record the change.
if
(
!
_
.
isEqual
(
now
[
attr
],
val
)
||
(
options
.
unset
&&
_
.
has
(
now
,
attr
)))
{
delete
escaped
[
attr
];
(
options
.
silent
?
this
.
_silent
:
changes
)[
attr
]
=
true
;
}
// Update or delete the current value.
options
.
unset
?
delete
now
[
attr
]
:
now
[
attr
]
=
val
;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if
(
!
_
.
isEqual
(
prev
[
attr
],
val
)
||
(
_
.
has
(
now
,
attr
)
!=
_
.
has
(
prev
,
attr
)))
{
this
.
changed
[
attr
]
=
val
;
if
(
!
options
.
silent
)
this
.
_pending
[
attr
]
=
true
;
}
else
{
delete
this
.
changed
[
attr
];
delete
this
.
_pending
[
attr
];
}
}
// Fire the `"change"` events.
if
(
!
options
.
silent
)
this
.
change
(
options
);
return
this
;
},
// Remove an attribute from the model, firing `"change"` unless you choose
// to silence it. `unset` is a noop if the attribute doesn't exist.
unset
:
function
(
attr
,
options
)
{
(
options
||
(
options
=
{})).
unset
=
true
;
return
this
.
set
(
attr
,
null
,
options
);
},
// Clear all attributes on the model, firing `"change"` unless you choose
// to silence it.
clear
:
function
(
options
)
{
(
options
||
(
options
=
{})).
unset
=
true
;
return
this
.
set
(
_
.
clone
(
this
.
attributes
),
options
);
},
// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overriden,
// triggering a `"change"` event.
fetch
:
function
(
options
)
{
options
=
options
?
_
.
clone
(
options
)
:
{};
var
model
=
this
;
var
success
=
options
.
success
;
options
.
success
=
function
(
resp
,
status
,
xhr
)
{
if
(
!
model
.
set
(
model
.
parse
(
resp
,
xhr
),
options
))
return
false
;
if
(
success
)
success
(
model
,
resp
);
};
options
.
error
=
Backbone
.
wrapError
(
options
.
error
,
model
,
options
);
return
(
this
.
sync
||
Backbone
.
sync
).
call
(
this
,
'
read
'
,
this
,
options
);
},
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save
:
function
(
key
,
value
,
options
)
{
var
attrs
,
current
;
// Handle both `("key", value)` and `({key: value})` -style calls.
if
(
_
.
isObject
(
key
)
||
key
==
null
)
{
attrs
=
key
;
options
=
value
;
}
else
{
attrs
=
{};
attrs
[
key
]
=
value
;
}
options
=
options
?
_
.
clone
(
options
)
:
{};
// If we're "wait"-ing to set changed attributes, validate early.
if
(
options
.
wait
)
{
if
(
!
this
.
_validate
(
attrs
,
options
))
return
false
;
current
=
_
.
clone
(
this
.
attributes
);
}
// Regular saves `set` attributes before persisting to the server.
var
silentOptions
=
_
.
extend
({},
options
,
{
silent
:
true
});
if
(
attrs
&&
!
this
.
set
(
attrs
,
options
.
wait
?
silentOptions
:
options
))
{
return
false
;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var
model
=
this
;
var
success
=
options
.
success
;
options
.
success
=
function
(
resp
,
status
,
xhr
)
{
var
serverAttrs
=
model
.
parse
(
resp
,
xhr
);
if
(
options
.
wait
)
{
delete
options
.
wait
;
serverAttrs
=
_
.
extend
(
attrs
||
{},
serverAttrs
);
}
if
(
!
model
.
set
(
serverAttrs
,
options
))
return
false
;
if
(
success
)
{
success
(
model
,
resp
);
}
else
{
model
.
trigger
(
'
sync
'
,
model
,
resp
,
options
);
}
};
// Finish configuring and sending the Ajax request.
options
.
error
=
Backbone
.
wrapError
(
options
.
error
,
model
,
options
);
var
method
=
this
.
isNew
()
?
'
create
'
:
'
update
'
;
var
xhr
=
(
this
.
sync
||
Backbone
.
sync
).
call
(
this
,
method
,
this
,
options
);
if
(
options
.
wait
)
this
.
set
(
current
,
silentOptions
);
return
xhr
;
},
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
destroy
:
function
(
options
)
{
options
=
options
?
_
.
clone
(
options
)
:
{};
var
model
=
this
;
var
success
=
options
.
success
;
var
triggerDestroy
=
function
()
{
model
.
trigger
(
'
destroy
'
,
model
,
model
.
collection
,
options
);
};
if
(
this
.
isNew
())
{
triggerDestroy
();
return
false
;
}
options
.
success
=
function
(
resp
)
{
if
(
options
.
wait
)
triggerDestroy
();
if
(
success
)
{
success
(
model
,
resp
);
}
else
{
model
.
trigger
(
'
sync
'
,
model
,
resp
,
options
);
}
};
options
.
error
=
Backbone
.
wrapError
(
options
.
error
,
model
,
options
);
var
xhr
=
(
this
.
sync
||
Backbone
.
sync
).
call
(
this
,
'
delete
'
,
this
,
options
);
if
(
!
options
.
wait
)
triggerDestroy
();
return
xhr
;
},
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url
:
function
()
{
var
base
=
getValue
(
this
,
'
urlRoot
'
)
||
getValue
(
this
.
collection
,
'
url
'
)
||
urlError
();
if
(
this
.
isNew
())
return
base
;
return
base
+
(
base
.
charAt
(
base
.
length
-
1
)
==
'
/
'
?
''
:
'
/
'
)
+
encodeURIComponent
(
this
.
id
);
},
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse
:
function
(
resp
,
xhr
)
{
return
resp
;
},
// Create a new model with identical attributes to this one.
clone
:
function
()
{
return
new
this
.
constructor
(
this
.
attributes
);
},
// A model is new if it has never been saved to the server, and lacks an id.
isNew
:
function
()
{
return
this
.
id
==
null
;
},
// Call this method to manually fire a `"change"` event for this model and
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change
:
function
(
options
)
{
options
||
(
options
=
{});
var
changing
=
this
.
_changing
;
this
.
_changing
=
true
;
// Silent changes become pending changes.
for
(
var
attr
in
this
.
_silent
)
this
.
_pending
[
attr
]
=
true
;
// Silent changes are triggered.
var
changes
=
_
.
extend
({},
options
.
changes
,
this
.
_silent
);
this
.
_silent
=
{};
for
(
var
attr
in
changes
)
{
this
.
trigger
(
'
change:
'
+
attr
,
this
,
this
.
get
(
attr
),
options
);
}
if
(
changing
)
return
this
;
// Continue firing `"change"` events while there are pending changes.
while
(
!
_
.
isEmpty
(
this
.
_pending
))
{
this
.
_pending
=
{};
this
.
trigger
(
'
change
'
,
this
,
options
);
// Pending and silent changes still remain.
for
(
var
attr
in
this
.
changed
)
{
if
(
this
.
_pending
[
attr
]
||
this
.
_silent
[
attr
])
continue
;
delete
this
.
changed
[
attr
];
}
this
.
_previousAttributes
=
_
.
clone
(
this
.
attributes
);
}
this
.
_changing
=
false
;
return
this
;
},
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged
:
function
(
attr
)
{
if
(
!
arguments
.
length
)
return
!
_
.
isEmpty
(
this
.
changed
);
return
_
.
has
(
this
.
changed
,
attr
);
},
// Return an object containing all the attributes that have changed, or
// false if there are no changed attributes. Useful for determining what
// parts of a view need to be updated and/or what attributes need to be
// persisted to the server. Unset attributes will be set to undefined.
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
changedAttributes
:
function
(
diff
)
{
if
(
!
diff
)
return
this
.
hasChanged
()
?
_
.
clone
(
this
.
changed
)
:
false
;
var
val
,
changed
=
false
,
old
=
this
.
_previousAttributes
;
for
(
var
attr
in
diff
)
{
if
(
_
.
isEqual
(
old
[
attr
],
(
val
=
diff
[
attr
])))
continue
;
(
changed
||
(
changed
=
{}))[
attr
]
=
val
;
}
return
changed
;
},
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous
:
function
(
attr
)
{
if
(
!
arguments
.
length
||
!
this
.
_previousAttributes
)
return
null
;
return
this
.
_previousAttributes
[
attr
];
},
// Get all of the attributes of the model at the time of the previous
// `"change"` event.
previousAttributes
:
function
()
{
return
_
.
clone
(
this
.
_previousAttributes
);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid
:
function
()
{
return
!
this
.
validate
(
this
.
attributes
);
},
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate
:
function
(
attrs
,
options
)
{
if
(
options
.
silent
||
!
this
.
validate
)
return
true
;
attrs
=
_
.
extend
({},
this
.
attributes
,
attrs
);
var
error
=
this
.
validate
(
attrs
,
options
);
if
(
!
error
)
return
true
;
if
(
options
&&
options
.
error
)
{
options
.
error
(
this
,
error
,
options
);
}
else
{
this
.
trigger
(
'
error
'
,
this
,
error
,
options
);
}
return
false
;
}
});
// Backbone.Collection
// -------------------
// Provides a standard collection class for our sets of models, ordered
// or unordered. If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
var
Collection
=
Backbone
.
Collection
=
function
(
models
,
options
)
{
options
||
(
options
=
{});
if
(
options
.
model
)
this
.
model
=
options
.
model
;
if
(
options
.
comparator
)
this
.
comparator
=
options
.
comparator
;
this
.
_reset
();
this
.
initialize
.
apply
(
this
,
arguments
);
if
(
models
)
this
.
reset
(
models
,
{
silent
:
true
,
parse
:
options
.
parse
});
};
// Define the Collection's inheritable methods.
_
.
extend
(
Collection
.
prototype
,
Events
,
{
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model
:
Model
,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON
:
function
(
options
)
{
return
this
.
map
(
function
(
model
){
return
model
.
toJSON
(
options
);
});
},
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add
:
function
(
models
,
options
)
{
var
i
,
index
,
length
,
model
,
cid
,
id
,
cids
=
{},
ids
=
{},
dups
=
[];
options
||
(
options
=
{});
models
=
_
.
isArray
(
models
)
?
models
.
slice
()
:
[
models
];
// Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added.
for
(
i
=
0
,
length
=
models
.
length
;
i
<
length
;
i
++
)
{
if
(
!
(
model
=
models
[
i
]
=
this
.
_prepareModel
(
models
[
i
],
options
)))
{
throw
new
Error
(
"
Can't add an invalid model to a collection
"
);
}
cid
=
model
.
cid
;
id
=
model
.
id
;
if
(
cids
[
cid
]
||
this
.
_byCid
[
cid
]
||
((
id
!=
null
)
&&
(
ids
[
id
]
||
this
.
_byId
[
id
])))
{
dups
.
push
(
i
);
continue
;
}
cids
[
cid
]
=
ids
[
id
]
=
model
;
}
// Remove duplicates.
i
=
dups
.
length
;
while
(
i
--
)
{
models
.
splice
(
dups
[
i
],
1
);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for
(
i
=
0
,
length
=
models
.
length
;
i
<
length
;
i
++
)
{
(
model
=
models
[
i
]).
on
(
'
all
'
,
this
.
_onModelEvent
,
this
);
this
.
_byCid
[
model
.
cid
]
=
model
;
if
(
model
.
id
!=
null
)
this
.
_byId
[
model
.
id
]
=
model
;
}
// Insert models into the collection, re-sorting if needed, and triggering
// `add` events unless silenced.
this
.
length
+=
length
;
index
=
options
.
at
!=
null
?
options
.
at
:
this
.
models
.
length
;
splice
.
apply
(
this
.
models
,
[
index
,
0
].
concat
(
models
));
if
(
this
.
comparator
)
this
.
sort
({
silent
:
true
});
if
(
options
.
silent
)
return
this
;
for
(
i
=
0
,
length
=
this
.
models
.
length
;
i
<
length
;
i
++
)
{
if
(
!
cids
[(
model
=
this
.
models
[
i
]).
cid
])
continue
;
options
.
index
=
i
;
model
.
trigger
(
'
add
'
,
model
,
this
,
options
);
}
return
this
;
},
// Remove a model, or a list of models from the set. Pass silent to avoid
// firing the `remove` event for every model removed.
remove
:
function
(
models
,
options
)
{
var
i
,
l
,
index
,
model
;
options
||
(
options
=
{});
models
=
_
.
isArray
(
models
)
?
models
.
slice
()
:
[
models
];
for
(
i
=
0
,
l
=
models
.
length
;
i
<
l
;
i
++
)
{
model
=
this
.
getByCid
(
models
[
i
])
||
this
.
get
(
models
[
i
]);
if
(
!
model
)
continue
;
delete
this
.
_byId
[
model
.
id
];
delete
this
.
_byCid
[
model
.
cid
];
index
=
this
.
indexOf
(
model
);
this
.
models
.
splice
(
index
,
1
);
this
.
length
--
;
if
(
!
options
.
silent
)
{
options
.
index
=
index
;
model
.
trigger
(
'
remove
'
,
model
,
this
,
options
);
}
this
.
_removeReference
(
model
);
}
return
this
;
},
// Add a model to the end of the collection.
push
:
function
(
model
,
options
)
{
model
=
this
.
_prepareModel
(
model
,
options
);
this
.
add
(
model
,
options
);
return
model
;
},
// Remove a model from the end of the collection.
pop
:
function
(
options
)
{
var
model
=
this
.
at
(
this
.
length
-
1
);
this
.
remove
(
model
,
options
);
return
model
;
},
// Add a model to the beginning of the collection.
unshift
:
function
(
model
,
options
)
{
model
=
this
.
_prepareModel
(
model
,
options
);
this
.
add
(
model
,
_
.
extend
({
at
:
0
},
options
));
return
model
;
},
// Remove a model from the beginning of the collection.
shift
:
function
(
options
)
{
var
model
=
this
.
at
(
0
);
this
.
remove
(
model
,
options
);
return
model
;
},
// Get a model from the set by id.
get
:
function
(
id
)
{
if
(
id
==
null
)
return
void
0
;
return
this
.
_byId
[
id
.
id
!=
null
?
id
.
id
:
id
];
},
// Get a model from the set by client id.
getByCid
:
function
(
cid
)
{
return
cid
&&
this
.
_byCid
[
cid
.
cid
||
cid
];
},
// Get the model at the given index.
at
:
function
(
index
)
{
return
this
.
models
[
index
];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
where
:
function
(
attrs
)
{
if
(
_
.
isEmpty
(
attrs
))
return
[];
return
this
.
filter
(
function
(
model
)
{
for
(
var
key
in
attrs
)
{
if
(
attrs
[
key
]
!==
model
.
get
(
key
))
return
false
;
}
return
true
;
});
},
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort
:
function
(
options
)
{
options
||
(
options
=
{});
if
(
!
this
.
comparator
)
throw
new
Error
(
'
Cannot sort a set without a comparator
'
);
var
boundComparator
=
_
.
bind
(
this
.
comparator
,
this
);
if
(
this
.
comparator
.
length
==
1
)
{
this
.
models
=
this
.
sortBy
(
boundComparator
);
}
else
{
this
.
models
.
sort
(
boundComparator
);
}
if
(
!
options
.
silent
)
this
.
trigger
(
'
reset
'
,
this
,
options
);
return
this
;
},
// Pluck an attribute from each model in the collection.
pluck
:
function
(
attr
)
{
return
_
.
map
(
this
.
models
,
function
(
model
){
return
model
.
get
(
attr
);
});
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished.
reset
:
function
(
models
,
options
)
{
models
||
(
models
=
[]);
options
||
(
options
=
{});
for
(
var
i
=
0
,
l
=
this
.
models
.
length
;
i
<
l
;
i
++
)
{
this
.
_removeReference
(
this
.
models
[
i
]);
}
this
.
_reset
();
this
.
add
(
models
,
_
.
extend
({
silent
:
true
},
options
));
if
(
!
options
.
silent
)
this
.
trigger
(
'
reset
'
,
this
,
options
);
return
this
;
},
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of resetting.
fetch
:
function
(
options
)
{
options
=
options
?
_
.
clone
(
options
)
:
{};
if
(
options
.
parse
===
undefined
)
options
.
parse
=
true
;
var
collection
=
this
;
var
success
=
options
.
success
;
options
.
success
=
function
(
resp
,
status
,
xhr
)
{
collection
[
options
.
add
?
'
add
'
:
'
reset
'
](
collection
.
parse
(
resp
,
xhr
),
options
);
if
(
success
)
success
(
collection
,
resp
);
};
options
.
error
=
Backbone
.
wrapError
(
options
.
error
,
collection
,
options
);
return
(
this
.
sync
||
Backbone
.
sync
).
call
(
this
,
'
read
'
,
this
,
options
);
},
// Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
create
:
function
(
model
,
options
)
{
var
coll
=
this
;
options
=
options
?
_
.
clone
(
options
)
:
{};
model
=
this
.
_prepareModel
(
model
,
options
);
if
(
!
model
)
return
false
;
if
(
!
options
.
wait
)
coll
.
add
(
model
,
options
);
var
success
=
options
.
success
;
options
.
success
=
function
(
nextModel
,
resp
,
xhr
)
{
if
(
options
.
wait
)
coll
.
add
(
nextModel
,
options
);
if
(
success
)
{
success
(
nextModel
,
resp
);
}
else
{
nextModel
.
trigger
(
'
sync
'
,
model
,
resp
,
options
);
}
};
model
.
save
(
null
,
options
);
return
model
;
},
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
parse
:
function
(
resp
,
xhr
)
{
return
resp
;
},
// Proxy to _'s chain. Can't be proxied the same way the rest of the
// underscore methods are proxied because it relies on the underscore
// constructor.
chain
:
function
()
{
return
_
(
this
.
models
).
chain
();
},
// Reset all internal state. Called when the collection is reset.
_reset
:
function
(
options
)
{
this
.
length
=
0
;
this
.
models
=
[];
this
.
_byId
=
{};
this
.
_byCid
=
{};
},
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel
:
function
(
model
,
options
)
{
options
||
(
options
=
{});
if
(
!
(
model
instanceof
Model
))
{
var
attrs
=
model
;
options
.
collection
=
this
;
model
=
new
this
.
model
(
attrs
,
options
);
if
(
!
model
.
_validate
(
model
.
attributes
,
options
))
model
=
false
;
}
else
if
(
!
model
.
collection
)
{
model
.
collection
=
this
;
}
return
model
;
},
// Internal method to remove a model's ties to a collection.
_removeReference
:
function
(
model
)
{
if
(
this
==
model
.
collection
)
{
delete
model
.
collection
;
}
model
.
off
(
'
all
'
,
this
.
_onModelEvent
,
this
);
},
// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent
:
function
(
event
,
model
,
collection
,
options
)
{
if
((
event
==
'
add
'
||
event
==
'
remove
'
)
&&
collection
!=
this
)
return
;
if
(
event
==
'
destroy
'
)
{
this
.
remove
(
model
,
options
);
}
if
(
model
&&
event
===
'
change:
'
+
model
.
idAttribute
)
{
delete
this
.
_byId
[
model
.
previous
(
model
.
idAttribute
)];
this
.
_byId
[
model
.
id
]
=
model
;
}
this
.
trigger
.
apply
(
this
,
arguments
);
}
});
// Underscore methods that we want to implement on the Collection.
var
methods
=
[
'
forEach
'
,
'
each
'
,
'
map
'
,
'
reduce
'
,
'
reduceRight
'
,
'
find
'
,
'
detect
'
,
'
filter
'
,
'
select
'
,
'
reject
'
,
'
every
'
,
'
all
'
,
'
some
'
,
'
any
'
,
'
include
'
,
'
contains
'
,
'
invoke
'
,
'
max
'
,
'
min
'
,
'
sortBy
'
,
'
sortedIndex
'
,
'
toArray
'
,
'
size
'
,
'
first
'
,
'
initial
'
,
'
rest
'
,
'
last
'
,
'
without
'
,
'
indexOf
'
,
'
shuffle
'
,
'
lastIndexOf
'
,
'
isEmpty
'
,
'
groupBy
'
];
// Mix in each Underscore method as a proxy to `Collection#models`.
_
.
each
(
methods
,
function
(
method
)
{
Collection
.
prototype
[
method
]
=
function
()
{
return
_
[
method
].
apply
(
_
,
[
this
.
models
].
concat
(
_
.
toArray
(
arguments
)));
};
});
// Backbone.Router
// -------------------
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var
Router
=
Backbone
.
Router
=
function
(
options
)
{
options
||
(
options
=
{});
if
(
options
.
routes
)
this
.
routes
=
options
.
routes
;
this
.
_bindRoutes
();
this
.
initialize
.
apply
(
this
,
arguments
);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var
namedParam
=
/:
\w
+/g
;
var
splatParam
=
/
\*\w
+/g
;
var
escapeRegExp
=
/
[
-[
\]
{}()+?.,
\\
^$|#
\s]
/g
;
// Set up all inheritable **Backbone.Router** properties and methods.
_
.
extend
(
Router
.
prototype
,
Events
,
{
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function(query, num) {
// ...
// });
//
route
:
function
(
route
,
name
,
callback
)
{
Backbone
.
history
||
(
Backbone
.
history
=
new
History
);
if
(
!
_
.
isRegExp
(
route
))
route
=
this
.
_routeToRegExp
(
route
);
if
(
!
callback
)
callback
=
this
[
name
];
Backbone
.
history
.
route
(
route
,
_
.
bind
(
function
(
fragment
)
{
var
args
=
this
.
_extractParameters
(
route
,
fragment
);
callback
&&
callback
.
apply
(
this
,
args
);
this
.
trigger
.
apply
(
this
,
[
'
route:
'
+
name
].
concat
(
args
));
Backbone
.
history
.
trigger
(
'
route
'
,
this
,
name
,
args
);
},
this
));
return
this
;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate
:
function
(
fragment
,
options
)
{
Backbone
.
history
.
navigate
(
fragment
,
options
);
},
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes
:
function
()
{
if
(
!
this
.
routes
)
return
;
var
routes
=
[];
for
(
var
route
in
this
.
routes
)
{
routes
.
unshift
([
route
,
this
.
routes
[
route
]]);
}
for
(
var
i
=
0
,
l
=
routes
.
length
;
i
<
l
;
i
++
)
{
this
.
route
(
routes
[
i
][
0
],
routes
[
i
][
1
],
this
[
routes
[
i
][
1
]]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp
:
function
(
route
)
{
route
=
route
.
replace
(
escapeRegExp
,
'
\\
$&
'
)
.
replace
(
namedParam
,
'
([^
\
/]+)
'
)
.
replace
(
splatParam
,
'
(.*?)
'
);
return
new
RegExp
(
'
^
'
+
route
+
'
$
'
);
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters
:
function
(
route
,
fragment
)
{
return
route
.
exec
(
fragment
).
slice
(
1
);
}
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var
History
=
Backbone
.
History
=
function
()
{
this
.
handlers
=
[];
_
.
bindAll
(
this
,
'
checkUrl
'
);
};
// Cached regex for cleaning leading hashes and slashes .
var
routeStripper
=
/^
[
#
\/]
/
;
// Cached regex for detecting MSIE.
var
isExplorer
=
/msie
[\w
.
]
+/
;
// Has the history handling already been started?
History
.
started
=
false
;
// Set up all inheritable **Backbone.History** properties and methods.
_
.
extend
(
History
.
prototype
,
Events
,
{
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval
:
50
,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash
:
function
(
windowOverride
)
{
var
loc
=
windowOverride
?
windowOverride
.
location
:
window
.
location
;
var
match
=
loc
.
href
.
match
(
/#
(
.*
)
$/
);
return
match
?
match
[
1
]
:
''
;
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment
:
function
(
fragment
,
forcePushState
)
{
if
(
fragment
==
null
)
{
if
(
this
.
_hasPushState
||
forcePushState
)
{
fragment
=
window
.
location
.
pathname
;
var
search
=
window
.
location
.
search
;
if
(
search
)
fragment
+=
search
;
}
else
{
fragment
=
this
.
getHash
();
}
}
if
(
!
fragment
.
indexOf
(
this
.
options
.
root
))
fragment
=
fragment
.
substr
(
this
.
options
.
root
.
length
);
return
fragment
.
replace
(
routeStripper
,
''
);
},
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start
:
function
(
options
)
{
if
(
History
.
started
)
throw
new
Error
(
"
Backbone.history has already been started
"
);
History
.
started
=
true
;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
this
.
options
=
_
.
extend
({},
{
root
:
'
/
'
},
this
.
options
,
options
);
this
.
_wantsHashChange
=
this
.
options
.
hashChange
!==
false
;
this
.
_wantsPushState
=
!!
this
.
options
.
pushState
;
this
.
_hasPushState
=
!!
(
this
.
options
.
pushState
&&
window
.
history
&&
window
.
history
.
pushState
);
var
fragment
=
this
.
getFragment
();
var
docMode
=
document
.
documentMode
;
var
oldIE
=
(
isExplorer
.
exec
(
navigator
.
userAgent
.
toLowerCase
())
&&
(
!
docMode
||
docMode
<=
7
));
if
(
oldIE
)
{
this
.
iframe
=
$
(
'
<iframe src="javascript:0" tabindex="-1" />
'
).
hide
().
appendTo
(
'
body
'
)[
0
].
contentWindow
;
this
.
navigate
(
fragment
);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if
(
this
.
_hasPushState
)
{
$
(
window
).
bind
(
'
popstate
'
,
this
.
checkUrl
);
}
else
if
(
this
.
_wantsHashChange
&&
(
'
onhashchange
'
in
window
)
&&
!
oldIE
)
{
$
(
window
).
bind
(
'
hashchange
'
,
this
.
checkUrl
);
}
else
if
(
this
.
_wantsHashChange
)
{
this
.
_checkUrlInterval
=
setInterval
(
this
.
checkUrl
,
this
.
interval
);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this
.
fragment
=
fragment
;
var
loc
=
window
.
location
;
var
atRoot
=
loc
.
pathname
==
this
.
options
.
root
;
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if
(
this
.
_wantsHashChange
&&
this
.
_wantsPushState
&&
!
this
.
_hasPushState
&&
!
atRoot
)
{
this
.
fragment
=
this
.
getFragment
(
null
,
true
);
window
.
location
.
replace
(
this
.
options
.
root
+
'
#
'
+
this
.
fragment
);
// Return immediately as browser will do redirect to new url
return
true
;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
}
else
if
(
this
.
_wantsPushState
&&
this
.
_hasPushState
&&
atRoot
&&
loc
.
hash
)
{
this
.
fragment
=
this
.
getHash
().
replace
(
routeStripper
,
''
);
window
.
history
.
replaceState
({},
document
.
title
,
loc
.
protocol
+
'
//
'
+
loc
.
host
+
this
.
options
.
root
+
this
.
fragment
);
}
if
(
!
this
.
options
.
silent
)
{
return
this
.
loadUrl
();
}
},
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop
:
function
()
{
$
(
window
).
unbind
(
'
popstate
'
,
this
.
checkUrl
).
unbind
(
'
hashchange
'
,
this
.
checkUrl
);
clearInterval
(
this
.
_checkUrlInterval
);
History
.
started
=
false
;
},
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
route
:
function
(
route
,
callback
)
{
this
.
handlers
.
unshift
({
route
:
route
,
callback
:
callback
});
},
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl
:
function
(
e
)
{
var
current
=
this
.
getFragment
();
if
(
current
==
this
.
fragment
&&
this
.
iframe
)
current
=
this
.
getFragment
(
this
.
getHash
(
this
.
iframe
));
if
(
current
==
this
.
fragment
)
return
false
;
if
(
this
.
iframe
)
this
.
navigate
(
current
);
this
.
loadUrl
()
||
this
.
loadUrl
(
this
.
getHash
());
},
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl
:
function
(
fragmentOverride
)
{
var
fragment
=
this
.
fragment
=
this
.
getFragment
(
fragmentOverride
);
var
matched
=
_
.
any
(
this
.
handlers
,
function
(
handler
)
{
if
(
handler
.
route
.
test
(
fragment
))
{
handler
.
callback
(
fragment
);
return
true
;
}
});
return
matched
;
},
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the history.
navigate
:
function
(
fragment
,
options
)
{
if
(
!
History
.
started
)
return
false
;
if
(
!
options
||
options
===
true
)
options
=
{
trigger
:
options
};
var
frag
=
(
fragment
||
''
).
replace
(
routeStripper
,
''
);
if
(
this
.
fragment
==
frag
)
return
;
// If pushState is available, we use it to set the fragment as a real URL.
if
(
this
.
_hasPushState
)
{
if
(
frag
.
indexOf
(
this
.
options
.
root
)
!=
0
)
frag
=
this
.
options
.
root
+
frag
;
this
.
fragment
=
frag
;
window
.
history
[
options
.
replace
?
'
replaceState
'
:
'
pushState
'
]({},
document
.
title
,
frag
);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
}
else
if
(
this
.
_wantsHashChange
)
{
this
.
fragment
=
frag
;
this
.
_updateHash
(
window
.
location
,
frag
,
options
.
replace
);
if
(
this
.
iframe
&&
(
frag
!=
this
.
getFragment
(
this
.
getHash
(
this
.
iframe
))))
{
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
// When replace is true, we don't want this.
if
(
!
options
.
replace
)
this
.
iframe
.
document
.
open
().
close
();
this
.
_updateHash
(
this
.
iframe
.
location
,
frag
,
options
.
replace
);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
}
else
{
window
.
location
.
assign
(
this
.
options
.
root
+
fragment
);
}
if
(
options
.
trigger
)
this
.
loadUrl
(
fragment
);
},
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
_updateHash
:
function
(
location
,
fragment
,
replace
)
{
if
(
replace
)
{
location
.
replace
(
location
.
toString
().
replace
(
/
(
javascript:|#
)
.*$/
,
''
)
+
'
#
'
+
fragment
);
}
else
{
location
.
hash
=
fragment
;
}
}
});
// Backbone.View
// -------------
// Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
var
View
=
Backbone
.
View
=
function
(
options
)
{
this
.
cid
=
_
.
uniqueId
(
'
view
'
);
this
.
_configure
(
options
||
{});
this
.
_ensureElement
();
this
.
initialize
.
apply
(
this
,
arguments
);
this
.
delegateEvents
();
};
// Cached regex to split keys for `delegate`.
var
delegateEventSplitter
=
/^
(\S
+
)\s
*
(
.*
)
$/
;
// List of view options to be merged as properties.
var
viewOptions
=
[
'
model
'
,
'
collection
'
,
'
el
'
,
'
id
'
,
'
attributes
'
,
'
className
'
,
'
tagName
'
];
// Set up all inheritable **Backbone.View** properties and methods.
_
.
extend
(
View
.
prototype
,
Events
,
{
// The default `tagName` of a View's element is `"div"`.
tagName
:
'
div
'
,
// jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be prefered to global lookups where possible.
$
:
function
(
selector
)
{
return
this
.
$el
.
find
(
selector
);
},
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
// **render** is the core function that your view should override, in order
// to populate its element (`this.el`), with the appropriate HTML. The
// convention is for **render** to always return `this`.
render
:
function
()
{
return
this
;
},
// Remove this view from the DOM. Note that the view isn't present in the
// DOM by default, so calling this method may be a no-op.
remove
:
function
()
{
this
.
$el
.
remove
();
return
this
;
},
// For small amounts of DOM Elements, where a full-blown template isn't
// needed, use **make** to manufacture elements, one at a time.
//
// var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
//
make
:
function
(
tagName
,
attributes
,
content
)
{
var
el
=
document
.
createElement
(
tagName
);
if
(
attributes
)
$
(
el
).
attr
(
attributes
);
if
(
content
)
$
(
el
).
html
(
content
);
return
el
;
},
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement
:
function
(
element
,
delegate
)
{
if
(
this
.
$el
)
this
.
undelegateEvents
();
this
.
$el
=
(
element
instanceof
$
)
?
element
:
$
(
element
);
this
.
el
=
this
.
$el
[
0
];
if
(
delegate
!==
false
)
this
.
delegateEvents
();
return
this
;
},
// Set callbacks, where `this.events` is a hash of
//
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save'
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents
:
function
(
events
)
{
if
(
!
(
events
||
(
events
=
getValue
(
this
,
'
events
'
))))
return
;
this
.
undelegateEvents
();
for
(
var
key
in
events
)
{
var
method
=
events
[
key
];
if
(
!
_
.
isFunction
(
method
))
method
=
this
[
events
[
key
]];
if
(
!
method
)
throw
new
Error
(
'
Method "
'
+
events
[
key
]
+
'
" does not exist
'
);
var
match
=
key
.
match
(
delegateEventSplitter
);
var
eventName
=
match
[
1
],
selector
=
match
[
2
];
method
=
_
.
bind
(
method
,
this
);
eventName
+=
'
.delegateEvents
'
+
this
.
cid
;
if
(
selector
===
''
)
{
this
.
$el
.
bind
(
eventName
,
method
);
}
else
{
this
.
$el
.
delegate
(
selector
,
eventName
,
method
);
}
}
},
// Clears all callbacks previously bound to the view with `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
undelegateEvents
:
function
()
{
this
.
$el
.
unbind
(
'
.delegateEvents
'
+
this
.
cid
);
},
// Performs the initial configuration of a View with a set of options.
// Keys with special meaning *(model, collection, id, className)*, are
// attached directly to the view.
_configure
:
function
(
options
)
{
if
(
this
.
options
)
options
=
_
.
extend
({},
this
.
options
,
options
);
for
(
var
i
=
0
,
l
=
viewOptions
.
length
;
i
<
l
;
i
++
)
{
var
attr
=
viewOptions
[
i
];
if
(
options
[
attr
])
this
[
attr
]
=
options
[
attr
];
}
this
.
options
=
options
;
},
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
_ensureElement
:
function
()
{
if
(
!
this
.
el
)
{
var
attrs
=
getValue
(
this
,
'
attributes
'
)
||
{};
if
(
this
.
id
)
attrs
.
id
=
this
.
id
;
if
(
this
.
className
)
attrs
[
'
class
'
]
=
this
.
className
;
this
.
setElement
(
this
.
make
(
this
.
tagName
,
attrs
),
false
);
}
else
{
this
.
setElement
(
this
.
el
,
false
);
}
}
});
// The self-propagating extend function that Backbone classes use.
var
extend
=
function
(
protoProps
,
classProps
)
{
var
child
=
inherits
(
this
,
protoProps
,
classProps
);
child
.
extend
=
this
.
extend
;
return
child
;
};
// Set up inheritance for the model, collection, and view.
Model
.
extend
=
Collection
.
extend
=
Router
.
extend
=
View
.
extend
=
extend
;
// Backbone.sync
// -------------
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var
methodMap
=
{
'
create
'
:
'
POST
'
,
'
update
'
:
'
PUT
'
,
'
delete
'
:
'
DELETE
'
,
'
read
'
:
'
GET
'
};
// Override this function to change the manner in which Backbone persists
// models to the server. You will be passed the type of request, and the
// model in question. By default, makes a RESTful Ajax request
// to the model's `url()`. Some possible customizations could be:
//
// * Use `setTimeout` to batch rapid-fire updates into a single request.
// * Send up the models as XML instead of JSON.
// * Persist models via WebSockets instead of Ajax.
//
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with a `_method` parameter containing the true HTTP method,
// as well as all requests with the body as `application/x-www-form-urlencoded`
// instead of `application/json` with the model in a param named `model`.
// Useful when interfacing with server-side languages like **PHP** that make
// it difficult to read the body of `PUT` requests.
Backbone
.
sync
=
function
(
method
,
model
,
options
)
{
var
type
=
methodMap
[
method
];
// Default options, unless specified.
options
||
(
options
=
{});
// Default JSON-request options.
var
params
=
{
type
:
type
,
dataType
:
'
json
'
};
// Ensure that we have a URL.
if
(
!
options
.
url
)
{
params
.
url
=
getValue
(
model
,
'
url
'
)
||
urlError
();
}
// Ensure that we have the appropriate request data.
if
(
!
options
.
data
&&
model
&&
(
method
==
'
create
'
||
method
==
'
update
'
))
{
params
.
contentType
=
'
application/json
'
;
params
.
data
=
JSON
.
stringify
(
model
.
toJSON
());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if
(
Backbone
.
emulateJSON
)
{
params
.
contentType
=
'
application/x-www-form-urlencoded
'
;
params
.
data
=
params
.
data
?
{
model
:
params
.
data
}
:
{};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if
(
Backbone
.
emulateHTTP
)
{
if
(
type
===
'
PUT
'
||
type
===
'
DELETE
'
)
{
if
(
Backbone
.
emulateJSON
)
params
.
data
.
_method
=
type
;
params
.
type
=
'
POST
'
;
params
.
beforeSend
=
function
(
xhr
)
{
xhr
.
setRequestHeader
(
'
X-HTTP-Method-Override
'
,
type
);
};
}
}
// Don't process data on a non-GET request.
if
(
params
.
type
!==
'
GET
'
&&
!
Backbone
.
emulateJSON
)
{
params
.
processData
=
false
;
}
// Make the request, allowing the user to override any Ajax options.
return
$
.
ajax
(
_
.
extend
(
params
,
options
));
};
// Wrap an optional error callback with a fallback error event.
Backbone
.
wrapError
=
function
(
onError
,
originalModel
,
options
)
{
return
function
(
model
,
resp
)
{
resp
=
model
===
originalModel
?
resp
:
model
;
if
(
onError
)
{
onError
(
originalModel
,
resp
,
options
);
}
else
{
originalModel
.
trigger
(
'
error
'
,
originalModel
,
resp
,
options
);
}
};
};
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var
ctor
=
function
(){};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var
inherits
=
function
(
parent
,
protoProps
,
staticProps
)
{
var
child
;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if
(
protoProps
&&
protoProps
.
hasOwnProperty
(
'
constructor
'
))
{
child
=
protoProps
.
constructor
;
}
else
{
child
=
function
(){
parent
.
apply
(
this
,
arguments
);
};
}
// Inherit class (static) properties from parent.
_
.
extend
(
child
,
parent
);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor
.
prototype
=
parent
.
prototype
;
child
.
prototype
=
new
ctor
();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if
(
protoProps
)
_
.
extend
(
child
.
prototype
,
protoProps
);
// Add static properties to the constructor function, if supplied.
if
(
staticProps
)
_
.
extend
(
child
,
staticProps
);
// Correctly set child's `prototype.constructor`.
child
.
prototype
.
constructor
=
child
;
// Set a convenience property in case the parent's prototype is needed later.
child
.
__super__
=
parent
.
prototype
;
return
child
;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
var
getValue
=
function
(
object
,
prop
)
{
if
(
!
(
object
&&
object
[
prop
]))
return
null
;
return
_
.
isFunction
(
object
[
prop
])
?
object
[
prop
]()
:
object
[
prop
];
};
// Throw an error when a URL is needed, and none is supplied.
var
urlError
=
function
()
{
throw
new
Error
(
'
A "url" property or function must be specified
'
);
};
}).
call
(
this
);
labs/architecture-examples/backbone_marionette_modules/js/lib/backbone.marionette.js
0 → 100644
View file @
b7cc462a
// Backbone.Marionette, v0.9.11
// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
// http://github.com/derickbailey/backbone.marionette
Backbone
.
Marionette
=
(
function
(
Backbone
,
_
,
$
){
var
Marionette
=
{};
// EventBinder
// -----------
// The event binder facilitates the binding and unbinding of events
// from objects that extend `Backbone.Events`. It makes
// unbinding events, even with anonymous callback functions,
// easy.
//
// Inspired by [Johnny Oshika](http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853)
Marionette
.
EventBinder
=
function
(){
this
.
_eventBindings
=
[];
};
_
.
extend
(
Marionette
.
EventBinder
.
prototype
,
{
// Store the event binding in array so it can be unbound
// easily, at a later point in time.
bindTo
:
function
(
obj
,
eventName
,
callback
,
context
)
{
context
=
context
||
this
;
obj
.
on
(
eventName
,
callback
,
context
);
var
binding
=
{
obj
:
obj
,
eventName
:
eventName
,
callback
:
callback
,
context
:
context
};
this
.
_eventBindings
.
push
(
binding
);
return
binding
;
},
// Unbind from a single binding object. Binding objects are
// returned from the `bindTo` method call.
unbindFrom
:
function
(
binding
){
binding
.
obj
.
off
(
binding
.
eventName
,
binding
.
callback
,
binding
.
context
);
this
.
_eventBindings
=
_
.
reject
(
this
.
_eventBindings
,
function
(
bind
){
return
bind
===
binding
;});
},
// Unbind all of the events that we have stored.
unbindAll
:
function
()
{
var
that
=
this
;
// The `unbindFrom` call removes elements from the array
// while it is being iterated, so clone it first.
var
bindings
=
_
.
map
(
this
.
_eventBindings
,
_
.
identity
);
_
.
each
(
bindings
,
function
(
binding
,
index
)
{
that
.
unbindFrom
(
binding
);
});
}
});
// Copy the `extend` function used by Backbone's classes
Marionette
.
EventBinder
.
extend
=
Backbone
.
View
.
extend
;
// Marionette.View
// ---------------
// The core view type that other Marionette views extend from.
Marionette
.
View
=
Backbone
.
View
.
extend
({
constructor
:
function
(){
var
eventBinder
=
new
Marionette
.
EventBinder
();
_
.
extend
(
this
,
eventBinder
);
Backbone
.
View
.
prototype
.
constructor
.
apply
(
this
,
arguments
);
this
.
bindTo
(
this
,
"
show
"
,
this
.
onShowCalled
,
this
);
},
// Get the template for this view
// instance. You can set a `template` attribute in the view
// definition or pass a `template: "whatever"` parameter in
// to the constructor options.
getTemplate
:
function
(){
var
template
;
// Get the template from `this.options.template` or
// `this.template`. The `options` takes precedence.
if
(
this
.
options
&&
this
.
options
.
template
){
template
=
this
.
options
.
template
;
}
else
{
template
=
this
.
template
;
}
return
template
;
},
// Serialize the model or collection for the view. If a model is
// found, `.toJSON()` is called. If a collection is found, `.toJSON()`
// is also called, but is used to populate an `items` array in the
// resulting data. If both are found, defaults to the model.
// You can override the `serializeData` method in your own view
// definition, to provide custom serialization for your view's data.
serializeData
:
function
(){
var
data
;
if
(
this
.
model
)
{
data
=
this
.
model
.
toJSON
();
}
else
if
(
this
.
collection
)
{
data
=
{
items
:
this
.
collection
.
toJSON
()
};
}
data
=
this
.
mixinTemplateHelpers
(
data
);
return
data
;
},
// Mix in template helper methods. Looks for a
// `templateHelpers` attribute, which can either be an
// object literal, or a function that returns an object
// literal. All methods and attributes from this object
// are copies to the object passed in.
mixinTemplateHelpers
:
function
(
target
){
target
=
target
||
{};
var
templateHelpers
=
this
.
templateHelpers
;
if
(
_
.
isFunction
(
templateHelpers
)){
templateHelpers
=
templateHelpers
.
call
(
this
);
}
return
_
.
extend
(
target
,
templateHelpers
);
},
// Configure `triggers` to forward DOM events to view
// events. `triggers: {"click .foo": "do:foo"}`
configureTriggers
:
function
(){
if
(
!
this
.
triggers
)
{
return
;
}
var
triggers
=
this
.
triggers
;
var
that
=
this
;
var
triggerEvents
=
{};
// Allow `triggers` to be configured as a function
if
(
_
.
isFunction
(
triggers
)){
triggers
=
triggers
.
call
(
this
);
}
// Configure the triggers, prevent default
// action and stop propagation of DOM events
_
.
each
(
triggers
,
function
(
value
,
key
){
triggerEvents
[
key
]
=
function
(
e
){
if
(
e
&&
e
.
preventDefault
){
e
.
preventDefault
();
}
if
(
e
&&
e
.
stopPropagation
){
e
.
stopPropagation
();
}
that
.
trigger
(
value
);
};
});
return
triggerEvents
;
},
// Overriding Backbone.View's delegateEvents specifically
// to handle the `triggers` configuration
delegateEvents
:
function
(
events
){
events
=
events
||
this
.
events
;
if
(
_
.
isFunction
(
events
)){
events
=
events
.
call
(
this
);
}
var
combinedEvents
=
{};
var
triggers
=
this
.
configureTriggers
();
_
.
extend
(
combinedEvents
,
events
,
triggers
);
Backbone
.
View
.
prototype
.
delegateEvents
.
call
(
this
,
combinedEvents
);
},
// Internal method, handles the `show` event.
onShowCalled
:
function
(){},
// Default `close` implementation, for removing a view from the
// DOM and unbinding it. Regions will call this method
// for you. You can specify an `onClose` method in your view to
// add custom code that is called after the view is closed.
close
:
function
(){
if
(
this
.
beforeClose
)
{
this
.
beforeClose
();
}
this
.
remove
();
if
(
this
.
onClose
)
{
this
.
onClose
();
}
this
.
trigger
(
'
close
'
);
this
.
unbindAll
();
this
.
unbind
();
},
// This method binds the elements specified in the "ui" hash inside the view's code with
// the associated jQuery selectors.
bindUIElements
:
function
(){
if
(
!
this
.
ui
)
{
return
;
}
var
that
=
this
;
if
(
!
this
.
uiBindings
)
{
// We want to store the ui hash in uiBindings, since afterwards the values in the ui hash
// will be overridden with jQuery selectors.
this
.
uiBindings
=
this
.
ui
;
}
// refreshing the associated selectors since they should point to the newly rendered elements.
this
.
ui
=
{};
_
.
each
(
_
.
keys
(
this
.
uiBindings
),
function
(
key
)
{
var
selector
=
that
.
uiBindings
[
key
];
that
.
ui
[
key
]
=
that
.
$
(
selector
);
});
}
});
// Item View
// ---------
// A single item view implementation that contains code for rendering
// with underscore.js templates, serializing the view's model or collection,
// and calling several methods on extended views, such as `onRender`.
Marionette
.
ItemView
=
Marionette
.
View
.
extend
({
constructor
:
function
(){
Marionette
.
View
.
prototype
.
constructor
.
apply
(
this
,
arguments
);
if
(
this
.
initialEvents
){
this
.
initialEvents
();
}
},
// Render the view, defaulting to underscore.js templates.
// You can override this in your view definition to provide
// a very specific rendering for your view. In general, though,
// you should override the `Marionette.Renderer` object to
// change how Marionette renders views.
render
:
function
(){
if
(
this
.
beforeRender
){
this
.
beforeRender
();
}
this
.
trigger
(
"
before:render
"
,
this
);
this
.
trigger
(
"
item:before:render
"
,
this
);
var
data
=
this
.
serializeData
();
var
template
=
this
.
getTemplate
();
var
html
=
Marionette
.
Renderer
.
render
(
template
,
data
);
this
.
$el
.
html
(
html
);
this
.
bindUIElements
();
if
(
this
.
onRender
){
this
.
onRender
();
}
this
.
trigger
(
"
render
"
,
this
);
this
.
trigger
(
"
item:rendered
"
,
this
);
return
this
;
},
// Override the default close event to add a few
// more events that are triggered.
close
:
function
(){
this
.
trigger
(
'
item:before:close
'
);
Marionette
.
View
.
prototype
.
close
.
apply
(
this
,
arguments
);
this
.
trigger
(
'
item:closed
'
);
}
});
// Collection View
// ---------------
// A view that iterates over a Backbone.Collection
// and renders an individual ItemView for each model.
Marionette
.
CollectionView
=
Marionette
.
View
.
extend
({
constructor
:
function
(){
Marionette
.
View
.
prototype
.
constructor
.
apply
(
this
,
arguments
);
this
.
initChildViewStorage
();
this
.
initialEvents
();
this
.
onShowCallbacks
=
new
Marionette
.
Callbacks
();
},
// Configured the initial events that the collection view
// binds to. Override this method to prevent the initial
// events, or to add your own initial events.
initialEvents
:
function
(){
if
(
this
.
collection
){
this
.
bindTo
(
this
.
collection
,
"
add
"
,
this
.
addChildView
,
this
);
this
.
bindTo
(
this
.
collection
,
"
remove
"
,
this
.
removeItemView
,
this
);
this
.
bindTo
(
this
.
collection
,
"
reset
"
,
this
.
render
,
this
);
}
},
// Handle a child item added to the collection
addChildView
:
function
(
item
,
collection
,
options
){
this
.
closeEmptyView
();
var
ItemView
=
this
.
getItemView
();
return
this
.
addItemView
(
item
,
ItemView
,
options
.
index
);
},
// Override from `Marionette.View` to guarantee the `onShow` method
// of child views is called.
onShowCalled
:
function
(){
this
.
onShowCallbacks
.
run
();
},
// Internal method to trigger the before render callbacks
// and events
triggerBeforeRender
:
function
(){
if
(
this
.
beforeRender
)
{
this
.
beforeRender
();
}
this
.
trigger
(
"
before:render
"
,
this
);
this
.
trigger
(
"
collection:before:render
"
,
this
);
},
// Internal method to trigger the rendered callbacks and
// events
triggerRendered
:
function
(){
if
(
this
.
onRender
)
{
this
.
onRender
();
}
this
.
trigger
(
"
render
"
,
this
);
this
.
trigger
(
"
collection:rendered
"
,
this
);
},
// Render the collection of items. Override this method to
// provide your own implementation of a render function for
// the collection view.
render
:
function
(){
this
.
triggerBeforeRender
();
this
.
closeEmptyView
();
this
.
closeChildren
();
if
(
this
.
collection
&&
this
.
collection
.
length
>
0
)
{
this
.
showCollection
();
}
else
{
this
.
showEmptyView
();
}
this
.
triggerRendered
();
return
this
;
},
// Internal method to loop through each item in the
// collection view and show it
showCollection
:
function
(){
var
that
=
this
;
var
ItemView
=
this
.
getItemView
();
this
.
collection
.
each
(
function
(
item
,
index
){
that
.
addItemView
(
item
,
ItemView
,
index
);
});
},
// Internal method to show an empty view in place of
// a collection of item views, when the collection is
// empty
showEmptyView
:
function
(){
var
EmptyView
=
this
.
options
.
emptyView
||
this
.
emptyView
;
if
(
EmptyView
&&
!
this
.
_showingEmptyView
){
this
.
_showingEmptyView
=
true
;
var
model
=
new
Backbone
.
Model
();
this
.
addItemView
(
model
,
EmptyView
,
0
);
}
},
// Internal method to close an existing emptyView instance
// if one exists. Called when a collection view has been
// rendered empty, and then an item is added to the collection.
closeEmptyView
:
function
(){
if
(
this
.
_showingEmptyView
){
this
.
closeChildren
();
delete
this
.
_showingEmptyView
;
}
},
// Retrieve the itemView type, either from `this.options.itemView`
// or from the `itemView` in the object definition. The "options"
// takes precedence.
getItemView
:
function
(){
var
itemView
=
this
.
options
.
itemView
||
this
.
itemView
;
if
(
!
itemView
){
var
err
=
new
Error
(
"
An `itemView` must be specified
"
);
err
.
name
=
"
NoItemViewError
"
;
throw
err
;
}
return
itemView
;
},
// Render the child item's view and add it to the
// HTML for the collection view.
addItemView
:
function
(
item
,
ItemView
,
index
){
var
that
=
this
;
var
view
=
this
.
buildItemView
(
item
,
ItemView
);
// Store the child view itself so we can properly
// remove and/or close it later
this
.
storeChild
(
view
);
if
(
this
.
onItemAdded
){
this
.
onItemAdded
(
view
);
}
this
.
trigger
(
"
item:added
"
,
view
);
// Render it and show it
var
renderResult
=
this
.
renderItemView
(
view
,
index
);
// call onShow for child item views
if
(
view
.
onShow
){
this
.
onShowCallbacks
.
add
(
view
.
onShow
,
view
);
}
// Forward all child item view events through the parent,
// prepending "itemview:" to the event name
var
childBinding
=
this
.
bindTo
(
view
,
"
all
"
,
function
(){
var
args
=
slice
.
call
(
arguments
);
args
[
0
]
=
"
itemview:
"
+
args
[
0
];
args
.
splice
(
1
,
0
,
view
);
that
.
trigger
.
apply
(
that
,
args
);
});
// Store all child event bindings so we can unbind
// them when removing / closing the child view
this
.
childBindings
=
this
.
childBindings
||
{};
this
.
childBindings
[
view
.
cid
]
=
childBinding
;
return
renderResult
;
},
// render the item view
renderItemView
:
function
(
view
,
index
)
{
view
.
render
();
this
.
appendHtml
(
this
,
view
,
index
);
},
// Build an `itemView` for every model in the collection.
buildItemView
:
function
(
item
,
ItemView
){
var
itemViewOptions
=
_
.
result
(
this
,
"
itemViewOptions
"
);
var
options
=
_
.
extend
({
model
:
item
},
itemViewOptions
);
var
view
=
new
ItemView
(
options
);
return
view
;
},
// Remove the child view and close it
removeItemView
:
function
(
item
){
var
view
=
this
.
children
[
item
.
cid
];
if
(
view
){
var
childBinding
=
this
.
childBindings
[
view
.
cid
];
if
(
childBinding
)
{
this
.
unbindFrom
(
childBinding
);
delete
this
.
childBindings
[
view
.
cid
];
}
view
.
close
();
delete
this
.
children
[
item
.
cid
];
}
if
(
!
this
.
collection
||
this
.
collection
.
length
===
0
){
this
.
showEmptyView
();
}
this
.
trigger
(
"
item:removed
"
,
view
);
},
// Append the HTML to the collection's `el`.
// Override this method to do something other
// then `.append`.
appendHtml
:
function
(
collectionView
,
itemView
,
index
){
collectionView
.
$el
.
append
(
itemView
.
el
);
},
// Store references to all of the child `itemView`
// instances so they can be managed and cleaned up, later.
storeChild
:
function
(
view
){
this
.
children
[
view
.
model
.
cid
]
=
view
;
},
// Internal method to set up the `children` object for
// storing all of the child views
initChildViewStorage
:
function
(){
this
.
children
=
{};
},
// Handle cleanup and other closing needs for
// the collection of views.
close
:
function
(){
this
.
trigger
(
"
collection:before:close
"
);
this
.
closeChildren
();
Marionette
.
View
.
prototype
.
close
.
apply
(
this
,
arguments
);
this
.
trigger
(
"
collection:closed
"
);
},
// Close the child views that this collection view
// is holding on to, if any
closeChildren
:
function
(){
var
that
=
this
;
if
(
this
.
children
){
_
.
each
(
_
.
clone
(
this
.
children
),
function
(
childView
){
that
.
removeItemView
(
childView
.
model
);
});
}
}
});
// Composite View
// --------------
// Used for rendering a branch-leaf, hierarchical structure.
// Extends directly from CollectionView and also renders an
// an item view as `modelView`, for the top leaf
Marionette
.
CompositeView
=
Marionette
.
CollectionView
.
extend
({
constructor
:
function
(
options
){
Marionette
.
CollectionView
.
apply
(
this
,
arguments
);
this
.
itemView
=
this
.
getItemView
();
},
// Configured the initial events that the composite view
// binds to. Override this method to prevent the initial
// events, or to add your own initial events.
initialEvents
:
function
(){
if
(
this
.
collection
){
this
.
bindTo
(
this
.
collection
,
"
add
"
,
this
.
addChildView
,
this
);
this
.
bindTo
(
this
.
collection
,
"
remove
"
,
this
.
removeItemView
,
this
);
this
.
bindTo
(
this
.
collection
,
"
reset
"
,
this
.
renderCollection
,
this
);
}
},
// Retrieve the `itemView` to be used when rendering each of
// the items in the collection. The default is to return
// `this.itemView` or Marionette.CompositeView if no `itemView`
// has been defined
getItemView
:
function
(){
return
this
.
itemView
||
this
.
constructor
;
},
// Renders the model once, and the collection once. Calling
// this again will tell the model's view to re-render itself
// but the collection will not re-render.
render
:
function
(){
var
that
=
this
;
this
.
resetItemViewContainer
();
var
html
=
this
.
renderModel
();
this
.
$el
.
html
(
html
);
// the ui bindings is done here and not at the end of render since they should be
// available before the collection is rendered.
this
.
bindUIElements
();
this
.
trigger
(
"
composite:model:rendered
"
);
this
.
trigger
(
"
render
"
);
this
.
renderCollection
();
this
.
trigger
(
"
composite:rendered
"
);
return
this
;
},
// Render the collection for the composite view
renderCollection
:
function
(){
Marionette
.
CollectionView
.
prototype
.
render
.
apply
(
this
,
arguments
);
this
.
trigger
(
"
composite:collection:rendered
"
);
},
// Render an individual model, if we have one, as
// part of a composite view (branch / leaf). For example:
// a treeview.
renderModel
:
function
(){
var
data
=
{};
data
=
this
.
serializeData
();
var
template
=
this
.
getTemplate
();
return
Marionette
.
Renderer
.
render
(
template
,
data
);
},
// Appends the `el` of itemView instances to the specified
// `itemViewContainer` (a jQuery selector). Override this method to
// provide custom logic of how the child item view instances have their
// HTML appended to the composite view instance.
appendHtml
:
function
(
cv
,
iv
){
var
$container
=
this
.
getItemViewContainer
(
cv
);
$container
.
append
(
iv
.
el
);
},
// Internal method to ensure an `$itemViewContainer` exists, for the
// `appendHtml` method to use.
getItemViewContainer
:
function
(
containerView
){
var
container
;
if
(
"
$itemViewContainer
"
in
containerView
){
container
=
containerView
.
$itemViewContainer
;
}
else
{
if
(
containerView
.
itemViewContainer
){
container
=
containerView
.
$
(
_
.
result
(
containerView
,
"
itemViewContainer
"
));
if
(
container
.
length
<=
0
)
{
var
err
=
new
Error
(
"
Missing `itemViewContainer`
"
);
err
.
name
=
"
ItemViewContainerMissingError
"
;
throw
err
;
}
}
else
{
container
=
containerView
.
$el
;
}
containerView
.
$itemViewContainer
=
container
;
}
return
container
;
},
// Internal method to reset the `$itemViewContainer` on render
resetItemViewContainer
:
function
(){
if
(
this
.
$itemViewContainer
){
delete
this
.
$itemViewContainer
;
}
}
});
// Region
// ------
// Manage the visual regions of your composite application. See
// http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
Marionette
.
Region
=
function
(
options
){
this
.
options
=
options
||
{};
var
eventBinder
=
new
Marionette
.
EventBinder
();
_
.
extend
(
this
,
eventBinder
,
options
);
if
(
!
this
.
el
){
var
err
=
new
Error
(
"
An 'el' must be specified
"
);
err
.
name
=
"
NoElError
"
;
throw
err
;
}
if
(
this
.
initialize
){
this
.
initialize
.
apply
(
this
,
arguments
);
}
};
_
.
extend
(
Marionette
.
Region
.
prototype
,
Backbone
.
Events
,
{
// Displays a backbone view instance inside of the region.
// Handles calling the `render` method for you. Reads content
// directly from the `el` attribute. Also calls an optional
// `onShow` and `close` method on your view, just after showing
// or just before closing the view, respectively.
show
:
function
(
view
){
this
.
ensureEl
();
this
.
close
();
view
.
render
();
this
.
open
(
view
);
if
(
view
.
onShow
)
{
view
.
onShow
();
}
view
.
trigger
(
"
show
"
);
if
(
this
.
onShow
)
{
this
.
onShow
(
view
);
}
this
.
trigger
(
"
view:show
"
,
view
);
this
.
currentView
=
view
;
},
ensureEl
:
function
(){
if
(
!
this
.
$el
||
this
.
$el
.
length
===
0
){
this
.
$el
=
this
.
getEl
(
this
.
el
);
}
},
// Override this method to change how the region finds the
// DOM element that it manages. Return a jQuery selector object.
getEl
:
function
(
selector
){
return
$
(
selector
);
},
// Override this method to change how the new view is
// appended to the `$el` that the region is managing
open
:
function
(
view
){
this
.
$el
.
html
(
view
.
el
);
},
// Close the current view, if there is one. If there is no
// current view, it does nothing and returns immediately.
close
:
function
(){
var
view
=
this
.
currentView
;
if
(
!
view
){
return
;
}
if
(
view
.
close
)
{
view
.
close
();
}
this
.
trigger
(
"
view:closed
"
,
view
);
delete
this
.
currentView
;
},
// Attach an existing view to the region. This
// will not call `render` or `onShow` for the new view,
// and will not replace the current HTML for the `el`
// of the region.
attachView
:
function
(
view
){
this
.
currentView
=
view
;
},
// Reset the region by closing any existing view and
// clearing out the cached `$el`. The next time a view
// is shown via this region, the region will re-query the
// DOM for the region's `el`.
reset
:
function
(){
this
.
close
();
delete
this
.
$el
;
}
});
// Copy the `extend` function used by Backbone's classes
Marionette
.
Region
.
extend
=
Backbone
.
View
.
extend
;
// Layout
// ------
// Used for managing application layouts, nested layouts and
// multiple regions within an application or sub-application.
//
// A specialized view type that renders an area of HTML and then
// attaches `Region` instances to the specified `regions`.
// Used for composite view management and sub-application areas.
Marionette
.
Layout
=
Marionette
.
ItemView
.
extend
({
regionType
:
Marionette
.
Region
,
constructor
:
function
()
{
Backbone
.
Marionette
.
ItemView
.
apply
(
this
,
arguments
);
this
.
initializeRegions
();
},
// Layout's render will use the existing region objects the
// first time it is called. Subsequent calls will close the
// views that the regions are showing and then reset the `el`
// for the regions to the newly rendered DOM elements.
render
:
function
(){
var
result
=
Marionette
.
ItemView
.
prototype
.
render
.
apply
(
this
,
arguments
);
// Rewrite this function to handle re-rendering and
// re-initializing the `el` for each region
this
.
render
=
function
(){
this
.
closeRegions
();
this
.
reInitializeRegions
();
var
result
=
Marionette
.
ItemView
.
prototype
.
render
.
apply
(
this
,
arguments
);
return
result
;
};
return
result
;
},
// Handle closing regions, and then close the view itself.
close
:
function
()
{
this
.
closeRegions
();
this
.
destroyRegions
();
Backbone
.
Marionette
.
ItemView
.
prototype
.
close
.
call
(
this
,
arguments
);
},
// Initialize the regions that have been defined in a
// `regions` attribute on this layout. The key of the
// hash becomes an attribute on the layout object directly.
// For example: `regions: { menu: ".menu-container" }`
// will product a `layout.menu` object which is a region
// that controls the `.menu-container` DOM element.
initializeRegions
:
function
()
{
if
(
!
this
.
regionManagers
){
this
.
regionManagers
=
{};
}
var
that
=
this
;
_
.
each
(
this
.
regions
,
function
(
region
,
name
)
{
if
(
typeof
region
!=
'
string
'
&&
typeof
region
.
selector
!=
'
string
'
)
{
throw
new
Exception
(
'
Region must be specified as a selector
'
+
'
string or an object with selector property
'
);
}
var
selector
=
typeof
region
===
'
string
'
?
region
:
region
.
selector
;
var
regionType
=
typeof
region
.
regionType
===
'
undefined
'
?
that
.
regionType
:
region
.
regionType
;
var
regionManager
=
new
regionType
({
el
:
selector
,
getEl
:
function
(
selector
){
return
that
.
$
(
selector
);
}
});
that
.
regionManagers
[
name
]
=
regionManager
;
that
[
name
]
=
regionManager
;
});
},
// Re-initialize all of the regions by updating the `el` that
// they point to
reInitializeRegions
:
function
(){
if
(
this
.
regionManagers
&&
_
.
size
(
this
.
regionManagers
)
===
0
){
this
.
initializeRegions
();
}
else
{
_
.
each
(
this
.
regionManagers
,
function
(
region
){
region
.
reset
();
});
}
},
// Close all of the regions that have been opened by
// this layout. This method is called when the layout
// itself is closed.
closeRegions
:
function
()
{
var
that
=
this
;
_
.
each
(
this
.
regionManagers
,
function
(
manager
,
name
)
{
manager
.
close
();
});
},
// Destroys all of the regions by removing references
// from the Layout
destroyRegions
:
function
(){
var
that
=
this
;
_
.
each
(
this
.
regionManagers
,
function
(
manager
,
name
)
{
delete
that
[
name
];
});
this
.
regionManagers
=
{};
}
});
// Application
// -----------
// Contain and manage the composite application as a whole.
// Stores and starts up `Region` objects, includes an
// event aggregator as `app.vent`
Marionette
.
Application
=
function
(
options
){
this
.
initCallbacks
=
new
Marionette
.
Callbacks
();
this
.
vent
=
new
Marionette
.
EventAggregator
();
this
.
submodules
=
{};
var
eventBinder
=
new
Marionette
.
EventBinder
();
_
.
extend
(
this
,
eventBinder
,
options
);
};
_
.
extend
(
Marionette
.
Application
.
prototype
,
Backbone
.
Events
,
{
// Add an initializer that is either run at when the `start`
// method is called, or run immediately if added after `start`
// has already been called.
addInitializer
:
function
(
initializer
){
this
.
initCallbacks
.
add
(
initializer
);
},
// kick off all of the application's processes.
// initializes all of the regions that have been added
// to the app, and runs all of the initializer functions
start
:
function
(
options
){
this
.
trigger
(
"
initialize:before
"
,
options
);
this
.
initCallbacks
.
run
(
options
,
this
);
this
.
trigger
(
"
initialize:after
"
,
options
);
this
.
trigger
(
"
start
"
,
options
);
},
// Add regions to your app.
// Accepts a hash of named strings or Region objects
// addRegions({something: "#someRegion"})
// addRegions{{something: Region.extend({el: "#someRegion"}) });
addRegions
:
function
(
regions
){
var
regionValue
,
regionObj
,
region
;
for
(
region
in
regions
){
if
(
regions
.
hasOwnProperty
(
region
)){
regionValue
=
regions
[
region
];
if
(
typeof
regionValue
===
"
string
"
){
regionObj
=
new
Marionette
.
Region
({
el
:
regionValue
});
}
else
{
regionObj
=
new
regionValue
();
}
this
[
region
]
=
regionObj
;
}
}
},
// Removes a region from your app.
// Accepts the regions name
// removeRegion('myRegion')
removeRegion
:
function
(
region
)
{
this
[
region
].
close
();
delete
this
[
region
];
},
// Create a module, attached to the application
module
:
function
(
moduleNames
,
moduleDefinition
){
// slice the args, and add this application object as the
// first argument of the array
var
args
=
slice
.
call
(
arguments
);
args
.
unshift
(
this
);
// see the Marionette.Module object for more information
return
Marionette
.
Module
.
create
.
apply
(
Marionette
.
Module
,
args
);
}
});
// Copy the `extend` function used by Backbone's classes
Marionette
.
Application
.
extend
=
Backbone
.
View
.
extend
;
// AppRouter
// ---------
// Reduce the boilerplate code of handling route events
// and then calling a single method on another object.
// Have your routers configured to call the method on
// your object, directly.
//
// Configure an AppRouter with `appRoutes`.
//
// App routers can only take one `controller` object.
// It is recommended that you divide your controller
// objects in to smaller peices of related functionality
// and have multiple routers / controllers, instead of
// just one giant router and controller.
//
// You can also add standard routes to an AppRouter.
Marionette
.
AppRouter
=
Backbone
.
Router
.
extend
({
constructor
:
function
(
options
){
Backbone
.
Router
.
prototype
.
constructor
.
call
(
this
,
options
);
if
(
this
.
appRoutes
){
var
controller
=
this
.
controller
;
if
(
options
&&
options
.
controller
)
{
controller
=
options
.
controller
;
}
this
.
processAppRoutes
(
controller
,
this
.
appRoutes
);
}
},
// Internal method to process the `appRoutes` for the
// router, and turn them in to routes that trigger the
// specified method on the specified `controller`.
processAppRoutes
:
function
(
controller
,
appRoutes
){
var
method
,
methodName
;
var
route
,
routesLength
,
i
;
var
routes
=
[];
var
router
=
this
;
for
(
route
in
appRoutes
){
if
(
appRoutes
.
hasOwnProperty
(
route
)){
routes
.
unshift
([
route
,
appRoutes
[
route
]]);
}
}
routesLength
=
routes
.
length
;
for
(
i
=
0
;
i
<
routesLength
;
i
++
){
route
=
routes
[
i
][
0
];
methodName
=
routes
[
i
][
1
];
method
=
controller
[
methodName
];
if
(
!
method
){
var
msg
=
"
Method '
"
+
methodName
+
"
' was not found on the controller
"
;
var
err
=
new
Error
(
msg
);
err
.
name
=
"
NoMethodError
"
;
throw
err
;
}
method
=
_
.
bind
(
method
,
controller
);
router
.
route
(
route
,
methodName
,
method
);
}
}
});
// Module
// ------
// A simple module system, used to create privacy and encapsulation in
// Marionette applications
Marionette
.
Module
=
function
(
moduleName
,
app
,
customArgs
){
this
.
moduleName
=
moduleName
;
// store sub-modules
this
.
submodules
=
{};
this
.
_setupInitializersAndFinalizers
();
// store the configuration for this module
this
.
_config
=
{};
this
.
_config
.
app
=
app
;
this
.
_config
.
customArgs
=
customArgs
;
this
.
_config
.
definitions
=
[];
// extend this module with an event binder
var
eventBinder
=
new
Marionette
.
EventBinder
();
_
.
extend
(
this
,
eventBinder
);
};
// Extend the Module prototype with events / bindTo, so that the module
// can be used as an event aggregator or pub/sub.
_
.
extend
(
Marionette
.
Module
.
prototype
,
Backbone
.
Events
,
{
// Initializer for a specific module. Initializers are run when the
// module's `start` method is called.
addInitializer
:
function
(
callback
){
this
.
_initializerCallbacks
.
add
(
callback
);
},
// Finalizers are run when a module is stopped. They are used to teardown
// and finalize any variables, references, events and other code that the
// module had set up.
addFinalizer
:
function
(
callback
){
this
.
_finalizerCallbacks
.
add
(
callback
);
},
// Start the module, and run all of it's initializers
start
:
function
(
options
){
// Prevent re-start the module
if
(
this
.
_isInitialized
){
return
;
}
this
.
_runModuleDefinition
();
this
.
_initializerCallbacks
.
run
(
options
,
this
);
this
.
_isInitialized
=
true
;
// start the sub-modules
if
(
this
.
submodules
){
_
.
each
(
this
.
submodules
,
function
(
mod
){
mod
.
start
(
options
);
});
}
},
// Stop this module by running its finalizers and then stop all of
// the sub-modules for this module
stop
:
function
(){
// if we are not initialized, don't bother finalizing
if
(
!
this
.
_isInitialized
){
return
;
}
this
.
_isInitialized
=
false
;
// run the finalizers
this
.
_finalizerCallbacks
.
run
();
// then reset the initializers and finalizers
this
.
_setupInitializersAndFinalizers
();
// stop the sub-modules
_
.
each
(
this
.
submodules
,
function
(
mod
){
mod
.
stop
();
});
},
// Configure the module with a definition function and any custom args
// that are to be passed in to the definition function
addDefinition
:
function
(
moduleDefinition
){
this
.
_config
.
definitions
.
push
(
moduleDefinition
);
},
// Internal method: run the module definition function with the correct
// arguments
_runModuleDefinition
:
function
(){
if
(
this
.
_config
.
definitions
.
length
===
0
)
{
return
;
}
// build the correct list of arguments for the module definition
var
args
=
_
.
flatten
([
this
,
this
.
_config
.
app
,
Backbone
,
Marionette
,
$
,
_
,
this
.
_config
.
customArgs
]);
// run the module definition function with the correct args
var
definitionCount
=
this
.
_config
.
definitions
.
length
-
1
;
for
(
var
i
=
0
;
i
<=
definitionCount
;
i
++
){
var
definition
=
this
.
_config
.
definitions
[
i
];
definition
.
apply
(
this
,
args
);
}
},
// Internal method: set up new copies of initializers and finalizers.
// Calling this method will wipe out all existing initializers and
// finalizers.
_setupInitializersAndFinalizers
:
function
(){
this
.
_initializerCallbacks
=
new
Marionette
.
Callbacks
();
this
.
_finalizerCallbacks
=
new
Marionette
.
Callbacks
();
}
});
// Function level methods to create modules
_
.
extend
(
Marionette
.
Module
,
{
// Create a module, hanging off the app parameter as the parent object.
create
:
function
(
app
,
moduleNames
,
moduleDefinition
){
var
that
=
this
;
var
parentModule
=
app
;
moduleNames
=
moduleNames
.
split
(
"
.
"
);
// get the custom args passed in after the module definition and
// get rid of the module name and definition function
var
customArgs
=
slice
.
apply
(
arguments
);
customArgs
.
splice
(
0
,
3
);
// Loop through all the parts of the module definition
var
length
=
moduleNames
.
length
;
_
.
each
(
moduleNames
,
function
(
moduleName
,
i
){
var
isLastModuleInChain
=
(
i
===
length
-
1
);
// Get an existing module of this name if we have one
var
module
=
parentModule
[
moduleName
];
if
(
!
module
){
// Create a new module if we don't have one
module
=
new
Marionette
.
Module
(
moduleName
,
app
,
customArgs
);
parentModule
[
moduleName
]
=
module
;
// store the module on the parent
parentModule
.
submodules
[
moduleName
]
=
module
;
}
// Only add a module definition and initializer when this is
// the last module in a "parent.child.grandchild" hierarchy of
// module names
if
(
isLastModuleInChain
){
that
.
_createModuleDefinition
(
module
,
moduleDefinition
,
app
);
}
// Reset the parent module so that the next child
// in the list will be added to the correct parent
parentModule
=
module
;
});
// Return the last module in the definition chain
return
parentModule
;
},
_createModuleDefinition
:
function
(
module
,
moduleDefinition
,
app
){
var
moduleOptions
=
this
.
_getModuleDefinitionOptions
(
moduleDefinition
);
// add the module definition
if
(
moduleOptions
.
definition
){
module
.
addDefinition
(
moduleOptions
.
definition
);
}
if
(
moduleOptions
.
startWithApp
){
// start the module when the app starts
app
.
addInitializer
(
function
(
options
){
module
.
start
(
options
);
});
}
},
_getModuleDefinitionOptions
:
function
(
moduleDefinition
){
// default to starting the module with the app
var
options
=
{
startWithApp
:
true
};
// short circuit if we don't have a module definition
if
(
!
moduleDefinition
){
return
options
;
}
if
(
_
.
isFunction
(
moduleDefinition
)){
// if the definition is a function, assign it directly
// and use the defaults
options
.
definition
=
moduleDefinition
;
}
else
{
// the definition is an object. grab the "define" attribute
// and the "startWithApp" attribute, as set the options
// appropriately
options
.
definition
=
moduleDefinition
.
define
;
if
(
moduleDefinition
.
hasOwnProperty
(
"
startWithApp
"
)){
options
.
startWithApp
=
moduleDefinition
.
startWithApp
;
}
}
return
options
;
}
});
// Template Cache
// --------------
// Manage templates stored in `<script>` blocks,
// caching them for faster access.
Marionette
.
TemplateCache
=
function
(
templateId
){
this
.
templateId
=
templateId
;
};
// TemplateCache object-level methods. Manage the template
// caches from these method calls instead of creating
// your own TemplateCache instances
_
.
extend
(
Marionette
.
TemplateCache
,
{
templateCaches
:
{},
// Get the specified template by id. Either
// retrieves the cached version, or loads it
// from the DOM.
get
:
function
(
templateId
){
var
that
=
this
;
var
cachedTemplate
=
this
.
templateCaches
[
templateId
];
if
(
!
cachedTemplate
){
cachedTemplate
=
new
Marionette
.
TemplateCache
(
templateId
);
this
.
templateCaches
[
templateId
]
=
cachedTemplate
;
}
return
cachedTemplate
.
load
();
},
// Clear templates from the cache. If no arguments
// are specified, clears all templates:
// `clear()`
//
// If arguments are specified, clears each of the
// specified templates from the cache:
// `clear("#t1", "#t2", "...")`
clear
:
function
(){
var
i
;
var
length
=
arguments
.
length
;
if
(
length
>
0
){
for
(
i
=
0
;
i
<
length
;
i
++
){
delete
this
.
templateCaches
[
arguments
[
i
]];
}
}
else
{
this
.
templateCaches
=
{};
}
}
});
// TemplateCache instance methods, allowing each
// template cache object to manage it's own state
// and know whether or not it has been loaded
_
.
extend
(
Marionette
.
TemplateCache
.
prototype
,
{
// Internal method to load the template asynchronously.
load
:
function
(){
var
that
=
this
;
// Guard clause to prevent loading this template more than once
if
(
this
.
compiledTemplate
){
return
this
.
compiledTemplate
;
}
// Load the template and compile it
var
template
=
this
.
loadTemplate
(
this
.
templateId
);
this
.
compiledTemplate
=
this
.
compileTemplate
(
template
);
return
this
.
compiledTemplate
;
},
// Load a template from the DOM, by default. Override
// this method to provide your own template retrieval,
// such as asynchronous loading from a server.
loadTemplate
:
function
(
templateId
){
var
template
=
$
(
templateId
).
html
();
if
(
!
template
||
template
.
length
===
0
){
var
msg
=
"
Could not find template: '
"
+
templateId
+
"
'
"
;
var
err
=
new
Error
(
msg
);
err
.
name
=
"
NoTemplateError
"
;
throw
err
;
}
return
template
;
},
// Pre-compile the template before caching it. Override
// this method if you do not need to pre-compile a template
// (JST / RequireJS for example) or if you want to change
// the template engine used (Handebars, etc).
compileTemplate
:
function
(
rawTemplate
){
return
_
.
template
(
rawTemplate
);
}
});
// Renderer
// --------
// Render a template with data by passing in the template
// selector and the data to render.
Marionette
.
Renderer
=
{
// Render a template with data. The `template` parameter is
// passed to the `TemplateCache` object to retrieve the
// template function. Override this method to provide your own
// custom rendering and template handling for all of Marionette.
render
:
function
(
template
,
data
){
var
templateFunc
=
typeof
template
===
'
function
'
?
template
:
Marionette
.
TemplateCache
.
get
(
template
);
var
html
=
templateFunc
(
data
);
return
html
;
}
};
// Callbacks
// ---------
// A simple way of managing a collection of callbacks
// and executing them at a later point in time, using jQuery's
// `Deferred` object.
Marionette
.
Callbacks
=
function
(){
this
.
deferred
=
$
.
Deferred
();
this
.
promise
=
this
.
deferred
.
promise
();
};
_
.
extend
(
Marionette
.
Callbacks
.
prototype
,
{
// Add a callback to be executed. Callbacks added here are
// guaranteed to execute, even if they are added after the
// `run` method is called.
add
:
function
(
callback
,
contextOverride
){
this
.
promise
.
done
(
function
(
context
,
options
){
if
(
contextOverride
){
context
=
contextOverride
;
}
callback
.
call
(
context
,
options
);
});
},
// Run all registered callbacks with the context specified.
// Additional callbacks can be added after this has been run
// and they will still be executed.
run
:
function
(
options
,
context
){
this
.
deferred
.
resolve
(
context
,
options
);
}
});
// Event Aggregator
// ----------------
// A pub-sub object that can be used to decouple various parts
// of an application through event-driven architecture.
Marionette
.
EventAggregator
=
Marionette
.
EventBinder
.
extend
({
// Extend any provided options directly on to the event binder
constructor
:
function
(
options
){
Marionette
.
EventBinder
.
apply
(
this
,
arguments
);
_
.
extend
(
this
,
options
);
},
// Override the `bindTo` method to ensure that the event aggregator
// is used as the event binding storage
bindTo
:
function
(
eventName
,
callback
,
context
){
return
Marionette
.
EventBinder
.
prototype
.
bindTo
.
call
(
this
,
this
,
eventName
,
callback
,
context
);
}
});
// Copy the basic Backbone.Events on to the event aggregator
_
.
extend
(
Marionette
.
EventAggregator
.
prototype
,
Backbone
.
Events
);
// Copy the `extend` function used by Backbone's classes
Marionette
.
EventAggregator
.
extend
=
Backbone
.
View
.
extend
;
// Helpers
// -------
// For slicing `arguments` in functions
var
slice
=
Array
.
prototype
.
slice
;
return
Marionette
;
})(
Backbone
,
_
,
window
.
jQuery
||
window
.
Zepto
||
window
.
ender
);
labs/architecture-examples/backbone_marionette_modules/js/lib/underscore.js
0 → 100644
View file @
b7cc462a
// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(
function
()
{
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var
root
=
this
;
// Save the previous value of the `_` variable.
var
previousUnderscore
=
root
.
_
;
// Establish the object that gets returned to break out of a loop iteration.
var
breaker
=
{};
// Save bytes in the minified (but not gzipped) version:
var
ArrayProto
=
Array
.
prototype
,
ObjProto
=
Object
.
prototype
,
FuncProto
=
Function
.
prototype
;
// Create quick reference variables for speed access to core prototypes.
var
slice
=
ArrayProto
.
slice
,
unshift
=
ArrayProto
.
unshift
,
toString
=
ObjProto
.
toString
,
hasOwnProperty
=
ObjProto
.
hasOwnProperty
;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach
=
ArrayProto
.
forEach
,
nativeMap
=
ArrayProto
.
map
,
nativeReduce
=
ArrayProto
.
reduce
,
nativeReduceRight
=
ArrayProto
.
reduceRight
,
nativeFilter
=
ArrayProto
.
filter
,
nativeEvery
=
ArrayProto
.
every
,
nativeSome
=
ArrayProto
.
some
,
nativeIndexOf
=
ArrayProto
.
indexOf
,
nativeLastIndexOf
=
ArrayProto
.
lastIndexOf
,
nativeIsArray
=
Array
.
isArray
,
nativeKeys
=
Object
.
keys
,
nativeBind
=
FuncProto
.
bind
;
// Create a safe reference to the Underscore object for use below.
var
_
=
function
(
obj
)
{
return
new
wrapper
(
obj
);
};
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if
(
typeof
exports
!==
'
undefined
'
)
{
if
(
typeof
module
!==
'
undefined
'
&&
module
.
exports
)
{
exports
=
module
.
exports
=
_
;
}
exports
.
_
=
_
;
}
else
{
root
[
'
_
'
]
=
_
;
}
// Current version.
_
.
VERSION
=
'
1.3.3
'
;
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var
each
=
_
.
each
=
_
.
forEach
=
function
(
obj
,
iterator
,
context
)
{
if
(
obj
==
null
)
return
;
if
(
nativeForEach
&&
obj
.
forEach
===
nativeForEach
)
{
obj
.
forEach
(
iterator
,
context
);
}
else
if
(
obj
.
length
===
+
obj
.
length
)
{
for
(
var
i
=
0
,
l
=
obj
.
length
;
i
<
l
;
i
++
)
{
if
(
i
in
obj
&&
iterator
.
call
(
context
,
obj
[
i
],
i
,
obj
)
===
breaker
)
return
;
}
}
else
{
for
(
var
key
in
obj
)
{
if
(
_
.
has
(
obj
,
key
))
{
if
(
iterator
.
call
(
context
,
obj
[
key
],
key
,
obj
)
===
breaker
)
return
;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_
.
map
=
_
.
collect
=
function
(
obj
,
iterator
,
context
)
{
var
results
=
[];
if
(
obj
==
null
)
return
results
;
if
(
nativeMap
&&
obj
.
map
===
nativeMap
)
return
obj
.
map
(
iterator
,
context
);
each
(
obj
,
function
(
value
,
index
,
list
)
{
results
[
results
.
length
]
=
iterator
.
call
(
context
,
value
,
index
,
list
);
});
if
(
obj
.
length
===
+
obj
.
length
)
results
.
length
=
obj
.
length
;
return
results
;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_
.
reduce
=
_
.
foldl
=
_
.
inject
=
function
(
obj
,
iterator
,
memo
,
context
)
{
var
initial
=
arguments
.
length
>
2
;
if
(
obj
==
null
)
obj
=
[];
if
(
nativeReduce
&&
obj
.
reduce
===
nativeReduce
)
{
if
(
context
)
iterator
=
_
.
bind
(
iterator
,
context
);
return
initial
?
obj
.
reduce
(
iterator
,
memo
)
:
obj
.
reduce
(
iterator
);
}
each
(
obj
,
function
(
value
,
index
,
list
)
{
if
(
!
initial
)
{
memo
=
value
;
initial
=
true
;
}
else
{
memo
=
iterator
.
call
(
context
,
memo
,
value
,
index
,
list
);
}
});
if
(
!
initial
)
throw
new
TypeError
(
'
Reduce of empty array with no initial value
'
);
return
memo
;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_
.
reduceRight
=
_
.
foldr
=
function
(
obj
,
iterator
,
memo
,
context
)
{
var
initial
=
arguments
.
length
>
2
;
if
(
obj
==
null
)
obj
=
[];
if
(
nativeReduceRight
&&
obj
.
reduceRight
===
nativeReduceRight
)
{
if
(
context
)
iterator
=
_
.
bind
(
iterator
,
context
);
return
initial
?
obj
.
reduceRight
(
iterator
,
memo
)
:
obj
.
reduceRight
(
iterator
);
}
var
reversed
=
_
.
toArray
(
obj
).
reverse
();
if
(
context
&&
!
initial
)
iterator
=
_
.
bind
(
iterator
,
context
);
return
initial
?
_
.
reduce
(
reversed
,
iterator
,
memo
,
context
)
:
_
.
reduce
(
reversed
,
iterator
);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_
.
find
=
_
.
detect
=
function
(
obj
,
iterator
,
context
)
{
var
result
;
any
(
obj
,
function
(
value
,
index
,
list
)
{
if
(
iterator
.
call
(
context
,
value
,
index
,
list
))
{
result
=
value
;
return
true
;
}
});
return
result
;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_
.
filter
=
_
.
select
=
function
(
obj
,
iterator
,
context
)
{
var
results
=
[];
if
(
obj
==
null
)
return
results
;
if
(
nativeFilter
&&
obj
.
filter
===
nativeFilter
)
return
obj
.
filter
(
iterator
,
context
);
each
(
obj
,
function
(
value
,
index
,
list
)
{
if
(
iterator
.
call
(
context
,
value
,
index
,
list
))
results
[
results
.
length
]
=
value
;
});
return
results
;
};
// Return all the elements for which a truth test fails.
_
.
reject
=
function
(
obj
,
iterator
,
context
)
{
var
results
=
[];
if
(
obj
==
null
)
return
results
;
each
(
obj
,
function
(
value
,
index
,
list
)
{
if
(
!
iterator
.
call
(
context
,
value
,
index
,
list
))
results
[
results
.
length
]
=
value
;
});
return
results
;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_
.
every
=
_
.
all
=
function
(
obj
,
iterator
,
context
)
{
var
result
=
true
;
if
(
obj
==
null
)
return
result
;
if
(
nativeEvery
&&
obj
.
every
===
nativeEvery
)
return
obj
.
every
(
iterator
,
context
);
each
(
obj
,
function
(
value
,
index
,
list
)
{
if
(
!
(
result
=
result
&&
iterator
.
call
(
context
,
value
,
index
,
list
)))
return
breaker
;
});
return
!!
result
;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var
any
=
_
.
some
=
_
.
any
=
function
(
obj
,
iterator
,
context
)
{
iterator
||
(
iterator
=
_
.
identity
);
var
result
=
false
;
if
(
obj
==
null
)
return
result
;
if
(
nativeSome
&&
obj
.
some
===
nativeSome
)
return
obj
.
some
(
iterator
,
context
);
each
(
obj
,
function
(
value
,
index
,
list
)
{
if
(
result
||
(
result
=
iterator
.
call
(
context
,
value
,
index
,
list
)))
return
breaker
;
});
return
!!
result
;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_
.
include
=
_
.
contains
=
function
(
obj
,
target
)
{
var
found
=
false
;
if
(
obj
==
null
)
return
found
;
if
(
nativeIndexOf
&&
obj
.
indexOf
===
nativeIndexOf
)
return
obj
.
indexOf
(
target
)
!=
-
1
;
found
=
any
(
obj
,
function
(
value
)
{
return
value
===
target
;
});
return
found
;
};
// Invoke a method (with arguments) on every item in a collection.
_
.
invoke
=
function
(
obj
,
method
)
{
var
args
=
slice
.
call
(
arguments
,
2
);
return
_
.
map
(
obj
,
function
(
value
)
{
return
(
_
.
isFunction
(
method
)
?
method
||
value
:
value
[
method
]).
apply
(
value
,
args
);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_
.
pluck
=
function
(
obj
,
key
)
{
return
_
.
map
(
obj
,
function
(
value
){
return
value
[
key
];
});
};
// Return the maximum element or (element-based computation).
_
.
max
=
function
(
obj
,
iterator
,
context
)
{
if
(
!
iterator
&&
_
.
isArray
(
obj
)
&&
obj
[
0
]
===
+
obj
[
0
])
return
Math
.
max
.
apply
(
Math
,
obj
);
if
(
!
iterator
&&
_
.
isEmpty
(
obj
))
return
-
Infinity
;
var
result
=
{
computed
:
-
Infinity
};
each
(
obj
,
function
(
value
,
index
,
list
)
{
var
computed
=
iterator
?
iterator
.
call
(
context
,
value
,
index
,
list
)
:
value
;
computed
>=
result
.
computed
&&
(
result
=
{
value
:
value
,
computed
:
computed
});
});
return
result
.
value
;
};
// Return the minimum element (or element-based computation).
_
.
min
=
function
(
obj
,
iterator
,
context
)
{
if
(
!
iterator
&&
_
.
isArray
(
obj
)
&&
obj
[
0
]
===
+
obj
[
0
])
return
Math
.
min
.
apply
(
Math
,
obj
);
if
(
!
iterator
&&
_
.
isEmpty
(
obj
))
return
Infinity
;
var
result
=
{
computed
:
Infinity
};
each
(
obj
,
function
(
value
,
index
,
list
)
{
var
computed
=
iterator
?
iterator
.
call
(
context
,
value
,
index
,
list
)
:
value
;
computed
<
result
.
computed
&&
(
result
=
{
value
:
value
,
computed
:
computed
});
});
return
result
.
value
;
};
// Shuffle an array.
_
.
shuffle
=
function
(
obj
)
{
var
shuffled
=
[],
rand
;
each
(
obj
,
function
(
value
,
index
,
list
)
{
rand
=
Math
.
floor
(
Math
.
random
()
*
(
index
+
1
));
shuffled
[
index
]
=
shuffled
[
rand
];
shuffled
[
rand
]
=
value
;
});
return
shuffled
;
};
// Sort the object's values by a criterion produced by an iterator.
_
.
sortBy
=
function
(
obj
,
val
,
context
)
{
var
iterator
=
_
.
isFunction
(
val
)
?
val
:
function
(
obj
)
{
return
obj
[
val
];
};
return
_
.
pluck
(
_
.
map
(
obj
,
function
(
value
,
index
,
list
)
{
return
{
value
:
value
,
criteria
:
iterator
.
call
(
context
,
value
,
index
,
list
)
};
}).
sort
(
function
(
left
,
right
)
{
var
a
=
left
.
criteria
,
b
=
right
.
criteria
;
if
(
a
===
void
0
)
return
1
;
if
(
b
===
void
0
)
return
-
1
;
return
a
<
b
?
-
1
:
a
>
b
?
1
:
0
;
}),
'
value
'
);
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_
.
groupBy
=
function
(
obj
,
val
)
{
var
result
=
{};
var
iterator
=
_
.
isFunction
(
val
)
?
val
:
function
(
obj
)
{
return
obj
[
val
];
};
each
(
obj
,
function
(
value
,
index
)
{
var
key
=
iterator
(
value
,
index
);
(
result
[
key
]
||
(
result
[
key
]
=
[])).
push
(
value
);
});
return
result
;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_
.
sortedIndex
=
function
(
array
,
obj
,
iterator
)
{
iterator
||
(
iterator
=
_
.
identity
);
var
low
=
0
,
high
=
array
.
length
;
while
(
low
<
high
)
{
var
mid
=
(
low
+
high
)
>>
1
;
iterator
(
array
[
mid
])
<
iterator
(
obj
)
?
low
=
mid
+
1
:
high
=
mid
;
}
return
low
;
};
// Safely convert anything iterable into a real, live array.
_
.
toArray
=
function
(
obj
)
{
if
(
!
obj
)
return
[];
if
(
_
.
isArray
(
obj
))
return
slice
.
call
(
obj
);
if
(
_
.
isArguments
(
obj
))
return
slice
.
call
(
obj
);
if
(
obj
.
toArray
&&
_
.
isFunction
(
obj
.
toArray
))
return
obj
.
toArray
();
return
_
.
values
(
obj
);
};
// Return the number of elements in an object.
_
.
size
=
function
(
obj
)
{
return
_
.
isArray
(
obj
)
?
obj
.
length
:
_
.
keys
(
obj
).
length
;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_
.
first
=
_
.
head
=
_
.
take
=
function
(
array
,
n
,
guard
)
{
return
(
n
!=
null
)
&&
!
guard
?
slice
.
call
(
array
,
0
,
n
)
:
array
[
0
];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_
.
initial
=
function
(
array
,
n
,
guard
)
{
return
slice
.
call
(
array
,
0
,
array
.
length
-
((
n
==
null
)
||
guard
?
1
:
n
));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_
.
last
=
function
(
array
,
n
,
guard
)
{
if
((
n
!=
null
)
&&
!
guard
)
{
return
slice
.
call
(
array
,
Math
.
max
(
array
.
length
-
n
,
0
));
}
else
{
return
array
[
array
.
length
-
1
];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_
.
rest
=
_
.
tail
=
function
(
array
,
index
,
guard
)
{
return
slice
.
call
(
array
,
(
index
==
null
)
||
guard
?
1
:
index
);
};
// Trim out all falsy values from an array.
_
.
compact
=
function
(
array
)
{
return
_
.
filter
(
array
,
function
(
value
){
return
!!
value
;
});
};
// Return a completely flattened version of an array.
_
.
flatten
=
function
(
array
,
shallow
)
{
return
_
.
reduce
(
array
,
function
(
memo
,
value
)
{
if
(
_
.
isArray
(
value
))
return
memo
.
concat
(
shallow
?
value
:
_
.
flatten
(
value
));
memo
[
memo
.
length
]
=
value
;
return
memo
;
},
[]);
};
// Return a version of the array that does not contain the specified value(s).
_
.
without
=
function
(
array
)
{
return
_
.
difference
(
array
,
slice
.
call
(
arguments
,
1
));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_
.
uniq
=
_
.
unique
=
function
(
array
,
isSorted
,
iterator
)
{
var
initial
=
iterator
?
_
.
map
(
array
,
iterator
)
:
array
;
var
results
=
[];
// The `isSorted` flag is irrelevant if the array only contains two elements.
if
(
array
.
length
<
3
)
isSorted
=
true
;
_
.
reduce
(
initial
,
function
(
memo
,
value
,
index
)
{
if
(
isSorted
?
_
.
last
(
memo
)
!==
value
||
!
memo
.
length
:
!
_
.
include
(
memo
,
value
))
{
memo
.
push
(
value
);
results
.
push
(
array
[
index
]);
}
return
memo
;
},
[]);
return
results
;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_
.
union
=
function
()
{
return
_
.
uniq
(
_
.
flatten
(
arguments
,
true
));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_
.
intersection
=
_
.
intersect
=
function
(
array
)
{
var
rest
=
slice
.
call
(
arguments
,
1
);
return
_
.
filter
(
_
.
uniq
(
array
),
function
(
item
)
{
return
_
.
every
(
rest
,
function
(
other
)
{
return
_
.
indexOf
(
other
,
item
)
>=
0
;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_
.
difference
=
function
(
array
)
{
var
rest
=
_
.
flatten
(
slice
.
call
(
arguments
,
1
),
true
);
return
_
.
filter
(
array
,
function
(
value
){
return
!
_
.
include
(
rest
,
value
);
});
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_
.
zip
=
function
()
{
var
args
=
slice
.
call
(
arguments
);
var
length
=
_
.
max
(
_
.
pluck
(
args
,
'
length
'
));
var
results
=
new
Array
(
length
);
for
(
var
i
=
0
;
i
<
length
;
i
++
)
results
[
i
]
=
_
.
pluck
(
args
,
""
+
i
);
return
results
;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_
.
indexOf
=
function
(
array
,
item
,
isSorted
)
{
if
(
array
==
null
)
return
-
1
;
var
i
,
l
;
if
(
isSorted
)
{
i
=
_
.
sortedIndex
(
array
,
item
);
return
array
[
i
]
===
item
?
i
:
-
1
;
}
if
(
nativeIndexOf
&&
array
.
indexOf
===
nativeIndexOf
)
return
array
.
indexOf
(
item
);
for
(
i
=
0
,
l
=
array
.
length
;
i
<
l
;
i
++
)
if
(
i
in
array
&&
array
[
i
]
===
item
)
return
i
;
return
-
1
;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_
.
lastIndexOf
=
function
(
array
,
item
)
{
if
(
array
==
null
)
return
-
1
;
if
(
nativeLastIndexOf
&&
array
.
lastIndexOf
===
nativeLastIndexOf
)
return
array
.
lastIndexOf
(
item
);
var
i
=
array
.
length
;
while
(
i
--
)
if
(
i
in
array
&&
array
[
i
]
===
item
)
return
i
;
return
-
1
;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_
.
range
=
function
(
start
,
stop
,
step
)
{
if
(
arguments
.
length
<=
1
)
{
stop
=
start
||
0
;
start
=
0
;
}
step
=
arguments
[
2
]
||
1
;
var
len
=
Math
.
max
(
Math
.
ceil
((
stop
-
start
)
/
step
),
0
);
var
idx
=
0
;
var
range
=
new
Array
(
len
);
while
(
idx
<
len
)
{
range
[
idx
++
]
=
start
;
start
+=
step
;
}
return
range
;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var
ctor
=
function
(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_
.
bind
=
function
bind
(
func
,
context
)
{
var
bound
,
args
;
if
(
func
.
bind
===
nativeBind
&&
nativeBind
)
return
nativeBind
.
apply
(
func
,
slice
.
call
(
arguments
,
1
));
if
(
!
_
.
isFunction
(
func
))
throw
new
TypeError
;
args
=
slice
.
call
(
arguments
,
2
);
return
bound
=
function
()
{
if
(
!
(
this
instanceof
bound
))
return
func
.
apply
(
context
,
args
.
concat
(
slice
.
call
(
arguments
)));
ctor
.
prototype
=
func
.
prototype
;
var
self
=
new
ctor
;
var
result
=
func
.
apply
(
self
,
args
.
concat
(
slice
.
call
(
arguments
)));
if
(
Object
(
result
)
===
result
)
return
result
;
return
self
;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_
.
bindAll
=
function
(
obj
)
{
var
funcs
=
slice
.
call
(
arguments
,
1
);
if
(
funcs
.
length
==
0
)
funcs
=
_
.
functions
(
obj
);
each
(
funcs
,
function
(
f
)
{
obj
[
f
]
=
_
.
bind
(
obj
[
f
],
obj
);
});
return
obj
;
};
// Memoize an expensive function by storing its results.
_
.
memoize
=
function
(
func
,
hasher
)
{
var
memo
=
{};
hasher
||
(
hasher
=
_
.
identity
);
return
function
()
{
var
key
=
hasher
.
apply
(
this
,
arguments
);
return
_
.
has
(
memo
,
key
)
?
memo
[
key
]
:
(
memo
[
key
]
=
func
.
apply
(
this
,
arguments
));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_
.
delay
=
function
(
func
,
wait
)
{
var
args
=
slice
.
call
(
arguments
,
2
);
return
setTimeout
(
function
(){
return
func
.
apply
(
null
,
args
);
},
wait
);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_
.
defer
=
function
(
func
)
{
return
_
.
delay
.
apply
(
_
,
[
func
,
1
].
concat
(
slice
.
call
(
arguments
,
1
)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_
.
throttle
=
function
(
func
,
wait
)
{
var
context
,
args
,
timeout
,
throttling
,
more
,
result
;
var
whenDone
=
_
.
debounce
(
function
(){
more
=
throttling
=
false
;
},
wait
);
return
function
()
{
context
=
this
;
args
=
arguments
;
var
later
=
function
()
{
timeout
=
null
;
if
(
more
)
func
.
apply
(
context
,
args
);
whenDone
();
};
if
(
!
timeout
)
timeout
=
setTimeout
(
later
,
wait
);
if
(
throttling
)
{
more
=
true
;
}
else
{
result
=
func
.
apply
(
context
,
args
);
}
whenDone
();
throttling
=
true
;
return
result
;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_
.
debounce
=
function
(
func
,
wait
,
immediate
)
{
var
timeout
;
return
function
()
{
var
context
=
this
,
args
=
arguments
;
var
later
=
function
()
{
timeout
=
null
;
if
(
!
immediate
)
func
.
apply
(
context
,
args
);
};
if
(
immediate
&&
!
timeout
)
func
.
apply
(
context
,
args
);
clearTimeout
(
timeout
);
timeout
=
setTimeout
(
later
,
wait
);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_
.
once
=
function
(
func
)
{
var
ran
=
false
,
memo
;
return
function
()
{
if
(
ran
)
return
memo
;
ran
=
true
;
return
memo
=
func
.
apply
(
this
,
arguments
);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_
.
wrap
=
function
(
func
,
wrapper
)
{
return
function
()
{
var
args
=
[
func
].
concat
(
slice
.
call
(
arguments
,
0
));
return
wrapper
.
apply
(
this
,
args
);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_
.
compose
=
function
()
{
var
funcs
=
arguments
;
return
function
()
{
var
args
=
arguments
;
for
(
var
i
=
funcs
.
length
-
1
;
i
>=
0
;
i
--
)
{
args
=
[
funcs
[
i
].
apply
(
this
,
args
)];
}
return
args
[
0
];
};
};
// Returns a function that will only be executed after being called N times.
_
.
after
=
function
(
times
,
func
)
{
if
(
times
<=
0
)
return
func
();
return
function
()
{
if
(
--
times
<
1
)
{
return
func
.
apply
(
this
,
arguments
);
}
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_
.
keys
=
nativeKeys
||
function
(
obj
)
{
if
(
obj
!==
Object
(
obj
))
throw
new
TypeError
(
'
Invalid object
'
);
var
keys
=
[];
for
(
var
key
in
obj
)
if
(
_
.
has
(
obj
,
key
))
keys
[
keys
.
length
]
=
key
;
return
keys
;
};
// Retrieve the values of an object's properties.
_
.
values
=
function
(
obj
)
{
return
_
.
map
(
obj
,
_
.
identity
);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_
.
functions
=
_
.
methods
=
function
(
obj
)
{
var
names
=
[];
for
(
var
key
in
obj
)
{
if
(
_
.
isFunction
(
obj
[
key
]))
names
.
push
(
key
);
}
return
names
.
sort
();
};
// Extend a given object with all the properties in passed-in object(s).
_
.
extend
=
function
(
obj
)
{
each
(
slice
.
call
(
arguments
,
1
),
function
(
source
)
{
for
(
var
prop
in
source
)
{
obj
[
prop
]
=
source
[
prop
];
}
});
return
obj
;
};
// Return a copy of the object only containing the whitelisted properties.
_
.
pick
=
function
(
obj
)
{
var
result
=
{};
each
(
_
.
flatten
(
slice
.
call
(
arguments
,
1
)),
function
(
key
)
{
if
(
key
in
obj
)
result
[
key
]
=
obj
[
key
];
});
return
result
;
};
// Fill in a given object with default properties.
_
.
defaults
=
function
(
obj
)
{
each
(
slice
.
call
(
arguments
,
1
),
function
(
source
)
{
for
(
var
prop
in
source
)
{
if
(
obj
[
prop
]
==
null
)
obj
[
prop
]
=
source
[
prop
];
}
});
return
obj
;
};
// Create a (shallow-cloned) duplicate of an object.
_
.
clone
=
function
(
obj
)
{
if
(
!
_
.
isObject
(
obj
))
return
obj
;
return
_
.
isArray
(
obj
)
?
obj
.
slice
()
:
_
.
extend
({},
obj
);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_
.
tap
=
function
(
obj
,
interceptor
)
{
interceptor
(
obj
);
return
obj
;
};
// Internal recursive comparison function.
function
eq
(
a
,
b
,
stack
)
{
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if
(
a
===
b
)
return
a
!==
0
||
1
/
a
==
1
/
b
;
// A strict comparison is necessary because `null == undefined`.
if
(
a
==
null
||
b
==
null
)
return
a
===
b
;
// Unwrap any wrapped objects.
if
(
a
.
_chain
)
a
=
a
.
_wrapped
;
if
(
b
.
_chain
)
b
=
b
.
_wrapped
;
// Invoke a custom `isEqual` method if one is provided.
if
(
a
.
isEqual
&&
_
.
isFunction
(
a
.
isEqual
))
return
a
.
isEqual
(
b
);
if
(
b
.
isEqual
&&
_
.
isFunction
(
b
.
isEqual
))
return
b
.
isEqual
(
a
);
// Compare `[[Class]]` names.
var
className
=
toString
.
call
(
a
);
if
(
className
!=
toString
.
call
(
b
))
return
false
;
switch
(
className
)
{
// Strings, numbers, dates, and booleans are compared by value.
case
'
[object String]
'
:
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return
a
==
String
(
b
);
case
'
[object Number]
'
:
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return
a
!=
+
a
?
b
!=
+
b
:
(
a
==
0
?
1
/
a
==
1
/
b
:
a
==
+
b
);
case
'
[object Date]
'
:
case
'
[object Boolean]
'
:
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return
+
a
==
+
b
;
// RegExps are compared by their source patterns and flags.
case
'
[object RegExp]
'
:
return
a
.
source
==
b
.
source
&&
a
.
global
==
b
.
global
&&
a
.
multiline
==
b
.
multiline
&&
a
.
ignoreCase
==
b
.
ignoreCase
;
}
if
(
typeof
a
!=
'
object
'
||
typeof
b
!=
'
object
'
)
return
false
;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var
length
=
stack
.
length
;
while
(
length
--
)
{
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if
(
stack
[
length
]
==
a
)
return
true
;
}
// Add the first object to the stack of traversed objects.
stack
.
push
(
a
);
var
size
=
0
,
result
=
true
;
// Recursively compare objects and arrays.
if
(
className
==
'
[object Array]
'
)
{
// Compare array lengths to determine if a deep comparison is necessary.
size
=
a
.
length
;
result
=
size
==
b
.
length
;
if
(
result
)
{
// Deep compare the contents, ignoring non-numeric properties.
while
(
size
--
)
{
// Ensure commutative equality for sparse arrays.
if
(
!
(
result
=
size
in
a
==
size
in
b
&&
eq
(
a
[
size
],
b
[
size
],
stack
)))
break
;
}
}
}
else
{
// Objects with different constructors are not equivalent.
if
(
'
constructor
'
in
a
!=
'
constructor
'
in
b
||
a
.
constructor
!=
b
.
constructor
)
return
false
;
// Deep compare objects.
for
(
var
key
in
a
)
{
if
(
_
.
has
(
a
,
key
))
{
// Count the expected number of properties.
size
++
;
// Deep compare each member.
if
(
!
(
result
=
_
.
has
(
b
,
key
)
&&
eq
(
a
[
key
],
b
[
key
],
stack
)))
break
;
}
}
// Ensure that both objects contain the same number of properties.
if
(
result
)
{
for
(
key
in
b
)
{
if
(
_
.
has
(
b
,
key
)
&&
!
(
size
--
))
break
;
}
result
=
!
size
;
}
}
// Remove the first object from the stack of traversed objects.
stack
.
pop
();
return
result
;
}
// Perform a deep comparison to check if two objects are equal.
_
.
isEqual
=
function
(
a
,
b
)
{
return
eq
(
a
,
b
,
[]);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_
.
isEmpty
=
function
(
obj
)
{
if
(
obj
==
null
)
return
true
;
if
(
_
.
isArray
(
obj
)
||
_
.
isString
(
obj
))
return
obj
.
length
===
0
;
for
(
var
key
in
obj
)
if
(
_
.
has
(
obj
,
key
))
return
false
;
return
true
;
};
// Is a given value a DOM element?
_
.
isElement
=
function
(
obj
)
{
return
!!
(
obj
&&
obj
.
nodeType
==
1
);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_
.
isArray
=
nativeIsArray
||
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object Array]
'
;
};
// Is a given variable an object?
_
.
isObject
=
function
(
obj
)
{
return
obj
===
Object
(
obj
);
};
// Is a given variable an arguments object?
_
.
isArguments
=
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object Arguments]
'
;
};
if
(
!
_
.
isArguments
(
arguments
))
{
_
.
isArguments
=
function
(
obj
)
{
return
!!
(
obj
&&
_
.
has
(
obj
,
'
callee
'
));
};
}
// Is a given value a function?
_
.
isFunction
=
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object Function]
'
;
};
// Is a given value a string?
_
.
isString
=
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object String]
'
;
};
// Is a given value a number?
_
.
isNumber
=
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object Number]
'
;
};
// Is a given object a finite number?
_
.
isFinite
=
function
(
obj
)
{
return
_
.
isNumber
(
obj
)
&&
isFinite
(
obj
);
};
// Is the given value `NaN`?
_
.
isNaN
=
function
(
obj
)
{
// `NaN` is the only value for which `===` is not reflexive.
return
obj
!==
obj
;
};
// Is a given value a boolean?
_
.
isBoolean
=
function
(
obj
)
{
return
obj
===
true
||
obj
===
false
||
toString
.
call
(
obj
)
==
'
[object Boolean]
'
;
};
// Is a given value a date?
_
.
isDate
=
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object Date]
'
;
};
// Is the given value a regular expression?
_
.
isRegExp
=
function
(
obj
)
{
return
toString
.
call
(
obj
)
==
'
[object RegExp]
'
;
};
// Is a given value equal to null?
_
.
isNull
=
function
(
obj
)
{
return
obj
===
null
;
};
// Is a given variable undefined?
_
.
isUndefined
=
function
(
obj
)
{
return
obj
===
void
0
;
};
// Has own property?
_
.
has
=
function
(
obj
,
key
)
{
return
hasOwnProperty
.
call
(
obj
,
key
);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_
.
noConflict
=
function
()
{
root
.
_
=
previousUnderscore
;
return
this
;
};
// Keep the identity function around for default iterators.
_
.
identity
=
function
(
value
)
{
return
value
;
};
// Run a function **n** times.
_
.
times
=
function
(
n
,
iterator
,
context
)
{
for
(
var
i
=
0
;
i
<
n
;
i
++
)
iterator
.
call
(
context
,
i
);
};
// Escape a string for HTML interpolation.
_
.
escape
=
function
(
string
)
{
return
(
''
+
string
).
replace
(
/&/g
,
'
&
'
).
replace
(
/</g
,
'
<
'
).
replace
(
/>/g
,
'
>
'
).
replace
(
/"/g
,
'
"
'
).
replace
(
/'/g
,
'
'
'
).
replace
(
/
\/
/g
,
'
/
'
);
};
// If the value of the named property is a function then invoke it;
// otherwise, return it.
_
.
result
=
function
(
object
,
property
)
{
if
(
object
==
null
)
return
null
;
var
value
=
object
[
property
];
return
_
.
isFunction
(
value
)
?
value
.
call
(
object
)
:
value
;
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_
.
mixin
=
function
(
obj
)
{
each
(
_
.
functions
(
obj
),
function
(
name
){
addToWrapper
(
name
,
_
[
name
]
=
obj
[
name
]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var
idCounter
=
0
;
_
.
uniqueId
=
function
(
prefix
)
{
var
id
=
idCounter
++
;
return
prefix
?
prefix
+
id
:
id
;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_
.
templateSettings
=
{
evaluate
:
/<%
([\s\S]
+
?)
%>/g
,
interpolate
:
/<%=
([\s\S]
+
?)
%>/g
,
escape
:
/<%-
([\s\S]
+
?)
%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var
noMatch
=
/.^/
;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var
escapes
=
{
'
\\
'
:
'
\\
'
,
"
'
"
:
"
'
"
,
'
r
'
:
'
\r
'
,
'
n
'
:
'
\n
'
,
'
t
'
:
'
\t
'
,
'
u2028
'
:
'
\
u2028
'
,
'
u2029
'
:
'
\
u2029
'
};
for
(
var
p
in
escapes
)
escapes
[
escapes
[
p
]]
=
p
;
var
escaper
=
/
\\
|'|
\r
|
\n
|
\t
|
\u
2028|
\u
2029/g
;
var
unescaper
=
/
\\(\\
|'|r|n|t|u2028|u2029
)
/g
;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var
unescape
=
function
(
code
)
{
return
code
.
replace
(
unescaper
,
function
(
match
,
escape
)
{
return
escapes
[
escape
];
});
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_
.
template
=
function
(
text
,
data
,
settings
)
{
settings
=
_
.
defaults
(
settings
||
{},
_
.
templateSettings
);
// Compile the template source, taking care to escape characters that
// cannot be included in a string literal and then unescape them in code
// blocks.
var
source
=
"
__p+='
"
+
text
.
replace
(
escaper
,
function
(
match
)
{
return
'
\\
'
+
escapes
[
match
];
})
.
replace
(
settings
.
escape
||
noMatch
,
function
(
match
,
code
)
{
return
"
'+
\n
_.escape(
"
+
unescape
(
code
)
+
"
)+
\n
'
"
;
})
.
replace
(
settings
.
interpolate
||
noMatch
,
function
(
match
,
code
)
{
return
"
'+
\n
(
"
+
unescape
(
code
)
+
"
)+
\n
'
"
;
})
.
replace
(
settings
.
evaluate
||
noMatch
,
function
(
match
,
code
)
{
return
"
';
\n
"
+
unescape
(
code
)
+
"
\n
;__p+='
"
;
})
+
"
';
\n
"
;
// If a variable is not specified, place data values in local scope.
if
(
!
settings
.
variable
)
source
=
'
with(obj||{}){
\n
'
+
source
+
'
}
\n
'
;
source
=
"
var __p='';
"
+
"
var print=function(){__p+=Array.prototype.join.call(arguments, '')};
\n
"
+
source
+
"
return __p;
\n
"
;
var
render
=
new
Function
(
settings
.
variable
||
'
obj
'
,
'
_
'
,
source
);
if
(
data
)
return
render
(
data
,
_
);
var
template
=
function
(
data
)
{
return
render
.
call
(
this
,
data
,
_
);
};
// Provide the compiled function source as a convenience for build time
// precompilation.
template
.
source
=
'
function(
'
+
(
settings
.
variable
||
'
obj
'
)
+
'
){
\n
'
+
source
+
'
}
'
;
return
template
;
};
// Add a "chain" function, which will delegate to the wrapper.
_
.
chain
=
function
(
obj
)
{
return
_
(
obj
).
chain
();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var
wrapper
=
function
(
obj
)
{
this
.
_wrapped
=
obj
;
};
// Expose `wrapper.prototype` as `_.prototype`
_
.
prototype
=
wrapper
.
prototype
;
// Helper function to continue chaining intermediate results.
var
result
=
function
(
obj
,
chain
)
{
return
chain
?
_
(
obj
).
chain
()
:
obj
;
};
// A method to easily add functions to the OOP wrapper.
var
addToWrapper
=
function
(
name
,
func
)
{
wrapper
.
prototype
[
name
]
=
function
()
{
var
args
=
slice
.
call
(
arguments
);
unshift
.
call
(
args
,
this
.
_wrapped
);
return
result
(
func
.
apply
(
_
,
args
),
this
.
_chain
);
};
};
// Add all of the Underscore functions to the wrapper object.
_
.
mixin
(
_
);
// Add all mutator Array functions to the wrapper.
each
([
'
pop
'
,
'
push
'
,
'
reverse
'
,
'
shift
'
,
'
sort
'
,
'
splice
'
,
'
unshift
'
],
function
(
name
)
{
var
method
=
ArrayProto
[
name
];
wrapper
.
prototype
[
name
]
=
function
()
{
var
wrapped
=
this
.
_wrapped
;
method
.
apply
(
wrapped
,
arguments
);
var
length
=
wrapped
.
length
;
if
((
name
==
'
shift
'
||
name
==
'
splice
'
)
&&
length
===
0
)
delete
wrapped
[
0
];
return
result
(
wrapped
,
this
.
_chain
);
};
});
// Add all accessor Array functions to the wrapper.
each
([
'
concat
'
,
'
join
'
,
'
slice
'
],
function
(
name
)
{
var
method
=
ArrayProto
[
name
];
wrapper
.
prototype
[
name
]
=
function
()
{
return
result
(
method
.
apply
(
this
.
_wrapped
,
arguments
),
this
.
_chain
);
};
});
// Start chaining a wrapped Underscore object.
wrapper
.
prototype
.
chain
=
function
()
{
this
.
_chain
=
true
;
return
this
;
};
// Extracts the result from a wrapped and chained object.
wrapper
.
prototype
.
value
=
function
()
{
return
this
.
_wrapped
;
};
}).
call
(
this
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment