| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,34 @@ | ||
| { | ||
| "name" : "node-activities-boilerplate", | ||
| "description" : "A boilerplate Activity Stream Client for any Cloud Foundry instance. Based on the work from Mathias Pettersson <mape@mape.me> on node-express-boilerplate", | ||
| "version" : "0.0.7", | ||
| "author" : "Monica Wilkinson <ciberch@gmail.com>", | ||
| "engines" : ["node"], | ||
| "repository" : { "type":"git", "url":"http://github.com/ciberch/node-express-boilerplate" }, | ||
| "scripts": { | ||
| "test": "make test" | ||
| }, | ||
| "dependencies" : { | ||
| "connect" : "1.x", | ||
| "connect-assetmanager" : ">=0.0.21", | ||
| "connect-assetmanager-handlers" : ">=0.0.17", | ||
| "jade": "", | ||
| "express" : "2.x", | ||
| "socket.io" : ">=0.7.8", | ||
| "connect-redis" : ">=1.0.7", | ||
| "connect-notifo" : ">=0.0.1", | ||
| "airbrake" : ">=0.2.0", | ||
| "everyauth" : "0.2.x", | ||
| "cloudfoundry" : ">=0.1.0", | ||
| "activity-streams-mongoose": "0.1.1", | ||
| "hiredis": "", | ||
| "underscore": "", | ||
| "mongoose-auth" : ">= 0.0.12", | ||
| "guid" : "", | ||
| "imagemagick" : "", | ||
| "mongoose": "3.x", | ||
| "mocha" : "1.4.1", | ||
| "should" : "1.1.0" | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| var ActivityCreateView = Backbone.View.extend({ | ||
| el: '#new_activity', | ||
| initialize: function(){ | ||
| _.bindAll(this, 'newAct', 'render', 'changeType', 'includeLocation', 'sendMessage', 'fileSelected'); | ||
|
|
||
| this.trimForServer = App.helper.trimForServer; | ||
|
|
||
| var streamName = this.$el.find('#streamName').val(); | ||
| var verb = this.trimForServer(this.$el.find('#verb-show')); | ||
| var objectType = this.trimForServer(this.$el.find('#object-show')); | ||
|
|
||
| this.newAct(streamName, verb, objectType); | ||
| this.render(); | ||
| }, | ||
| events: { | ||
| "click .type-select" : "changeType", | ||
| "click #includeLocation" : "includeLocation", | ||
| "click #send-message" : "sendMessage", | ||
| "change #input-file-input" : "fileSelected" | ||
| }, | ||
| newAct : function(streamName, verb, objectType) { | ||
| this.streamName = streamName; | ||
| this.model = new Activity({ | ||
| object: {content: '', objectType: objectType, title: '', url: ''}, | ||
| verb: verb, | ||
| streams: [streamName] | ||
| }); | ||
| }, | ||
| render: function(){ | ||
| var actData = this.model.toJSON(); | ||
| this.$el.find("#specific-activity-input").html(jade.templates[actData.object.objectType]({act: actData})); | ||
|
|
||
| if(!actData.object.image) { | ||
| var actView = this; | ||
| $('#new_photo').ajaxForm(function(data) { | ||
| actView.model.set('object', data.object); | ||
| actView.render(); | ||
| }); | ||
| } | ||
|
|
||
| return this; // for chainable calls, like .render().el | ||
| }, | ||
| changeType : function(event) { | ||
| console.log(event); | ||
| var itemName = $(event.target).data("type-show"); | ||
| if (itemName) { | ||
| $("#" + itemName)[0].innerHTML = event.target.text + " "; | ||
| var val = this.trimForServer(event.target.text); | ||
| if (itemName == "verb-show") { | ||
| this.model.set('verb', val); | ||
| } else { | ||
| var obj = this.model.get('object'); | ||
| obj.objectType = val; | ||
| this.model.set('object', obj); | ||
| } | ||
| } | ||
| this.render(); | ||
| }, | ||
| includeLocation : function(event) { | ||
| if (navigator.geolocation) { | ||
| navigator.geolocation.getCurrentPosition(App.helper.getLocation); | ||
| } else { | ||
| alert("Geo Location is not supported on your device"); | ||
| } | ||
| }, | ||
| sendMessage : function() { | ||
| console.log("In send message"); | ||
|
|
||
| var obj = this.model.get('object'); | ||
| obj.content = $("#msg").val(); | ||
| obj.url = $('#url').val(); | ||
| obj.title = $('#title').val(); | ||
| obj.objectType = this.trimForServer($('#object-show')); | ||
| this.model.set('object', obj); | ||
|
|
||
| var streamName = $('#streamName').val(); | ||
| this.model.set('streams', [streamName]); | ||
|
|
||
| var verb = this.trimForServer($('#verb-show')); | ||
| this.model.set('verb', verb); | ||
|
|
||
| if (this.model.isValid()) { | ||
| if (this.model.save()) { | ||
| this.newAct(streamName, verb, obj.objectType); | ||
| this.render(); | ||
| } | ||
| } | ||
|
|
||
| }, | ||
| fileSelected : function(event){ | ||
|
|
||
| if (event.target.files && event.target.files[0]) { | ||
| var file = event.target.files[0]; | ||
| this.file = file; | ||
| console.dir(file); | ||
| $('#title').val(file.name); | ||
| } | ||
| this.$el.find("#upload-file").show(); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| var Activity = Backbone.Model.extend({ | ||
| url : "/activities", | ||
| // From activity-streams-mongoose/lib/activityMongoose.js | ||
| defaults: { | ||
| verb: 'post', | ||
| object: null, //ActivityObject | ||
| actor: null, //ActivityObject | ||
| url: '', | ||
| title: '', | ||
| content: '', | ||
| icon: null, // MediaLinkHash | ||
| target: null, //ActivityObject | ||
| published: Date.now, | ||
| updated: Date.now, | ||
| inReplyTo: null, //Activity | ||
| provider: null, //ActivityObject | ||
| generator: null, //ActivityObject | ||
| streams: ['firehose'], | ||
| likes: {}, | ||
| likes_count: 0, | ||
| comments: [], | ||
| comments_count: 0, | ||
| userFriendlyDate: 'No idea when' | ||
| }, | ||
| validate: function(attrs) { | ||
|
|
||
| if (! attrs.object) { | ||
| return "Object is missing" | ||
| } | ||
| if (!attrs.object.title) { | ||
| return "Title is missing"; | ||
| } | ||
| } | ||
| }); | ||
| var ActivityList = Backbone.Collection.extend({ | ||
| model: Activity, | ||
| url : "/activities" | ||
| }); | ||
|
|
||
| var ActivityObject = Backbone.Model.extend({ | ||
| defaults : { | ||
| image: null, // MediaLinkHash | ||
| icon: null, // MediaLinkHash | ||
| displayName: '', | ||
| summary: '', | ||
| content: '', | ||
| url: '', | ||
| author: null, //ActivityObject | ||
| published: Date.now, | ||
| objectType: '', | ||
| attachments: [], //ActivityObject | ||
| upstreamDuplicates: [], //string | ||
| downstreamDuplicates: [], //string | ||
| updated: Date.now | ||
| } | ||
|
|
||
| }); | ||
|
|
||
| var ActivityObjectType = Backbone.Model.extend({}); | ||
|
|
||
| var ActivityObjectTypeList = Backbone.Collection.extend({ | ||
| model : ActivityObjectType, | ||
| url : "/objectTypes" | ||
| }); | ||
|
|
||
| var ActivityVerb = Backbone.Model.extend({}); | ||
| var ActivityVerbList = Backbone.Collection.extend({ | ||
| model : ActivityVerb, | ||
| url : "/verbs" | ||
| }); | ||
|
|
||
| var User = Backbone.Model.extend({ | ||
| defaults : { | ||
| signedIn : false, | ||
| displayName : 'User' | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| var ActivityView = Backbone.View.extend({ | ||
| initialize: function(){ | ||
| _.bindAll(this, 'render', 'like', 'comment'); // every function that uses 'this' as the current object should be in here | ||
| this.model.bind('change', this.render, this); | ||
| // this.model.on('change:likes_count', 'render'); | ||
| // this.model.on('change:comments_count', 'render'); | ||
| }, | ||
| events: { | ||
| "click .like-button" : "like", | ||
| "click .comment-button" : "comment" | ||
| }, | ||
| render: function(){ | ||
| var actData = this.model.toJSON(); | ||
| var date = Date.parse(this.model.get('published')); | ||
| var awesomeDate = App.helper.fuzzy(date); | ||
| actData['userFriendlyDate'] = awesomeDate; | ||
| this.$el.html(jade.templates["activity"]({activities: [actData]})); | ||
| return this; // for chainable calls, like .render().el | ||
| }, | ||
| like : function() { | ||
| var likes = this.model.get("likes"); | ||
| if (!likes) { | ||
| likes = {}; | ||
| } | ||
| likes[this.user] = true; | ||
| this.model.set("likes", likes); | ||
|
|
||
| var likes_count = _.keys(likes).length; | ||
| this.model.set("likes_count", likes_count) | ||
| this.model.save(); | ||
| return this; | ||
| }, | ||
| comment: function() { | ||
| var content = this.$el.find(".comment-area").val(); | ||
| var comments = this.model.get("comments"); | ||
| if (!comments){ | ||
| comments = []; | ||
| } | ||
| comments.push({actor : App.currentUser, object: { objectType : 'comment', content: content}, published : Date.new}); | ||
| var comments_count = comments.length; | ||
| this.model.set("comments_count", comments_count) | ||
| this.model.save(); | ||
| return this; | ||
| } | ||
|
|
||
|
|
||
| }); | ||
|
|
||
| var ActivityStreamView = Backbone.View.extend({ | ||
| el: '#main_stream', // el attaches to existing element | ||
|
|
||
| initialize: function(){ | ||
| _.bindAll(this, 'render', 'appendItem'); // every function that uses 'this' as the current object should be in here | ||
| this.collection = new ActivityList(); | ||
| this.collection.bind('add', this.appendItem); // collection event binder | ||
| this.maxSize = 20; | ||
| }, | ||
| render: function(){ | ||
| _(this.collection.models).each(function(item){ // in case collection is not empty | ||
| self.appendItem(item); | ||
| }, this); | ||
| }, | ||
|
|
||
| appendItem: function(item){ | ||
| var itemView = new ActivityView({ model: item }); | ||
|
|
||
| this.$el.prepend(itemView.render().el); | ||
|
|
||
| if (this.el.children.count > this.maxSize) { | ||
| this.el.children.last.remove(); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
|
|
||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| function AppHelper(){ | ||
|
|
||
| this.fuzzy = function(date) { | ||
| // Make a fuzzy time | ||
| var delta = Math.round((+new Date - date) / 1000); | ||
|
|
||
| var minute = 60, | ||
| hour = minute * 60, | ||
| day = hour * 24, | ||
| week = day * 7; | ||
|
|
||
| var fuzzy; | ||
|
|
||
| if (delta < 30) { | ||
| fuzzy = 'just now.'; | ||
| } else if (delta < minute) { | ||
| fuzzy = delta + ' seconds ago.'; | ||
| } else if (delta < 2 * minute) { | ||
| fuzzy = 'a minute ago.' | ||
| } else if (delta < hour) { | ||
| fuzzy = Math.floor(delta / minute) + ' minutes ago.'; | ||
| } else if (Math.floor(delta / hour) == 1) { | ||
| fuzzy = '1 hour ago.' | ||
| } else if (delta < day) { | ||
| fuzzy = Math.floor(delta / hour) + ' hours ago.'; | ||
| } else if (delta < day * 2) { | ||
| fuzzy = 'yesterday'; | ||
| } | ||
|
|
||
| return fuzzy; | ||
| }; | ||
|
|
||
| this.trimForServer = function (items) { | ||
| if (items) { | ||
| if (typeof(items) === "object") { | ||
| var val = items[0]; | ||
| return val.innerText.trimRight().toLowerCase(); | ||
| } else { | ||
| return items.trimRight().toLowerCase(); | ||
| } | ||
| } | ||
| return null; | ||
| }; | ||
|
|
||
| this.getLocation = function(position) { | ||
| var mapOptions = { | ||
| zoom: 15, | ||
| mapTypeId: google.maps.MapTypeId.ROADMAP | ||
| }; | ||
| App.map = new google.maps.Map(document.getElementById('map'),mapOptions); | ||
| var pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); | ||
| App.map.setCenter(pos); | ||
| var marker = new google.maps.Marker({ | ||
| position: pos, | ||
| map: App.map, | ||
| title: 'Drag to the proper location', | ||
| draggable:true | ||
| }); | ||
| google.maps.event.addListener(marker, 'click', function() { | ||
| console.log("New position is "); | ||
| console.dir(marker.getPosition()); | ||
| App.map.setCenter(marker.getPosition()); | ||
| }); | ||
|
|
||
| $("#map").show(); | ||
|
|
||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // The DashboardRouter acts as the controller in this MVC setup | ||
| // It is responsible for listening to the events from each view, and then dispatching them back to all the other views | ||
| // It also handles the responsibility of the routing, so using Backbone's internal routing mechanisms, it | ||
| // listens to routes and handles setting up the views appropriately. | ||
| var DashboardRouter = Backbone.Router.extend({ | ||
|
|
||
| // set up the router with all the views on our page, and then listen to all the events that | ||
| // can get triggered | ||
| initialize : function(options) { | ||
| var _this = this; | ||
|
|
||
| this.activityEditView = options.activityEditView, | ||
| this.filterView = options.filterView; | ||
| this.streamView = options.streamView; | ||
| this.activityView = options.activityView; | ||
|
|
||
| this.filterView.on("change_verb change_actor change_object", function(text) { | ||
| Backbone.history.navigate(_this.streamView.stream + "/" + _this.filterView.verb + "/" + _this.filterView.objectType + "/" + _this.filterView.actorType, {trigger: true}); | ||
| }); | ||
|
|
||
| this.streamView.on("change_stream", function(text) { | ||
| Backbone.history.navigate(_this.streamView.stream + "/" + _this.filterView.verb + "/" + _this.filterView.objectType + "/" + _this.filterView.actorType , {trigger: true}); | ||
| }); | ||
| }, | ||
|
|
||
| // set up all the routes in this object | ||
| // make sure to handle every possible scenario, since we want to handle every possibility a user can type in | ||
| routes : { | ||
| "" : "all", | ||
| ":stream/" : "show", | ||
| ":stream/:verb" : "show", | ||
| ":stream/:verb/:objectType" : "show", | ||
| ":stream/:verb/:objectType/:actorType" : "show" | ||
| }, | ||
|
|
||
| // handle the index case specially, and reroute it to the dashboard | ||
| all : function() { | ||
| Backbone.history.navigate("firehose/", {trigger: true}); | ||
| }, | ||
|
|
||
| // this function is called by Backbone when any route is matched from our routes object | ||
| // it defaults the parameters if they aren't passed in, and then sets these values on the views themselves | ||
| // finally, it is responsible for re-rendering the views | ||
| // remember, the views now have new data, so they must be re-rendered to show the new data | ||
| show : function(stream, verb, objectType, actorType) { | ||
| stream = stream || "firehose"; | ||
| verb = verb || ""; | ||
| objectType = objectType || ""; | ||
| actorType = actorType || ""; | ||
| this.filterView.setFilter(verb, objectType, actorType); | ||
| this.filterView.render(); | ||
|
|
||
| this.streamView.setStream(stream); | ||
| this.streamView.render(); | ||
|
|
||
| this.activityView.setFilters(stream, verb, objectType, actorType); | ||
| this.activityView.render(); | ||
| } | ||
|
|
||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| var ActivityDropDownView = Backbone.View.extend({ | ||
| tagName : "li", | ||
| // we don't need a template file if the view is this simple | ||
| template : _.template('<a href="#" class="type-select" data-filter-type="<%=name%>"><%=name%></a>'), | ||
|
|
||
| // a standard render function | ||
| render : function() { | ||
| var html = this.template(this.model.toJSON()); | ||
| this.$el.html(html); | ||
| return this; | ||
| }, | ||
|
|
||
| // bind to the event when a country is selected | ||
| events : { | ||
| "click .type-select" : "selectType" | ||
| }, | ||
|
|
||
| // handle the event and trigger it so listeners can respond to it | ||
| selectType : function(e) { | ||
| this.trigger("select", e, this); | ||
| } | ||
| }); | ||
|
|
||
| var ActivityObjectEditorView = Backbone.View.extend({ | ||
| tagName : "ul", | ||
|
|
||
| initialize: function(){ | ||
| this.typeSelector = new ActivityDropDownView(); | ||
|
|
||
| }, | ||
|
|
||
| render: function(){ | ||
| $(this.el).append(this.typeSelector.render()); | ||
| } | ||
|
|
||
| }); | ||
|
|
||
| var ActivityEditorView = Backbone.View.extend({ | ||
| el: $("#new_activity"), | ||
|
|
||
| initialize: function(){ | ||
| this.actorEditor = new ActivityObjectEditorView(); | ||
| this.objectEditor = new ActivityObjectEditorView(); | ||
|
|
||
| }, | ||
|
|
||
| events: { | ||
| "click .verb-select": "openVerbs", | ||
| "change #streamName" : "setStream", | ||
| "click #send-message" : "save" | ||
| }, | ||
|
|
||
| render: function(){ | ||
|
|
||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,239 @@ | ||
| // These objects handle the 3 comboboxes in the application | ||
| // Note how the code is extremly long to handle seemingly simple things | ||
| // It points out one of the criticisms of Backbone, that it contains too much boiler plate | ||
|
|
||
|
|
||
|
|
||
| // this object is larger view that contains the previous 3 views | ||
| // it acts as the controller for the other 3 views and captures events from them and reacts to them | ||
| var FilterView = Backbone.View.extend({ | ||
|
|
||
| tagName : "div", | ||
|
|
||
| initialize : function(options) { | ||
|
|
||
| var _this = this; | ||
|
|
||
| // get the template from the JST file | ||
| this.template = JST["templates/filter"]; | ||
|
|
||
| // default values | ||
| this.country = "all"; | ||
| this.make = "all"; | ||
| this.carmodel = "all"; | ||
|
|
||
| // grab the Collection objects from the options object | ||
| this.activityObjectCollection = options.activityObjectCollection; | ||
| this.verbCollection = options.verbCollection; | ||
| this.carmodelCollection = options.carmodelCollection; | ||
|
|
||
| // in these blocks of code, we bind to the "reset" event of each collection. | ||
| // notice we only render the specific combobox that had the "reset" event triggered, and don't just | ||
| // call a blind "render()" - that would cause unnecessary repaints | ||
| // | ||
| // we also mixin the ParamFetchCollection mixin to add some functionality to the Collections | ||
| if (this.activityObjectCollection) { | ||
| this.activityObjectCollection.on("reset", this.renderCountry, this); | ||
| _.extend(this.countryCollection, Backbone.ParamFetchCollection.prototype); | ||
| } | ||
| if (this.makeCollection) { | ||
| this.makeCollection.on("reset", this.renderMake, this); | ||
| _.extend(this.makeCollection, Backbone.ParamFetchCollection.prototype); | ||
| } | ||
| if (this.carmodelCollection) { | ||
| this.carmodelCollection.on("reset", this.renderCarmodel, this); | ||
| _.extend(this.carmodelCollection, Backbone.ParamFetchCollection.prototype); | ||
| } | ||
| }, | ||
|
|
||
| // handle the events from the comboboxes, but only the "all" choice (the others are handled by their respective views) | ||
| events : { | ||
| "click .all-country-filter" : "selectCountry", | ||
| "click .all-make-filter" : "selectMake", | ||
| "click .all-carmodel-filter" : "selectCarmodel" | ||
| }, | ||
|
|
||
| // render the entire view | ||
| // notice we construct our own JSON object here, to keep it simple and error-proof | ||
| // also notice we simply delegate the majority of the rendering to 3 small methods | ||
| render : function() { | ||
|
|
||
| var c = (this.countryCollection !== undefined); | ||
| var m = (this.makeCollection !== undefined); | ||
| var r = (this.carmodelCollection !== undefined); | ||
|
|
||
| var json = '{"country":' + c + ', "make":' + m + ', "carmodel":' + r + '}'; | ||
|
|
||
| var html = this.template($.parseJSON(json)); | ||
| this.$el.html(html); | ||
|
|
||
| if (this.countryCollection) { | ||
| this.renderCountry(); | ||
| } | ||
| if (this.makeCollection) { | ||
| this.renderMake(); | ||
| } | ||
| if (this.carmodelCollection) { | ||
| this.renderCarmodel(); | ||
| } | ||
|
|
||
| return this; | ||
| }, | ||
|
|
||
| // renders the country combobox | ||
| renderCountry : function() { | ||
| var _this = this; | ||
| // keep the first one, which is always "All Countries" | ||
| this.$el.find(".country-filter").slice(1).remove(); | ||
| // loop through each country, create a view, and have it rendered | ||
| // bind to the "select" event so we can capture events from this view in this object | ||
| this.countryCollection.each(function(country){ | ||
| var view = new CountryView({model:country}); | ||
| view.on("select", function(e, v){ | ||
| _this.selectCountry(e, v); | ||
| }); | ||
| _this.$el.find(".country-dropdown-menu").append(view.render().el); | ||
| }); | ||
| $(".country-filter").each(function(){ | ||
| if (_this.country == $(this).data("filter-type")) | ||
| { | ||
| $("#all-countries").find(".choice-title").text($(this).text()); | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| // renders the make combobox | ||
| renderMake : function() { | ||
| var _this = this; | ||
| // keep the first one, which is always "All Makes" | ||
| this.$el.find(".make-filter").slice(1).remove(); | ||
| // loop through each make, create a view, and have it rendered | ||
| // bind to the "select" event so we can capture events from this view in this object | ||
| this.makeCollection.each(function(make){ | ||
| var view = new MakeView({model:make}); | ||
| view.on("select", function(e, v){ | ||
| _this.selectMake(e, v); | ||
| }); | ||
| _this.$el.find(".make-dropdown-menu").append(view.render().el); | ||
| }); | ||
| $(".make-filter").each(function(){ | ||
| if (_this.make == $(this).data("filter-type")) | ||
| { | ||
| $("#all-makes").find(".choice-title").text($(this).text()); | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| // renders the carmodel combobox | ||
| renderCarmodel : function() { | ||
| var _this = this; | ||
| // keep the first one, which is always "All Cars" | ||
| this.$el.find(".carmodel-filter").slice(1).remove(); | ||
| // loop through each carmodel, create a view, and have it rendered | ||
| // bind to the "select" event so we can capture events from this view in this object | ||
| this.carmodelCollection.each(function(carmodel){ | ||
| var view = new CarModelView({model:carmodel}); | ||
| view.on("select", function(e, v){ | ||
| _this.selectCarmodel(e, v); | ||
| }); | ||
| _this.$el.find(".carmodel-dropdown-menu").append(view.render().el); | ||
| }); | ||
| $(".carmodel-filter").each(function(){ | ||
| if (_this.carmodel == $(this).data("filter-type")) | ||
| { | ||
| $("#all-carmodels").find(".choice-title").text($(this).text()); | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| // handles the events when one of the countries are selected | ||
| // we pass it the event "e", and the "view" so we can work with the objects | ||
| selectCountry : function(e, view) { | ||
| if (!view) { | ||
| this.country = "all"; | ||
| } | ||
| else { | ||
| // get the country from the view that triggered the event | ||
| this.country = view.model.get("name"); | ||
| } | ||
| this.make = "all"; | ||
| this.carmodel = "all"; | ||
|
|
||
| // fire an event so the Router can be aware of changes to the data | ||
| this.trigger("change_country", this.country); | ||
|
|
||
| // update the make combobox | ||
| this.makeCollection.fetchWithParams({}, {country_name: this.country}); | ||
|
|
||
| // update the car combobox | ||
| this.carmodelCollection.fetchWithParams({}, {country_name: this.country, make_name: this.make}); | ||
|
|
||
| // a little trick/hack to prevent the URL from putting a "#" at the end | ||
| // this is only required because we are overriding some undesired Bootstrap code | ||
| e.preventDefault(); | ||
|
|
||
| }, | ||
|
|
||
| selectMake : function(e, view) { | ||
| if (!view) { | ||
| this.make = "all"; | ||
| } | ||
| else | ||
| { | ||
| // get the country and make from the view that triggered the event | ||
| this.country = view.model.get("country_name"); | ||
| this.make = view.model.get("name"); | ||
| } | ||
| this.carmodel = "all"; | ||
|
|
||
| // fire an event so that Router can be aware of changes to the data | ||
| this.trigger("change_make", this.make); | ||
|
|
||
| // update the car combobox | ||
| this.carmodelCollection.fetchWithParams({},{make_name: this.make, country_name: this.country}); | ||
|
|
||
| // a little trick/hack to prevent the URL from putting a "#" at the end | ||
| // this is only required because we are overriding some undesired Bootstrap code | ||
| e.preventDefault(); | ||
| }, | ||
|
|
||
| selectCarmodel : function(e, view) { | ||
| if (!view) { | ||
| this.carmodel = "all"; | ||
| } | ||
| else | ||
| { | ||
| // get the country, make, and carmodel from the view that triggered the event | ||
| this.country = view.model.get("country_name"); | ||
| this.make = view.model.get("make_name"); | ||
| this.carmodel = view.model.get("name"); | ||
| } | ||
|
|
||
| // fire and event so that the Router can be aware of changes to the data | ||
| this.trigger("change_carmodel", this.carmodel); | ||
|
|
||
| // a little trick/hack to prevent the URL from putting a "#" at the end | ||
| // this is only required because we are overriding some undesired Bootstrap code | ||
| e.preventDefault(); | ||
| }, | ||
|
|
||
| // convenience functions to make setting data on this object easier | ||
| setFilter : function(country, make, carmodel) { | ||
| this.setCountry(country); | ||
| this.setMake(make); | ||
| this.setCarmodel(carmodel); | ||
| }, | ||
|
|
||
| setCountry : function(country) { | ||
| this.country = country; | ||
| }, | ||
|
|
||
| setMake : function(make) { | ||
| this.make = make; | ||
| }, | ||
|
|
||
| setCarmodel : function(carmodel) { | ||
| this.carmodel = carmodel; | ||
| } | ||
|
|
||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| { | ||
| "currentUser":{ | ||
| "displayName":"Monica Wilkinson", // Should have more info on user | ||
| "image":{ | ||
| "url":"https://fbcdn-profile-a.akamaihd.net/hprofile-ak-snc4/276322_608201527_2138067735_q.jpg" | ||
| } | ||
| }, | ||
| "providerFavicon": "/facebook.ico", // FIX to use Absolute | ||
| "streams":{ | ||
| "firehose":{ | ||
| "name":"firehose", | ||
| "items":[ | ||
| { | ||
| "title":"started", | ||
| "_id":"503e9cf914d6db492e000002", //ugly | ||
| "__v":0, // ugly | ||
| "streams":["firehose"], | ||
| "generator":null, | ||
| "provider":null, | ||
| "updated":"2012-08-29T22:51:37.503Z", | ||
| "published":"2012-08-29T22:51:37.503Z", | ||
| "target":"503e9cf914d6db492e000001", | ||
| "actor":{ | ||
| "displayName":"mwilkinson@vmware.com", | ||
| "image":{ | ||
| "url":"img/me.jpg" // FIX to use Absolute | ||
| } | ||
| }, | ||
| "object":{ | ||
| "displayName":"Instance 0 -- Local" // Can also have title, content, url, location | ||
| }, | ||
| "icon":null, | ||
| "verb":"start" | ||
| } | ||
| ] | ||
| } | ||
| }, | ||
| "desiredStream":"firehose", | ||
| "objectTypes":["Application", "Article", "Bookmark", "Comment", "Event", "File", "Folder", "Group", "List", "Note", "Person", "Photo", "Place", "Playlist", "Product", "Review", "Stream", "Service", "Song", "Status", "Video"], | ||
| "actorTypes":["Person", "Group", "Application", "Service"], | ||
| "verbs":["Post", "Favorite", "Follow", "Join", "Like", "Friend", "Play", "Save", "Share", "Tag", "Create", "Update", "Read", "Delete", "Check In"], | ||
| "usedVerbs":["start", "connect", "post", "disconnect", "like"], | ||
| "usedObjects":[ | ||
| { | ||
| "displayName":"Instance 0 -- Local" | ||
| }, | ||
| { | ||
| "url":"http://asms.cloudfoundry.com", | ||
| "title":"ASMS", | ||
| "objectType":"application", | ||
| "content":"Streams Data" | ||
| }, | ||
| { | ||
| "url":"http://ciberch.cloudfoundry.com", | ||
| "title":"A photo of Awesome", | ||
| "objectType":"photo", | ||
| "content":"Smell" | ||
| } | ||
| ], | ||
| "usedObjectTypes":["none", "application", "photo"], | ||
| "usedActorObjectTypes":["none"], | ||
| "usedActors":[ | ||
| { | ||
| "image":{ | ||
| "url":"img/me.jpg" // FIX to use Absolute | ||
| }, | ||
| "displayName":"mwilkinson@vmware.com" | ||
| }, | ||
| { | ||
| "image":{ | ||
| "url":"https://fbcdn-profile-a.akamaihd.net/hprofile-ak-snc4/276322_608201527_2138067735_q.jpg" | ||
| }, | ||
| "displayName":"Monica Wilkinson" | ||
| } | ||
| ]} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| var should = require('should') | ||
| , mongoose = require('mongoose') | ||
| , UserSchema = new mongoose.Schema() | ||
| , authPlugin = require('mongoose-auth') | ||
| , User; | ||
|
|
||
| UserSchema.plugin(authPlugin, { | ||
| password: true | ||
| }); | ||
|
|
||
| mongoose.model('User', UserSchema); | ||
| User = mongoose.model('User'); | ||
|
|
||
| describe('User', function () { | ||
| it('should generate a salt and set a hash when password is set', function () { | ||
| var user = new User(); | ||
| should.strictEqual(undefined, user.salt); | ||
| should.strictEqual(undefined, user.hash); | ||
| user.password = 'hello'; | ||
| user.password.should.equal('hello'); | ||
| user.salt.should.not.be.undefined; | ||
| user.hash.should.not.be.undefined; | ||
| }); | ||
| it('should authenticate with a correct password', function (done) { | ||
| var user = new User(); | ||
| user.password = 'hello'; | ||
| user.authenticate('hello', function (err, matched) { | ||
| matched.should.be.true; | ||
| done(); | ||
| }); | ||
| }); | ||
| it('should fail authentication with an incorrect password', function (done) { | ||
| var user = new User(); | ||
| user.password = 'correct'; | ||
| user.authenticate('incorrect', function (err, matched) { | ||
| matched.should.be.false; | ||
| done(); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| .row.actions | ||
| .span2 | ||
| small | ||
| a.btn.btn-mini.btn-warning.like-button(href="#") | ||
| i.icon-ok.icon-white | ||
| small Like | ||
| .span5 | ||
| .row | ||
| .span5.right | ||
| textarea.span4.comment-area() | ||
| .row | ||
| .span2.offset3.right#comment-post | ||
| small | ||
| a.btn.btn-mini.btn-success.comment-button(href="#") | ||
| i.icon-pencil.icon-white | ||
| small Post | ||
| .row.action_results | ||
| .span2 | ||
| -if (act.likes_count && act.likes_count > 0) { | ||
| small#like_count | ||
| #{act.likes_count} | ||
| small.rest | ||
| | liked this | ||
| -} | ||
| -if (act.comments) { | ||
| .span4 | ||
| ul | ||
| -for(var i=0; i < act.comments.length; i++) { | ||
| - var comment = comments[i]; | ||
| li | ||
| .row.comment | ||
| .span1.actor | ||
| - var actor = comment.actor; | ||
| include activity_stream_actor | ||
| .span6.action | ||
| .row.title | ||
| .span5 | ||
| strong=actor.displayName | ||
| | ||
| =comment.title | ||
| .row.activity_object | ||
| - var object = comment.object; | ||
| include activity_object | ||
| .row.details | ||
| .span5 | ||
| - var timedItem = comment; | ||
| include activity_details | ||
|
|
||
| - } | ||
| - } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,25 @@ | ||
| each act in activities | ||
| - if (!act.object) continue; | ||
| - var objType = " objectType-" + (act.object.objectType ? act.object.objectType : 'none'); | ||
| - var actorType = " actorType-" + (act.actor.objectType ? act.actor.objectType : 'none'); | ||
| - var className = "verb-" + act.verb + objType+actorType; | ||
| li(class=className) | ||
| .row.activity | ||
| .span1.actor | ||
| - var actor = act.actor; | ||
| include activity_stream_actor | ||
| .span6.action | ||
| .row.title | ||
| .span5 | ||
| strong=act.actor.displayName | ||
| | ||
| =act.title | ||
| .row.activity_object | ||
| - var object = act.object; | ||
| include activity_object | ||
| .row.details | ||
| .span5 | ||
| - var timedItem = act; | ||
| include activity_details | ||
| include actions | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,34 @@ | ||
| .row | ||
| .span8 | ||
| h3 Share something... | ||
| #new_activity | ||
| .row | ||
| include user-box | ||
| .span5.large-text | ||
| | Did you | ||
| span.btn-group#verb | ||
| a.btn.btn-info.dropdown-toggle(data-toggle="dropdown") | ||
| span#verb-show=chosenVerb | ||
| ul.dropdown-menu | ||
| each verb in locals.verbs | ||
| li | ||
| a.type-select(href="#", data-type-show="verb-show")=verb | ||
| | a new | ||
| span.btn-group#objectType | ||
| a.btn.btn-info.dropdown-toggle(data-toggle="dropdown") | ||
| span#object-show=chosenObject | ||
| ul.dropdown-menu | ||
| each objectType in locals.objectTypes | ||
| li | ||
| a.type-select(href="#", data-type-show="object-show")=objectType | ||
| | ? | ||
| .row | ||
| #specific-activity-input | ||
| .row#finish | ||
| .span10.offset2 | ||
| span.large-text Final Step: | ||
| button.btn.btn-success#send-message Publish | ||
| | the activity to the | ||
| span.input-prepend | ||
| span.add-on # | ||
| input#streamName.span2.prepend-fix(type="text", value=locals.desiredStream) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| .span3.timestamp | ||
| .small=(timedItem.userFriendlyDate ? timedItem.userFriendlyDate : timedItem.published) | ||
| - if (timedItem.provider && timedItem.provider.icon) { | ||
| .span3.timestamp | ||
| span Via | ||
| a(href="#{timedItem.provider.url}", target="_blank") | ||
| span=timedItem.provider.displayName | ||
| img.service(src= timedItem.provider.icon.url) | ||
| -} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| - var objUrl = object.url ? object.url : '#'; | ||
| .span6 | ||
| br | ||
| blockquote | ||
| -if (object.image && object.image.url) { | ||
| a(href="#{objUrl}") | ||
| img.img-rounded.avatar(src= object.image.url) | ||
| -} | ||
| -if (object.displayName) { | ||
| strong.activity-displayName=object.displayName | ||
| -} else if (object.title) { | ||
| strong.activity-title=object.title | ||
| -} | ||
| -if (object.content) { | ||
| .activity-content=object.content | ||
| -} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| - var actUrl = actor.url? actor.url: '#'; | ||
| -if (actor.image && actor.image.url) { | ||
| a.actor(href="#{actUrl}") | ||
| img.img-rounded.avatar(src= actor.image.url) | ||
| -} else { | ||
| h2 :-) | ||
| -} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| .row | ||
| .span8.offset1 | ||
| .row | ||
| .span8 | ||
| input.span8#input-title(type="text", placeholder="What is it called ?")#title | ||
| .row | ||
| .span8 | ||
| textarea.span8(placeholder="What does it do?")#msg | ||
| .row | ||
| .span8 | ||
| input.span8(type="text", placeholder="What is it's url ?")#url | ||
| .row | ||
| .span6 | ||
| label.checkbox | ||
| input#includeLocation(type="checkbox") | ||
| span Include Location ? | ||
| #map.hide |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| .row | ||
| .span8.offset1 | ||
| .row | ||
| .span8 | ||
| input.span8#input-title(type="text", placeholder="Title of Article")#title | ||
| .row | ||
| .span8 | ||
| textarea.span8(placeholder="Article Summary")#msg | ||
| .row | ||
| .span8 | ||
| input.span8(type="text", placeholder="Article's url ?")#url |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| ul.dropdown-menu | ||
| li.facebook | ||
| a(href="/auth/facebook") | ||
| img(src="/facebook.ico", alt="Facebook") | ||
| span Facebook | ||
| li.twitter | ||
| a(href="/auth/twitter") | ||
| img(src="/twitter.ico", alt="Twitter") | ||
| span Twitter | ||
| li.github | ||
| a(href="/auth/github") | ||
| img(src="/github.ico", alt="GitHub") | ||
| span Github |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,46 @@ | ||
| include nav_bar | ||
|
|
||
| .container | ||
| include start-modal | ||
| - var chosenVerb = locals.verbs[0]; | ||
| - var chosenObject = locals.objectTypes[0]; | ||
| - if(session.auth) | ||
| include activity_create | ||
| - else | ||
| .hero-unit.second-hero | ||
| .row | ||
| .span5 | ||
| a(href="http://blog.cloudfoundry.com/tag/asms/", target="_blank") | ||
| img.arch(src="/img/asms-blog.png") | ||
| .span5 | ||
| h1 Welcome | ||
| p This is a boilerplate Activity Streams App. Login or Sign Up to Share Activities ! | ||
| .btn-group | ||
| a.btn.btn-primary.dropdown-toggle(href="#", "data-toggle"="dropdown") Get Started via | ||
| include auth | ||
|
|
||
| .row | ||
| .span4#filters | ||
| .well.second-hero | ||
| - var included = locals.included | ||
| include filters | ||
| .span8#stream | ||
| h3 Activity Stream | ||
| .wello | ||
| .nav-tabs-container | ||
| -if (locals.streams) { | ||
| ul.nav.nav-tabs | ||
| each stream in locals.streams | ||
| -if (stream.items && stream.items.length>0) | ||
| li.active | ||
| a(href="/streams/#{stream.name}")=stream.name | ||
| -else | ||
| li | ||
| a(href="/streams/#{stream.name}")=stream.name | ||
| -} | ||
| ul.unstyled#main_stream | ||
| - var activities = locals.streams[desiredStream].items | ||
| -if (activities && activities.length) { | ||
| include activity | ||
| -} | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| .row | ||
| .span8.offset1 | ||
| .row | ||
| .span8 | ||
| input.span8#input-title(type="text", placeholder="Display Name")#title | ||
| .row | ||
| .span8 | ||
| input.span8(type="text", placeholder="Person's web page ?")#url |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| .row | ||
| - var photoTitle = act.object ? act.object.title : ''; | ||
| - var photoUrl = act.object ? act.object.url : ''; | ||
| - if(act.object && act.object.image) { | ||
| .span3#thumbnail.offset1 | ||
| img(src="#{act.object.image.url}") | ||
| - } else { | ||
| .span3#photo.offset1 | ||
| form#new_photo(method="post", enctype="multipart/form-data", action="/photos") | ||
| .row | ||
| .span3 | ||
| h4 Step 1: Select Photo | ||
| .row | ||
| .span3 | ||
| .input-file | ||
| input#input-file-input(type="file", name="image", accept="image/*", required="required") | ||
| .clearfix | ||
| hr | ||
| h4 | ||
| | Step 2: Upload to Cloud | ||
| i.icon-upload | ||
| input.btn.btn-warning#upload-file(type="submit", value="Upload File") | ||
| - } | ||
| .span6.offset1#details | ||
| .row | ||
| .span6 | ||
| h4 Step 3: Complete details | ||
| .row | ||
| .span6 | ||
| input.span6#input-title(type="text", placeholder="Describe the photo", value="#{photoTitle}")#title | ||
| .row | ||
| .span6 | ||
| input.span6(type="text", placeholder="Url of photo on another site ?", value="#{photoUrl}")#url | ||
| .row | ||
| .span6 | ||
| label.checkbox | ||
| input#includeLocation(type="checkbox") | ||
| span Include Location ? | ||
| #map.hide |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| .row | ||
| .span8.offset1 | ||
| .row | ||
| .span8 | ||
| input.span8#input-title(type="text", placeholder="What is it called ?")#title | ||
| .row | ||
| .span8 | ||
| textarea.span8(placeholder="Describe the place")#msg | ||
| .row | ||
| .span8 | ||
| input.span8(type="text", placeholder="Website for the place")#url | ||
| .row | ||
| .span6 | ||
| label.checkbox | ||
| input#includeLocation(type="checkbox") | ||
| span Where is this place ? | ||
| #map.hide |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| .row | ||
| .span8.offset1 | ||
| .row | ||
| .span8 | ||
| input.span8#input-title(type="text", placeholder="What is it called ?")#title | ||
| .row | ||
| .span8 | ||
| textarea.span8(placeholder="Describe the service.")#msg | ||
| .row | ||
| .span8 | ||
| input.span8(type="text", placeholder="What is it's url ?")#url | ||
| .row | ||
| .span6 | ||
| label.checkbox | ||
| input#includeLocation(type="checkbox") | ||
| span Include Location ? | ||
| #map.hide |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| .modal.hide.fade.main-hero#start-modal(role="dialog", aria-labelledby="Welcome Screen", aria-hidden="true") | ||
| .modal-header | ||
| button(type="button", class="close",data-dismiss="modal",aria-hidden="true") × | ||
| h2 | ||
| a(href="#") Node.js Activities Boilerplate App | ||
| span Publish activities to the stream of your choice. | ||
| span Consume streams in Real-time. | ||
|
|
||
| .modal-body | ||
| h4 To Use | ||
| p Start by describing new activities and content others may find interesting or you want to remember. | ||
| hr | ||
| h4 Note: | ||
| ul | ||
| li | ||
| span To change the actor you can | ||
| a(href="#") Login | ||
| span into different accounts via | ||
| a(href="#") OAuth | ||
| li This app can also be used programmatically via the API | ||
| .modal-footer | ||
| button.btn(data-dismiss="modal", aria-hidden="true") OK |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .user-box.span1 | ||
| a(href="#", rel="tooltip", title="#{locals.currentUser.displayName}") | ||
| img.avatar-small.img-rounded.avatar(src="#{locals.currentUser.image.url}") | ||
| .clearfix |