got qualified browse working, just needs style

This commit is contained in:
tmont 2011-02-16 10:11:55 +00:00
parent 4b877df2d9
commit 973b8196d9
16 changed files with 125 additions and 56 deletions

View File

@ -13,12 +13,12 @@
<many-to-one name="Creator" column="creator" not-null="true" foreign-key="fk_game_user"/>
<set access="field" name="systems" table="game_system_map" cascade="save-update">
<set access="nosetter.camelcase" name="Systems" table="game_system_map" cascade="save-update">
<key column="game_id" />
<many-to-many class="VideoGameQuotes.Api.GamingSystem" column="system_id" />
</set>
<set access="field" name="publishers" table="game_publisher_map" cascade="save-update">
<set access="nosetter.camelcase" name="Publishers" table="game_publisher_map" cascade="save-update">
<key column="game_id" />
<many-to-many class="VideoGameQuotes.Api.Publisher" column="publisher_id" />
</set>

View File

@ -13,17 +13,17 @@
<many-to-one name="Creator" column="creator" not-null="true" foreign-key="fk_quote_user"/>
<many-to-one name="Game" column="game_id" not-null="true" foreign-key="fk_quote_game" cascade="save-update" />
<set access="field" name="categories" table="quote_category_map" cascade="save-update">
<set access="nosetter.camelcase" name="Categories" table="quote_category_map" cascade="save-update">
<key column="quote_id" />
<many-to-many class="VideoGameQuotes.Api.Category" column="category_id" />
</set>
<set access="field" name="votes" table="vote" cascade="save-update">
<set access="nosetter.camelcase" name="Votes" table="vote" cascade="save-update">
<key column="quote_id" />
<one-to-many class="VideoGameQuotes.Api.Vote" />
</set>
<set access="field" name="flags" table="quote_flag" cascade="save-update">
<set access="nosetter.camelcase" name="Flags" table="quote_flag" cascade="save-update">
<key column="quote_id" />
<one-to-many class="VideoGameQuotes.Api.QuoteFlag" />
</set>

View File

@ -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<Quote> {
protected override Func<Quote, bool> HandleInteger(int value) {
return quote => quote.Game.Id == value;
}
}
#endregion
}
public class QuoteDto {

View File

@ -30,7 +30,9 @@ namespace VideoGameQuotes.Web.Controllers {
private readonly IDictionary<string, CriterionHandler<Category>> categoryHandlers = new Dictionary<string, CriterionHandler<Category>>();
private readonly IDictionary<string, CriterionHandler<GamingSystem>> systemHandlers = new Dictionary<string, CriterionHandler<GamingSystem>>();
private readonly IDictionary<string, CriterionHandler<Publisher>> publisherHandlers = new Dictionary<string, CriterionHandler<Publisher>>();
private readonly IDictionary<string, CriterionHandler<Quote>> quoteHandlers = new Dictionary<string, CriterionHandler<Quote>>();
private readonly IDictionary<string, CriterionHandler<Quote>> quoteHandlers = new Dictionary<string, CriterionHandler<Quote>> {
{ "game", new Quote.GameCriterionHandler() }
};
private readonly IDictionary<string, CriterionHandler<Game>> gameHandlers = new Dictionary<string, CriterionHandler<Game>> {
{ "system", new Game.SystemCriterionHandler() },
{ "publisher", new Game.PublisherCriterionHandler() }

View File

@ -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]

View File

@ -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 });
}
}
}

View File

@ -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<object> ParseCommaSeparatedCriterion(string csv) {
var values = csv.Split(',');
var results = new List<object>();
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) {

View File

@ -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<int>();
}
public User User { get; set; }
public IEnumerable<int> GameIds { get; set; }
public IEnumerable<int> SystemIds { get; set; }
public IEnumerable<int> CategoryIds { get; set; }
public IEnumerable<int> 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 {

View File

@ -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;

View File

@ -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<Game>();
Systems = Enumerable.Empty<GamingSystem>();
Categories = Enumerable.Empty<Category>();
Publishers = Enumerable.Empty<Publisher>();
}
public IEnumerable<Game> Games { get; set; }
public IEnumerable<GamingSystem> Systems { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Publisher> Publishers { get; set; }
}
}

View File

@ -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<Quote> Quotes { get; set; }
}
}

View File

@ -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<Quote> GetBestQuotes(int start, int end);
Vote SaveVote(Vote vote);
Vote GetVoteOrCreateNew(Quote quote, User voter);
IEnumerable<Quote> GetBrowsableQuotes(BrowseModel model);
}
public class QuoteService : IQuoteService {
@ -147,5 +149,24 @@ namespace VideoGameQuotes.Web.Services {
Voter = voter
};
}
[UnitOfWork]
public IEnumerable<Quote> 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;
}
}
}

View File

@ -89,7 +89,7 @@
<Compile Include="Models\ApiModel.cs" />
<Compile Include="Models\BrowseModel.cs" />
<Compile Include="Models\BrowseModelBinder.cs" />
<Compile Include="Models\BrowseViewModel.cs" />
<Compile Include="Models\QualifiedBrowseModel.cs" />
<Compile Include="Models\PagedQuoteCollectionModel.cs" />
<Compile Include="Models\QuoteCollectionModel.cs" />
<Compile Include="Models\QuoteModel.cs" />

View File

@ -63,6 +63,17 @@
};
}();
var systemCellRenderer = function() {
var $template = $("<a/>");
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;
});
});
//]]></script>
</asp:Content>

View File

@ -1,8 +1,15 @@
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<VideoGameQuotes.Web.Models.BrowseViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<VideoGameQuotes.Web.Models.QualifiedBrowseModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
<%@ Import Namespace="VideoGameQuotes.Web.Models" %>
<asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent">Browse</asp:Content>
<asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent">
<p>
This is the qualified browse view.
Here are some quotes
</p>
<%
foreach (var quote in Model.Quotes) {
Html.RenderPartial("SingleQuote", new QuoteModel { Quote = quote, User = Model.User });
}
%>
</asp:Content>

View File

@ -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;