classNames: ["new-post-component"], classNameBindings: ["formState", "isInvalid"], formState: "initial", // FORM-STATES: "initial", "editing", "loading", "failure", "success" isValid: Ember.computed.alias("gifPost.isValid"), isInvalid: Ember.computed.not("isValid"), /* OBSERVERS */ // This computes the displayed message for all success/failure message: function(){ var formState = this.get("formState") // On failure, delegate message to the object if (formState === "failure") { return this.get("gifPost.message"); // On success, just scream real loud! } else if (formState === "success") { return "New gif posted: " + this.get("gifPost.parsedUrl") // If it's invalid but has a gif, it's too long: } else if (this.get("isInvalid") && !!this.get("gifPost.isGif")) { return "Your message is too long."; // Otherwise if it's not valid it's not a gif } else if (this.get("isInvalid")) { return "Please add a valid gif link to this post."; } else { return null; } }.property("formState", "isInvalid", "gifPost.isGif", "gifPost.message"), /* ACTIONS */ // These actions are primarily about pushing state around. actions: { showDialog: function() { this.set("formState", "editing"); }, cancel: function() { this.set("formState", "initial"); this.set("gifPost", App.store.createRecord("gifPost")); }, submit: function() { controller = this; controller.set("formState", "loading"); var gifPost = this.get("gifPost"); gifPost.save().then(function(data) { // Success controller.set("formState", "success"); controller.defer(function() { controller.set("formState", "initial") }, 5000); // Failure }, function(data) { controller.set("formState", "failure"); if (!!data.jqXHR && data.jqXHR.status == 422) { // 422: validation error controller.get("gifPost").set("message", data.jqXHR.responseJSON.errors.url[0]); } else { controller.get("gifPost").set("message", "There was an error posting your gif. Please wait and try again.") } controller.defer(function() { controller.set("formState", "editing") }, 5000); }); } }, /* FUNCTIONS */ // Allow injectable setTimeout override for testing defer: function(callback, delay) { setTimeout(callback, delay); } }); /* INITIALIZATION */ $(document).ready(function() { $("#new-post-container").each(function(){ var component = App.GfNewPostComponent.create({ gifPost: App.store.createRecord("gifPost") }); component.replaceIn(this); }); }); App.GfListPostsComponent = Ember.Component.extend({ /* PROPERTIES */ layoutName: "components/gf-list-posts", gifPosts: null, classNames: ["list-posts-component"], // We only want to show posts that are saved to the server persistedGifPosts: Ember.computed.filterBy("gifPosts", "isNew", false), sortedPosts: function() { // well this is a neat trick, sort in reverse ID order return this.get("persistedGifPosts").sortBy("id:desc"); }.property("persistedGifPosts.@each"), /* ACTIONS */ actions: { delete: function(gifPost) { if (confirm("Really delete this lovely gif?")) { gifPost.destroyRecord().then(function(result){ //message? }, function(){ gifPost.rollback(); }); } } } }); /* INITIALIZATION */ $(document).ready(function() { if ($("#gif-posts-container").length) { App.store.find("gifPost").then(function(result) { $("#gif-posts-container").each(function(){ var component = App.GfListPostsComponent.create({ gifPosts: result }); component.replaceIn(this); }); }); } }); App.GifPost = DS.Model.extend({ /* PROPERTIES */ body: DS.attr("string"), url: DS.attr("string"), username: DS.attr("string"), message: null, parsedUrl: function() { if (!!this.get("body")) { var matches = this.get("body").match(this.get("regex")) return (matches && matches.length > 0) ? matches[0] : ""; } }.property("body"), isGif: function() { return /.gif$/.test(this.get("parsedUrl")) }.property("parsedUrl"), charCount: function() { if (this.get("isGif")) { return this.get("body.length") - this.get("parsedUrl.length") } else { return this.get("body.length") } }.property("body", "parsedUrl", "isGif"), isValid: function() { return !!(this.get("charCount") <= 140 && this.get("isGif")) }.property("charCount", "isGif"), permalink: function() { return "/gif_posts/" + this.get("id"); }.property("id"), /* OBSERVERS */ // Copy the computed parsedUrl into the canonical url to send to the server setUrl: function() { this.set("url", this.get("parsedUrl")); }.observes("parsedUrl"), /* MISC */ // This property is at the end basically because it breaks Emacs auto-indent :/ regex: function() { return /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/ }.property() }); after