/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /* global loop:true */ var loop = loop || {}; loop.shared = loop.shared || {}; loop.shared.views = (function(_, OT, l10n) { "use strict"; var sharedModels = loop.shared.models; /** * L10n view. Translates resulting view DOM fragment once rendered. */ var L10nView = (function() { var L10nViewImpl = Backbone.View.extend(), // Original View constructor originalExtend = L10nViewImpl.extend; // Original static extend fn /** * Patches View extend() method so we can hook and patch any declared render * method. * * @return {Backbone.View} Extended view with patched render() method. */ L10nViewImpl.extend = function() { var ExtendedView = originalExtend.apply(this, arguments), originalRender = ExtendedView.prototype.render; /** * Wraps original render() method to translate contents once they're * rendered. * * @return {Backbone.View} Extended view instance. */ ExtendedView.prototype.render = function() { if (originalRender) { originalRender.apply(this, arguments); l10n.translate(this.el); } return this; }; return ExtendedView; }; return L10nViewImpl; })(); /** * Base view. */ var BaseView = L10nView.extend({ /** * Hides view element. * * @return {BaseView} */ hide: function() { this.$el.hide(); return this; }, /** * Shows view element. * * @return {BaseView} */ show: function() { this.$el.show(); return this; }, /** * Base render implementation: renders an attached template if available. * * Note: You need to override this if you want to do fancier stuff, eg. * rendering the template using model data. * * @return {BaseView} */ render: function() { if (this.template) { this.$el.html(this.template()); } return this; } }); /** * Conversation view. */ var ConversationView = BaseView.extend({ className: "conversation", template: _.template([ '', '
', // Both these wrappers are required by the SDK; this is fragile and // will break if a future version of the SDK updates this generated DOM, // especially as the SDK seems to actually move wrapped contents into // their own generated stuff. '
', '
', '
' ].join("")), // height set to "auto" to fix video layout on Google Chrome // @see https://bugzilla.mozilla.org/show_bug.cgi?id=991122 videoStyles: { width: "100%", height: "auto", style: { "bugDisplayMode": "off" } }, events: { 'click .btn.stop': 'hangup' }, /** * Establishes webrtc communication using OT sdk. */ initialize: function(options) { options = options || {}; if (!options.sdk) { throw new Error("missing required sdk"); } this.sdk = options.sdk; this.listenTo(this.model, "session:connected", this.publish); this.listenTo(this.model, "session:stream-created", this._streamCreated); this.listenTo(this.model, ["session:peer-hungup", "session:network-disconnected", "session:ended"].join(" "), this.unpublish); this.model.startSession(); }, /** * Subscribes and attaches each created stream to a DOM element. * * XXX: for now we only support a single remote stream, hence a single DOM * element. * * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html * * @param {StreamEvent} event */ _streamCreated: function(event) { var incoming = this.$(".incoming").get(0); event.streams.forEach(function(stream) { if (stream.connection.connectionId !== this.model.session.connection.connectionId) { this.model.session.subscribe(stream, incoming, this.videoStyles); } }.bind(this)); }, /** * Hangs up current conversation. * * @param {MouseEvent} event */ hangup: function(event) { event.preventDefault(); this.unpublish(); this.model.endSession(); }, /** * Publishes remote streams available once a session is connected. * * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html * * @param {SessionConnectEvent} event */ publish: function(event) { var outgoing = this.$(".outgoing").get(0); this.publisher = this.sdk.initPublisher(outgoing, this.videoStyles); this.model.session.publish(this.publisher); }, /** * Unpublishes local stream. */ unpublish: function() { this.model.session.unpublish(this.publisher); }, /** * Renders this view. * * @return {ConversationView} */ render: function() { this.$el.html(this.template(this.model.toJSON())); return this; } }); /** * Notification view. */ var NotificationView = BaseView.extend({ template: _.template([ '
', ' ', '

<%- message %>

', '
' ].join("")), events: { "click .close": "dismiss" }, dismiss: function(event) { event.preventDefault(); this.$el.addClass("fade-out"); setTimeout(function() { this.collection.remove(this.model); this.remove(); }.bind(this), 500); // XXX make timeout value configurable }, render: function() { this.$el.html(this.template(this.model.toJSON())); return this; } }); /** * Notification list view. */ var NotificationListView = Backbone.View.extend({ /** * Constructor. * * Available options: * - {loop.shared.models.NotificationCollection} collection Notifications * collection * * @param {Object} options Options object */ initialize: function(options) { options = options || {}; if (!options.collection) { this.collection = new sharedModels.NotificationCollection(); } this.listenTo(this.collection, "reset add remove", this.render); }, /** * Clears the notification stack. */ clear: function() { this.collection.reset(); }, /** * Adds a new notification to the stack, triggering rendering of it. * * @param {Object|NotificationModel} notification Notification data. */ notify: function(notification) { this.collection.add(notification); }, /** * Adds a new notification to the stack using an l10n message identifier, * triggering rendering of it. * * @param {String} messageId L10n message id * @param {String} level Notification level */ notifyL10n: function(messageId, level) { this.notify({ message: l10n.get(messageId), level: level }); }, /** * Adds a warning notification to the stack and renders it. * * @return {String} message */ warn: function(message) { this.notify({level: "warning", message: message}); }, /** * Adds a l10n warning notification to the stack and renders it. * * @param {String} messageId L10n message id */ warnL10n: function(messageId) { this.warn(l10n.get(messageId)); }, /** * Adds an error notification to the stack and renders it. * * @return {String} message */ error: function(message) { this.notify({level: "error", message: message}); }, /** * Adds a l10n rror notification to the stack and renders it. * * @param {String} messageId L10n message id */ errorL10n: function(messageId) { this.error(l10n.get(messageId)); }, /** * Renders this view. * * @return {loop.shared.views.NotificationListView} */ render: function() { this.$el.html(this.collection.map(function(notification) { return new NotificationView({ model: notification, collection: this.collection }).render().$el; }.bind(this))); return this; } }); /** * Unsupported Browsers view. */ var UnsupportedBrowserView = BaseView.extend({ template: _.template([ '
', '

', '

', '

', '

', '
' ].join("")) }); /** * Unsupported Browsers view. */ var UnsupportedDeviceView = BaseView.extend({ template: _.template([ '
', '

', '

', '

', '
' ].join("")) }); return { L10nView: L10nView, BaseView: BaseView, ConversationView: ConversationView, NotificationListView: NotificationListView, NotificationView: NotificationView, UnsupportedBrowserView: UnsupportedBrowserView, UnsupportedDeviceView: UnsupportedDeviceView }; })(_, window.OT, document.webL10n || document.mozL10n);