1/* Backbone Extension */ 2void function ( root, factory ) { 3 4 // Set up Backbone appropriately for the environment. Start with AMD. 5 if ( typeof define === 'function' && define.amd ) { 6 define([ 'jquery', 'underscore', 'backbone', 'exports' ], function ( $, _, Backbone, exports ) { 7 // Export global even in AMD case in case this script is loaded with 8 // others that may still expect a global Backbone. 9 root.B = factory(root, exports, $, _, Backbone); 10 }); 11 12 // Next for Node.js or CommonJS. 13 } else if ( typeof exports !== 'undefined' ) { 14 var $ = require('jquery'); 15 var _ = require('underscore'); 16 var Backbone = require('backbone'); 17 factory(root, exports, $, _, Backbone); 18 19 // Finally, as a browser global. 20 } else { 21 root.B = factory(root, root.B || {}, root.jQuery, root._, root.Backbone); 22 } 23 24}(this, function ( root, B, $, _, Backbone ) { 25 26 var View, CollectionView, DataBinder; 27 28 _.mixin({ 29 isPrototypeOf: function( child, parent ) { 30 if ( !child || !parent ) 31 return false; 32 var result = false; 33 var proto = child.prototype; 34 while ( proto ) { 35 if ( proto == parent.prototype ) { 36 result = true; 37 break; 38 } 39 proto = proto.__proto__; 40 } 41 return result; 42 }, 43 setPrototypeOf: function( child, prototype ) { 44 if (_.isFunction(Object.setPrototypeOf)) 45 Object.setPrototypeOf(child.prototype || child, prototype); 46 else 47 (child.prototype || child).__proto__ = prototype; 48 return child 49 }, 50 extendPrototype: function( child, prototype ) { 51 _.extend(child.prototype, prototype) 52 return child 53 }, 54 }); 55 56 // Cached backbone prototypes and methods 57 var BackboneModel = Backbone.Model; 58 var BackboneCollection = Backbone.Collection; 59 var BackboneEvents = Backbone.Events; 60 var BackboneView = Backbone.View; 61 var BackboneViewEnsureElement = BackboneView.prototype._ensureElement; 62 63 // Use this element to store unused element of deleted views 64 var $reusableElements = $('<div>'); 65 66 67 /* --- View --- */ 68 View = B.View = function() { 69 function View( options ) { 70 71 if ( !(this instanceof View) ) 72 return new View(options); 73 74 var options = (options || {}); 75 76 this.__ready = _.once(this.__ready); 77 this.subViews = []; 78 79 _.extend(this, _.pick(options, 'template', 'views', 'bindings', 'events', 'modelEvents', 'superView')); 80 81 if ( this.template ) 82 switch ( typeof this.template ) { 83 case 'string': 84 this.template = this.template.trim(); 85 if (!this.template.match('<') && !this.template.match(/\s/)) 86 this.template = $(this.template).html(); 87 break; 88 } 89 90 Object.defineProperties(this, { 91 'rootView': { 92 get: function() { 93 var root = this; 94 var parent = this.superView; 95 while ( parent ) { 96 root = parent; 97 parent = parent.superView; 98 } 99 return root; 100 } 101 } 102 }); 103 104 BackboneView.call(this, options); 105 this.el && this.el.parentNode && this.__ready(); 106 } 107 108 _.extend(View.prototype, { 109 bindings : false, 110 initialize : _.noop, 111 ready : _.noop, 112 _ensureElement : function() { 113 BackboneViewEnsureElement.call(this); 114 this.render(); 115 // This function is called right before initialize 116 // So when initialize is called, element HTML is ready to be bound to views 117 }, 118 setModel : function( model ) { 119 if ( !model ) 120 return; 121 if ( this.modelEvents ) 122 _(this.modelEvents).each(function( handler, bind ) { 123 bind = _([ 'event', 'selector' ]).object(_(bind.match(/(.*)\s(.*)/) || [ 0, bind ]).rest(1)); 124 handler = _.isString(handler) ? this[ handler ] : handler; 125 126 var unbindModel = this.model && bind.selector ? this.model.get(bind.selector) : this.model; 127 var bindModel = (bind.selector && this.model) ? this.model.get(bind.selector) : model; 128 129 unbindModel && this.stopListening(unbindModel, bind.event); 130 this.listenTo(bindModel, bind.event, handler); 131 132 }, this); 133 this.model = model; 134 this.setBindingModel(this.model) 135 this.trigger('set:model', this.model) 136 }, 137 setBindingModel: function( model ) { 138 !this.__dataBinding && console.warn("Data-binding hasn't been initialized for this view."); 139 this.__dataBinding && this.__dataBinding.setModel(model); 140 this.trigger('set:binding:models', this.__dataBinding.models) 141 return this; 142 }, 143 __ready : function() { 144 this.initSubViews(); 145 this.initDataBinding(); 146 this.setModel(this.model); 147 this.ready(); 148 }, 149 freeze : function() { 150 this._frozen = true; 151 this.trigger('freeze'); 152 return this; 153 }, 154 unfreeze : function() { 155 this._frozen = false;
156 this.trigger('unfreeze'); 157 return this; 158 }, 159 preventDefault : function( e ) { 160 e.preventDefault(); 161 }, 162 stopPropagation: function( e ) { 163 e.stopPropagation(); 164 }, 165 defer : function( fn, context ) { 166 _.defer(_.bind(fn, context || this)); 167 return this; 168 }, 169 delay : function( fn, delay, context ) { 170 _.delay(_.bind(fn, context || this), delay); 171 return this; 172 }, 173 render : function() { 174 return this.__render.apply(this, arguments); 175 }, 176 __render : function() { 177 this.trigger('before:render'); 178 this.template && this.$el.html(this.template); 179 this.trigger('render'); 180 return this; 181 }, 182 183 initDataBinding: function() { 184 if ( this.__dataBinding ) 185 return; 186 this.__dataBinding = new DataBinder(this, this.model, { bindings: _.result(this, 'bindings') || [] }); 187 }, 188 initSubViews : function() { 189 var regex = (/^(\w+)(?: (collection|model):([$\w.]+))?\s*>\s*(.*)$/); 190 _.each(this.views, function( view, define ) { 191 var match = define.match(regex); 192 if ( !match ) 193 throw "View definition syntax error: '" + define + "'"; 194 var key = match[ 1 ]; 195 var type = match[ 2 ]; 196 var attr = match[ 3 ]; 197 var selector = match[ 4 ]; 198 var options = {}; 199 type && attr && (options[ type ] = (attr == '$model') ? this.model : (attr == '$collection') ? this.collection : this.model.get(attr)); 200 this[ key ] = this.attachView(view, selector, options); 201 }, this); 202 }, 203 hasView : function( view ) { 204 return this.subViews && this.subViews.indexOf(view) > -1; 205 }, 206 attachView : function( view, selector, options ) { 207 var el = this.$(selector).get(0); 208 if ( !el ) 209 throw 'No element found for selector "' + selector + '"'; 210 if ( _.isFunction(view) ) { 211 options || (options = {}); 212 options.el = el; 213 options.superView = this; 214 view = new view(options); 215 } 216 else { 217 view.setElement(el); 218 view.superView = this; 219 this.hasView(view) || this.subViews.push(view); 220 } 221 view.__ready(); 222 return view; 223 }, 224 appendView : function( view ) { 225 this.hasView(view) || this.subViews.push(view); 226 view.superView = this; 227 view.$el.appendTo(this.el); 228 view.__ready(); 229 return view; 230 }, 231 prependView : function( view ) { 232 this.hasView(view) || this.subViews.push(view); 233 view.superView = this; 234 view.$el.prependTo(this.el); 235 view.__ready(); 236 return view; 237 }, 238 remove : function() { 239 this.trigger('before:remove'); 240 BackboneView.prototype.remove.call(this); 241 this.superView && 242 this.superView.subViews && 243 _(this.superView.subViews).each(function( subView, index ) { 244 if ( subView && subView.cid == this.cid ) 245 this.superView.subViews.splice(index, 1); 246 }, this); 247 this.trigger('remove'); 248 return this; 249 } 250 }); 251 _.setPrototypeOf(View, BackboneView.prototype); 252 View.extend = BackboneView.extend; 253 return View; 254 }() 255 256 /* --- CollectionView --- */ 257 CollectionView = B.CollectionView = function() { 258 function CollectionView( options ) { 259 260 if ( !(this instanceof CollectionView) ) 261 return new CollectionView(options); 262 263 this.previousSubViews = {}; 264 this.options = options || {}; 265 this._debounceReset = _.debounce(this.reset); 266 options.collection || (options.collection = new BackboneCollection); 267 View.call(this, options); 268 } 269 270 _.extend(CollectionView.prototype, { 271 itemView : View, 272 itemViews : {}, 273 _reverseOrder : false, 274 _ensureElement : function() { 275 BackboneViewEnsureElement.call(this); 276 this.__render(); 277 // This function is called right before initialize 278 // So when initialize is called, element HTML is ready to be bound to views 279 this.getItemTemplate(); 280 }, 281 __ready : function() { 282 this.setCollection(this.collection); 283 this.ready(); 284 }, 285 getItemTemplate: function() { 286 if (!_.isEmpty(this.itemViews)) { 287 _.each(this.el.children, function( child ) { 288 var rel = child.getAttribute('data-relation'); 289 if (rel && this.itemViews[rel]) { 290 var template = child.innerHTML.trim(); 291 if ( this.itemViews[rel].prototype == View.prototype ) 292 this.itemViews[rel] = this.itemViews[rel].extend(); 293 _.extend(this.itemViews[rel].prototype, { 294 template : template, 295 tagName : child.tagName, 296 className : child.className, 297 attributes: _(child.attributes) 298 .chain() 299 .values() 300 .map(function( attr ) { 301 return attr.name; 302 }) 303 .object([]) 304 .mapObject(function( value, key ) { 305 return child.getAttribute(key); 306 }) 307 .value() 308 }); 309 } 310 }, this); 311 } 312 else { 313 var child = this.el.children[ 0 ]; 314 if ( child ) { 315 var template = child.innerHTML.trim(); 316 if ( this.itemView.prototype == View.prototype ) 317 this.itemView = this.itemView.extend(); 318 _.extend(this.itemView.prototype, { 319 template : template, 320 tagName : child.tagName, 321 className : child.className, 322 attributes: _(child.attributes) 323 .chain() 324 .values() 325 .map(function( attr ) { 326 return attr.name; 327 }) 328 .object([]) 329 .mapObject(function( value, key ) { 330 return child.getAttribute(key); 331 }) 332 .value() 333 }); 334 } 335 } 336 this.$el.empty(); 337 }, 338 setCollection : function( collection ) { 339 if ( !collection ) 340 return; 341 if ( this.collection ) { 342 this.stopListening(this.collection, 'add', this.reset); 343 this.stopListening(this.collection, 'reset', this.reset); 344 this.stopListening(this.collection, 'sort', this.reset); 345 this.trigger('unset:collection', this.collection); 346 } 347 this.collection = collection; 348 this.listenTo(this.collection, 'add', this.reset); 349 this.listenTo(this.collection, 'reset', this.reset); 350 this.listenTo(this.collection, 'sort', this.reset); 351 this.trigger('set:collection', this.collection); 352 this.reset(); 353 }, 354 add : function( model ) { 355 if ( this.previousSubViews[ model.cid ] ) { 356 var view = this.previousSubViews[ model.cid ]; 357 delete this.previousSubViews[ model.cid ]; 358 } 359 else { 360 var viewClass = this.itemView; 361 if (model.get('_rel') && this.itemViews[model.get('_rel')]) 362 viewClass = this.itemViews[model.get('_rel')]; 363 var view = new viewClass({ model: model }); 364 } 365 view.mid = model.cid; 366 this._reverseOrder ? 367 this.prependView(view) : 368 this.appendView(view); 369 return view; 370 }, 371 reset : function( models ) { 372 while ( this.subViews.length ) { 373 var view = this.subViews.pop(); 374 this.previousSubViews[ view.mid ] = view; 375 } 376 this.collection.each(this.add, this); 377 378 for ( var key in this.previousSubViews ) { 379 this.previousSubViews[ key ].$el.appendTo($reusableElements); 380 //delete this.previousSubViews[key]; 381 } 382 return this; 383 } 384 }) 385 _.setPrototypeOf(CollectionView, View.prototype); 386 CollectionView.extend = View.extend; 387 return CollectionView; 388 }() 389 390 /* --- Data Binder --- */ 391 DataBinder = B.DataBind = function() { 392 393 // Parse element data binding definition string 394 var bindingRegex = (/(?:(\w+):)?({.*}|[^;]+);?/ig); 395 396 var parseb = function parseBindingString( string ) { 397 var bindRegex = (/\s*(\w+)\s*:\s*([^,{]+(?!\()|\{(.+)\},(?=\s*\w)|\w+\(.+\)),?/g); 398 var match, obj = Object.create(null, {}); 399 var string = string 400 .replace(/\s+/, ' ') 401 .replace(/(.*)\}$/, '$1},a'); 402 while ( match = bindRegex.exec(string) ) { 403 var key = match[ 1 ], 404 value = match[ 2 ], 405 nestedValues = match[ 3 ]; 406 if ( nestedValues ) 407 value = parseBindingString(nestedValues); 408 obj[ key ] = value; 409 } 410 return obj; 411 }; 412 413 function DataBinder( view, models, options ) { 414 415 if ( this instanceof DataBinder === false ) 416 return new DataBinder(view, models, options); 417
418 options || (options = {}); 419 420 this.options = _(options).defaults({ 421 bindings: [] 422 }); 423 424 this.view = null; 425 this.models = []; 426 this.bindings = []; 427 428 _.bindAll(this, 429 'inputEventHandler', 430 'changeEventHandler' 431 ); 432 433 this.setupView(view); 434 this.setModel(models); 435 436 return this; 437 }; 438 439 _.setPrototypeOf(DataBinder, BackboneEvents); 440 441 _.extend(DataBinder.prototype, { 442 443 setModel: function( models ) { 444 var self = this; 445 _(self.models).each(function( model ) { 446 self.stopListening(model); 447 }, self); 448 self.models = _.filter(_.isArray(models) ? models : [ models ], function( model ) { 449 return model instanceof BackboneModel; 450 }); 451 _(self.models).each(function( model ) { 452 _.each(self.bindingsIndex, function( bindings, attr ) { 453 self.listenTo(model, 'change:' + attr, function( model, value, options ) { 454 self.updateView(attr) 455 }); 456 }) 457 }, self); 458 self.updateView(); 459 self.trigger('set:models', self.models) 460 return this; 461 }, 462 463 setupView : function( view ) { 464 if ( view instanceof Backbone.View == false ) 465 return false; 466 467 _(this.bindings).each(function( binding ) { 468 binding.$el.off(binding.events || 'change', this.changeEventHandler); 469 }, this); 470 471 var self = this; 472 var binding, $children, matched, type, value; 473 474 this.view = view; 475 this.bindings = []; 476 this.bindingsIndex = {}; 477 478 _(this.options.bindings).each(function( opt ) { 479 $children = view.$(opt.selector); 480 ($children.length ? $children : view.$el).each(function( index, el ) { 481 _(_.isString(opt.attr) ? [ opt.attr ] : opt.attr).each(function( attribute, key ) { 482 binding = _({ 483 $el : $(el), 484 el : el, 485 type: opt.type, 486 attr: attribute 487 }).defaults(opt); 488 if ( _.isString(key) ) 489 binding.key = key; 490 491 self.bindings.push(binding); 492 493 boundAttributes = [ binding.attr ]; 494 _(boundAttributes).each(function( attr ) { 495 self.bindingsIndex[ attr ] || (self.bindingsIndex[ attr ] = []); 496 self.bindingsIndex[ attr ].indexOf(binding) == -1 && self.bindingsIndex[ attr ].push(binding); 497 }); 498 }); 499 }); 500 }); 501 502 var hasBindingEl; 503 if ( view.$el.attr('data-bind') ) 504 parseBinding(view.el); 505 while ( hasBindingEl = view.el.querySelector('[data-bind]') ) 506 parseBinding(hasBindingEl); 507 508 function parseBinding( el ) { 509 510 var $el = $(el); 511 var syntax = ($el.attr('data-bind') || '').replace(/\s+/g, ''); 512 bindingRegex.lastIndex = 0; 513 $el.removeAttr('data-bind'); 514 515 matching : while ( matched = bindingRegex.exec(syntax) ) { 516 type = matched[ 1 ]; 517 value = matched[ 2 ]; 518 519 if ( !type ) 520 if ( $el.is('input[type="checkbox"]') ) 521 type = 'checked'; 522 else if ( $el.is('input[type="radio"]') ) 523 type = 'radio'; 524 else if ( $el.is('input,select,textarea') ) 525 type = 'value'; 526 else if ( $el.is('[contenteditable]') ) 527 type = 'html'; 528 else 529 type = 'text'; 530 531 if ( type == 'model' ) { 532 _.defer(function( view, value, template ) { 533 var childView = view[ value + 'View' ] = new View({ 534 el: el, 535 template: template 536 }); 537 view.on('set:model', function() { 538 var model = value == 'this' || value == '$model' ? view.model : view.model.get(value); 539 childView.setModel(model); 540 }); 541 view.model && view.trigger('set:model', view.model); 542 }, view, value, el.innerHTML); 543 544 el.innerHTML = ''; 545 continue matching; 546 } 547 if ( type == 'collection' ) { 548 _.defer(function( view, value, template ) { 549 var childView = view[ value + 'View' ] = new CollectionView({ 550 el : el, 551 template: template 552 }); 553 if (value == 'this' || value == '$collection') { 554 view.on('set:collection', function() { 555 childView.setCollection(view.collection); 556 }) 557 view.collection && view.trigger('set:collection', view.collection); 558 } 559 else { 560 view.on('set:model', function() { 561 childView.setCollection(view.model.get(value)); 562 }); 563 view.model && view.trigger('set:model', view.model); 564 } 565 }, view, value, el.innerHTML); 566 567 el.innerHTML = ''; 568 continue matching; 569 } 570 571 nested = value.match(/{(.*)}/); 572 value = nested ? nested[ 1 ] : value; 573 _(nested ? nested[ 1 ].split(',') : [ value ]).each(function( string ) { 574 split = string.split(':'); 575 binding = { 576 $el : $el, 577 el : el, 578 type: type, 579 attr: split.length == 1 ? split[ 0 ] : split[ 1 ] 580 } 581 if ( split.length == 2 ) 582 binding.key = split[ 0 ]; 583 if ( $el.data('bind-events') ) 584 binding.events = $el.data('bind-events'); 585 586 self.bindings.push(binding); 587 588 boundAttributes = [ binding.attr ]; 589 _(boundAttributes).each(function( attr ) { 590 self.bindingsIndex[ attr ] || (self.bindingsIndex[ attr ] = []); 591 self.bindingsIndex[ attr ].indexOf(binding) == -1 && self.bindingsIndex[ attr ].push(binding); 592 }); 593 }); 594 } 595 } 596 597 _(this.bindings).each(function( binding ) { 598 //binding.$el.on( 'input', { binding: binding }, this.inputEventHandler); 599 binding.$el.on(binding.events || 'change', { binding: binding }, this.changeEventHandler); 600 601 binding.$el 602 .removeAttr('data-bind') 603 .removeAttr('data-bind-events'); 604 }, this); 605 }, 606 updateView: function( attr ) { 607 608 var self = this; 609 var value, originalValue, setter; 610 611 _(attr ? self.bindingsIndex[ attr ] : self.bindings).each(function( binding ) { 612 613 value = self.getData(binding.attr); 614 if ( value instanceof BackboneModel || value instanceof BackboneCollection ) { 615 originalValue = value; 616 value = value.toJSON(); 617 } 618 619 setter = binding.set || self.setters[ binding.type ] || null; 620 621 if ( setter ) { 622 if ( _.isFunction(binding.parse) ) 623 value = binding.parse.call(binding, value, binding.key, self.view); 624 setter(binding.$el, value, binding.key); 625 } 626 }); 627 }, 628 629 setters: { 630 text : function( $el, value ) { 631 ($el.text() === value) || $el.text(_.isUndefined(value) ? ' ' : value); 632 }, 633 html : function( $el, value ) { 634 ($el.html() === value) || $el.html(_.isUndefined(value) ? ' ' : value); 635 }, 636 value : function( $el, value ) { 637 ($el.val() === value) || $el.val(_.isUndefined(value) ? ' ' : value); 638 }, 639 attr : function( $el, value, key ) { 640 if ( !key ) return; 641 $el.attr(key, value); 642 }, 643 prop : function( $el, value, key ) { 644 if ( !key ) return; 645 $el.prop(key, value); 646 }, 647 style : function( $el, value, key ) { 648 key ? $el.css(key, value) : $el.css(value || {}); 649 }, 650 class : function( $el, value, key ) { 651 $el[ value ? 'addClass' : 'removeClass' ](key); 652 }, 653 checked : function( $el, value ) { 654 $el.attr('checked', value); 655 }, 656 radio : function( $el, value ) { 657 $el.attr('checked', $el.val() == value ? true : false ); 658 }, 659 enabled : function( $el, value ) { 660 $el.attr('disabled', !value); 661 }, 662 disabled: function( $el, value ) { 663 $el.attr('disabled', value); 664 }, 665 toggle : function( $el, value ) { 666 $el.toggle(value); 667 }, 668 visible : function( $el, value ) { 669 $el.toggle(!!value); 670 }, 671 hidden : function( $el, value ) { 672 $el.toggle(!value); 673 }, 674 }, 675 getters: { 676 text : function( $el ) { 677 return $el.text(); 678 }, 679 html : function( $el ) { 680 return $el.html(); 681 }, 682 value : function( $el ) { 683 var value = $el.val(); 684 if ( $el.is('input[type="number"],input[type="range"]') ) { 685 value = parseFloat(value); 686 if(_.isNaN(value) ) value = ''; 687 } 688 return value 689 }, 690 checked: function( $el ) { 691 return $el.prop('checked'); 692 }, 693 radio: function( $el ) { 694 return $el.prop('checked') ? $el.val() : undefined; 695 }, 696 }, 697 698 getData: function( key ) { 699 var values = []; 700 _.each(this.models, function( model ) { 701 var value = model.get(key); 702 values.indexOf(value) == -1 && values.push(value); 703 }); 704 return values.length < 2 ? values[ 0 ] : '-'; 705 }, 706 setData: function( key, value ) { 707 _.invoke(this.models, 'set', key, value); 708 }, 709 710 inputEventHandler : function( e ) { 711 return; 712 if ( e.data && e.data.binding && e.data.binding.$el.is(e.currentTarget) ) { 713 var binding = e.data.binding; 714 if ( this.getters[ binding.type ] ) { 715 var value = this.getters[ binding.type ](binding.$el); 716 console.log(value) 717 _.invoke(this.models, 'trigger', 'input', binding.attr, value); 718 _.invoke(this.models, 'trigger', 'input:' + binding.attr, value); 719 } 720 } 721 }, 722 changeEventHandler: function( e ) { 723 if ( e.data && e.data.binding && e.data.binding.$el.is(e.currentTarget) ) { 724 var binding = e.data.binding; 725 if ( this.getters[ binding.type ] ) { 726 var value = this.getters[ binding.type ](binding.$el, e); 727 value === undefined || this.setData( binding.attr, value ); 728 } 729 } 730 } 731 732 }); 733 734 return DataBinder; 735 }(); 736 737 return B; 738});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.