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
28b669e4
Commit
28b669e4
authored
Mar 16, 2015
by
Sam Saccone
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1164 from yavorsky/master
Update exoskeleton to 0.7.0
parents
f90608ba
8e9e5c77
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
902 additions
and
806 deletions
+902
-806
examples/exoskeleton/.gitignore
examples/exoskeleton/.gitignore
+3
-0
examples/exoskeleton/index.html
examples/exoskeleton/index.html
+2
-0
examples/exoskeleton/js/views/app-view.js
examples/exoskeleton/js/views/app-view.js
+13
-7
examples/exoskeleton/js/views/todo-view.js
examples/exoskeleton/js/views/todo-view.js
+1
-1
examples/exoskeleton/node_modules/backbone.nativeview/backbone.nativeview.js
...n/node_modules/backbone.nativeview/backbone.nativeview.js
+169
-0
examples/exoskeleton/node_modules/exoskeleton/exoskeleton.js
examples/exoskeleton/node_modules/exoskeleton/exoskeleton.js
+712
-797
examples/exoskeleton/package.json
examples/exoskeleton/package.json
+2
-1
No files found.
examples/exoskeleton/.gitignore
View file @
28b669e4
...
...
@@ -4,6 +4,9 @@ node_modules/exoskeleton/*
node_modules/backbone.localstorage/*
!node_modules/backbone.localstorage/backbone.localStorage.js
node_modules/backbone.nativeview/*
!node_modules/backbone.nativeview/backbone.nativeview.js
node_modules/todomvc-app-css/*
!node_modules/todomvc-app-css/index.css
...
...
examples/exoskeleton/index.html
View file @
28b669e4
...
...
@@ -53,6 +53,8 @@
<script
src=
"node_modules/exoskeleton/exoskeleton.js"
></script>
<script
src=
"node_modules/microtemplates/index.js"
></script>
<script
src=
"node_modules/backbone.localstorage/backbone.localStorage.js"
></script>
<script
src=
"node_modules/backbone.nativeview/backbone.nativeview.js"
></script>
<script>
Backbone
.
View
=
Backbone
.
NativeView
;
</script>
<script
src=
"js/models/todo.js"
></script>
<script
src=
"js/collections/todos.js"
></script>
<script
src=
"js/views/todo-view.js"
></script>
...
...
examples/exoskeleton/js/views/app-view.js
View file @
28b669e4
...
...
@@ -8,6 +8,12 @@ var app = app || {};
el
.
style
.
display
=
toggle
?
''
:
'
none
'
;
};
var
matchesSelector
=
function
(
node
,
selector
)
{
return
[].
some
.
call
(
document
.
querySelectorAll
(
selector
),
function
(
el
)
{
return
el
===
node
;
});
};
// The Application
// ---------------
...
...
@@ -32,10 +38,10 @@ var app = app || {};
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize
:
function
()
{
this
.
allCheckbox
=
this
.
find
(
'
#toggle-all
'
);
this
.
input
=
this
.
find
(
'
#new-todo
'
);
this
.
footer
=
this
.
find
(
'
#footer
'
);
this
.
main
=
this
.
find
(
'
#main
'
);
this
.
allCheckbox
=
this
.
$
(
'
#toggle-all
'
).
item
(
0
);
this
.
input
=
this
.
$
(
'
#new-todo
'
).
item
(
0
);
this
.
footer
=
this
.
$
(
'
#footer
'
).
item
(
0
);
this
.
main
=
this
.
$
(
'
#main
'
).
item
(
0
);
this
.
listenTo
(
app
.
todos
,
'
add
'
,
this
.
addOne
);
this
.
listenTo
(
app
.
todos
,
'
reset
'
,
this
.
addAll
);
...
...
@@ -66,9 +72,9 @@ var app = app || {};
remaining
:
remaining
});
this
.
findAll
(
'
#filters li a
'
).
forEach
(
function
(
el
)
{
[].
forEach
.
call
(
this
.
$
(
'
#filters li a
'
),
function
(
el
)
{
el
.
classList
.
remove
(
'
selected
'
);
if
(
Backbone
.
utils
.
matchesSelector
(
el
,
selector
))
{
if
(
matchesSelector
(
el
,
selector
))
{
el
.
classList
.
add
(
'
selected
'
);
}
});
...
...
@@ -90,7 +96,7 @@ var app = app || {};
// Add all items in the **Todos** collection at once.
addAll
:
function
()
{
this
.
find
(
'
#todo-list
'
).
innerHTML
=
''
;
this
.
$
(
'
#todo-list
'
).
item
(
0
).
innerHTML
=
''
;
app
.
todos
.
forEach
(
this
.
addOne
,
this
);
},
...
...
examples/exoskeleton/js/views/todo-view.js
View file @
28b669e4
...
...
@@ -41,7 +41,7 @@ var app = app || {};
var
method
=
this
.
model
.
get
(
'
completed
'
)
?
'
add
'
:
'
remove
'
;
this
.
el
.
classList
[
method
](
'
completed
'
);
this
.
toggleVisible
();
this
.
input
=
this
.
find
(
'
.edit
'
);
this
.
input
=
this
.
$
(
'
.edit
'
).
item
(
0
);
return
this
;
},
...
...
examples/exoskeleton/node_modules/backbone.nativeview/backbone.nativeview.js
0 → 100644
View file @
28b669e4
// Backbone.NativeView.js 0.3.2
// ---------------
// (c) 2014 Adam Krebs, Jimmy Yuen Ho Wong
// Backbone.NativeView may be freely distributed under the MIT license.
// For all details and documentation:
// https://github.com/akre54/Backbone.NativeView
(
function
(
factory
)
{
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
define
([
'
backbone
'
],
factory
);
}
else
if
(
typeof
exports
===
'
object
'
)
{
factory
(
require
(
'
backbone
'
));
}
else
{
factory
(
Backbone
);
}
}(
function
(
Backbone
)
{
// Cached regex to match an opening '<' of an HTML tag, possibly left-padded
// with whitespace.
var
paddedLt
=
/^
\s
*</
;
// Caches a local reference to `Element.prototype` for faster access.
var
ElementProto
=
(
typeof
Element
!==
'
undefined
'
&&
Element
.
prototype
)
||
{};
// Cross-browser event listener shims
var
elementAddEventListener
=
ElementProto
.
addEventListener
||
function
(
eventName
,
listener
)
{
return
this
.
attachEvent
(
'
on
'
+
eventName
,
listener
);
}
var
elementRemoveEventListener
=
ElementProto
.
removeEventListener
||
function
(
eventName
,
listener
)
{
return
this
.
detachEvent
(
'
on
'
+
eventName
,
listener
);
}
var
indexOf
=
function
(
array
,
item
)
{
for
(
var
i
=
0
,
len
=
array
.
length
;
i
<
len
;
i
++
)
if
(
array
[
i
]
===
item
)
return
i
;
return
-
1
;
}
// Find the right `Element#matches` for IE>=9 and modern browsers.
var
matchesSelector
=
ElementProto
.
matches
||
ElementProto
.
webkitMatchesSelector
||
ElementProto
.
mozMatchesSelector
||
ElementProto
.
msMatchesSelector
||
ElementProto
.
oMatchesSelector
||
// Make our own `Element#matches` for IE8
function
(
selector
)
{
// Use querySelectorAll to find all elements matching the selector,
// then check if the given element is included in that list.
// Executing the query on the parentNode reduces the resulting nodeList,
// (document doesn't have a parentNode).
var
nodeList
=
(
this
.
parentNode
||
document
).
querySelectorAll
(
selector
)
||
[];
return
!!~
indexOf
(
nodeList
,
this
);
};
// Cache Backbone.View for later access in constructor
var
BBView
=
Backbone
.
View
;
// To extend an existing view to use native methods, extend the View prototype
// with the mixin: _.extend(MyView.prototype, Backbone.NativeViewMixin);
Backbone
.
NativeViewMixin
=
{
_domEvents
:
null
,
constructor
:
function
()
{
this
.
_domEvents
=
[];
return
BBView
.
apply
(
this
,
arguments
);
},
$
:
function
(
selector
)
{
return
this
.
el
.
querySelectorAll
(
selector
);
},
_removeElement
:
function
()
{
this
.
undelegateEvents
();
if
(
this
.
el
.
parentNode
)
this
.
el
.
parentNode
.
removeChild
(
this
.
el
);
},
// Apply the `element` to the view. `element` can be a CSS selector,
// a string of HTML, or an Element node.
_setElement
:
function
(
element
)
{
if
(
typeof
element
==
'
string
'
)
{
if
(
paddedLt
.
test
(
element
))
{
var
el
=
document
.
createElement
(
'
div
'
);
el
.
innerHTML
=
element
;
this
.
el
=
el
.
firstChild
;
}
else
{
this
.
el
=
document
.
querySelector
(
element
);
}
}
else
{
this
.
el
=
element
;
}
},
// Set a hash of attributes to the view's `el`. We use the "prop" version
// if available, falling back to `setAttribute` for the catch-all.
_setAttributes
:
function
(
attrs
)
{
for
(
var
attr
in
attrs
)
{
attr
in
this
.
el
?
this
.
el
[
attr
]
=
attrs
[
attr
]
:
this
.
el
.
setAttribute
(
attr
,
attrs
[
attr
]);
}
},
// Make a event delegation handler for the given `eventName` and `selector`
// and attach it to `this.el`.
// If selector is empty, the listener will be bound to `this.el`. If not, a
// new handler that will recursively traverse up the event target's DOM
// hierarchy looking for a node that matches the selector. If one is found,
// the event's `delegateTarget` property is set to it and the return the
// result of calling bound `listener` with the parameters given to the
// handler.
delegate
:
function
(
eventName
,
selector
,
listener
)
{
if
(
typeof
selector
===
'
function
'
)
{
listener
=
selector
;
selector
=
null
;
}
var
root
=
this
.
el
;
var
handler
=
selector
?
function
(
e
)
{
var
node
=
e
.
target
||
e
.
srcElement
;
for
(;
node
&&
node
!=
root
;
node
=
node
.
parentNode
)
{
if
(
matchesSelector
.
call
(
node
,
selector
))
{
e
.
delegateTarget
=
node
;
listener
(
e
);
}
}
}
:
listener
;
elementAddEventListener
.
call
(
this
.
el
,
eventName
,
handler
,
false
);
this
.
_domEvents
.
push
({
eventName
:
eventName
,
handler
:
handler
,
listener
:
listener
,
selector
:
selector
});
return
handler
;
},
// Remove a single delegated event. Either `eventName` or `selector` must
// be included, `selector` and `listener` are optional.
undelegate
:
function
(
eventName
,
selector
,
listener
)
{
if
(
typeof
selector
===
'
function
'
)
{
listener
=
selector
;
selector
=
null
;
}
if
(
this
.
el
)
{
var
handlers
=
this
.
_domEvents
.
slice
();
for
(
var
i
=
0
,
len
=
handlers
.
length
;
i
<
len
;
i
++
)
{
var
item
=
handlers
[
i
];
var
match
=
item
.
eventName
===
eventName
&&
(
listener
?
item
.
listener
===
listener
:
true
)
&&
(
selector
?
item
.
selector
===
selector
:
true
);
if
(
!
match
)
continue
;
elementRemoveEventListener
.
call
(
this
.
el
,
item
.
eventName
,
item
.
handler
,
false
);
this
.
_domEvents
.
splice
(
indexOf
(
handlers
,
item
),
1
);
}
}
return
this
;
},
// Remove all events created with `delegate` from `el`
undelegateEvents
:
function
()
{
if
(
this
.
el
)
{
for
(
var
i
=
0
,
len
=
this
.
_domEvents
.
length
;
i
<
len
;
i
++
)
{
var
item
=
this
.
_domEvents
[
i
];
elementRemoveEventListener
.
call
(
this
.
el
,
item
.
eventName
,
item
.
handler
,
false
);
};
this
.
_domEvents
.
length
=
0
;
}
return
this
;
}
};
Backbone
.
NativeView
=
Backbone
.
View
.
extend
(
Backbone
.
NativeViewMixin
);
return
Backbone
.
NativeView
;
}));
examples/exoskeleton/node_modules/exoskeleton/exoskeleton.js
View file @
28b669e4
/*!
* Exoskeleton.js 0.
3
.0
* Exoskeleton.js 0.
7
.0
* (c) 2013 Paul Miller <http://paulmillr.com>
* Based on Backbone.js
* (c) 2010-2013 Jeremy Ashkenas, DocumentCloud
* Exoskeleton may be freely distributed under the MIT license.
* For all details and documentation: <http://exos
kel.at
>
* For all details and documentation: <http://exos
js.com
>
*/
(
function
(
factory
)
{
(
function
(
root
,
factory
)
{
// Set up Backbone appropriately for the environment.
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
define
([
'
underscore
'
,
'
jquery
'
],
factory
);
}
else
if
(
typeof
exports
===
'
object
'
)
{
factory
(
require
(
'
underscore
'
),
require
(
'
jquery
'
));
define
([
'
underscore
'
,
'
jquery
'
,
'
exports
'
],
function
(
_
,
$
,
exports
)
{
root
.
Backbone
=
root
.
Exoskeleton
=
factory
(
root
,
exports
,
_
,
$
);
});
}
else
if
(
typeof
exports
!==
'
undefined
'
)
{
var
_
,
$
;
try
{
_
=
require
(
'
underscore
'
);
}
catch
(
e
)
{
}
try
{
$
=
require
(
'
jquery
'
);
}
catch
(
e
)
{
}
factory
(
root
,
exports
,
_
,
$
);
}
else
{
factory
(
this
.
_
,
this
.
jQuery
||
this
.
Zepto
||
this
.
ender
||
this
.
$
);
root
.
Backbone
=
root
.
Exoskeleton
=
factory
(
root
,
{},
root
.
_
,
(
root
.
jQuery
||
root
.
Zepto
||
root
.
ender
||
root
.
$
)
);
}
})(
function
(
_
,
$
)
{
})(
this
,
function
(
root
,
Backbone
,
_
,
$
)
{
'
use strict
'
;
// Initial Setup
// -------------
// Save a reference to the global object (`window` in the browser, `exports`
// on the server).
var
root
=
(
typeof
window
===
'
undefined
'
)
?
exports
:
window
;
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var
previousBackbone
=
root
.
Backbone
;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both the browser and the server.
var
Backbone
;
if
(
typeof
exports
!==
'
undefined
'
)
{
Backbone
=
exports
;
}
else
{
Backbone
=
root
.
Backbone
=
{};
}
var
previousExoskeleton
=
root
.
Exoskeleton
;
// Underscore replacement.
var
utils
=
_
=
Backbone
.
utils
=
_
||
{}
;
var
utils
=
Backbone
.
utils
=
_
=
(
_
||
{})
;
// Hold onto a local reference to `$`. Can be changed at any point.
Backbone
.
$
=
$
;
...
...
@@ -48,7 +43,7 @@
var
array
=
[];
var
push
=
array
.
push
;
var
slice
=
array
.
slice
;
var
splice
=
array
.
splice
;
var
toString
=
({}).
toString
;
// Current version of the library. Keep in sync with `package.json`.
// Backbone.VERSION = '1.0.0';
...
...
@@ -57,20 +52,10 @@
// to its previous owner. Returns a reference to this Backbone object.
Backbone
.
noConflict
=
function
()
{
root
.
Backbone
=
previousBackbone
;
root
.
Exoskeleton
=
previousExoskeleton
;
return
this
;
};
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
// will fake `"PATCH"`, `"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
;
// Helpers
// -------
...
...
@@ -84,7 +69,7 @@
// 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
&&
hasOwnProperty
.
call
(
protoProps
,
'
constructor
'
))
{
if
(
protoProps
&&
_
.
has
(
protoProps
,
'
constructor
'
))
{
child
=
protoProps
.
constructor
;
}
else
{
child
=
function
(){
return
parent
.
apply
(
this
,
arguments
);
};
...
...
@@ -123,195 +108,177 @@
model
.
trigger
(
'
error
'
,
model
,
resp
,
options
);
};
};
utils
.
result
=
function
result
(
object
,
property
)
{
var
value
=
object
?
object
[
property
]
:
undefined
;
return
typeof
value
===
'
function
'
?
object
[
property
]()
:
value
;
};
utils
.
defaults
=
function
defaults
(
obj
,
from1
,
from2
)
{
[].
slice
.
call
(
arguments
,
1
).
forEach
(
function
(
item
)
{
for
(
var
key
in
item
)
if
(
obj
[
key
]
===
undefined
)
obj
[
key
]
=
item
[
key
];
});
return
obj
;
};
// Checker for utility methods. Useful for custom builds.
var
utilExists
=
function
(
method
)
{
return
typeof
_
[
method
]
===
'
function
'
;
};
utils
.
extend
=
function
extend
(
obj
)
{
[].
slice
.
call
(
arguments
,
1
).
forEach
(
function
(
item
)
{
for
(
var
key
in
item
)
obj
[
key
]
=
item
[
key
];
});
return
obj
;
};
utils
.
result
=
function
result
(
object
,
property
)
{
var
value
=
object
?
object
[
property
]
:
undefined
;
return
typeof
value
===
'
function
'
?
object
[
property
]()
:
value
;
};
var
htmlEscapes
=
{
'
&
'
:
'
&
'
,
'
<
'
:
'
<
'
,
'
>
'
:
'
>
'
,
'
"
'
:
'
"
'
,
"
'
"
:
'
'
'
};
utils
.
defaults
=
function
defaults
(
obj
)
{
slice
.
call
(
arguments
,
1
).
forEach
(
function
(
item
)
{
for
(
var
key
in
item
)
if
(
obj
[
key
]
===
undefined
)
obj
[
key
]
=
item
[
key
];
});
return
obj
;
};
utils
.
escape
=
function
escape
(
string
)
{
return
string
==
null
?
''
:
String
(
string
).
replace
(
/
[
&<>"'
]
/g
,
function
(
match
)
{
return
htmlEscapes
[
match
];
});
};
utils
.
extend
=
function
extend
(
obj
)
{
slice
.
call
(
arguments
,
1
).
forEach
(
function
(
item
)
{
for
(
var
key
in
item
)
obj
[
key
]
=
item
[
key
];
});
return
obj
;
};
utils
.
sortedIndex
=
function
sortedIndex
(
array
,
obj
,
iterator
,
context
)
{
iterator
=
iterator
==
null
?
Function
.
prototype
:
(
typeof
iterator
===
'
function
'
?
iterator
:
function
(
obj
){
return
obj
[
iterator
];
});
var
value
=
iterator
.
call
(
context
,
obj
);
var
low
=
0
,
high
=
array
.
length
;
while
(
low
<
high
)
{
var
mid
=
(
low
+
high
)
>>>
1
;
iterator
.
call
(
context
,
array
[
mid
])
<
value
?
low
=
mid
+
1
:
high
=
mid
;
}
return
low
;
};
var
htmlEscapes
=
{
'
&
'
:
'
&
'
,
'
<
'
:
'
<
'
,
'
>
'
:
'
>
'
,
'
"
'
:
'
"
'
,
"
'
"
:
'
'
'
};
utils
.
sortBy
=
function
(
obj
,
value
,
context
)
{
var
iterator
=
typeof
value
===
'
function
'
?
value
:
function
(
obj
){
return
obj
[
value
];
};
return
obj
.
map
(
function
(
value
,
index
,
list
)
{
return
{
value
:
value
,
index
:
index
,
criteria
:
iterator
.
call
(
context
,
value
,
index
,
list
)
};
})
.
sort
(
function
(
left
,
right
)
{
var
a
=
left
.
criteria
;
var
b
=
right
.
criteria
;
if
(
a
!==
b
)
{
if
(
a
>
b
||
a
===
void
0
)
return
1
;
if
(
a
<
b
||
b
===
void
0
)
return
-
1
;
}
return
left
.
index
-
right
.
index
;
})
.
map
(
function
(
item
)
{
return
item
.
value
;
utils
.
escape
=
function
escape
(
string
)
{
return
string
==
null
?
''
:
String
(
string
).
replace
(
/
[
&<>"'
]
/g
,
function
(
match
)
{
return
htmlEscapes
[
match
];
});
};
};
/** Used to generate unique IDs */
var
idCounter
=
0
;
utils
.
sortBy
=
function
(
obj
,
value
,
context
)
{
var
iterator
=
typeof
value
===
'
function
'
?
value
:
function
(
obj
){
return
obj
[
value
];
};
return
obj
.
map
(
function
(
value
,
index
,
list
)
{
return
{
value
:
value
,
index
:
index
,
criteria
:
iterator
.
call
(
context
,
value
,
index
,
list
)
};
})
.
sort
(
function
(
left
,
right
)
{
var
a
=
left
.
criteria
;
var
b
=
right
.
criteria
;
if
(
a
!==
b
)
{
if
(
a
>
b
||
a
===
void
0
)
return
1
;
if
(
a
<
b
||
b
===
void
0
)
return
-
1
;
}
return
left
.
index
-
right
.
index
;
})
.
map
(
function
(
item
)
{
return
item
.
value
;
});
};
utils
.
uniqueId
=
function
uniqueId
(
prefix
)
{
var
id
=
++
idCounter
+
''
;
return
prefix
?
prefix
+
id
:
id
;
};
/** Used to generate unique IDs */
var
idCounter
=
0
;
var
eq
=
function
(
a
,
b
,
aStack
,
bStack
)
{
// 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 instanceof _) a = a._wrapped;
//if (b instanceof _) b = b._wrapped;
// 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
=
aStack
.
length
;
while
(
length
--
)
{
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if
(
aStack
[
length
]
==
a
)
return
bStack
[
length
]
==
b
;
}
// Objects with different constructors are not equivalent, but `Object`s
// from different frames are.
var
aCtor
=
a
.
constructor
,
bCtor
=
b
.
constructor
;
if
(
aCtor
!==
bCtor
&&
!
(
typeof
aCtor
===
'
function
'
&&
(
aCtor
instanceof
aCtor
)
&&
typeof
bCtor
===
'
function
'
&&
(
bCtor
instanceof
bCtor
)))
{
return
false
;
}
// Add the first object to the stack of traversed objects.
aStack
.
push
(
a
);
bStack
.
push
(
b
);
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
--
)
{
if
(
!
(
result
=
eq
(
a
[
size
],
b
[
size
],
aStack
,
bStack
)))
break
;
}
utils
.
uniqueId
=
function
uniqueId
(
prefix
)
{
var
id
=
++
idCounter
+
''
;
return
prefix
?
prefix
+
id
:
id
;
};
utils
.
has
=
function
(
obj
,
key
)
{
return
Object
.
hasOwnProperty
.
call
(
obj
,
key
);
};
var
eq
=
function
(
a
,
b
,
aStack
,
bStack
)
{
// 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 instanceof _) a = a._wrapped;
//if (b instanceof _) b = b._wrapped;
// 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
;
}
}
else
{
// Deep compare objects.
for
(
var
key
in
a
)
{
if
(
hasOwnProperty
.
call
(
a
,
key
))
{
// Count the expected number of properties.
size
++
;
// Deep compare each member.
if
(
!
(
result
=
hasOwnProperty
.
call
(
b
,
key
)
&&
eq
(
a
[
key
],
b
[
key
],
aStack
,
bStack
)))
break
;
}
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
=
aStack
.
length
;
while
(
length
--
)
{
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if
(
aStack
[
length
]
==
a
)
return
bStack
[
length
]
==
b
;
}
// Objects with different constructors are not equivalent, but `Object`s
// from different frames are.
var
aCtor
=
a
.
constructor
,
bCtor
=
b
.
constructor
;
if
(
aCtor
!==
bCtor
&&
!
(
typeof
aCtor
===
'
function
'
&&
(
aCtor
instanceof
aCtor
)
&&
typeof
bCtor
===
'
function
'
&&
(
bCtor
instanceof
bCtor
)))
{
return
false
;
}
// Ensure that both objects contain the same number of properties.
if
(
result
)
{
for
(
key
in
b
)
{
if
(
hasOwnProperty
.
call
(
b
,
key
)
&&
!
(
size
--
))
break
;
// Add the first object to the stack of traversed objects.
aStack
.
push
(
a
);
bStack
.
push
(
b
);
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
--
)
{
if
(
!
(
result
=
eq
(
a
[
size
],
b
[
size
],
aStack
,
bStack
)))
break
;
}
}
}
else
{
// 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
],
aStack
,
bStack
)))
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
;
}
result
=
!
size
;
}
}
// Remove the first object from the stack of traversed objects.
aStack
.
pop
();
bStack
.
pop
();
return
result
;
};
// Remove the first object from the stack of traversed objects.
aStack
.
pop
();
bStack
.
pop
();
return
result
;
};
// Perform a deep comparison to check if two objects are equal.
utils
.
isEqual
=
function
(
a
,
b
)
{
return
eq
(
a
,
b
,
[],
[]);
};
utils
.
matchesSelector
=
(
function
()
{
// Suffix.
var
sfx
=
'
MatchesSelector
'
;
var
tag
=
document
.
createElement
(
'
div
'
);
var
name
;
[
'
matches
'
,
'
webkit
'
+
sfx
,
'
moz
'
+
sfx
,
'
ms
'
+
sfx
].
some
(
function
(
item
)
{
var
valid
=
(
item
in
tag
);
name
=
item
;
return
valid
;
});
if
(
!
name
)
{
throw
new
Error
(
'
Element#matches is not supported
'
);
}
return
function
(
element
,
selector
)
{
return
element
[
name
](
selector
)
// Perform a deep comparison to check if two objects are equal.
utils
.
isEqual
=
function
(
a
,
b
)
{
return
eq
(
a
,
b
,
[],
[]);
};
})();
// Backbone.Events
// ---------------
...
...
@@ -343,7 +310,6 @@ var Events = Backbone.Events = {
if
(
!
eventsApi
(
this
,
'
once
'
,
name
,
[
callback
,
context
])
||
!
callback
)
return
this
;
var
self
=
this
;
var
ran
;
var
once
=
function
()
{
if
(
ran
)
return
;
ran
=
true
;
...
...
@@ -362,10 +328,9 @@ var Events = Backbone.Events = {
var
retain
,
ev
,
events
,
names
,
i
,
l
,
j
,
k
;
if
(
!
this
.
_events
||
!
eventsApi
(
this
,
'
off
'
,
name
,
[
callback
,
context
]))
return
this
;
if
(
!
name
&&
!
callback
&&
!
context
)
{
this
.
_events
=
{}
;
this
.
_events
=
void
0
;
return
this
;
}
names
=
name
?
[
name
]
:
Object
.
keys
(
this
.
_events
);
for
(
i
=
0
,
l
=
names
.
length
;
i
<
l
;
i
++
)
{
name
=
names
[
i
];
...
...
@@ -459,7 +424,7 @@ var triggerEvents = function(events, args) {
case
1
:
while
(
++
i
<
l
)
(
ev
=
events
[
i
]).
callback
.
call
(
ev
.
ctx
,
a1
);
return
;
case
2
:
while
(
++
i
<
l
)
(
ev
=
events
[
i
]).
callback
.
call
(
ev
.
ctx
,
a1
,
a2
);
return
;
case
3
:
while
(
++
i
<
l
)
(
ev
=
events
[
i
]).
callback
.
call
(
ev
.
ctx
,
a1
,
a2
,
a3
);
return
;
default
:
while
(
++
i
<
l
)
(
ev
=
events
[
i
]).
callback
.
apply
(
ev
.
ctx
,
args
);
default
:
while
(
++
i
<
l
)
(
ev
=
events
[
i
]).
callback
.
apply
(
ev
.
ctx
,
args
);
return
;
}
};
...
...
@@ -483,6 +448,11 @@ Object.keys(listenMethods).forEach(function(method) {
// Aliases for backwards compatibility.
Events
.
bind
=
Events
.
on
;
Events
.
unbind
=
Events
.
off
;
// Allow the `Backbone` object to serve as a global event bus, for folks who
// want global "pubsub" in a convenient place.
_
.
extend
(
Backbone
,
Events
);
// Backbone.Model
// --------------
...
...
@@ -525,7 +495,7 @@ _.extend(Model.prototype, Events, {
// Return a copy of the model's `attributes` object.
toJSON
:
function
(
options
)
{
return
_
.
extend
(
Object
.
create
(
null
)
,
this
.
attributes
);
return
_
.
extend
(
{}
,
this
.
attributes
);
},
// Proxy `Backbone.sync` by default -- but override this if you need
...
...
@@ -600,7 +570,7 @@ _.extend(Model.prototype, Events, {
// Trigger all relevant attribute changes.
if
(
!
silent
)
{
if
(
changes
.
length
)
this
.
_pending
=
true
;
if
(
changes
.
length
)
this
.
_pending
=
options
;
for
(
var
i
=
0
,
l
=
changes
.
length
;
i
<
l
;
i
++
)
{
this
.
trigger
(
'
change:
'
+
changes
[
i
],
this
,
current
[
changes
[
i
]],
options
);
}
...
...
@@ -611,6 +581,7 @@ _.extend(Model.prototype, Events, {
if
(
changing
)
return
this
;
if
(
!
silent
)
{
while
(
this
.
_pending
)
{
options
=
this
.
_pending
;
this
.
_pending
=
false
;
this
.
trigger
(
'
change
'
,
this
,
options
);
}
...
...
@@ -637,7 +608,7 @@ _.extend(Model.prototype, Events, {
// If you specify an attribute name, determine if that attribute has changed.
hasChanged
:
function
(
attr
)
{
if
(
attr
==
null
)
return
!!
Object
.
keys
(
this
.
changed
).
length
;
return
hasOwnProperty
.
call
(
this
.
changed
,
attr
);
return
_
.
has
(
this
.
changed
,
attr
);
},
// Return an object containing all the attributes that have changed, or
...
...
@@ -778,9 +749,12 @@ _.extend(Model.prototype, Events, {
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url
:
function
()
{
var
base
=
_
.
result
(
this
,
'
urlRoot
'
)
||
_
.
result
(
this
.
collection
,
'
url
'
)
||
urlError
();
var
base
=
_
.
result
(
this
,
'
urlRoot
'
)
||
_
.
result
(
this
.
collection
,
'
url
'
)
||
urlError
();
if
(
this
.
isNew
())
return
base
;
return
base
+
(
base
.
charAt
(
base
.
length
-
1
)
===
'
/
'
?
''
:
'
/
'
)
+
encodeURIComponent
(
this
.
id
);
return
base
.
replace
(
/
([^\/])
$/
,
'
$1
/
'
)
+
encodeURIComponent
(
this
.
id
);
},
// **parse** converts a response into the hash of attributes to be `set` on
...
...
@@ -796,7 +770,7 @@ _.extend(Model.prototype, Events, {
// A model is new if it has never been saved to the server, and lacks an id.
isNew
:
function
()
{
return
this
.
id
==
null
;
return
!
this
.
has
(
this
.
idAttribute
)
;
},
// Check if the model is currently in a valid state.
...
...
@@ -822,7 +796,7 @@ if (_.keys) {
var
modelMethods
=
[
'
keys
'
,
'
values
'
,
'
pairs
'
,
'
invert
'
,
'
pick
'
,
'
omit
'
];
// Mix in each Underscore method as a proxy to `Model#attributes`.
modelMethods
.
forEach
(
function
(
method
)
{
modelMethods
.
f
ilter
(
utilExists
).
f
orEach
(
function
(
method
)
{
Model
.
prototype
[
method
]
=
function
()
{
var
args
=
slice
.
call
(
arguments
);
args
.
unshift
(
this
.
attributes
);
...
...
@@ -830,6 +804,7 @@ if (_.keys) {
};
});
}
// Backbone.Collection
// -------------------
...
...
@@ -901,7 +876,7 @@ _.extend(Collection.prototype, Events, {
options
.
index
=
index
;
model
.
trigger
(
'
remove
'
,
model
,
this
,
options
);
}
this
.
_removeReference
(
model
);
this
.
_removeReference
(
model
,
options
);
}
return
singular
?
models
[
0
]
:
models
;
},
...
...
@@ -927,11 +902,11 @@ _.extend(Collection.prototype, Events, {
// Turn bare objects into model references, and prevent invalid models
// from being added.
for
(
i
=
0
,
l
=
models
.
length
;
i
<
l
;
i
++
)
{
attrs
=
models
[
i
];
attrs
=
models
[
i
]
||
{}
;
if
(
attrs
instanceof
Model
)
{
id
=
model
=
attrs
;
}
else
{
id
=
attrs
[
targetModel
.
prototype
.
idAttribute
];
id
=
attrs
[
targetModel
.
prototype
.
idAttribute
||
'
id
'
];
}
// If a duplicate is found, prevent it from being added and
...
...
@@ -951,14 +926,13 @@ _.extend(Collection.prototype, Events, {
model
=
models
[
i
]
=
this
.
_prepareModel
(
attrs
,
options
);
if
(
!
model
)
continue
;
toAdd
.
push
(
model
);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model
.
on
(
'
all
'
,
this
.
_onModelEvent
,
this
);
this
.
_byId
[
model
.
cid
]
=
model
;
if
(
model
.
id
!=
null
)
this
.
_byId
[
model
.
id
]
=
model
;
this
.
_addReference
(
model
,
options
);
}
if
(
order
)
order
.
push
(
existing
||
model
);
// Do not add multiple models with the same `id`.
model
=
existing
||
model
;
if
(
order
&&
(
model
.
isNew
()
||
!
modelMap
[
model
.
id
]))
order
.
push
(
model
);
modelMap
[
model
.
id
]
=
true
;
}
// Remove nonexistent models if appropriate.
...
...
@@ -1008,7 +982,7 @@ _.extend(Collection.prototype, Events, {
reset
:
function
(
models
,
options
)
{
options
||
(
options
=
{});
for
(
var
i
=
0
,
l
=
this
.
models
.
length
;
i
<
l
;
i
++
)
{
this
.
_removeReference
(
this
.
models
[
i
]);
this
.
_removeReference
(
this
.
models
[
i
]
,
options
);
}
options
.
previousModels
=
this
.
models
;
this
.
_reset
();
...
...
@@ -1049,7 +1023,7 @@ _.extend(Collection.prototype, Events, {
// Get a model from the set by id.
get
:
function
(
obj
)
{
if
(
obj
==
null
)
return
void
0
;
return
this
.
_byId
[
obj
.
id
]
||
this
.
_byId
[
obj
.
cid
]
||
this
.
_byId
[
obj
];
return
this
.
_byId
[
obj
]
||
this
.
_byId
[
obj
.
id
]
||
this
.
_byId
[
obj
.
cid
];
},
// Get the model at the given index.
...
...
@@ -1127,7 +1101,7 @@ _.extend(Collection.prototype, Events, {
if
(
!
options
.
wait
)
this
.
add
(
model
,
options
);
var
collection
=
this
;
var
success
=
options
.
success
;
options
.
success
=
function
(
model
,
resp
,
options
)
{
options
.
success
=
function
(
model
,
resp
)
{
if
(
options
.
wait
)
collection
.
add
(
model
,
options
);
if
(
success
)
success
(
model
,
resp
,
options
);
};
...
...
@@ -1151,17 +1125,14 @@ _.extend(Collection.prototype, Events, {
_reset
:
function
()
{
this
.
length
=
0
;
this
.
models
=
[];
this
.
_byId
=
{}
;
this
.
_byId
=
Object
.
create
(
null
)
;
},
// Prepare a hash of attributes (or other model) to be added to this
// collection.
_prepareModel
:
function
(
attrs
,
options
)
{
if
(
attrs
instanceof
Collection
.
prototype
.
model
)
{
if
(
!
attrs
.
collection
)
attrs
.
collection
=
this
;
return
attrs
;
}
options
=
options
?
_
.
extend
({},
options
)
:
{};
if
(
attrs
instanceof
Model
)
return
attrs
;
options
=
_
.
extend
({},
options
);
options
.
collection
=
this
;
var
model
=
new
this
.
model
(
attrs
,
options
);
if
(
!
model
.
validationError
)
return
model
;
...
...
@@ -1169,8 +1140,16 @@ _.extend(Collection.prototype, Events, {
return
false
;
},
// Internal method to create a model's ties to a collection.
_addReference
:
function
(
model
,
options
)
{
this
.
_byId
[
model
.
cid
]
=
model
;
if
(
model
.
id
!=
null
)
this
.
_byId
[
model
.
id
]
=
model
;
if
(
!
model
.
collection
)
model
.
collection
=
this
;
model
.
on
(
'
all
'
,
this
.
_onModelEvent
,
this
);
},
// Internal method to sever a model's ties to a collection.
_removeReference
:
function
(
model
)
{
_removeReference
:
function
(
model
,
options
)
{
if
(
this
===
model
.
collection
)
delete
model
.
collection
;
model
.
off
(
'
all
'
,
this
.
_onModelEvent
,
this
);
},
...
...
@@ -1191,7 +1170,7 @@ _.extend(Collection.prototype, Events, {
});
if
(
_
.
each
)
{
if
(
utilExists
(
'
each
'
)
)
{
// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
...
...
@@ -1203,7 +1182,7 @@ if (_.each) {
'
lastIndexOf
'
,
'
isEmpty
'
,
'
chain
'
];
// Mix in each Underscore method as a proxy to `Collection#models`.
methods
.
forEach
(
function
(
method
)
{
methods
.
f
ilter
(
utilExists
).
f
orEach
(
function
(
method
)
{
Collection
.
prototype
[
method
]
=
function
()
{
var
args
=
slice
.
call
(
arguments
);
args
.
unshift
(
this
.
models
);
...
...
@@ -1215,7 +1194,7 @@ if (_.each) {
var
attributeMethods
=
[
'
groupBy
'
,
'
countBy
'
,
'
sortBy
'
];
// Use attributes instead of properties.
attributeMethods
.
forEach
(
function
(
method
)
{
attributeMethods
.
f
ilter
(
utilExists
).
f
orEach
(
function
(
method
)
{
Collection
.
prototype
[
method
]
=
function
(
value
,
context
)
{
var
iterator
=
typeof
value
===
'
function
'
?
value
:
function
(
model
)
{
return
model
.
get
(
value
);
...
...
@@ -1226,12 +1205,23 @@ if (_.each) {
}
else
{
[
'
forEach
'
,
'
map
'
,
'
filter
'
,
'
some
'
,
'
every
'
,
'
reduce
'
,
'
reduceRight
'
,
'
indexOf
'
,
'
lastIndexOf
'
].
forEach
(
function
(
method
)
{
var
fn
=
Array
.
prototype
[
method
];
Collection
.
prototype
[
method
]
=
function
(
arg
,
context
)
{
return
fn
.
call
(
this
.
models
,
arg
,
context
);
return
this
.
models
[
method
](
arg
,
context
);
};
});
// Exoskeleton-specific:
Collection
.
prototype
.
find
=
function
(
iterator
,
context
)
{
var
result
;
this
.
some
(
function
(
value
,
index
,
list
)
{
if
(
iterator
.
call
(
context
,
value
,
index
,
list
))
{
result
=
value
;
return
true
;
}
});
return
result
;
};
// Underscore methods that take a property name as an argument.
[
'
sortBy
'
].
forEach
(
function
(
method
)
{
Collection
.
prototype
[
method
]
=
function
(
value
,
context
)
{
...
...
@@ -1242,614 +1232,539 @@ if (_.each) {
};
});
}
// Backbone.View
// -------------
// Backbone Views are almost more convention than they are actual code. A View
// is simply a JavaScript object that represents a logical chunk of UI in the
// DOM. This might be a single item, an entire list, a sidebar or panel, or
// even the surrounding frame which wraps your whole app. Defining a chunk of
// UI as a **View** allows you to define your DOM events declaratively, without
// having to worry about render order ... and makes it easy for the view to
// react to specific changes in the state of your models.
// Options with special meaning *(e.g. model, collection, id, className)* are
// attached directly to the view. See `viewOptions` for an exhaustive
// list.
// 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
'
,
'
events
'
];
// 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
'
);
if
(
options
)
Object
.
keys
(
options
).
forEach
(
function
(
key
)
{
if
(
viewOptions
.
indexOf
(
key
)
!==
-
1
)
this
[
key
]
=
options
[
key
];
},
this
);
this
.
_handlers
=
[];
this
.
_ensureElement
();
this
.
initialize
.
apply
(
this
,
arguments
);
this
.
delegateEvents
();
};
// 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 preferred to global lookups where possible.
$
:
function
(
selector
)
{
return
Backbone
.
$
?
this
.
$el
.
find
(
selector
)
:
this
.
findAll
(
selector
);
},
// Backbone.View
// -------------
// Exoskeleton-related DOM methods.
find
:
function
(
selector
)
{
return
this
.
el
.
querySelector
(
selector
);
},
// Backbone Views are almost more convention than they are actual code. A View
// is simply a JavaScript object that represents a logical chunk of UI in the
// DOM. This might be a single item, an entire list, a sidebar or panel, or
// even the surrounding frame which wraps your whole app. Defining a chunk of
// UI as a **View** allows you to define your DOM events declaratively, without
// having to worry about render order ... and makes it easy for the view to
// react to specific changes in the state of your models.
// 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
'
);
if
(
options
)
Object
.
keys
(
options
).
forEach
(
function
(
key
)
{
if
(
viewOptions
.
indexOf
(
key
)
!==
-
1
)
this
[
key
]
=
options
[
key
];
},
this
);
this
.
_ensureElement
();
this
.
initialize
.
apply
(
this
,
arguments
);
};
findAll
:
function
(
selector
)
{
return
slice
.
call
(
this
.
el
.
querySelectorAll
(
selector
));
},
// Cached regex to split keys for `delegate`.
var
delegateEventSplitter
=
/^
(\S
+
)\s
*
(
.*
)
$/
;
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
// List of view options to be merged as properties.
var
viewOptions
=
[
'
model
'
,
'
collection
'
,
'
el
'
,
'
id
'
,
'
attributes
'
,
'
className
'
,
'
tagName
'
,
'
events
'
];
// **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
;
},
// Set up all inheritable **Backbone.View** properties and methods.
_
.
extend
(
View
.
prototype
,
Events
,
{
// Remove this view by taking the element out of the DOM, and removing any
// applicable Backbone.Events listeners.
remove
:
function
()
{
if
(
Backbone
.
$
)
{
this
.
$el
.
remove
()
}
else
if
(
this
.
el
.
parentNode
)
{
this
.
el
.
parentNode
.
removeChild
(
this
.
el
);
}
this
.
stopListening
();
return
this
;
},
// The default `tagName` of a View's element is `"div"`.
tagName
:
'
div
'
,
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement
:
function
(
element
,
delegate
)
{
if
(
Backbone
.
$
)
{
if
(
this
.
$el
)
this
.
undelegateEvents
();
this
.
$el
=
element
instanceof
Backbone
.
$
?
element
:
Backbone
.
$
(
element
);
this
.
el
=
this
.
$el
[
0
];
}
else
{
if
(
this
.
el
)
this
.
undelegateEvents
();
var
el
=
(
typeof
element
===
'
string
'
)
?
document
.
querySelector
(
element
)
:
element
;
this
.
el
=
el
;
}
if
(
delegate
!==
false
)
this
.
delegateEvents
();
return
this
;
},
// jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be preferred to global lookups where possible.
$
:
function
(
selector
)
{
return
this
.
$el
.
find
(
selector
);
},
delegate
:
function
(
eventName
,
selector
,
callback
)
{
if
(
typeof
selector
===
'
function
'
)
{
callback
=
selector
;
selector
=
null
;
}
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
if
(
typeof
callback
!==
'
function
'
)
{
throw
new
TypeError
(
'
View#delegate expects callback 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
;
},
var
root
=
this
.
el
;
var
bound
=
callback
.
bind
(
this
);
var
handler
=
selector
?
function
(
event
)
{
// if (event.target === root) {
// event.delegateTarget = el;
// return bound(event);
// }
for
(
var
el
=
event
.
target
;
el
&&
el
!==
root
;
el
=
el
.
parentNode
)
{
if
(
utils
.
matchesSelector
(
el
,
selector
))
{
// event.currentTarget or event.target are read-only.
event
.
delegateTarget
=
el
;
return
bound
(
event
);
}
// Remove this view by taking the element out of the DOM, and removing any
// applicable Backbone.Events listeners.
remove
:
function
()
{
this
.
_removeElement
();
this
.
stopListening
();
return
this
;
},
// Remove this view's element from the document and all event listeners
// attached to it. Exposed for subclasses using an alternative DOM
// manipulation API.
_removeElement
:
function
()
{
this
.
$el
.
remove
();
},
// Change the view's element (`this.el` property) and re-delegate the
// view's events on the new element.
setElement
:
function
(
element
)
{
this
.
undelegateEvents
();
this
.
_setElement
(
element
);
this
.
delegateEvents
();
return
this
;
},
// Creates the `this.el` and `this.$el` references for this view using the
// given `el` and a hash of `attributes`. `el` can be a CSS selector or an
// HTML string, a jQuery context or an element. Subclasses can override
// this to utilize an alternative DOM manipulation API and are only required
// to set the `this.el` property.
_setElement
:
function
(
el
)
{
if
(
!
Backbone
.
$
)
throw
new
Error
(
'
You must either include jQuery or override Backbone.View.prototype methods (Google Backbone.NativeView)
'
);
this
.
$el
=
el
instanceof
Backbone
.
$
?
el
:
Backbone
.
$
(
el
);
this
.
el
=
this
.
$el
[
0
];
},
// 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`.
delegateEvents
:
function
(
events
)
{
if
(
!
(
events
||
(
events
=
_
.
result
(
this
,
'
events
'
))))
return
this
;
this
.
undelegateEvents
();
for
(
var
key
in
events
)
{
var
method
=
events
[
key
];
if
(
typeof
method
!==
'
function
'
)
method
=
this
[
events
[
key
]];
// if (!method) continue;
var
match
=
key
.
match
(
delegateEventSplitter
);
this
.
delegate
(
match
[
1
],
match
[
2
],
method
.
bind
(
this
));
}
}
:
bound
;
root
.
addEventListener
(
eventName
,
handler
,
false
);
this
.
_handlers
.
push
({
eventName
:
eventName
,
selector
:
selector
,
callback
:
callback
,
handler
:
handler
});
return
handler
;
},
undelegate
:
function
(
eventName
,
selector
,
callback
)
{
if
(
typeof
selector
===
'
function
'
)
{
callback
=
selector
;
selector
=
null
;
}
var
root
=
this
.
el
;
var
handlers
=
this
.
_handlers
;
var
removeListener
=
function
(
item
)
{
root
.
removeEventListener
(
item
.
eventName
,
item
.
handler
,
false
);
};
// Remove all handlers.
if
(
!
eventName
&&
!
selector
&&
!
callback
)
{
handlers
.
forEach
(
removeListener
);
this
.
_handlers
=
[];
}
else
{
// Remove some handlers.
handlers
.
filter
(
function
(
item
)
{
return
item
.
eventName
===
eventName
&&
(
callback
?
item
.
callback
===
callback
:
true
)
&&
(
selector
?
item
.
selector
===
selector
:
true
);
})
.
forEach
(
function
(
item
)
{
removeListener
(
item
);
handlers
.
splice
(
handlers
.
indexOf
(
item
),
1
);
});
}
},
// 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
,
keepOld
)
{
if
(
!
(
events
||
(
events
=
_
.
result
(
this
,
'
events
'
))))
return
this
;
if
(
!
keepOld
)
this
.
undelegateEvents
();
for
(
var
key
in
events
)
{
var
method
=
events
[
key
];
if
(
typeof
method
!==
'
function
'
)
method
=
this
[
events
[
key
]];
// if (!method) continue;
var
match
=
key
.
match
(
delegateEventSplitter
);
var
eventName
=
match
[
1
],
selector
=
match
[
2
];
if
(
Backbone
.
$
)
{
eventName
+=
'
.delegateEvents
'
+
this
.
cid
;
method
=
method
.
bind
(
this
);
this
.
$el
.
on
(
eventName
,
(
selector
?
selector
:
null
),
method
);
return
this
;
},
// Add a single event listener to the view's element (or a child element
// using `selector`). This only works for delegate-able events: not `focus`,
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate
:
function
(
eventName
,
selector
,
listener
)
{
this
.
$el
.
on
(
eventName
+
'
.delegateEvents
'
+
this
.
cid
,
selector
,
listener
);
},
// Clears all callbacks previously bound to the view by `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
()
{
if
(
this
.
$el
)
this
.
$el
.
off
(
'
.delegateEvents
'
+
this
.
cid
);
return
this
;
},
// A finer-grained `undelegateEvents` for removing a single delegated event.
// `selector` and `listener` are both optional.
undelegate
:
function
(
eventName
,
selector
,
listener
)
{
this
.
$el
.
off
(
eventName
+
'
.delegateEvents
'
+
this
.
cid
,
selector
,
listener
);
},
// Produces a DOM element to be assigned to your view. Exposed for
// subclasses using an alternative DOM manipulation API.
_createElement
:
function
(
tagName
)
{
return
document
.
createElement
(
tagName
);
},
// 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
=
_
.
extend
({},
_
.
result
(
this
,
'
attributes
'
));
if
(
this
.
id
)
attrs
.
id
=
_
.
result
(
this
,
'
id
'
);
if
(
this
.
className
)
attrs
[
'
class
'
]
=
_
.
result
(
this
,
'
className
'
);
this
.
setElement
(
this
.
_createElement
(
_
.
result
(
this
,
'
tagName
'
)));
this
.
_setAttributes
(
attrs
);
}
else
{
this
.
delegate
(
eventName
,
selector
,
method
)
this
.
setElement
(
_
.
result
(
this
,
'
el
'
));
}
}
return
this
;
},
// 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
()
{
if
(
Backbone
.
$
)
{
this
.
$el
.
off
(
'
.delegateEvents
'
+
this
.
cid
);
}
else
{
this
.
undelegate
();
}
return
this
;
},
},
// 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
=
_
.
extend
({},
_
.
result
(
this
,
'
attributes
'
));
if
(
this
.
id
)
attrs
.
id
=
_
.
result
(
this
,
'
id
'
);
if
(
this
.
className
)
attrs
.
className
=
_
.
result
(
this
,
'
className
'
);
if
(
attrs
[
'
class
'
])
attrs
.
className
=
attrs
[
'
class
'
];
var
el
=
_
.
extend
(
document
.
createElement
(
_
.
result
(
this
,
'
tagName
'
)),
attrs
);
this
.
setElement
(
el
,
false
);
}
else
{
this
.
setElement
(
_
.
result
(
this
,
'
el
'
),
false
);
// Set attributes from a hash on this view's element. Exposed for
// subclasses using an alternative DOM manipulation API.
_setAttributes
:
function
(
attributes
)
{
this
.
$el
.
attr
(
attributes
);
}
}
});
// Backbone.sync
// -------------
// 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.
_
.
defaults
(
options
||
(
options
=
{}),
{
emulateHTTP
:
Backbone
.
emulateHTTP
,
emulateJSON
:
Backbone
.
emulateJSON
});
// Backbone.sync
// -------------
// Default JSON-request options.
var
params
=
{
type
:
type
,
dataType
:
'
json
'
};
// Ensure that we have a URL.
if
(
!
options
.
url
)
{
params
.
url
=
_
.
result
(
model
,
'
url
'
)
||
urlError
();
}
// Ensure that we have the appropriate request data.
if
(
options
.
data
==
null
&&
model
&&
(
method
===
'
create
'
||
method
===
'
update
'
||
method
===
'
patch
'
))
{
params
.
contentType
=
'
application/json
'
;
params
.
data
=
JSON
.
stringify
(
options
.
attrs
||
model
.
toJSON
(
options
));
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if
(
options
.
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
(
options
.
emulateHTTP
&&
(
type
===
'
PUT
'
||
type
===
'
DELETE
'
||
type
===
'
PATCH
'
))
{
params
.
type
=
'
POST
'
;
if
(
options
.
emulateJSON
)
params
.
data
.
_method
=
type
;
var
beforeSend
=
options
.
beforeSend
;
options
.
beforeSend
=
function
(
xhr
)
{
xhr
.
setRequestHeader
(
'
X-HTTP-Method-Override
'
,
type
);
if
(
beforeSend
)
return
beforeSend
.
apply
(
this
,
arguments
);
};
}
// Don't process data on a non-GET request.
if
(
params
.
type
!==
'
GET
'
&&
!
options
.
emulateJSON
)
{
params
.
processData
=
false
;
}
// Make the request, allowing the user to override any Ajax options.
var
xhr
=
options
.
xhr
=
Backbone
.
ajax
(
_
.
extend
(
params
,
options
));
model
.
trigger
(
'
request
'
,
model
,
xhr
,
options
);
return
xhr
;
};
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var
methodMap
=
{
'
create
'
:
'
POST
'
,
'
update
'
:
'
PUT
'
,
'
patch
'
:
'
PATCH
'
,
'
delete
'
:
'
DELETE
'
,
'
read
'
:
'
GET
'
};
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
// Override this if you'd like to use a different library.
Backbone
.
ajax
=
function
()
{
return
Backbone
.
$
.
ajax
.
apply
(
Backbone
.
$
,
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
optionalParam
=
/
\((
.*
?)\)
/g
;
var
namedParam
=
/
(\(\?)?
:
\w
+/g
;
var
splatParam
=
/
\*\w
+/g
;
var
escapeRegExp
=
/
[\-
{}
\[\]
+?.,
\\\^
$|#
\s]
/g
;
var
isRegExp
=
function
(
value
)
{
return
value
?
(
typeof
value
===
'
object
'
&&
toString
.
call
(
value
)
===
'
[object RegExp]
'
)
:
false
;
};
// 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.
Backbone
.
sync
=
function
(
method
,
model
,
options
)
{
options
||
(
options
=
{})
// Set up all inheritable **Backbone.Router** properties and methods.
_
.
extend
(
Router
.
prototype
,
Events
,
{
var
type
=
methodMap
[
method
];
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize
:
function
(){},
// Default JSON-request options.
var
params
=
{
type
:
type
,
dataType
:
'
json
'
};
// 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
)
{
if
(
!
isRegExp
(
route
))
route
=
this
.
_routeToRegExp
(
route
);
if
(
typeof
name
===
'
function
'
)
{
callback
=
name
;
name
=
''
;
// Ensure that we have a URL.
if
(
!
options
.
url
)
{
params
.
url
=
_
.
result
(
model
,
'
url
'
)
||
urlError
();
}
if
(
!
callback
)
callback
=
this
[
name
];
var
router
=
this
;
Backbone
.
history
.
route
(
route
,
function
(
fragment
)
{
var
args
=
router
.
_extractParameters
(
route
,
fragment
);
callback
&&
callback
.
apply
(
router
,
args
);
router
.
trigger
.
apply
(
router
,
[
'
route:
'
+
name
].
concat
(
args
));
router
.
trigger
(
'
route
'
,
name
,
args
);
Backbone
.
history
.
trigger
(
'
route
'
,
router
,
name
,
args
);
});
return
this
;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate
:
function
(
fragment
,
options
)
{
Backbone
.
history
.
navigate
(
fragment
,
options
);
return
this
;
},
// 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
;
this
.
routes
=
_
.
result
(
this
,
'
routes
'
);
var
route
,
routes
=
Object
.
keys
(
this
.
routes
);
while
((
route
=
routes
.
pop
())
!=
null
)
{
this
.
route
(
route
,
this
.
routes
[
route
]);
// Ensure that we have the appropriate request data.
if
(
options
.
data
==
null
&&
model
&&
(
method
===
'
create
'
||
method
===
'
update
'
||
method
===
'
patch
'
))
{
params
.
contentType
=
'
application/json
'
;
params
.
data
=
JSON
.
stringify
(
options
.
attrs
||
model
.
toJSON
(
options
));
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp
:
function
(
route
)
{
route
=
route
.
replace
(
escapeRegExp
,
'
\\
$&
'
)
.
replace
(
optionalParam
,
'
(?:$1)?
'
)
.
replace
(
namedParam
,
function
(
match
,
optional
)
{
return
optional
?
match
:
'
([^
\
/]+)
'
;
})
.
replace
(
splatParam
,
'
(.*?)
'
);
return
new
RegExp
(
'
^
'
+
route
+
'
$
'
);
},
// Don't process data on a non-GET request.
if
(
params
.
type
!==
'
GET
'
)
{
params
.
processData
=
false
;
}
// Given a route, and a URL fragment that it matches, return the array of
// extracted decoded parameters. Empty or unmatched parameters will be
// treated as `null` to normalize cross-browser behavior.
_extractParameters
:
function
(
route
,
fragment
)
{
var
params
=
route
.
exec
(
fragment
).
slice
(
1
);
return
params
.
map
(
function
(
param
)
{
return
param
?
decodeURIComponent
(
param
)
:
null
;
});
}
// Make the request, allowing the user to override any Ajax options.
var
xhr
=
options
.
xhr
=
Backbone
.
ajax
(
_
.
extend
(
params
,
options
));
model
.
trigger
(
'
request
'
,
model
,
xhr
,
options
);
return
xhr
;
};
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on either
// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
// and URL fragments.
var
History
=
Backbone
.
History
=
function
()
{
this
.
handlers
=
[];
this
.
checkUrl
=
this
.
checkUrl
.
bind
(
this
);
// Ensure that `History` can be used outside of the browser.
if
(
typeof
window
!==
'
undefined
'
)
{
this
.
location
=
window
.
location
;
this
.
history
=
window
.
history
;
}
};
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var
methodMap
=
{
'
create
'
:
'
POST
'
,
'
update
'
:
'
PUT
'
,
'
patch
'
:
'
PATCH
'
,
'
delete
'
:
'
DELETE
'
,
'
read
'
:
'
GET
'
};
// Cached regex for stripping a leading hash/slash and trailing space.
var
routeStripper
=
/^
[
#
\/]
|
\s
+$/g
;
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
// Override this if you'd like to use a different library.
Backbone
.
ajax
=
function
()
{
if
(
!
Backbone
.
$
)
throw
new
Error
(
'
You must either include jQuery or override Backbone.ajax (Google Backbone.NativeAjax)
'
);
return
Backbone
.
$
.
ajax
.
apply
(
Backbone
.
$
,
arguments
);
};
// Cached regex for stripping leading and trailing slashes.
var
rootStripper
=
/^
\/
+|
\/
+$/g
;
// Backbone.Router
// ---------------
// Cached regex for removing a trailing slash.
var
trailingSlash
=
/
\/
$/
;
// 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 regex for stripping urls of hash and query.
var
pathStripper
=
/
[
#
]
.*$/
;
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var
optionalParam
=
/
\((
.*
?)\)
/g
;
var
namedParam
=
/
(\(\?)?
:
\w
+/g
;
var
splatParam
=
/
\*\w
+/g
;
var
escapeRegExp
=
/
[\-
{}
\[\]
+?.,
\\\^
$|#
\s]
/g
;
// Has the history handling already been started?
History
.
started
=
false
;
var
isRegExp
=
function
(
value
)
{
return
value
?
(
typeof
value
===
'
object
'
&&
toString
.
call
(
value
)
===
'
[object RegExp]
'
)
:
false
;
};
// Set up all inheritable **Backbone.History** properties and methods.
_
.
extend
(
History
.
prototype
,
Events
,
{
// 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
)
{
if
(
!
isRegExp
(
route
))
route
=
this
.
_routeToRegExp
(
route
);
if
(
typeof
name
===
'
function
'
)
{
callback
=
name
;
name
=
''
;
}
if
(
!
callback
)
callback
=
this
[
name
];
var
router
=
this
;
Backbone
.
history
.
route
(
route
,
function
(
fragment
)
{
var
args
=
router
.
_extractParameters
(
route
,
fragment
);
router
.
execute
(
callback
,
args
);
router
.
trigger
.
apply
(
router
,
[
'
route:
'
+
name
].
concat
(
args
));
router
.
trigger
(
'
route
'
,
name
,
args
);
Backbone
.
history
.
trigger
(
'
route
'
,
router
,
name
,
args
);
});
return
this
;
},
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash
:
function
(
window
)
{
var
match
=
(
window
||
this
).
location
.
href
.
match
(
/#
(
.*
)
$/
);
return
match
?
match
[
1
]
:
''
;
},
// Execute a route handler with the provided parameters. This is an
// excellent place to do pre-route setup or post-route cleanup.
execute
:
function
(
callback
,
args
)
{
if
(
callback
)
callback
.
apply
(
this
,
args
);
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment
:
function
(
fragment
)
{
if
(
fragment
==
null
)
{
if
(
this
.
_wantsPushState
||
!
this
.
_wantsHashChange
)
{
// CHANGED: Make fragment include query string.
fragment
=
this
.
location
.
pathname
+
this
.
location
.
search
;
var
root
=
this
.
root
.
replace
(
trailingSlash
,
''
);
if
(
!
fragment
.
indexOf
(
root
))
fragment
=
fragment
.
slice
(
root
.
length
);
}
else
{
fragment
=
this
.
getHash
();
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate
:
function
(
fragment
,
options
)
{
Backbone
.
history
.
navigate
(
fragment
,
options
);
return
this
;
},
// 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
;
this
.
routes
=
_
.
result
(
this
,
'
routes
'
);
var
route
,
routes
=
Object
.
keys
(
this
.
routes
);
while
((
route
=
routes
.
pop
())
!=
null
)
{
this
.
route
(
route
,
this
.
routes
[
route
]);
}
}
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.
// Is pushState desired or should we use hashchange only?
this
.
options
=
_
.
extend
({
root
:
'
/
'
},
this
.
options
,
options
);
this
.
root
=
this
.
options
.
root
;
this
.
_wantsHashChange
=
this
.
options
.
hashChange
!==
false
;
this
.
_wantsPushState
=
!!
this
.
options
.
pushState
;
var
fragment
=
this
.
getFragment
();
// Normalize root to always include a leading and trailing slash.
this
.
root
=
(
'
/
'
+
this
.
root
+
'
/
'
).
replace
(
rootStripper
,
'
/
'
);
// Depending on whether we're using pushState or hashes, determine how we
// check the URL state.
if
(
this
.
_wantsPushState
)
{
window
.
addEventListener
(
'
popstate
'
,
this
.
checkUrl
,
false
);
}
else
if
(
this
.
_wantsHashChange
)
{
window
.
addEventListener
(
'
hashchange
'
,
this
.
checkUrl
,
false
);
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp
:
function
(
route
)
{
route
=
route
.
replace
(
escapeRegExp
,
'
\\
$&
'
)
.
replace
(
optionalParam
,
'
(?:$1)?
'
)
.
replace
(
namedParam
,
function
(
match
,
optional
)
{
return
optional
?
match
:
'
([^/?]+)
'
;
})
.
replace
(
splatParam
,
'
([^?]*?)
'
);
return
new
RegExp
(
'
^
'
+
route
+
'
(?:
\\
?([
\\
s
\\
S]*))?$
'
);
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted decoded parameters. Empty or unmatched parameters will be
// treated as `null` to normalize cross-browser behavior.
_extractParameters
:
function
(
route
,
fragment
)
{
var
params
=
route
.
exec
(
fragment
).
slice
(
1
);
return
params
.
map
(
function
(
param
,
i
)
{
// Don't decode the search params.
if
(
i
===
params
.
length
-
1
)
return
param
||
null
;
return
param
?
decodeURIComponent
(
param
)
:
null
;
});
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this
.
fragment
=
fragment
;
var
loc
=
this
.
location
;
var
atRoot
=
loc
.
pathname
.
replace
(
/
[^\/]
$/
,
'
$&/
'
)
===
this
.
root
;
// Transition from hashChange to pushState or vice versa if both are
// requested.
if
(
this
.
_wantsHashChange
&&
this
.
_wantsPushState
)
{
// If we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
if
(
atRoot
&&
loc
.
hash
)
{
this
.
fragment
=
this
.
getHash
().
replace
(
routeStripper
,
''
);
// CHANGED: It's no longer needed to add loc.search at the end,
// as query params have been already included into @fragment
this
.
history
.
replaceState
({},
document
.
title
,
this
.
root
+
this
.
fragment
);
}
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on either
// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
// and URL fragments.
var
History
=
Backbone
.
History
=
function
()
{
this
.
handlers
=
[];
this
.
checkUrl
=
this
.
checkUrl
.
bind
(
this
);
// Ensure that `History` can be used outside of the browser.
if
(
typeof
window
!==
'
undefined
'
)
{
this
.
location
=
window
.
location
;
this
.
history
=
window
.
history
;
}
};
if
(
!
this
.
options
.
silent
)
return
this
.
loadUrl
();
},
// Cached regex for stripping a leading hash/slash and trailing space.
var
routeStripper
=
/^
[
#
\/]
|
\s
+$/g
;
// Cached regex for stripping leading and trailing slashes.
var
rootStripper
=
/^
\/
+|
\/
+$/g
;
// Cached regex for removing a trailing slash.
var
trailingSlash
=
/
\/
$/
;
// Cached regex for stripping urls of hash and query.
var
pathStripper
=
/
[
#
]
.*$/
;
// Has the history handling already been started?
History
.
started
=
false
;
// Set up all inheritable **Backbone.History** properties and methods.
_
.
extend
(
History
.
prototype
,
Events
,
{
// Are we at the app root?
atRoot
:
function
()
{
return
this
.
location
.
pathname
.
replace
(
/
[^\/]
$/
,
'
$&/
'
)
===
this
.
root
;
},
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash
:
function
(
window
)
{
var
match
=
(
window
||
this
).
location
.
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
.
_wantsPushState
||
!
this
.
_wantsHashChange
)
{
fragment
=
decodeURI
(
this
.
location
.
pathname
+
this
.
location
.
search
);
var
root
=
this
.
root
.
replace
(
trailingSlash
,
''
);
if
(
!
fragment
.
indexOf
(
root
))
fragment
=
fragment
.
slice
(
root
.
length
);
}
else
{
fragment
=
this
.
getHash
();
}
}
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.
// Is pushState desired or should we use hashchange only?
this
.
options
=
_
.
extend
({
root
:
'
/
'
},
this
.
options
,
options
);
this
.
root
=
this
.
options
.
root
;
this
.
_wantsHashChange
=
this
.
options
.
hashChange
!==
false
;
this
.
_wantsPushState
=
!!
this
.
options
.
pushState
;
var
fragment
=
this
.
getFragment
();
// Normalize root to always include a leading and trailing slash.
this
.
root
=
(
'
/
'
+
this
.
root
+
'
/
'
).
replace
(
rootStripper
,
'
/
'
);
// Depending on whether we're using pushState or hashes, determine how we
// check the URL state.
if
(
this
.
_wantsPushState
)
{
window
.
addEventListener
(
'
popstate
'
,
this
.
checkUrl
,
false
);
}
else
if
(
this
.
_wantsHashChange
)
{
window
.
addEventListener
(
'
hashchange
'
,
this
.
checkUrl
,
false
);
}
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop
:
function
()
{
window
.
removeEventListener
(
'
popstate
'
,
this
.
checkUrl
);
window
.
removeEventListener
(
'
hashchange
'
,
this
.
checkUrl
);
History
.
started
=
false
;
},
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this
.
fragment
=
fragment
;
var
loc
=
this
.
location
;
// 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
});
},
// Transition from hashChange to pushState or vice versa if both are
// requested.
if
(
this
.
_wantsHashChange
&&
this
.
_wantsPushState
)
{
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`.
checkUrl
:
function
(
e
)
{
var
current
=
this
.
getFragment
();
if
(
current
===
this
.
fragment
)
return
false
;
this
.
loadUrl
();
},
// If we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
if
(
this
.
atRoot
()
&&
loc
.
hash
)
{
this
.
fragment
=
this
.
getHash
().
replace
(
routeStripper
,
''
);
this
.
history
.
replaceState
({},
document
.
title
,
this
.
root
+
this
.
fragment
);
}
// 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
(
fragment
)
{
fragment
=
this
.
fragment
=
this
.
getFragment
(
fragment
);
return
this
.
handlers
.
some
(
function
(
handler
)
{
if
(
handler
.
route
.
test
(
fragment
))
{
handler
.
callback
(
fragment
);
return
true
;
}
});
},
// 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
url
=
this
.
root
+
(
fragment
=
this
.
getFragment
(
fragment
||
''
));
// Strip the fragment of the query and hash for matching.
fragment
=
fragment
.
replace
(
pathStripper
,
''
);
if
(
this
.
fragment
===
fragment
)
return
;
this
.
fragment
=
fragment
;
// Don't include a trailing slash on the root.
if
(
fragment
===
''
&&
url
!==
'
/
'
)
url
=
url
.
slice
(
0
,
-
1
);
// If we're using pushState we use it to set the fragment as a real URL.
if
(
this
.
_wantsPushState
)
{
this
.
history
[
options
.
replace
?
'
replaceState
'
:
'
pushState
'
]({},
document
.
title
,
url
);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
}
else
if
(
this
.
_wantsHashChange
)
{
this
.
_updateHash
(
this
.
location
,
fragment
,
options
.
replace
);
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
}
else
{
return
this
.
location
.
assign
(
url
);
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
.
removeEventListener
(
'
popstate
'
,
this
.
checkUrl
);
window
.
removeEventListener
(
'
hashchange
'
,
this
.
checkUrl
);
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`.
checkUrl
:
function
()
{
var
current
=
this
.
getFragment
();
if
(
current
===
this
.
fragment
)
return
false
;
this
.
loadUrl
();
},
// 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
(
fragment
)
{
fragment
=
this
.
fragment
=
this
.
getFragment
(
fragment
);
return
this
.
handlers
.
some
(
function
(
handler
)
{
if
(
handler
.
route
.
test
(
fragment
))
{
handler
.
callback
(
fragment
);
return
true
;
}
});
},
// 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
url
=
this
.
root
+
(
fragment
=
this
.
getFragment
(
fragment
||
''
));
// Strip the hash for matching.
fragment
=
fragment
.
replace
(
pathStripper
,
''
);
if
(
this
.
fragment
===
fragment
)
return
;
this
.
fragment
=
fragment
;
// Don't include a trailing slash on the root.
if
(
fragment
===
''
&&
url
!==
'
/
'
)
url
=
url
.
slice
(
0
,
-
1
);
// If we're using pushState we use it to set the fragment as a real URL.
if
(
this
.
_wantsPushState
)
{
this
.
history
[
options
.
replace
?
'
replaceState
'
:
'
pushState
'
]({},
document
.
title
,
url
);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
}
else
if
(
this
.
_wantsHashChange
)
{
this
.
_updateHash
(
this
.
location
,
fragment
,
options
.
replace
);
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
}
else
{
return
this
.
location
.
assign
(
url
);
}
if
(
options
.
trigger
)
return
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
)
{
var
href
=
location
.
href
.
replace
(
/
(
javascript:|#
)
.*$/
,
''
);
location
.
replace
(
href
+
'
#
'
+
fragment
);
}
else
{
// Some browsers require that `hash` contains a leading #.
location
.
hash
=
'
#
'
+
fragment
;
}
}
if
(
options
.
trigger
)
return
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
)
{
var
href
=
location
.
href
.
replace
(
/
(
javascript:|#
)
.*$/
,
''
);
location
.
replace
(
href
+
'
#
'
+
fragment
);
}
else
{
// Some browsers require that `hash` contains a leading #.
location
.
hash
=
'
#
'
+
fragment
;
}
}
});
});
// !!!
// Init.
Model
.
extend
=
Collection
.
extend
=
Router
.
extend
=
View
.
extend
=
History
.
extend
=
Backbone
.
extend
;
[
'
Model
'
,
'
Collection
'
,
'
Router
'
,
'
View
'
,
'
History
'
].
forEach
(
function
(
name
)
{
var
item
=
Backbone
[
name
];
if
(
item
)
item
.
extend
=
Backbone
.
extend
;
});
// Allow the `Backbone` object to serve as a global event bus, for folks who
// want global "pubsub" in a convenient place.
_
.
extend
(
Backbone
,
Events
);
// Create the default Backbone.history.
Backbone
.
history
=
new
History
;
// Create the default Backbone.history
if the History module is included
.
if
(
History
)
Backbone
.
history
=
new
History
()
;
return
Backbone
;
});
examples/exoskeleton/package.json
View file @
28b669e4
{
"private"
:
true
,
"dependencies"
:
{
"exoskeleton"
:
"^0.
3
.0"
,
"exoskeleton"
:
"^0.
7
.0"
,
"backbone.localstorage"
:
"^1.1.16"
,
"backbone.nativeview"
:
"^0.3.2"
,
"todomvc-app-css"
:
"^1.0.0"
,
"todomvc-common"
:
"^1.0.1"
,
"microtemplates"
:
"^0.1.0"
...
...
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