Showing preview only (1,553K chars total). Download the full file or copy to clipboard to get everything.
Repository: jashkenas/backbone
Branch: master
Commit: e8bc45acb0a8
Files: 58
Total size: 1.5 MB
Directory structure:
gitextract_aenff3om/
├── .editorconfig
├── .eslintrc
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── Bugs.yml
│ │ ├── Documentation.yml
│ │ ├── Features.yml
│ │ └── config.yml
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── CNAME
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── backbone-min.js
├── backbone.js
├── bower.json
├── debug-info.js
├── docs/
│ ├── backbone.html
│ ├── backbone.localStorage.html
│ ├── docco.css
│ ├── examples/
│ │ ├── backbone.localStorage.html
│ │ └── todos/
│ │ └── todos.html
│ ├── js/
│ │ └── jquery.lazyload.js
│ ├── public/
│ │ └── stylesheets/
│ │ └── normalize.css
│ ├── search.js
│ └── todos.html
├── examples/
│ ├── backbone.localStorage.js
│ └── todos/
│ ├── index.html
│ ├── todos.css
│ └── todos.js
├── index.html
├── karma.conf-sauce.js
├── karma.conf.js
├── modules/
│ ├── .eslintrc
│ ├── debug-info.js
│ └── package.json
├── package.json
└── test/
├── .eslintrc
├── collection.js
├── debuginfo.js
├── events.js
├── index.html
├── model.coffee
├── model.js
├── noconflict.js
├── router.js
├── setup/
│ ├── dom-setup.js
│ └── environment.js
├── sync.js
├── vendor/
│ ├── jquery.js
│ ├── json2.js
│ ├── qunit.css
│ ├── qunit.js
│ ├── require.js
│ └── underscore.js
└── view.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[**/package.json]
indent_size = 2
================================================
FILE: .eslintrc
================================================
{
"env": {
"browser": true,
"node": true,
"amd": true
},
"globals": {
"attachEvent": false,
"detachEvent": false
},
"rules": {
"array-bracket-spacing": 2,
"block-scoped-var": 2,
"brace-style": [1, "1tbs", {"allowSingleLine": true}],
"camelcase": 2,
"comma-dangle": [2, "never"],
"comma-spacing": 2,
"computed-property-spacing": [2, "never"],
"dot-notation": [2, { "allowKeywords": false }],
"eol-last": 2,
"eqeqeq": [2, "smart"],
"indent": [2, 2, {
"MemberExpression": 0,
"SwitchCase": 1,
"VariableDeclarator": 2
}],
"key-spacing": 1,
"keyword-spacing": [2, { "after": true }],
"linebreak-style": 2,
"max-depth": [1, 4],
"max-params": [1, 5],
"new-cap": [2, {"newIsCapExceptions": ["model"]}],
"no-alert": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-console": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 1,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 1,
"no-empty-character-class": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 1,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inner-declarations": 2,
"no-irregular-whitespace": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-negated-in-lhs": 1,
"no-new-object": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-shadow": 2,
"no-spaced-func": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": [2, {"allowTernary": true, "allowShortCircuit": true}],
"no-with": 2,
"object-curly-spacing": [2, "never"],
"quote-props": [1, "consistent-as-needed", {"keywords": true}],
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": 2,
"space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"use-isnan": 2,
"valid-typeof": 2,
"wrap-iife": [2, "inside"]
}
}
================================================
FILE: .github/FUNDING.yml
================================================
tidelift: "npm/backbone"
patreon: juliangonggrijp
github: [jgonggrijp]
================================================
FILE: .github/ISSUE_TEMPLATE/Bugs.yml
================================================
name: Bug report
description: |
Report something that is not working correctly.
Not intended for security issues!
title: Foo.bar should bazoonite, but frobulates instead
body:
- type: markdown
attributes:
value: "
Thank you for taking the effort to report a bug.\n\n
Is your bug a security issue? In that case, **please do not use this
form!** Instead, see the [security
policy](https://github.com/jashkenas/backbone/security/policy) on how to
report the issue.\n\n
## Identification\n\n
To start, some quick questions to pinpoint the issue."
- type: input
id: component
attributes:
label: Affected component
description: >
Which part of Backbone is affected? Please be as specific as possible,
for example “the silent option of Collection.reset” or
“importing Backbone with require.js”.
placeholder: the sync event triggered after Model.fetch
validations:
required: true
- type: input
id: expected
attributes:
label: Expected behavior
description: |
In one sentence, what *should* the affected component do?
placeholder: |
Forward all options passed to Model.fetch to the event handler
validations:
required: true
- type: input
id: actual
attributes:
label: Actual behavior
description: |
In one sentence, what does the affected component *actually* do?
placeholder: |
Forward options to the method called last, e.g. save.
validations:
required: true
- type: markdown
attributes:
value: "
After filling the above three fields, please review the issue title. It
should be short, including elements of all three fields and not much
else.\n\n
For example: **After Model.fetch, sync event may include
options of a later sync, save or destroy call**\n\n
## Context"
- type: textarea
id: docs
attributes:
label: Relevant documentation
description: |
Which documentation, if any, did you base your above expectation on?
Provide one link per line.
placeholder: |
- https://backbonejs.org/#Model-fetch
- https://backbonejs.org/#Events-catalog
- type: textarea
id: stack
attributes:
label: Software stack
description: "
With which version(s) of Backbone, Underscore/Lodash, jQuery/Zepto,
other relevant libraries or tools, your browser, etcetera, did you
experience this problem? Please list one per line, including name,
version number(s) and variant(s) if applicable.\n\n
**Tip:** if you are using the bleeding-edge version of
Backbone, much of this information can be obtained by using
[debugInfo](https://backbonejs.org/#Utility-Backbone-debugInfo)
and copy-pasting its console output below."
placeholder: "
- Backbone 1.4.1 and latest `master` (commit fcf5df6)\n
- Underscore 1.13.6\n
- jQuery 3.6.3 (slim build)\n
- Marionette 4.1.2\n
- Firefox 100\n
- Node.js 14.6\n
OR (stretch form field to see example content):\n
```json\n
Backbone debug info: {\n
\ \ \"backbone\": \"1.5.0\",\n
\ \ \"distribution\": \"MARK_DEVELOPMENT\",\n
\ \ \"_\": \"lodash 4.17.21\",\n
\ \ \"$\": \"3.6.0\",\n
\ \ \"navigator\": {\n
\ \ \ \ \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/116.0\",\n
\ \ \ \ \"platform\": \"MacIntel\",\n
\ \ \ \ \"webdriver\": false\n
\ \ }\n
}\n
```\n
- Backbone `master` checked out on August 10, 2023\n
- Marionette 4.1.2"
validations:
required: true
- type: textarea
id: discourse
attributes:
label: Related issues, prior discussion and CCs
description: >
Please list any issue numbers, pull requests or links to discussions
elsewhere on the internet that may be relevant. You can also attract the
attention of other GitHub users by listing their `@handles` here.
placeholder: "
#4229, #3410\n
a Stack Overflow or Matrix link\n
@jgonggrijp"
- type: markdown
attributes:
value: "## Bug details"
- type: input
id: error
attributes:
label: Error
description: >
If possible, name the error that you observed and that anyone trying to
reproduce the bug should look for.
placeholder: TypeError (options.success is not an object)
- type: textarea
id: repro
attributes:
label: Steps to reproduce
description: >
List the minimal steps needed to make the bug happen. Include code
examples as needed.
validations:
required: true
- type: textarea
id: details
attributes:
label: Additional information
description: >-
This is a free-form field where you can add any further details that may
help to understand the bug. For example, you might provide permalinks to
the affected lines of code in your actual project, attach logs or
screenshots, point out things you noticed while debugging, and explain
why the bug is especially problematic for your use case.
- type: markdown
attributes:
value: "## Closing"
- type: textarea
id: solution
attributes:
label: Suggested solution(s)
description: >
If you have any idea on how the problem could (or should) be solved,
please feel welcome to describe it here. Of course, if your idea is very
concrete, you may as well submit a pull request!
- type: textarea
id: remarks
attributes:
label: Other remarks
description: >-
If there is anything else you would like to say about the issue, you can
do so here.
================================================
FILE: .github/ISSUE_TEMPLATE/Documentation.yml
================================================
name: Documentation issue
description: >-
Report information that is missing, incomplete, vague, misleading or plain
wrong in online documentation such as the website, the README, the wiki,
etcetera.
body:
- type: markdown
attributes:
value: >-
Thank you for taking the time to help us improve the documentation.
- type: input
id: reference
attributes:
label: Reference to current documentation
description: >-
If you can identify an existing piece of documentation that is lacking,
please provide a URL (or multiple) below. If your report is about
missing information and there is no single obvious place where it should
be added, you can leave this empty.
placeholder: "https://backbonejs.org/#Model-changedAttributes"
- type: textarea
id: quote
attributes:
label: Quote of current documentation
description: >-
If you provided a URL in the previous field, please quote the
problematic documentation below. This ensures that future readers
understand what you were responding to, in case the referred page
disappears or changes content.
placeholder: "
> Retrieve a hash of only the model's attributes that have changed since
the last
[set](https://backbonejs.org/#Model-set), or `false` if there are none.
Optionally, an external **attributes** hash can be passed in, returning
the attributes in that hash which differ from the model. This can be
used to figure out which portions of a view should be updated, or what
calls need to be made to sync the changes to the server."
- type: textarea
id: effect
attributes:
label: Effect of the problem
description: >-
What did you or someone else not know, misunderstand or falsely believe
due to the current state of the documentation? How has this misguided
your or someone else's behavior?
placeholder: >-
I did not know that I could ..., so I needlessly ...
validations:
required: true
- type: textarea
id: cause
attributes:
label: Cause of the problem
description: >-
What is it about the current documentation that caused your problem?
What is missing, ambiguous or wrong? Pinpoint specific words or phrases
if possible.
placeholder: >-
It says "...", which seems to suggest that ..., while actually, ...
validations:
required: true
- type: textarea
id: suggestion
attributes:
label: Suggestion
description: >-
If you have any idea on how the documentation could be improved, please
share it here. Of course, if your idea is very concrete, you can also
submit a pull request!
- type: textarea
id: remarks
attributes:
label: Other remarks
description: >-
If there is anything else you would like to say about the issue, you can
do so here.
================================================
FILE: .github/ISSUE_TEMPLATE/Features.yml
================================================
name: Feature request
description: >-
Tell us about functionality that you miss in Backbone.
body:
- type: markdown
attributes:
value: |
Thank you for proposing a new feature.
Let us begin with the end in mind.
- type: textarea
id: goal
attributes:
label: Ultimate goal
description: >-
This first question is not about Backbone but about the mission that you
hope to accomplish with Backbone. What work do you need to get done?
placeholder: |
GOOD: I work on an application that needs to ...
BAD (later question): I think Backbone.Collection should have a method that ...
validations:
required: true
- type: textarea
id: shortcoming
attributes:
label: Shortcomings
description: >-
Working towards your end goal, what task is currently difficult to
achieve with Backbone as it is? Which features in Backbone or other
libraries are currently available to you, which do not quite do what you
need?
placeholder: |
In my application, I need to ..., but when I define a
Backbone.Collection subclass, there does not seem to be any way to ...
- I could use `Collection.slice`, but ...
- I could use <some other library>, but ...
validations:
required: true
- type: textarea
id: justification
attributes:
label: Justification
description: >-
Why do you believe that the missing functionality belongs in Backbone
proper? Why could or should it not be provided by another library or
tool?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposal
description: >-
Go ahead, describe your ideal solution. Show what it should look like
and explain how it should behave.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Possible alternatives
description: >-
Can you think of other ways in which your desired functionality could be
provided? If they appeal less to you, why is this the case?
- type: textarea
id: remarks
attributes:
label: Other remarks
description: >-
If there is anything else you would like to say about your feature
request, you can do so here.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
contact_links:
- name: Security issue
url: https://github.com/jashkenas/backbone/security/policy
about: Go here if you would like to report a security issue.
- name: Stack Overflow
url: https://stackoverflow.com/questions/tagged/backbone.js
about: The best place to get help making your code work (be sure to include the `backbone.js` tag).
- name: Matrix/Gitter
url: https://matrix.to/#/#jashkenas_backbone:gitter.im
about: Discussions about, and general help with, Backbone.
================================================
FILE: .github/workflows/tests.yml
================================================
name: Test
on:
push:
branches:
- master
pull_request:
branches:
- master
paths:
- '.github/workflows/**'
- 'test/**'
- '*.js'
- 'package.json'
jobs:
tests:
env:
NPM_CONFIG_PROGRESS: "false"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'npm'
- name: Install dependencies
run: |
npm ci
npm install --no-audit karma-cli karma-sauce-launcher
- name: Test in Sauce Labs
run: BUILD_NUMBER="$GITHUB_RUN_NUMBER" BUILD_ID="$GITHUB_RUN_ID" JOB_NUMBER="$GITHUB_JOB" ./node_modules/.bin/karma start karma.conf-sauce.js
env:
SAUCE_USERNAME: ${{ secrets.SauceUsername }}
SAUCE_ACCESS_KEY: ${{ secrets.SauceAccessKey }}
================================================
FILE: .gitignore
================================================
raw
*.sw?
.DS_Store
node_modules
bower_components
================================================
FILE: CNAME
================================================
backbonejs.org
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
jashkenas@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Moderation
Edits of another user's comment must be clearly marked with "**edit**", the
moderator's username, and a timestamp for each occurrence. The only acceptable
reasons for editing another user's comment are:
1. to edit out violations of Our Pledge. These edits must include a rationale.
2. to direct future readers to a relevant point later in the conversation
(usually the resolution). These edits must be append-only.
Deletion of another user's comment is only acceptable when the comment includes
no original value, such as "+1", ":+1:", or "me too".
## Self-Moderation
Edits of your own comment after someone has responded must be append-only and
clearly marked with "**edit**". Typographical and formatting fixes to your own
comment which do not affect its meaning are exempt from this requirement.
Deletion of your own comment is only acceptable before any later comments have
been posted.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
================================================
FILE: CONTRIBUTING.md
================================================
## How to Open a Backbone.js Ticket
* Do not use tickets to ask for help with (debugging) your application. Ask on
the [mailing list](https://groups.google.com/forum/#!forum/backbonejs),
in the IRC channel (`#documentcloud` on Freenode), or if you understand your
specific problem, on [StackOverflow](http://stackoverflow.com/questions/tagged/backbone.js).
* Before you open a ticket or send a pull request,
[search](https://github.com/jashkenas/backbone/issues) for previous
discussions about the same feature or issue. Add to the earlier ticket if you
find one.
* Before sending a pull request for a feature or bug fix, be sure to have
[tests](http://backbonejs.org/test/) and to document any new functionality in
the `index.html`.
* Use the same coding style as the rest of the
[codebase](https://github.com/jashkenas/backbone/blob/master/backbone.js).
* In your pull request, do not regenerate the annotated sources or rebuild the
minified `backbone-min.js` file. We'll do that before cutting a new release.
* All pull requests should be made to the `master` branch.
================================================
FILE: LICENSE
================================================
Copyright (c) 2010-2024 Jeremy Ashkenas, DocumentCloud
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
____ __ __
/\ _`\ /\ \ /\ \ __
\ \ \ \ \ __ ___\ \ \/'\\ \ \____ ___ ___ __ /\_\ ____
\ \ _ <' /'__`\ /'___\ \ , < \ \ '__`\ / __`\ /' _ `\ /'__`\ \/\ \ /',__\
\ \ \ \ \/\ \ \.\_/\ \__/\ \ \\`\\ \ \ \ \/\ \ \ \/\ \/\ \/\ __/ __ \ \ \/\__, `\
\ \____/\ \__/.\_\ \____\\ \_\ \_\ \_,__/\ \____/\ \_\ \_\ \____\/\_\_\ \ \/\____/
\/___/ \/__/\/_/\/____/ \/_/\/_/\/___/ \/___/ \/_/\/_/\/____/\/_/\ \_\ \/___/
\ \____/
\/___/
(_'_______________________________________________________________________________'_)
(_.———————————————————————————————————————————————————————————————————————————————._)
Backbone supplies structure to JavaScript-heavy applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful JSON interface.
For Docs, License, Tests, pre-packed downloads, and everything else, really, see:
https://backbonejs.org
To suggest a feature or report a bug:
https://github.com/jashkenas/backbone/issues
For questions on working with Backbone or general discussions:
[security policy](SECURITY.md),
https://stackoverflow.com/questions/tagged/backbone.js,
https://matrix.to/#/#jashkenas_backbone:gitter.im or
https://groups.google.com/g/backbonejs
Backbone is an open-sourced component of DocumentCloud:
https://github.com/documentcloud
Testing powered by SauceLabs:
https://saucelabs.com
Many thanks to our contributors:
https://github.com/jashkenas/backbone/graphs/contributors
Special thanks to Robert Kieffer for the original philosophy behind Backbone.
https://github.com/broofa
This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
We currently support the following versions of Backbone with security updates:
- the latest commit on the `master` branch (published as "edge" on the
[project website][website]);
- the 1.x release tagged as [latest][npm-latest] on npm;
- any release tagged as [preview][npm-preview] on npm, if present.
[website]: https://backbonejs.org
[npm-latest]: https://www.npmjs.com/package/backbone/v/latest
[npm-preview]: https://www.npmjs.com/package/backbone/v/preview
## Reporting a Vulnerability
Please report security issues by sending an email to
dev@juliangonggrijp.com and jashkenas@gmail.com.
Do __not__ submit an issue ticket or pull request or otherwise publicly
disclose the issue.
After receiving your email, we will respond as soon as possible and indicate
what we plan to do.
## Disclosure policy
After confirming a vulnerability, we will generally release a security update
as soon as possible, including the minimum amount of information required for
software maintainers and system administrators to assess the urgency of the
update for their particular situation.
We postpone the publication of any further details such as code comments,
tests, commit history and diffs, in order to enable a substantial share of the
users to install the security fix before this time.
Upon publication of full details, we will credit the reporter if the reporter wishes to be publicly identified.
================================================
FILE: backbone-min.js
================================================
(function(r){var n=typeof self=="object"&&self.self===self&&self||typeof global=="object"&&global.global===global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(t,e,i){n.Backbone=r(n,i,t,e)})}else if(typeof exports!=="undefined"){var t=require("underscore"),e;try{e=require("jquery")}catch(t){}r(n,exports,t,e)}else{n.Backbone=r(n,{},n._,n.jQuery||n.Zepto||n.ender||n.$)}})(function(t,h,x,e){var i=t.Backbone;var a=Array.prototype.slice;h.VERSION="1.6.1";h.$=e;h.noConflict=function(){t.Backbone=i;return this};h.emulateHTTP=false;h.emulateJSON=false;var r=h.Events={};var o=/\s+/;var l;var u=function(t,e,i,r,n){var s=0,a;if(i&&typeof i==="object"){if(r!==void 0&&"context"in n&&n.context===void 0)n.context=r;for(a=x.keys(i);s<a.length;s++){e=u(t,e,a[s],i[a[s]],n)}}else if(i&&o.test(i)){for(a=i.split(o);s<a.length;s++){e=t(e,a[s],r,n)}}else{e=t(e,i,r,n)}return e};r.on=function(t,e,i){this._events=u(n,this._events||{},t,e,{context:i,ctx:this,listening:l});if(l){var r=this._listeners||(this._listeners={});r[l.id]=l;l.interop=false}return this};r.listenTo=function(t,e,i){if(!t)return this;var r=t._listenId||(t._listenId=x.uniqueId("l"));var n=this._listeningTo||(this._listeningTo={});var s=l=n[r];if(!s){this._listenId||(this._listenId=x.uniqueId("l"));s=l=n[r]=new p(this,t)}var a=c(t,e,i,this);l=void 0;if(a)throw a;if(s.interop)s.on(e,i);return this};var n=function(t,e,i,r){if(i){var n=t[e]||(t[e]=[]);var s=r.context,a=r.ctx,o=r.listening;if(o)o.count++;n.push({callback:i,context:s,ctx:s||a,listening:o})}return t};var c=function(t,e,i,r){try{t.on(e,i,r)}catch(t){return t}};r.off=function(t,e,i){if(!this._events)return this;this._events=u(s,this._events,t,e,{context:i,listeners:this._listeners});return this};r.stopListening=function(t,e,i){var r=this._listeningTo;if(!r)return this;var n=t?[t._listenId]:x.keys(r);for(var s=0;s<n.length;s++){var a=r[n[s]];if(!a)break;a.obj.off(e,i,this);if(a.interop)a.off(e,i)}if(x.isEmpty(r))this._listeningTo=void 0;return this};var s=function(t,e,i,r){if(!t)return;var n=r.context,s=r.listeners;var a=0,o;if(!e&&!n&&!i){for(o=x.keys(s);a<o.length;a++){s[o[a]].cleanup()}return}o=e?[e]:x.keys(t);for(;a<o.length;a++){e=o[a];var h=t[e];if(!h)break;var l=[];for(var u=0;u<h.length;u++){var c=h[u];if(i&&i!==c.callback&&i!==c.callback._callback||n&&n!==c.context){l.push(c)}else{var f=c.listening;if(f)f.off(e,i)}}if(l.length){t[e]=l}else{delete t[e]}}return t};r.once=function(t,e,i){var r=u(f,{},t,e,this.off.bind(this));if(typeof t==="string"&&i==null)e=void 0;return this.on(r,e,i)};r.listenToOnce=function(t,e,i){var r=u(f,{},e,i,this.stopListening.bind(this,t));return this.listenTo(t,r)};var f=function(t,e,i,r){if(i){var n=t[e]=x.once(function(){r(e,n);i.apply(this,arguments)});n._callback=i}return t};r.trigger=function(t){if(!this._events)return this;var e=Math.max(0,arguments.length-1);var i=Array(e);for(var r=0;r<e;r++)i[r]=arguments[r+1];u(d,this._events,t,void 0,i);return this};var d=function(t,e,i,r){if(t){var n=t[e];var s=t.all;if(n&&s)s=s.slice();if(n)v(n,r);if(s)v(s,[e].concat(r))}return t};var v=function(t,e){var i,r=-1,n=t.length,s=e[0],a=e[1],o=e[2];switch(e.length){case 0:while(++r<n)(i=t[r]).callback.call(i.ctx);return;case 1:while(++r<n)(i=t[r]).callback.call(i.ctx,s);return;case 2:while(++r<n)(i=t[r]).callback.call(i.ctx,s,a);return;case 3:while(++r<n)(i=t[r]).callback.call(i.ctx,s,a,o);return;default:while(++r<n)(i=t[r]).callback.apply(i.ctx,e);return}};var p=function(t,e){this.id=t._listenId;this.listener=t;this.obj=e;this.interop=true;this.count=0;this._events=void 0};p.prototype.on=r.on;p.prototype.off=function(t,e){var i;if(this.interop){this._events=u(s,this._events,t,e,{context:void 0,listeners:void 0});i=!this._events}else{this.count--;i=this.count===0}if(i)this.cleanup()};p.prototype.cleanup=function(){delete this.listener._listeningTo[this.obj._listenId];if(!this.interop)delete this.obj._listeners[this.id]};r.bind=r.on;r.unbind=r.off;x.extend(h,r);var g=h.Model=function(t,e){var i=t||{};e||(e={});this.preinitialize.apply(this,arguments);this.cid=x.uniqueId(this.cidPrefix);this.attributes={};if(e.collection)this.collection=e.collection;if(e.parse)i=this.parse(i,e)||{};var r=x.result(this,"defaults");i=x.defaults(x.extend({},r,i),r);this.set(i,e);this.changed={};this.initialize.apply(this,arguments)};x.extend(g.prototype,r,{changed:null,validationError:null,idAttribute:"id",cidPrefix:"c",preinitialize:function(){},initialize:function(){},toJSON:function(t){return x.clone(this.attributes)},sync:function(){return h.sync.apply(this,arguments)},get:function(t){return this.attributes[t]},escape:function(t){return x.escape(this.get(t))},has:function(t){return this.get(t)!=null},matches:function(t){return!!x.iteratee(t,this)(this.attributes)},set:function(t,e,i){if(t==null)return this;var r;if(typeof t==="object"){r=t;i=e}else{(r={})[t]=e}i||(i={});if(!this._validate(r,i))return false;var n=i.unset;var s=i.silent;var a=[];var o=this._changing;this._changing=true;if(!o){this._previousAttributes=x.clone(this.attributes);this.changed={}}var h=this.attributes;var l=this.changed;var u=this._previousAttributes;for(var c in r){e=r[c];if(!x.isEqual(h[c],e))a.push(c);if(!x.isEqual(u[c],e)){l[c]=e}else{delete l[c]}n?delete h[c]:h[c]=e}if(this.idAttribute in r){var f=this.id;this.id=this.get(this.idAttribute);if(this.id!==f){this.trigger("changeId",this,f,i)}}if(!s){if(a.length)this._pending=i;for(var d=0;d<a.length;d++){this.trigger("change:"+a[d],this,h[a[d]],i)}}if(o)return this;if(!s){while(this._pending){i=this._pending;this._pending=false;this.trigger("change",this,i)}}this._pending=false;this._changing=false;return this},unset:function(t,e){return this.set(t,void 0,x.extend({},e,{unset:true}))},clear:function(t){var e={};for(var i in this.attributes)e[i]=void 0;return this.set(e,x.extend({},t,{unset:true}))},hasChanged:function(t){if(t==null)return!x.isEmpty(this.changed);return x.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?x.clone(this.changed):false;var e=this._changing?this._previousAttributes:this.attributes;var i={};var r;for(var n in t){var s=t[n];if(x.isEqual(e[n],s))continue;i[n]=s;r=true}return r?i:false},previous:function(t){if(t==null||!this._previousAttributes)return null;return this._previousAttributes[t]},previousAttributes:function(){return x.clone(this._previousAttributes)},fetch:function(i){i=x.extend({parse:true},i);var r=this;var n=i.success;i.success=function(t){var e=i.parse?r.parse(t,i):t;if(!r.set(e,i))return false;if(n)n.call(i.context,r,t,i);r.trigger("sync",r,t,i)};G(this,i);return this.sync("read",this,i)},save:function(t,e,i){var r;if(t==null||typeof t==="object"){r=t;i=e}else{(r={})[t]=e}i=x.extend({validate:true,parse:true},i);var n=i.wait;if(r&&!n){if(!this.set(r,i))return false}else if(!this._validate(r,i)){return false}var s=this;var a=i.success;var o=this.attributes;i.success=function(t){s.attributes=o;var e=i.parse?s.parse(t,i):t;if(n)e=x.extend({},r,e);if(e&&!s.set(e,i))return false;if(a)a.call(i.context,s,t,i);s.trigger("sync",s,t,i)};G(this,i);if(r&&n)this.attributes=x.extend({},o,r);var h=this.isNew()?"create":i.patch?"patch":"update";if(h==="patch"&&!i.attrs)i.attrs=r;var l=this.sync(h,this,i);this.attributes=o;return l},destroy:function(e){e=e?x.clone(e):{};var i=this;var r=e.success;var n=e.wait;var s=function(){i.stopListening();i.trigger("destroy",i,i.collection,e)};e.success=function(t){if(n)s();if(r)r.call(e.context,i,t,e);if(!i.isNew())i.trigger("sync",i,t,e)};var t=false;if(this.isNew()){x.defer(e.success)}else{G(this,e);t=this.sync("delete",this,e)}if(!n)s();return t},url:function(){var t=x.result(this,"urlRoot")||x.result(this.collection,"url")||V();if(this.isNew())return t;var e=this.get(this.idAttribute);return t.replace(/[^\/]$/,"$&/")+encodeURIComponent(e)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return!this.has(this.idAttribute)},isValid:function(t){return this._validate({},x.extend({},t,{validate:true}))},_validate:function(t,e){if(!e.validate||!this.validate)return true;t=x.extend({},this.attributes,t);var i=this.validationError=this.validate(t,e)||null;if(!i)return true;this.trigger("invalid",this,i,x.extend(e,{validationError:i}));return false}});var m=h.Collection=function(t,e){e||(e={});this.preinitialize.apply(this,arguments);if(e.model)this.model=e.model;if(e.comparator!==void 0)this.comparator=e.comparator;this._reset();this.initialize.apply(this,arguments);if(t)this.reset(t,x.extend({silent:true},e))};var w={add:true,remove:true,merge:true};var _={add:true,remove:false};var E=function(t,e,i){i=Math.min(Math.max(i,0),t.length);var r=Array(t.length-i);var n=e.length;var s;for(s=0;s<r.length;s++)r[s]=t[s+i];for(s=0;s<n;s++)t[s+i]=e[s];for(s=0;s<r.length;s++)t[s+n+i]=r[s]};x.extend(m.prototype,r,{model:g,preinitialize:function(){},initialize:function(){},toJSON:function(e){return this.map(function(t){return t.toJSON(e)})},sync:function(){return h.sync.apply(this,arguments)},add:function(t,e){return this.set(t,x.extend({merge:false},e,_))},remove:function(t,e){e=x.extend({},e);var i=!x.isArray(t);t=i?[t]:t.slice();var r=this._removeModels(t,e);if(!e.silent&&r.length){e.changes={added:[],merged:[],removed:r};this.trigger("update",this,e)}return i?r[0]:r},set:function(t,e){if(t==null)return;e=x.extend({},w,e);if(e.parse&&!this._isModel(t)){t=this.parse(t,e)||[]}var i=!x.isArray(t);t=i?[t]:t.slice();var r=e.at;if(r!=null)r=+r;if(r>this.length)r=this.length;if(r<0)r+=this.length+1;var n=[];var s=[];var a=[];var o=[];var h={};var l=e.add;var u=e.merge;var c=e.remove;var f=false;var d=this.comparator&&r==null&&e.sort!==false;var v=x.isString(this.comparator)?this.comparator:null;var p,g;for(g=0;g<t.length;g++){p=t[g];var m=this.get(p);if(m){if(u&&p!==m){var _=this._isModel(p)?p.attributes:p;if(e.parse)_=m.parse(_,e);m.set(_,e);a.push(m);if(d&&!f)f=m.hasChanged(v)}if(!h[m.cid]){h[m.cid]=true;n.push(m)}t[g]=m}else if(l){p=t[g]=this._prepareModel(p,e);if(p){s.push(p);this._addReference(p,e);h[p.cid]=true;n.push(p)}}}if(c){for(g=0;g<this.length;g++){p=this.models[g];if(!h[p.cid])o.push(p)}if(o.length)this._removeModels(o,e)}var y=false;var b=!d&&l&&c;if(n.length&&b){y=this.length!==n.length||x.some(this.models,function(t,e){return t!==n[e]});this.models.length=0;E(this.models,n,0);this.length=this.models.length}else if(s.length){if(d)f=true;E(this.models,s,r==null?this.length:r);this.length=this.models.length}if(f)this.sort({silent:true});if(!e.silent){for(g=0;g<s.length;g++){if(r!=null)e.index=r+g;p=s[g];p.trigger("add",p,this,e)}if(f||y)this.trigger("sort",this,e);if(s.length||o.length||a.length){e.changes={added:s,removed:o,merged:a};this.trigger("update",this,e)}}return i?t[0]:t},reset:function(t,e){e=e?x.clone(e):{};for(var i=0;i<this.models.length;i++){this._removeReference(this.models[i],e)}e.previousModels=this.models;this._reset();t=this.add(t,x.extend({silent:true},e));if(!e.silent)this.trigger("reset",this,e);return t},push:function(t,e){return this.add(t,x.extend({at:this.length},e))},pop:function(t){var e=this.at(this.length-1);return this.remove(e,t)},unshift:function(t,e){return this.add(t,x.extend({at:0},e))},shift:function(t){var e=this.at(0);return this.remove(e,t)},slice:function(){return a.apply(this.models,arguments)},get:function(t){if(t==null)return void 0;return this._byId[t]||this._byId[this.modelId(this._isModel(t)?t.attributes:t,t.idAttribute)]||t.cid&&this._byId[t.cid]},has:function(t){return this.get(t)!=null},at:function(t){if(t<0)t+=this.length;return this.models[t]},where:function(t,e){return this[e?"find":"filter"](t)},findWhere:function(t){return this.where(t,true)},sort:function(t){var e=this.comparator;if(!e)throw new Error("Cannot sort a set without a comparator");t||(t={});var i=e.length;if(x.isFunction(e))e=e.bind(this);if(i===1||x.isString(e)){this.models=this.sortBy(e)}else{this.models.sort(e)}if(!t.silent)this.trigger("sort",this,t);return this},pluck:function(t){return this.map(t+"")},fetch:function(i){i=x.extend({parse:true},i);var r=i.success;var n=this;i.success=function(t){var e=i.reset?"reset":"set";n[e](t,i);if(r)r.call(i.context,n,t,i);n.trigger("sync",n,t,i)};G(this,i);return this.sync("read",this,i)},create:function(t,e){e=e?x.clone(e):{};var r=e.wait;t=this._prepareModel(t,e);if(!t)return false;if(!r)this.add(t,e);var n=this;var s=e.success;e.success=function(t,e,i){if(r){t.off("error",n._forwardPristineError,n);n.add(t,i)}if(s)s.call(i.context,t,e,i)};if(r){t.once("error",this._forwardPristineError,this)}t.save(null,e);return t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models,{model:this.model,comparator:this.comparator})},modelId:function(t,e){return t[e||this.model.prototype.idAttribute||"id"]},values:function(){return new b(this,S)},keys:function(){return new b(this,I)},entries:function(){return new b(this,k)},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(t,e){if(this._isModel(t)){if(!t.collection)t.collection=this;return t}e=e?x.clone(e):{};e.collection=this;var i;if(this.model.prototype){i=new this.model(t,e)}else{i=this.model(t,e)}if(!i.validationError)return i;this.trigger("invalid",this,i.validationError,e);return false},_removeModels:function(t,e){var i=[];for(var r=0;r<t.length;r++){var n=this.get(t[r]);if(!n)continue;var s=this.indexOf(n);this.models.splice(s,1);this.length--;delete this._byId[n.cid];var a=this.modelId(n.attributes,n.idAttribute);if(a!=null)delete this._byId[a];if(!e.silent){e.index=s;n.trigger("remove",n,this,e)}i.push(n);this._removeReference(n,e)}if(t.length>0&&!e.silent)delete e.index;return i},_isModel:function(t){return t instanceof g},_addReference:function(t,e){this._byId[t.cid]=t;var i=this.modelId(t.attributes,t.idAttribute);if(i!=null)this._byId[i]=t;t.on("all",this._onModelEvent,this)},_removeReference:function(t,e){delete this._byId[t.cid];var i=this.modelId(t.attributes,t.idAttribute);if(i!=null)delete this._byId[i];if(this===t.collection)delete t.collection;t.off("all",this._onModelEvent,this)},_onModelEvent:function(t,e,i,r){if(e){if((t==="add"||t==="remove")&&i!==this)return;if(t==="destroy")this.remove(e,r);if(t==="changeId"){var n=this.modelId(e.previousAttributes(),e.idAttribute);var s=this.modelId(e.attributes,e.idAttribute);if(n!=null)delete this._byId[n];if(s!=null)this._byId[s]=e}}this.trigger.apply(this,arguments)},_forwardPristineError:function(t,e,i){if(this.has(t))return;this._onModelEvent("error",t,e,i)}});var y=typeof Symbol==="function"&&Symbol.iterator;if(y){m.prototype[y]=m.prototype.values}var b=function(t,e){this._collection=t;this._kind=e;this._index=0};var S=1;var I=2;var k=3;if(y){b.prototype[y]=function(){return this}}b.prototype.next=function(){if(this._collection){if(this._index<this._collection.length){var t=this._collection.at(this._index);this._index++;var e;if(this._kind===S){e=t}else{var i=this._collection.modelId(t.attributes,t.idAttribute);if(this._kind===I){e=i}else{e=[i,t]}}return{value:e,done:false}}this._collection=void 0}return{value:void 0,done:true}};var A=h.View=function(t){this.cid=x.uniqueId("view");this.preinitialize.apply(this,arguments);x.extend(this,x.pick(t,T));this._ensureElement();this.initialize.apply(this,arguments)};var P=/^(\S+)\s*(.*)$/;var T=["model","collection","el","id","attributes","className","tagName","events"];x.extend(A.prototype,r,{tagName:"div",$:function(t){return this.$el.find(t)},preinitialize:function(){},initialize:function(){},render:function(){return this},remove:function(){this._removeElement();this.stopListening();return this},_removeElement:function(){this.$el.remove()},setElement:function(t){this.undelegateEvents();this._setElement(t);this.delegateEvents();return this},_setElement:function(t){this.$el=t instanceof h.$?t:h.$(t);this.el=this.$el[0]},delegateEvents:function(t){t||(t=x.result(this,"events"));if(!t)return this;this.undelegateEvents();for(var e in t){var i=t[e];if(!x.isFunction(i))i=this[i];if(!i)continue;var r=e.match(P);this.delegate(r[1],r[2],i.bind(this))}return this},delegate:function(t,e,i){this.$el.on(t+".delegateEvents"+this.cid,e,i);return this},undelegateEvents:function(){if(this.$el)this.$el.off(".delegateEvents"+this.cid);return this},undelegate:function(t,e,i){this.$el.off(t+".delegateEvents"+this.cid,e,i);return this},_createElement:function(t){return document.createElement(t)},_ensureElement:function(){if(!this.el){var t=x.extend({},x.result(this,"attributes"));if(this.id)t.id=x.result(this,"id");if(this.className)t["class"]=x.result(this,"className");this.setElement(this._createElement(x.result(this,"tagName")));this._setAttributes(t)}else{this.setElement(x.result(this,"el"))}},_setAttributes:function(t){this.$el.attr(t)}});var H=function(r,t,n,s){switch(t){case 1:return function(){return r[n](this[s])};case 2:return function(t){return r[n](this[s],t)};case 3:return function(t,e){return r[n](this[s],C(t,this),e)};case 4:return function(t,e,i){return r[n](this[s],C(t,this),e,i)};default:return function(){var t=a.call(arguments);t.unshift(this[s]);return r[n].apply(r,t)}}};var $=function(i,r,t,n){x.each(t,function(t,e){if(r[e])i.prototype[e]=H(r,t,e,n)})};var C=function(e,t){if(x.isFunction(e))return e;if(x.isObject(e)&&!t._isModel(e))return R(e);if(x.isString(e))return function(t){return t.get(e)};return e};var R=function(t){var e=x.matches(t);return function(t){return e(t.attributes)}};var M={forEach:3,each:3,map:3,collect:3,reduce:0,foldl:0,inject:0,reduceRight:0,foldr:0,find:3,detect:3,filter:3,select:3,reject:3,every:3,all:3,some:3,any:3,include:3,includes:3,contains:3,invoke:0,max:3,min:3,toArray:1,size:1,first:3,head:3,take:3,initial:3,rest:3,tail:3,drop:3,last:3,without:0,difference:0,indexOf:3,shuffle:1,lastIndexOf:3,isEmpty:1,chain:1,sample:3,partition:3,groupBy:3,countBy:3,sortBy:3,indexBy:3,findIndex:3,findLastIndex:3};var N={keys:1,values:1,pairs:1,invert:1,pick:0,omit:0,chain:1,isEmpty:1};x.each([[m,M,"models"],[g,N,"attributes"]],function(t){var i=t[0],e=t[1],r=t[2];i.mixin=function(t){var e=x.reduce(x.functions(t),function(t,e){t[e]=0;return t},{});$(i,t,e,r)};$(i,x,e,r)});h.sync=function(t,e,r){var i=j[t];x.defaults(r||(r={}),{emulateHTTP:h.emulateHTTP,emulateJSON:h.emulateJSON});var n={type:i,dataType:"json"};if(!r.url){n.url=x.result(e,"url")||V()}if(r.data==null&&e&&(t==="create"||t==="update"||t==="patch")){n.contentType="application/json";n.data=JSON.stringify(r.attrs||e.toJSON(r))}if(r.emulateJSON){n.contentType="application/x-www-form-urlencoded";n.data=n.data?{model:n.data}:{}}if(r.emulateHTTP&&(i==="PUT"||i==="DELETE"||i==="PATCH")){n.type="POST";if(r.emulateJSON)n.data._method=i;var s=r.beforeSend;r.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",i);if(s)return s.apply(this,arguments)}}if(n.type!=="GET"&&!r.emulateJSON){n.processData=false}var a=r.error;r.error=function(t,e,i){r.textStatus=e;r.errorThrown=i;if(a)a.call(r.context,t,e,i)};var o=r.xhr=h.ajax(x.extend(n,r));e.trigger("request",e,o,r);return o};var j={create:"POST",update:"PUT",patch:"PATCH",delete:"DELETE",read:"GET"};h.ajax=function(){return h.$.ajax.apply(h.$,arguments)};var O=h.Router=function(t){t||(t={});this.preinitialize.apply(this,arguments);if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var U=/\((.*?)\)/g;var z=/(\(\?)?:\w+/g;var q=/\*\w+/g;var F=/[\-{}\[\]+?.,\\\^$|#\s]/g;x.extend(O.prototype,r,{preinitialize:function(){},initialize:function(){},route:function(i,r,n){if(!x.isRegExp(i))i=this._routeToRegExp(i);if(x.isFunction(r)){n=r;r=""}if(!n)n=this[r];var s=this;h.history.route(i,function(t){var e=s._extractParameters(i,t);if(s.execute(n,e,r)!==false){s.trigger.apply(s,["route:"+r].concat(e));s.trigger("route",r,e);h.history.trigger("route",s,r,e)}});return this},execute:function(t,e,i){if(t)t.apply(this,e)},navigate:function(t,e){h.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=x.result(this,"routes");var t,e=x.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(F,"\\$&").replace(U,"(?:$1)?").replace(z,function(t,e){return e?t:"([^/?]+)"}).replace(q,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return x.map(i,function(t,e){if(e===i.length-1)return t||null;return t?decodeURIComponent(t):null})}});var B=h.History=function(){this.handlers=[];this.checkUrl=this.checkUrl.bind(this);if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var J=/^[#\/]|\s+$/g;var L=/^\/+|\/+$/g;var W=/#.*$/;B.started=false;x.extend(B.prototype,r,{interval:50,atRoot:function(){var t=this.location.pathname.replace(/[^\/]$/,"$&/");return t===this.root&&!this.getSearch()},matchRoot:function(){var t=this.decodeFragment(this.location.pathname);var e=t.slice(0,this.root.length-1)+"/";return e===this.root},decodeFragment:function(t){return decodeURI(t.replace(/%25/g,"%2525"))},getSearch:function(){var t=this.location.href.replace(/#.*/,"").match(/\?.+/);return t?t[0]:""},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getPath:function(){var t=this.decodeFragment(this.location.pathname+this.getSearch()).slice(this.root.length-1);return t.charAt(0)==="/"?t.slice(1):t},getFragment:function(t){if(t==null){if(this._usePushState||!this._wantsHashChange){t=this.getPath()}else{t=this.getHash()}}return t.replace(J,"")},start:function(t){if(B.started)throw new Error("Backbone.history has already been started");B.started=true;this.options=x.extend({root:"/"},this.options,t);this.root=this.options.root;this._trailingSlash=this.options.trailingSlash;this._wantsHashChange=this.options.hashChange!==false;this._hasHashChange="onhashchange"in window&&(document.documentMode===void 0||document.documentMode>7);this._useHashChange=this._wantsHashChange&&this._hasHashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.history&&this.history.pushState);this._usePushState=this._wantsPushState&&this._hasPushState;this.fragment=this.getFragment();this.root=("/"+this.root+"/").replace(L,"/");if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";this.location.replace(e+"#"+this.getPath());return true}else if(this._hasPushState&&this.atRoot()){this.navigate(this.getHash(),{replace:true})}}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe");this.iframe.src="javascript:0";this.iframe.style.display="none";this.iframe.tabIndex=-1;var i=document.body;var r=i.insertBefore(this.iframe,i.firstChild).contentWindow;r.document.open();r.document.close();r.location.hash="#"+this.fragment}var n=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState){n("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){n("hashchange",this.checkUrl,false)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}if(!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};if(this._usePushState){t("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){t("hashchange",this.checkUrl,false)}if(this.iframe){document.body.removeChild(this.iframe);this.iframe=null}if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);B.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getHash(this.iframe.contentWindow)}if(e===this.fragment){if(!this.matchRoot())return this.notfound();return false}if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(e){if(!this.matchRoot())return this.notfound();e=this.fragment=this.getFragment(e);return x.some(this.handlers,function(t){if(t.route.test(e)){t.callback(e);return true}})||this.notfound()},notfound:function(){this.trigger("notfound");return false},navigate:function(t,e){if(!B.started)return false;if(!e||e===true)e={trigger:!!e};t=this.getFragment(t||"");var i=this.root;if(!this._trailingSlash&&(t===""||t.charAt(0)==="?")){i=i.slice(0,-1)||"/"}var r=i+t;t=t.replace(W,"");var n=this.decodeFragment(t);if(this.fragment===n)return;this.fragment=n;if(this._usePushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,r)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var s=this.iframe.contentWindow;if(!e.replace){s.document.open();s.document.close()}this._updateHash(s.location,t,e.replace)}}else{return this.location.assign(r)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});h.history=new B;var D=function(t,e){var i=this;var r;if(t&&x.has(t,"constructor")){r=t.constructor}else{r=function(){return i.apply(this,arguments)}}x.extend(r,i,e);r.prototype=x.create(i.prototype,t);r.prototype.constructor=r;r.__super__=i.prototype;return r};g.extend=m.extend=O.extend=A.extend=B.extend=D;var V=function(){throw new Error('A "url" property or function must be specified')};var G=function(e,i){var r=i.error;i.error=function(t){if(r)r.call(i.context,e,t,i);e.trigger("error",e,t,i)}};h._debug=function(){return{root:t,_:x}};return h});
//# sourceMappingURL=backbone-min.js.map
================================================
FILE: backbone.js
================================================
// Backbone.js 1.6.1
// (c) 2010-2024 Jeremy Ashkenas and DocumentCloud
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(factory) {
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
// We use `self` instead of `window` for `WebWorker` support.
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global;
// Set up Backbone appropriately for the environment. Start with AMD.
if (typeof define === 'function' && define.amd) {
define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
// Export global even in AMD case in case this script is loaded with
// others that may still expect a global Backbone.
root.Backbone = factory(root, exports, _, $);
});
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
} else if (typeof exports !== 'undefined') {
var _ = require('underscore'), $;
try { $ = require('jquery'); } catch (e) {}
factory(root, exports, _, $);
// Finally, as a browser global.
} else {
root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$);
}
})(function(root, Backbone, _, $) {
// Initial Setup
// -------------
// 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 a common array method we'll want to use later.
var slice = Array.prototype.slice;
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.6.1';
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable.
Backbone.$ = $;
// 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 `"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 ... this 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
// ---------------
// A module that can be mixed in to *any object* in order to provide it with
// a custom event channel. You may bind a callback to an event with `on` or
// remove with `off`; `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 = {};
// Regular expression used to split event strings.
var eventSplitter = /\s+/;
// A private global variable to share between listeners and listenees.
var _listening;
// Iterates over the standard `event, callback` (as well as the fancy multiple
// space-separated events `"change blur", callback` and jQuery-style event
// maps `{event: callback}`).
var eventsApi = function(iteratee, events, name, callback, opts) {
var i = 0, names;
if (name && typeof name === 'object') {
// Handle event maps.
if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
for (names = _.keys(name); i < names.length ; i++) {
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
}
} else if (name && eventSplitter.test(name)) {
// Handle space-separated event names by delegating them individually.
for (names = name.split(eventSplitter); i < names.length; i++) {
events = iteratee(events, names[i], callback, opts);
}
} else {
// Finally, standard events.
events = iteratee(events, name, callback, opts);
}
return events;
};
// Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
Events.on = function(name, callback, context) {
this._events = eventsApi(onApi, this._events || {}, name, callback, {
context: context,
ctx: this,
listening: _listening
});
if (_listening) {
var listeners = this._listeners || (this._listeners = {});
listeners[_listening.id] = _listening;
// Allow the listening to use a counter, instead of tracking
// callbacks for library interop
_listening.interop = false;
}
return this;
};
// Inversion-of-control versions of `on`. Tell *this* object to listen to
// an event in another object... keeping track of what it's listening to
// for easier unbinding later.
Events.listenTo = function(obj, name, callback) {
if (!obj) return this;
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
var listeningTo = this._listeningTo || (this._listeningTo = {});
var listening = _listening = listeningTo[id];
// This object is not listening to any other events on `obj` yet.
// Setup the necessary references to track the listening callbacks.
if (!listening) {
this._listenId || (this._listenId = _.uniqueId('l'));
listening = _listening = listeningTo[id] = new Listening(this, obj);
}
// Bind callbacks on obj.
var error = tryCatchOn(obj, name, callback, this);
_listening = void 0;
if (error) throw error;
// If the target obj is not Backbone.Events, track events manually.
if (listening.interop) listening.on(name, callback);
return this;
};
// The reducing API that adds a callback to the `events` object.
var onApi = function(events, name, callback, options) {
if (callback) {
var handlers = events[name] || (events[name] = []);
var context = options.context, ctx = options.ctx, listening = options.listening;
if (listening) listening.count++;
handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
}
return events;
};
// An try-catch guarded #on function, to prevent poisoning the global
// `_listening` variable.
var tryCatchOn = function(obj, name, callback, context) {
try {
obj.on(name, callback, context);
} catch (e) {
return e;
}
};
// 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 `name` is null, removes all bound
// callbacks for all events.
Events.off = function(name, callback, context) {
if (!this._events) return this;
this._events = eventsApi(offApi, this._events, name, callback, {
context: context,
listeners: this._listeners
});
return this;
};
// Tell this object to stop listening to either specific events ... or
// to every object it's currently listening to.
Events.stopListening = function(obj, name, callback) {
var listeningTo = this._listeningTo;
if (!listeningTo) return this;
var ids = obj ? [obj._listenId] : _.keys(listeningTo);
for (var i = 0; i < ids.length; i++) {
var listening = listeningTo[ids[i]];
// If listening doesn't exist, this object is not currently
// listening to obj. Break out early.
if (!listening) break;
listening.obj.off(name, callback, this);
if (listening.interop) listening.off(name, callback);
}
if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
return this;
};
// The reducing API that removes a callback from the `events` object.
var offApi = function(events, name, callback, options) {
if (!events) return;
var context = options.context, listeners = options.listeners;
var i = 0, names;
// Delete all event listeners and "drop" events.
if (!name && !context && !callback) {
for (names = _.keys(listeners); i < names.length; i++) {
listeners[names[i]].cleanup();
}
return;
}
names = name ? [name] : _.keys(events);
for (; i < names.length; i++) {
name = names[i];
var handlers = events[name];
// Bail out if there are no events stored.
if (!handlers) break;
// Find any remaining events.
var remaining = [];
for (var j = 0; j < handlers.length; j++) {
var handler = handlers[j];
if (
callback && callback !== handler.callback &&
callback !== handler.callback._callback ||
context && context !== handler.context
) {
remaining.push(handler);
} else {
var listening = handler.listening;
if (listening) listening.off(name, callback);
}
}
// Replace events if there are any remaining. Otherwise, clean up.
if (remaining.length) {
events[name] = remaining;
} else {
delete events[name];
}
}
return events;
};
// Bind an event to only be triggered a single time. After the first time
// the callback is invoked, its listener will be removed. If multiple events
// are passed in using the space-separated syntax, the handler will fire
// once for each event, not once for a combination of all events.
Events.once = function(name, callback, context) {
// Map the event into a `{event: once}` object.
var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
if (typeof name === 'string' && context == null) callback = void 0;
return this.on(events, callback, context);
};
// Inversion-of-control versions of `once`.
Events.listenToOnce = function(obj, name, callback) {
// Map the event into a `{event: once}` object.
var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
return this.listenTo(obj, events);
};
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
// `offer` unbinds the `onceWrapper` after it has been called.
var onceMap = function(map, name, callback, offer) {
if (callback) {
var once = map[name] = _.once(function() {
offer(name, once);
callback.apply(this, arguments);
});
once._callback = callback;
}
return map;
};
// 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).
Events.trigger = function(name) {
if (!this._events) return this;
var length = Math.max(0, arguments.length - 1);
var args = Array(length);
for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
eventsApi(triggerApi, this._events, name, void 0, args);
return this;
};
// Handles triggering the appropriate event callbacks.
var triggerApi = function(objEvents, name, callback, args) {
if (objEvents) {
var events = objEvents[name];
var allEvents = objEvents.all;
if (events && allEvents) allEvents = allEvents.slice();
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, [name].concat(args));
}
return objEvents;
};
// A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
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); return;
}
};
// A listening class that tracks and cleans up memory bindings
// when all callbacks have been offed.
var Listening = function(listener, obj) {
this.id = listener._listenId;
this.listener = listener;
this.obj = obj;
this.interop = true;
this.count = 0;
this._events = void 0;
};
Listening.prototype.on = Events.on;
// Offs a callback (or several).
// Uses an optimized counter if the listenee uses Backbone.Events.
// Otherwise, falls back to manual tracking to support events
// library interop.
Listening.prototype.off = function(name, callback) {
var cleanup;
if (this.interop) {
this._events = eventsApi(offApi, this._events, name, callback, {
context: void 0,
listeners: void 0
});
cleanup = !this._events;
} else {
this.count--;
cleanup = this.count === 0;
}
if (cleanup) this.cleanup();
};
// Cleans up memory bindings between the listener and the listenee.
Listening.prototype.cleanup = function() {
delete this.listener._listeningTo[this.obj._listenId];
if (!this.interop) delete this.obj._listeners[this.id];
};
// 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
// --------------
// Backbone **Models** are the basic data object in the framework --
// frequently representing a row in a table in a database on your server.
// A discrete chunk of data and a bunch of useful, related methods for
// performing computations and transformations on that data.
// Create a new model with the specified attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) {
var attrs = attributes || {};
options || (options = {});
this.preinitialize.apply(this, arguments);
this.cid = _.uniqueId(this.cidPrefix);
this.attributes = {};
if (options.collection) this.collection = options.collection;
if (options.parse) attrs = this.parse(attrs, options) || {};
var defaults = _.result(this, 'defaults');
// Just _.defaults would work fine, but the additional _.extends
// is in there for historical reasons. See #3843.
attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
this.set(attrs, options);
this.changed = {};
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,
// The value returned during the last failed validation.
validationError: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
// The prefix is used to create the client id which is used to identify models locally.
// You may want to override this if you're experiencing name clashes with model ids.
cidPrefix: 'c',
// preinitialize is an empty function by default. You can override it with a function
// or object. preinitialize will run before any instantiation logic is run in the Model.
preinitialize: function(){},
// 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);
},
// Proxy `Backbone.sync` by default -- but override this if you need
// custom syncing semantics for *this* particular model.
sync: function() {
return Backbone.sync.apply(this, arguments);
},
// Get the value of an attribute.
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
return _.escape(this.get(attr));
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Special-cased proxy to underscore's `_.matches` method.
matches: function(attrs) {
return !!_.iteratee(attrs, this)(this.attributes);
},
// Set a hash of model attributes on the object, firing `"change"`. This is
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
set: function(key, val, options) {
if (key == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
var attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options || (options = {});
// Run validation.
if (!this._validate(attrs, options)) return false;
// Extract attributes and options.
var unset = options.unset;
var silent = options.silent;
var changes = [];
var changing = this._changing;
this._changing = true;
if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
var current = this.attributes;
var changed = this.changed;
var prev = this._previousAttributes;
// For each `set` attribute, update or delete the current value.
for (var attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
changed[attr] = val;
} else {
delete changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
// Update the `id`.
if (this.idAttribute in attrs) {
var prevId = this.id;
this.id = this.get(this.idAttribute);
if (this.id !== prevId) {
this.trigger('changeId', this, prevId, options);
}
}
// Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = options;
for (var i = 0; i < changes.length; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
}
// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing) return this;
if (!silent) {
while (this._pending) {
options = this._pending;
this._pending = false;
this.trigger('change', this, options);
}
}
this._pending = false;
this._changing = false;
return this;
},
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
// if the attribute doesn't exist.
unset: function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
},
// Clear all attributes on the model, firing `"change"`.
clear: function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;
return this.set(attrs, _.extend({}, options, {unset: true}));
},
// 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 (attr == null) 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 old = this._changing ? this._previousAttributes : this.attributes;
var changed = {};
var hasChanged;
for (var attr in diff) {
var val = diff[attr];
if (_.isEqual(old[attr], val)) continue;
changed[attr] = val;
hasChanged = true;
}
return hasChanged ? changed : false;
},
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous: function(attr) {
if (attr == null || !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);
},
// Fetch the model from the server, merging the response with the model's
// local attributes. Any changed attributes will trigger a "change" event.
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
return this.sync('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, val, options) {
// Handle both `"key", value` and `{key: value}` -style arguments.
var attrs;
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.extend({validate: true, parse: true}, options);
var wait = options.wait;
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !wait) {
if (!this.set(attrs, options)) return false;
} else if (!this._validate(attrs, 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;
var attributes = this.attributes;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
if (serverAttrs && !model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
// Set temporary attributes if `{wait: true}` to properly find new ids.
if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
if (method === 'patch' && !options.attrs) options.attrs = attrs;
var xhr = this.sync(method, this, options);
// Restore attributes.
this.attributes = attributes;
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 wait = options.wait;
var destroy = function() {
model.stopListening();
model.trigger('destroy', model, model.collection, options);
};
options.success = function(resp) {
if (wait) destroy();
if (success) success.call(options.context, model, resp, options);
if (!model.isNew()) model.trigger('sync', model, resp, options);
};
var xhr = false;
if (this.isNew()) {
_.defer(options.success);
} else {
wrapError(this, options);
xhr = this.sync('delete', this, options);
}
if (!wait) destroy();
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 =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
var id = this.get(this.idAttribute);
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(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, options) {
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.has(this.idAttribute);
},
// Check if the model is currently in a valid state.
isValid: function(options) {
return this._validate({}, _.extend({}, options, {validate: true}));
},
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
_validate: function(attrs, options) {
if (!options.validate || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
return false;
}
});
// Backbone.Collection
// -------------------
// If models tend to represent a single row of data, a Backbone Collection is
// more analogous to a table full of data ... or a small slice or page of that
// table, or a collection of rows that belong together for a particular reason
// -- all of the messages in this particular folder, all of the documents
// belonging to this particular author, and so on. Collections maintain
// indexes of their models, both in order, and for lookup by `id`.
// Create a new **Collection**, perhaps to contain a specific type of `model`.
// 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 = {});
this.preinitialize.apply(this, arguments);
if (options.model) this.model = options.model;
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, _.extend({silent: true}, options));
};
// Default options for `Collection#set`.
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false};
// Splices `insert` into `array` at index `at`.
var splice = function(array, insert, at) {
at = Math.min(Math.max(at, 0), array.length);
var tail = Array(array.length - at);
var length = insert.length;
var i;
for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
for (i = 0; i < length; i++) array[i + at] = insert[i];
for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
};
// 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,
// preinitialize is an empty function by default. You can override it with a function
// or object. preinitialize will run before any instantiation logic is run in the Collection.
preinitialize: function(){},
// 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); });
},
// Proxy `Backbone.sync` by default.
sync: function() {
return Backbone.sync.apply(this, arguments);
},
// Add a model, or list of models to the set. `models` may be Backbone
// Models or raw JavaScript objects to be converted to Models, or any
// combination of the two.
add: function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
},
// Remove a model, or a list of models from the set.
remove: function(models, options) {
options = _.extend({}, options);
var singular = !_.isArray(models);
models = singular ? [models] : models.slice();
var removed = this._removeModels(models, options);
if (!options.silent && removed.length) {
options.changes = {added: [], merged: [], removed: removed};
this.trigger('update', this, options);
}
return singular ? removed[0] : removed;
},
// Update a collection by `set`-ing a new list of models, adding new ones,
// removing models that are no longer present, and merging models that
// already exist in the collection, as necessary. Similar to **Model#set**,
// the core operation for updating the data contained by the collection.
set: function(models, options) {
if (models == null) return;
options = _.extend({}, setOptions, options);
if (options.parse && !this._isModel(models)) {
models = this.parse(models, options) || [];
}
var singular = !_.isArray(models);
models = singular ? [models] : models.slice();
var at = options.at;
if (at != null) at = +at;
if (at > this.length) at = this.length;
if (at < 0) at += this.length + 1;
var set = [];
var toAdd = [];
var toMerge = [];
var toRemove = [];
var modelMap = {};
var add = options.add;
var merge = options.merge;
var remove = options.remove;
var sort = false;
var sortable = this.comparator && at == null && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
// Turn bare objects into model references, and prevent invalid models
// from being added.
var model, i;
for (i = 0; i < models.length; i++) {
model = models[i];
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
var existing = this.get(model);
if (existing) {
if (merge && model !== existing) {
var attrs = this._isModel(model) ? model.attributes : model;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
toMerge.push(existing);
if (sortable && !sort) sort = existing.hasChanged(sortAttr);
}
if (!modelMap[existing.cid]) {
modelMap[existing.cid] = true;
set.push(existing);
}
models[i] = existing;
// If this is a new, valid model, push it to the `toAdd` list.
} else if (add) {
model = models[i] = this._prepareModel(model, options);
if (model) {
toAdd.push(model);
this._addReference(model, options);
modelMap[model.cid] = true;
set.push(model);
}
}
}
// Remove stale models.
if (remove) {
for (i = 0; i < this.length; i++) {
model = this.models[i];
if (!modelMap[model.cid]) toRemove.push(model);
}
if (toRemove.length) this._removeModels(toRemove, options);
}
// See if sorting is needed, update `length` and splice in new models.
var orderChanged = false;
var replace = !sortable && add && remove;
if (set.length && replace) {
orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
return m !== set[index];
});
this.models.length = 0;
splice(this.models, set, 0);
this.length = this.models.length;
} else if (toAdd.length) {
if (sortable) sort = true;
splice(this.models, toAdd, at == null ? this.length : at);
this.length = this.models.length;
}
// Silently sort the collection if appropriate.
if (sort) this.sort({silent: true});
// Unless silenced, it's time to fire all appropriate add/sort/update events.
if (!options.silent) {
for (i = 0; i < toAdd.length; i++) {
if (at != null) options.index = at + i;
model = toAdd[i];
model.trigger('add', model, this, options);
}
if (sort || orderChanged) this.trigger('sort', this, options);
if (toAdd.length || toRemove.length || toMerge.length) {
options.changes = {
added: toAdd,
removed: toRemove,
merged: toMerge
};
this.trigger('update', this, options);
}
}
// Return the added (or merged) model (or models).
return singular ? models[0] : models;
},
// 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 granular `add` or `remove` events. Fires `reset` when finished.
// Useful for bulk operations and optimizations.
reset: function(models, options) {
options = options ? _.clone(options) : {};
for (var i = 0; i < this.models.length; i++) {
this._removeReference(this.models[i], options);
}
options.previousModels = this.models;
this._reset();
models = this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return models;
},
// Add a model to the end of the collection.
push: function(model, options) {
return this.add(model, _.extend({at: this.length}, options));
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
return this.remove(model, options);
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
return this.add(model, _.extend({at: 0}, options));
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
return this.remove(model, options);
},
// Slice out a sub-array of models from the collection.
slice: function() {
return slice.apply(this.models, arguments);
},
// Get a model from the set by id, cid, model object with id or cid
// properties, or an attributes object that is transformed through modelId.
get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj] ||
this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] ||
obj.cid && this._byId[obj.cid];
},
// Returns `true` if the model is in the collection.
has: function(obj) {
return this.get(obj) != null;
},
// Get the model at the given index.
at: function(index) {
if (index < 0) index += this.length;
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of
// `filter`.
where: function(attrs, first) {
return this[first ? 'find' : 'filter'](attrs);
},
// Return the first model with matching attributes. Useful for simple cases
// of `find`.
findWhere: function(attrs) {
return this.where(attrs, 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) {
var comparator = this.comparator;
if (!comparator) throw new Error('Cannot sort a set without a comparator');
options || (options = {});
var length = comparator.length;
if (_.isFunction(comparator)) comparator = comparator.bind(this);
// Run sort based on type of `comparator`.
if (length === 1 || _.isString(comparator)) {
this.models = this.sortBy(comparator);
} else {
this.models.sort(comparator);
}
if (!options.silent) this.trigger('sort', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return this.map(attr + '');
},
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
options = _.extend({parse: true}, options);
var success = options.success;
var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options);
if (success) success.call(options.context, collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
wrapError(this, options);
return this.sync('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) {
options = options ? _.clone(options) : {};
var wait = options.wait;
model = this._prepareModel(model, options);
if (!model) return false;
if (!wait) this.add(model, options);
var collection = this;
var success = options.success;
options.success = function(m, resp, callbackOpts) {
if (wait) {
m.off('error', collection._forwardPristineError, collection);
collection.add(m, callbackOpts);
}
if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
};
// In case of wait:true, our collection is not listening to any
// of the model's events yet, so it will not forward the error
// event. In this special case, we need to listen for it
// separately and handle the event just once.
// (The reason we don't need to do this for the sync event is
// in the success handler above: we add the model first, which
// causes the collection to listen, and then invoke the callback
// that triggers the event.)
if (wait) {
model.once('error', this._forwardPristineError, this);
}
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, options) {
return resp;
},
// Create a new collection with an identical list of models as this one.
clone: function() {
return new this.constructor(this.models, {
model: this.model,
comparator: this.comparator
});
},
// Define how to uniquely identify models in the collection.
modelId: function(attrs, idAttribute) {
return attrs[idAttribute || this.model.prototype.idAttribute || 'id'];
},
// Get an iterator of all models in this collection.
values: function() {
return new CollectionIterator(this, ITERATOR_VALUES);
},
// Get an iterator of all model IDs in this collection.
keys: function() {
return new CollectionIterator(this, ITERATOR_KEYS);
},
// Get an iterator of all [ID, model] tuples in this collection.
entries: function() {
return new CollectionIterator(this, ITERATOR_KEYSVALUES);
},
// Private method to reset all internal state. Called when the collection
// is first initialized or reset.
_reset: function() {
this.length = 0;
this.models = [];
this._byId = {};
},
// Prepare a hash of attributes (or other model) to be added to this
// collection.
_prepareModel: function(attrs, options) {
if (this._isModel(attrs)) {
if (!attrs.collection) attrs.collection = this;
return attrs;
}
options = options ? _.clone(options) : {};
options.collection = this;
var model;
if (this.model.prototype) {
model = new this.model(attrs, options);
} else {
// ES class methods didn't have prototype
model = this.model(attrs, options);
}
if (!model.validationError) return model;
this.trigger('invalid', this, model.validationError, options);
return false;
},
// Internal method called by both remove and set.
_removeModels: function(models, options) {
var removed = [];
for (var i = 0; i < models.length; i++) {
var model = this.get(models[i]);
if (!model) continue;
var index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
// Remove references before triggering 'remove' event to prevent an
// infinite loop. #3693
delete this._byId[model.cid];
var id = this.modelId(model.attributes, model.idAttribute);
if (id != null) delete this._byId[id];
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
removed.push(model);
this._removeReference(model, options);
}
if (models.length > 0 && !options.silent) delete options.index;
return removed;
},
// Method for checking whether an object should be considered a model for
// the purposes of adding to the collection.
_isModel: function(model) {
return model instanceof Model;
},
// Internal method to create a model's ties to a collection.
_addReference: function(model, options) {
this._byId[model.cid] = model;
var id = this.modelId(model.attributes, model.idAttribute);
if (id != null) this._byId[id] = model;
model.on('all', this._onModelEvent, this);
},
// Internal method to sever a model's ties to a collection.
_removeReference: function(model, options) {
delete this._byId[model.cid];
var id = this.modelId(model.attributes, model.idAttribute);
if (id != null) delete this._byId[id];
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 (model) {
if ((event === 'add' || event === 'remove') && collection !== this) return;
if (event === 'destroy') this.remove(model, options);
if (event === 'changeId') {
var prevId = this.modelId(model.previousAttributes(), model.idAttribute);
var id = this.modelId(model.attributes, model.idAttribute);
if (prevId != null) delete this._byId[prevId];
if (id != null) this._byId[id] = model;
}
}
this.trigger.apply(this, arguments);
},
// Internal callback method used in `create`. It serves as a
// stand-in for the `_onModelEvent` method, which is not yet bound
// during the `wait` period of the `create` call. We still want to
// forward any `'error'` event at the end of the `wait` period,
// hence a customized callback.
_forwardPristineError: function(model, collection, options) {
// Prevent double forward if the model was already in the
// collection before the call to `create`.
if (this.has(model)) return;
this._onModelEvent('error', model, collection, options);
}
});
// Defining an @@iterator method implements JavaScript's Iterable protocol.
// In modern ES2015 browsers, this value is found at Symbol.iterator.
/* global Symbol */
var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
if ($$iterator) {
Collection.prototype[$$iterator] = Collection.prototype.values;
}
// CollectionIterator
// ------------------
// A CollectionIterator implements JavaScript's Iterator protocol, allowing the
// use of `for of` loops in modern browsers and interoperation between
// Backbone.Collection and other JavaScript functions and third-party libraries
// which can operate on Iterables.
var CollectionIterator = function(collection, kind) {
this._collection = collection;
this._kind = kind;
this._index = 0;
};
// This "enum" defines the three possible kinds of values which can be emitted
// by a CollectionIterator that correspond to the values(), keys() and entries()
// methods on Collection, respectively.
var ITERATOR_VALUES = 1;
var ITERATOR_KEYS = 2;
var ITERATOR_KEYSVALUES = 3;
// All Iterators should themselves be Iterable.
if ($$iterator) {
CollectionIterator.prototype[$$iterator] = function() {
return this;
};
}
CollectionIterator.prototype.next = function() {
if (this._collection) {
// Only continue iterating if the iterated collection is long enough.
if (this._index < this._collection.length) {
var model = this._collection.at(this._index);
this._index++;
// Construct a value depending on what kind of values should be iterated.
var value;
if (this._kind === ITERATOR_VALUES) {
value = model;
} else {
var id = this._collection.modelId(model.attributes, model.idAttribute);
if (this._kind === ITERATOR_KEYS) {
value = id;
} else { // ITERATOR_KEYSVALUES
value = [id, model];
}
}
return {value: value, done: false};
}
// Once exhausted, remove the reference to the collection so future
// calls to the next method always return done.
this._collection = void 0;
}
return {value: void 0, done: true};
};
// 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.
// 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.preinitialize.apply(this, arguments);
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
};
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// List of view options to be set as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
// 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 this.$el.find(selector);
},
// preinitialize is an empty function by default. You can override it with a function
// or object. preinitialize will run before any instantiation logic is run in the View
preinitialize: function(){},
// 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 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`. `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) {
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) {
events || (events = _.result(this, 'events'));
if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], method.bind(this));
}
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);
return this;
},
// 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);
return this;
},
// 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.setElement(_.result(this, 'el'));
}
},
// 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);
}
});
// Proxy Backbone class methods to Underscore functions, wrapping the model's
// `attributes` object or collection's `models` array behind the scenes.
//
// collection.filter(function(model) { return model.get('age') > 10 });
// collection.each(this.addView);
//
// `Function#apply` can be slow so we use the method's arg count, if we know it.
var addMethod = function(base, length, method, attribute) {
switch (length) {
case 1: return function() {
return base[method](this[attribute]);
};
case 2: return function(value) {
return base[method](this[attribute], value);
};
case 3: return function(iteratee, context) {
return base[method](this[attribute], cb(iteratee, this), context);
};
case 4: return function(iteratee, defaultVal, context) {
return base[method](this[attribute], cb(iteratee, this), defaultVal, context);
};
default: return function() {
var args = slice.call(arguments);
args.unshift(this[attribute]);
return base[method].apply(base, args);
};
}
};
var addUnderscoreMethods = function(Class, base, methods, attribute) {
_.each(methods, function(length, method) {
if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute);
});
};
// Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
var cb = function(iteratee, instance) {
if (_.isFunction(iteratee)) return iteratee;
if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
return iteratee;
};
var modelMatcher = function(attrs) {
var matcher = _.matches(attrs);
return function(model) {
return matcher(model.attributes);
};
};
// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
// Underscore methods that we want to implement on the Model, mapped to the
// number of arguments they take.
var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
omit: 0, chain: 1, isEmpty: 1};
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each([
[Collection, collectionMethods, 'models'],
[Model, modelMethods, 'attributes']
], function(config) {
var Base = config[0],
methods = config[1],
attribute = config[2];
Base.mixin = function(obj) {
var mappings = _.reduce(_.functions(obj), function(memo, name) {
memo[name] = 0;
return memo;
}, {});
addUnderscoreMethods(Base, obj, mappings, attribute);
};
addUnderscoreMethods(Base, _, methods, attribute);
});
// 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
});
// 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;
}
// Pass along `textStatus` and `errorThrown` from jQuery.
var error = options.error;
options.error = function(xhr, textStatus, errorThrown) {
options.textStatus = textStatus;
options.errorThrown = errorThrown;
if (error) error.call(options.context, xhr, textStatus, errorThrown);
};
// 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 = {});
this.preinitialize.apply(this, arguments);
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;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
// preinitialize is an empty function by default. You can override it with a function
// or object. preinitialize will run before any instantiation logic is run in the Router.
preinitialize: function(){},
// 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 (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
if (router.execute(callback, args, name) !== false) {
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
}
});
return this;
},
// 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, name) {
if (callback) callback.apply(this, args);
},
// 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 = _.keys(this.routes);
while ((route = routes.pop()) != null) {
this.route(route, this.routes[route]);
}
},
// 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 _.map(params, function(param, i) {
// Don't decode the search params.
if (i === params.length - 1) return param || null;
return param ? decodeURIComponent(param) : null;
});
}
});
// 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. If the browser supports neither (old IE, natch),
// falls back to polling.
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;
}
};
// 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 stripping urls of hash.
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, {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval: 50,
// Are we at the app root?
atRoot: function() {
var path = this.location.pathname.replace(/[^\/]$/, '$&/');
return path === this.root && !this.getSearch();
},
// Does the pathname match the root?
matchRoot: function() {
var path = this.decodeFragment(this.location.pathname);
var rootPath = path.slice(0, this.root.length - 1) + '/';
return rootPath === this.root;
},
// Unicode characters in `location.pathname` are percent encoded so they're
// decoded for comparison. `%25` should not be decoded since it may be part
// of an encoded parameter.
decodeFragment: function(fragment) {
return decodeURI(fragment.replace(/%25/g, '%2525'));
},
// In IE6, the hash fragment and search params are incorrect if the
// fragment contains `?`.
getSearch: function() {
var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
return match ? match[0] : '';
},
// 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 pathname and search params, without the root.
getPath: function() {
var path = this.decodeFragment(
this.location.pathname + this.getSearch()
).slice(this.root.length - 1);
return path.charAt(0) === '/' ? path.slice(1) : path;
},
// Get the cross-browser normalized URL fragment from the path or hash.
getFragment: function(fragment) {
if (fragment == null) {
if (this._usePushState || !this._wantsHashChange) {
fragment = this.getPath();
} 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. Do we need an iframe?
// Is pushState desired ... is it available?
this.options = _.extend({root: '/'}, this.options, options);
this.root = this.options.root;
this._trailingSlash = this.options.trailingSlash;
this._wantsHashChange = this.options.hashChange !== false;
this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
this._useHashChange = this._wantsHashChange && this._hasHashChange;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.history && this.history.pushState);
this._usePushState = this._wantsPushState && this._hasPushState;
this.fragment = this.getFragment();
// Normalize root to always include a leading and trailing slash.
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
// Transition from hashChange to pushState or vice versa if both are
// requested.
if (this._wantsHashChange && this._wantsPushState) {
// 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._hasPushState && !this.atRoot()) {
var rootPath = this.root.slice(0, -1) || '/';
this.location.replace(rootPath + '#' + this.getPath());
// 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._hasPushState && this.atRoot()) {
this.navigate(this.getHash(), {replace: true});
}
}
// Proxy an iframe to handle location events if the browser doesn't
// support the `hashchange` event, HTML5 history, or the user wants
// `hashChange` but not `pushState`.
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
this.iframe = document.createElement('iframe');
this.iframe.src = 'javascript:0';
this.iframe.style.display = 'none';
this.iframe.tabIndex = -1;
var body = document.body;
// Using `appendChild` will throw on IE < 9 if the document is not ready.
var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
iWindow.document.open();
iWindow.document.close();
iWindow.location.hash = '#' + this.fragment;
}
// Add a cross-platform `addEventListener` shim for older browsers.
var addEventListener = window.addEventListener || function(eventName, listener) {
return attachEvent('on' + eventName, listener);
};
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._usePushState) {
addEventListener('popstate', this.checkUrl, false);
} else if (this._useHashChange && !this.iframe) {
addEventListener('hashchange', this.checkUrl, false);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
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() {
// Add a cross-platform `removeEventListener` shim for older browsers.
var removeEventListener = window.removeEventListener || function(eventName, listener) {
return detachEvent('on' + eventName, listener);
};
// Remove window listeners.
if (this._usePushState) {
removeEventListener('popstate', this.checkUrl, false);
} else if (this._useHashChange && !this.iframe) {
removeEventListener('hashchange', this.checkUrl, false);
}
// Clean up the iframe if necessary.
if (this.iframe) {
document.body.removeChild(this.iframe);
this.iframe = null;
}
// Some environments will throw when clearing an undefined interval.
if (this._checkUrlInterval) 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 the user pressed the back button, the iframe's hash will have
// changed and we should use that for comparison.
if (current === this.fragment && this.iframe) {
current = this.getHash(this.iframe.contentWindow);
}
if (current === this.fragment) {
if (!this.matchRoot()) return this.notfound();
return false;
}
if (this.iframe) this.navigate(current);
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) {
// If the root doesn't match, no routes can match either.
if (!this.matchRoot()) return this.notfound();
fragment = this.fragment = this.getFragment(fragment);
return _.some(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
}) || this.notfound();
},
// When no route could be matched, this method is called internally to
// trigger the `'notfound'` event. It returns `false` so that it can be used
// in tail position.
notfound: function() {
this.trigger('notfound');
return false;
},
// 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};
// Normalize the fragment.
fragment = this.getFragment(fragment || '');
// Strip trailing slash on the root unless _trailingSlash is true
var rootPath = this.root;
if (!this._trailingSlash && (fragment === '' || fragment.charAt(0) === '?')) {
rootPath = rootPath.slice(0, -1) || '/';
}
var url = rootPath + fragment;
// Strip the fragment of the query and hash for matching.
fragment = fragment.replace(pathStripper, '');
// Decode for matching.
var decodedFragment = this.decodeFragment(fragment);
if (this.fragment === decodedFragment) return;
this.fragment = decodedFragment;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._usePushState) {
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 (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
var iWindow = this.iframe.contentWindow;
// 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) {
iWindow.document.open();
iWindow.document.close();
}
this._updateHash(iWindow.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;
}
}
});
// Create the default Backbone.history.
Backbone.history = new History;
// Helpers
// -------
// 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 extend = function(protoProps, staticProps) {
var parent = this;
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 constructor.
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function and add the prototype properties.
child.prototype = _.create(parent.prototype, protoProps);
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
};
// Set up inheritance for the model, collection, router, view and history.
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
// 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');
};
// Wrap an optional error callback with a fallback error event.
var wrapError = function(model, options) {
var error = options.error;
options.error = function(resp) {
if (error) error.call(options.context, model, resp, options);
model.trigger('error', model, resp, options);
};
};
// Provide useful information when things go wrong. This method is not meant
// to be used directly; it merely provides the necessary introspection for the
// external `debugInfo` function.
Backbone._debug = function() {
return {root: root, _: _};
};
return Backbone;
});
================================================
FILE: bower.json
================================================
{
"name" : "backbone",
"main" : "backbone.js",
"dependencies" : {
"underscore" : ">=1.8.3"
},
"ignore" : ["docs", "examples", "test", "*.yml", "*.html", "*.ico", "*.md", "CNAME", ".*", "karma.*", "package.json"]
}
================================================
FILE: debug-info.js
================================================
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone')) :
typeof define === 'function' && define.amd ? define(['backbone'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, (global.Backbone = global.Backbone || {}, global.Backbone.debugInfo = factory(global.Backbone)));
})(this, (function (Backbone) {
// Provide useful information when things go wrong.
function debugInfo() {
// Introspect Backbone.
var $ = Backbone.$, _b = Backbone._debug(), _ = _b._, root = _b.root;
// Use the `partialRight` function as a Lodash indicator. It was never in
// Underscore, has been in Lodash at least since version 1.3.1, and is
// unlikely to be mixed into Underscore since nobody needs it.
var lodash = !!_.partialRight;
var info = {
backbone: Backbone.VERSION,
// Is this the exact released version, or a later development version?
/* This is automatically temporarily replaced when publishing a release,
so please don't edit this. */
distribution: 'MARK_DEVELOPMENT',
_: (lodash ? 'lodash ' : '') + _.VERSION,
$: !$ ? false : $.fn && $.fn.jquery ? $.fn.jquery :
$.zepto ? 'zepto' : $.ender ? 'ender' : true
};
if (typeof root.Deno !== 'undefined') {
info.deno = _.pick(root.Deno, 'version', 'build');
} else if (typeof root.process !== 'undefined') {
info.process = _.pick(root.process, 'version', 'platform', 'arch');
} else if (typeof root.navigator !== 'undefined') {
info.navigator = _.pick(root.navigator, 'userAgent', 'platform', 'webdriver');
}
/* eslint-disable-next-line no-console */
console.debug('Backbone debug info: ', JSON.stringify(info, null, 4));
return info;
}
return debugInfo;
}));
================================================
FILE: docs/backbone.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>backbone.js</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<link rel="stylesheet" media="all" href="docco.css" />
</head>
<body>
<div id="container">
<div id="background"></div>
<ul class="sections">
<li id="title">
<div class="annotation">
<h1>backbone.js</h1>
</div>
</li>
<li id="section-1">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-1">§</a>
</div>
<pre><code><span class="hljs-title class_">Backbone</span>.<span class="hljs-property">js</span> <span class="hljs-number">1.6</span><span class="hljs-number">.1</span>
</code></pre>
</div>
</li>
<li id="section-2">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-2">§</a>
</div>
<pre><code>(c) <span class="hljs-number">2010</span>-<span class="hljs-number">2024</span> <span class="hljs-title class_">Jeremy</span> <span class="hljs-title class_">Ashkenas</span> and <span class="hljs-title class_">DocumentCloud</span>
<span class="hljs-title class_">Backbone</span> may be freely distributed under the <span class="hljs-variable constant_">MIT</span> license.
<span class="hljs-title class_">For</span> all details and <span class="hljs-attr">documentation</span>:
<span class="hljs-attr">http</span>:<span class="hljs-comment">//backbonejs.org</span>
</code></pre>
</div>
<div class="content"><div class='highlight'><pre>
(<span class="hljs-keyword">function</span>(<span class="hljs-params">factory</span>) {</pre></div></div>
</li>
<li id="section-3">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-3">§</a>
</div>
<p>Establish the root object, <code>window</code> (<code>self</code>) in the browser, or <code>global</code> on the server.
We use <code>self</code> instead of <code>window</code> for <code>WebWorker</code> support.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> root = <span class="hljs-keyword">typeof</span> self == <span class="hljs-string">'object'</span> && self.<span class="hljs-property">self</span> === self && self ||
<span class="hljs-keyword">typeof</span> <span class="hljs-variable language_">global</span> == <span class="hljs-string">'object'</span> && <span class="hljs-variable language_">global</span>.<span class="hljs-property">global</span> === <span class="hljs-variable language_">global</span> && <span class="hljs-variable language_">global</span>;</pre></div></div>
</li>
<li id="section-4">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-4">§</a>
</div>
<p>Set up Backbone appropriately for the environment. Start with AMD.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> define === <span class="hljs-string">'function'</span> && define.<span class="hljs-property">amd</span>) {
<span class="hljs-title function_">define</span>([<span class="hljs-string">'underscore'</span>, <span class="hljs-string">'jquery'</span>, <span class="hljs-string">'exports'</span>], <span class="hljs-keyword">function</span>(<span class="hljs-params">_, $, <span class="hljs-built_in">exports</span></span>) {</pre></div></div>
</li>
<li id="section-5">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-5">§</a>
</div>
<p>Export global even in AMD case in case this script is loaded with
others that may still expect a global Backbone.</p>
</div>
<div class="content"><div class='highlight'><pre> root.<span class="hljs-property">Backbone</span> = <span class="hljs-title function_">factory</span>(root, <span class="hljs-built_in">exports</span>, _, $);
});</pre></div></div>
</li>
<li id="section-6">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-6">§</a>
</div>
<p>Next for Node.js or CommonJS. jQuery may not be needed as a module.</p>
</div>
<div class="content"><div class='highlight'><pre> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-built_in">exports</span> !== <span class="hljs-string">'undefined'</span>) {
<span class="hljs-keyword">var</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'underscore'</span>), $;
<span class="hljs-keyword">try</span> { $ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jquery'</span>); } <span class="hljs-keyword">catch</span> (e) {}
<span class="hljs-title function_">factory</span>(root, <span class="hljs-built_in">exports</span>, _, $);</pre></div></div>
</li>
<li id="section-7">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-7">§</a>
</div>
<p>Finally, as a browser global.</p>
</div>
<div class="content"><div class='highlight'><pre> } <span class="hljs-keyword">else</span> {
root.<span class="hljs-property">Backbone</span> = <span class="hljs-title function_">factory</span>(root, {}, root.<span class="hljs-property">_</span>, root.<span class="hljs-property">jQuery</span> || root.<span class="hljs-property">Zepto</span> || root.<span class="hljs-property">ender</span> || root.<span class="hljs-property">$</span>);
}
})(<span class="hljs-keyword">function</span>(<span class="hljs-params">root, Backbone, _, $</span>) {</pre></div></div>
</li>
<li id="section-8">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-8">§</a>
</div>
<h2 id="initial-setup">Initial Setup</h2>
</div>
</li>
<li id="section-9">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-9">§</a>
</div>
</div>
</li>
<li id="section-10">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-10">§</a>
</div>
<p>Save the previous value of the <code>Backbone</code> variable, so that it can be
restored later on, if <code>noConflict</code> is used.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> previousBackbone = root.<span class="hljs-property">Backbone</span>;</pre></div></div>
</li>
<li id="section-11">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-11">§</a>
</div>
<p>Create a local reference to a common array method we’ll want to use later.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> slice = <span class="hljs-title class_">Array</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">slice</span>;</pre></div></div>
</li>
<li id="section-12">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-12">§</a>
</div>
<p>Current version of the library. Keep in sync with <code>package.json</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">VERSION</span> = <span class="hljs-string">'1.6.1'</span>;</pre></div></div>
</li>
<li id="section-13">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-13">§</a>
</div>
<p>For Backbone’s purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
the <code>$</code> variable.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">$</span> = $;</pre></div></div>
</li>
<li id="section-14">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-14">§</a>
</div>
<p>Runs Backbone.js in <em>noConflict</em> mode, returning the <code>Backbone</code> variable
to its previous owner. Returns a reference to this Backbone object.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">noConflict</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
root.<span class="hljs-property">Backbone</span> = previousBackbone;
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
};</pre></div></div>
</li>
<li id="section-15">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-15">§</a>
</div>
<p>Turn on <code>emulateHTTP</code> to support legacy HTTP servers. Setting this option
will fake <code>"PATCH"</code>, <code>"PUT"</code> and <code>"DELETE"</code> requests via the <code>_method</code> parameter and
set a <code>X-Http-Method-Override</code> header.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">emulateHTTP</span> = <span class="hljs-literal">false</span>;</pre></div></div>
</li>
<li id="section-16">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-16">§</a>
</div>
<p>Turn on <code>emulateJSON</code> to support legacy servers that can’t deal with direct
<code>application/json</code> requests … this will encode the body as
<code>application/x-www-form-urlencoded</code> instead and will send the model in a
form param named <code>model</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">emulateJSON</span> = <span class="hljs-literal">false</span>;</pre></div></div>
</li>
<li id="section-17">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-17">§</a>
</div>
<h2 id="backboneevents">Backbone.Events</h2>
</div>
</li>
<li id="section-18">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-18">§</a>
</div>
</div>
</li>
<li id="section-19">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-19">§</a>
</div>
<p>A module that can be mixed in to <em>any object</em> in order to provide it with
a custom event channel. You may bind a callback to an event with <code>on</code> or
remove with <code>off</code>; <code>trigger</code>-ing an event fires all callbacks in
succession.</p>
<pre><code><span class="hljs-keyword">var</span> object = {};
_.<span class="hljs-title function_">extend</span>(object, <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">Events</span>);
object.<span class="hljs-title function_">on</span>(<span class="hljs-string">'expand'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){ <span class="hljs-title function_">alert</span>(<span class="hljs-string">'expanded'</span>); });
object.<span class="hljs-title function_">trigger</span>(<span class="hljs-string">'expand'</span>);
</code></pre>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> <span class="hljs-title class_">Events</span> = <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">Events</span> = {};</pre></div></div>
</li>
<li id="section-20">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-20">§</a>
</div>
<p>Regular expression used to split event strings.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> eventSplitter = <span class="hljs-regexp">/\s+/</span>;</pre></div></div>
</li>
<li id="section-21">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-21">§</a>
</div>
<p>A private global variable to share between listeners and listenees.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> _listening;</pre></div></div>
</li>
<li id="section-22">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-22">§</a>
</div>
<p>Iterates over the standard <code>event, callback</code> (as well as the fancy multiple
space-separated events <code>"change blur", callback</code> and jQuery-style event
maps <code>{event: callback}</code>).</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> eventsApi = <span class="hljs-keyword">function</span>(<span class="hljs-params">iteratee, events, name, callback, opts</span>) {
<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>, names;
<span class="hljs-keyword">if</span> (name && <span class="hljs-keyword">typeof</span> name === <span class="hljs-string">'object'</span>) {</pre></div></div>
</li>
<li id="section-23">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-23">§</a>
</div>
<p>Handle event maps.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (callback !== <span class="hljs-keyword">void</span> <span class="hljs-number">0</span> && <span class="hljs-string">'context'</span> <span class="hljs-keyword">in</span> opts && opts.<span class="hljs-property">context</span> === <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>) opts.<span class="hljs-property">context</span> = callback;
<span class="hljs-keyword">for</span> (names = _.<span class="hljs-title function_">keys</span>(name); i < names.<span class="hljs-property">length</span> ; i++) {
events = <span class="hljs-title function_">eventsApi</span>(iteratee, events, names[i], name[names[i]], opts);
}
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (name && eventSplitter.<span class="hljs-title function_">test</span>(name)) {</pre></div></div>
</li>
<li id="section-24">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-24">§</a>
</div>
<p>Handle space-separated event names by delegating them individually.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">for</span> (names = name.<span class="hljs-title function_">split</span>(eventSplitter); i < names.<span class="hljs-property">length</span>; i++) {
events = <span class="hljs-title function_">iteratee</span>(events, names[i], callback, opts);
}
} <span class="hljs-keyword">else</span> {</pre></div></div>
</li>
<li id="section-25">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-25">§</a>
</div>
<p>Finally, standard events.</p>
</div>
<div class="content"><div class='highlight'><pre> events = <span class="hljs-title function_">iteratee</span>(events, name, callback, opts);
}
<span class="hljs-keyword">return</span> events;
};</pre></div></div>
</li>
<li id="section-26">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-26">§</a>
</div>
<p>Bind an event to a <code>callback</code> function. Passing <code>"all"</code> will bind
the callback to all events fired.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">on</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">name, callback, context</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span> = <span class="hljs-title function_">eventsApi</span>(onApi, <span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span> || {}, name, callback, {
<span class="hljs-attr">context</span>: context,
<span class="hljs-attr">ctx</span>: <span class="hljs-variable language_">this</span>,
<span class="hljs-attr">listening</span>: _listening
});
<span class="hljs-keyword">if</span> (_listening) {
<span class="hljs-keyword">var</span> listeners = <span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeners</span> || (<span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeners</span> = {});
listeners[_listening.<span class="hljs-property">id</span>] = _listening;</pre></div></div>
</li>
<li id="section-27">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-27">§</a>
</div>
<p>Allow the listening to use a counter, instead of tracking
callbacks for library interop</p>
</div>
<div class="content"><div class='highlight'><pre> _listening.<span class="hljs-property">interop</span> = <span class="hljs-literal">false</span>;
}
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
};</pre></div></div>
</li>
<li id="section-28">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-28">§</a>
</div>
<p>Inversion-of-control versions of <code>on</code>. Tell <em>this</em> object to listen to
an event in another object… keeping track of what it’s listening to
for easier unbinding later.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">listenTo</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">obj, name, callback</span>) {
<span class="hljs-keyword">if</span> (!obj) <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
<span class="hljs-keyword">var</span> id = obj.<span class="hljs-property">_listenId</span> || (obj.<span class="hljs-property">_listenId</span> = _.<span class="hljs-title function_">uniqueId</span>(<span class="hljs-string">'l'</span>));
<span class="hljs-keyword">var</span> listeningTo = <span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeningTo</span> || (<span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeningTo</span> = {});
<span class="hljs-keyword">var</span> listening = _listening = listeningTo[id];</pre></div></div>
</li>
<li id="section-29">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-29">§</a>
</div>
<p>This object is not listening to any other events on <code>obj</code> yet.
Setup the necessary references to track the listening callbacks.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (!listening) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">_listenId</span> || (<span class="hljs-variable language_">this</span>.<span class="hljs-property">_listenId</span> = _.<span class="hljs-title function_">uniqueId</span>(<span class="hljs-string">'l'</span>));
listening = _listening = listeningTo[id] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Listening</span>(<span class="hljs-variable language_">this</span>, obj);
}</pre></div></div>
</li>
<li id="section-30">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-30">§</a>
</div>
<p>Bind callbacks on obj.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> error = <span class="hljs-title function_">tryCatchOn</span>(obj, name, callback, <span class="hljs-variable language_">this</span>);
_listening = <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">throw</span> error;</pre></div></div>
</li>
<li id="section-31">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-31">§</a>
</div>
<p>If the target obj is not Backbone.Events, track events manually.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (listening.<span class="hljs-property">interop</span>) listening.<span class="hljs-title function_">on</span>(name, callback);
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
};</pre></div></div>
</li>
<li id="section-32">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-32">§</a>
</div>
<p>The reducing API that adds a callback to the <code>events</code> object.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> onApi = <span class="hljs-keyword">function</span>(<span class="hljs-params">events, name, callback, options</span>) {
<span class="hljs-keyword">if</span> (callback) {
<span class="hljs-keyword">var</span> handlers = events[name] || (events[name] = []);
<span class="hljs-keyword">var</span> context = options.<span class="hljs-property">context</span>, ctx = options.<span class="hljs-property">ctx</span>, listening = options.<span class="hljs-property">listening</span>;
<span class="hljs-keyword">if</span> (listening) listening.<span class="hljs-property">count</span>++;
handlers.<span class="hljs-title function_">push</span>({<span class="hljs-attr">callback</span>: callback, <span class="hljs-attr">context</span>: context, <span class="hljs-attr">ctx</span>: context || ctx, <span class="hljs-attr">listening</span>: listening});
}
<span class="hljs-keyword">return</span> events;
};</pre></div></div>
</li>
<li id="section-33">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-33">§</a>
</div>
<p>An try-catch guarded #on function, to prevent poisoning the global
<code>_listening</code> variable.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> tryCatchOn = <span class="hljs-keyword">function</span>(<span class="hljs-params">obj, name, callback, context</span>) {
<span class="hljs-keyword">try</span> {
obj.<span class="hljs-title function_">on</span>(name, callback, context);
} <span class="hljs-keyword">catch</span> (e) {
<span class="hljs-keyword">return</span> e;
}
};</pre></div></div>
</li>
<li id="section-34">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-34">§</a>
</div>
<p>Remove one or many callbacks. If <code>context</code> is null, removes all
callbacks with that function. If <code>callback</code> is null, removes all
callbacks for the event. If <code>name</code> is null, removes all bound
callbacks for all events.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">off</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">name, callback, context</span>) {
<span class="hljs-keyword">if</span> (!<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span>) <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span> = <span class="hljs-title function_">eventsApi</span>(offApi, <span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span>, name, callback, {
<span class="hljs-attr">context</span>: context,
<span class="hljs-attr">listeners</span>: <span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeners</span>
});
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
};</pre></div></div>
</li>
<li id="section-35">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-35">§</a>
</div>
<p>Tell this object to stop listening to either specific events … or
to every object it’s currently listening to.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">stopListening</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">obj, name, callback</span>) {
<span class="hljs-keyword">var</span> listeningTo = <span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeningTo</span>;
<span class="hljs-keyword">if</span> (!listeningTo) <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
<span class="hljs-keyword">var</span> ids = obj ? [obj.<span class="hljs-property">_listenId</span>] : _.<span class="hljs-title function_">keys</span>(listeningTo);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i < ids.<span class="hljs-property">length</span>; i++) {
<span class="hljs-keyword">var</span> listening = listeningTo[ids[i]];</pre></div></div>
</li>
<li id="section-36">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-36">§</a>
</div>
<p>If listening doesn’t exist, this object is not currently
listening to obj. Break out early.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (!listening) <span class="hljs-keyword">break</span>;
listening.<span class="hljs-property">obj</span>.<span class="hljs-title function_">off</span>(name, callback, <span class="hljs-variable language_">this</span>);
<span class="hljs-keyword">if</span> (listening.<span class="hljs-property">interop</span>) listening.<span class="hljs-title function_">off</span>(name, callback);
}
<span class="hljs-keyword">if</span> (_.<span class="hljs-title function_">isEmpty</span>(listeningTo)) <span class="hljs-variable language_">this</span>.<span class="hljs-property">_listeningTo</span> = <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
};</pre></div></div>
</li>
<li id="section-37">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-37">§</a>
</div>
<p>The reducing API that removes a callback from the <code>events</code> object.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> offApi = <span class="hljs-keyword">function</span>(<span class="hljs-params">events, name, callback, options</span>) {
<span class="hljs-keyword">if</span> (!events) <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">var</span> context = options.<span class="hljs-property">context</span>, listeners = options.<span class="hljs-property">listeners</span>;
<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>, names;</pre></div></div>
</li>
<li id="section-38">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-38">§</a>
</div>
<p>Delete all event listeners and “drop” events.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (!name && !context && !callback) {
<span class="hljs-keyword">for</span> (names = _.<span class="hljs-title function_">keys</span>(listeners); i < names.<span class="hljs-property">length</span>; i++) {
listeners[names[i]].<span class="hljs-title function_">cleanup</span>();
}
<span class="hljs-keyword">return</span>;
}
names = name ? [name] : _.<span class="hljs-title function_">keys</span>(events);
<span class="hljs-keyword">for</span> (; i < names.<span class="hljs-property">length</span>; i++) {
name = names[i];
<span class="hljs-keyword">var</span> handlers = events[name];</pre></div></div>
</li>
<li id="section-39">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-39">§</a>
</div>
<p>Bail out if there are no events stored.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (!handlers) <span class="hljs-keyword">break</span>;</pre></div></div>
</li>
<li id="section-40">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-40">§</a>
</div>
<p>Find any remaining events.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> remaining = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j < handlers.<span class="hljs-property">length</span>; j++) {
<span class="hljs-keyword">var</span> handler = handlers[j];
<span class="hljs-keyword">if</span> (
callback && callback !== handler.<span class="hljs-property">callback</span> &&
callback !== handler.<span class="hljs-property">callback</span>.<span class="hljs-property">_callback</span> ||
context && context !== handler.<span class="hljs-property">context</span>
) {
remaining.<span class="hljs-title function_">push</span>(handler);
} <span class="hljs-keyword">else</span> {
<span class="hljs-keyword">var</span> listening = handler.<span class="hljs-property">listening</span>;
<span class="hljs-keyword">if</span> (listening) listening.<span class="hljs-title function_">off</span>(name, callback);
}
}</pre></div></div>
</li>
<li id="section-41">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-41">§</a>
</div>
<p>Replace events if there are any remaining. Otherwise, clean up.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> (remaining.<span class="hljs-property">length</span>) {
events[name] = remaining;
} <span class="hljs-keyword">else</span> {
<span class="hljs-keyword">delete</span> events[name];
}
}
<span class="hljs-keyword">return</span> events;
};</pre></div></div>
</li>
<li id="section-42">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-42">§</a>
</div>
<p>Bind an event to only be triggered a single time. After the first time
the callback is invoked, its listener will be removed. If multiple events
are passed in using the space-separated syntax, the handler will fire
once for each event, not once for a combination of all events.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">once</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">name, callback, context</span>) {</pre></div></div>
</li>
<li id="section-43">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-43">§</a>
</div>
<p>Map the event into a <code>{event: once}</code> object.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> events = <span class="hljs-title function_">eventsApi</span>(onceMap, {}, name, callback, <span class="hljs-variable language_">this</span>.<span class="hljs-property">off</span>.<span class="hljs-title function_">bind</span>(<span class="hljs-variable language_">this</span>));
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> name === <span class="hljs-string">'string'</span> && context == <span class="hljs-literal">null</span>) callback = <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">on</span>(events, callback, context);
};</pre></div></div>
</li>
<li id="section-44">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-44">§</a>
</div>
<p>Inversion-of-control versions of <code>once</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">listenToOnce</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">obj, name, callback</span>) {</pre></div></div>
</li>
<li id="section-45">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-45">§</a>
</div>
<p>Map the event into a <code>{event: once}</code> object.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> events = <span class="hljs-title function_">eventsApi</span>(onceMap, {}, name, callback, <span class="hljs-variable language_">this</span>.<span class="hljs-property">stopListening</span>.<span class="hljs-title function_">bind</span>(<span class="hljs-variable language_">this</span>, obj));
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">listenTo</span>(obj, events);
};</pre></div></div>
</li>
<li id="section-46">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-46">§</a>
</div>
<p>Reduces the event callbacks into a map of <code>{event: onceWrapper}</code>.
<code>offer</code> unbinds the <code>onceWrapper</code> after it has been called.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> onceMap = <span class="hljs-keyword">function</span>(<span class="hljs-params">map, name, callback, offer</span>) {
<span class="hljs-keyword">if</span> (callback) {
<span class="hljs-keyword">var</span> once = map[name] = _.<span class="hljs-title function_">once</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
<span class="hljs-title function_">offer</span>(name, once);
callback.<span class="hljs-title function_">apply</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-variable language_">arguments</span>);
});
once.<span class="hljs-property">_callback</span> = callback;
}
<span class="hljs-keyword">return</span> map;
};</pre></div></div>
</li>
<li id="section-47">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-47">§</a>
</div>
<p>Trigger one or many events, firing all bound callbacks. Callbacks are
passed the same arguments as <code>trigger</code> is, apart from the event name
(unless you’re listening on <code>"all"</code>, which will cause your callback to
receive the true name of the event as the first argument).</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">trigger</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">name</span>) {
<span class="hljs-keyword">if</span> (!<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span>) <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
<span class="hljs-keyword">var</span> length = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">max</span>(<span class="hljs-number">0</span>, <span class="hljs-variable language_">arguments</span>.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>);
<span class="hljs-keyword">var</span> args = <span class="hljs-title class_">Array</span>(length);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i < length; i++) args[i] = <span class="hljs-variable language_">arguments</span>[i + <span class="hljs-number">1</span>];
<span class="hljs-title function_">eventsApi</span>(triggerApi, <span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span>, name, <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>, args);
<span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>;
};</pre></div></div>
</li>
<li id="section-48">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-48">§</a>
</div>
<p>Handles triggering the appropriate event callbacks.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> triggerApi = <span class="hljs-keyword">function</span>(<span class="hljs-params">objEvents, name, callback, args</span>) {
<span class="hljs-keyword">if</span> (objEvents) {
<span class="hljs-keyword">var</span> events = objEvents[name];
<span class="hljs-keyword">var</span> allEvents = objEvents.<span class="hljs-property">all</span>;
<span class="hljs-keyword">if</span> (events && allEvents) allEvents = allEvents.<span class="hljs-title function_">slice</span>();
<span class="hljs-keyword">if</span> (events) <span class="hljs-title function_">triggerEvents</span>(events, args);
<span class="hljs-keyword">if</span> (allEvents) <span class="hljs-title function_">triggerEvents</span>(allEvents, [name].<span class="hljs-title function_">concat</span>(args));
}
<span class="hljs-keyword">return</span> objEvents;
};</pre></div></div>
</li>
<li id="section-49">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-49">§</a>
</div>
<p>A difficult-to-believe, but optimized internal dispatch function for
triggering events. Tries to keep the usual cases speedy (most internal
Backbone events have 3 arguments).</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> triggerEvents = <span class="hljs-keyword">function</span>(<span class="hljs-params">events, args</span>) {
<span class="hljs-keyword">var</span> ev, i = -<span class="hljs-number">1</span>, l = events.<span class="hljs-property">length</span>, a1 = args[<span class="hljs-number">0</span>], a2 = args[<span class="hljs-number">1</span>], a3 = args[<span class="hljs-number">2</span>];
<span class="hljs-keyword">switch</span> (args.<span class="hljs-property">length</span>) {
<span class="hljs-keyword">case</span> <span class="hljs-number">0</span>: <span class="hljs-keyword">while</span> (++i < l) (ev = events[i]).<span class="hljs-property">callback</span>.<span class="hljs-title function_">call</span>(ev.<span class="hljs-property">ctx</span>); <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">case</span> <span class="hljs-number">1</span>: <span class="hljs-keyword">while</span> (++i < l) (ev = events[i]).<span class="hljs-property">callback</span>.<span class="hljs-title function_">call</span>(ev.<span class="hljs-property">ctx</span>, a1); <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">case</span> <span class="hljs-number">2</span>: <span class="hljs-keyword">while</span> (++i < l) (ev = events[i]).<span class="hljs-property">callback</span>.<span class="hljs-title function_">call</span>(ev.<span class="hljs-property">ctx</span>, a1, a2); <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">case</span> <span class="hljs-number">3</span>: <span class="hljs-keyword">while</span> (++i < l) (ev = events[i]).<span class="hljs-property">callback</span>.<span class="hljs-title function_">call</span>(ev.<span class="hljs-property">ctx</span>, a1, a2, a3); <span class="hljs-keyword">return</span>;
<span class="hljs-attr">default</span>: <span class="hljs-keyword">while</span> (++i < l) (ev = events[i]).<span class="hljs-property">callback</span>.<span class="hljs-title function_">apply</span>(ev.<span class="hljs-property">ctx</span>, args); <span class="hljs-keyword">return</span>;
}
};</pre></div></div>
</li>
<li id="section-50">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-50">§</a>
</div>
<p>A listening class that tracks and cleans up memory bindings
when all callbacks have been offed.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> <span class="hljs-title class_">Listening</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">listener, obj</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">id</span> = listener.<span class="hljs-property">_listenId</span>;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">listener</span> = listener;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">obj</span> = obj;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">interop</span> = <span class="hljs-literal">true</span>;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">count</span> = <span class="hljs-number">0</span>;
<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span> = <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>;
};
<span class="hljs-title class_">Listening</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">on</span> = <span class="hljs-title class_">Events</span>.<span class="hljs-property">on</span>;</pre></div></div>
</li>
<li id="section-51">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-51">§</a>
</div>
<p>Offs a callback (or several).
Uses an optimized counter if the listenee uses Backbone.Events.
Otherwise, falls back to manual tracking to support events
library interop.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Listening</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">off</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">name, callback</span>) {
<span class="hljs-keyword">var</span> cleanup;
<span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<span class="hljs-property">interop</span>) {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span> = <span class="hljs-title function_">eventsApi</span>(offApi, <span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span>, name, callback, {
<span class="hljs-attr">context</span>: <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>,
<span class="hljs-attr">listeners</span>: <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>
});
cleanup = !<span class="hljs-variable language_">this</span>.<span class="hljs-property">_events</span>;
} <span class="hljs-keyword">else</span> {
<span class="hljs-variable language_">this</span>.<span class="hljs-property">count</span>--;
cleanup = <span class="hljs-variable language_">this</span>.<span class="hljs-property">count</span> === <span class="hljs-number">0</span>;
}
<span class="hljs-keyword">if</span> (cleanup) <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">cleanup</span>();
};</pre></div></div>
</li>
<li id="section-52">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-52">§</a>
</div>
<p>Cleans up memory bindings between the listener and the listenee.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Listening</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">cleanup</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">delete</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">listener</span>.<span class="hljs-property">_listeningTo</span>[<span class="hljs-variable language_">this</span>.<span class="hljs-property">obj</span>.<span class="hljs-property">_listenId</span>];
<span class="hljs-keyword">if</span> (!<span class="hljs-variable language_">this</span>.<span class="hljs-property">interop</span>) <span class="hljs-keyword">delete</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">obj</span>.<span class="hljs-property">_listeners</span>[<span class="hljs-variable language_">this</span>.<span class="hljs-property">id</span>];
};</pre></div></div>
</li>
<li id="section-53">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-53">§</a>
</div>
<p>Aliases for backwards compatibility.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-title class_">Events</span>.<span class="hljs-property">bind</span> = <span class="hljs-title class_">Events</span>.<span class="hljs-property">on</span>;
<span class="hljs-title class_">Events</span>.<span class="hljs-property">unbind</span> = <span class="hljs-title class_">Events</span>.<span class="hljs-property">off</span>;</pre></div></div>
</li>
<li id="section-54">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-54">§</a>
</div>
<p>Allow the <code>Backbone</code> object to serve as a global event bus, for folks who
want global “pubsub” in a convenient place.</p>
</div>
<div class="content"><div class='highlight'><pre> _.<span class="hljs-title function_">extend</span>(<span class="hljs-title class_">Backbone</span>, <span class="hljs-title class_">Events</span>);</pre></div></div>
</li>
<li id="section-55">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-55">§</a>
</div>
<h2 id="backbonemodel">Backbone.Model</h2>
</div>
</li>
<li id="section-56">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-56">§</a>
</div>
</div>
</li>
<li id="section-57">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-57">§</a>
</div>
<p>Backbone <strong>Models</strong> are the basic data object in the framework –
frequently representing a row in a table in a database on your server.
A discrete chunk of data and a bunch of useful, related methods for
performing computations and transformations on that data.</p>
</div>
</li>
<li id="section-58">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-58">§</a>
</div>
<p>Create a new model with the specified attributes. A client id (<code>cid</code>)
is automatically generated and assigned for you.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> <span class="hljs-title class_">Model</span> = <span class="hljs-title class_">Backbone</span>.<span class="hljs-property">Model</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">attributes, options</span>) {
<span class="hljs-keyword">var</span> attrs = attributes || {};
options || (options = {});
<span class="hljs-variable language_">this</span>.<span class="hljs-property">preinitialize</span>.<span class="hljs-title function_">apply</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-variable language_">arguments</span>);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">cid</span> = _.<span class="hljs-title function_">uniqueId</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">cidPrefix</span>);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">attributes</span> = {};
<span class="hljs-keyword">if</span> (options.<span class="hljs-property">collection</span>) <span class="hljs-variable language_">this</span>.<span class="hljs-property">collection</span> = options.<span class="hljs-property">collection</span>;
<span class="hljs-keyword">if</span> (options.<span class="hljs-property">parse</span>) attrs = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">parse</span>(attrs, options) || {};
<span class="hljs-keyword">var</span> defaults = _.<span class="hljs-title function_">result</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-string">'defaults'</span>);</pre></div></div>
</li>
<li id="section-59">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-59">§</a>
</div>
<p>Just _.defaults would work fine, but the additional _.extends
is in there for historical reasons. See #3843.</p>
</div>
<div class="content"><div class='highlight'><pre> attrs = _.<span class="hljs-title function_">defaults</span>(_.<span class="hljs-title function_">extend</span>({}, defaults, attrs), defaults);
<span class="hljs-variable language_">this</span>.<span class="hljs-title function_">set</span>(attrs, options);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">changed</span> = {};
<span class="hljs-variable language_">this</span>.<span class="hljs-property">initialize</span>.<span class="hljs-title function_">apply</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-variable language_">arguments</span>);
};</pre></div></div>
</li>
<li id="section-60">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-60">§</a>
</div>
<p>Attach all inheritable methods to the Model prototype.</p>
</div>
<div class="content"><div class='highlight'><pre> _.<span class="hljs-title function_">extend</span>(<span class="hljs-title class_">Model</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>, <span class="hljs-title class_">Events</span>, {</pre></div></div>
</li>
<li id="section-61">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-61">§</a>
</div>
<p>A hash of attributes whose current and previous value differ.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attr">changed</span>: <span class="hljs-literal">null</span>,</pre></div></div>
</li>
<li id="section-62">
<div class="annotation">
<div class="sswrap ">
<a class="ss" href="#section-62">§</a>
</div>
<p>The value returned during the last failed validation.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-attr">validationError</span>: <span class="hljs-literal">null</span>,</pre></div></div>
</li>
<li id="section-63">
<div class="annotation">
<div class="sswrap ">
gitextract_aenff3om/
├── .editorconfig
├── .eslintrc
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── Bugs.yml
│ │ ├── Documentation.yml
│ │ ├── Features.yml
│ │ └── config.yml
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── CNAME
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── backbone-min.js
├── backbone.js
├── bower.json
├── debug-info.js
├── docs/
│ ├── backbone.html
│ ├── backbone.localStorage.html
│ ├── docco.css
│ ├── examples/
│ │ ├── backbone.localStorage.html
│ │ └── todos/
│ │ └── todos.html
│ ├── js/
│ │ └── jquery.lazyload.js
│ ├── public/
│ │ └── stylesheets/
│ │ └── normalize.css
│ ├── search.js
│ └── todos.html
├── examples/
│ ├── backbone.localStorage.js
│ └── todos/
│ ├── index.html
│ ├── todos.css
│ └── todos.js
├── index.html
├── karma.conf-sauce.js
├── karma.conf.js
├── modules/
│ ├── .eslintrc
│ ├── debug-info.js
│ └── package.json
├── package.json
└── test/
├── .eslintrc
├── collection.js
├── debuginfo.js
├── events.js
├── index.html
├── model.coffee
├── model.js
├── noconflict.js
├── router.js
├── setup/
│ ├── dom-setup.js
│ └── environment.js
├── sync.js
├── vendor/
│ ├── jquery.js
│ ├── json2.js
│ ├── qunit.css
│ ├── qunit.js
│ ├── require.js
│ └── underscore.js
└── view.js
SYMBOL INDEX (162 symbols across 10 files)
FILE: debug-info.js
function debugInfo (line 7) | function debugInfo() {
FILE: docs/js/jquery.lazyload.js
function update (line 33) | function update() {
FILE: docs/search.js
function strIn (line 6) | function strIn(a, b) {
function doesMatch (line 12) | function doesMatch(element) {
function filterElement (line 18) | function filterElement(element) {
function filterToc (line 22) | function filterToc() {
function gotoFirst (line 35) | function gotoFirst() {
FILE: examples/backbone.localStorage.js
function S4 (line 27) | function S4() {
function guid (line 32) | function guid() {
FILE: test/debuginfo.js
function spyDebug (line 7) | function spyDebug() {
FILE: test/vendor/jquery.js
function createOptions (line 911) | function createOptions( options ) {
function dataAttr (line 1801) | function dataAttr( elem, key, data ) {
function isEmptyDataObject (line 1833) | function isEmptyDataObject( obj ) {
function returnFalse (line 3274) | function returnFalse() {
function returnTrue (line 3277) | function returnTrue() {
function Sizzle (line 3880) | function Sizzle( selector, context, results, seed ) {
function createInputPseudo (line 3949) | function createInputPseudo( type ) {
function createButtonPseudo (line 3957) | function createButtonPseudo( type ) {
function createPositionalPseudo (line 3965) | function createPositionalPseudo( fn ) {
function siblingCheck (line 4560) | function siblingCheck( a, b, ret ) {
function tokenize (line 4683) | function tokenize( selector, parseOnly ) {
function addCombinator (line 4746) | function addCombinator( matcher, combinator, base ) {
function elementMatcher (line 4798) | function elementMatcher( matchers ) {
function condense (line 4812) | function condense( unmatched, map, filter, context, xml ) {
function setMatcher (line 4833) | function setMatcher( preFilter, selector, matcher, postFilter, postFinde...
function matcherFromTokens (line 4926) | function matcherFromTokens( tokens ) {
function matcherFromGroupMatchers (line 4978) | function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
function multipleContexts (line 5102) | function multipleContexts( selector, contexts, results ) {
function select (line 5111) | function select( selector, context, results, seed, xml ) {
function setFilters (line 5338) | function setFilters() {}
function isDisconnected (line 5504) | function isDisconnected( node ) {
function sibling (line 5508) | function sibling( cur, dir ) {
function winnow (line 5616) | function winnow( elements, qualifier, keep ) {
function createSafeFragment (line 5649) | function createSafeFragment( document ) {
function findOrAppend (line 6033) | function findOrAppend( elem, tag ) {
function cloneCopyEvent (line 6037) | function cloneCopyEvent( src, dest ) {
function cloneFixAttributes (line 6065) | function cloneFixAttributes( src, dest ) {
function getAll (line 6208) | function getAll( elem ) {
function fixDefaultChecked (line 6221) | function fixDefaultChecked( elem ) {
function jQuerySub (line 6517) | function jQuerySub( selector, context ) {
function vendorPropName (line 6563) | function vendorPropName( style, name ) {
function isHidden (line 6585) | function isHidden( elem, el ) {
function showHide (line 6590) | function showHide( elements, show ) {
function setPositiveNumber (line 6900) | function setPositiveNumber( elem, value, subtract ) {
function augmentWidthOrHeight (line 6907) | function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
function getWidthOrHeight (line 6949) | function getWidthOrHeight( elem, name, extra ) {
function css_defaultDisplay (line 6992) | function css_defaultDisplay( nodeName ) {
function buildParams (line 7246) | function buildParams( prefix, obj, traditional, add ) {
function addToPrefiltersOrTransports (line 7335) | function addToPrefiltersOrTransports( structure ) {
function inspectPrefiltersOrTransports (line 7369) | function inspectPrefiltersOrTransports( structure, options, originalOpti...
function ajaxExtend (line 7411) | function ajaxExtend( target, src ) {
function done (line 7722) | function done( status, nativeStatusText, responses, headers ) {
function ajaxHandleResponses (line 8015) | function ajaxHandleResponses( s, jqXHR, responses ) {
function ajaxConvert (line 8077) | function ajaxConvert( s, response ) {
function createStandardXHR (line 8344) | function createStandardXHR() {
function createActiveXHR (line 8350) | function createActiveXHR() {
function createFxNow (line 8604) | function createFxNow() {
function createTweens (line 8611) | function createTweens( animation, props ) {
function Animation (line 8626) | function Animation( elem, properties, options ) {
function propFilter (line 8724) | function propFilter( props, specialEasing ) {
function defaultPrefilter (line 8791) | function defaultPrefilter( elem, props, opts ) {
function Tween (line 8915) | function Tween( elem, options, prop, end, easing ) {
function genFx (line 9099) | function genFx( type, includeWidth ) {
function getWindow (line 9405) | function getWindow( elem ) {
FILE: test/vendor/json2.js
function f (line 168) | function f(n) {
function quote (line 209) | function quote(string) {
function str (line 227) | function str(key, holder) {
function walk (line 410) | function walk(holder, key) {
FILE: test/vendor/qunit.js
function diff (line 50) | function diff( a, b ) {
function inArray (line 67) | function inArray( elem, array ) {
function objectValues (line 88) | function objectValues ( obj ) {
function extend (line 100) | function extend( a, b, undefOnly ) {
function objectType (line 120) | function objectType( obj ) {
function is (line 156) | function is( type, obj ) {
function extractStacktrace (line 187) | function extractStacktrace( e, offset ) {
function sourceFromStacktrace (line 224) | function sourceFromStacktrace( offset ) {
function registerLoggingCallbacks (line 333) | function registerLoggingCallbacks( obj ) {
function runLoggingCallbacks (line 369) | function runLoggingCallbacks( key, args ) {
function verifyLoggingCallbacks (line 381) | function verifyLoggingCallbacks() {
function createModule (line 494) | function createModule() {
function setCurrentModule (line 518) | function setCurrentModule( module ) {
function begin (line 640) | function begin() {
function process (line 676) | function process( last ) {
function pauseProcessing (line 703) | function pauseProcessing() {
function resumeProcessing (line 720) | function resumeProcessing() {
function done (line 740) | function done() {
function setHook (line 769) | function setHook( module, hookName ) {
function Test (line 782) | function Test( settings ) {
function runTest (line 903) | function runTest( test ) {
function callHook (line 929) | function callHook() {
function processHooks (line 940) | function processHooks( test, module ) {
function run (line 1021) | function run() {
function testInModuleChain (line 1143) | function testInModuleChain( testModule ) {
function generateHash (line 1238) | function generateHash( module, testName ) {
function synchronize (line 1260) | function synchronize( callback, priority ) {
function saveGlobal (line 1281) | function saveGlobal() {
function checkPollution (line 1298) | function checkPollution() {
function asyncTest (line 1317) | function asyncTest( testName, expected, callback ) {
function test (line 1327) | function test( testName, expected, callback, async ) {
function skip (line 1348) | function skip( testName ) {
function only (line 1360) | function only( testName, expected, callback, async ) {
function Assert (line 1383) | function Assert( testContext ) {
function errorString (line 1574) | function errorString( error ) {
function useStrictEquality (line 1611) | function useStrictEquality( b, a ) {
function compareConstructors (line 1626) | function compareConstructors( a, b ) {
function getRegExpFlags (line 1655) | function getRegExpFlags( regexp ) {
function typeEquiv (line 1813) | function typeEquiv( a, b ) {
function innerEquiv (line 1819) | function innerEquiv( a, b ) {
function quote (line 1839) | function quote( str ) {
function literal (line 1842) | function literal( o ) {
function join (line 1845) | function join( pre, arr, post ) {
function array (line 1857) | function array( arr, stack ) {
function applyCurrent (line 2109) | function applyCurrent( current ) {
function DiffMatchPatch (line 2201) | function DiffMatchPatch() {
function diffHalfMatchI (line 2581) | function diffHalfMatchI( longtext, shorttext, i ) {
function diffLinesToCharsMunge (line 3074) | function diffLinesToCharsMunge( text ) {
function escapeText (line 3374) | function escapeText( s ) {
function addEvent (line 3402) | function addEvent( elem, type, fn ) {
function addEvents (line 3426) | function addEvents( elems, type, fn ) {
function hasClass (line 3433) | function hasClass( elem, name ) {
function addClass (line 3437) | function addClass( elem, name ) {
function toggleClass (line 3443) | function toggleClass( elem, name ) {
function removeClass (line 3451) | function removeClass( elem, name ) {
function id (line 3463) | function id( name ) {
function getUrlConfigHtml (line 3467) | function getUrlConfigHtml() {
function toolbarChanged (line 3535) | function toolbarChanged() {
function setUrl (line 3565) | function setUrl( params ) {
function applyUrlParams (line 3587) | function applyUrlParams() {
function toolbarUrlConfigContainer (line 3605) | function toolbarUrlConfigContainer() {
function toolbarLooseFilter (line 3620) | function toolbarLooseFilter() {
function toolbarModuleFilterHtml (line 3654) | function toolbarModuleFilterHtml() {
function toolbarModuleFilter (line 3682) | function toolbarModuleFilter() {
function appendToolbar (line 3699) | function appendToolbar() {
function appendHeader (line 3708) | function appendHeader() {
function appendBanner (line 3718) | function appendBanner() {
function appendTestResults (line 3726) | function appendTestResults() {
function storeFixture (line 3744) | function storeFixture() {
function appendFilteredTest (line 3751) | function appendFilteredTest() {
function appendUserAgent (line 3762) | function appendUserAgent() {
function appendTestsList (line 3775) | function appendTestsList( modules ) {
function appendTest (line 3793) | function appendTest( name, testId, moduleName ) {
function getNameHtml (line 3902) | function getNameHtml( name, module ) {
function stripHtml (line 3939) | function stripHtml( string ) {
FILE: test/vendor/require.js
function isFunction (line 41) | function isFunction(it) {
function isArray (line 45) | function isArray(it) {
function each (line 53) | function each(ary, func) {
function eachReverse (line 68) | function eachReverse(ary, func) {
function hasProp (line 79) | function hasProp(obj, prop) {
function getOwn (line 83) | function getOwn(obj, prop) {
function eachProp (line 92) | function eachProp(obj, func) {
function mixin (line 107) | function mixin(target, source, force, deepStringMixin) {
function bind (line 127) | function bind(obj, fn) {
function scripts (line 133) | function scripts() {
function defaultOnError (line 137) | function defaultOnError(err) {
function getGlobal (line 143) | function getGlobal(value) {
function makeError (line 162) | function makeError(id, msg, err, requireModules) {
function newContext (line 194) | function newContext(contextName) {
function getInteractiveScript (line 1908) | function getInteractiveScript() {
FILE: test/vendor/underscore.js
function createReduce (line 178) | function createReduce(dir) {
function createPredicateIndexFinder (line 614) | function createPredicateIndexFinder(dir) {
function createIndexFinder (line 644) | function createIndexFinder(dir, predicateFind, sortedIndex) {
function collectNonEnumProps (line 909) | function collectNonEnumProps(obj, keys) {
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,643K chars).
[
{
"path": ".editorconfig",
"chars": 301,
"preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
},
{
"path": ".eslintrc",
"chars": 2521,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"node\": true,\n \"amd\": true\n },\n \"globals\": {\n \"attachEvent\": false,\n \"d"
},
{
"path": ".github/FUNDING.yml",
"chars": 71,
"preview": "tidelift: \"npm/backbone\"\npatreon: juliangonggrijp\ngithub: [jgonggrijp]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/Bugs.yml",
"chars": 6006,
"preview": "name: Bug report\ndescription: |\n Report something that is not working correctly.\n Not intended for security issues!\nti"
},
{
"path": ".github/ISSUE_TEMPLATE/Documentation.yml",
"chars": 3016,
"preview": "name: Documentation issue\ndescription: >-\n Report information that is missing, incomplete, vague, misleading or plain\n "
},
{
"path": ".github/ISSUE_TEMPLATE/Features.yml",
"chars": 2394,
"preview": "name: Feature request\ndescription: >-\n Tell us about functionality that you miss in Backbone.\nbody:\n - type: markdown\n"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 507,
"preview": "contact_links:\n - name: Security issue\n url: https://github.com/jashkenas/backbone/security/policy\n about: Go her"
},
{
"path": ".github/workflows/tests.yml",
"chars": 870,
"preview": "name: Test\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n paths:\n - '.g"
},
{
"path": ".gitignore",
"chars": 50,
"preview": "raw\n*.sw?\n.DS_Store\nnode_modules\nbower_components\n"
},
{
"path": "CNAME",
"chars": 14,
"preview": "backbonejs.org"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 6566,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 1077,
"preview": "## How to Open a Backbone.js Ticket\n\n* Do not use tickets to ask for help with (debugging) your application. Ask on\nthe "
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "Copyright (c) 2010-2024 Jeremy Ashkenas, DocumentCloud\n\nPermission is hereby granted, free of charge, to any person\nobta"
},
{
"path": "README.md",
"chars": 2106,
"preview": " ____ __ __\n /\\ _`\\ /\\ \\ /\\ \\ __\n"
},
{
"path": "SECURITY.md",
"chars": 1446,
"preview": "# Security Policy\n\n## Supported Versions\n\nWe currently support the following versions of Backbone with security updates:"
},
{
"path": "backbone-min.js",
"chars": 25690,
"preview": "(function(r){var n=typeof self==\"object\"&&self.self===self&&self||typeof global==\"object\"&&global.global===global&&globa"
},
{
"path": "backbone.js",
"chars": 80436,
"preview": "// Backbone.js 1.6.1\n\n// (c) 2010-2024 Jeremy Ashkenas and DocumentCloud\n// Backbone may be freely distribut"
},
{
"path": "bower.json",
"chars": 255,
"preview": "{\n \"name\" : \"backbone\",\n \"main\" : \"backbone.js\",\n \"dependencies\" : {\n \"underscore\" : \">=1.8.3\""
},
{
"path": "debug-info.js",
"chars": 1862,
"preview": "(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory("
},
{
"path": "docs/backbone.html",
"chars": 313292,
"preview": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>backbone.js</title>\n <meta http-equiv=\"content-type\" content=\"text/html; charse"
},
{
"path": "docs/backbone.localStorage.html",
"chars": 366,
"preview": "<!DOCTYPE html>\n\n<html>\n<head></head>\n<body>\n <p>This annotated source has moved to <a href=\"examples/backbone.localSto"
},
{
"path": "docs/docco.css",
"chars": 9889,
"preview": "/*--------------------- Typography ----------------------------*/\n\n@font-face {\n font-family: 'aller-light';\n src:"
},
{
"path": "docs/examples/backbone.localStorage.html",
"chars": 26232,
"preview": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>backbone.localStorage.js</title>\n <meta http-equiv=\"content-type\" content=\"text"
},
{
"path": "docs/examples/todos/todos.html",
"chars": 36750,
"preview": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>todos.js</title>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=U"
},
{
"path": "docs/js/jquery.lazyload.js",
"chars": 7572,
"preview": "/*\n * Lazy Load - jQuery plugin for lazy loading images\n *\n * Copyright (c) 2007-2012 Mika Tuupola\n *\n * Licensed under "
},
{
"path": "docs/public/stylesheets/normalize.css",
"chars": 6874,
"preview": "/*! normalize.css v2.0.1 | MIT License | git.io/normalize */\n\n/* ======================================================="
},
{
"path": "docs/search.js",
"chars": 1669,
"preview": "(function() {\n var functions = document.querySelectorAll('[data-name]');\n var sections = document.querySelectorAll('.s"
},
{
"path": "docs/todos.html",
"chars": 336,
"preview": "<!DOCTYPE html>\n\n<html>\n<head></head>\n<body>\n <p>This annotated source has moved to <a href=\"examples/todos/todos.html\""
},
{
"path": "examples/backbone.localStorage.js",
"chars": 5698,
"preview": "/**\n * Backbone localStorage Adapter\n * Version 1.1.0\n *\n * https://github.com/jeromegn/Backbone.localStorage\n */\n(funct"
},
{
"path": "examples/todos/index.html",
"chars": 1832,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <title>Backbone.js Todos</title>\n <link rel=\"styles"
},
{
"path": "examples/todos/todos.css",
"chars": 4047,
"preview": "html,\nbody {\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbody {\n\tfont: 14px \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\tline-height"
},
{
"path": "examples/todos/todos.js",
"chars": 6710,
"preview": "// An example Backbone application contributed by\n// [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple\n// "
},
{
"path": "index.html",
"chars": 228584,
"preview": "<!DOCTYPE HTML>\n<html>\n<head>\n <meta http-equiv=\"content-type\" content=\"text/html;charset=UTF-8\" />\n <meta http-equiv="
},
{
"path": "karma.conf-sauce.js",
"chars": 2834,
"preview": "var _ = require('underscore');\n\n// Browsers to run on Sauce Labs platforms\nvar sauceBrowsers = _.reduce([\n ['firefox', "
},
{
"path": "karma.conf.js",
"chars": 1576,
"preview": "// Note some browser launchers should be installed before using karma start.\n// For example:\n// npm install karma-firefo"
},
{
"path": "modules/.eslintrc",
"chars": 207,
"preview": "{\n \"parserOptions\": {\n \"ecmaVersion\": 6,\n \"sourceType\": \"module\",\n },\n \"plugins\": [\n \"import\"\n ],\n \"extend"
},
{
"path": "modules/debug-info.js",
"chars": 1387,
"preview": "import Backbone from 'backbone';\n\n// Provide useful information when things go wrong.\nexport default function() {\n // I"
},
{
"path": "modules/package.json",
"chars": 36,
"preview": "{\"type\":\"module\",\"version\":\"1.4.1\"}\n"
},
{
"path": "package.json",
"chars": 2016,
"preview": "{\n \"name\": \"backbone\",\n \"description\": \"Give your JS App some Backbone with Models, Views, Collections, and Events.\",\n"
},
{
"path": "test/.eslintrc",
"chars": 197,
"preview": "{\n \"env\": {\n \"browser\": true\n },\n \"globals\": {\n \"QUnit\": false,\n \"Backbone\": true,\n \"_\": true,\n \"$\": t"
},
{
"path": "test/collection.js",
"chars": 76409,
"preview": "(function(QUnit) {\n\n var a, b, c, d, e, col, otherCol;\n\n QUnit.module('Backbone.Collection', {\n\n beforeEach: functi"
},
{
"path": "test/debuginfo.js",
"chars": 1509,
"preview": "/* eslint-disable no-console */\n\n(function(QUnit) {\n\n var logs, originalDebug = console.debug;\n\n function spyDebug() {"
},
{
"path": "test/events.js",
"chars": 23531,
"preview": "(function(QUnit) {\n\n QUnit.module('Backbone.Events');\n\n QUnit.test('on and trigger', function(assert) {\n assert.exp"
},
{
"path": "test/index.html",
"chars": 847,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset='utf8'>\n <title>Backbone Test Suite</title>\n <link rel=\"stylesheet\" href"
},
{
"path": "test/model.coffee",
"chars": 886,
"preview": "# Quick Backbone/CoffeeScript tests to make sure that inheritance\n# works correctly.\n\n{ok, equal, deepEqual} = requ"
},
{
"path": "test/model.js",
"chars": 47392,
"preview": "(function(QUnit) {\n\n var ProxyModel = Backbone.Model.extend();\n var Klass = Backbone.Collection.extend({\n url: func"
},
{
"path": "test/noconflict.js",
"chars": 424,
"preview": "(function(QUnit) {\n\n QUnit.module('Backbone.noConflict');\n\n QUnit.test('noConflict', function(assert) {\n assert.exp"
},
{
"path": "test/router.js",
"chars": 37565,
"preview": "(function(QUnit) {\n\n var router = null;\n var location = null;\n var lastRoute = null;\n var lastArgs = [];\n\n var onRo"
},
{
"path": "test/setup/dom-setup.js",
"chars": 89,
"preview": "$('body').append(\n '<div id=\"qunit\"></div>' +\n '<div id=\"qunit-fixture\"></div>'\n);\n"
},
{
"path": "test/setup/environment.js",
"chars": 1167,
"preview": "(function(QUnit) {\n\n var sync = Backbone.sync;\n var ajax = Backbone.ajax;\n var emulateHTTP = Backbone.emulateHTTP;\n "
},
{
"path": "test/sync.js",
"chars": 7859,
"preview": "(function(QUnit) {\n\n var Library = Backbone.Collection.extend({\n url: function() { return '/library'; }\n });\n var "
},
{
"path": "test/vendor/jquery.js",
"chars": 267739,
"preview": "/*!\n * jQuery JavaScript Library v1.8.3\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Cop"
},
{
"path": "test/vendor/json2.js",
"chars": 17348,
"preview": "/*\n http://www.JSON.org/json2.js\n 2009-09-29\n\n Public Domain.\n\n NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOU"
},
{
"path": "test/vendor/qunit.css",
"chars": 5399,
"preview": "/*!\n * QUnit 1.21.0\n * https://qunitjs.com/\n *\n * Copyright jQuery Foundation and other contributors\n * Released under t"
},
{
"path": "test/vendor/qunit.js",
"chars": 111791,
"preview": "/*!\n * QUnit 1.21.0\n * https://qunitjs.com/\n *\n * Copyright jQuery Foundation and other contributors\n * Released under t"
},
{
"path": "test/vendor/require.js",
"chars": 82687,
"preview": "/** vim: et:ts=4:sw=4:sts=4\n * @license RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved"
},
{
"path": "test/vendor/underscore.js",
"chars": 52910,
"preview": "// Underscore.js 1.8.3\n// http://underscorejs.org\n// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Invest"
},
{
"path": "test/view.js",
"chars": 13575,
"preview": "(function(QUnit) {\n\n var view;\n\n QUnit.module('Backbone.View', {\n\n beforeEach: function() {\n $('#qunit-fixture"
}
]
About this extraction
This page contains the full source code of the jashkenas/backbone GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (1.5 MB), approximately 407.4k tokens, and a symbol index with 162 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.