diff --git a/Src/VideoGameQuotes.Api/Mappings/Game.hbm.xml b/Src/VideoGameQuotes.Api/Mappings/Game.hbm.xml index 7aa98ac..bd01d88 100644 --- a/Src/VideoGameQuotes.Api/Mappings/Game.hbm.xml +++ b/Src/VideoGameQuotes.Api/Mappings/Game.hbm.xml @@ -13,12 +13,12 @@ - + - + diff --git a/Src/VideoGameQuotes.Api/Mappings/Quote.hbm.xml b/Src/VideoGameQuotes.Api/Mappings/Quote.hbm.xml index d11f525..1c94ec9 100644 --- a/Src/VideoGameQuotes.Api/Mappings/Quote.hbm.xml +++ b/Src/VideoGameQuotes.Api/Mappings/Quote.hbm.xml @@ -13,17 +13,17 @@ - + - + - + diff --git a/Src/VideoGameQuotes.Api/Quote.cs b/Src/VideoGameQuotes.Api/Quote.cs index 4e7e57c..a8e629d 100644 --- a/Src/VideoGameQuotes.Api/Quote.cs +++ b/Src/VideoGameQuotes.Api/Quote.cs @@ -82,6 +82,7 @@ namespace VideoGameQuotes.Api { public virtual int UpVotes { get { return Votes.Count(vote => vote.Direction == VoteDirection.Up); } } public virtual int DownVotes { get { return Votes.Count(vote => vote.Direction == VoteDirection.Down); } } public virtual int Score { get { return Votes.Sum(vote => (int)vote); } } + public virtual QuoteDto ToDto() { return new QuoteDto { Id = Id, @@ -95,6 +96,14 @@ namespace VideoGameQuotes.Api { Score = Score }; } + + #region nested types + public class GameCriterionHandler : CriterionHandler { + protected override Func HandleInteger(int value) { + return quote => quote.Game.Id == value; + } + } + #endregion } public class QuoteDto { diff --git a/Src/VideoGameQuotes.Web/Controllers/ApiController.cs b/Src/VideoGameQuotes.Web/Controllers/ApiController.cs index 3ac35d4..cc5a1ac 100644 --- a/Src/VideoGameQuotes.Web/Controllers/ApiController.cs +++ b/Src/VideoGameQuotes.Web/Controllers/ApiController.cs @@ -30,7 +30,9 @@ namespace VideoGameQuotes.Web.Controllers { private readonly IDictionary> categoryHandlers = new Dictionary>(); private readonly IDictionary> systemHandlers = new Dictionary>(); private readonly IDictionary> publisherHandlers = new Dictionary>(); - private readonly IDictionary> quoteHandlers = new Dictionary>(); + private readonly IDictionary> quoteHandlers = new Dictionary> { + { "game", new Quote.GameCriterionHandler() } + }; private readonly IDictionary> gameHandlers = new Dictionary> { { "system", new Game.SystemCriterionHandler() }, { "publisher", new Game.PublisherCriterionHandler() } diff --git a/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs b/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs index b5b2f15..0afc67b 100644 --- a/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs +++ b/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs @@ -23,26 +23,11 @@ namespace VideoGameQuotes.Web.Controllers { } public ActionResult Browse(BrowseModel model) { - var viewModel = new BrowseViewModel(); - - if (model.GameIds.Any()) { - viewModel.Games = model.GameIds.Select(id => quoteService.GetGame(id)); - } - if (model.SystemIds.Any()) { - viewModel.Systems = model.SystemIds.Select(id => quoteService.GetSystem(id)); - } - if (model.PublisherIds.Any()) { - viewModel.Publishers = model.PublisherIds.Select(id => quoteService.GetPublisher(id)); - } - if (model.CategoryIds.Any()) { - viewModel.Categories = model.CategoryIds.Select(id => quoteService.GetCategory(id)); - } - - if (!viewModel.Games.Any() && !viewModel.Systems.Any() && !viewModel.Categories.Any() && !viewModel.Publishers.Any()) { + if (model.IsEmpty) { return View("DefaultBrowse"); } - return View("QualifiedBrowse", viewModel); + return View("QualifiedBrowse", new QualifiedBrowseModel(model) { Quotes = quoteService.GetBrowsableQuotes(model) }); } [HttpPost, IsValidUser] diff --git a/Src/VideoGameQuotes.Web/Global.asax.cs b/Src/VideoGameQuotes.Web/Global.asax.cs index dfff90b..4a9362b 100644 --- a/Src/VideoGameQuotes.Web/Global.asax.cs +++ b/Src/VideoGameQuotes.Web/Global.asax.cs @@ -44,7 +44,7 @@ namespace VideoGameQuotes.Web { routes.IgnoreRoute("media/{*anything}"); - routes.MapRoute("api", "api/{action}/{id}/{*criteria}", new { controller = "Api" }, new { action = "game", id = @"\d+|all" }); + routes.MapRoute("api", "api/{action}/{id}/{*criteria}", new { controller = "Api" }, new { action = "game|system|category|publisher|quote", id = @"\d+|all" }); routes.MapRoute("home", "{action}", new { controller = "Home", action = "Index" }, new { action = "about|contact" }); @@ -53,8 +53,6 @@ namespace VideoGameQuotes.Web { routes.MapRoute("quote", "{action}", new { controller = "Quote" }, new { action = "submit|search|recent|random|best|vote|report" }); routes.MapRoute("individual-quote", "quote/{id}/{*text}", new { controller = "Quote", action = "Quote" }, new { id = @"\d+" }); routes.MapRoute("create-category", "category/create", new { controller = "Quote", action = "CreateCategory" }); - - routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }); } } } \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Models/ApiModel.cs b/Src/VideoGameQuotes.Web/Models/ApiModel.cs index 0dc7bd6..6850af9 100644 --- a/Src/VideoGameQuotes.Web/Models/ApiModel.cs +++ b/Src/VideoGameQuotes.Web/Models/ApiModel.cs @@ -35,17 +35,14 @@ namespace VideoGameQuotes.Web.Models { for (var i = 0; i < criteria.Length; i++) { switch (criteria[i]) { - case "system": + default: if (i < criteria.Length - 1) { - model.Criteria["system"] = ParseCommaSeparatedCriterion(criteria[i + 1]); + model.Criteria[criteria[i]] = ParseCommaSeparatedCriterion(criteria[i + 1]); i++; } else { controllerContext.AddModelError("criteria", "Unable to parse criteria for key \"system\""); } - break; - default: - controllerContext.AddModelError("criteria", string.Format("Unable to parse criteria for key \"{0}\"", criteria[i])); break; } } @@ -54,14 +51,17 @@ namespace VideoGameQuotes.Web.Models { private static IEnumerable ParseCommaSeparatedCriterion(string csv) { var values = csv.Split(','); + var results = new List(); foreach (var value in values) { int result; if (int.TryParse(value, out result)) { - yield return result; + results.Add(result); } else { - yield return value; + results.Add(value); } } + + return results; } private static void ParseSort(ControllerContext controllerContext, ApiModel model) { diff --git a/Src/VideoGameQuotes.Web/Models/BrowseModel.cs b/Src/VideoGameQuotes.Web/Models/BrowseModel.cs index 2168cfa..c4435bb 100644 --- a/Src/VideoGameQuotes.Web/Models/BrowseModel.cs +++ b/Src/VideoGameQuotes.Web/Models/BrowseModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using VideoGameQuotes.Api; namespace VideoGameQuotes.Web.Models { public class BrowseModel { @@ -10,12 +11,18 @@ namespace VideoGameQuotes.Web.Models { PublisherIds = Enumerable.Empty(); } + public User User { get; set; } + public IEnumerable GameIds { get; set; } public IEnumerable SystemIds { get; set; } public IEnumerable CategoryIds { get; set; } public IEnumerable PublisherIds { get; set; } public SortMethod SortMethod { get; set; } public SortOrder SortOrder { get; set; } + + public bool IsEmpty { + get { return !GameIds.Any() && !SystemIds.Any() && !CategoryIds.Any() && !PublisherIds.Any(); } + } } public enum SortMethod { diff --git a/Src/VideoGameQuotes.Web/Models/BrowseModelBinder.cs b/Src/VideoGameQuotes.Web/Models/BrowseModelBinder.cs index c325ece..83d0cc7 100644 --- a/Src/VideoGameQuotes.Web/Models/BrowseModelBinder.cs +++ b/Src/VideoGameQuotes.Web/Models/BrowseModelBinder.cs @@ -24,7 +24,7 @@ namespace VideoGameQuotes.Web.Models { case "category": case "publisher": if (i < segments.Length - 1) { - var ids = ParseCommaSeparatedIntegers(segments[i + 1]); + var ids = ParseCommaSeparatedIntegers(segments[i + 1]).ToArray(); //nhibernate requires non-iterator switch (segments[i]) { case "game": model.GameIds = ids; diff --git a/Src/VideoGameQuotes.Web/Models/BrowseViewModel.cs b/Src/VideoGameQuotes.Web/Models/BrowseViewModel.cs deleted file mode 100644 index d6dadce..0000000 --- a/Src/VideoGameQuotes.Web/Models/BrowseViewModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using VideoGameQuotes.Api; - -namespace VideoGameQuotes.Web.Models { - public class BrowseViewModel { - public BrowseViewModel() { - Games = Enumerable.Empty(); - Systems = Enumerable.Empty(); - Categories = Enumerable.Empty(); - Publishers = Enumerable.Empty(); - } - - public IEnumerable Games { get; set; } - public IEnumerable Systems { get; set; } - public IEnumerable Categories { get; set; } - public IEnumerable Publishers { get; set; } - } -} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Models/QualifiedBrowseModel.cs b/Src/VideoGameQuotes.Web/Models/QualifiedBrowseModel.cs new file mode 100644 index 0000000..049e5c8 --- /dev/null +++ b/Src/VideoGameQuotes.Web/Models/QualifiedBrowseModel.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using VideoGameQuotes.Api; + +namespace VideoGameQuotes.Web.Models { + public class QualifiedBrowseModel : BrowseModel { + public QualifiedBrowseModel(BrowseModel model) { + CategoryIds = model.CategoryIds; + GameIds = model.GameIds; + PublisherIds = model.PublisherIds; + SortMethod = model.SortMethod; + SystemIds = model.SystemIds; + SortOrder = model.SortOrder; + } + + public IEnumerable Quotes { get; set; } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Services/QuoteService.cs b/Src/VideoGameQuotes.Web/Services/QuoteService.cs index d6895d6..1422814 100644 --- a/Src/VideoGameQuotes.Web/Services/QuoteService.cs +++ b/Src/VideoGameQuotes.Web/Services/QuoteService.cs @@ -4,6 +4,7 @@ using System.Linq; using JetBrains.Annotations; using Portoa.Persistence; using VideoGameQuotes.Api; +using VideoGameQuotes.Web.Models; namespace VideoGameQuotes.Web.Services { public interface IQuoteService { @@ -24,6 +25,7 @@ namespace VideoGameQuotes.Web.Services { IEnumerable GetBestQuotes(int start, int end); Vote SaveVote(Vote vote); Vote GetVoteOrCreateNew(Quote quote, User voter); + IEnumerable GetBrowsableQuotes(BrowseModel model); } public class QuoteService : IQuoteService { @@ -147,5 +149,24 @@ namespace VideoGameQuotes.Web.Services { Voter = voter }; } + + [UnitOfWork] + public IEnumerable GetBrowsableQuotes(BrowseModel model) { + var quotes = quoteRepository.Records; + if (model.GameIds.Any()) { + quotes = quotes.Where(quote => model.GameIds.Contains(quote.Game.Id)); + } + if (model.PublisherIds.Any()) { + quotes = quotes.Where(quote => quote.Game.Publishers.Any(publisher => model.PublisherIds.Contains(publisher.Id))); + } + if (model.SystemIds.Any()) { + quotes = quotes.Where(quote => quote.Game.Systems.Any(system => model.SystemIds.Contains(system.Id))); + } + if (model.CategoryIds.Any()) { + quotes = quotes.Where(quote => quote.Categories.Any(category => model.CategoryIds.Contains(category.Id))); + } + + return quotes; + } } } \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj b/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj index 05a5e6e..53e7486 100644 --- a/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj +++ b/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj @@ -89,7 +89,7 @@ - + diff --git a/Src/VideoGameQuotes.Web/Views/Quote/DefaultBrowse.aspx b/Src/VideoGameQuotes.Web/Views/Quote/DefaultBrowse.aspx index 4930c5e..a4cad2d 100644 --- a/Src/VideoGameQuotes.Web/Views/Quote/DefaultBrowse.aspx +++ b/Src/VideoGameQuotes.Web/Views/Quote/DefaultBrowse.aspx @@ -63,6 +63,17 @@ }; }(); + var systemCellRenderer = function() { + var $template = $(""); + return function(system, count) { + return $template + .clone() + .attr("href", "/browse/system/" + system.Id) + .attr("title", system.Name) + .text(system.Abbreviation); + }; + }(); + $("#show-default-menu").click(function() { $content.empty(); $container.hide(); @@ -94,6 +105,31 @@ return false; }); + + $("#browse-system").click(function() { + $browseMenu.hide(); + $container.show(); + var cellsPerRow = 12; + + if (systems.length === 0) { + $.ajax("/api/system/all", { + data: { sort: "alphabetical" }, + success: function(data, status, $xhr) { + if (data.Error !== null) { + alert(data.Error); + return; + } + + systems = data.Data.systems; + renderData(systems, systemCellRenderer, cellsPerRow); + } + }); + } else { + renderData(systems, systemCellRenderer, cellsPerRow); + } + + return false; + }); }); //]]> diff --git a/Src/VideoGameQuotes.Web/Views/Quote/QualifiedBrowse.aspx b/Src/VideoGameQuotes.Web/Views/Quote/QualifiedBrowse.aspx index c995d51..d8e5932 100644 --- a/Src/VideoGameQuotes.Web/Views/Quote/QualifiedBrowse.aspx +++ b/Src/VideoGameQuotes.Web/Views/Quote/QualifiedBrowse.aspx @@ -1,8 +1,15 @@ -<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Import Namespace="VideoGameQuotes.Web.Models" %> Browse

- This is the qualified browse view. + Here are some quotes

+ + <% + foreach (var quote in Model.Quotes) { + Html.RenderPartial("SingleQuote", new QuoteModel { Quote = quote, User = Model.User }); + } + %>
\ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/media/css/global.css b/Src/VideoGameQuotes.Web/media/css/global.css index 3703d29..d102a0c 100644 --- a/Src/VideoGameQuotes.Web/media/css/global.css +++ b/Src/VideoGameQuotes.Web/media/css/global.css @@ -122,6 +122,12 @@ ul.menu li { #browse-default-container { display: none; } +#browse-default-content table { + width: 100%; +} +#browse-default-content td { + padding: 5px; +} #header { background-color: #669966;