diff options
| author | Jules Laplace <jules@okfoc.us> | 2015-08-03 22:33:34 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2015-08-03 22:33:34 -0400 |
| commit | 1977a99c596485d2e42e544b2b0ecd0625395775 (patch) | |
| tree | 472f8e30a71226047d7d5d900745c296a1ba69f5 /public/js/lib | |
| parent | 6824643f459c6d63ffd8ff846b373b2e49707abc (diff) | |
app skeleton
Diffstat (limited to 'public/js/lib')
| -rw-r--r-- | public/js/lib/formview.js | 135 | ||||
| -rw-r--r-- | public/js/lib/parser.js | 286 | ||||
| -rw-r--r-- | public/js/lib/view.js | 142 |
3 files changed, 563 insertions, 0 deletions
diff --git a/public/js/lib/formview.js b/public/js/lib/formview.js new file mode 100644 index 0000000..f5845e7 --- /dev/null +++ b/public/js/lib/formview.js @@ -0,0 +1,135 @@ +var FormView = View.extend({ + + method: "post", + useMinotaur: false, + + events: { + "submit form": "save" + }, + + initialize: function(opt){ + if (opt && opt.parent) { + this.parent = opt.parent + } + this.$form = this.$("form") + this.$errors = this.$(".errors") + this.$errorList = this.$(".errorList") + }, + + reset: function(){ + this.$("input,textarea").not("[type='submit']").not("[type='hidden']").val("") + }, + + showErrors: function(errors){ + if (errors && errors.length) { + this.$errorList.empty(); + for (var i in errors) { + this.$errorList.append('<div>' + errors[i] + '</div>'); + } + this.$errors.css("opacity", 1.0); + setTimeout(function(){ + this.$errors.show().css("opacity", 1.0); + }.bind(this), 200) + } + }, + + serialize: function(){ + var fd = new FormData(), hasCSRF = false + + this.$("input[name], select[name], textarea[name]").each( function(){ + if (this.type == "file") { + if (this.files.length > 0) { + fd.append(this.name, this.files[0]); + } + } + else if (this.type == "password") { + if (this.value.length > 0) { + fd.append(this.name, SHA1.hex('lol$' + this.value + '$vvalls')) + } + } + else { + fd.append(this.name, this.value); + hasCSRF = hasCSRF || this.name == "_csrf" + } + }); + + if (! hasCSRF) { + fd.append("_csrf", $("[name=_csrf]").val()) + } + + return fd + }, + + save: function(e, successCallback, errorCallback){ + e && e.preventDefault() + + this.$errors.hide().css("opacity", 0.0); + + if (this.validate) { + var errors = this.validate() + if (errors && errors.length) { + if (errorCallback) { + errorCallback(errors) + } + else { + this.showErrors(errors) + } + return + } + } + + var action = typeof this.action == "function" ? this.action() : this.action + if (! action) return + + var request = $.ajax({ + url: action, + type: this.method, + data: this.serialize(), + dataType: "json", + processData: false, + contentType: false, + }) + + if (this.useMinotaur) { + Minotaur.show() + } + + request.done($.proxy(function (response) { + if (this.useMinotaur) { + Minotaur.hide() + } + if (response.error) { + var errors = [] + for (var key in response.error.errors) { + errors.push(response.error.errors[key].message); + } + if (errorCallback) { + errorCallback(errors) + } + else { + this.showErrors(errors) + } + return + } + else { + if (successCallback) { + successCallback(response) + } + if (this.success) { + this.success(response) + } + } + }, this)); + } + +}) + + +var ModalFormView = ModalView.extend(FormView.prototype).extend({ + + load: function(){ + this.reset() + this.show() + } + +}) diff --git a/public/js/lib/parser.js b/public/js/lib/parser.js new file mode 100644 index 0000000..20c5306 --- /dev/null +++ b/public/js/lib/parser.js @@ -0,0 +1,286 @@ +var Parser = (function(){ + var Parser = {} + Parser.integrations = [{ + type: 'image', + regex: /\.(jpeg|jpg|gif|png|svg)(\?.*)?$/i, + fetch: function(url, done) { + var img = new Image () + img.onload = function(){ + if (!img) return + var width = img.naturalWidth, height = img.naturalHeight + img = null + done({ + url: url, + type: "image", + token: "", + thumbnail: "", + title: "", + width: width, + height: height, + }) + } + img.src = url + if (img.complete) { + img.onload() + } + }, + tag: function (media) { + return '<img src="' + media.url + '">'; + } + }, { + type: 'video', + regex: /\.(mp4|webm)(\?.*)?$/i, + fetch: function(url, done) { + var video = document.createElement("video") + video.addEventListener("loadedmetadata", function(){ + var width = video.videoWidth, height = video.videoHeight + video = null + done({ + url: url, + type: "video", + token: "", + thumbnail: "", + title: "", + width: width, + height: height, + }) + }) + video.src = url + video.load() + }, + tag: function (media) { + return '<video src="' + media.url + '">'; + } + }, { + type: 'youtube', + regex: /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i, + fetch: function(url, done) { + var id = (url.match(/v=([-_a-zA-Z0-9]{11})/i) || url.match(/youtu.be\/([-_a-zA-Z0-9]{11})/i) || url.match(/embed\/([-_a-zA-Z0-9]{11})/i))[1].split('&')[0]; + var thumb = "http://i.ytimg.com/vi/" + id + "/hqdefault.jpg" + $.ajax({ + type: 'GET', + url: 'https://www.googleapis.com/youtube/v3/videos', + dataType: "jsonp", + data: { + id: id, + key: "AIzaSyDYPKGD0-_VRBWpUYRmX8Qg6BtWmcPU_cM", + part: "id,contentDetails,snippet,status", + }, + success: function(result){ + var res = result.items[0] + done({ + url: url, + type: "youtube", + token: id, + thumbnail: thumb, + title: res.snippet.title, + width: 640, + height: 360, + }) + } + }) + }, + tag: function (media) { + // return '<img class="video" type="youtube" vid="'+media.token+'" src="'+media.thumbnail+'"><span class="playvid">▶</span>'; + return '<div class="video" style="width: ' + media.width + 'px; height: ' + media.height + 'px; overflow: hidden; position: relative;"><iframe frameborder="0" scrolling="no" seamless="seamless" webkitallowfullscreen="webkitAllowFullScreen" mozallowfullscreen="mozallowfullscreen" allowfullscreen="allowfullscreen" id="okplayer" width="' + media.width + '" height="' + media.height + '" src="http://youtube.com/embed/' + media.token + '?showinfo=0" style="position: absolute; top: 0px; left: 0px; width: ' + media.width + 'px; height: ' + media.height + 'px;"></iframe></div>' + } + }, { + type: 'vimeo', + regex: /vimeo.com\/\d+$/i, + fetch: function(url, done) { + var id = url.match(/\d+$/i)[0]; + $.ajax({ + type: 'GET', + url: 'http://vimeo.com/api/v2/video/' + id + '.json', + success: function(result){ + if (result.length == 0) { return done(id, "", 640, 360) } + var res = result[0] + if (res.embed_privacy != "anywhere") { + AlertModal.alert("Sorry, the author of this video has marked it private, preventing it from being embedded.", function(){}) + return + } + done({ + url: url, + type: "vimeo", + token: id, + thumbnail: res.thumbnail_large, + title: res.title, + width: res.width, + height: res.height, + }) + } + }) + }, + tag: function (media) { + // return '<img class="video" type="vimeo" vid="'+media.token+'" src="'+media.thumbnail+'"><span class="playvid">▶</span>'; + return '<div class="video" style="width: ' + media.width + 'px; height: ' + media.height + 'px; overflow: hidden; position: relative;"><iframe frameborder="0" scrolling="no" seamless="seamless" webkitallowfullscreen="webkitAllowFullScreen" mozallowfullscreen="mozallowfullscreen" allowfullscreen="allowfullscreen" id="okplayer" src="http://player.vimeo.com/video/' + media.token + '?api=1&title=0&byline=0&portrait=0&playbar=0&player_id=okplayer&loop=0&autoplay=0" width="' + media.width + '" height="' + media.height + '" style="position: absolute; top: 0px; left: 0px; width: ' + media.width + 'px; height: ' + media.height + 'px;"></iframe></div>' + } + }, + { + type: 'soundcloud', + regex: /soundcloud.com\/[-a-zA-Z0-9]+\/[-a-zA-Z0-9]+\/?$/i, + fetch: function (url, done) { + $.ajax({ + type: 'GET', + url: 'http://api.soundcloud.com/resolve.json?url=' + + url + + '&client_id=' + + '0673fbe6fc794a7750f680747e863b10', + success: function(result) { + // console.log(result) + done({ + url: url, + type: "soundcloud", + token: result.id, + thumbnail: result.artwork_url || result.user.avatar_url, + title: result.user.username + " - " + result.title, + width: 166, + height: 166, + }) + } + }); + }, + tag: function (media) { + return '<iframe width="166" height="166" scrolling="no" frameborder="no"' + + 'src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/' + media.token + + '&color=ff6600&auto_play=false&show_artwork=true"></iframe>' + } + }, + /* + { + type: 'link', + regex: /^http.+/i, + fetch: function(url, done) { + done({ + url: url, + type: "link", + token: "", + thumbnail: "", + title: "", + width: 100, + height: 100, + }) + }, + tag: function (media) { + return '<a href="' + media.url + '" target="_blank">' + media.url + '</a>' + } + } + */ + ] + + Parser.tumblr = function(url, cb){ + var domain = url.replace(/^https?:\/\//,"").split("/")[0] + if (domain.indexOf(".") == -1) { + domain += ".tumblr.com" + } + $.ajax({ + type: 'GET', + url: "http://" + domain + "/api/read", + dataType: "jsonp", + data: { + format: "json", + }, + success: function(data){ + var media_list = [] + var blog = data.tumblelog + + data.posts.forEach(parse) + cb(media_list) + + function parse(post){ + var media, caption, url + switch (post.type) { + case 'photo': + caption = stripHTML(post['photo-caption']) + if (post.photos.length) { + post.photos.forEach(function(photo){ + var media = { + url: photo['photo-url-1280'], + type: "image", + token: "", + thumbnail: photo['photo-url-500'], + description: caption, + width: parseInt(photo.width), + height: parseInt(photo.height), + } + media_list.push(media) + }) + } + else { + media = { + url: post['photo-url-1280'], + type: "image", + token: "", + thumbnail: post['photo-url-500'], + description: caption, + width: parseInt(post.width), + height: parseInt(post.height), + } + media_list.push(media) + } + break + case 'video': + url = post['video-source'] + if (url.indexOf("http") !== 0) { break } + if (Parser.lookup.youtube.regex.test(url)) { + var id = (url.match(/v=([-_a-zA-Z0-9]{11})/i) || url.match(/youtu.be\/([-_a-zA-Z0-9]{11})/i) || url.match(/embed\/([-_a-zA-Z0-9]{11})/i))[1].split('&')[0]; + var thumb = "http://i.ytimg.com/vi/" + id + "/hqdefault.jpg" + media = { + url: post['video-source'], + type: "youtube", + token: id, + thumbnail: thumb, + title: stripHTML(post['video-caption']), + width: 640, + height: 360, + } + media_list.push(media) + } + break + } + } +// console.log(post) + } + }) + } + + Parser.parse = function (url, cb) { + var matched = Parser.integrations.some(function(integration){ + if (integration.regex.test(url)) { + integration.fetch(url, function(res){ + cb(res) + }) + return true + } + return false + }) + if (! matched) { + cb(null) + } + } + + Parser.tag = function (media){ + if (media.type in Parser.lookup) { + return Parser.lookup[media.type].tag(media) + } + return "" + } + + Parser.loadImage = function(url, cb, error){ + if (Parser.lookup.image.regex.test(url)) { + Parser.lookup.image.fetch(url, function(media){ + cb(media) + }) + } + else error && error() + } + + Parser.thumbnail = function (media) { + return '<img src="' + (media.thumbnail || media.url) + '" class="thumb">'; + } + + Parser.lookup = _.indexBy(Parser.integrations, 'type'); + + return Parser +})() + diff --git a/public/js/lib/view.js b/public/js/lib/view.js new file mode 100644 index 0000000..87d6ee4 --- /dev/null +++ b/public/js/lib/view.js @@ -0,0 +1,142 @@ +var View = (function($, _){ + + var View = function(options) { + this._id = _.uniqueId('view') + this.type = "view" + options || (options = {}); + _.extend(this, _.pick(options, viewOptions)) + this._ensureElement() + this.initialize.apply(this, arguments) + this.delegateEvents() + } + + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + _.extend(View.prototype, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + $: function(selector) { + return this.$el.find(selector); + }, + + initialize: function(){}, + + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof $ ? element : $(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this._id; + if (is_mobile && (selector === 'mouseenter' || selector === 'mouseleave')) { + continue + } + else if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this._id); + return this; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + this.setElement(_.result(this, 'el'), false); + }, + + preventDefault: function(e){ + e && e.preventDefault() + }, + + stopPropagation: function(e){ + e && e.stopPropagation() + }, + + }); + + + var extend = function(protoProps, staticProps) { + var staticProps = staticProps || {} + var parent = this; + var child; + var childEvents = {}; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent's constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Extend events so we can subclass views + _.extend(childEvents, parent.prototype.events, protoProps.events) + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + var Surrogate = function(){ this.constructor = child; }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate; + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Set a convenience property in case the parent's prototype is needed + // later. + child.prototype.__super__ = parent.prototype; + child.prototype.events = childEvents + + return child; + }; + + View.extend = extend; + + return View; +})(Zepto, _) |
