* submit quote is a little bit sexier (still needs some work)

* added the loading gif when you vote on a quote
This commit is contained in:
tmont 2011-02-23 01:30:53 +00:00
parent 5cb296c978
commit d159a91208
16 changed files with 248 additions and 125 deletions

View File

@ -11,9 +11,9 @@
<property name="Modified" column="modified" not-null="false" type="DateTime" />
<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" />
<many-to-one name="Game" column="game_id" not-null="true" foreign-key="fk_quote_game" />
<set access="nosetter.camelcase" name="Categories" table="quote_category_map" cascade="save-update">
<set access="nosetter.camelcase" name="Categories" table="quote_category_map">
<key column="quote_id" />
<many-to-many class="VideoGameQuotes.Api.Category" column="category_id" />
</set>

View File

@ -0,0 +1,40 @@
using System.Web.Mvc;
using Portoa.Util;
using Portoa.Web;
using Portoa.Web.Controllers;
using VideoGameQuotes.Api;
using VideoGameQuotes.Web.Models;
using VideoGameQuotes.Web.Security;
using VideoGameQuotes.Web.Services;
namespace VideoGameQuotes.Web.Controllers {
public class GameController : Controller {
private readonly IGameService gameService;
private readonly ICurrentUserProvider<User> userProvider;
public GameController(IGameService gameService, ICurrentUserProvider<User> userProvider) {
this.gameService = gameService;
this.userProvider = userProvider;
}
[HttpPost, VerifyUser]
public ActionResult Create(CreateGameModel model) {
if (!ModelState.IsValid) {
return Json(this.CreateJsonErrorResponse("Some errors occurred."));
}
if (gameService.FindByNameAndSystems(model.GameName, model.SystemIds) != null) {
return Json(this.CreateJsonResponse("A game with that name for one of those systems already exists."));
}
var game = new Game { Name = model.GameName, Website = model.GameWebsite, Region = model.GameRegions, Creator = userProvider.CurrentUser };
model.SystemIds.Walk(id => game.AddSystem(new GamingSystem { Id = id }));
if (model.PublisherIds != null) {
model.PublisherIds.Walk(id => game.AddPublisher(new Publisher {Id = id}));
}
game = gameService.Save(game);
return Json(this.CreateJsonResponse(data: game.ToDto()));
}
}
}

View File

@ -0,0 +1,30 @@
using System.Web.Mvc;
using Portoa.Web.Controllers;
using VideoGameQuotes.Api;
using VideoGameQuotes.Web.Models;
using VideoGameQuotes.Web.Security;
using VideoGameQuotes.Web.Services;
namespace VideoGameQuotes.Web.Controllers {
public class PublisherController : Controller {
private readonly IPublisherService publisherService;
public PublisherController(IPublisherService publisherService) {
this.publisherService = publisherService;
}
[HttpPost, VerifyUser]
public ActionResult Create(CreatePublisherModel model) {
if (!ModelState.IsValid) {
return Json(this.CreateJsonErrorResponse("Some errors occurred."));
}
if (publisherService.FindByName(model.PublisherName) != null) {
return Json(this.CreateJsonResponse("A publisher with that name already exists."));
}
var publisher = publisherService.Save(new Publisher { Name = model.PublisherName, Website = model.PublisherWebsite });
return Json(this.CreateJsonResponse(data: publisher.ToDto()));
}
}
}

View File

@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Portoa.Util;
using Portoa.Web;
using Portoa.Web.Controllers;
using VideoGameQuotes.Api;
using VideoGameQuotes.Web.Models;
@ -10,59 +8,6 @@ using VideoGameQuotes.Web.Security;
using VideoGameQuotes.Web.Services;
namespace VideoGameQuotes.Web.Controllers {
public class GameController : Controller {
private readonly IGameService gameService;
private readonly ICurrentUserProvider<User> userProvider;
public GameController(IGameService gameService, ICurrentUserProvider<User> userProvider) {
this.gameService = gameService;
this.userProvider = userProvider;
}
[HttpPost, VerifyUser]
public ActionResult Create(CreateGameModel model) {
if (!ModelState.IsValid) {
return Json(this.CreateJsonErrorResponse("Invalid request"));
}
if (gameService.FindByNameAndSystems(model.Name, model.SystemIds) != null) {
return Json(this.CreateJsonResponse("A game with that name for one of those systems already exists"));
}
var game = new Game { Name = model.Name, Website = model.Website, Region = model.Region, Creator = userProvider.CurrentUser };
model.SystemIds.Walk(id => game.AddSystem(new GamingSystem { Id = id }));
if (model.PublisherIds != null) {
model.PublisherIds.Walk(id => game.AddPublisher(new Publisher {Id = id}));
}
game = gameService.Save(game);
return Json(this.CreateJsonResponse(data: game.ToDto()));
}
}
public class PublisherController : Controller {
private readonly IPublisherService publisherService;
public PublisherController(IPublisherService publisherService) {
this.publisherService = publisherService;
}
[HttpPost, VerifyUser]
public ActionResult Create(CreatePublisherModel model) {
if (!ModelState.IsValid) {
return Json(this.CreateJsonErrorResponse("Invalid request"));
}
if (publisherService.FindByName(model.Name) != null) {
return Json(this.CreateJsonResponse("A publisher with that name already exists"));
}
var publisher = publisherService.Save(new Publisher { Name = model.Name, Website = model.Website });
return Json(this.CreateJsonResponse(data: publisher.ToDto()));
}
}
public class SystemController : Controller {
private readonly ISystemService systemService;
@ -73,14 +18,14 @@ namespace VideoGameQuotes.Web.Controllers {
[HttpPost, VerifyUser]
public ActionResult Create(CreateSystemModel model) {
if (!ModelState.IsValid) {
return Json(this.CreateJsonErrorResponse("Invalid request"));
return Json(this.CreateJsonErrorResponse("Some errors occurred"));
}
if (systemService.FindByNameOrAbbreviation(model.Name, model.Abbreviation).Any()) {
return Json(this.CreateJsonResponse("A system with that name or abbreviation already exists"));
if (systemService.FindByNameOrAbbreviation(model.SystemName, model.SystemAbbreviation).Any()) {
return Json(this.CreateJsonResponse("A system with that name or abbreviation already exists."));
}
var system = systemService.Save(new GamingSystem { Abbreviation = model.Abbreviation, Name = model.Name, ReleaseDate = model.ReleaseDate });
var system = systemService.Save(new GamingSystem { Abbreviation = model.SystemAbbreviation, Name = model.SystemName, ReleaseDate = model.SystemReleaseDate });
return Json(this.CreateJsonResponse(data: system.ToDto()));
}
}

View File

@ -23,7 +23,7 @@ namespace VideoGameQuotes.Web {
protected override void ConfigureModelBinders(ModelBinderDictionary binders) {
binders
.Add<Region, FlagEnumModelBinder>()
.Add<Region, FlagEnumModelBinder<Region>>()
.Add<BrowseModel, BrowseModelBinder>()
.Add<ApiModel, ApiModelBinder>();
}

View File

@ -4,12 +4,16 @@ using VideoGameQuotes.Api;
namespace VideoGameQuotes.Web.Models {
public class CreateGameModel {
[Required]
public string Name { get; set; }
public string Website { get; set; }
public Region Region { get; set; }
[Required]
public CreateGameModel() {
GameRegions = Region.Unknown;
}
[Required(ErrorMessage = "The game must have a name, idiot")]
public string GameName { get; set; }
public string GameWebsite { get; set; }
public Region GameRegions { get; set; }
public List<int> PublisherIds { get; set; }
[Required(ErrorMessage = "At least one system must be specified, uglyface")]
public List<int> SystemIds { get; set; }
}
}

View File

@ -1,6 +1,9 @@
namespace VideoGameQuotes.Web.Models {
using System.ComponentModel.DataAnnotations;
namespace VideoGameQuotes.Web.Models {
public class CreatePublisherModel {
public string Name { get; set; }
public string Website { get; set; }
[Required(ErrorMessage = "Publishers must have a name, douchebag")]
public string PublisherName { get; set; }
public string PublisherWebsite { get; set; }
}
}

View File

@ -3,11 +3,11 @@ using System.ComponentModel.DataAnnotations;
namespace VideoGameQuotes.Web.Models {
public class CreateSystemModel {
[Required]
public string Name { get; set; }
[Required, StringLength(12)]
public string Abbreviation { get; set; }
[Required]
public DateTime ReleaseDate { get; set; }
[Required(ErrorMessage = "Systems must have a name, fatty")]
public string SystemName { get; set; }
[Required(ErrorMessage = "Abbreviation is required, ugly"), StringLength(12, ErrorMessage = "Abbreviations cannot be longer than 12 characters, flabcakes")]
public string SystemAbbreviation { get; set; }
[Required(ErrorMessage = "How about giving an accurate release date (try Wikipedia), nardlover?")]
public DateTime SystemReleaseDate { get; set; }
}
}

View File

@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
using Portoa.Validation.DataAnnotations;
using VideoGameQuotes.Api;
using VideoGameQuotes.Web.Validation;
namespace VideoGameQuotes.Web.Models {
@ -29,11 +30,14 @@ namespace VideoGameQuotes.Web.Models {
public int QuoteId { get; set; }
public List<QuoteFlag> Flags { get; set; }
[NonEmptyText(ErrorMessage = "Quote text must be non-empty"), DisplayName("Quote")]
[Required(ErrorMessage = "Quote text must be non-empty, saddlebags")]
[StringLength(1024, ErrorMessage = "Quote can't be longer than 1024 characters, slut")]
[DisplayName("Quote")]
public string QuoteText { get; set; }
[DisplayName("Categories")]
public IList<int> CategoryIds { get; set; }
[GreaterThanZero(ErrorMessage = "Quit screwing with the form, faggle"), DisplayName("Game")]
public int GameId { get; set; }
public IEnumerable<Game> AllGames { get; set; }
@ -106,7 +110,7 @@ namespace VideoGameQuotes.Web.Models {
return MakeTable(
"region-checkbox-table",
cellsPerRow,
(IEnumerable<Region>)Enum.GetValues(typeof(Region)),
Enum.GetValues(typeof(Region)).Cast<Region>().Where(region => region != Region.Unknown),
region => CreateCheckbox("GameRegions", region, "region_" + (int)region, region.ToString())
);
}

View File

@ -86,7 +86,9 @@
<ItemGroup>
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\ApiController.cs" />
<Compile Include="Controllers\GameController.cs" />
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Controllers\PublisherController.cs" />
<Compile Include="Controllers\SystemController.cs" />
<Compile Include="Models\CreateGameModel.cs" />
<Compile Include="Models\CreatePublisherModel.cs" />
@ -138,7 +140,9 @@
<Content Include="media\css\global.css" />
<Content Include="media\css\quote.css" />
<Content Include="media\css\reset.css" />
<Content Include="media\images\add.png" />
<Content Include="media\images\favicon.png" />
<Content Include="media\images\loading.gif" />
<Content Include="media\images\search.png" />
<Content Include="media\js\jquery.cookie.js" />
<Content Include="media\js\vgquotes.js" />

View File

@ -6,60 +6,63 @@
<%= Html.HiddenFor(model => model.QuoteId, new { id = "quote-id" })%>
<p id="game-select">
<%= Html.Label("Game", "GameId") %>
<%= Html.LabelFor(model => model.GameId, new { @class = "label" })%>
<br />
<%= Html.DropDownListFor(model => model.GameId, Model.GetGameList()) %>
<a href="#" id="create-game-link">Create new game</a>
<a href="#" id="create-game-link" class="create-new-link">Create new game</a>
</p>
<div id="create-game-form">
<fieldset>
<legend>Create new game</legend>
<p class="error-message"></p>
<p>
<%= Html.Label("Name", "GameName") %>
<%= Html.Label("Name", "GameName", new { @class = "label" })%>
<br />
<%= Html.TextBox("GameName") %>
</p>
<p>
<%= Html.Label("Website", "GameWebsite") %> <small>(link to Wikipedia page or something)</small>
<%= Html.Label("Website", "GameWebsite", new { @class = "label" })%> <small>(link to Wikipedia page or something)</small>
<br />
<%= Html.TextBox("GameWebsite") %>
</p>
<p>
Regions
<span id="GameRegions" class="label">Regions</span>
<br />
<%= Model.MakeRegionTable() %>
</p>
<div id="system-select">
Systems
<span id="SystemIds" class="label">Systems</span>
<br />
<%= Model.MakeSystemTable(Html) %>
<a href="#" id="create-system-link">Create new system</a>
<a href="#" id="create-system-link" class="create-new-link">Create new system&hellip;</a>
</div>
<div id="create-system-form">
<fieldset>
<legend>Create new system</legend>
<p class="error-message"></p>
<p>
<%= Html.Label("Name", "SystemName") %>
<%= Html.Label("Name", "SystemName", new { @class = "label" })%>
<br />
<%= Html.TextBox("SystemName") %>
</p>
<p>
<%= Html.Label("Abbreviation", "SystemAbbreviation") %>
<%= Html.Label("Abbreviation", "SystemAbbreviation", new { @class = "label" })%>
<br />
<%= Html.TextBox("SystemAbbreviation")%>
</p>
<p>
<%= Html.Label("Release Date", "SystemReleaseDate") %>
<%= Html.Label("Release Date", "SystemReleaseDate", new { @class = "label" })%>
<br />
<%= Html.TextBox("SystemReleaseDate", null) %>
</p>
@ -70,24 +73,26 @@
</div>
<div id="publisher-select">
Publishers
<span class="label">Publishers</span>
<br />
<%= Model.MakePublisherTable(Html) %>
<a href="#" id="create-publisher-link">Create new publisher</a>
<a href="#" id="create-publisher-link" class="create-new-link">Create new publisher</a>
</div>
<div id="create-publisher-form">
<fieldset>
<legend>Create new publisher</legend>
<p class="error-message"></p>
<p>
<%= Html.Label("Name", "PublisherName") %>
<%= Html.Label("Name", "PublisherName", new { @class = "label" })%>
<br />
<%= Html.TextBox("PublisherName") %>
</p>
<p>
<%= Html.Label("Website", "PublisherWebsite") %>
<%= Html.Label("Website", "PublisherWebsite", new { @class = "label" }) %>
<br />
<%= Html.TextBox("PublisherWebsite") %>
</p>
@ -103,18 +108,18 @@
</div>
<p>
<%= Html.LabelFor(model => model.QuoteText) %>
<%= Html.LabelFor(model => model.QuoteText, new { @class = "label" })%>
<br />
<%= Html.TextAreaFor(model => model.QuoteText) %>
</p>
<div>
<p>
<%= Html.LabelFor(model => model.CategoryIds) %>
<span class="label">Categories</span>
</p>
<%= Model.MakeCategoryTable(Html) %>
<a href="#" id="create-category-link">Create new category</a>
<a href="#" id="create-category-link" class="create-new-link">Create new category</a>
</div>
<% if (Model.QuoteId > 0) { %>

View File

@ -2,7 +2,7 @@
<%@ Import Namespace="VideoGameQuotes.Api" %>
<li><%= Html.ActionLink("Recent", "recent", "Quote", null, new { title = "View most recently submitted quotes" })%></li>
<li><%= Html.RouteLink("Best", "quote", new { action = "best" }, new { title = "View the top rated quotes" })%></li>
<li><%= Html.ActionLink("Best", "best", "quote", null, new { title = "View the top rated quotes" })%></li>
<li><%= Html.ActionLink("Browse", "browse", "Quote", new { qualifiers = "" }, new { title = "Browse the quote database" })%></li>
<li><%= Html.ActionLink("Random", "random", "Quote", null, new { title = "View a random quote" })%></li>
<li><%= Html.ActionLink("Submit", "submit", "Quote", null, new { title = "Submit a new quote" }) %></li>

View File

@ -112,6 +112,14 @@ ul.menu {
width: 600px;
}
.error-message {
display: none;
color: #990000;
}
.field-validation-error {
color: #990000;
}
.paging-menu p {
text-align: right;
font-size: 80%;
@ -130,6 +138,25 @@ ul.menu {
border: none !important;
}
.label {
font-weight: bold;
}
#edit-quote-form input[type="text"] {
width: 300px;
}
#edit-quote-form td {
padding: 3px;
}
#edit-quote-form textarea {
width: 500px;
height: 120px;
}
.create-new-link {
padding-left: 20px;
background: transparent url(/media/images/add.png) left center no-repeat;
}
#browse-default-menu li {
margin-bottom: 2px;
padding: 3px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,13 +1,58 @@
(function($, window, undefined){
$.fn.center = function () {
$.fn.center = function() {
this.css("top", ($(window).height() - this.height()) / 2 + $(window).scrollTop() + "px");
this.css("left", ($(window).width() - this.width()) / 2 + $(window).scrollLeft() + "px");
return this;
};
$.fn.applyModelErrors = function(errorMessage, errorData) {
var $this = this;
if (errorMessage !== null) {
$this.find(".error-message").first().text(errorMessage).show();
}
$.each(errorData, function(inputFieldName, value) {
var $input = $this.find("input[name='" + inputFieldName + "']");
if ($input.length > 1) {
$input = $("#" + inputFieldName);
}
if ($input.length) {
$input.addClass("input-validation-error");
$("<span/>")
.addClass("field-validation-error")
.text(value)
.insertAfter($input);
}
});
};
$.fn.clearModelErrors = function() {
this
.find(".field-validation-error")
.remove()
.end()
.find(".input-validation-error")
.removeClass("input-validation-error")
.end()
.find(".error-message")
.empty()
.hide();
};
$.vgquotes = {
refresh: function() { }
refresh: function() { },
ajaxErrorHandler: function(xhr) {
alert("An error occurred (" + xhr.statusCode + ")");
},
preload: function(images) {
//MM_preloadImages(lulz)
$.each(images, function() {
$('<img/>')[0].src = this;
});
}
};
(function(){
@ -61,6 +106,10 @@
var setupVoting = function() {
var voting = false;
var loadingGif = "/media/images/loading.gif";
$.vgquotes.preload([loadingGif]);
$(".vote-for, .vote-against").live("click", function() {
if (voting) {
alert("Please wait for the current vote to process before voting again");
@ -72,23 +121,23 @@
var $container = $votingLink.parents(".quote-container");
var direction = $votingLink.hasClass("vote-for") ? 1 : 0;
var quoteId = $container.find("input.quote-id").val();
var $score = $container.find(".quote-score");
var score = $score.text();
$.ajax("/vote", {
type: "POST",
data: {
QuoteId: quoteId,
Direction: direction
data: { QuoteId: quoteId, Direction: direction },
beforeSend: function() {
$score.empty().append($("<img/>").attr({ src: loadingGif, title: "submitting your vote" + String.fromCharCode(0x2026) }));
},
success: function(data, status, $xhr) {
if (data.Error !== null) {
alert(data.Error);
window.alert(data.Error);
return;
}
$container
.find(".quote-score")
.attr("title", "+" + data.Data.upVotes + ", -" + data.Data.downVotes)
.text(data.Data.netVotes);
score = data.Data.netVotes;
$score.attr("title", "+" + data.Data.upVotes + ", -" + data.Data.downVotes);
//remove the voting arrow, and add the other one if needed
$votingLink.remove();
@ -111,7 +160,15 @@
}
},
complete: function() { voting = false; }
complete: function() {
voting = false;
var $img = $score.find("img");
if ($img.length) {
$img.remove();
}
$score.text(score);
}
});
return false;
@ -257,12 +314,14 @@
$("#create-publisher-link, #create-publisher-cancel").click(toggleFormAndLink("publisher"));
$("#create-system-submit").click(function() {
var $container = $("#create-system-form");
$.ajax("/system/create", {
type: "POST",
data: { Name: $("#SystemName").val(), Abbreviation: $("#SystemAbbreviation").val(), ReleaseDate: $("#SystemReleaseDate").val() },
data: { SystemName: $("#SystemName").val(), SystemAbbreviation: $("#SystemAbbreviation").val(), SystemReleaseDate: $("#SystemReleaseDate").val() },
beforeSend: function() { $container.clearModelErrors(); },
success: function(data, statux, $xhr) {
if (data.Error !== null) {
alert(data.Error);
$container.applyModelErrors(data.Error, data.Data);
return;
}
@ -276,12 +335,14 @@
});
$("#create-publisher-submit").click(function() {
var $container = $("#create-publisher-form");
$.ajax("/publisher/create", {
type: "POST",
data: { Name: $("#PublisherName").val(), Website: $("#PublisherWebsite").val() },
data: { PublisherName: $("#PublisherName").val(), PublisherWebsite: $("#PublisherWebsite").val() },
beforeSend: function() { $container.clearModelErrors(); },
success: function(data, statux, $xhr) {
if (data.Error !== null) {
alert(data.Error);
$container.applyModelErrors(data.Error, data.Data);
return;
}
@ -294,24 +355,20 @@
});
$("#create-game-submit").click(function() {
var regions = [], publishers = [], systems = [];
$("input:checked[name='GameRegions']").each(function(index, input) { regions.push(input.value); });
$("input:checked[name='SystemIds']").each(function(index, input) { systems.push(input.value); });
$("input:checked[name='PublisherIds']").each(function(index, input) { publishers.push(input.value); });
var regions = [], publishers = [], systems = [], data = { GameName: $("#GameName").val(), GameWebsite: $("#GameWebsite").val() };
$("input:checked[name='GameRegions']").each(function(index, input) { if (typeof(data.GameRegions) === "undefined") { data.GameRegions = []; } data.GameRegions.push(input.value); });
$("input:checked[name='SystemIds']").each(function(index, input) { if (typeof(data.SystemIds) === "undefined") { data.SystemIds = []; } data.SystemIds.push(input.value); });
$("input:checked[name='PublisherIds']").each(function(index, input) { if (typeof(data.PublisherIds) === "undefined") { data.PublisherIds = []; } data.PublisherIds.push(input.value); });
var $container = $("#create-game-form");
$.ajax("/game/create", {
type: "POST",
data: {
Name: $("#GameName").val(),
Website: $("#GameWebsite").val(),
Region: regions,
SystemIds: systems,
PublisherIds: publishers
},
data: data,
traditional: true,
beforeSend: function() { $container.clearModelErrors(); },
success: function(data, statux, $xhr) {
if (data.Error !== null) {
alert(data.Error);
$container.applyModelErrors(data.Error, data.Data);
return;
}
@ -425,6 +482,10 @@
if ($("#edit-quote-form").length > 0) {
setupQuoteForm();
}
$("body").ajaxError(function(e, xhr, options, error){
$.vgquotes.ajaxErrorHandler.call(this, xhr);
});
});
}());