diff --git a/app/assets/javascripts/facebook_share_widget/application.js.coffee b/app/assets/javascripts/facebook_share_widget/application.js.coffee
index 4276650..951b394 100644
--- a/app/assets/javascripts/facebook_share_widget/application.js.coffee
+++ b/app/assets/javascripts/facebook_share_widget/application.js.coffee
@@ -4,4 +4,13 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#= require_tree .
-#
\ No newline at end of file
+#
+
+window.app =
+ models: {}
+ views: {}
+
+window.facebookShareWidget =
+ callbacks:
+ success: (friend) ->
+ fail: (friend) ->
\ No newline at end of file
diff --git a/app/assets/javascripts/facebook_share_widget/facebook_friend.js.coffee b/app/assets/javascripts/facebook_share_widget/facebook_friend.js.coffee
new file mode 100644
index 0000000..f51426a
--- /dev/null
+++ b/app/assets/javascripts/facebook_share_widget/facebook_friend.js.coffee
@@ -0,0 +1,46 @@
+#= require underscore.js
+#= require backbone.js
+#= require handlebars-runtime.js
+
+$ ->
+ class FacebookFriend extends Backbone.Model
+ url: '/widget/facebook/share'
+
+ isShared: ->
+ @get('status') == 'shared'
+
+ shared: ->
+ @set('status': 'shared')
+
+ isSharing: ->
+ @get('status') == 'sharing'
+
+ startSharing: ->
+ @set('status': 'sharing')
+
+ shareFailedBecause: (reason)->
+ @set('status': 'failed', 'reason': reason)
+
+ isShareFailed: ->
+ @get('status') == 'failed'
+
+ share: (template) ->
+ friend = this
+ data = $.extend({}, $.parseJSON(template))
+ data.facebook_id = @id
+ data.message = @messageModel.content()
+ @startSharing()
+ $.ajax
+ url: @url
+ type: "post"
+ data: { post: data }
+ success: (resp) =>
+ friend.shared()
+ error: (jqXHR, textStatus, errorThrown) =>
+ json = $.parseJSON(jqXHR.responseText)
+ friend.shareFailedBecause(json.message)
+
+ setMessageModel: (model) ->
+ @messageModel = model
+
+ app.models.FacebookFriend = FacebookFriend
\ No newline at end of file
diff --git a/app/assets/javascripts/facebook_share_widget/facebook_friend_view.js.coffee b/app/assets/javascripts/facebook_share_widget/facebook_friend_view.js.coffee
new file mode 100644
index 0000000..6e6d6c4
--- /dev/null
+++ b/app/assets/javascripts/facebook_share_widget/facebook_friend_view.js.coffee
@@ -0,0 +1,50 @@
+#= require underscore.js
+#= require backbone.js
+#= require handlebars-runtime.js
+
+$ ->
+ class FacebookFriendView extends Backbone.View
+ tagName: 'li'
+ events:
+ 'click.share-button': 'shareToFriend'
+
+ initialize: ->
+ @model.on('change', @render, this)
+
+ render: ->
+ friend = @model.toJSON()
+ $(@el).html(HandlebarsTemplates['facebook_share_widget/templates/friend'](friend))
+ if @model.isShared()
+ @renderShared()
+ else if @model.isSharing()
+ @renderSharing()
+ else if @model.isShareFailed()
+ @renderFailed(friend)
+ else
+ @renderToShare(friend)
+ this
+
+ renderShared: ->
+ $(@el).append("
Shared
")
+ this
+
+ renderSharing: ->
+ $(@el).append(" Sharing
")
+ this
+
+ renderToShare: (friend) ->
+ $(@el).append("Share")
+ this
+
+ renderFailed: (friend) ->
+ $(@el).children('.error-message').text(friend.reason)
+ this
+
+ shareToFriend: (event) ->
+ event.preventDefault()
+ @model.share(@sharingTemplate())
+
+ sharingTemplate: ->
+ $('.template').text()
+
+ app.views.FacebookFriendView = FacebookFriendView
\ No newline at end of file
diff --git a/app/assets/javascripts/facebook_share_widget/facebook_share.js.coffee.erb b/app/assets/javascripts/facebook_share_widget/facebook_share.js.coffee.erb
index 03e5bb3..f11f104 100644
--- a/app/assets/javascripts/facebook_share_widget/facebook_share.js.coffee.erb
+++ b/app/assets/javascripts/facebook_share_widget/facebook_share.js.coffee.erb
@@ -1,52 +1,9 @@
#= require underscore.js
#= require backbone.js
#= require handlebars-runtime.js
+#= require facebook_share_widget/sharing_message
$ ->
- class FacebookFriend extends Backbone.Model
- url: '/widget/facebook/share'
-
- isShared: ->
- @get('status') == 'shared'
-
- shared: ->
- @set('status': 'shared')
-
- isSharing: ->
- @get('status') == 'sharing'
-
- startSharing: ->
- @set('status': 'sharing')
-
- shareFailedBecause: (reason)->
- @set('status': 'failed', 'reason': reason)
-
- isShareFailed: ->
- @get('status') == 'failed'
-
- share: (template) ->
- friend = this
- data = $.extend({}, $.parseJSON(template))
- data.facebook_id = @id
- data.message = @messageModel.content()
- @startSharing()
- $.ajax
- url: @url
- type: "post"
- data: { post: data }
- success: (resp) =>
- friend.shared()
- error: (jqXHR, textStatus, errorThrown) =>
- json = $.parseJSON(jqXHR.responseText)
- friend.shareFailedBecause(json.message)
-
- setMessageModel: (model) ->
- @messageModel = model
-
- class SharingMessage extends Backbone.Model
- content: ->
- @get('msg')
-
class NameSearch extends Backbone.Model
searchFilter: ->
@get('filter')
@@ -56,7 +13,7 @@ $ ->
class FacebookFriends extends Backbone.Collection
url: '/widget/facebook/friends'
- model: FacebookFriend
+ model: app.models.FacebookFriend
filterBy: (criteria) ->
unless criteria
@@ -94,53 +51,9 @@ $ ->
filterUpdate: ->
@model.setSearchFilter($('input.search-text').val())
- class FacebookFriendView extends Backbone.View
- tagName: 'li'
- events:
- 'click.share-button': 'shareToFriend'
-
- initialize: ->
- @model.on('change', @render, this)
-
- render: ->
- friend = @model.toJSON()
- $(@el).html(HandlebarsTemplates['facebook_share_widget/templates/friend'](friend))
- if @model.isShared()
- @renderShared()
- else if @model.isSharing()
- @renderSharing()
- else if @model.isShareFailed()
- @renderFailed(friend)
- else
- @renderToShare(friend)
- this
-
- renderShared: ->
- $(@el).append(" Shared
")
- this
-
- renderSharing: ->
- $(@el).append(" Sharing
")
- this
-
- renderToShare: (friend) ->
- $(@el).append("Share")
- this
-
- renderFailed: (friend) ->
- $(@el).children('.error-message').text(friend.reason)
- this
-
- shareToFriend: (event) ->
- event.preventDefault()
- @model.share(@sharingTemplate())
-
- sharingTemplate: ->
- $('.template').text()
-
class FacebookFriendsView extends Backbone.View
initialize: ->
- @sharingMessage = new SharingMessage
+ @sharingMessage = new app.models.SharingMessage
@sharingMessageView = new SharingMessageView(el: $('.message-pane'), model: @sharingMessage)
@sharingMessageView.render()
@@ -166,8 +79,9 @@ $ ->
@appendFriend friend for friend in filteredFriends
appendFriend: (friend) ->
- friendView = new FacebookFriendView(model: friend)
+ friendView = new app.views.FacebookFriendView(model: friend)
@$('#friends').append(friendView.render().el)
if $('#container').length
- new FacebookFriendsView(el: $('#container'))
\ No newline at end of file
+ new FacebookFriendsView(el: $('#container'))
+
diff --git a/app/assets/javascripts/facebook_share_widget/facebook_share_widget.js.coffee.erb b/app/assets/javascripts/facebook_share_widget/facebook_share_widget.js.coffee.erb
deleted file mode 100644
index a9c6df0..0000000
--- a/app/assets/javascripts/facebook_share_widget/facebook_share_widget.js.coffee.erb
+++ /dev/null
@@ -1,111 +0,0 @@
-root = exports ? this
-
-class @FacebookShareWidget
- throttleRate: 250
- friends: []
- base_path: ""
- template: {}
-
- constructor: (@container, @options) ->
- @base_path = @options.base_path
- @template = @options.template if @options.template?
- @callbacks = {
- success: @options.callbacks && options.callbacks.success ? facebookShareWidget.callbacks.success
- fail: @options.callbacks && options.callbacks.fail ? facebookShareWidget.callbacks.fail
- }
-
- $.ajax
- url: @base_path + "/facebook/friends"
- type: "get"
- dataType: "json"
- data: $.extend({}, @template)
-
- success: (resp) =>
- @friends = resp
- $("ul", @container).html(this.friendsToListItems())
- error: (jqXHR, textStatus, errorThrown) ->
- json = $.parseJSON(jqXHR.responseText)
- console.debug(json.message)
-
- $('input.search-text', @container).keydown (event) =>
- this.filter_list()
- #throttle(self, this.filter_list, this.throttleRate)
-
- $("ul", @container).on "click", "a", (event) =>
- link = $(event.target)
- id = link.data("facebook-id").toString()
- friend = this.friendWithId(id)
- message = $('textarea.message', @container).val()
-
- errorMessage = link.siblings(".error-message")
- indicator = $("\" />").insertBefore(link)
- link.remove()
- friend.status = "loading"
-
- data = $.extend({}, @template)
- data.facebook_id = id
- data.message = message
-
- $.ajax
- url: @base_path + "/facebook/share"
- type: "post"
- data: { post: data }
- success: (resp) =>
- indicator.attr src: '<%= asset_path("facebook_share_widget/tick.png") %>'
- friend.status = "loaded"
- @callbacks.success(friend)
- error: (jqXHR, textStatus, errorThrown) =>
- json = $.parseJSON(jqXHR.responseText)
- indicator.attr src: '<%= asset_path("facebook_share_widget/cross.png") %>'
- errorMessage.text(json.message)
- friend.status = null
- @callbacks.fail(friend)
-
- friendWithId: (id) ->
- for friend in @friends
- return friend if friend.id == id
-
- friendsToListItems: ->
- html = ""
- for friend in @friends
- html += this.friendToListItem(friend)
- html
-
- friendToListItem: (item) ->
- html = ""
- html += ""
- html += "#{item.name}"
- html += ""
- if item.status == "loading"
- html += "\" />"
- else if item.status == "loaded"
- html += "\" /> Shared
"
- else
- html += "Share"
- html += ''
-
- filter_list: =>
- text = $('input.search-text', @container).val()
- if !!text
- regex = new RegExp(text, "i")
- html = ""
- for friend in @friends
- if regex.test(friend.name)
- html += this.friendToListItem(friend)
- $("ul", @container).html(html)
- else
- $("ul", @container).html(this.friendsToListItems())
-
-# throttle: (context, fn, delay) ->
-# alert('foo')
-# timer = null
-# ->
-# args = arguments
-# clearTimeout(timer)
-# callback = -> fn.apply(context, args)
-# timer = setTimeout(callback, delay)
-
-root.facebookShareWidget =
- callbacks:
- success: (friend) ->
- fail: (friend) ->
diff --git a/app/assets/javascripts/facebook_share_widget/sharing_message.js.coffee b/app/assets/javascripts/facebook_share_widget/sharing_message.js.coffee
new file mode 100644
index 0000000..8d367d6
--- /dev/null
+++ b/app/assets/javascripts/facebook_share_widget/sharing_message.js.coffee
@@ -0,0 +1,5 @@
+$ ->
+ class SharingMessage extends Backbone.Model
+ content: ->
+ @get('msg')
+ app.models.SharingMessage = SharingMessage
\ No newline at end of file
diff --git a/app/views/facebook_share_widget/facebook/_widget.html.haml b/app/views/facebook_share_widget/facebook/_widget.html.haml
index 37ebe5a..fa755a7 100644
--- a/app/views/facebook_share_widget/facebook/_widget.html.haml
+++ b/app/views/facebook_share_widget/facebook/_widget.html.haml
@@ -5,6 +5,12 @@
.template.hide
= options[:template].to_json
+ .tick.hide
+ = image_tag "facebook_share_widget/tick.png"
+ .loader.hide
+ = image_tag "facebook_share_widget/loader.gif"
+ .logo.hide
+ = image_tag "facebook_share_widget/facebook-logo.png"
#container
= image_tag 'facebook_share_widget/loader.gif'
\ No newline at end of file
diff --git a/spec/coffeescripts/facebook_share_spec.js.coffee b/spec/coffeescripts/facebook_share_spec.js.coffee
index 4a141a4..290cdde 100644
--- a/spec/coffeescripts/facebook_share_spec.js.coffee
+++ b/spec/coffeescripts/facebook_share_spec.js.coffee
@@ -1,5 +1,30 @@
describe "FacebookFriend ", ->
+
+ beforeEach ->
+ window.HandlebarsTemplates = {}
+ window.HandlebarsTemplates['facebook_share_widget/templates/friend'] = (f) ->
+ setFixtures('')
+
+
it "should share message to friend", ->
-# facebookFriend = new FacebookFriend
- expect(1).toBe(1)
+
+ spyOn($, "ajax").andCallFake (options)->
+ options.success()
+ facebookFriend = new app.models.FacebookFriend
+ facebookFriend.setMessageModel(new app.models.SharingMessage( msg: 'msg'))
+ facebookFriendView = new app.views.FacebookFriendView(model: facebookFriend, el: $('#sandbox'))
+ facebookFriendView.render()
+
+ $('.share-button').click()
+
+ expect(facebookFriend.isShared()).toBe(true)
+
+ it "should share message with callback", ->
+
+
+
+
+
+
+
diff --git a/spec/javascripts/helpers/jasmine-jquery.js b/spec/javascripts/helpers/jasmine-jquery.js
new file mode 100644
index 0000000..7e85548
--- /dev/null
+++ b/spec/javascripts/helpers/jasmine-jquery.js
@@ -0,0 +1,288 @@
+var readFixtures = function() {
+ return jasmine.getFixtures().proxyCallTo_('read', arguments);
+};
+
+var preloadFixtures = function() {
+ jasmine.getFixtures().proxyCallTo_('preload', arguments);
+};
+
+var loadFixtures = function() {
+ jasmine.getFixtures().proxyCallTo_('load', arguments);
+};
+
+var setFixtures = function(html) {
+ jasmine.getFixtures().set(html);
+};
+
+var sandbox = function(attributes) {
+ return jasmine.getFixtures().sandbox(attributes);
+};
+
+var spyOnEvent = function(selector, eventName) {
+ jasmine.JQuery.events.spyOn(selector, eventName);
+}
+
+jasmine.getFixtures = function() {
+ return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
+};
+
+jasmine.Fixtures = function() {
+ this.containerId = 'jasmine-fixtures';
+ this.fixturesCache_ = {};
+ this.fixturesPath = 'spec/javascripts/fixtures';
+};
+
+jasmine.Fixtures.prototype.set = function(html) {
+ this.cleanUp();
+ this.createContainer_(html);
+};
+
+jasmine.Fixtures.prototype.preload = function() {
+ this.read.apply(this, arguments);
+};
+
+jasmine.Fixtures.prototype.load = function() {
+ this.cleanUp();
+ this.createContainer_(this.read.apply(this, arguments));
+};
+
+jasmine.Fixtures.prototype.read = function() {
+ var htmlChunks = [];
+
+ var fixtureUrls = arguments;
+ for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
+ htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
+ }
+
+ return htmlChunks.join('');
+};
+
+jasmine.Fixtures.prototype.clearCache = function() {
+ this.fixturesCache_ = {};
+};
+
+jasmine.Fixtures.prototype.cleanUp = function() {
+ jQuery('#' + this.containerId).remove();
+};
+
+jasmine.Fixtures.prototype.sandbox = function(attributes) {
+ var attributesToSet = attributes || {};
+ return jQuery('').attr(attributesToSet);
+};
+
+jasmine.Fixtures.prototype.createContainer_ = function(html) {
+ var container;
+ if(html instanceof jQuery) {
+ container = jQuery('');
+ container.html(html);
+ } else {
+ container = '' + html + '
'
+ }
+ jQuery('body').append(container);
+};
+
+jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
+ if (typeof this.fixturesCache_[url] == 'undefined') {
+ this.loadFixtureIntoCache_(url);
+ }
+ return this.fixturesCache_[url];
+};
+
+jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
+ var self = this;
+ var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
+ jQuery.ajax({
+ async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
+ cache: false,
+ dataType: 'html',
+ url: url,
+ success: function(data) {
+ self.fixturesCache_[relativeUrl] = data;
+ },
+ error: function(jqXHR, status, errorThrown) {
+ throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
+ }
+ });
+};
+
+jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
+ return this[methodName].apply(this, passedArguments);
+};
+
+
+jasmine.JQuery = function() {};
+
+jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
+ return jQuery('').append(html).html();
+};
+
+jasmine.JQuery.elementToString = function(element) {
+ return jQuery('').append(element.clone()).html();
+};
+
+jasmine.JQuery.matchersClass = {};
+
+(function(namespace) {
+ var data = {
+ spiedEvents: {},
+ handlers: []
+ };
+
+ namespace.events = {
+ spyOn: function(selector, eventName) {
+ var handler = function(e) {
+ data.spiedEvents[[selector, eventName]] = e;
+ };
+ jQuery(selector).bind(eventName, handler);
+ data.handlers.push(handler);
+ },
+
+ wasTriggered: function(selector, eventName) {
+ return !!(data.spiedEvents[[selector, eventName]]);
+ },
+
+ cleanUp: function() {
+ data.spiedEvents = {};
+ data.handlers = [];
+ }
+ }
+})(jasmine.JQuery);
+
+(function(){
+ var jQueryMatchers = {
+ toHaveClass: function(className) {
+ return this.actual.hasClass(className);
+ },
+
+ toBeVisible: function() {
+ return this.actual.is(':visible');
+ },
+
+ toBeHidden: function() {
+ return this.actual.is(':hidden');
+ },
+
+ toBeSelected: function() {
+ return this.actual.is(':selected');
+ },
+
+ toBeChecked: function() {
+ return this.actual.is(':checked');
+ },
+
+ toBeEmpty: function() {
+ return this.actual.is(':empty');
+ },
+
+ toExist: function() {
+ return this.actual.size() > 0;
+ },
+
+ toHaveAttr: function(attributeName, expectedAttributeValue) {
+ return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
+ },
+
+ toHaveId: function(id) {
+ return this.actual.attr('id') == id;
+ },
+
+ toHaveHtml: function(html) {
+ return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
+ },
+
+ toHaveText: function(text) {
+ if (text && jQuery.isFunction(text.test)) {
+ return text.test(this.actual.text());
+ } else {
+ return this.actual.text() == text;
+ }
+ },
+
+ toHaveValue: function(value) {
+ return this.actual.val() == value;
+ },
+
+ toHaveData: function(key, expectedValue) {
+ return hasProperty(this.actual.data(key), expectedValue);
+ },
+
+ toBe: function(selector) {
+ return this.actual.is(selector);
+ },
+
+ toContain: function(selector) {
+ return this.actual.find(selector).size() > 0;
+ },
+
+ toBeDisabled: function(selector){
+ return this.actual.is(':disabled');
+ },
+
+ // tests the existence of a specific event binding
+ toHandle: function(eventName) {
+ var events = this.actual.data("events");
+ return events && events[eventName].length > 0;
+ },
+
+ // tests the existence of a specific event binding + handler
+ toHandleWith: function(eventName, eventHandler) {
+ var stack = this.actual.data("events")[eventName];
+ var i;
+ for (i = 0; i < stack.length; i++) {
+ if (stack[i].handler == eventHandler) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ var hasProperty = function(actualValue, expectedValue) {
+ if (expectedValue === undefined) {
+ return actualValue !== undefined;
+ }
+ return actualValue == expectedValue;
+ };
+
+ var bindMatcher = function(methodName) {
+ var builtInMatcher = jasmine.Matchers.prototype[methodName];
+
+ jasmine.JQuery.matchersClass[methodName] = function() {
+ if (this.actual instanceof jQuery) {
+ var result = jQueryMatchers[methodName].apply(this, arguments);
+ this.actual = jasmine.JQuery.elementToString(this.actual);
+ return result;
+ }
+
+ if (builtInMatcher) {
+ return builtInMatcher.apply(this, arguments);
+ }
+
+ return false;
+ };
+ };
+
+ for(var methodName in jQueryMatchers) {
+ bindMatcher(methodName);
+ }
+})();
+
+beforeEach(function() {
+ this.addMatchers(jasmine.JQuery.matchersClass);
+ this.addMatchers({
+ toHaveBeenTriggeredOn: function(selector) {
+ this.message = function() {
+ return [
+ "Expected event " + this.actual + " to have been triggered on" + selector,
+ "Expected event " + this.actual + " not to have been triggered on" + selector
+ ];
+ };
+ return jasmine.JQuery.events.wasTriggered(selector, this.actual);
+ }
+ })
+});
+
+afterEach(function() {
+ jasmine.getFixtures().cleanUp();
+ jasmine.JQuery.events.cleanUp();
+});
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
index cff2396..f3da064 100644
--- a/spec/javascripts/support/jasmine.yml
+++ b/spec/javascripts/support/jasmine.yml
@@ -4,7 +4,6 @@ src_files:
- jquery.js
- underscore.js
- backbone.js
- - handlebars-runtime.js
- compiled/*.js
spec_dir: spec/javascripts