W9D3: Hacking Site To Size
I felt like today was a wealth of adding hacky features to my website. There were many styling issues, some of which I just ended up solving with JavaScript functions (suboptimal). I think I'm also getting the hang of bootstrap and good CSS principles, I'll be spending a portion of tomorrow just going through my website and removing the sub-optimal styling that I'd implemented previously.
Other than that, I've gotten a lot of features implemented today-- making photos inherit an album's location if it does not have a location of it's own, the search feature, the map view on everything, the log-in/sign-up page styling, and the guest account log-in.
There was an issue where Google Maps would not render properly-- it would zoom in on the wrong location and then only load the upper left corner of the map. The map would properly load if the page was resized. Turns out this was an issue with how I had my swapview set up and when I was rendering my page. Basically, render each page only once, and render Google maps only once it's finished loading.
// app/assets/javascripts/routers/user_data_router.js _swapview: function (newView) { this._oldview && this._oldview.remove(); this._oldview = newView; //Put element on page before rendering this.$rootEl.html(newView.$el); newView.render(); Instacation.resize(); } // app/assets/javascripts/views/maps/map_item.js // Only render map once it's loaded google.maps.event.addListenerOnce(this._map, 'idle', this.zoomOrResize.bind(this));
My footer would just not sit flush to the bottom of the page. After much tinkering, I finally used a combination of CSS and Javascript to get the behavior I wanted. The problem was that even if the footer sat at the bottom of the page, sometimes the html on the page was smaller than the actual window (due to use of modals).
// app/assets/javascripts/instacation.js // I run this upon initializing the Instaction object and window resize changeHeightForFooter: function () { var docHeight = $(window).height(); if ($(".modal-dialog").length !== 0 ) var modalHeight = $(".modal-dialog").height() + $(".modal-dialog").offset().top ; var footerHeight = $('footer').height(); var footerTop = $('footer').position().top + footerHeight; if (footerTop < docHeight) { $('footer').css('margin-top', 10 + (docHeight - footerTop) + 'px'); } else if (footerTop < modalHeight) { $('footer').css('margin-top', 100 + (modalHeight - footerTop) + 'px'); } }, // app/assets/stylesheets/application.css.scss // I'm not sure which of these setting were relevant, but the combination did the trick! html, .container, #main, .main, .main > .row { height: 100%; width: 100%; } body { padding-top: 50px; width: 100%; height: 100%; overflow-y: hidden; } .backdrop { position: relative; height: 100%; width: 100%; } .navbar { padding-left: 50px; padding-right: 50px; } .navbar-bottom { width: 100%; margin-bottom: 0; }
In a similar vein, I modified a JavaScript function I found online so that it would resize my font to fit within an element that had a defined height.
// app/assets/javascripts/instacation.js // I run this upon initializing the Instaction object and window resize resize: function () { $('.fitable-text').each(function (index, textArea) { while( $(textArea).height() > $($('.fit-container')[index]).height() ) { $(textArea).css('font-size', (parseInt($(textArea).css('font-size')) - 1) + "px" ); } }); },
One large time-sink today was manipulating my MapsItem view so that it can recognize and distinguish between the photo's location and the album's location. When there is no photo location defined, the photo's location should default to the album.
One little feature that was nice to implement was closing previously opened Google Map infowindows when a new one is opened either through a hover event on the images or a click event on the map's marker. That was completed by storing a "lastOpened" marker in the User show or Album show page, and then updating it whenever an event is triggered.
Google Maps apparently only allows 10 queries per second, which I was exceeding for no good reason when I queried the database every time I needed to retrieve a parent album's location for the photo. To solve this problem, I first stored the google place_id as a key in a hash, with the values pointing to all the photo items or album items that have that place id. Then, I had a separate hash that stored the place_id and the google-place object. After that, it was just a matter of going through the hash and adding a marker for each of the locations.
Today, I finally added a guest account to my webpage. By clicking on "guest login", the form inputs are deactivated and they user gets to watch a phantom user fill in the user name, password, and signin to the website.
// app/assets/javascripts/instacation.js // There's an event listener that will trigger the guestLogin function upon clicking the guest login button guestLogIn: function () { //collects the username and password fields $username = $("#username"); $password = $("#password"); //makes the fields readonly (as opposed to disabled, in which case the input will not store inserted values) $username.attr("readonly", "readonly"); $password.attr("readonly", "readonly"); $button = $("#signin-button"); //initiate typewriter method which will pretend to type out the username, then password, then click the signin button this.typewriter($username, "worldtraveler", function () { this.typewriter($password, "testtest", function () { $button.click(); }); }.bind(this)); }, typewriter: function ($el, string, fn) { var stringLength = string.length; var char = 0; $el.val("|"); this.type(char, string, $el, fn); }, type: function (char, content, $el, fn) { if (char < content.length) { var typingSpeed = Math.round(Math.random() * 120) + 10; setTimeout(function(){ char++; var type = content.substring(0, char); $el.val(type + "|"); this.type(char, content, $el, fn); }.bind(this), typingSpeed); } else { $el.val(content) fn.call(); } },
I was being lazy and didn't feel like creating a custom search route and possibly installing gems to help return search results. So instead, I implemented the search on the current user's page by assigning a bunch of classes (state, city, and country) to each album-item. When a user selects a location, all other album-items are hidden by appending a "hidden" class to album-items that do not have the location class specified.
Attach button for creating new albums and new photos to the navbar.
Put all the form on modals.
Add a pretty header to user page. This might just consist of removing the user name and instead making the location selector more prominant.
Hide markers for hidden locations.
Maybe update map upon updating album/photo location. This seems like a pain, but could be interesting.
I really want to add the ability to share links to albums with others.
Fix view of albums with only one location? I've solved this problem already, but sometimes the map won't zoom properly for some reason.
Add pagination? This will mess up the current implementation of instant-location search, but it's worth looking into since Google Maps will reject more than 10 queries a second.
Consider making links from location descriptions that show all photos/albums in the same name/state/country
If there's time, add search for picture captions
If there's time, solve the problem of the disappearing navbar in Firefox.