1 /* 2 * Timemap.js Copyright 2010 Nick Rabinowitz. 3 * Licensed under the MIT License (see LICENSE.txt) 4 */ 5 6 /** 7 * @fileOverview 8 * Progressive loader 9 * 10 * @author Nick Rabinowitz (www.nickrabinowitz.com) 11 */ 12 13 // for JSLint 14 /*global TimeMap */ 15 16 /** 17 * @class 18 * Progressive loader class - basically a wrapper for another remote loader that can 19 * load data progressively by date range, depending on timeline position. 20 * 21 * <p>The progressive loader can take either another loader or parameters for 22 * another loader. It expects a loader with a "url" attribute including placeholder 23 * strings [start] and [end] for the start and end dates to retrieve. The assumption 24 * is that the data service can take start and end parameters and return the data for 25 * that date range.</p> 26 * 27 * @example 28 TimeMap.init({ 29 datasets: [ 30 { 31 title: "Progressive JSONP Dataset", 32 type: "progressive", 33 options: { 34 type: "jsonp", 35 url: "http://www.test.com/getsomejson.php?start=[start]&end=[end]callback=" 36 } 37 } 38 ], 39 // etc... 40 }); 41 * 42 * @example 43 TimeMap.init({ 44 datasets: [ 45 { 46 title: "Progressive KML Dataset", 47 type: "progressive", 48 options: { 49 loader: new TimeMap.loaders.kml({ 50 url: "/mydata.kml?start=[start]&end=[end]" 51 }) 52 } 53 } 54 ], 55 // etc... 56 }); 57 * @see <a href="../../examples/progressive.html">Progressive Loader Example</a> 58 * 59 * @constructor 60 * @param {Object} options All options for the loader 61 * @param {TimeMap.loaders.remote} [options.loader] Instantiated loader class (overrides "type") 62 * @param {String} [options.type] Name of loader class to use 63 * @param {String|Date} options.start Start of initial date range, as date or string 64 * @param {Number} options.interval Size in milliseconds of date ranges to load at a time 65 * @param {String|Date} [options.dataMinDate] Minimum date available in data (optional, will avoid 66 * unnecessary service requests if supplied) 67 * @param {String|Date} [options.dataMaxDate] Maximum date available in data (optional, will avoid 68 * unnecessary service requests if supplied) 69 * @param {Function} [options.formatUrl] Function taking (urlTemplate, start, end) and returning 70 * a URL formatted as needed by the service 71 * @param {Function} [options.formatDate={@link TimeMap.util.formatDate}] 72 * Function to turn a date into a string formatted 73 * as needed by the service 74 * @param {mixed} [options[...]] Other options needed by the "type" loader 75 */ 76 TimeMap.loaders.progressive = function(options) { 77 // get loader 78 var loader = options.loader, 79 type = options.type; 80 if (!loader) { 81 // get loader class 82 var loaderClass = (typeof(type) == 'string') ? TimeMap.loaders[type] : type; 83 loader = new loaderClass(options); 84 } 85 86 // save loader attributes 87 var baseUrl = loader.opts.url, 88 baseLoadFunction = loader.load, 89 interval = options.interval, 90 formatDate = options.formatDate || TimeMap.util.formatDate, 91 formatUrl = options.formatUrl || function(url, start, end) { 92 return url 93 .replace('[start]', formatDate(start)) 94 .replace('[end]', formatDate(end)); 95 }, 96 parseDate = TimeMap.dateParsers.hybrid, 97 zeroDate = parseDate(options.start), 98 dataMinDate = parseDate(options.dataMinDate), 99 dataMaxDate = parseDate(options.dataMaxDate), 100 loaded = {}; 101 102 // We don't start with a TimeMap reference, so we need 103 // to stick the listener in on the first load() call 104 var addListener = function(dataset) { 105 var band = dataset.timemap.timeline.getBand(0); 106 // add listener 107 band.addOnScrollListener(function() { 108 // determine relevant blocks 109 var now = band.getCenterVisibleDate(), 110 currBlock = Math.floor((now.getTime() - zeroDate.getTime()) / interval), 111 currBlockTime = zeroDate.getTime() + (interval * currBlock), 112 nextBlockTime = currBlockTime + interval, 113 prevBlockTime = currBlockTime - interval, 114 // no callback necessary? 115 callback = function() { 116 dataset.timemap.timeline.layout(); 117 }; 118 119 // is the current block loaded? 120 if ((!dataMaxDate || currBlockTime < dataMaxDate.getTime()) && 121 (!dataMinDate || currBlockTime > dataMinDate.getTime()) && 122 !loaded[currBlock]) { 123 // load it 124 // console.log("loading current block (" + currBlock + ")"); 125 loader.load(dataset, callback, new Date(currBlockTime), currBlock); 126 } 127 // are we close enough to load the next block, and is it loaded? 128 if (nextBlockTime < band.getMaxDate().getTime() && 129 (!dataMaxDate || nextBlockTime < dataMaxDate.getTime()) && 130 !loaded[currBlock + 1]) { 131 // load next block 132 // console.log("loading next block (" + (currBlock + 1) + ")"); 133 loader.load(dataset, callback, new Date(nextBlockTime), currBlock + 1); 134 } 135 // are we close enough to load the previous block, and is it loaded? 136 if (prevBlockTime > band.getMinDate().getTime() && 137 (!dataMinDate || prevBlockTime > dataMinDate.getTime()) && 138 !loaded[currBlock - 1]) { 139 // load previous block 140 // console.log("loading prev block (" + (currBlock - 1) + ")"); 141 loader.load(dataset, callback, new Date(prevBlockTime), currBlock - 1); 142 } 143 }); 144 // kill this function so that listener is only added once 145 addListener = false; 146 }; 147 148 /** 149 * Load data based on current time 150 * @name TimeMap.loaders.progressive#load 151 * @function 152 * @param {TimeMapDataset} dataset Dataset to load data into 153 * @param {Function} callback Callback to execute when data is loaded 154 * @param {Date} start Start date to load data from 155 * @param {Number} currBlock Index of the current time block 156 */ 157 loader.load = function(dataset, callback, start, currBlock) { 158 // set start date, defaulting to zero date 159 start = parseDate(start) || zeroDate; 160 // set current block, defaulting to 0 161 currBlock = currBlock || 0; 162 // set end by interval 163 var end = new Date(start.getTime() + interval); 164 165 // set current block as loaded 166 // XXX: Failed loads will give a false positive here... 167 // but I'm not sure how else to avoid multiple loads :( 168 loaded[currBlock] = true; 169 170 // put dates into URL 171 loader.opts.url = formatUrl(baseUrl, start, end); 172 173 // load data 174 baseLoadFunction.call(loader, dataset, function() { 175 // add onscroll listener if not yet done 176 if (addListener) { 177 addListener(dataset); 178 } 179 // run callback 180 callback(); 181 }); 182 }; 183 184 return loader; 185 }; 186