├── src ├── finn │ ├── carousel │ │ ├── .sorting │ │ ├── thumbnailNavigation.js │ │ ├── slideshow.js │ │ ├── autolint.json │ │ ├── clickToGallery.js │ │ ├── keyboardNavigation.js │ │ ├── splitProjectorNavigation.js │ │ ├── loopingController.js │ │ ├── dottedIndexDisplayer.js │ │ ├── indexDisplayer.js │ │ ├── emptyList.js │ │ ├── clickableElementNavigation.js │ │ ├── overlayProjector.js │ │ ├── animator.js │ │ ├── clickNavigation.js │ │ ├── elementList.js │ │ ├── controller.js │ │ ├── touchNavigation.js │ │ ├── fader.js │ │ ├── lazyImageList.js │ │ ├── slider.org │ │ ├── fullscreenView.js │ │ ├── animatedProjector.js │ │ ├── scrollingThumbnails.js │ │ └── horizontalSlider.js │ ├── core.js │ └── elementBuilder.js └── lib │ ├── bane │ └── bane.js │ └── underscore │ └── underscore.js ├── .gitignore ├── samples ├── img │ ├── sample1.jpg │ ├── sample2.jpg │ ├── sample3.jpg │ ├── sample4.jpg │ ├── sample5.jpg │ ├── sample6.jpg │ └── sample7.jpg ├── slideshow │ ├── setup.js │ └── index.html ├── css │ └── samples.css ├── lazyloading │ ├── setup.js │ └── index.html └── simplegallery │ ├── setup.js │ └── index.html ├── package.json ├── test ├── resources │ └── jsTestDriver.conf ├── lib │ ├── setup-sinon-buster-for-jstestdriver.js │ ├── test-helper.js │ ├── sinon │ │ ├── sinon-ie-1.3.1.js │ │ └── sinon-buster.js │ ├── buster │ │ ├── buster-format.js │ │ └── buster-assertions.js │ └── jquery-ui-1.8.13.custom.min.js └── finn │ ├── carousel │ ├── slideshowTest.js │ ├── dottedIndexDisplayerTest.js │ ├── loopingControllerTest.js │ ├── keyboardNavigationTest.js │ ├── animatedProjectorTest.js │ ├── elementListTest.js │ ├── overlayProjectorTest.js │ ├── emptyListTest.js │ ├── clickNavigationTest.js │ ├── indexDisplayerTest.js │ ├── controllerTest.js │ ├── clickableElementNavigationTest.js │ ├── touchNavigationTest.js │ └── lazyImageListTest.js │ └── elementBuilderTest.js ├── .jshintrc ├── gruntfile.js ├── README.md └── dist └── carousel-1.0.0.min.js /src/finn/carousel/.sorting: -------------------------------------------------------------------------------- 1 | elementList.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .DS* 4 | target 5 | node_modules 6 | -------------------------------------------------------------------------------- /samples/img/sample1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample1.jpg -------------------------------------------------------------------------------- /samples/img/sample2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample2.jpg -------------------------------------------------------------------------------- /samples/img/sample3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample3.jpg -------------------------------------------------------------------------------- /samples/img/sample4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample4.jpg -------------------------------------------------------------------------------- /samples/img/sample5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample5.jpg -------------------------------------------------------------------------------- /samples/img/sample6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample6.jpg -------------------------------------------------------------------------------- /samples/img/sample7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/carousel-js/master/samples/img/sample7.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "carousel", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "grunt": "~0.4.1", 6 | "grunt-contrib-jshint": "~0.1.1", 7 | "grunt-contrib-uglify": "~0.2.1", 8 | "grunt-jstestdriver": "~2.2.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/finn/carousel/thumbnailNavigation.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C, $) { 3 | "use strict"; 4 | 5 | C.setupThumbnailNavigation = function (controller, root, getElementIndexOverride) { 6 | C.setupClickableElementNavigation(controller, root, "img", "selectedThumb", getElementIndexOverride); 7 | C.setupClickNavigation(controller, root, $(".thumbnails")); 8 | }; 9 | }(FINN.carousel, jQuery)); 10 | -------------------------------------------------------------------------------- /src/finn/carousel/slideshow.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | (function (C) { 4 | "use strict"; 5 | 6 | C.setupSlideshow = function (controller, interval) { 7 | var timeout; 8 | controller.on("show", function () { 9 | clearTimeout(timeout); 10 | timeout = setTimeout(controller.next.bind(controller), interval); 11 | }); 12 | }; 13 | }(FINN.carousel)); 14 | -------------------------------------------------------------------------------- /src/finn/carousel/autolint.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | "*.js" 4 | ], 5 | "excludes": [ 6 | "ckeditor", 7 | "jquery", 8 | "raphael", 9 | "underscore", 10 | "webtrends" 11 | ], 12 | "linterOptions": { 13 | "plusplus": false, 14 | "browser": true, 15 | "nomen": false, 16 | "maxlen": 120, 17 | "regexp": false, 18 | "onevar": false, 19 | "predef": [ 20 | "jQuery", "FINN", "_" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/resources/jsTestDriver.conf: -------------------------------------------------------------------------------- 1 | 2 | server: http://localhost:5555 3 | 4 | load: 5 | - ../../src/lib/bane/bane.js 6 | - ../../src/lib/underscore/underscore.js 7 | 8 | - ../../src/finn/elementBuilder.js 9 | - ../../src/finn/carousel/*.js 10 | 11 | test: 12 | - ../lib/jquery-1.8.2.js 13 | - ../lib/buster/buster-assertions.js 14 | - ../lib/buster/buster-format.js 15 | - ../lib/sinon/sinon-1.3.1.js 16 | - ../lib/sinon/sinon-ie-1.3.1.js 17 | - ../lib/sinon/sinon-buster.js 18 | - ../lib/setup-sinon-buster-for-jstestdriver.js 19 | - ../lib/test-helper.js 20 | - ../finn/elementBuilderTest.js 21 | - ../finn/carousel/*.js -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "newcap": true, 4 | "noarg": true, 5 | "eqeqeq": true, 6 | "eqnull": true, 7 | "undef": true, 8 | "nonew": true, 9 | "trailing": true, 10 | "immed": true, 11 | "es5": true, 12 | "node": true, 13 | "jquery": true, 14 | "validthis": true, 15 | "predef": [ 16 | "describe", 17 | "assert", 18 | "it", 19 | "before", 20 | "beforeEach", 21 | "after", 22 | "afterEach", 23 | "FINN", 24 | "document", 25 | "bane", 26 | "sinon", 27 | "testCase", 28 | "refute", 29 | "_" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/finn/carousel/clickToGallery.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | // Add gallery url in markup: 5 | (function (C) { 6 | "use strict"; 7 | 8 | function sendToGallery(e) { 9 | var targetUrl = $(e.target).data("gallery-url"); 10 | if (targetUrl) { 11 | document.location = targetUrl; 12 | } 13 | } 14 | 15 | C.setupClickToGallery = function (carousel) { 16 | $(carousel).on("click", sendToGallery); 17 | }; 18 | 19 | }(FINN.carousel)); 20 | -------------------------------------------------------------------------------- /src/finn/carousel/keyboardNavigation.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C, $) { 3 | "use strict"; 4 | 5 | C.setupKeyboardNavigation = function (controller) { 6 | $(document).bind("keyup", function (e) { 7 | if (!$(e.target).is(":input")) { 8 | switch (e.which) { 9 | case 37: 10 | controller.prev(); 11 | break; 12 | case 39: 13 | controller.next(); 14 | break; 15 | } 16 | } 17 | }); 18 | }; 19 | 20 | }(FINN.carousel, jQuery)); 21 | -------------------------------------------------------------------------------- /test/lib/setup-sinon-buster-for-jstestdriver.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var formatter = buster.create(buster.format); 3 | formatter.quoteStrings = false; 4 | buster.assertions.fail = fail; 5 | buster.assertions.format = function format() { 6 | return formatter.ascii.apply(formatter, arguments); 7 | }; 8 | var log = jstestdriver.console.log; 9 | jstestdriver.console.log = function () { 10 | var messages = []; 11 | for (var i = 0, l = arguments.length; i < l; ++i) { 12 | messages.push(formatter.ascii.call(formatter, arguments[i])); 13 | } 14 | log.call(jstestdriver.console, messages.join(" ")); 15 | } 16 | }()); 17 | 18 | var assert = buster.assertions.assert; 19 | var refute = buster.assertions.refute; 20 | -------------------------------------------------------------------------------- /src/finn/carousel/splitProjectorNavigation.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C, $) { 3 | "use strict"; 4 | 5 | C.setupSplitProjectorNavigation = function(controller, view) { 6 | $(view).on('click', function (e) { 7 | var projectorLeft = $(this).offset().left; 8 | var clickLeft = e.pageX; 9 | var howFarFromLeft = clickLeft - projectorLeft; 10 | var leftPartOfProjector = $(this).width() / 2; 11 | 12 | if (howFarFromLeft <= leftPartOfProjector) { 13 | controller.prev(); 14 | } 15 | else { 16 | controller.next(); 17 | } 18 | }); 19 | }; 20 | }(FINN.carousel, jQuery)); -------------------------------------------------------------------------------- /src/finn/carousel/loopingController.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C) { 5 | "use strict"; 6 | 7 | C.loopingController = FINN.compose(C.controller, { 8 | 9 | show: function(id) { 10 | if (id < 0) { 11 | id = this.seq.size() - 1; 12 | } else if (id === 0 || !this.seq.contains(id) || this.currentId === id) { 13 | id = 0; 14 | } 15 | 16 | this.currentId = id; 17 | this.emit("show", id); 18 | }, 19 | 20 | prev: function(){ 21 | var id = typeof this.currentId === "number" ? this.currentId - 1 : -1; 22 | this.show(id); 23 | } 24 | 25 | }); 26 | }(FINN.carousel)); -------------------------------------------------------------------------------- /src/finn/carousel/dottedIndexDisplayer.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | C.setupDottedIndexDisplayer = function (params) { 6 | if (!params) { throw new TypeError("Params must be given"); } 7 | var root = params.root; 8 | var list = params.list; 9 | var controller = params.controller; 10 | 11 | function updateDisplay() { 12 | var dottedHtml = ""; 13 | for (var i=0;i"; 16 | } 17 | root.innerHTML = dottedHtml; 18 | } 19 | updateDisplay(); 20 | controller.on("show", updateDisplay); 21 | }; 22 | }(FINN.carousel)); 23 | -------------------------------------------------------------------------------- /src/finn/carousel/indexDisplayer.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | C.setupIndexDisplayer = function (params) { 6 | if (!params) { throw new TypeError("Params must be given"); } 7 | var root = params.root; 8 | var type = params.type; 9 | var list = params.list; 10 | var label = params.label; 11 | if (!label){ 12 | label = type + " {0} av {1}"; 13 | } 14 | var controller = params.controller; 15 | 16 | function updateDisplay() { 17 | root.innerHTML = list.isBounded ? label.replace("{0}", (controller.currentId + 1)).replace("{1}", list.size()) : type + " " + (controller.currentId + 1); 18 | } 19 | 20 | updateDisplay(); 21 | controller.on("show", updateDisplay); 22 | }; 23 | }(FINN.carousel)); 24 | -------------------------------------------------------------------------------- /test/finn/carousel/slideshowTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (carousel) { 3 | "use strict"; 4 | var C = FINN.carousel || {}; 5 | testCase("Slideshow", sinon.testCase({ 6 | setUp: function () { 7 | this.next = sinon.spy(); 8 | this.controller = bane.createEventEmitter(); 9 | this.controller.next = this.next; 10 | this.clock = sinon.useFakeTimers(); 11 | }, 12 | 13 | "test should call next on controller": function () { 14 | C.setupSlideshow(this.controller, 500); 15 | refute.called(this.next); 16 | 17 | this.controller.emit("show"); 18 | 19 | this.clock.tick(499); 20 | refute.called(this.next); 21 | this.clock.tick(1); 22 | assert.calledOnce(this.next); 23 | } 24 | })); 25 | }(FINN.carousel)); 26 | -------------------------------------------------------------------------------- /src/finn/carousel/emptyList.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C, _) { 5 | "use strict"; 6 | 7 | C.emptyList = { 8 | isBounded: false, 9 | 10 | create: function (methods) { 11 | methods = methods || {}; 12 | var instance = FINN.compose(this, methods); 13 | 14 | if (methods.get) { 15 | instance.get = function (index, callback) { 16 | var result = methods.get.call(this, index, callback); 17 | if (typeof result !== "undefined") { callback(result); } 18 | }; 19 | } 20 | 21 | return instance; 22 | }, 23 | 24 | size: function () { return 0; }, 25 | contains: function (index) { return false; }, 26 | get: function (index, callback) {} 27 | }; 28 | }(FINN.carousel, _)); 29 | -------------------------------------------------------------------------------- /src/finn/carousel/clickableElementNavigation.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | C.setupClickableElementNavigation = function (controller, root, element, selector, getElementIndexOverride) { 6 | var selected = selector || "selectedElement"; 7 | 8 | var getElementIndex = function(item){ 9 | return $(item).index(); 10 | }; 11 | if (getElementIndexOverride){ 12 | getElementIndex = getElementIndexOverride; 13 | } 14 | 15 | $(root).delegate(element, "click", function () { 16 | controller.show(getElementIndex(this)); 17 | }); 18 | $(root).find(element).css("cursor", "pointer"); 19 | 20 | controller.on("show", function (index) { 21 | $(root).children().removeClass(selected). 22 | eq(index).addClass(selected); 23 | }); 24 | }; 25 | }(FINN.carousel)); 26 | -------------------------------------------------------------------------------- /src/finn/carousel/overlayProjector.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | C.overlayProjector = { 6 | create: function (controller, data, view) { 7 | return FINN.compose(this, { 8 | controller: controller, 9 | data: data, 10 | view: view 11 | }); 12 | }, 13 | 14 | buildCarousel: function () { 15 | var self = this; 16 | this.controller.on("show", function () { return self.show.apply(self, arguments); }); 17 | this.show(0); 18 | return this.view; 19 | }, 20 | 21 | show: function (index) { 22 | var self = this; 23 | this.data.get(index, function (element) { 24 | self.view.innerHTML = ""; 25 | self.view.appendChild(element); 26 | }); 27 | } 28 | }; 29 | }(FINN.carousel)); -------------------------------------------------------------------------------- /src/finn/carousel/animator.js: -------------------------------------------------------------------------------- 1 | FINN.carousel = FINN.carousel || {}; 2 | 3 | (function (C, $) { 4 | "use strict"; 5 | 6 | C.animator = { 7 | append: function (element) { 8 | throw new Error("Implement the append method to add element to the animation"); 9 | }, 10 | 11 | prepend: function (element) { 12 | this.append(element); 13 | }, 14 | 15 | detach: function () { 16 | this.element.parentNode.removeChild(this.element); 17 | }, 18 | 19 | animate: function (callback) { 20 | throw new Error("Implement the animate method to start the animation"); 21 | }, 22 | 23 | revertAnimation: function (callback) { 24 | throw new Error("Implement the animate method to revert the animation curretn"); 25 | }, 26 | 27 | stopAnimation: function () { 28 | if (this.animation) { 29 | this.animation.stop(); 30 | } 31 | } 32 | }; 33 | }(FINN.carousel, jQuery)); 34 | -------------------------------------------------------------------------------- /src/finn/carousel/clickNavigation.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function ($, C) { 5 | "use strict"; 6 | 7 | C.setupClickNavigation = function (controller, list, view) { 8 | var $next = $(view).find(".next"); 9 | var $prev = $(view).find(".prev"); 10 | 11 | $next.bind("click", function (e) { 12 | e.preventDefault(); 13 | controller.next(); 14 | }); 15 | 16 | $prev.bind("click", function (e) { 17 | e.preventDefault(); 18 | controller.prev(); 19 | }); 20 | 21 | var updateLinkBoundaries = function (index) { 22 | $prev.toggleClass("faded", index === 0); 23 | $next.toggleClass("faded", list.isBounded && index >= list.size() - 1); 24 | }; 25 | 26 | controller.on("show", updateLinkBoundaries); 27 | updateLinkBoundaries(controller.currentId); 28 | }; 29 | 30 | }(jQuery, FINN.carousel)); 31 | -------------------------------------------------------------------------------- /src/finn/carousel/elementList.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C, $) { 5 | "use strict"; 6 | 7 | function childAt(element, index) { 8 | if (index >= 0){ 9 | return $(element).children().get(index); 10 | } 11 | return false; 12 | } 13 | 14 | C.elementList = { 15 | isBounded: true, 16 | 17 | create: function (element) { 18 | return FINN.compose(this, { 19 | element: element 20 | }); 21 | }, 22 | 23 | size: function () { 24 | return $(this.element).children().length; 25 | }, 26 | 27 | contains: function (index) { 28 | return !!childAt(this.element, index); 29 | }, 30 | 31 | get: function (index, callback) { 32 | var node = childAt(this.element, index); 33 | callback($(node).clone().get(0)); 34 | } 35 | }; 36 | }(FINN.carousel, jQuery)); 37 | -------------------------------------------------------------------------------- /src/finn/core.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | var FINN = FINN || {}; 3 | (function(F){ 4 | "use strict"; 5 | if (typeof Object.create !== "function") { 6 | Object.create = function (obj) { 7 | function Proxy(){} 8 | Proxy.prototype = obj; 9 | return new Proxy(); 10 | }; 11 | } 12 | F.create = function(o){ 13 | return Object.create(o); 14 | }; 15 | F.compose = function () { 16 | if (arguments.length === 0 || !arguments[0]) { 17 | throw new TypeError("compose expects at least one object argument"); 18 | } 19 | var instance = FINN.create(arguments[0]), i, l, prop; 20 | for (i = 1, l = arguments.length; i < l; ++i) { 21 | if (!arguments[i]) { 22 | throw new TypeError("Tried to compose null or undefined"); 23 | } 24 | for (prop in arguments[i]) { 25 | instance[prop] = arguments[i][prop]; 26 | } 27 | } 28 | return instance; 29 | }; 30 | }(FINN)); 31 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | jshint: { 5 | all: [ 6 | 'src/finn/**/*.js', 7 | 'test/finn/**/*.js' 8 | ], 9 | options: { 10 | jshintrc: '.jshintrc', 11 | }, 12 | }, 13 | uglify: { 14 | options: { 15 | banner: '/*! <%=pkg.name%> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>.' + ' Copyright (c) <%= grunt.template.today("yyyy") %> FINN.no AS - http://finn.no/; Licensed MIT */\n' 16 | }, 17 | build: { 18 | src: ['src/finn/core.js', 'src/finn/elementBuilder.js', 'src/finn/<%=pkg.name%>/**/*.js'], 19 | dest: 'dist/<%= pkg.name %>-<%=pkg.version%>.min.js' 20 | } 21 | }, 22 | jstestdriver: { 23 | options: { 24 | canFail: true, 25 | verbose: true 26 | }, 27 | files: ["test/resources/jsTestDriver.conf"] 28 | } 29 | }); 30 | 31 | grunt.loadNpmTasks('grunt-contrib-jshint'); 32 | grunt.loadNpmTasks('grunt-contrib-uglify'); 33 | grunt.loadNpmTasks('grunt-jstestdriver'); 34 | 35 | grunt.registerTask('default', ['jshint', 'uglify', 'jstestdriver', ]); 36 | }; -------------------------------------------------------------------------------- /src/finn/carousel/controller.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C) { 5 | "use strict"; 6 | C.controller = bane.createEventEmitter({ 7 | 8 | create: function (seq) { 9 | return FINN.compose(this, { seq: seq, currentId: 0 }); 10 | }, 11 | 12 | start: function (startIndex) { 13 | this.currentId = -1; 14 | this.show(+startIndex || 0); 15 | }, 16 | 17 | show: function (id) { 18 | if (id < 0 || !this.seq.contains(id) || this.currentId === id) { 19 | return; 20 | } 21 | this.currentId = id; 22 | this.emit("show", id); 23 | }, 24 | 25 | peek: function(){ 26 | return this.currentId; 27 | }, 28 | 29 | setCurrentId: function(id){ 30 | this.currentId = id; 31 | }, 32 | 33 | next: function () { 34 | this.show(this.currentId + 1); 35 | }, 36 | 37 | prev: function () { 38 | var id = typeof this.currentId === "number" ? this.currentId - 1 : 0; 39 | this.show(id); 40 | } 41 | }); 42 | }(FINN.carousel)); 43 | -------------------------------------------------------------------------------- /samples/slideshow/setup.js: -------------------------------------------------------------------------------- 1 | (function($,carousel){ 2 | 3 | var images = document.querySelector("[data-carousel-itemList='albumPhotos']"); 4 | var imageList = carousel.lazyImageList.create(images); 5 | var controller = carousel.loopingController.create(imageList); 6 | 7 | var usesOverlayProjector = false; 8 | var imageProjector; 9 | 10 | if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { 11 | imageProjector = carousel.overlayProjector.create( 12 | controller, 13 | imageList, 14 | document.querySelector("[data-carousel-projector='albumPhotos']") 15 | ); 16 | usesOverlayProjector = true; 17 | } else { 18 | imageProjector = carousel.animatedProjector.create(controller, imageList, { 19 | duration: 500, 20 | animator: carousel.horizontalSlider 21 | }); 22 | } 23 | var imageCarousel = imageProjector.buildCarousel(); 24 | if (!usesOverlayProjector) { 25 | images.parentNode.insertBefore(imageCarousel, images); 26 | } 27 | images.style.display = "none"; 28 | 29 | // Simple sample of how you can extend the default behaviour to make a slideshow 30 | carousel.setupSlideshow(controller, 1800); 31 | controller.show(); 32 | 33 | }(jQuery, FINN.carousel)); -------------------------------------------------------------------------------- /samples/css/samples.css: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none; 3 | } 4 | .unselectify { 5 | -webkit-user-select: none; 6 | -moz-user-select: none; 7 | -ms-user-select: none; 8 | -o-user-select: none; 9 | user-select: none; 10 | } 11 | .centerify { 12 | text-align: center; 13 | } 14 | .imagecontainer { 15 | background-color: #f0f0f0; 16 | width: 80%; 17 | margin: auto; 18 | position: relative; 19 | } 20 | .imagecontainer:hover .btn-gallery { 21 | visibility: visible; 22 | } 23 | .btn-gallery{ 24 | padding: 10px; 25 | visibility: hidden; 26 | position:absolute; 27 | top:35%; 28 | z-index:50; 29 | background-color: #000; 30 | background-color: rgba(0, 0, 0, .3); 31 | } 32 | .btn-gallery:hover{ 33 | background-color: #000; 34 | background-color: rgba(0, 0, 0, .6); 35 | } 36 | .btn-gallery:after { 37 | content: ""; 38 | display:block; 39 | border-bottom: 15px solid transparent; 40 | border-top: 15px solid transparent; 41 | z-index:60; 42 | visibility: visible; 43 | } 44 | .prev-img:after { 45 | border-right: 15px solid #fff; 46 | } 47 | .next-img:after { 48 | border-left: 15px solid #fff; 49 | } 50 | .prev-img{ 51 | left:0; 52 | border-radius: 0 2px 2px 0; 53 | } 54 | .next-img{ 55 | right:0; 56 | border-radius: 2px 0 0 2px; 57 | } -------------------------------------------------------------------------------- /test/finn/carousel/dottedIndexDisplayerTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | testCase("DottedIndexDisplayerTest", sinon.testCase({ 5 | setUp: function () { 6 | this.list = { 7 | isBounded: true, 8 | size: function () { return 5; }, 9 | contains: function () { return true; } 10 | }; 11 | this.controller = C.controller.create(this.list); 12 | this.root = document.createElement("div"); 13 | this.params = { 14 | list: this.list, 15 | controller: this.controller, 16 | root: this.root 17 | }; 18 | }, 19 | "test should provide for passing in custom label rendering functions to allow dotted info": function(){ 20 | var updateDisplayFunc = this.params; 21 | this.controller.currentId = 2; 22 | C.setupDottedIndexDisplayer(this.params); 23 | assert.match(this.root.innerHTML, "" + 24 | "" + 25 | "" + 26 | "" + 27 | ""); 28 | }, 29 | })); 30 | }(FINN.carousel)); -------------------------------------------------------------------------------- /samples/lazyloading/setup.js: -------------------------------------------------------------------------------- 1 | (function($,carousel){ 2 | 3 | var images = document.querySelector("[data-carousel-itemList='albumPhotos']"); 4 | var imageList = carousel.lazyImageList.create(images); 5 | var controller = carousel.loopingController.create(imageList); 6 | 7 | var usesOverlayProjector = false; 8 | var imageProjector; 9 | 10 | if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { 11 | imageProjector = carousel.overlayProjector.create( 12 | controller, 13 | imageList, 14 | document.querySelector("[data-carousel-projector='albumPhotos']") 15 | ); 16 | usesOverlayProjector = true; 17 | } else { 18 | imageProjector = carousel.animatedProjector.create(controller, imageList, { 19 | duration: 500, 20 | animator: carousel.horizontalSlider 21 | }); 22 | } 23 | var imageCarousel = imageProjector.buildCarousel(); 24 | carousel.setupClickNavigation( 25 | controller, 26 | imageList, 27 | document.querySelector("[data-carousel-prevNext='albumPhotos']") 28 | ); 29 | carousel.setupKeyboardNavigation(controller); 30 | carousel.setupTouchNavigation(controller, imageCarousel); 31 | if (!usesOverlayProjector) { 32 | images.parentNode.insertBefore(imageCarousel, images); 33 | } 34 | images.style.display = "none"; 35 | }(jQuery, FINN.carousel)); -------------------------------------------------------------------------------- /test/finn/carousel/loopingControllerTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (carousel, $) { 3 | "use strict"; 4 | testCase("LoopingControllerTest", sinon.testCase({ 5 | setUp: function () { 6 | this.collection = carousel.elementList.create($('
')); 7 | this.controller = carousel.loopingController.create(this.collection); 8 | this.callback = sinon.spy(); 9 | this.controller.on("show", this.callback); 10 | 11 | this._last_collection_index = this.collection.size() - 1; 12 | }, 13 | 14 | "test should show last image when displaying first image and navigating previous": function () { 15 | this.controller.start(); 16 | this.controller.prev(); 17 | 18 | assert.calledTwice(this.callback); 19 | assert.equals(this.callback.getCall(1).args[0], this._last_collection_index); 20 | }, 21 | 22 | "test should show first image when displaying last image and navigating next": function () { 23 | this.controller.start(this._last_collection_index); 24 | this.controller.next(); 25 | 26 | assert.calledTwice(this.callback); 27 | assert.equals(this.callback.getCall(1).args[0], 0); 28 | } 29 | })); 30 | }(FINN.carousel, jQuery)); 31 | -------------------------------------------------------------------------------- /src/finn/carousel/touchNavigation.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | /* global FINN */ 3 | (function (C) { 4 | "use strict"; 5 | 6 | function getPosition(e) { 7 | return { 8 | x: e.targetTouches[0].pageX, 9 | y: e.targetTouches[0].pageY 10 | }; 11 | } 12 | 13 | C.setupTouchNavigation = function (controller, element) { 14 | var startPosition; 15 | 16 | element.addEventListener("touchstart", function (e) { 17 | if (e.touches.length > 1) { return; } 18 | startPosition = getPosition(e); 19 | }, false); 20 | 21 | element.addEventListener("touchmove", function (e) { 22 | if (!startPosition || e.touches.length > 1) { 23 | startPosition = null; 24 | return; 25 | } 26 | var currentPosition = getPosition(e); 27 | var dx = startPosition.x - currentPosition.x; 28 | var dy = startPosition.y - currentPosition.y; 29 | 30 | if (Math.abs(dy) > Math.abs(dx)) { 31 | return; 32 | } 33 | 34 | e.preventDefault(); 35 | 36 | if (dx >= 30) { 37 | controller.next(); 38 | } 39 | 40 | if (dx <= -30) { 41 | controller.prev(); 42 | } 43 | }, false); 44 | }; 45 | }(FINN.carousel)); 46 | -------------------------------------------------------------------------------- /test/finn/carousel/keyboardNavigationTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | testCase("KeyboardNavigationTest", sinon.testCase({ 6 | setUp: function () { 7 | this.controller = C.controller.create({ 8 | contains: this.stub().returns(true) 9 | }); 10 | this.controller.show(1); 11 | 12 | this.listener = this.spy(); 13 | this.controller.on("show", this.listener); 14 | 15 | C.setupKeyboardNavigation(this.controller); 16 | }, 17 | 18 | "test should show next when right target key is released": function () { 19 | $(document).trigger(new jQuery.Event("keyup", { which: 39 })); 20 | 21 | assert.calledOnceWith(this.listener, 2); 22 | }, 23 | 24 | "test should show previous when left target key is released": function () { 25 | $(document).trigger(new jQuery.Event("keyup", { which: 37 })); 26 | 27 | assert.calledOnceWith(this.listener, 0); 28 | }, 29 | 30 | "test should not trigger show when target is a form input element": function () { 31 | $(document).trigger(new jQuery.Event("keyup", { which: 37 , target: document.createElement("input")})); 32 | $(document).trigger(new jQuery.Event("keyup", { which: 37 , target: document.createElement("textarea")})); 33 | 34 | refute.called(this.listener); 35 | } 36 | })); 37 | }(FINN.carousel)); 38 | -------------------------------------------------------------------------------- /test/lib/test-helper.js: -------------------------------------------------------------------------------- 1 | var testCase = TestCase; 2 | 3 | window.alert = function alert(msg) { 4 | jstestdriver.console.log(msg); 5 | }; 6 | 7 | buster.assertions.add("exceptionWithMessage", { 8 | assert: function (func, message) { 9 | try { 10 | func(); 11 | this.actual = "no exception was thrown"; 12 | return false; 13 | } catch (e) { 14 | this.actual = "was " + e.message; 15 | return e.message === message; 16 | } 17 | }, 18 | assertMessage: "Expected exception with message ${1}: " + 19 | "but ${actual}" 20 | }); 21 | 22 | var FINN = FINN || {}; 23 | 24 | (function(F){ 25 | if (typeof Object.create !== "function") { 26 | Object.create = function (obj) { 27 | function Proxy(){}; 28 | Proxy.prototype = obj; 29 | return new Proxy(); 30 | }; 31 | } 32 | F.create = function(o){ 33 | return Object.create(o); 34 | }; 35 | 36 | /** Pulled out of globals.js from Oppdrag, required for carousel **/ 37 | F.compose = function () { 38 | if (arguments.length === 0 || !arguments[0]) { 39 | throw new TypeError("compose expects at least one object argument"); 40 | } 41 | var instance = FINN.create(arguments[0]), i, l, prop; 42 | for (i = 1, l = arguments.length; i < l; ++i) { 43 | if (!arguments[i]) { 44 | throw new TypeError("Tried to compose null or undefined"); 45 | } 46 | for (prop in arguments[i]) { 47 | instance[prop] = arguments[i][prop]; 48 | } 49 | } 50 | return instance; 51 | }; 52 | }(FINN)); -------------------------------------------------------------------------------- /test/finn/carousel/animatedProjectorTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | var div = FINN.elementBuilder("div"); 6 | 7 | testCase("AnimatedProjectorTest", sinon.testCase({ 8 | setUp: function () { 9 | this.slider = { 10 | tagName: "div", 11 | insertBefore: this.spy(), 12 | get: function (index, callback) { 13 | callback(div("Item #" + index)); 14 | }, 15 | size: function () { 16 | return 10; 17 | } 18 | }; 19 | this.controller = bane.createEventEmitter(); 20 | this.projector = C.animatedProjector.create(this.controller, this.slider, { 21 | animator: this.spy() 22 | }); 23 | this.projector.buildCarousel(); 24 | }, 25 | 26 | "test should animate backwards when at the first image and going to previous image": function () { 27 | assert(this.projector.isAnimatingBackwards(9)); 28 | }, 29 | 30 | "test should not animate backwards when at last image and going to next image": function () { 31 | this.projector.currentId = 9; 32 | 33 | refute(this.projector.isAnimatingBackwards(0)); 34 | }, 35 | 36 | "test should not animate backwards when having two images and going from the first to second image": function () { 37 | this.slider.size = this.stub().returns(2); 38 | 39 | refute(this.projector.isAnimatingBackwards(1)); 40 | } 41 | })); 42 | 43 | }(FINN.carousel)); 44 | -------------------------------------------------------------------------------- /samples/simplegallery/setup.js: -------------------------------------------------------------------------------- 1 | (function($,carousel){ 2 | 3 | var images = document.querySelector("[data-carousel-itemList='albumPhotos']"); 4 | var imageList = carousel.elementList.create(images); 5 | var controller = carousel.controller.create(imageList); 6 | 7 | var usesOverlayProjector = false; 8 | var imageProjector; 9 | 10 | if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { 11 | imageProjector = carousel.overlayProjector.create( 12 | controller, 13 | imageList, 14 | document.querySelector("[data-carousel-projector='albumPhotos']") 15 | ); 16 | usesOverlayProjector = true; 17 | } else { 18 | imageProjector = carousel.animatedProjector.create(controller, imageList, { 19 | duration: 500, 20 | animator: carousel.horizontalSlider 21 | }); 22 | } 23 | var imageCarousel = imageProjector.buildCarousel(); 24 | carousel.setupClickNavigation( 25 | controller, 26 | imageList, 27 | document.querySelector("[data-carousel-prevNext='albumPhotos']") 28 | ); 29 | // Add a displayer of how many images are in the carousel 30 | carousel.setupIndexDisplayer({ 31 | type: "Image", 32 | controller: controller, 33 | list: imageList, 34 | label: "{0} / {1}", 35 | root: document.querySelector("[data-carousel-indexDisplayer='albumPhotos']") 36 | }); 37 | 38 | carousel.setupKeyboardNavigation(controller); 39 | carousel.setupTouchNavigation(controller, imageCarousel); 40 | if (!usesOverlayProjector) { 41 | images.parentNode.insertBefore(imageCarousel, images); 42 | } 43 | images.style.display = "none"; 44 | }(jQuery, FINN.carousel)); -------------------------------------------------------------------------------- /test/finn/carousel/elementListTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | testCase("ElementListTest", sinon.testCase({ 5 | 6 | setUp: function () { 7 | /*:DOC ol =
    8 |
  1. 9 |
  2. 10 |
*/ 11 | this.list = C.elementList.create(this.ol); 12 | }, 13 | 14 | "test should be bounded": function () { 15 | assert(this.list.isBounded); 16 | }, 17 | 18 | "test should contain two elements": function () { 19 | assert(this.list.contains(0)); 20 | assert(this.list.contains(1)); 21 | assert.equals(this.list.size(), 2); 22 | }, 23 | 24 | "test should not contain more elements than child nodes": function () { 25 | refute(this.list.contains(2)); 26 | }, 27 | 28 | "test should not contain elements at negative index": function () { 29 | refute(this.list.contains(-1)); 30 | }, 31 | 32 | "test get yields cloned child element at position": function () { 33 | var original = this.ol.getElementsByTagName("li")[0]; 34 | var callback = this.spy(); 35 | this.list.get(0, callback); 36 | assert.calledOnce(callback); 37 | 38 | var clone = callback.args[0][0]; 39 | 40 | refute.equals(callback.args[0][0], original); 41 | assert.equals(clone.tagName, original.tagName); 42 | }, 43 | 44 | "test get yields undefined outside range": function () { 45 | var callback = this.spy(); 46 | this.list.get(5, callback); 47 | assert.calledOnceWith(callback, undefined); 48 | } 49 | 50 | })); 51 | }(FINN.carousel)); 52 | -------------------------------------------------------------------------------- /test/finn/carousel/overlayProjectorTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | var div = FINN.elementBuilder("div"); 5 | 6 | testCase("OverlayProjectorTest", sinon.testCase({ 7 | setUp: function () { 8 | this.slider = { 9 | tagName: "div", 10 | insertBefore: this.spy(), 11 | get: function (index, callback) { 12 | callback(div("Item #" + index)); 13 | } 14 | }; 15 | this.div = document.createElement("div"); 16 | this.controller = bane.createEventEmitter(); 17 | this.projector = C.overlayProjector.create(this.controller, this.slider, this.div); 18 | }, 19 | 20 | "test should put first element in frame": function () { 21 | var carousel = this.projector.buildCarousel(); 22 | 23 | assert.tagName(this.div.firstChild, "div"); 24 | assert.equals(this.div.firstChild.innerHTML, "Item #0"); 25 | }, 26 | 27 | "test should replace frame contents on show": function () { 28 | var carousel = this.projector.buildCarousel(); 29 | this.projector.show(1); 30 | 31 | assert.equals(this.div.firstChild.innerHTML, "Item #1"); 32 | }, 33 | 34 | "test should show item on controller signal": function () { 35 | var carousel = this.projector.buildCarousel(); 36 | this.controller.emit("show", 3); 37 | 38 | assert.equals(this.div.firstChild.innerHTML, "Item #3"); 39 | }, 40 | 41 | "test should return its view after building carousel": function () { 42 | var carousel = this.projector.buildCarousel(); 43 | 44 | assert.equals(this.div, carousel); 45 | } 46 | })); 47 | 48 | }(FINN.carousel)); 49 | -------------------------------------------------------------------------------- /src/finn/carousel/fader.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C, $) { 5 | "use strict"; 6 | 7 | var div = FINN.elementBuilder("div"); 8 | 9 | C.fader = FINN.compose(C.animator, { 10 | create: function (element, animOpt) { 11 | return FINN.compose(this, { 12 | animOpt: $.extend({ duration: 150 }, animOpt), 13 | element: div({ 14 | className: "frame", 15 | style: { position: "relative", display: "none" } 16 | }, element) 17 | }); 18 | }, 19 | 20 | append: function (element) { 21 | $(element).css({ 22 | position: "absolute", 23 | left: 0, 24 | top: 0, 25 | display: "none" 26 | }); 27 | var el = this.element.firstChild.nextSibling; 28 | while (el) { 29 | el.parentNode.removeChild(el); 30 | el = el.nextSibling; 31 | } 32 | this.element.appendChild(element); 33 | }, 34 | 35 | animate: function (callback) { 36 | this.stopAnimation(); 37 | $(this.element).css("display", "block"); 38 | var duration = this.animOpt.duration; 39 | this.animation = $(this.element.lastChild).fadeIn(duration, function () { 40 | callback(this.element.lastChild); 41 | }.bind(this)); 42 | 43 | }, 44 | 45 | revertAnimation: function (callback) { 46 | this.stopAnimation(); 47 | var duration = this.animOpt.duration; 48 | this.animation = $(this.element.lastChild).fadeOut(duration, function () { 49 | callback(this.element.firstChild); 50 | }.bind(this)); 51 | } 52 | }); 53 | }(FINN.carousel, jQuery)); 54 | -------------------------------------------------------------------------------- /src/finn/carousel/lazyImageList.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C, $) { 3 | "use strict"; 4 | 5 | function childAt(element, index) { 6 | return $(element).children().get(index); 7 | } 8 | 9 | function activateDataSrc(index, readyCallback) { 10 | if (!this.contains(index)) { return; } 11 | var node = childAt(this.element, index); 12 | var img = $(node).find("img").get(0); 13 | var dataSrc = img.getAttribute("data-src"); 14 | if (dataSrc) { 15 | notifyReadyWhenLoaded(img, node, readyCallback); 16 | downloadAlternativeImageWhenNotFound.call(this, img); 17 | 18 | img.src = dataSrc; 19 | img.setAttribute("data-src", ""); 20 | } else if (readyCallback !== undefined) { 21 | readyCallback($(node).clone().get(0)); 22 | } 23 | } 24 | 25 | function notifyReadyWhenLoaded(img, node, readyCallback) { 26 | if (readyCallback === undefined) { return; } 27 | 28 | $(img).one('load', function () { 29 | readyCallback($(node).clone().get(0)); 30 | }); 31 | } 32 | 33 | function downloadAlternativeImageWhenNotFound(img) { 34 | if (this.errorCallback === undefined) { return; } 35 | var resolver = this.errorCallback; 36 | 37 | $(img).one('error', function() { 38 | var alternativePath = resolver(this.getAttribute("src")); 39 | if (alternativePath !== undefined) { 40 | this.src = alternativePath; 41 | } 42 | }); 43 | } 44 | 45 | C.lazyImageList = FINN.compose(C.elementList, { 46 | create: function (element, errorCallback) { 47 | return FINN.compose(this, { 48 | element: element, 49 | errorCallback: errorCallback 50 | }); 51 | }, 52 | 53 | get: function (index, readyCallback) { 54 | activateDataSrc.call(this, index, readyCallback); 55 | activateDataSrc.call(this, index + 1); 56 | } 57 | }); 58 | 59 | }(FINN.carousel, jQuery)); -------------------------------------------------------------------------------- /samples/slideshow/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample image gallery 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/finn/carousel/slider.org: -------------------------------------------------------------------------------- 1 | * Projector [0/2] 2 | ** TODO Bygge viewport [10/12] 3 | *** Input: slider, som er et DOM-element med flere children som skal karuselles 4 | *** DONE Wrappe slider i carousel-element 5 | *** DONE Bygg viewport (putt i carousel) 6 | *** DONE Sett position: relative på viewport 7 | *** DONE Sett width: 100% på viewport 8 | *** DONE Sett overflow: hidden på viewport 9 | *** DONE Bygg frame 10 | **** Tag navn på hentes fra opprinnelig slider 11 | *** DONE Hente start-element fra slider, putt i frame 12 | *** TODO Gi viewport mulighet for å vise mer enn ett element ad gangen 13 | *** TODO Start-element må ha max-width = 100 / antall-som-vises 14 | *** DONE Putt frame i viewport-elementet 15 | *** DONE Skjule slider 16 | *** DONE Lytte på events fra controlleren 17 | ** TODO Animere / vis neste [18/21] 18 | *** DONE Lag en ny frame 19 | *** DONE Kopier(!) current element inn i frame 20 | *** DONE Flytt(!) neste element inn i frame 21 | *** DONE Les bredde på current frame 22 | *** DONE Sett bredde på ny frame 23 | *** DONE Plasser ny frame under current frame i DOM-en 24 | *** DONE Begge elementene i ny frame må få eksplisitt bredde 25 | *** DONE Begge elementene i ny frame må floates 26 | *** DONE Absolutt-posisjonere den nye framen (0,0) 27 | *** DONE Animer frame til left: -currentWidth 28 | *** DONE Flytt innhold i ny frame til current frame 29 | *** DONE Fjern ny frame 30 | *** DONE Vis forrige 31 | *** DONE Lag animation frame 32 | *** DONE Refaktorer projector til slidingElementProjector 33 | *** DONE Lag overlayProjector 34 | *** TODO Vis når abs(show - current) > 1 35 | *** TODO Viewport viser mer enn ett element 36 | *** TODO Når flere enn ett element vises i slengen, beregn bredde deretter 37 | *** DONE Forrige må animere riktig vei 38 | *** DONE Ny show mens en animasjon går [4/4] 39 | **** DONE Stopp gjeldende animasjon 40 | **** DONE Legg til element i animasjonsramme 41 | **** DONE Regn ut nye start/stopp-verdier 42 | **** DONE Start animasjon på nytt 43 | * Controller [2/2] 44 | ** DONE La den ta en "seq" som input 45 | ** DONE Default state må starte på 0 46 | * Clean-up/annet [1/2] 47 | ** DONE Finn tester som animerer, og clock.tick i teardown (lykke til) 48 | ** TODO horizontalSlider bør bli til en overlay-typ for IE 49 | evnt. fix anim :-P 50 | -------------------------------------------------------------------------------- /test/finn/carousel/emptyListTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | testCase("EmptyListTest", sinon.testCase({ 5 | 6 | "test should create usable data object": function () { 7 | var list = C.emptyList.create(); 8 | assert.isFunction(list.contains); 9 | assert.isFunction(list.get); 10 | }, 11 | 12 | "test contains nothing by default": function () { 13 | var list = C.emptyList.create(); 14 | refute(list.contains(0)); 15 | refute(list.contains(1)); 16 | refute(list.contains(100)); 17 | }, 18 | 19 | "test should not be bounded": function () { 20 | var list = C.emptyList.create(); 21 | refute(list.isBounded); 22 | }, 23 | 24 | "test should have size 0": function () { 25 | var list = C.emptyList.create(); 26 | assert.equals(list.size(), 0); 27 | }, 28 | 29 | "test provides custom contains implementation": function () { 30 | var list = C.emptyList.create({ 31 | contains: this.stub().returns(true) 32 | }); 33 | 34 | assert(list.contains(0)); 35 | assert(list.contains(1)); 36 | }, 37 | 38 | "test default get does not call back": function () { 39 | var list = C.emptyList.create(); 40 | var callback = this.spy(); 41 | list.get(0, callback); 42 | refute.called(callback); 43 | }, 44 | 45 | "test default get converts return value to callback": function () { 46 | var list = C.emptyList.create({ 47 | get: this.stub().returns(42) 48 | }); 49 | var callback = this.spy(); 50 | list.get(0, callback); 51 | assert.calledOnceWith(callback, 42); 52 | }, 53 | 54 | "test does not interfere with get method that invokes callback": function () { 55 | var list = C.emptyList.create({ 56 | get: this.stub().yields(73) 57 | }); 58 | var callback = this.spy(); 59 | list.get(12, callback); 60 | assert.calledOnceWith(callback, 73); 61 | } 62 | 63 | })); 64 | }(FINN.carousel)); 65 | -------------------------------------------------------------------------------- /src/finn/carousel/fullscreenView.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function(C,$){ 5 | "use strict"; 6 | 7 | C.hasFullscreenSupport = function(){ 8 | var docElm = document.documentElement; 9 | if (docElm.requestFullscreen) { 10 | return true; 11 | } 12 | else if (docElm.mozRequestFullScreen) { 13 | return true; 14 | } 15 | else if (docElm.webkitRequestFullScreen) { 16 | return true; 17 | } 18 | return false; 19 | }; 20 | 21 | C.setupFullscreenSupport = function(triggerId, controller){ 22 | var viewFullScreen = document.getElementById(triggerId); 23 | if (viewFullScreen) { 24 | viewFullScreen.addEventListener("click", function () { 25 | $("#" + triggerId).hide(); 26 | enterFullscreen(); 27 | }, false); 28 | } 29 | document.addEventListener("fullscreenchange", function () { 30 | if (!document.fullscreen){ 31 | $("#" + triggerId).show(); 32 | } 33 | }, false); 34 | 35 | document.addEventListener("mozfullscreenchange", function () { 36 | if (!document.mozFullScreen){ 37 | $("#" + triggerId).show(); 38 | } 39 | }, false); 40 | 41 | document.addEventListener("webkitfullscreenchange", function () { 42 | if (!document.webkitIsFullScreen){ 43 | $("#" + triggerId).show(); 44 | } 45 | }, false); 46 | 47 | }; 48 | C.setupKeyboardShortcut = function(keyCode){ 49 | $(document).bind("keyup", function (e) { 50 | if (e) { 51 | console.log(e.which); 52 | if (e.which === 70){ 53 | enterFullscreen(); 54 | } 55 | } 56 | }); 57 | }; 58 | function enterFullscreen(){ 59 | var docElm = document.documentElement; 60 | if (docElm.requestFullscreen) { 61 | docElm.requestFullscreen(); 62 | } 63 | else if (docElm.mozRequestFullScreen) { 64 | docElm.mozRequestFullScreen(); 65 | } 66 | else if (docElm.webkitRequestFullScreen) { 67 | docElm.webkitRequestFullScreen(); 68 | } 69 | } 70 | }(FINN.carousel, jQuery)); -------------------------------------------------------------------------------- /samples/lazyloading/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample image gallery 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /samples/simplegallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample image gallery 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/finn/carousel/clickNavigationTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | testCase("ClickNavigationTest", sinon.testCase({ 5 | setUp: function () { 6 | /*:DOC element =
    7 | 8 | 9 |
*/ 10 | var list = { 11 | size: function () { return 120; }, 12 | contains: this.stub().returns(true) 13 | }; 14 | this.controller = C.controller.create(list); 15 | this.listener = this.spy(); 16 | this.controller.on("show", this.listener); 17 | this.next = $(this.element).find(".next").get(0); 18 | this.prev = $(this.element).find(".prev").get(0); 19 | 20 | C.setupClickNavigation(this.controller, list, this.element); 21 | }, 22 | 23 | "test should ask for next frame when clicking next element": function () { 24 | $(this.next).trigger("click"); 25 | 26 | assert.calledOnceWith(this.listener, 1); 27 | }, 28 | 29 | "test should ask for previous frame when clicking previous element": function () { 30 | this.controller.next(); 31 | $(this.prev).trigger("click"); 32 | 33 | assert.calledWith(this.listener, 0); 34 | }, 35 | 36 | "test should fade out prev when at first element": function () { 37 | assert.className(this.prev, "faded"); 38 | }, 39 | 40 | "test should fade prev in again when at second element": function () { 41 | this.controller.next(); 42 | 43 | refute.className(this.prev, "faded"); 44 | }, 45 | 46 | "test should fade out prev when going back to first": function () { 47 | this.controller.next(); 48 | this.controller.prev(); 49 | 50 | assert.className(this.prev, "faded"); 51 | }, 52 | 53 | "test should fade out next when at last element": function () { 54 | var list = C.emptyList.create({ isBounded: true }); 55 | var controller = C.controller.create(list); 56 | C.setupClickNavigation(controller, list, this.element); 57 | 58 | assert.className(this.next, "faded"); 59 | }, 60 | 61 | "test should never fade out next for unbounded lists": function () { 62 | var list = C.emptyList.create({ isBounded: false }); 63 | var controller = C.controller.create(list); 64 | C.setupClickNavigation(controller, list, this.element); 65 | 66 | refute.className(this.next, "faded"); 67 | } 68 | })); 69 | 70 | }(FINN.carousel)); 71 | -------------------------------------------------------------------------------- /test/finn/carousel/indexDisplayerTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | testCase("IndexDisplayerTest", sinon.testCase({ 5 | setUp: function () { 6 | this.list = { 7 | isBounded: true, 8 | size: function () { return 5; }, 9 | contains: function () { return true; } 10 | }; 11 | this.controller = C.controller.create(this.list); 12 | this.root = document.createElement("div"); 13 | this.params = { 14 | type: "Bilde", 15 | list: this.list, 16 | controller: this.controller, 17 | root: this.root 18 | }; 19 | }, 20 | 21 | "test should whine if given no params": function () { 22 | assert.exception(function () { 23 | C.setupIndexDisplayer(); 24 | }); 25 | }, 26 | 27 | "test should not show upper bound for unbounded lists": function () { 28 | this.list.isBounded = false; 29 | C.setupIndexDisplayer(this.params); 30 | 31 | assert.match(this.root.innerHTML, "Bilde 1"); 32 | refute.match(this.root.innerHTML, "Bilde 1 av 5"); 33 | }, 34 | 35 | "test should show index in root": function () { 36 | C.setupIndexDisplayer(this.params); 37 | 38 | assert.match(this.root.innerHTML, "Bilde 1 av 5"); 39 | }, 40 | 41 | "test should display correct current image index": function () { 42 | this.controller.currentId = 1; 43 | C.setupIndexDisplayer(this.params); 44 | 45 | assert.match(this.root.innerHTML, "Bilde 2 av 5"); 46 | }, 47 | 48 | "test should update index on show": function () { 49 | C.setupIndexDisplayer(this.params); 50 | this.controller.next(); 51 | 52 | assert.match(this.root.innerHTML, "Bilde 2 av 5"); 53 | }, 54 | 55 | "test should provide for passing in custom label patterns": function () { 56 | var labelOverrideParams = this.params; 57 | 58 | labelOverrideParams.label = "Videoer {0} av {1}"; 59 | C.setupIndexDisplayer(this.params); 60 | assert.match(this.root.innerHTML, "Videoer 1 av 5"); 61 | 62 | labelOverrideParams.label = "Totalt ant. videoer {1}"; 63 | C.setupIndexDisplayer(this.params); 64 | assert.match(this.root.innerHTML, "Totalt ant. videoer 5"); 65 | 66 | labelOverrideParams.label = "Video nr {0}"; 67 | C.setupIndexDisplayer(this.params); 68 | assert.match(this.root.innerHTML, "Video nr 1"); 69 | 70 | } 71 | })); 72 | }(FINN.carousel)); 73 | -------------------------------------------------------------------------------- /src/finn/elementBuilder.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | /*global FINN,jQuery,document */ 3 | (function (F, $) { 4 | "use strict"; 5 | 6 | F.fragment = function () { 7 | var fragment = document.createDocumentFragment(); 8 | var args = $.isArray(arguments[0]) ? arguments[0] : arguments; 9 | 10 | for (var i = 0, l = args.length; i < l; ++i) { 11 | if (args[i]) { fragment.appendChild(args[i]); } 12 | } 13 | 14 | return fragment; 15 | }; 16 | 17 | var documentFragmentType = 11; 18 | 19 | var builder = F.elementBuilder = function (tagName) { 20 | return function (attributes) { 21 | var content, sliceIndex = 1, attrs = attributes; 22 | 23 | if (!attributes || 24 | attributes.tagName || 25 | attributes.nodeType === documentFragmentType || 26 | typeof attributes === "string") { 27 | sliceIndex = 0; 28 | attrs = {}; 29 | } 30 | 31 | return builder.addContent( 32 | builder.attr(document.createElement(tagName), attrs), 33 | [].slice.call(arguments, sliceIndex)); 34 | }; 35 | }; 36 | 37 | F.elementBuilder.build = function (tagName, attribtues) { 38 | return F.elementBuilder(tagName).apply(null, [].slice.call(arguments, 1)); 39 | }; 40 | 41 | builder.attr = function (el, attributes) { 42 | var attr, handler; 43 | 44 | for (attr in attributes) { 45 | if (attributes.hasOwnProperty(attr)) { 46 | handler = builder.attrHandlers[attr]; 47 | if (handler) { 48 | handler(el, attributes[attr]); 49 | } else { 50 | el[attr] = attributes[attr]; 51 | } 52 | } 53 | } 54 | 55 | return el; 56 | }; 57 | 58 | builder.addContent = function (element, content) { 59 | for (var i = 0, l = content.length; i < l; ++i) { 60 | if (typeof content[i] === "string") { 61 | element.appendChild(document.createTextNode(content[i])); 62 | } else { 63 | element.appendChild(content[i]); 64 | } 65 | } 66 | 67 | return element; 68 | }; 69 | 70 | builder.attrHandlers = { 71 | events: function (element, events) { 72 | var $element = $(element); 73 | 74 | for (var event in events) { 75 | if (events.hasOwnProperty(event)) { 76 | $element.bind(event, events[event]); 77 | } 78 | } 79 | }, 80 | style: function (element, styles) { 81 | $(element).css(styles); 82 | } 83 | }; 84 | 85 | var div = builder("div"); 86 | 87 | builder.mod = function (moduleType) { 88 | return div({ className: "mod mod_" + moduleType }, 89 | builder.addContent(div({ className: "bd" }), 90 | [].slice.call(arguments, 1))); 91 | }; 92 | }(FINN, jQuery)); 93 | -------------------------------------------------------------------------------- /test/finn/carousel/controllerTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (carousel) { 3 | "use strict"; 4 | testCase("ControllerTest", sinon.testCase({ 5 | setUp: function () { 6 | this.collection = { contains: this.stub().returns(true) }; 7 | this.controller = carousel.controller.create(this.collection); 8 | this.callback = sinon.spy(); 9 | this.controller.on("show", this.callback); 10 | }, 11 | 12 | "test should show 0 on start": function () { 13 | this.controller.start(); 14 | 15 | assert.calledOnceWith(this.callback, 0); 16 | }, 17 | 18 | "test should show given index on start": function () { 19 | this.controller.start("5"); 20 | 21 | assert.calledOnceWith(this.callback, 5); 22 | }, 23 | 24 | "test show emits id to show": function () { 25 | this.controller.show(1); 26 | 27 | assert.calledOnceWith(this.callback, 1); 28 | }, 29 | 30 | "test show does not emit on index overflow": function () { 31 | this.collection.contains.withArgs(4).returns(false); 32 | this.controller.show(4); 33 | 34 | refute.called(this.callback); 35 | }, 36 | 37 | "test show does not emit on index underflow": function () { 38 | this.collection.contains.withArgs(-1).returns(false); 39 | this.controller.show(-1); 40 | 41 | refute.called(this.callback); 42 | }, 43 | 44 | "test show does not emit duplicate position": function () { 45 | this.controller.show(1); 46 | this.controller.show(1); 47 | 48 | assert.calledOnce(this.callback); 49 | }, 50 | 51 | "test next emits 1 on first call": function () { 52 | this.controller.next(); 53 | 54 | assert.calledOnceWith(this.callback, 1); 55 | }, 56 | 57 | "test next advances current index until last index": function () { 58 | this.collection.contains.withArgs(3).returns(false); 59 | this.controller.next(); 60 | this.controller.next(); 61 | this.controller.next(); 62 | 63 | assert.calledTwice(this.callback); 64 | assert.equals(this.callback.getCall(0).args[0], 1); 65 | assert.equals(this.callback.getCall(1).args[0], 2); 66 | }, 67 | 68 | "test prev emits 0 on first call": function () { 69 | this.controller.prev(); 70 | refute.called(this.callback); 71 | }, 72 | 73 | "test prev moves index back until 0": function () { 74 | this.controller.show(2); 75 | this.controller.prev(); 76 | this.controller.prev(); 77 | this.controller.prev(); 78 | 79 | assert.calledThrice(this.callback); 80 | assert.equals(this.callback.getCall(0).args[0], 2); 81 | assert.equals(this.callback.getCall(1).args[0], 1); 82 | assert.equals(this.callback.getCall(2).args[0], 0); 83 | } 84 | })); 85 | }(FINN.carousel)); 86 | -------------------------------------------------------------------------------- /test/finn/carousel/clickableElementNavigationTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | testCase("ClickableElementNavigationTest", sinon.testCase({ 5 | setUp: function () { 6 | /*:DOC thumbs =
7 | 8 | 9 | 10 | 11 |
*/ 12 | 13 | this.catler = $(this.thumbs).children().get(0); 14 | this.catlin = $(this.thumbs).children().get(1); 15 | this.lolcat = $(this.thumbs).children().get(2); 16 | this.catnip = $(this.thumbs).children().get(3); 17 | 18 | this.controller = C.controller.create({ 19 | contains: this.stub().returns(true) 20 | }); 21 | this.controller.show(1); 22 | 23 | this.listener = this.spy(); 24 | this.controller.on("show", this.listener); 25 | 26 | C.setupClickableElementNavigation(this.controller, this.thumbs, "img"); 27 | }, 28 | 29 | "test should show image when thumb is clicked": function () { 30 | $(this.lolcat).trigger("click"); 31 | 32 | assert.calledOnceWith(this.listener, 2); 33 | }, 34 | 35 | "test should show correct image": function () { 36 | $(this.catnip).trigger("click"); 37 | 38 | assert.calledOnceWith(this.listener, 3); 39 | }, 40 | 41 | "test should highlight image when shown": function () { 42 | this.controller.show(2); 43 | 44 | assert.className(this.lolcat, "selectedElement"); 45 | }, 46 | 47 | "test should not highlight previous image": function () { 48 | this.controller.show(2); 49 | this.controller.show(1); 50 | this.controller.show(0); 51 | 52 | assert.className(this.catler, "selectedElement"); 53 | refute.className(this.catlin, "selectedElement"); 54 | refute.className(this.lolcat, "selectedElement"); 55 | }, 56 | 57 | "test should unhighlight images when selected is out of bounds": function () { 58 | this.controller.show(2); 59 | this.controller.show(5); 60 | 61 | refute.className(this.lolcat, "selectedElement"); 62 | }, 63 | 64 | "test should be possible to give custom selector": function () { 65 | C.setupClickableElementNavigation(this.controller, this.thumbs, "img", "test"); 66 | this.controller.show(2); 67 | 68 | assert.className(this.lolcat, "test"); 69 | }, 70 | 71 | "test should be possible to pass in a custom function for selecting the element index in the list": function(){ 72 | var elementIndexSpy = sinon.spy(); 73 | C.setupClickableElementNavigation(this.controller, this.thumbs, "img", "test", elementIndexSpy); 74 | $(this.thumbs).find("img:first").click(); 75 | assert.called(elementIndexSpy); 76 | } 77 | })); 78 | }(FINN.carousel)); 79 | -------------------------------------------------------------------------------- /test/finn/carousel/touchNavigationTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C) { 3 | "use strict"; 4 | 5 | function touchEvent(x, y) { 6 | var touches = [ { pageX: x, pageY: y || 100 } ]; 7 | return { 8 | touches: touches, 9 | targetTouches: touches, 10 | preventDefault: sinon.stub() 11 | }; 12 | } 13 | 14 | testCase("TouchNavigationTest", sinon.testCase({ 15 | setUp: function () { 16 | this.element = { addEventListener: this.stub() }; 17 | 18 | this.controller = C.controller.create({ 19 | contains: this.stub().returns(true) 20 | }); 21 | this.controller.show(1); 22 | 23 | this.listener = this.spy(); 24 | this.controller.on("show", this.listener); 25 | 26 | C.setupTouchNavigation(this.controller, this.element); 27 | 28 | this.touchStartHandler = this.element.addEventListener.getCall(0).args[1]; 29 | this.touchMoveHandler = this.element.addEventListener.getCall(1).args[1]; 30 | 31 | this.touchStartHandler(touchEvent(100, 100)); 32 | }, 33 | 34 | "test should show for touch event over treshold": function () { 35 | this.touchMoveHandler(touchEvent(70)); 36 | 37 | assert.calledOnceWith(this.listener, 2); 38 | }, 39 | 40 | "test should not show if movement below threshold": function () { 41 | this.touchMoveHandler(touchEvent(80)); 42 | 43 | refute.called(this.listener); 44 | }, 45 | 46 | "test should show if several movements exceed threshold": function () { 47 | this.touchMoveHandler(touchEvent(80)); 48 | this.touchMoveHandler(touchEvent(60)); 49 | 50 | assert.calledOnceWith(this.listener, 2); 51 | }, 52 | 53 | "test should not register as swipe if vertical swipe": function () { 54 | this.touchMoveHandler(touchEvent(70, 60)); 55 | 56 | refute.called(this.listener); 57 | }, 58 | 59 | "test should not prevent default when vertical swipe": function () { 60 | var event = touchEvent(70, 60); 61 | this.touchMoveHandler(event); 62 | 63 | refute.called(event.preventDefault); 64 | }, 65 | 66 | "test should prevent default when horizontal swipe": function () { 67 | var event = touchEvent(130); 68 | this.touchMoveHandler(event); 69 | 70 | assert.calledOnce(event.preventDefault); 71 | }, 72 | 73 | "test should show previous image if right swipe": function () { 74 | this.touchMoveHandler(touchEvent(130)); 75 | 76 | assert.calledOnceWith(this.listener, 0); 77 | }, 78 | 79 | "test should not swipe with multiple touches": function () { 80 | var event = touchEvent(130); 81 | event.touches.push({ pageX: 40, pageY: 30 }); 82 | this.touchMoveHandler(event); 83 | 84 | refute.called(this.listener); 85 | }, 86 | 87 | "test should not swipe if there was multiple touches": function () { 88 | var event = touchEvent(120); 89 | event.touches.push({ pageX: 40, pageY: 30 }); 90 | this.touchMoveHandler(event); 91 | this.touchMoveHandler(touchEvent(140)); 92 | 93 | refute.called(this.listener); 94 | } 95 | })); 96 | }(FINN.carousel)); 97 | -------------------------------------------------------------------------------- /test/lib/sinon/sinon-ie-1.3.1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sinon.JS 1.3.1, 2012/01/04 3 | * 4 | * @author Christian Johansen (christian@cjohansen.no) 5 | * 6 | * (The BSD License) 7 | * 8 | * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * * Neither the name of Christian Johansen nor the names of his contributors 20 | * may be used to endorse or promote products derived from this software 21 | * without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | "use strict"; 36 | /*global sinon, setTimeout, setInterval, clearTimeout, clearInterval, Date*/ 37 | /** 38 | * Helps IE run the fake timers. By defining global functions, IE allows 39 | * them to be overwritten at a later point. If these are not defined like 40 | * this, overwriting them will result in anything from an exception to browser 41 | * crash. 42 | * 43 | * If you don't require fake timers to work in IE, don't include this file. 44 | * 45 | * @author Christian Johansen (christian@cjohansen.no) 46 | * @license BSD 47 | * 48 | * Copyright (c) 2010-2011 Christian Johansen 49 | */ 50 | function setTimeout() {} 51 | function clearTimeout() {} 52 | function setInterval() {} 53 | function clearInterval() {} 54 | function Date() {} 55 | 56 | // Reassign the original functions. Now their writable attribute 57 | // should be true. Hackish, I know, but it works. 58 | setTimeout = sinon.timers.setTimeout; 59 | clearTimeout = sinon.timers.clearTimeout; 60 | setInterval = sinon.timers.setInterval; 61 | clearInterval = sinon.timers.clearInterval; 62 | Date = sinon.timers.Date; 63 | 64 | /*global sinon*/ 65 | /** 66 | * Helps IE run the fake XMLHttpRequest. By defining global functions, IE allows 67 | * them to be overwritten at a later point. If these are not defined like 68 | * this, overwriting them will result in anything from an exception to browser 69 | * crash. 70 | * 71 | * If you don't require fake XHR to work in IE, don't include this file. 72 | * 73 | * @author Christian Johansen (christian@cjohansen.no) 74 | * @license BSD 75 | * 76 | * Copyright (c) 2010-2011 Christian Johansen 77 | */ 78 | function XMLHttpRequest() {} 79 | 80 | // Reassign the original function. Now its writable attribute 81 | // should be true. Hackish, I know, but it works. 82 | XMLHttpRequest = sinon.xhr.XMLHttpRequest || undefined; 83 | -------------------------------------------------------------------------------- /test/finn/carousel/lazyImageListTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (C, $) { 3 | "use strict"; 4 | 5 | testCase("LazyImageListTest", sinon.testCase({ 6 | setUp: function () { 7 | this.$ol = $('
    '+ 8 | '
  1. '+ 9 | '
  2. '+ 10 | '
  3. '+ 11 | '
'); 12 | this.list = C.lazyImageList.create(this.$ol); 13 | }, 14 | 15 | "test should be an elementList": function () { 16 | assert.equals(C.lazyImageList.size, C.elementList.size); 17 | }, 18 | 19 | "test should should swap data-src with src on get": function () { 20 | this.list.get(1, function (el) { 21 | assert.match(el.firstChild.src, "catlin.png"); 22 | refute.match(el.firstChild.getAttribute("data-src"), "catlin.png"); 23 | }); 24 | }, 25 | 26 | "test should eagerly fetch the next image too": function () { 27 | this.list.get(1, this.stub()); 28 | var el = this.$ol.find("li:last").get(0); 29 | 30 | assert.match(el.firstChild.src, "catpictures.png"); 31 | refute.match(el.firstChild.getAttribute("data-src"), "catpictures.png"); 32 | }, 33 | 34 | "test should compute on last image": function () { 35 | this.list.get(2, this.stub()); 36 | }, 37 | 38 | "test should notify callback when image just got loaded": function () { 39 | var readyCallback = this.spy(); 40 | var $notDownloadedImage = this.$ol.find("img:last"); 41 | 42 | this.list.get(2, readyCallback); 43 | 44 | refute.called(readyCallback); 45 | $notDownloadedImage.trigger('load'); 46 | assert.called(readyCallback); 47 | }, 48 | 49 | "test should notify callback immediately when image is already downloaded": function () { 50 | var readyCallback = this.spy(); 51 | 52 | this.list.get(0, readyCallback); 53 | assert.called(readyCallback); 54 | } 55 | })); 56 | 57 | testCase("LazyImageListErrorTest", sinon.testCase({ 58 | setUp: function () { 59 | this.$ol = $('
    '+ 60 | '
  1. '+ 61 | '
  2. '+ 62 | '
  3. '+ 63 | '
'); 64 | }, 65 | 66 | "test should ask errorCallback for alternative image path when image retrieval fails": function () { 67 | var errorCallback = this.spy(); 68 | var $invalidImage = this.$ol.find("img:last"); 69 | 70 | this.whenCreatingImageListWithErrorHandler(errorCallback); 71 | this.list.get(2); 72 | 73 | $invalidImage.trigger('error'); 74 | assert.calledWith(errorCallback, "catpictures.png"); 75 | }, 76 | 77 | "test should use alternative image path from given by errorCallback when image retrieval fails": function () { 78 | var errorCallback = this.stub().returns("not-found.png"); 79 | var $invalidImage = this.$ol.find("img:last"); 80 | 81 | this.whenCreatingImageListWithErrorHandler(errorCallback); 82 | this.list.get(2); 83 | 84 | $invalidImage.trigger('error'); 85 | assert.equals("not-found.png", $invalidImage.attr("src")); 86 | }, 87 | 88 | whenCreatingImageListWithErrorHandler: function (errorCallback) { 89 | this.list = C.lazyImageList.create(this.$ol, errorCallback); 90 | } 91 | })); 92 | }(FINN.carousel, jQuery)); 93 | -------------------------------------------------------------------------------- /src/finn/carousel/animatedProjector.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C, $) { 5 | "use strict"; 6 | 7 | var div = FINN.elementBuilder("div"); 8 | 9 | C.animatedProjector = { 10 | create: function (controller, data, options) { 11 | var instance = FINN.compose(this, { 12 | data: data, 13 | options: options || {}, 14 | animator: options.animator || C.horizontalSlider, 15 | currentId: 0, 16 | queue: [] 17 | }); 18 | controller.on("show", function () { return instance.show.apply(instance, arguments); }); 19 | return instance; 20 | }, 21 | 22 | buildCarousel: function (callback) { 23 | var carousel = this.carousel = div({ className: "carousel" }); 24 | var self = this; 25 | this.data.get(0, function (element) { 26 | self.mainFrame = div({ className: "frame" }); 27 | self.mainFrame.appendChild(element); 28 | self.viewport = div({ className: "viewport" }, self.mainFrame); 29 | self.viewport.style.position = "relative"; 30 | self.viewport.style.overflow = "hidden"; 31 | carousel.appendChild(self.viewport); 32 | if (typeof callback === "function") { callback(carousel); } 33 | }); 34 | return carousel; 35 | }, 36 | 37 | show: function (index) { 38 | if (!this.isAnimating() && index === this.currentId) { return; } 39 | var self = this; 40 | var animComplete = function () { 41 | return self.animationComplete.apply(self, [index].concat([].slice.call(arguments))); 42 | }; 43 | this.currTarget = index; 44 | 45 | if (index === this.currentId) { 46 | return this.animationFrame().revertAnimation(animComplete); 47 | } 48 | 49 | this.data.get(index, function (element) { 50 | self.addElementToFrame(element, index); 51 | self.animationFrame().animate(animComplete); 52 | }); 53 | }, 54 | 55 | isAnimating: function () { 56 | return typeof this.currTarget === "number"; 57 | }, 58 | 59 | animationComplete: function (index, content) { 60 | this.currentId = index; 61 | delete this.currTarget; 62 | this.display(content.innerHTML); 63 | this.finishAnimation(); 64 | }, 65 | 66 | animationFrame: function () { 67 | if (!this._animFrame) { 68 | var currEl = this.mainFrame.firstChild.cloneNode(true); 69 | currEl.style.width = $(this.mainFrame).css("width"); 70 | 71 | this._animFrame = this.animator.create(currEl, this.options); 72 | this.viewport.appendChild(this._animFrame.element); 73 | this.mainFrame.firstChild.innerHTML = ""; 74 | } 75 | return this._animFrame; 76 | }, 77 | 78 | finishAnimation: function () { 79 | if (this._animFrame) { 80 | this._animFrame.detach(); 81 | delete this._animFrame; 82 | } 83 | delete this.animation; 84 | }, 85 | 86 | isAnimatingBackwards: function (targetIndex) { 87 | var lastIndex = this.data.size() - 1; 88 | var isOverflow = (this.currentId === 0 && targetIndex === lastIndex) || 89 | (this.currentId === lastIndex && targetIndex === 0); 90 | 91 | if (isOverflow && lastIndex > 1) { return targetIndex > 0; } 92 | if (targetIndex < this.currentId) { return true; } 93 | if (targetIndex > this.currentId) { return false; } 94 | return targetIndex < this.currTarget; 95 | }, 96 | 97 | addElementToFrame: function (element, index) { 98 | if (this.isAnimatingBackwards(index)) { 99 | this.animationFrame().prepend(element); 100 | } else { 101 | this.animationFrame().append(element); 102 | } 103 | }, 104 | 105 | display: function (content) { 106 | this.mainFrame.firstChild.innerHTML = content; 107 | } 108 | }; 109 | }(FINN.carousel, jQuery)); 110 | -------------------------------------------------------------------------------- /src/finn/carousel/scrollingThumbnails.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | /** 4 | * WAAAAAARNING THIS WIDGET IS NOT COMPLETE, PLEASE DON'T USE IT!! 5 | **/ 6 | (function (C, $) { 7 | "use strict"; 8 | 9 | var centerIndex = 0; 10 | function isWebKitTransform(){ 11 | return '-webkit-transform' in document.body.style; 12 | } 13 | function arrangeStrip(index, thumbs, stripSelector, controller){ 14 | var thumbnails = $(thumbs).find(stripSelector); 15 | var itemsOnEachSide = Math.ceil(thumbnails.length - 6) / 2; 16 | var projectedThumb = thumbnails[index]; 17 | var li = thumbnails.length; 18 | var newViewIndex = 0; 19 | 20 | for (var i=0; i 14 | ``` 15 | 16 | Then add markup for listing the images. 17 | 18 | ```html 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | ``` 30 | 31 | Now let's wire up the JS code required to make this thing work. This should be in a separate file loaded after the carousel lib. 32 | 33 | ```js 34 | (function(carousel){ 35 | var sourceImageList = document.getElementById("imageList"); 36 | var imageList = carousel.elementList.create(sourceImageList); 37 | var controller = carousel.controller.create(imageList); 38 | 39 | var imageProjector = carousel.animatedProjector.create(controller, imageList, { 40 | duration: 500, 41 | animator: carousel.horizontalSlider 42 | }); 43 | var imageCarousel = imageProjector.buildCarousel(); 44 | carousel.setupClickNavigation( 45 | controller, 46 | imageList, 47 | document.getElementById("prevNextNavigation") 48 | ); 49 | carousel.setupKeyboardNavigation(controller); 50 | carousel.setupTouchNavigation(controller, imageCarousel); 51 | sourceImageList.style.display = "none"; 52 | }(FINN.carousel)); 53 | ``` 54 | 55 | That's it! You can use this same setup for things other than images, you can loop through any type of DOM node. Wether it's an image, canvas, svg or whatever. 56 | 57 | The carousel consists of many components which can easily be composed together to give the carousel different behaviours. Below are some samples, the rest is in the source code. 58 | 59 | ## More sample usages 60 | 61 | There are a few samples in the [samples](samples/) folder which shows how you can combine the different components to fit your requirements. 62 | 63 | ### Samples in the wild 64 | Feel free to add your samples to this section or let us know if you use it. 65 | 66 | * [Show cases module](http://www.finn.no/finn/torget/partnerinfo) uses the carousel to provide users with the option to view success stories (towards the bottom) 67 | * [Simple gallery](http://www.finn.no/bedrift/svendsen-s-glass-service-as-1137850/album/7994) just a simple image gallery with keyboard and touch navigation 68 | * [Gallery with thumbnail strip](http://www.finn.no/finn/car/used/viewimage?finnkode=41884971) gallery with a simple thumbnail strip 69 | 70 | # Building the source 71 | Working with the carousel all you need to build it is to have [Grunt](http://gruntjs.com/) installed and then just run the simple command: 72 | 73 | ```sh 74 | grunt 75 | ``` 76 | 77 | This runs tests and puts a new package in your local dist folder, ready to use. 78 | 79 | ## Running tests 80 | 81 | Currently all tests are run using [JsTestDriver](https://code.google.com/p/js-test-driver/). Tests use the [Buster assertion library](http://docs.busterjs.org/en/latest/modules/buster-assertions/). 82 | In order to run the tests you must have Grunt installed and make sure you have the [Grunt JsTestDriver plugin](https://github.com/rickyclegg/grunt-jstestdriver) installed. The configuration is already in the project grunt file. 83 | 84 | To run the tests all you need to do is this: 85 | 86 | ```sh 87 | $ java -jar node_modules/grunt-jstestdriver/lib/jstestdriver.jar --port 5555 88 | $ grunt jstestdriver 89 | ``` 90 | 91 | # Component 92 | 93 | The carousel package contains numerous components which can be packaged together to give you exactly the kind of carousel widget you want. 94 | 95 | # Dependencies 96 | 97 | * [Underscore](http://underscorejs.org/) 98 | * [Bane](https://github.com/busterjs/bane/blob/master/lib/bane.js) 99 | * [jQuery 1.7 and up](http://jquery.com) 100 | 101 | Some components rely on jQuery, we are working to lose those as soon as possible. 102 | 103 | # Contributors 104 | 105 | * [Magnar Sveen](https://github.com/magnars) 106 | * [Christian Johansen](https://github.com/cjohansen) 107 | * [Jostein Holje](https://github.com/jstnhlj) 108 | * [Espen Dalløkken](https://github.com/leftieFriele) 109 | -------------------------------------------------------------------------------- /test/finn/elementBuilderTest.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | (function (elementBuilder) { 3 | "use strict"; 4 | 5 | var div = elementBuilder("div"); 6 | var p = elementBuilder("p"); 7 | 8 | testCase("ElementBuilderTest", sinon.testCase({ 9 | "test should create div element": function () { 10 | assert.tagName(div(), "div"); 11 | }, 12 | 13 | "test should create element with attribute": function () { 14 | var a = elementBuilder("a"); 15 | assert.match(a({ href: "/yo" }).href, /\/yo$/); 16 | }, 17 | 18 | "test should create element with multiple attributes": function () { 19 | var a = elementBuilder("a"); 20 | var link = a({ href: "/yo", className: "link-thingie" }); 21 | 22 | assert.className(link, "link-thingie"); 23 | assert.match(link.href, /\/yo$/); 24 | }, 25 | 26 | "test should create element with children": function () { 27 | var el = div(div()); 28 | 29 | assert.equals(el.childNodes.length, 1); 30 | }, 31 | 32 | "test should create element with children and attributes": function () { 33 | var el = div({ className: "hey", id: "ho" }, div()); 34 | 35 | assert.equals(el.childNodes.length, 1); 36 | assert.className(el, "hey"); 37 | assert.equals(el.id, "ho"); 38 | }, 39 | 40 | "test should create element with text content": function () { 41 | var el = div("Hey man"); 42 | 43 | assert.equals(el.innerHTML, "Hey man"); 44 | }, 45 | 46 | "test should not insert text as html": function () { 47 | var el = div("

x

s

s

"); 48 | 49 | assert.equals(el.childNodes.length, 1); 50 | }, 51 | 52 | "test should create element with text and DOM element content": function () { 53 | var el = div("Hey man", div(), "Awright"); 54 | 55 | assert.match(el.innerHTML, /hey man\s*
<\/div>awright/i); 56 | }, 57 | 58 | "test should set style properties": function () { 59 | var el = div({ style: { position: "relative" } }); 60 | 61 | assert.equals(el.style.position, "relative"); 62 | }, 63 | 64 | "test should create element event handler": function () { 65 | var spy = this.spy(); 66 | var el = div({ events: { click: spy } }); 67 | document.body.appendChild(el); 68 | 69 | $(el).trigger("click"); 70 | 71 | assert.calledOnce(spy); 72 | }, 73 | 74 | "test should create element with customly handled atrribute": function () { 75 | elementBuilder.attrHandlers["for"] = function (el, attr) { 76 | el.htmlFor = attr; 77 | }; 78 | 79 | var el = div({ "for": "somebody" }); 80 | 81 | assert.equals(el.htmlFor, "somebody"); 82 | }, 83 | 84 | "test should create div element directly": function () { 85 | var div = FINN.elementBuilder.build("div", { className: "Yay" }); 86 | assert.tagName(div, "div"); 87 | assert.className(div, "Yay"); 88 | }, 89 | 90 | "test should create group of elements as fragment": function () { 91 | var group = FINN.fragment(div({ id: "d1" }), div({ id: "d2" })); 92 | var myDiv = div(group); 93 | 94 | assert.equals(myDiv.childNodes.length, 2); 95 | assert.equals(myDiv.firstChild.id, "d1"); 96 | }, 97 | 98 | "test should create array of elements as fragment": function () { 99 | var group = FINN.fragment([div({ id: "d1" }), div({ id: "d2" })]); 100 | var myDiv = div(group); 101 | 102 | assert.equals(myDiv.childNodes.length, 2); 103 | assert.equals(myDiv.firstChild.id, "d1"); 104 | } 105 | })); 106 | 107 | testCase("ElementBuilderModuleTest", sinon.testCase({ 108 | "test should create .mod div": function () { 109 | var mod = elementBuilder.mod(); 110 | 111 | assert.tagName(mod, "div"); 112 | assert.className(mod, "mod"); 113 | }, 114 | 115 | "test should add module type": function () { 116 | var mod = elementBuilder.mod("2nd"); 117 | 118 | assert.className(mod, "mod_2nd"); 119 | }, 120 | 121 | "test should add body-div": function () { 122 | var mod = elementBuilder.mod(); 123 | 124 | assert.equals(mod.childNodes.length, 1); 125 | assert.tagName(mod.firstChild, "div"); 126 | assert.className(mod.firstChild, "bd"); 127 | }, 128 | 129 | "test should add more elements": function () { 130 | var mod = elementBuilder.mod("1st", div(), p()); 131 | var bd = mod.firstChild; 132 | 133 | assert.equals(bd.childNodes.length, 2); 134 | assert.tagName(bd.firstChild, "div"); 135 | assert.tagName(bd.lastChild, "p"); 136 | } 137 | })); 138 | 139 | }(FINN.elementBuilder)); 140 | -------------------------------------------------------------------------------- /test/lib/buster/buster-format.js: -------------------------------------------------------------------------------- 1 | var buster = this.buster || {}; 2 | 3 | if (typeof require != "undefined") { 4 | buster = require("buster-core"); 5 | } 6 | 7 | buster.format = buster.format || {}; 8 | buster.format.excludeConstructors = ["Object", /^.$/]; 9 | buster.format.quoteStrings = true; 10 | 11 | buster.format.ascii = (function () { 12 | function keys(object) { 13 | var k = Object.keys && Object.keys(object) || []; 14 | 15 | if (k.length == 0) { 16 | for (var prop in object) { 17 | if (object.hasOwnProperty(prop)) { 18 | k.push(prop); 19 | } 20 | } 21 | } 22 | 23 | return k.sort(); 24 | } 25 | 26 | function isCircular(object, objects) { 27 | if (typeof object != "object") { 28 | return false; 29 | } 30 | 31 | for (var i = 0, l = objects.length; i < l; ++i) { 32 | if (objects[i] === object) { 33 | return true; 34 | } 35 | } 36 | 37 | return false; 38 | } 39 | 40 | function ascii(object, processed, indent) { 41 | if (typeof object == "string") { 42 | var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; 43 | return processed || quote ? '"' + object + '"' : object; 44 | } 45 | 46 | if (typeof object == "function" && !(object instanceof RegExp)) { 47 | return ascii.func(object); 48 | } 49 | 50 | processed = processed || []; 51 | 52 | if (isCircular(object, processed)) { 53 | return "[Circular]"; 54 | } 55 | 56 | if (Object.prototype.toString.call(object) == "[object Array]") { 57 | return ascii.array(object); 58 | } 59 | 60 | if (!object) { 61 | return "" + object; 62 | } 63 | 64 | if (buster.isElement(object)) { 65 | return ascii.element(object); 66 | } 67 | 68 | if (object.toString !== Object.prototype.toString) { 69 | return object.toString(); 70 | } 71 | 72 | return ascii.object.call(this, object, processed, indent); 73 | } 74 | 75 | ascii.func = function (func) { 76 | return "function " + buster.functionName(func) + "() {}"; 77 | }; 78 | 79 | ascii.array = function (array, processed) { 80 | processed = processed || []; 81 | processed.push(array); 82 | var pieces = []; 83 | 84 | for (var i = 0, l = array.length; i < l; ++i) { 85 | pieces.push(ascii(array[i], processed)); 86 | } 87 | 88 | return "[" + pieces.join(", ") + "]"; 89 | }; 90 | 91 | ascii.object = function (object, processed, indent) { 92 | processed = processed || []; 93 | processed.push(object); 94 | indent = indent || 0; 95 | var pieces = [], properties = keys(object), prop, str, obj; 96 | var is = ""; 97 | var length = 3; 98 | 99 | for (var i = 0, l = indent; i < l; ++i) { 100 | is += " "; 101 | } 102 | 103 | for (i = 0, l = properties.length; i < l; ++i) { 104 | prop = properties[i]; 105 | obj = object[prop]; 106 | 107 | if (isCircular(obj, processed)) { 108 | str = "[Circular]"; 109 | } else { 110 | str = ascii.call(this, obj, processed, indent + 2); 111 | } 112 | 113 | str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; 114 | length += str.length; 115 | pieces.push(str); 116 | } 117 | 118 | var cons = ascii.constructorName.call(this, object); 119 | var prefix = cons ? "[" + cons + "] " : "" 120 | 121 | return (length + indent) > 80 ? 122 | prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : 123 | prefix + "{ " + pieces.join(", ") + " }"; 124 | }; 125 | 126 | ascii.element = function (element) { 127 | var tagName = element.tagName.toLowerCase(); 128 | var attrs = element.attributes, attribute, pairs = [], attrName; 129 | 130 | for (var i = 0, l = attrs.length; i < l; ++i) { 131 | attribute = attrs.item(i); 132 | attrName = attribute.nodeName.toLowerCase().replace("html:", ""); 133 | 134 | if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { 135 | continue; 136 | } 137 | 138 | if (!!attribute.nodeValue) { 139 | pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); 140 | } 141 | } 142 | 143 | var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); 144 | var content = element.innerHTML; 145 | 146 | if (content.length > 20) { 147 | content = content.substr(0, 20) + "[...]"; 148 | } 149 | 150 | var res = formatted + pairs.join(" ") + ">" + content + ""; 151 | 152 | return res.replace(/ contentEditable="inherit"/, ""); 153 | }; 154 | 155 | ascii.constructorName = function (object) { 156 | var name = buster.functionName(object && object.constructor); 157 | var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; 158 | 159 | for (var i = 0, l = excludes.length; i < l; ++i) { 160 | if (typeof excludes[i] == "string" && excludes[i] == name) { 161 | return ""; 162 | } else if (excludes[i].test && excludes[i].test(name)) { 163 | return ""; 164 | } 165 | } 166 | 167 | return name; 168 | }; 169 | 170 | return ascii; 171 | }()); 172 | 173 | if (typeof module != "undefined") { 174 | module.exports = buster.format; 175 | } 176 | -------------------------------------------------------------------------------- /src/lib/bane/bane.js: -------------------------------------------------------------------------------- 1 | ((typeof define === "function" && define.amd && function (m) { define("bane", m); }) || 2 | (typeof module === "object" && function (m) { module.exports = m(); }) || 3 | function (m) { this.bane = m(); } 4 | )(function () { 5 | "use strict"; 6 | var slice = Array.prototype.slice; 7 | 8 | function handleError(event, error, errbacks) { 9 | var i, l = errbacks.length; 10 | if (l > 0) { 11 | for (i = 0; i < l; ++i) { errbacks[i](event, error); } 12 | return; 13 | } 14 | setTimeout(function () { 15 | error.message = event + " listener threw error: " + error.message; 16 | throw error; 17 | }, 0); 18 | } 19 | 20 | function assertFunction(fn) { 21 | if (typeof fn !== "function") { 22 | throw new TypeError("Listener is not function"); 23 | } 24 | return fn; 25 | } 26 | 27 | function supervisors(object) { 28 | if (!object.supervisors) { object.supervisors = []; } 29 | return object.supervisors; 30 | } 31 | 32 | function listeners(object, event) { 33 | if (!object.listeners) { object.listeners = {}; } 34 | if (event && !object.listeners[event]) { object.listeners[event] = []; } 35 | return event ? object.listeners[event] : object.listeners; 36 | } 37 | 38 | function errbacks(object) { 39 | if (!object.errbacks) { object.errbacks = []; } 40 | return object.errbacks; 41 | } 42 | 43 | /** 44 | * @signature var emitter = bane.createEmitter([object]); 45 | * 46 | * Create a new event emitter. If an object is passed, it will be modified 47 | * by adding the event emitter methods (see below). 48 | */ 49 | function createEventEmitter(object) { 50 | object = object || {}; 51 | 52 | function notifyListener(event, listener, args) { 53 | try { 54 | listener.listener.apply(listener.thisp || object, args); 55 | } catch (e) { 56 | handleError(event, e, errbacks(object)); 57 | } 58 | } 59 | 60 | object.on = function (event, listener, thisp) { 61 | if (typeof event === "function") { 62 | return supervisors(this).push({ 63 | listener: event, 64 | thisp: listener 65 | }); 66 | } 67 | listeners(this, event).push({ 68 | listener: assertFunction(listener), 69 | thisp: thisp 70 | }); 71 | }; 72 | 73 | object.off = function (event, listener) { 74 | var fns, events, i, l; 75 | if (!event) { 76 | fns = supervisors(this); 77 | fns.splice(0, fns.length); 78 | 79 | events = listeners(this); 80 | for (i in events) { 81 | if (events.hasOwnProperty(i)) { 82 | fns = listeners(this, i); 83 | fns.splice(0, fns.length); 84 | } 85 | } 86 | 87 | fns = errbacks(this); 88 | fns.splice(0, fns.length); 89 | 90 | return; 91 | } 92 | if (typeof event === "function") { 93 | fns = supervisors(this); 94 | listener = event; 95 | } else { 96 | fns = listeners(this, event); 97 | } 98 | if (!listener) { 99 | fns.splice(0, fns.length); 100 | return; 101 | } 102 | for (i = 0, l = fns.length; i < l; ++i) { 103 | if (fns[i].listener === listener) { 104 | fns.splice(i, 1); 105 | return; 106 | } 107 | } 108 | }; 109 | 110 | object.once = function (event, listener, thisp) { 111 | var wrapper = function () { 112 | object.off(event, wrapper); 113 | listener.apply(this, arguments); 114 | }; 115 | 116 | object.on(event, wrapper, thisp); 117 | }; 118 | 119 | object.bind = function (object, events) { 120 | var prop, i, l; 121 | if (!events) { 122 | for (prop in object) { 123 | if (typeof object[prop] === "function") { 124 | this.on(prop, object[prop], object); 125 | } 126 | } 127 | } else { 128 | for (i = 0, l = events.length; i < l; ++i) { 129 | if (typeof object[events[i]] === "function") { 130 | this.on(events[i], object[events[i]], object); 131 | } else { 132 | throw new Error("No such method " + events[i]); 133 | } 134 | } 135 | } 136 | return object; 137 | }; 138 | 139 | object.emit = function (event) { 140 | var toNotify = supervisors(this); 141 | var args = slice.call(arguments), i, l; 142 | 143 | for (i = 0, l = toNotify.length; i < l; ++i) { 144 | notifyListener(event, toNotify[i], args); 145 | } 146 | 147 | toNotify = listeners(this, event).slice(); 148 | args = slice.call(arguments, 1); 149 | for (i = 0, l = toNotify.length; i < l; ++i) { 150 | notifyListener(event, toNotify[i], args); 151 | } 152 | }; 153 | 154 | object.errback = function (listener) { 155 | if (!this.errbacks) { this.errbacks = []; } 156 | this.errbacks.push(assertFunction(listener)); 157 | }; 158 | 159 | return object; 160 | } 161 | 162 | return { createEventEmitter: createEventEmitter }; 163 | }); 164 | -------------------------------------------------------------------------------- /src/finn/carousel/horizontalSlider.js: -------------------------------------------------------------------------------- 1 | /*! carousel-js - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | FINN.carousel = FINN.carousel || {}; 3 | 4 | (function (C, $) { 5 | "use strict"; 6 | 7 | var div = FINN.elementBuilder("div"); 8 | var transition; 9 | 10 | var LEFT = -1, RIGHT = 1; 11 | var TRANSITION_TYPES = { 12 | "WebkitTransition": { endEvent: "webkitTransitionEnd", cssStyle: "-webkit-transition" }, 13 | "MozTransition": { endEvent: "transitionend", cssStyle: "-moz-transition" }, 14 | "OTransition": { endEvent: "oTransitionEnd", cssStyle: "-o-transition" }, 15 | "msTransition": { endEvent: "MSTransitionEnd", cssStyle: "-ms-transition" }, // correct endEvent? 16 | "transition": { endEvent: "transitionend", cssStyle: "transition" } 17 | }; 18 | 19 | function countSteps(skipSteps, direction, newDirection) { 20 | if (direction !== newDirection) { 21 | return 1; 22 | } else { 23 | return skipSteps + 1; 24 | } 25 | } 26 | 27 | function animate(pos, callback) { 28 | $(this.element).css("left", pos.from + "px"); 29 | this.startPos = pos.from; 30 | 31 | if (supportsCssTransitions()) { 32 | return slideWithCss.call(this, pos, callback); 33 | } 34 | 35 | return slideWithJquery.call(this, pos, callback); 36 | } 37 | 38 | function slideWithJquery(pos, callback) { 39 | var opt = $.extend({}, this.animOpt, { 40 | complete: this.animationCallback(callback) 41 | }); 42 | var props = { left: pos.to + "px" }; 43 | 44 | this.animation = $(this.element).animate(props, opt); 45 | return this.animation; 46 | } 47 | 48 | function slideWithCss(pos, callback) { 49 | var duration = (this.animOpt.duration / 1000) + "s"; 50 | 51 | $(this.element).css('left'); // causes necessary reflow 52 | 53 | $(this.element).one(transition.endEvent, this.animationCallback(callback)) 54 | .css(transition.cssStyle, "left " + duration) 55 | .css("left", pos.to + "px"); 56 | 57 | this.animation = $(this.element); 58 | return this.animation; 59 | } 60 | 61 | function resolveSupportedTransition() { 62 | for (var transitionType in TRANSITION_TYPES) { 63 | if (!TRANSITION_TYPES.hasOwnProperty(transitionType)) { 64 | continue; 65 | } 66 | 67 | if (document.body && typeof document.body.style[transitionType] !== 'undefined') { 68 | transition = TRANSITION_TYPES[transitionType]; 69 | break; 70 | } 71 | } 72 | } 73 | 74 | function supportsCssTransitions() { 75 | return (typeof transition !== "undefined"); 76 | } 77 | 78 | C.horizontalSlider = FINN.compose(C.animator, { 79 | create: function (element, animOpt) { 80 | var frame = FINN.compose(this, { 81 | width: $(element).width(), 82 | direction: LEFT, 83 | animOpt: animOpt, 84 | element: div({ className: "frame" }) 85 | }); 86 | $(frame.element).css({ position: "relative", top: 0 }); 87 | frame.append(element); 88 | frame.skipSteps = 0; 89 | resolveSupportedTransition(); 90 | return frame; 91 | }, 92 | 93 | destination: function (forcedTo) { 94 | var offset = this.width * this.skipSteps; 95 | var pos = { from: 0, to: -offset, backwards: false }; 96 | if (this.headedLeft()) { 97 | pos = { from: -offset, to: 0 }; 98 | } 99 | if (this.animation) { 100 | pos.from = parseInt(this.animation.css("left"), 10); 101 | } 102 | if (typeof forcedTo === "number") { pos.to = forcedTo; } 103 | if (pos.from < pos.to) { pos.backwards = true; } 104 | return pos; 105 | }, 106 | 107 | registerElement: function (element) { 108 | element.style.width = this.width + "px"; 109 | element.style.cssFloat = "left"; 110 | var newWidth = $(this.element).width() + this.width; 111 | this.element.style.width = newWidth + "px"; 112 | this.previousContent = this.contentElement; 113 | this.contentElement = element; 114 | }, 115 | 116 | append: function (element) { 117 | this.element.appendChild(element); 118 | this.registerElement(element); 119 | this.skipSteps = countSteps(this.skipSteps, this.direction, RIGHT); 120 | this.direction = RIGHT; 121 | }, 122 | 123 | prepend: function (element) { 124 | this.element.insertBefore(element, this.element.firstChild); 125 | this.registerElement(element); 126 | var leftPos = parseInt($(this.element).css("left") || 0, 10); 127 | $(this.element).css("left", (leftPos - this.width)); 128 | this.skipSteps = countSteps(this.skipSteps, this.direction, LEFT); 129 | this.direction = LEFT; 130 | }, 131 | 132 | animate: function (callback) { 133 | this.stopAnimation(); 134 | return animate.call(this, this.destination(), callback); 135 | }, 136 | 137 | animationCallback: function (callback) { 138 | var self = this; 139 | return function () { 140 | callback(self.contentElement); 141 | delete self.startPos; 142 | }; 143 | }, 144 | 145 | revertAnimation: function (cb) { 146 | this.stopAnimation(); 147 | this.contentElement = this.previousContent; 148 | return animate.call(this, this.destination(this.startPos), cb); 149 | }, 150 | 151 | headedLeft: function () { 152 | return this.direction === LEFT; 153 | } 154 | }); 155 | }(FINN.carousel, jQuery)); -------------------------------------------------------------------------------- /test/lib/sinon/sinon-buster.js: -------------------------------------------------------------------------------- 1 | /*jslint onevar: false, eqeqeq: false*/ 2 | /*global require*/ 3 | if (typeof require != "undefined") { 4 | var sinon = require("sinon"); 5 | 6 | var buster = { 7 | assertions: require("buster-assertions"), 8 | format: require("buster-format"), 9 | testRunner: require("buster-test").testRunner, 10 | stackFilter: require("buster-test").stackFilter 11 | }; 12 | } 13 | 14 | if (buster.stackFilter && buster.stackFilter.filters) { 15 | buster.stackFilter.filters.push("lib/sinon"); 16 | } 17 | 18 | (function (ba) { 19 | if (buster.testRunner) { 20 | buster.testRunner.onCreate(function (runner) { 21 | runner.on("test:setUp", function (test) { 22 | var config = sinon.getConfig(sinon.config); 23 | config.useFakeServer = false; 24 | var sandbox = sinon.sandbox.create(); 25 | sandbox.inject(test.testCase); 26 | 27 | test.testCase.useFakeTimers = function () { 28 | return sandbox.useFakeTimers.apply(sandbox, arguments); 29 | }; 30 | 31 | test.testCase.sandbox = sandbox; 32 | var testFunc = test.func; 33 | }); 34 | 35 | runner.on("test:tearDown", function (test) { 36 | try { 37 | test.testCase.sandbox.verifyAndRestore(); 38 | } catch (e) { 39 | runner.error(e, test); 40 | } 41 | }); 42 | }); 43 | } 44 | 45 | if (buster.format) { 46 | var formatter = buster.create(buster.format); 47 | formatter.quoteStrings = false; 48 | sinon.format = function format() { 49 | return formatter.ascii.apply(formatter, arguments); 50 | }; 51 | } 52 | 53 | if (!ba) { return; } 54 | 55 | // Sinon assertions for buster 56 | function verifyFakes() { 57 | var method, isNot; 58 | 59 | for (var i = 0, l = arguments.length; i < l; ++i) { 60 | method = arguments[i]; 61 | isNot = (method || "fake") + " is not "; 62 | 63 | if (!method) this.fail(isNot + "a spy"); 64 | if (typeof method != "function") this.fail(isNot + "a function"); 65 | if (typeof method.getCall != "function") this.fail(isNot + "stubbed"); 66 | } 67 | 68 | return true; 69 | } 70 | 71 | var sf = sinon.spy.formatters; 72 | var spyValues = function (spy) { return [spy, sf.c(spy), sf.C(spy)]; }; 73 | 74 | ba.add("called", { 75 | assert: function (spy) { 76 | verifyFakes.call(this, spy); 77 | return spy.called; 78 | }, 79 | assertMessage: "Expected ${0} to be called at least once but was never called", 80 | refuteMessage: "Expected ${0} to not be called but was called ${1}${2}", 81 | expectation: "toBeCalled", 82 | values: spyValues 83 | }); 84 | 85 | function slice(arr, from, to) { 86 | return [].slice.call(arr, from, to); 87 | } 88 | 89 | ba.add("callOrder", { 90 | assert: function (spy) { 91 | verifyFakes.apply(this, arguments); 92 | if (sinon.calledInOrder(arguments)) return true; 93 | 94 | this.expected = [].join.call(arguments, ", "); 95 | this.actual = sinon.orderByFirstCall(slice(arguments)).join(", "); 96 | }, 97 | 98 | assertMessage: "Expected ${expected} to be called in order but were called as ${actual}", 99 | refuteMessage: "Expected ${expected} not to be called in order" 100 | }); 101 | 102 | function addCallCountAssertion(count) { 103 | var c = count.toLowerCase(); 104 | 105 | ba.add("called" + count, { 106 | assert: function (spy) { 107 | verifyFakes.call(this, spy); 108 | return spy["called" + count]; 109 | }, 110 | assertMessage: "Expected ${0} to be called " + c + " but was called ${1}${2}", 111 | refuteMessage: "Expected ${0} to not be called exactly " + c + "${2}", 112 | expectation: "toBeCalled" + count, 113 | values: spyValues 114 | }); 115 | } 116 | 117 | addCallCountAssertion("Once"); 118 | addCallCountAssertion("Twice"); 119 | addCallCountAssertion("Thrice"); 120 | 121 | function valuesWithThis(spy, thisObj) { 122 | return [spy, thisObj, spy.printf && spy.printf("%t") || ""]; 123 | } 124 | 125 | ba.add("calledOn", { 126 | assert: function (spy, thisObj) { 127 | verifyFakes.call(this, spy); 128 | return spy.calledOn(thisObj); 129 | }, 130 | assertMessage: "Expected ${0} to be called with ${1} as this but was called on ${2}", 131 | refuteMessage: "Expected ${0} not to be called with ${1} as this", 132 | expectation: "toBeCalledOn", 133 | values: valuesWithThis 134 | }); 135 | 136 | ba.add("alwaysCalledOn", { 137 | assert: function (spy, thisObj) { 138 | verifyFakes.call(this, spy); 139 | return spy.alwaysCalledOn(thisObj); 140 | }, 141 | assertMessage: "Expected ${0} to always be called with ${1} as this but was called on ${2}", 142 | refuteMessage: "Expected ${0} not to always be called with ${1} as this", 143 | expectation: "toAlwaysBeCalledOn", 144 | values: valuesWithThis 145 | }); 146 | 147 | function formattedArgs(args, i) { 148 | for (var l = args.length, result = []; i < l; ++i) { 149 | result.push(sinon.format(args[i])); 150 | } 151 | 152 | return result.join(", "); 153 | } 154 | 155 | function spyAndCalls(spy) { 156 | return [spy, formattedArgs(arguments, 1), spy.printf && spy.printf("%C")]; 157 | } 158 | 159 | ba.add("calledWith", { 160 | assert: function (spy) { 161 | verifyFakes.call(this, spy); 162 | return spy.calledWith.apply(spy, slice(arguments, 1)); 163 | }, 164 | assertMessage: "Expected ${0} to be called with arguments ${1}${2}", 165 | refuteMessage: "Expected ${0} not to be called with arguments ${1}${2}", 166 | expectation: "toBeCalledWith", 167 | values: spyAndCalls 168 | }); 169 | 170 | ba.add("alwaysCalledWith", { 171 | assert: function (spy) { 172 | verifyFakes.call(this, spy); 173 | return spy.alwaysCalledWith.apply(spy, slice(arguments, 1)); 174 | }, 175 | assertMessage: "Expected ${0} to always be called with arguments ${1}${2}", 176 | refuteMessage: "Expected ${0} not to always be called with arguments${1}${2}", 177 | expectation: "toAlwaysBeCalledWith", 178 | values: spyAndCalls 179 | }); 180 | 181 | ba.add("calledOnceWith", { 182 | assert: function (spy) { 183 | verifyFakes.call(this, spy); 184 | return spy.calledOnce && spy.calledWith.apply(spy, slice(arguments, 1)); 185 | }, 186 | assertMessage: "Expected ${0} to be called once with arguments ${1}${2}", 187 | refuteMessage: "Expected ${0} not to be called once with arguments ${1}${2}", 188 | expectation: "toBeCalledWith", 189 | values: spyAndCalls 190 | }); 191 | 192 | ba.add("calledWithExactly", { 193 | assert: function (spy) { 194 | verifyFakes.call(this, spy); 195 | return spy.calledWithExactly.apply(spy, slice(arguments, 1)); 196 | }, 197 | assertMessage: "Expected ${0} to be called with exact arguments ${1}${2}", 198 | refuteMessage: "Expected ${0} not to be called with exact arguments${1}${2}", 199 | expectation: "toBeCalledWithExactly", 200 | values: spyAndCalls 201 | }); 202 | 203 | ba.add("alwaysCalledWithExactly", { 204 | assert: function (spy) { 205 | verifyFakes.call(this, spy); 206 | return spy.alwaysCalledWithExactly.apply(spy, slice(arguments, 1)); 207 | }, 208 | assertMessage: "Expected ${0} to always be called with exact arguments ${1}${2}", 209 | refuteMessage: "Expected ${0} not to always be called with exact arguments${1}${2}", 210 | expectation: "toAlwaysBeCalledWithExactly", 211 | values: spyAndCalls 212 | }); 213 | 214 | function spyAndException(spy, exception) { 215 | return [spy, spy.printf && spy.printf("%C")]; 216 | } 217 | 218 | ba.add("threw", { 219 | assert: function (spy) { 220 | verifyFakes.call(this, spy); 221 | return spy.threw(arguments[1]); 222 | }, 223 | assertMessage: "Expected ${0} to throw an exception${1}", 224 | refuteMessage: "Expected ${0} not to throw an exception${1}", 225 | expectation: "toHaveThrown", 226 | values: spyAndException 227 | }); 228 | 229 | ba.add("alwaysThrew", { 230 | assert: function (spy) { 231 | verifyFakes.call(this, spy); 232 | return spy.alwaysThrew(arguments[1]); 233 | }, 234 | assertMessage: "Expected ${0} to always throw an exception${1}", 235 | refuteMessage: "Expected ${0} not to always throw an exception${1}", 236 | expectation: "toAlwaysHaveThrown", 237 | values: spyAndException 238 | }); 239 | }(buster.assertions)); 240 | -------------------------------------------------------------------------------- /dist/carousel-1.0.0.min.js: -------------------------------------------------------------------------------- 1 | /*! carousel - v1.0.0 - 2013-06-14. Copyright (c) 2013 FINN.no AS - http://finn.no/; Licensed MIT */ 2 | var FINN=FINN||{};!function(a){"use strict";"function"!=typeof Object.create&&(Object.create=function(a){function b(){}return b.prototype=a,new b}),a.create=function(a){return Object.create(a)},a.compose=function(){if(0===arguments.length||!arguments[0])throw new TypeError("compose expects at least one object argument");var a,b,c,d=FINN.create(arguments[0]);for(a=1,b=arguments.length;b>a;++a){if(!arguments[a])throw new TypeError("Tried to compose null or undefined");for(c in arguments[a])d[c]=arguments[a][c]}return d}}(FINN),function(a,b){"use strict";a.fragment=function(){for(var a=document.createDocumentFragment(),c=b.isArray(arguments[0])?arguments[0]:arguments,d=0,e=c.length;e>d;++d)c[d]&&a.appendChild(c[d]);return a};var c=11,d=a.elementBuilder=function(a){return function(b){var e=1,f=b;return(!b||b.tagName||b.nodeType===c||"string"==typeof b)&&(e=0,f={}),d.addContent(d.attr(document.createElement(a),f),[].slice.call(arguments,e))}};a.elementBuilder.build=function(b){return a.elementBuilder(b).apply(null,[].slice.call(arguments,1))},d.attr=function(a,b){var c,e;for(c in b)b.hasOwnProperty(c)&&(e=d.attrHandlers[c],e?e(a,b[c]):a[c]=b[c]);return a},d.addContent=function(a,b){for(var c=0,d=b.length;d>c;++c)"string"==typeof b[c]?a.appendChild(document.createTextNode(b[c])):a.appendChild(b[c]);return a},d.attrHandlers={events:function(a,c){var d=b(a);for(var e in c)c.hasOwnProperty(e)&&d.bind(e,c[e])},style:function(a,c){b(a).css(c)}};var e=d("div");d.mod=function(a){return e({className:"mod mod_"+a},d.addContent(e({className:"bd"}),[].slice.call(arguments,1)))}}(FINN,jQuery),FINN.carousel=FINN.carousel||{},function(a,b){"use strict";var c=FINN.elementBuilder("div");a.animatedProjector={create:function(b,c,d){var e=FINN.compose(this,{data:c,options:d||{},animator:d.animator||a.horizontalSlider,currentId:0,queue:[]});return b.on("show",function(){return e.show.apply(e,arguments)}),e},buildCarousel:function(a){var b=this.carousel=c({className:"carousel"}),d=this;return this.data.get(0,function(e){d.mainFrame=c({className:"frame"}),d.mainFrame.appendChild(e),d.viewport=c({className:"viewport"},d.mainFrame),d.viewport.style.position="relative",d.viewport.style.overflow="hidden",b.appendChild(d.viewport),"function"==typeof a&&a(b)}),b},show:function(a){if(this.isAnimating()||a!==this.currentId){var b=this,c=function(){return b.animationComplete.apply(b,[a].concat([].slice.call(arguments)))};return this.currTarget=a,a===this.currentId?this.animationFrame().revertAnimation(c):(this.data.get(a,function(d){b.addElementToFrame(d,a),b.animationFrame().animate(c)}),void 0)}},isAnimating:function(){return"number"==typeof this.currTarget},animationComplete:function(a,b){this.currentId=a,delete this.currTarget,this.display(b.innerHTML),this.finishAnimation()},animationFrame:function(){if(!this._animFrame){var a=this.mainFrame.firstChild.cloneNode(!0);a.style.width=b(this.mainFrame).css("width"),this._animFrame=this.animator.create(a,this.options),this.viewport.appendChild(this._animFrame.element),this.mainFrame.firstChild.innerHTML=""}return this._animFrame},finishAnimation:function(){this._animFrame&&(this._animFrame.detach(),delete this._animFrame),delete this.animation},isAnimatingBackwards:function(a){var b=this.data.size()-1,c=0===this.currentId&&a===b||this.currentId===b&&0===a;return c&&b>1?a>0:athis.currentId?!1:a=c.size()-1)};b.on("show",g),g(b.currentId)}}(jQuery,FINN.carousel),FINN.carousel=FINN.carousel||{},function(a){"use strict";function b(a){var b=$(a.target).data("gallery-url");b&&(document.location=b)}a.setupClickToGallery=function(a){$(a).on("click",b)}}(FINN.carousel),function(a){"use strict";a.setupClickableElementNavigation=function(a,b,c,d,e){var f=d||"selectedElement",g=function(a){return $(a).index()};e&&(g=e),$(b).delegate(c,"click",function(){a.show(g(this))}),$(b).find(c).css("cursor","pointer"),a.on("show",function(a){$(b).children().removeClass(f).eq(a).addClass(f)})}}(FINN.carousel),FINN.carousel=FINN.carousel||{},function(a){"use strict";a.controller=bane.createEventEmitter({create:function(a){return FINN.compose(this,{seq:a,currentId:0})},start:function(a){this.currentId=-1,this.show(+a||0)},show:function(a){0>a||!this.seq.contains(a)||this.currentId===a||(this.currentId=a,this.emit("show",a))},peek:function(){return this.currentId},setCurrentId:function(a){this.currentId=a},next:function(){this.show(this.currentId+1)},prev:function(){var a="number"==typeof this.currentId?this.currentId-1:0;this.show(a)}})}(FINN.carousel),function(a){"use strict";a.setupDottedIndexDisplayer=function(a){function b(){for(var a="",b=0;b'}c.innerHTML=a}if(!a)throw new TypeError("Params must be given");var c=a.root,d=a.list,e=a.controller;b(),e.on("show",b)}}(FINN.carousel),FINN.carousel=FINN.carousel||{},function(a,b){"use strict";function c(a,c){return c>=0?b(a).children().get(c):!1}a.elementList={isBounded:!0,create:function(a){return FINN.compose(this,{element:a})},size:function(){return b(this.element).children().length},contains:function(a){return!!c(this.element,a)},get:function(a,d){var e=c(this.element,a);d(b(e).clone().get(0))}}}(FINN.carousel,jQuery),FINN.carousel=FINN.carousel||{},function(a){"use strict";a.emptyList={isBounded:!1,create:function(a){a=a||{};var b=FINN.compose(this,a);return a.get&&(b.get=function(b,c){var d=a.get.call(this,b,c);"undefined"!=typeof d&&c(d)}),b},size:function(){return 0},contains:function(){return!1},get:function(){}}}(FINN.carousel,_),FINN.carousel=FINN.carousel||{},function(a,b){"use strict";var c=FINN.elementBuilder("div");a.fader=FINN.compose(a.animator,{create:function(a,d){return FINN.compose(this,{animOpt:b.extend({duration:150},d),element:c({className:"frame",style:{position:"relative",display:"none"}},a)})},append:function(a){b(a).css({position:"absolute",left:0,top:0,display:"none"});for(var c=this.element.firstChild.nextSibling;c;)c.parentNode.removeChild(c),c=c.nextSibling;this.element.appendChild(a)},animate:function(a){this.stopAnimation(),b(this.element).css("display","block");var c=this.animOpt.duration;this.animation=b(this.element.lastChild).fadeIn(c,function(){a(this.element.lastChild)}.bind(this))},revertAnimation:function(a){this.stopAnimation();var c=this.animOpt.duration;this.animation=b(this.element.lastChild).fadeOut(c,function(){a(this.element.firstChild)}.bind(this))}})}(FINN.carousel,jQuery),FINN.carousel=FINN.carousel||{},function(a,b){"use strict";function c(){var a=document.documentElement;a.requestFullscreen?a.requestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.webkitRequestFullScreen&&a.webkitRequestFullScreen()}a.hasFullscreenSupport=function(){var a=document.documentElement;return a.requestFullscreen?!0:a.mozRequestFullScreen?!0:a.webkitRequestFullScreen?!0:!1},a.setupFullscreenSupport=function(a){var d=document.getElementById(a);d&&d.addEventListener("click",function(){b("#"+a).hide(),c()},!1),document.addEventListener("fullscreenchange",function(){document.fullscreen||b("#"+a).show()},!1),document.addEventListener("mozfullscreenchange",function(){document.mozFullScreen||b("#"+a).show()},!1),document.addEventListener("webkitfullscreenchange",function(){document.webkitIsFullScreen||b("#"+a).show()},!1)},a.setupKeyboardShortcut=function(){b(document).bind("keyup",function(a){a&&(console.log(a.which),70===a.which&&c())})}}(FINN.carousel,jQuery),FINN.carousel=FINN.carousel||{},function(a,b){"use strict";function c(a,b,c){return b!==c?1:a+1}function d(a,c){return b(this.element).css("left",a.from+"px"),this.startPos=a.from,h()?f.call(this,a,c):e.call(this,a,c)}function e(a,c){var d=b.extend({},this.animOpt,{complete:this.animationCallback(c)}),e={left:a.to+"px"};return this.animation=b(this.element).animate(e,d),this.animation}function f(a,c){var d=this.animOpt.duration/1e3+"s";return b(this.element).css("left"),b(this.element).one(i.endEvent,this.animationCallback(c)).css(i.cssStyle,"left "+d).css("left",a.to+"px"),this.animation=b(this.element),this.animation}function g(){for(var a in m)if(m.hasOwnProperty(a)&&document.body&&"undefined"!=typeof document.body.style[a]){i=m[a];break}}function h(){return"undefined"!=typeof i}var i,j=FINN.elementBuilder("div"),k=-1,l=1,m={WebkitTransition:{endEvent:"webkitTransitionEnd",cssStyle:"-webkit-transition"},MozTransition:{endEvent:"transitionend",cssStyle:"-moz-transition"},OTransition:{endEvent:"oTransitionEnd",cssStyle:"-o-transition"},msTransition:{endEvent:"MSTransitionEnd",cssStyle:"-ms-transition"},transition:{endEvent:"transitionend",cssStyle:"transition"}};a.horizontalSlider=FINN.compose(a.animator,{create:function(a,c){var d=FINN.compose(this,{width:b(a).width(),direction:k,animOpt:c,element:j({className:"frame"})});return b(d.element).css({position:"relative",top:0}),d.append(a),d.skipSteps=0,g(),d},destination:function(a){var b=this.width*this.skipSteps,c={from:0,to:-b,backwards:!1};return this.headedLeft()&&(c={from:-b,to:0}),this.animation&&(c.from=parseInt(this.animation.css("left"),10)),"number"==typeof a&&(c.to=a),c.froma?a=this.seq.size()-1:0!==a&&this.seq.contains(a)&&this.currentId!==a||(a=0),this.currentId=a,this.emit("show",a)},prev:function(){var a="number"==typeof this.currentId?this.currentId-1:-1;this.show(a)}})}(FINN.carousel),function(a){"use strict";a.overlayProjector={create:function(a,b,c){return FINN.compose(this,{controller:a,data:b,view:c})},buildCarousel:function(){var a=this;return this.controller.on("show",function(){return a.show.apply(a,arguments)}),this.show(0),this.view},show:function(a){var b=this;this.data.get(a,function(a){b.view.innerHTML="",b.view.appendChild(a)})}}}(FINN.carousel),FINN.carousel=FINN.carousel||{},function(a,b){"use strict";function c(){return"-webkit-transform"in document.body.style}function d(a,c,d){for(var e=b(c).find(d),f=Math.ceil(e.length-6)/2,g=e[a],i=e.length,j=0,k=0;i>k;k++)k<=Math.ceil(f+1)||(b(g).before(b(e[k]).clone()),b(e[k]).remove(),j=++j);return console.log("newViewIndex",j),h=j+1,j}function e(a,c,d,e){var f=b(d).find(e),i=b(f[0]),j=f.length-1,k=f[j];b(d).find(".thumbwrap").removeClass("selectedThumb"),g(a,c)||0===c&&a===j?b(d).append(i):b(k).insertBefore(i),b(b(d).find(".thumbwrap")[h]).addClass("selectedThumb")}function f(a,d,e,f,g,h){f+=1;var i=b(d).find(e),j=b(i[f]),k=j.position(),l=b(d.parentNode.parentNode).parent().width(),m=j.outerWidth(!0),n=70,o=l/2-(m-j.width()),p=0;null!=k&&(p=k.left+n);var q={left:-1*p+o};b(d).find(".thumbwrap").removeClass("selected"),g===i.length-1&&0===f||0===g&&f===i.length-1?c()&&b("#thumbnails").css({"-webkit-transition":"all 0.0s ease-in-out","-webkit-transform":"translate3d("+q.left+"px,0,0)"}):c()?b("#thumbnails").css({"-webkit-transition":"all 0.4s","-webkit-transform":"translate3d("+q.left+"px,0,0)"}):b("#thumbnails").animate(q,376,"swing",function(){j.addClass("selected"),console.log("Added selected")}),h&&h.apply(null,this)}function g(a,b){return b>a}var h=0;a.setupScrollingThumbnailStrip=function(a,b,c){var g=!1,h=0,i=d(0,b,c,a);f(a,b,c,i,h),a.on("show",function(a){console.log("pushAndPop",h,a),e(h,a,b,c),h=a,g=!0})}}(FINN.carousel,jQuery),FINN.carousel=FINN.carousel||{},function(a){"use strict";a.setupSlideshow=function(a,b){var c;a.on("show",function(){clearTimeout(c),c=setTimeout(a.next.bind(a),b)})}}(FINN.carousel),function(a,b){"use strict";a.setupSplitProjectorNavigation=function(a,c){b(c).on("click",function(c){var d=b(this).offset().left,e=c.pageX,f=e-d,g=b(this).width()/2;g>=f?a.prev():a.next()})}}(FINN.carousel,jQuery),function(a,b){"use strict";a.setupThumbnailNavigation=function(c,d,e){a.setupClickableElementNavigation(c,d,"img","selectedThumb",e),a.setupClickNavigation(c,d,b(".thumbnails"))}}(FINN.carousel,jQuery),function(a){"use strict";function b(a){return{x:a.targetTouches[0].pageX,y:a.targetTouches[0].pageY}}a.setupTouchNavigation=function(a,c){var d;c.addEventListener("touchstart",function(a){a.touches.length>1||(d=b(a))},!1),c.addEventListener("touchmove",function(c){if(!d||c.touches.length>1)return d=null,void 0;var e=b(c),f=d.x-e.x,g=d.y-e.y;Math.abs(g)>Math.abs(f)||(c.preventDefault(),f>=30&&a.next(),-30>=f&&a.prev())},!1)}}(FINN.carousel); -------------------------------------------------------------------------------- /test/lib/jquery-ui-1.8.13.custom.min.js: -------------------------------------------------------------------------------- 1 | // Core, Widget, Position, Autocomplete 2 | /*! 3 | * jQuery UI 1.8.13 4 | * 5 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | * 9 | * http://docs.jquery.com/UI 10 | */ 11 | (function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.13", 12 | keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); 13 | b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, 14 | "overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", 15 | function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, 16 | outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); 17 | return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= 18 | 0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= 48 | a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), 49 | g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); 50 | ;/* 51 | * jQuery UI Autocomplete 1.8.13 52 | * 53 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 54 | * Dual licensed under the MIT or GPL Version 2 licenses. 55 | * http://jquery.org/license 56 | * 57 | * http://docs.jquery.com/UI/Autocomplete 58 | * 59 | * Depends: 60 | * jquery.ui.core.js 61 | * jquery.ui.widget.js 62 | * jquery.ui.position.js 63 | */ 64 | (function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){g= 65 | false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!= 66 | a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)}; 67 | this.menu=d("
    ").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&& 68 | a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"); 69 | d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&& 70 | b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source= 71 | this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(d("").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, 75 | "\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery); 76 | (function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", 77 | -1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.scrollTop(),c=this.element.height();if(b<0)this.element.scrollTop(g+b);else b>=c&&this.element.scrollTop(g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id"); 78 | this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b, 79 | this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active|| 80 | this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active|| 81 | this.first()?":last":":first"))},hasScroll:function(){return this.element.height() 1 ? "s" : "")); 28 | return false; 29 | } 30 | 31 | return true; 32 | } 33 | 34 | function defineAssertion(type, name, func, fl, messageValues) { 35 | ba[type][name] = function () { 36 | var fullName = type + "." + name; 37 | countAssertion(); 38 | if (!assertEnoughArguments(fullName, arguments, fl || func.length)) return; 39 | 40 | var failed = false; 41 | 42 | var ctx = { 43 | fail: function () { 44 | failed = true; 45 | var failArgs = [type, name].concat(slice.call(arguments)); 46 | fail.apply(this, failArgs); 47 | return true; 48 | } 49 | }; 50 | 51 | var args = slice.call(arguments, 0); 52 | 53 | if (typeof messageValues == "function") { 54 | args = messageValues.apply(this, args); 55 | } 56 | 57 | if (!func.apply(ctx, arguments)) { 58 | return fail.apply(ctx, [type, name, "message"].concat(args)); 59 | } 60 | 61 | if (!failed) { 62 | ba.emit.apply(ba, ["pass", fullName].concat(args)); 63 | } 64 | }; 65 | } 66 | 67 | ba.add = function (name, options) { 68 | var refuteArgs; 69 | 70 | if (options.refute) { 71 | refuteArgs = options.refute.length; 72 | } else { 73 | refuteArgs = options.assert.length; 74 | options.refute = function () { 75 | return !options.assert.apply(this, arguments); 76 | }; 77 | } 78 | 79 | var values = options && options.values; // TODO: Remove 80 | defineAssertion("assert", name, options.assert, options.assert.length, values); 81 | defineAssertion("refute", name, options.refute, refuteArgs, values); 82 | 83 | assert[name].message = options.assertMessage; 84 | refute[name].message = options.refuteMessage; 85 | 86 | if (options.expectation) { 87 | if (ba.expect && ba.expect.wrapAssertion) { 88 | ba.expect.wrapAssertion(name, options.expectation); 89 | } else { 90 | assert[name].expectationName = options.expectation; 91 | refute[name].expectationName = options.expectation; 92 | } 93 | } 94 | }; 95 | 96 | function interpolate(string, property, value) { 97 | return string.replace(new RegExp("\\$\\{" + property + "\\}", "g"), value); 98 | } 99 | 100 | function interpolatePosArg(message, values) { 101 | var value; 102 | values = values || []; 103 | 104 | for (var i = 0, l = values.length; i < l; i++) { 105 | message = interpolate(message, i, ba.format(values[i])); 106 | } 107 | 108 | return message; 109 | } 110 | 111 | function interpolateProperties(msg, properties) { 112 | for (var prop in properties) { 113 | msg = interpolate(msg, prop, ba.format(properties[prop])); 114 | } 115 | 116 | return msg || ""; 117 | } 118 | 119 | function fail(type, assertion, msg) { 120 | delete this.fail; 121 | var message = interpolateProperties( 122 | interpolatePosArg(ba[type][assertion][msg] || msg, 123 | [].slice.call(arguments, 3)), this); 124 | ba.fail("[" + type + "." + assertion + "] " + message); 125 | } 126 | 127 | function isDate(value) { 128 | // Duck typed dates, allows objects to take on the role of dates 129 | // without actually being dates 130 | return typeof value.getTime == "function" && 131 | value.getTime() == value.valueOf(); 132 | } 133 | 134 | ba.isDate = isDate; 135 | 136 | function areEqual(expected, actual) { 137 | if (expected === actual) { 138 | return true; 139 | } 140 | 141 | // Elements are only equal if expected === actual 142 | if (buster.isElement(expected) || buster.isElement(actual)) { 143 | return false; 144 | } 145 | 146 | // null and undefined only pass for null === null and 147 | // undefined === undefined 148 | /*jsl: ignore*/ 149 | if (expected == null || actual == null) { 150 | return actual === expected; 151 | } 152 | /*jsl: end*/ 153 | 154 | if (isDate(expected) || isDate(actual)) { 155 | return isDate(expected) && isDate(actual) && 156 | expected.getTime() == actual.getTime(); 157 | } 158 | 159 | var useCoercingEquality = typeof expected != "object" || typeof actual != "object"; 160 | 161 | if (expected instanceof RegExp && actual instanceof RegExp) { 162 | if (expected.toString() != actual.toString()) { 163 | return false; 164 | } 165 | 166 | useCoercingEquality = false; 167 | } 168 | 169 | // Arrays can only be equal to arrays 170 | var expectedStr = toString.call(expected); 171 | var actualStr = toString.call(actual); 172 | 173 | // Coerce and compare when primitives are involved 174 | if (useCoercingEquality) { 175 | return expectedStr != "[object Array]" && actualStr != "[object Array]" && 176 | expected == actual; 177 | } 178 | 179 | var expectedKeys = ba.keys(expected); 180 | var actualKeys = ba.keys(actual); 181 | 182 | if (isArguments(expected) || isArguments(actual)) { 183 | if (expected.length != actual.length) { 184 | return false; 185 | } 186 | } else { 187 | if (typeof expected != typeof actual || expectedStr != actualStr || 188 | expectedKeys.length != actualKeys.length) { 189 | return false; 190 | } 191 | } 192 | 193 | var key; 194 | 195 | for (var i = 0, l = expectedKeys.length; i < l; i++) { 196 | key = expectedKeys[i]; 197 | 198 | if (!Object.prototype.hasOwnProperty.call(actual, key) || 199 | !areEqual(expected[key], actual[key])) { 200 | return false; 201 | } 202 | } 203 | 204 | return true; 205 | } 206 | 207 | ba.deepEqual = areEqual; 208 | 209 | assert = ba.assert = function assert(actual, message) { 210 | countAssertion(); 211 | if (!assertEnoughArguments("assert", arguments, 1)) return; 212 | 213 | if (!actual) { 214 | var val = ba.format(actual) 215 | ba.fail(message || "[assert] Expected " + val + " to be truthy"); 216 | } else { 217 | ba.emit("pass", "assert", message || "", actual); 218 | } 219 | }; 220 | 221 | assert.toString = function () { 222 | return "buster.assert"; 223 | }; 224 | 225 | refute = ba.refute = function (actual, message) { 226 | countAssertion(); 227 | if (!assertEnoughArguments("refute", arguments, 1)) return; 228 | 229 | if (actual) { 230 | var val = ba.format(actual) 231 | ba.fail(message || "[refute] Expected " + val + " to be falsy"); 232 | } else { 233 | ba.emit("pass", "refute", message || "", actual); 234 | } 235 | }; 236 | 237 | assert.message = "[assert] Expected ${0} to be thruthy"; 238 | ba.count = 0; 239 | 240 | ba.fail = function (message) { 241 | var exception = new Error(message); 242 | exception.name = "AssertionError"; 243 | 244 | try { 245 | throw exception; 246 | } catch (e) { 247 | ba.emit("failure", e); 248 | } 249 | 250 | if (typeof ba.throwOnFailure != "boolean" || ba.throwOnFailure) { 251 | throw exception; 252 | } 253 | }; 254 | 255 | ba.format = function (object) { 256 | return "" + object; 257 | }; 258 | 259 | function msg(message) { 260 | if (!message) { return ""; } 261 | return message + (/[.:!?]$/.test(message) ? " " : ": "); 262 | } 263 | 264 | function actualAndExpectedMessageValues(actual, expected, message) { 265 | return [actual, expected, msg(message)] 266 | } 267 | 268 | function actualMessageValues(actual) { 269 | return [actual, msg(arguments[1])]; 270 | } 271 | 272 | function actualAndTypeOfMessageValues(actual) { 273 | return [actual, typeof actual, msg(arguments[1])]; 274 | } 275 | 276 | ba.add("same", { 277 | assert: function (actual, expected) { 278 | return actual === expected; 279 | }, 280 | refute: function (actual, expected) { 281 | return actual !== expected; 282 | }, 283 | assertMessage: "${2}${0} expected to be the same object as ${1}", 284 | refuteMessage: "${2}${0} expected not to be the same object as ${1}", 285 | expectation: "toBeSameAs", 286 | values: actualAndExpectedMessageValues 287 | }); 288 | 289 | function multiLineStringDiff(actual, expected, message) { 290 | if (actual == expected) return true; 291 | 292 | var message = interpolatePosArg(assert.equals.multiLineStringHeading, [message]), 293 | actualLines = actual.split("\n"), 294 | expectedLines = expected.split("\n"), 295 | lineCount = Math.max(expectedLines.length, actualLines.length), 296 | lines = []; 297 | 298 | for (var i = 0; i < lineCount; ++i) { 299 | if (expectedLines[i] != actualLines[i]) { 300 | lines.push("line " + (i + 1) + ": " + (expectedLines[i] || "") + 301 | "\nwas: " + (actualLines[i] || "")); 302 | } 303 | } 304 | 305 | ba.fail("[assert.equals] " + message + lines.join("\n\n")); 306 | return false; 307 | } 308 | 309 | ba.add("equals", { 310 | assert: function (actual, expected) { 311 | if (typeof actual == "string" && typeof expected == "string" && 312 | (actual.indexOf("\n") >= 0 || expected.indexOf("\n") >= 0)) { 313 | var message = msg(arguments[2]); 314 | return multiLineStringDiff.call(this, actual, expected, message); 315 | } 316 | 317 | return areEqual(actual, expected); 318 | }, 319 | 320 | refute: function (actual, expected) { 321 | return !areEqual(actual, expected); 322 | }, 323 | 324 | assertMessage: "${2}${0} expected to be equal to ${1}", 325 | refuteMessage: "${2}${0} expected not to be equal to ${1}", 326 | expectation: "toEqual", 327 | values: actualAndExpectedMessageValues 328 | }); 329 | 330 | assert.equals.multiLineStringHeading = "${0}Expected multi-line strings to be equal:\n"; 331 | 332 | ba.add("typeOf", { 333 | assert: function (actual, expected) { 334 | return typeof actual == expected; 335 | }, 336 | assertMessage: "${3}typeof ${0} (${2}) expected to be ${1}", 337 | refuteMessage: "${3}typeof ${0} expected not to be ${1}", 338 | expectation: "toBeType", 339 | 340 | values: function (actual, expected) { 341 | return [actual, expected, typeof actual, msg(arguments[2])]; 342 | } 343 | }); 344 | 345 | ba.add("defined", { 346 | assert: function (actual) { 347 | return typeof actual != "undefined"; 348 | }, 349 | assertMessage: "${2}Expected to be defined", 350 | refuteMessage: "${2}Expected ${0} (${1}) not to be defined", 351 | expectation: "toBeDefined", 352 | values: actualAndTypeOfMessageValues 353 | }); 354 | 355 | ba.add("isNull", { 356 | assert: function (actual) { 357 | return actual === null; 358 | }, 359 | assertMessage: "${1}Expected ${0} to be null", 360 | refuteMessage: "${1}Expected not to be null", 361 | expectation: "toBeNull", 362 | values: actualMessageValues 363 | }); 364 | 365 | function match(object, matcher) { 366 | if (object === matcher) { 367 | return true; 368 | } 369 | 370 | if (matcher && typeof matcher.test == "function") { 371 | return matcher.test(object); 372 | } 373 | 374 | if (typeof matcher == "function") { 375 | return matcher(object) === true; 376 | } 377 | 378 | if (typeof matcher == "string") { 379 | matcher = matcher.toLowerCase(); 380 | return !!object && ("" + object).toLowerCase().indexOf(matcher) >= 0; 381 | } 382 | 383 | if (typeof matcher == "number") { 384 | return matcher == object; 385 | } 386 | 387 | if (typeof matcher == "boolean") { 388 | return matcher === object; 389 | } 390 | 391 | if (matcher && typeof matcher == "object") { 392 | for (var prop in matcher) { 393 | if (!match(object[prop], matcher[prop])) { 394 | return false; 395 | } 396 | } 397 | 398 | return true; 399 | } 400 | 401 | throw new Error("Matcher (" + ba.format(matcher) + ") was not a " + 402 | "string, a number, a function, a boolean or an object"); 403 | } 404 | 405 | ba.match = match; 406 | 407 | ba.add("match", { 408 | assert: function (actual, matcher) { 409 | var passed; 410 | 411 | try { 412 | passed = match(actual, matcher); 413 | } catch (e) { 414 | return this.fail("exceptionMessage", e.message, msg(arguments[2])); 415 | } 416 | 417 | return passed; 418 | }, 419 | 420 | refute: function (actual, matcher) { 421 | var passed; 422 | 423 | try { 424 | passed = match(actual, matcher); 425 | } catch (e) { 426 | return this.fail("exceptionMessage", e.message); 427 | } 428 | 429 | return !passed; 430 | }, 431 | 432 | assertMessage: "${2}${0} expected to match ${1}", 433 | refuteMessage: "${2}${0} expected not to match ${1}", 434 | expectation: "toMatch", 435 | values: actualAndExpectedMessageValues 436 | }); 437 | 438 | assert.match.exceptionMessage = "${1}${0}"; 439 | refute.match.exceptionMessage = "${1}${0}"; 440 | 441 | ba.add("isObject", { 442 | assert: function (actual) { 443 | return typeof actual == "object" && !!actual; 444 | }, 445 | assertMessage: "${2}${0} (${1}) expected to be object and not null", 446 | refuteMessage: "${2}${0} expected to be null or not an object", 447 | expectation: "toBeObject", 448 | values: actualAndTypeOfMessageValues 449 | }); 450 | 451 | ba.add("isFunction", { 452 | assert: function (actual) { 453 | return typeof actual == "function"; 454 | }, 455 | assertMessage: "${2}${0} (${1}) expected to be function", 456 | refuteMessage: "${2}${0} expected not to be function", 457 | expectation: "toBeFunction", 458 | values: function (actual) { 459 | return [("" + actual).replace("\n", ""), typeof actual, msg(arguments[1])]; 460 | } 461 | }); 462 | 463 | ba.add("isTrue", { 464 | assert: function (actual) { 465 | return actual === true; 466 | }, 467 | assertMessage: "${1}Expected ${0} to be true", 468 | refuteMessage: "${1}Expected ${0} to not be true", 469 | expectation: "toBeTrue", 470 | values: actualMessageValues 471 | }); 472 | 473 | ba.add("isFalse", { 474 | assert: function (actual) { 475 | return actual === false; 476 | }, 477 | assertMessage: "${1}Expected ${0} to be false", 478 | refuteMessage: "${1}Expected ${0} to not be false", 479 | expectation: "toBeFalse", 480 | values: actualMessageValues 481 | }); 482 | 483 | ba.add("isString", { 484 | assert: function (actual) { 485 | return typeof actual == "string"; 486 | }, 487 | assertMessage: "${2}Expected ${0} (${1}) to be string", 488 | refuteMessage: "${2}Expected ${0} not to be string", 489 | expectation: "toBeString", 490 | values: actualAndTypeOfMessageValues 491 | }); 492 | 493 | ba.add("isBoolean", { 494 | assert: function (actual) { 495 | return typeof actual == "boolean"; 496 | }, 497 | assertMessage: "${2}Expected ${0} (${1}) to be boolean", 498 | refuteMessage: "${2}Expected ${0} not to be boolean", 499 | expectation: "toBeBoolean", 500 | values: actualAndTypeOfMessageValues 501 | }); 502 | 503 | ba.add("isNumber", { 504 | assert: function (actual) { 505 | return typeof actual == "number" && !isNaN(actual); 506 | }, 507 | assertMessage: "${2}Expected ${0} (${1}) to be a non-NaN number", 508 | refuteMessage: "${2}Expected ${0} to be NaN or another non-number value", 509 | expectation: "toBeNumber", 510 | values: actualAndTypeOfMessageValues 511 | }); 512 | 513 | ba.add("isNaN", { 514 | assert: function (actual) { 515 | return typeof actual == "number" && isNaN(actual); 516 | }, 517 | assertMessage: "${2}Expected ${0} to be NaN", 518 | refuteMessage: "${2}Expected not to be NaN", 519 | expectation: "toBeNaN", 520 | values: actualAndTypeOfMessageValues 521 | }); 522 | 523 | ba.add("isArray", { 524 | assert: function (actual) { 525 | return toString.call(actual) == "[object Array]"; 526 | }, 527 | assertMessage: "${2}Expected ${0} to be array", 528 | refuteMessage: "${2}Expected ${0} not to be array", 529 | expectation: "toBeArray", 530 | values: actualAndTypeOfMessageValues 531 | }); 532 | 533 | function isArrayLike(object) { 534 | return toString.call(object) == "[object Array]" || 535 | (!!object && typeof object.length == "number" && 536 | typeof object.splice == "function") || 537 | ba.isArguments(object); 538 | } 539 | 540 | ba.isArrayLike = isArrayLike; 541 | 542 | ba.add("isArrayLike", { 543 | assert: function (actual) { 544 | return isArrayLike(actual); 545 | }, 546 | assertMessage: "${2}Expected ${0} to be array like", 547 | refuteMessage: "${2}Expected ${0} not to be array like", 548 | expectation: "toBeArrayLike", 549 | values: actualAndTypeOfMessageValues 550 | }); 551 | 552 | function captureException(callback) { 553 | try { 554 | callback(); 555 | } catch (e) { 556 | return e; 557 | } 558 | 559 | return null; 560 | } 561 | 562 | ba.captureException = captureException; 563 | 564 | assert.exception = function (callback, exception, message) { 565 | countAssertion(); 566 | if (!assertEnoughArguments("assert.exception", arguments, 1)) return 567 | 568 | if (!callback) { 569 | return; 570 | } 571 | 572 | var err = captureException(callback); 573 | message = msg(message); 574 | 575 | if (!err) { 576 | if (exception) { 577 | return fail.call({}, "assert", "exception", "typeNoExceptionMessage", 578 | message, exception); 579 | } else { 580 | return fail.call({}, "assert", "exception", "message", 581 | message, exception); 582 | } 583 | } 584 | 585 | if (exception && err.name != exception) { 586 | if (typeof window != "undefined" && typeof console != "undefined") { 587 | console.log(err); 588 | } 589 | 590 | return fail.call({}, "assert", "exception", "typeFailMessage", 591 | message, exception, err.name, err.message); 592 | } 593 | 594 | ba.emit("pass", "assert.exception", message, callback, exception); 595 | }; 596 | 597 | assert.exception.typeNoExceptionMessage = "${0}Expected ${1} but no exception was thrown"; 598 | assert.exception.message = "${0}Expected exception"; 599 | assert.exception.typeFailMessage = "${0}Expected ${1} but threw ${2} (${3})"; 600 | assert.exception.expectationName = "toThrow"; 601 | 602 | refute.exception = function (callback) { 603 | countAssertion(); 604 | if (!assertEnoughArguments("refute.exception", arguments, 1)) return; 605 | 606 | var err = captureException(callback); 607 | 608 | if (err) { 609 | fail("refute", "exception", "message", 610 | msg(arguments[1]), err.name, err.message, callback); 611 | } else { 612 | ba.emit("pass", "refute.exception", callback); 613 | } 614 | }; 615 | 616 | refute.exception.message = "${0}Expected not to throw but threw ${1} (${2})"; 617 | refute.exception.expectationName = "toThrow"; 618 | 619 | ba.add("inDelta", { 620 | assert: function (actual, expected, delta) { 621 | return Math.abs(actual - expected) <= delta; 622 | }, 623 | assertMessage: "${3}Expected ${0} to be equal to ${1} +/- ${2}", 624 | refuteMessage: "${3}Expected ${0} not to be equal to ${1} +/- ${2}", 625 | expectation: "toBeInDelta", 626 | values: function (actual, expected, delta, message) { 627 | return [actual, expected, delta, msg(message)]; 628 | } 629 | }); 630 | 631 | ba.add("hasPrototype", { 632 | assert: function (actual, protoObj) { 633 | return protoObj.isPrototypeOf(actual); 634 | }, 635 | assertMessage: "${2}Expected ${0} to have ${1} on its prototype chain", 636 | refuteMessage: "${2}Expected ${0} not to have ${1} on its prototype chain", 637 | expectation: "toHavePrototype", 638 | values: actualAndExpectedMessageValues 639 | }); 640 | 641 | ba.add("tagName", { 642 | assert: function (element, tagName) { 643 | if (!element.tagName) { 644 | return this.fail("noTagNameMessage", tagName, element, msg(arguments[2])); 645 | } 646 | 647 | return tagName.toLowerCase && 648 | tagName.toLowerCase() == element.tagName.toLowerCase(); 649 | }, 650 | assertMessage: "${2}Expected tagName to be ${0} but was ${1}", 651 | refuteMessage: "${2}Expected tagName not to be ${0}", 652 | expectation: "toHaveTagName", 653 | values: function (element, tagName, message) { 654 | return [tagName, element.tagName, msg(message)]; 655 | } 656 | }); 657 | 658 | assert.tagName.noTagNameMessage = "${2}Expected ${1} to have tagName property"; 659 | refute.tagName.noTagNameMessage = "${2}Expected ${1} to have tagName property"; 660 | 661 | function indexOf(arr, item) { 662 | for (var i = 0, l = arr.length; i < l; i++) { 663 | if (arr[i] == item) { 664 | return i; 665 | } 666 | } 667 | 668 | return -1; 669 | } 670 | 671 | ba.add("className", { 672 | assert: function (element, className) { 673 | if (typeof element.className == "undefined") { 674 | return this.fail("noClassNameMessage", className, element, msg(arguments[2])); 675 | } 676 | 677 | var expected = typeof className == "string" ? className.split(" ") : className; 678 | var actual = element.className.split(" "); 679 | 680 | for (var i = 0, l = expected.length; i < l; i++) { 681 | if (indexOf(actual, expected[i]) < 0) { 682 | return false; 683 | } 684 | } 685 | 686 | return true; 687 | }, 688 | assertMessage: "${2}Expected object's className to include ${0} but was ${1}", 689 | refuteMessage: "${2}Expected object's className not to include ${0}", 690 | expectation: "toHaveClassName", 691 | values: function (element, className, message) { 692 | return [className, element.className, msg(message)]; 693 | } 694 | }); 695 | 696 | assert.className.noClassNameMessage = "${2}Expected object to have className property"; 697 | refute.className.noClassNameMessage = "${2}Expected object to have className property"; 698 | 699 | if (typeof module != "undefined") { 700 | ba.expect = function () { 701 | ba.expect = require("./buster-assertions/expect"); 702 | return ba.expect.apply(exports, arguments); 703 | }; 704 | } 705 | 706 | function isArguments(obj) { 707 | if (typeof obj != "object" || typeof obj.length != "number" || 708 | toString.call(obj) == "[object Array]") { 709 | return false; 710 | } 711 | 712 | if (typeof obj.callee == "function") { 713 | return true; 714 | } 715 | 716 | try { 717 | obj[obj.length] = 6; 718 | delete obj[obj.length]; 719 | } catch (e) { 720 | return true; 721 | } 722 | 723 | return false; 724 | } 725 | 726 | ba.isArguments = isArguments; 727 | 728 | if (Object.keys) { 729 | ba.keys = function (obj) { 730 | return Object.keys(obj) 731 | }; 732 | } else { 733 | ba.keys = function (object) { 734 | var keys = []; 735 | 736 | for (var prop in object) { 737 | if (Object.prototype.hasOwnProperty.call(object, prop)) { 738 | keys.push(prop); 739 | } 740 | } 741 | 742 | return keys; 743 | } 744 | } 745 | }()); 746 | -------------------------------------------------------------------------------- /src/lib/underscore/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.1.5 2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | 9 | (function() { 10 | 11 | // Baseline setup 12 | // -------------- 13 | 14 | // Establish the root object, `window` in the browser, or `global` on the server. 15 | var root = this; 16 | 17 | // Save the previous value of the `_` variable. 18 | var previousUnderscore = root._; 19 | 20 | // Establish the object that gets returned to break out of a loop iteration. 21 | var breaker = {}; 22 | 23 | // Save bytes in the minified (but not gzipped) version: 24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 25 | 26 | // Create quick reference variables for speed access to core prototypes. 27 | var slice = ArrayProto.slice, 28 | unshift = ArrayProto.unshift, 29 | toString = ObjProto.toString, 30 | hasOwnProperty = ObjProto.hasOwnProperty; 31 | 32 | // All **ECMAScript 5** native function implementations that we hope to use 33 | // are declared here. 34 | var 35 | nativeForEach = ArrayProto.forEach, 36 | nativeMap = ArrayProto.map, 37 | nativeReduce = ArrayProto.reduce, 38 | nativeReduceRight = ArrayProto.reduceRight, 39 | nativeFilter = ArrayProto.filter, 40 | nativeEvery = ArrayProto.every, 41 | nativeSome = ArrayProto.some, 42 | nativeIndexOf = ArrayProto.indexOf, 43 | nativeLastIndexOf = ArrayProto.lastIndexOf, 44 | nativeIsArray = Array.isArray, 45 | nativeKeys = Object.keys, 46 | nativeBind = FuncProto.bind; 47 | 48 | // Create a safe reference to the Underscore object for use below. 49 | var _ = function(obj) { return new wrapper(obj); }; 50 | 51 | // Export the Underscore object for **CommonJS**, with backwards-compatibility 52 | // for the old `require()` API. If we're not in CommonJS, add `_` to the 53 | // global object. 54 | if (typeof module !== 'undefined' && module.exports) { 55 | module.exports = _; 56 | _._ = _; 57 | } else { 58 | root._ = _; 59 | } 60 | 61 | // Current version. 62 | _.VERSION = '1.1.5'; 63 | 64 | // Collection Functions 65 | // -------------------- 66 | 67 | // The cornerstone, an `each` implementation, aka `forEach`. 68 | // Handles objects implementing `forEach`, arrays, and raw objects. 69 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 70 | var each = _.each = _.forEach = function(obj, iterator, context) { 71 | if (obj == null) return; 72 | if (nativeForEach && obj.forEach === nativeForEach) { 73 | obj.forEach(iterator, context); 74 | } else if (_.isNumber(obj.length)) { 75 | for (var i = 0, l = obj.length; i < l; i++) { 76 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 77 | } 78 | } else { 79 | for (var key in obj) { 80 | if (hasOwnProperty.call(obj, key)) { 81 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 82 | } 83 | } 84 | } 85 | }; 86 | 87 | // Return the results of applying the iterator to each element. 88 | // Delegates to **ECMAScript 5**'s native `map` if available. 89 | _.map = function(obj, iterator, context) { 90 | var results = []; 91 | if (obj == null) return results; 92 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 93 | each(obj, function(value, index, list) { 94 | results[results.length] = iterator.call(context, value, index, list); 95 | }); 96 | return results; 97 | }; 98 | 99 | // **Reduce** builds up a single result from a list of values, aka `inject`, 100 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 101 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 102 | var initial = memo !== void 0; 103 | if (obj == null) obj = []; 104 | if (nativeReduce && obj.reduce === nativeReduce) { 105 | if (context) iterator = _.bind(iterator, context); 106 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 107 | } 108 | each(obj, function(value, index, list) { 109 | if (!initial && index === 0) { 110 | memo = value; 111 | initial = true; 112 | } else { 113 | memo = iterator.call(context, memo, value, index, list); 114 | } 115 | }); 116 | if (!initial) throw new TypeError("Reduce of empty array with no initial value"); 117 | return memo; 118 | }; 119 | 120 | // The right-associative version of reduce, also known as `foldr`. 121 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 122 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 123 | if (obj == null) obj = []; 124 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 125 | if (context) iterator = _.bind(iterator, context); 126 | return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 127 | } 128 | var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); 129 | return _.reduce(reversed, iterator, memo, context); 130 | }; 131 | 132 | // Return the first value which passes a truth test. Aliased as `detect`. 133 | _.find = _.detect = function(obj, iterator, context) { 134 | var result; 135 | any(obj, function(value, index, list) { 136 | if (iterator.call(context, value, index, list)) { 137 | result = value; 138 | return true; 139 | } 140 | }); 141 | return result; 142 | }; 143 | 144 | // Return all the elements that pass a truth test. 145 | // Delegates to **ECMAScript 5**'s native `filter` if available. 146 | // Aliased as `select`. 147 | _.filter = _.select = function(obj, iterator, context) { 148 | var results = []; 149 | if (obj == null) return results; 150 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 151 | each(obj, function(value, index, list) { 152 | if (iterator.call(context, value, index, list)) results[results.length] = value; 153 | }); 154 | return results; 155 | }; 156 | 157 | // Return all the elements for which a truth test fails. 158 | _.reject = function(obj, iterator, context) { 159 | var results = []; 160 | if (obj == null) return results; 161 | each(obj, function(value, index, list) { 162 | if (!iterator.call(context, value, index, list)) results[results.length] = value; 163 | }); 164 | return results; 165 | }; 166 | 167 | // Determine whether all of the elements match a truth test. 168 | // Delegates to **ECMAScript 5**'s native `every` if available. 169 | // Aliased as `all`. 170 | _.every = _.all = function(obj, iterator, context) { 171 | iterator = iterator || _.identity; 172 | var result = true; 173 | if (obj == null) return result; 174 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 175 | each(obj, function(value, index, list) { 176 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 177 | }); 178 | return result; 179 | }; 180 | 181 | // Determine if at least one element in the object matches a truth test. 182 | // Delegates to **ECMAScript 5**'s native `some` if available. 183 | // Aliased as `any`. 184 | var any = _.some = _.any = function(obj, iterator, context) { 185 | iterator = iterator || _.identity; 186 | var result = false; 187 | if (obj == null) return result; 188 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 189 | each(obj, function(value, index, list) { 190 | if (result = iterator.call(context, value, index, list)) return breaker; 191 | }); 192 | return result; 193 | }; 194 | 195 | // Determine if a given value is included in the array or object using `===`. 196 | // Aliased as `contains`. 197 | _.include = _.contains = function(obj, target) { 198 | var found = false; 199 | if (obj == null) return found; 200 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 201 | any(obj, function(value) { 202 | if (found = value === target) return true; 203 | }); 204 | return found; 205 | }; 206 | 207 | // Invoke a method (with arguments) on every item in a collection. 208 | _.invoke = function(obj, method) { 209 | var args = slice.call(arguments, 2); 210 | return _.map(obj, function(value) { 211 | return (method ? value[method] : value).apply(value, args); 212 | }); 213 | }; 214 | 215 | // Convenience version of a common use case of `map`: fetching a property. 216 | _.pluck = function(obj, key) { 217 | return _.map(obj, function(value){ return value[key]; }); 218 | }; 219 | 220 | // Return the maximum element or (element-based computation). 221 | _.max = function(obj, iterator, context) { 222 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); 223 | var result = {computed : -Infinity}; 224 | each(obj, function(value, index, list) { 225 | var computed = iterator ? iterator.call(context, value, index, list) : value; 226 | computed >= result.computed && (result = {value : value, computed : computed}); 227 | }); 228 | return result.value; 229 | }; 230 | 231 | // Return the minimum element (or element-based computation). 232 | _.min = function(obj, iterator, context) { 233 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); 234 | var result = {computed : Infinity}; 235 | each(obj, function(value, index, list) { 236 | var computed = iterator ? iterator.call(context, value, index, list) : value; 237 | computed < result.computed && (result = {value : value, computed : computed}); 238 | }); 239 | return result.value; 240 | }; 241 | 242 | // Sort the object's values by a criterion produced by an iterator. 243 | _.sortBy = function(obj, iterator, context) { 244 | return _.pluck(_.map(obj, function(value, index, list) { 245 | return { 246 | value : value, 247 | criteria : iterator.call(context, value, index, list) 248 | }; 249 | }).sort(function(left, right) { 250 | var a = left.criteria, b = right.criteria; 251 | return a < b ? -1 : a > b ? 1 : 0; 252 | }), 'value'); 253 | }; 254 | 255 | // Use a comparator function to figure out at what index an object should 256 | // be inserted so as to maintain order. Uses binary search. 257 | _.sortedIndex = function(array, obj, iterator) { 258 | iterator = iterator || _.identity; 259 | var low = 0, high = array.length; 260 | while (low < high) { 261 | var mid = (low + high) >> 1; 262 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 263 | } 264 | return low; 265 | }; 266 | 267 | // Safely convert anything iterable into a real, live array. 268 | _.toArray = function(iterable) { 269 | if (!iterable) return []; 270 | if (iterable.toArray) return iterable.toArray(); 271 | if (_.isArray(iterable)) return iterable; 272 | if (_.isArguments(iterable)) return slice.call(iterable); 273 | return _.values(iterable); 274 | }; 275 | 276 | // Return the number of elements in an object. 277 | _.size = function(obj) { 278 | return _.toArray(obj).length; 279 | }; 280 | 281 | // Array Functions 282 | // --------------- 283 | 284 | // Get the first element of an array. Passing **n** will return the first N 285 | // values in the array. Aliased as `head`. The **guard** check allows it to work 286 | // with `_.map`. 287 | _.first = _.head = function(array, n, guard) { 288 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 289 | }; 290 | 291 | // Returns everything but the first entry of the array. Aliased as `tail`. 292 | // Especially useful on the arguments object. Passing an **index** will return 293 | // the rest of the values in the array from that index onward. The **guard** 294 | // check allows it to work with `_.map`. 295 | _.rest = _.tail = function(array, index, guard) { 296 | return slice.call(array, (index == null) || guard ? 1 : index); 297 | }; 298 | 299 | // Get the last element of an array. 300 | _.last = function(array) { 301 | return array[array.length - 1]; 302 | }; 303 | 304 | // Trim out all falsy values from an array. 305 | _.compact = function(array) { 306 | return _.filter(array, function(value){ return !!value; }); 307 | }; 308 | 309 | // Return a completely flattened version of an array. 310 | _.flatten = function(array) { 311 | return _.reduce(array, function(memo, value) { 312 | if (_.isArray(value)) return memo.concat(_.flatten(value)); 313 | memo[memo.length] = value; 314 | return memo; 315 | }, []); 316 | }; 317 | 318 | // Return a version of the array that does not contain the specified value(s). 319 | _.without = function(array) { 320 | var values = slice.call(arguments, 1); 321 | return _.filter(array, function(value){ return !_.include(values, value); }); 322 | }; 323 | 324 | // Produce a duplicate-free version of the array. If the array has already 325 | // been sorted, you have the option of using a faster algorithm. 326 | // Aliased as `unique`. 327 | _.uniq = _.unique = function(array, isSorted) { 328 | return _.reduce(array, function(memo, el, i) { 329 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; 330 | return memo; 331 | }, []); 332 | }; 333 | 334 | // Produce an array that contains every item shared between all the 335 | // passed-in arrays. 336 | _.intersect = function(array) { 337 | var rest = slice.call(arguments, 1); 338 | return _.filter(_.uniq(array), function(item) { 339 | return _.every(rest, function(other) { 340 | return _.indexOf(other, item) >= 0; 341 | }); 342 | }); 343 | }; 344 | 345 | // Zip together multiple lists into a single array -- elements that share 346 | // an index go together. 347 | _.zip = function() { 348 | var args = slice.call(arguments); 349 | var length = _.max(_.pluck(args, 'length')); 350 | var results = new Array(length); 351 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 352 | return results; 353 | }; 354 | 355 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 356 | // we need this function. Return the position of the first occurrence of an 357 | // item in an array, or -1 if the item is not included in the array. 358 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 359 | // If the array is large and already in sort order, pass `true` 360 | // for **isSorted** to use binary search. 361 | _.indexOf = function(array, item, isSorted) { 362 | if (array == null) return -1; 363 | var i, l; 364 | if (isSorted) { 365 | i = _.sortedIndex(array, item); 366 | return array[i] === item ? i : -1; 367 | } 368 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 369 | for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; 370 | return -1; 371 | }; 372 | 373 | 374 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 375 | _.lastIndexOf = function(array, item) { 376 | if (array == null) return -1; 377 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 378 | var i = array.length; 379 | while (i--) if (array[i] === item) return i; 380 | return -1; 381 | }; 382 | 383 | // Generate an integer Array containing an arithmetic progression. A port of 384 | // the native Python `range()` function. See 385 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 386 | _.range = function(start, stop, step) { 387 | if (arguments.length <= 1) { 388 | stop = start || 0; 389 | start = 0; 390 | } 391 | step = arguments[2] || 1; 392 | 393 | var len = Math.max(Math.ceil((stop - start) / step), 0); 394 | var idx = 0; 395 | var range = new Array(len); 396 | 397 | while(idx < len) { 398 | range[idx++] = start; 399 | start += step; 400 | } 401 | 402 | return range; 403 | }; 404 | 405 | // Function (ahem) Functions 406 | // ------------------ 407 | 408 | // Create a function bound to a given object (assigning `this`, and arguments, 409 | // optionally). Binding with arguments is also known as `curry`. 410 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 411 | _.bind = function(func, obj) { 412 | if (nativeBind && func.bind === nativeBind) return func.bind.apply(func, slice.call(arguments, 1)); 413 | var args = slice.call(arguments, 2); 414 | return function() { 415 | return func.apply(obj, args.concat(slice.call(arguments))); 416 | }; 417 | }; 418 | 419 | // Bind all of an object's methods to that object. Useful for ensuring that 420 | // all callbacks defined on an object belong to it. 421 | _.bindAll = function(obj) { 422 | var funcs = slice.call(arguments, 1); 423 | if (funcs.length == 0) funcs = _.functions(obj); 424 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 425 | return obj; 426 | }; 427 | 428 | // Memoize an expensive function by storing its results. 429 | _.memoize = function(func, hasher) { 430 | var memo = {}; 431 | hasher = hasher || _.identity; 432 | return function() { 433 | var key = hasher.apply(this, arguments); 434 | return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 435 | }; 436 | }; 437 | 438 | // Delays a function for the given number of milliseconds, and then calls 439 | // it with the arguments supplied. 440 | _.delay = function(func, wait) { 441 | var args = slice.call(arguments, 2); 442 | return setTimeout(function(){ return func.apply(func, args); }, wait); 443 | }; 444 | 445 | // Defers a function, scheduling it to run after the current call stack has 446 | // cleared. 447 | _.defer = function(func) { 448 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 449 | }; 450 | 451 | // Internal function used to implement `_.throttle` and `_.debounce`. 452 | var limit = function(func, wait, debounce) { 453 | var timeout; 454 | return function() { 455 | var context = this, args = arguments; 456 | var throttler = function() { 457 | timeout = null; 458 | func.apply(context, args); 459 | }; 460 | if (debounce) clearTimeout(timeout); 461 | if (debounce || !timeout) timeout = setTimeout(throttler, wait); 462 | }; 463 | }; 464 | 465 | // Returns a function, that, when invoked, will only be triggered at most once 466 | // during a given window of time. 467 | _.throttle = function(func, wait) { 468 | return limit(func, wait, false); 469 | }; 470 | 471 | // Returns a function, that, as long as it continues to be invoked, will not 472 | // be triggered. The function will be called after it stops being called for 473 | // N milliseconds. 474 | _.debounce = function(func, wait) { 475 | return limit(func, wait, true); 476 | }; 477 | 478 | // Returns a function that will be executed at most one time, no matter how 479 | // often you call it. Useful for lazy initialization. 480 | _.once = function(func) { 481 | var ran = false, memo; 482 | return function() { 483 | if (ran) return memo; 484 | ran = true; 485 | return memo = func.apply(this, arguments); 486 | }; 487 | }; 488 | 489 | // Returns the first function passed as an argument to the second, 490 | // allowing you to adjust arguments, run code before and after, and 491 | // conditionally execute the original function. 492 | _.wrap = function(func, wrapper) { 493 | return function() { 494 | var args = [func].concat(slice.call(arguments)); 495 | return wrapper.apply(this, args); 496 | }; 497 | }; 498 | 499 | // Returns a function that is the composition of a list of functions, each 500 | // consuming the return value of the function that follows. 501 | _.compose = function() { 502 | var funcs = slice.call(arguments); 503 | return function() { 504 | var args = slice.call(arguments); 505 | for (var i=funcs.length-1; i >= 0; i--) { 506 | args = [funcs[i].apply(this, args)]; 507 | } 508 | return args[0]; 509 | }; 510 | }; 511 | 512 | // Object Functions 513 | // ---------------- 514 | 515 | // Retrieve the names of an object's properties. 516 | // Delegates to **ECMAScript 5**'s native `Object.keys` 517 | _.keys = nativeKeys || function(obj) { 518 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 519 | var keys = []; 520 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; 521 | return keys; 522 | }; 523 | 524 | // Retrieve the values of an object's properties. 525 | _.values = function(obj) { 526 | return _.map(obj, _.identity); 527 | }; 528 | 529 | // Return a sorted list of the function names available on the object. 530 | // Aliased as `methods` 531 | _.functions = _.methods = function(obj) { 532 | return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); 533 | }; 534 | 535 | // Extend a given object with all the properties in passed-in object(s). 536 | _.extend = function(obj) { 537 | each(slice.call(arguments, 1), function(source) { 538 | for (var prop in source) obj[prop] = source[prop]; 539 | }); 540 | return obj; 541 | }; 542 | 543 | // Fill in a given object with default properties. 544 | _.defaults = function(obj) { 545 | each(slice.call(arguments, 1), function(source) { 546 | for (var prop in source) if (obj[prop] == null) obj[prop] = source[prop]; 547 | }); 548 | return obj; 549 | }; 550 | 551 | // Create a (shallow-cloned) duplicate of an object. 552 | _.clone = function(obj) { 553 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 554 | }; 555 | 556 | // Invokes interceptor with the obj, and then returns obj. 557 | // The primary purpose of this method is to "tap into" a method chain, in 558 | // order to perform operations on intermediate results within the chain. 559 | _.tap = function(obj, interceptor) { 560 | interceptor(obj); 561 | return obj; 562 | }; 563 | 564 | // Perform a deep comparison to check if two objects are equal. 565 | _.isEqual = function(a, b) { 566 | // Check object identity. 567 | if (a === b) return true; 568 | // Different types? 569 | var atype = typeof(a), btype = typeof(b); 570 | if (atype != btype) return false; 571 | // Basic equality test (watch out for coercions). 572 | if (a == b) return true; 573 | // One is falsy and the other truthy. 574 | if ((!a && b) || (a && !b)) return false; 575 | // Unwrap any wrapped objects. 576 | if (a._chain) a = a._wrapped; 577 | if (b._chain) b = b._wrapped; 578 | // One of them implements an isEqual()? 579 | if (a.isEqual) return a.isEqual(b); 580 | // Check dates' integer values. 581 | if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); 582 | // Both are NaN? 583 | if (_.isNaN(a) && _.isNaN(b)) return false; 584 | // Compare regular expressions. 585 | if (_.isRegExp(a) && _.isRegExp(b)) 586 | return a.source === b.source && 587 | a.global === b.global && 588 | a.ignoreCase === b.ignoreCase && 589 | a.multiline === b.multiline; 590 | // If a is not an object by this point, we can't handle it. 591 | if (atype !== 'object') return false; 592 | // Check for different array lengths before comparing contents. 593 | if (a.length && (a.length !== b.length)) return false; 594 | // Nothing else worked, deep compare the contents. 595 | var aKeys = _.keys(a), bKeys = _.keys(b); 596 | // Different object sizes? 597 | if (aKeys.length != bKeys.length) return false; 598 | // Recursive comparison of contents. 599 | for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; 600 | return true; 601 | }; 602 | 603 | // Is a given array or object empty? 604 | _.isEmpty = function(obj) { 605 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 606 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; 607 | return true; 608 | }; 609 | 610 | // Is a given value a DOM element? 611 | _.isElement = function(obj) { 612 | return !!(obj && obj.nodeType == 1); 613 | }; 614 | 615 | // Is a given value an array? 616 | // Delegates to ECMA5's native Array.isArray 617 | _.isArray = nativeIsArray || function(obj) { 618 | return toString.call(obj) === '[object Array]'; 619 | }; 620 | 621 | // Is a given variable an arguments object? 622 | _.isArguments = function(obj) { 623 | return !!(obj && hasOwnProperty.call(obj, 'callee')); 624 | }; 625 | 626 | // Is a given value a function? 627 | _.isFunction = function(obj) { 628 | return !!(obj && obj.constructor && obj.call && obj.apply); 629 | }; 630 | 631 | // Is a given value a string? 632 | _.isString = function(obj) { 633 | return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); 634 | }; 635 | 636 | // Is a given value a number? 637 | _.isNumber = function(obj) { 638 | return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); 639 | }; 640 | 641 | // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript 642 | // that does not equal itself. 643 | _.isNaN = function(obj) { 644 | return obj !== obj; 645 | }; 646 | 647 | // Is a given value a boolean? 648 | _.isBoolean = function(obj) { 649 | return obj === true || obj === false; 650 | }; 651 | 652 | // Is a given value a date? 653 | _.isDate = function(obj) { 654 | return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); 655 | }; 656 | 657 | // Is the given value a regular expression? 658 | _.isRegExp = function(obj) { 659 | return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); 660 | }; 661 | 662 | // Is a given value equal to null? 663 | _.isNull = function(obj) { 664 | return obj === null; 665 | }; 666 | 667 | // Is a given variable undefined? 668 | _.isUndefined = function(obj) { 669 | return obj === void 0; 670 | }; 671 | 672 | // Utility Functions 673 | // ----------------- 674 | 675 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 676 | // previous owner. Returns a reference to the Underscore object. 677 | _.noConflict = function() { 678 | root._ = previousUnderscore; 679 | return this; 680 | }; 681 | 682 | // Keep the identity function around for default iterators. 683 | _.identity = function(value) { 684 | return value; 685 | }; 686 | 687 | // Run a function **n** times. 688 | _.times = function (n, iterator, context) { 689 | for (var i = 0; i < n; i++) iterator.call(context, i); 690 | }; 691 | 692 | // Add your own custom functions to the Underscore object, ensuring that 693 | // they're correctly added to the OOP wrapper as well. 694 | _.mixin = function(obj) { 695 | each(_.functions(obj), function(name){ 696 | addToWrapper(name, _[name] = obj[name]); 697 | }); 698 | }; 699 | 700 | // Generate a unique integer id (unique within the entire client session). 701 | // Useful for temporary DOM ids. 702 | var idCounter = 0; 703 | _.uniqueId = function(prefix) { 704 | var id = idCounter++; 705 | return prefix ? prefix + id : id; 706 | }; 707 | 708 | // By default, Underscore uses ERB-style template delimiters, change the 709 | // following template settings to use alternative delimiters. 710 | _.templateSettings = { 711 | evaluate : /<%([\s\S]+?)%>/g, 712 | interpolate : /<%=([\s\S]+?)%>/g 713 | }; 714 | 715 | // JavaScript micro-templating, similar to John Resig's implementation. 716 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 717 | // and correctly escapes quotes within interpolated code. 718 | _.template = function(str, data) { 719 | var c = _.templateSettings; 720 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 721 | 'with(obj||{}){__p.push(\'' + 722 | str.replace(/\\/g, '\\\\') 723 | .replace(/'/g, "\\'") 724 | .replace(c.interpolate, function(match, code) { 725 | return "'," + code.replace(/\\'/g, "'") + ",'"; 726 | }) 727 | .replace(c.evaluate || null, function(match, code) { 728 | return "');" + code.replace(/\\'/g, "'") 729 | .replace(/[\r\n\t]/g, ' ') + "__p.push('"; 730 | }) 731 | .replace(/\r/g, '\\r') 732 | .replace(/\n/g, '\\n') 733 | .replace(/\t/g, '\\t') 734 | + "');}return __p.join('');"; 735 | var func = new Function('obj', tmpl); 736 | return data ? func(data) : func; 737 | }; 738 | 739 | // The OOP Wrapper 740 | // --------------- 741 | 742 | // If Underscore is called as a function, it returns a wrapped object that 743 | // can be used OO-style. This wrapper holds altered versions of all the 744 | // underscore functions. Wrapped objects may be chained. 745 | var wrapper = function(obj) { this._wrapped = obj; }; 746 | 747 | // Expose `wrapper.prototype` as `_.prototype` 748 | _.prototype = wrapper.prototype; 749 | 750 | // Helper function to continue chaining intermediate results. 751 | var result = function(obj, chain) { 752 | return chain ? _(obj).chain() : obj; 753 | }; 754 | 755 | // A method to easily add functions to the OOP wrapper. 756 | var addToWrapper = function(name, func) { 757 | wrapper.prototype[name] = function() { 758 | var args = slice.call(arguments); 759 | unshift.call(args, this._wrapped); 760 | return result(func.apply(_, args), this._chain); 761 | }; 762 | }; 763 | 764 | // Add all of the Underscore functions to the wrapper object. 765 | _.mixin(_); 766 | 767 | // Add all mutator Array functions to the wrapper. 768 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 769 | var method = ArrayProto[name]; 770 | wrapper.prototype[name] = function() { 771 | method.apply(this._wrapped, arguments); 772 | return result(this._wrapped, this._chain); 773 | }; 774 | }); 775 | 776 | // Add all accessor Array functions to the wrapper. 777 | each(['concat', 'join', 'slice'], function(name) { 778 | var method = ArrayProto[name]; 779 | wrapper.prototype[name] = function() { 780 | return result(method.apply(this._wrapped, arguments), this._chain); 781 | }; 782 | }); 783 | 784 | // Start chaining a wrapped Underscore object. 785 | wrapper.prototype.chain = function() { 786 | this._chain = true; 787 | return this; 788 | }; 789 | 790 | // Extracts the result from a wrapped and chained object. 791 | wrapper.prototype.value = function() { 792 | return this._wrapped; 793 | }; 794 | 795 | })(); --------------------------------------------------------------------------------