UNPKG

backbone-fusioncharts

Version:

Simple and Lightweight Backbone wrapper for FusionCharts JavaScript Charting Library

1,303 lines (1,120 loc) 425 kB
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = "./example/index.js"); /******/ }) /************************************************************************/ /******/ ({ /***/ "../../../../../usr/local/lib/node_modules/webpack/buildin/global.js": /*!***********************************!*\ !*** (webpack)/buildin/global.js ***! \***********************************/ /*! no static exports found */ /***/ (function(module, exports) { var g; // This works in non-strict mode g = (function() { return this; })(); try { // This works if eval is allowed (see CSP) g = g || Function("return this")() || (1, eval)("this"); } catch (e) { // This works if the window reference is available if (typeof window === "object") g = window; } // g can still be undefined, but nothing to do about it... // We return undefined, instead of nothing here, so it's // easier to handle this case. if(!global) { ...} module.exports = g; /***/ }), /***/ "../../../../../usr/local/lib/node_modules/webpack/buildin/module.js": /*!***********************************!*\ !*** (webpack)/buildin/module.js ***! \***********************************/ /*! no static exports found */ /***/ (function(module, exports) { module.exports = function(module) { if (!module.webpackPolyfill) { module.deprecate = function() {}; module.paths = []; // module.parent = undefined by default if (!module.children) module.children = []; Object.defineProperty(module, "loaded", { enumerable: true, get: function() { return module.l; } }); Object.defineProperty(module, "id", { enumerable: true, get: function() { return module.i; } }); module.webpackPolyfill = 1; } return module; }; /***/ }), /***/ "./dist/backbone-fusioncharts.js": /*!***************************************!*\ !*** ./dist/backbone-fusioncharts.js ***! \***************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__; var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; (function webpackUniversalModuleDefinition(root, factory) { if (( false ? undefined : _typeof2(exports)) === 'object' && ( false ? undefined : _typeof2(module)) === 'object') module.exports = factory(__webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"));else if (true) !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));else {} })(window, function (__WEBPACK_EXTERNAL_MODULE_backbone__) { return (/******/function (modules) { // webpackBootstrap /******/ // The module cache /******/var installedModules = {}; /******/ /******/ // The require function /******/function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/if (installedModules[moduleId]) { /******/return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/var module = installedModules[moduleId] = { /******/i: moduleId, /******/l: false, /******/exports: {} /******/ }; /******/ /******/ // Execute the module function /******/modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/module.l = true; /******/ /******/ // Return the exports of the module /******/return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/__webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/__webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/__webpack_require__.d = function (exports, name, getter) { /******/if (!__webpack_require__.o(exports, name)) { /******/Object.defineProperty(exports, name, { /******/configurable: false, /******/enumerable: true, /******/get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/__webpack_require__.r = function (exports) { /******/Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/__webpack_require__.n = function (module) { /******/var getter = module && module.__esModule ? /******/function getDefault() { return module['default']; } : /******/function getModuleExports() { return module; }; /******/__webpack_require__.d(getter, 'a', getter); /******/return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/__webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/__webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/return __webpack_require__(__webpack_require__.s = "./src/controller.js"); /******/ }( /************************************************************************/ /******/{ /***/"./src/controller.js": /*!***************************!*\ !*** ./src/controller.js ***! \***************************/ /*! no static exports found */ /***/function srcControllerJs(module, exports, __webpack_require__) { "use strict"; var _backbone = __webpack_require__( /*! backbone */"backbone"); var _backbone2 = _interopRequireDefault(_backbone); var _view = __webpack_require__( /*! ./view */"./src/view.js"); var _view2 = _interopRequireDefault(_view); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var BackboneFusionCharts = function BackboneFusionCharts(options) { _classCallCheck(this, BackboneFusionCharts); this.model = new _backbone2.default.Model(options); this.view = new _view2.default({ model: this.model }); }; module.exports = BackboneFusionCharts; /***/ }, /***/"./src/utils/options.js": /*!******************************!*\ !*** ./src/utils/options.js ***! \******************************/ /*! no static exports found */ /***/function srcUtilsOptionsJs(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = ['renderAt', 'type', 'id', 'width', 'height', 'dataFormat', 'dataSource', 'events', 'link', 'showDataLoadingMessage', 'showChartLoadingMessage', 'baseChartMessageFont', 'baseChartMessageFontSize', 'baseChartMessageColor', 'dataLoadStartMessage', 'dataLoadErrorMessage', 'dataInvalidMessage', 'dataEmptyMessage', 'typeNotSupportedMessage', 'loadMessage', 'renderErrorMessage', 'containerBackgroundColor', 'containerBackgroundOpacity', 'containerClassName', 'baseChartMessageImageHAlign', 'baseChartMessageImageVAlign', 'baseChartMessageImageAlpha', 'baseChartMessageImageScale', 'typeNotSupportedMessageImageHAalign', 'typeNotSupportedMessageImageVAlign', 'typeNotSupportedMessageImageAlpha', 'typeNotSupportedMessageImageScale', 'dataLoadErrorMessageImageHAlign', 'dataLoadErrorMessageImageVAlign', 'dataLoadErrorMessageImageAlpha', 'dataLoadErrorMessageImageScale', 'dataLoadStartMessageImageHAlign', 'dataLoadStartMessageImageVAlign', 'dataLoadStartMessageImageAlpha', 'dataLoadStartMessageImageScale', 'dataInvalidMessageImageHAlign', 'dataInvalidMessageImageVAlign', 'dataInvalidMessageImageAlpha', 'dataInvalidMessageImageScale', 'dataEmptyMessageImageHAlign', 'dataEmptyMessageImageVAlign', 'dataEmptyMessageImageAlpha', 'dataEmptyMessageImageScale', 'renderErrorMessageImageHAlign', 'renderErrorMessageImageVAlign', 'renderErrorMessageImageAlpha', 'renderErrorMessageImageScale', 'loadMessageImageHAlign', 'loadMessageImageVAlign', 'loadMessageImageAlpha', 'loadMessageImageScale']; module.exports = exports['default']; /***/ }, /***/"./src/utils/utils.js": /*!****************************!*\ !*** ./src/utils/utils.js ***! \****************************/ /*! no static exports found */ /***/function srcUtilsUtilsJs(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) { return typeof obj === 'undefined' ? 'undefined' : _typeof2(obj); } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === 'undefined' ? 'undefined' : _typeof2(obj); }; exports.isObject = isObject; exports.isCallable = isCallable; exports.isSameObjectContent = isSameObjectContent; exports.isUndefined = isUndefined; exports.deepCopyOf = deepCopyOf; function isObject(value) { return value !== null && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object'; } function isCallable(value) { return typeof value === 'function'; } function isSameObjectContent(obj1, obj2) { if (Object.keys(obj1).length !== Object.keys(obj2).length) { return false; } var keys = Object.keys(obj1); for (var i = 0; i < keys.length; i += 1) { var key = keys[i]; if (isObject(obj1[key]) && isObject(obj2[key])) { if (!isSameObjectContent(obj1[key], obj2[key])) { return false; } } else if (obj1[key] !== obj2[key]) { return false; } } return true; } function isUndefined(value) { // eslint-disable-next-line no-void var UNDEFINED = void 0; return value === UNDEFINED; } function deepCopyOf(obj) { return JSON.parse(JSON.stringify(obj)); } /***/ }, /***/"./src/view.js": /*!*********************!*\ !*** ./src/view.js ***! \*********************/ /*! no static exports found */ /***/function srcViewJs(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _backbone = __webpack_require__( /*! backbone */"backbone"); var _backbone2 = _interopRequireDefault(_backbone); var _options = __webpack_require__( /*! ./utils/options */"./src/utils/options.js"); var _options2 = _interopRequireDefault(_options); var _utils = __webpack_require__( /*! ./utils/utils */"./src/utils/utils.js"); var utils = _interopRequireWildcard(_utils); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {};if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } }newObj.default = obj;return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _backbone2.default.View.extend({ initialize: function initialize() { this.model.bind('change', this.onChange.bind(this)); this.props = this.model.toJSON(); this.render(); }, onChange: function onChange(nextProps) { var currentOptions = this.resolveChartOptions(nextProps.toJSON()); var oldOptions = this.oldOptions; var optionsUpdatedNatively = ['width', 'height', 'type', 'dataFormat', 'dataSource', 'events']; this.checkAndUpdateChartRenderAt(currentOptions, oldOptions); this.checkAndUpdateChartDimensions(currentOptions, oldOptions); this.checkAndUpdateChartType(currentOptions, oldOptions); this.checkAndUpdateChartData(currentOptions, oldOptions); this.checkAndUpdateEvents(currentOptions, oldOptions); this.checkAndUpdateRestOptions(_options2.default.filter(function (option) { return optionsUpdatedNatively.indexOf(option) === -1; }), currentOptions, oldOptions); this.oldOptions = currentOptions; }, checkAndUpdateChartRenderAt: function checkAndUpdateChartRenderAt(currentOptions, oldOptions) { var currRenderAt = currentOptions.renderAt; var oldRenderAt = oldOptions.renderAt; if (String(currRenderAt) !== String(oldRenderAt)) { this.chart.dispose(); this.props.renderAt = currRenderAt; this.render(); } }, checkAndUpdateChartDimensions: function checkAndUpdateChartDimensions(currentOptions, oldOptions) { var currWidth = currentOptions.width; var currHeight = currentOptions.height; var oldWidth = oldOptions.width; var oldHeight = oldOptions.height; if (String(currWidth) !== String(oldWidth) || String(currHeight) !== String(oldHeight)) { if (!utils.isUndefined(currWidth) && !utils.isUndefined(currHeight)) { this.chart.resizeTo(currWidth, currHeight); } else { if (!utils.isUndefined(currWidth)) { this.chart.resizeTo({ w: currWidth }); } if (!utils.isUndefined(currHeight)) { this.chart.resizeTo({ h: currHeight }); } } } }, checkAndUpdateChartType: function checkAndUpdateChartType(currentOptions, oldOptions) { var currType = currentOptions.type; var oldType = oldOptions.type; if (String(currType).toLowerCase() !== String(oldType).toLowerCase()) { if (!utils.isUndefined(currType)) { this.chart.chartType(String(currType).toLowerCase()); } } }, checkAndUpdateChartData: function checkAndUpdateChartData(currentOptions, oldOptions) { var currDataFormat = currentOptions.dataFormat; var currData = currentOptions.dataSource; var oldDataFormat = oldOptions.dataFormat; var oldData = oldOptions.dataSource; if (String(currDataFormat).toLowerCase() !== String(oldDataFormat).toLowerCase()) { if (!utils.isUndefined(currDataFormat) && !utils.isUndefined(currData)) { this.chart.setChartData(currData, String(currDataFormat).toLowerCase()); // If the chart dataFormat is changed then // animate the chart to show the changes this.chart.render(); } } else if (!this.isSameChartData(currData, oldData)) { if (!utils.isUndefined(currData)) { this.chart.setChartData(currData, // When dataFormat is not given, but data is changed, // then use 'json' as default dataFormat currDataFormat ? String(currDataFormat).toLowerCase() : 'json'); } } }, isSameChartData: function isSameChartData(currData, oldData) { if (utils.isObject(currData) && utils.isObject(oldData)) { return utils.isSameObjectContent(currData, oldData); } return currData === oldData; }, checkAndUpdateEvents: function checkAndUpdateEvents(currentOptions, oldOptions) { var _this = this; var currEvents = currentOptions.events; var oldEvents = oldOptions.events; var temp1 = void 0; var temp2 = void 0; if (this.detectChartEventsChange(currEvents, oldEvents)) { if (!utils.isUndefined(currEvents)) { temp1 = Object.assign({}, currEvents); temp2 = utils.isUndefined(oldEvents) ? {} : Object.assign({}, oldEvents); Object.keys(temp2).forEach(function (eventName) { if (temp2[eventName] === temp1[eventName]) { temp1[eventName] = undefined; } else { _this.chart.removeEventListener(eventName, temp2[eventName]); } }); Object.keys(temp1).forEach(function (eventName) { if (temp1[eventName]) { _this.chart.addEventListener(eventName, temp1[eventName]); } }); } } }, detectChartEventsChange: function detectChartEventsChange(currEvents, oldEvents) { if (utils.isObject(currEvents) && utils.isObject(oldEvents)) { return !this.isSameChartEvents(currEvents, oldEvents); } return !(currEvents === oldEvents); }, isSameChartEvents: function isSameChartEvents(currEvents, oldEvents) { if (Object.keys(currEvents).length !== Object.keys(oldEvents).length) { return false; } var currEventNames = Object.keys(currEvents); for (var i = 0; i < currEventNames.length; ++i) { var evName = currEventNames[i]; if (currEvents[evName] !== oldEvents[evName]) { return false; } } return true; }, checkAndUpdateRestOptions: function checkAndUpdateRestOptions(restOptions, currentOptions, oldOptions) { var _this2 = this; var optionsUpdated = false; restOptions.forEach(function (optionName) { var currValue = currentOptions[optionName]; var oldValue = oldOptions[optionName]; if (!_this2.isSameOptionValue(currValue, oldValue)) { if (!utils.isUndefined(currValue)) { if (_this2.chart.options && _this2.chart.options.hasOwnProperty(optionName)) { _this2.chart.options[optionName] = currValue; optionsUpdated = true; } } } }); if (optionsUpdated) { this.chart.render(); // re-render the chart to reflect the changes } }, isSameOptionValue: function isSameOptionValue(currValue, oldValue) { if (utils.isObject(currValue) && utils.isObject(oldValue)) { return utils.isSameObjectContent(currValue, oldValue); } return String(currValue) === String(oldValue); }, resolveChartOptions: function resolveChartOptions(props) { var chartConfig = props.chartConfig ? props.chartConfig : {}; var inlineOptions = _options2.default.reduce(function (options, optionName) { options[optionName] = props[optionName]; return options; }, {}); Object.assign(inlineOptions, chartConfig); if (utils.isObject(inlineOptions.dataSource)) { inlineOptions.dataSource = utils.deepCopyOf(inlineOptions.dataSource); } if (utils.isObject(inlineOptions.link)) { inlineOptions.link = utils.deepCopyOf(inlineOptions.link); } if (utils.isObject(inlineOptions.events)) { inlineOptions.events = Object.assign({}, inlineOptions.events); } return inlineOptions; }, render: function render() { var currentOptions = this.resolveChartOptions(this.props); this.chart = new FusionCharts(currentOptions); this.chart.render(); this.oldOptions = currentOptions; return this; } }); /* eslint-disable no-prototype-builtins, no-plusplus, no-param-reassign */ module.exports = exports['default']; /***/ }, /***/"backbone": /*!**************************************************************************************************!*\ !*** external {"root":"Backbone","commonjs2":"backbone","commonjs":"backbone","amd":"backbone"} ***! \**************************************************************************************************/ /*! no static exports found */ /***/function backbone(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_backbone__; /***/ } /******/ }) ); }); //# sourceMappingURL=backbone-fusioncharts.js.map /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../../../../../usr/local/lib/node_modules/webpack/buildin/module.js */ "../../../../../usr/local/lib/node_modules/webpack/buildin/module.js")(module))) /***/ }), /***/ "./example/index.js": /*!**************************!*\ !*** ./example/index.js ***! \**************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var _backboneFusioncharts = __webpack_require__(/*! ../dist/backbone-fusioncharts */ "./dist/backbone-fusioncharts.js"); var _backboneFusioncharts2 = _interopRequireDefault(_backboneFusioncharts); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var dataSource = { chart: { caption: 'Backbone FusionCharts Sample', theme: 'fint' }, data: [{ value: 1.9 }, { value: 2.3 }, { value: 2.1 }] }; var fc = new _backboneFusioncharts2.default({ renderAt: 'fusioncharts', type: 'Pie2d', dataSource: dataSource }); setTimeout(function () { fc.model.set('renderAt', 'fusioncharts2'); }, 3000); console.log(fc); /***/ }), /***/ "./node_modules/backbone/backbone.js": /*!*******************************************!*\ !*** ./node_modules/backbone/backbone.js ***! \*******************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Backbone.js 1.3.3 // (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // 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 (true) { !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! underscore */ "./node_modules/underscore/underscore.js"), __webpack_require__(/*! jquery */ "./node_modules/jquery/dist/jquery.js"), exports], __WEBPACK_AMD_DEFINE_RESULT__ = (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, _, $); }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); // Next for Node.js or CommonJS. jQuery may not be needed as a module. } else { var _, $; } })(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.3.3'; // 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; // 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(length, method, attribute) { switch (length) { case 1: return function() { return _[method](this[attribute]); }; case 2: return function(value) { return _[method](this[attribute], value); }; case 3: return function(iteratee, context) { return _[method](this[attribute], cb(iteratee, this), context); }; case 4: return function(iteratee, defaultVal, context) { return _[method](this[attribute], cb(iteratee, this), defaultVal, context); }; default: return function() { var args = slice.call(arguments); args.unshift(this[attribute]); return _[method].apply(_, args); }; } }; var addUnderscoreMethods = function(Class, methods, attribute) { _.each(methods, function(length, method) { if (_[method]) Class.prototype[method] = addMethod(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); }; }; // 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+/; // 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) { return internalOn(this, name, callback, context); }; // Guard the `listening` argument from the public API. var internalOn = function(obj, name, callback, context, listening) { obj._events = eventsApi(onApi, obj._events || {}, name, callback, { context: context, ctx: obj, listening: listening }); if (listening) { var listeners = obj._listeners || (obj._listeners = {}); listeners[listening.id] = listening; } return obj; }; // 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 = 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) { var thisId = this._listenId || (this._listenId = _.uniqueId('l')); listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0}; } // Bind callbacks on obj, and keep track of them on listening. internalOn(obj, name, callback, this, listening); 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; }; // 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); } return this; }; // The reducing API that removes a callback from the `events` object. var offApi = function(events, name, callback, options) { if (!events) return; var i = 0, listening; var context = options.context, listeners = options.listeners; // Delete all events listeners and "drop" events. if (!name && !callback && !context) { var ids = _.keys(listeners); for (; i < ids.length; i++) { listening = listeners[ids[i]]; delete listeners[listening.id]; delete listening.listeningTo[listening.objId]; } return; } var 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; // Replace events if there are any remaining. Otherwise, clean up. 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 { listening = handler.listening; if (listening && --listening.count === 0) { delete listeners[listening.id]; delete listening.listeningTo[listening.objId]; } } } // Update tail event if the list has any events. 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, _.bind(this.off, 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, _.bind(this.stopListening, 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; } }; // 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.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'); 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', // 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) this.id = this.get(this.idAttribute); // 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 = {}; for (var attr in diff) { var val = diff[attr]; if (_.isEqual(old[attr], val)) continue; changed[attr] = val; } return _.size(changed) ? 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 r