PostLoader = function() { document.on("viewer:need-more-thumbs", this.need_more_post_data.bindAsEventListener(this)); document.on("viewer:perform-search", this.perform_search.bindAsEventListener(this)); this.hashchange_tags = this.hashchange_tags.bind(this); UrlHash.observe("tags", this.hashchange_tags); this.cached_posts = new Hash(); this.cached_pools = new Hash(); this.sample_preload_container = null; this.preloading_sample_for_post_id = null; this.load({results_mode: "center-on-current"}); } PostLoader.prototype.need_more_post_data = function() { /* We'll receive this message often once we're close to needing more posts. Only * start loading more data the first time. */ if(this.loaded_extended_results) return; this.load({extending: true}); } /* * This is a response time optimization. If we know the sample URL of what we want to display, * we can start loading it from the server without waiting for the full post.json response * to come back and tell us. This saves us the time of a round-trip before we start loading the * image. The common case is if the user was on post and clicked on a link with "use * post browser" enabled. This allows us to start loading the image immediately, without waiting * for any other network activity. * * We only do this for the sample image, to get a head-start loading it. This is safe because * the image URLs are immutable (or effectively so). The rest of the post information isn't cached. */ PostLoader.prototype.preload_sample_image = function() { var post_id = UrlHash.get("post-id"); if(this.preloading_sample_for_post_id == post_id) return; this.preloading_sample_for_post_id = post_id; if(this.sample_preload_container) { this.sample_preload_container.destroy(); this.sample_preload_container = null; } if(post_id == null) return; /* If this returns null, the browser doesn't support this. */ var cached_sample_urls = Post.get_cached_sample_urls(); if(cached_sample_urls == null) return; if(!(String(post_id) in cached_sample_urls)) return; var sample_url = cached_sample_urls[String(post_id)]; /* If we have an existing preload_container, just add to it and allow any other * preloads to continue. */ debug("Advance preloading sample image for post " + post_id); this.sample_preload_container = new PreloadContainer(); this.sample_preload_container.preload(sample_url); } PostLoader.prototype.server_load_pool = function() { if(this.result.pool_id == null) return; if(!this.result.disable_cache) { var pool = this.cached_pools.get(this.result.pool_id); if(pool) { this.result.pool = pool; this.request_finished(); return; } } new Ajax.Request("/pool/show.json", { parameters: { id: this.result.pool_id }, method: "get", onCreate: function(resp) { this.current_ajax_requests.push(resp.request); }.bind(this), onComplete: function(resp) { this.current_ajax_requests = this.current_ajax_requests.without(resp.request); this.request_finished(); }.bind(this), onSuccess: function(resp) { if(this.current_ajax_requests.indexOf(resp.request) == -1) return; this.result.pool = resp.responseJSON; this.cached_pools.set(this.result.pool_id, this.result.pool); }.bind(this) }); } PostLoader.prototype.server_load_posts = function() { var tags = this.result.tags; // Put holds:false at the beginning, so the search can override it. Put limit: at // the end, so it can't. var search = "holds:false " + tags + " limit:" + this.result.post_limit; if(!this.result.disable_cache) { var results = this.cached_posts.get(search); if(results) { this.result.posts = results; /* Don't Post.register the results when serving out of cache. They're already * registered, and the data in the post registry may be more current than the * cached search results. */ this.request_finished(); return; } } new Ajax.Request("/post.json", { parameters: { tags: search, api_version: 2, filter: 1, include_tags: 1, include_votes: 1, include_pools: 1 }, method: "get", onCreate: function(resp) { this.current_ajax_requests.push(resp.request); }.bind(this), onComplete: function(resp) { this.current_ajax_requests = this.current_ajax_requests.without(resp.request); this.request_finished(); }.bind(this), onSuccess: function(resp) { if(this.current_ajax_requests.indexOf(resp.request) == -1) return; var resp = resp.responseJSON; this.result.posts = resp.posts; Post.register_resp(resp); this.cached_posts.set(search, this.result.posts); }.bind(this), onFailure: function(resp) { var error = "error " + resp.status; if(resp.responseJSON) error = resp.responseJSON.reason; notice("Error loading posts: " + error); this.result.error = true; }.bind(this) }); } PostLoader.prototype.request_finished = function() { if(this.current_ajax_requests.length) return; /* Event handlers for the events we fire below might make requests back to us. Save and * clear this.result before firing the events, so that behaves properly. */ var result = this.result; this.result = null; /* If server_load_posts hit an error, it already displayed it; stop. */ if(result.error != null) return; /* If we have no search tags (result.tags == null, result.posts == null), then we're just * displaying a post with no search, eg. "/post/browse#12345". We'll still fire off the * same code path to make the post display in the view. */ var new_post_ids = []; if(result.posts != null) { for(var i = 0; i < result.posts.length; ++i) new_post_ids.push(result.posts[i].id); } document.fire("viewer:displayed-pool-changed", { pool: result.pool }); document.fire("viewer:searched-tags-changed", { tags: result.tags }); /* Tell the thumbnail viewer whether it should allow scrolling over the left side. */ var can_be_extended_further = true; /* If we're reading from a pool, we requested a large block already. */ if(result.pool) can_be_extended_further = false; /* If we're already extending, don't extend further. */ if(result.load_options.extending) can_be_extended_further = false; /* If we received fewer results than we requested we're at the end of the results, * so don't waste time requesting more. */ if(new_post_ids.length < result.post_limit) { debug("Received posts fewer than requested (" + new_post_ids.length + " < " + result.post_limit + "), clamping"); can_be_extended_further = false; } /* Now that we have the result, update the URL hash. Firing loaded-posts may change * the displayed post, causing the post ID in the URL hash to change, so use set_deferred * to help ensure these happen atomically. */ UrlHash.set_deferred({tags: result.tags}); document.fire("viewer:loaded-posts", { tags: result.tags, /* this will be null if no search was actually performed (eg. URL with a post-id and no tags) */ post_ids: new_post_ids, pool: result.pool, extending: result.load_options.extending, can_be_extended_further: can_be_extended_further, load_options: result.load_options }); } /* If extending is true, load a larger set of posts. */ PostLoader.prototype.load = function(load_options) { if(!load_options) load_options = {} var disable_cache = load_options.disable_cache; var extending = load_options.extending; var tags = load_options.tags; if(tags == null) tags = UrlHash.get("tags"); /* If neither a search nor a post-id is specified, set a default search. */ if(!extending && tags == null && UrlHash.get("post-id") == null) { UrlHash.set({tags: ""}); /* We'll receive another hashchange message for setting "tags". Don't load now or we'll * end up loading twice. */ return; } debug("PostLoader.load(" + extending + ", " + disable_cache + ")"); this.preload_sample_image(); this.loaded_extended_results = extending; /* Discard any running AJAX requests. */ this.current_ajax_requests = []; this.result = {}; this.result.load_options = load_options; this.result.tags = tags; this.result.disable_cache = disable_cache; if(this.result.tags == null) { /* If no search is specified, don't run one; return empty results. */ this.request_finished(); return; } /* See if we have a pool search. This only checks for pool:id searches, not pool:*name* searches; * we want to know if we're displaying posts only from a single pool. */ var pool_id = null; this.result.tags.split(" ").each(function(tag) { var m = tag.match(/^pool:(\d+)/); if(!m) return; pool_id = parseInt(m[1]); }); /* If we're loading from a pool, load the pool's data. */ this.result.pool_id = pool_id; /* Load the posts to display. If we're loading a pool, load all posts (up to 1000); * otherwise set a limit. */ var limit = extending? 1000:100; if(pool_id != null) limit = 1000; this.result.post_limit = limit; /* Make sure that request_finished doesn't consider this request complete until we've * actually started every request. */ this.current_ajax_requests.push(null); this.server_load_pool(); this.server_load_posts(); this.current_ajax_requests = this.current_ajax_requests.without(null); this.request_finished(); } PostLoader.prototype.hashchange_tags = function() { var tags = UrlHash.get("tags"); if(tags == this.last_seen_tags) return; this.last_seen_tags = tags; debug("changed tags"); this.load(); } PostLoader.prototype.perform_search = function(event) { var tags = event.memo.tags; this.last_seen_tags = tags; var results_mode = event.memo.results_mode || "center-on-first"; debug("do search: " + tags); this.load({tags: tags, results_mode: results_mode}); }