diff --git a/jstests/tests.html b/jstests/tests.html index c0476efe6..71390c226 100644 --- a/jstests/tests.html +++ b/jstests/tests.html @@ -12,5 +12,8 @@
+
+
+
diff --git a/jstests/tests/home/test_models.js b/jstests/tests/home/test_models.js new file mode 100644 index 000000000..965237e0e --- /dev/null +++ b/jstests/tests/home/test_models.js @@ -0,0 +1,20 @@ +define(function (require) { + "use strict"; + var models = require("home/models"); + + var MockApi = function () { + this.available_applications_info = function() { + return [{}, {}]; + }; + }; + + QUnit.module("home.models"); + QUnit.test("instantiation", function (assert) { + var mock_api = new MockApi(); + var model = new models.ApplicationListModel(mock_api); + assert.equal(model.data.length, 0); + model.update().done(function() { + assert.equal(model.data.length, 2); + }); + }); +}); diff --git a/jstests/tests/home/test_views.js b/jstests/tests/home/test_views.js new file mode 100644 index 000000000..61e95bff2 --- /dev/null +++ b/jstests/tests/home/test_views.js @@ -0,0 +1,44 @@ +define(function (require) { + "use strict"; + var models = require("home/models"); + var views = require("home/views"); + var $ = require("jquery"); + + var MockApi = function () { + this.available_applications_info = function() { + return [ + { + image : { + ui_name : "foo", + policy : { + allow_home: true + } + } + }, + { + image: { + ui_name : "bar", + policy : { + allow_home: true + } + } + }]; + }; + }; + + QUnit.module("home.views"); + QUnit.test("rendering", function (assert) { + var mock_api = new MockApi(); + var model = new models.ApplicationListModel(mock_api); + var view = new views.ApplicationListView(model); + model.update() + .done(function() { view.render(); } ) + .done(function() { + var applist = $("#applist"); + assert.equal(applist.children().length, 2); + assert.equal($("#applist > div:nth-child(1) > div > h4").text(), "foo"); + assert.equal($("#applist > div:nth-child(2) > div > h4").text(), "bar"); + }); + + }); +}); diff --git a/jstests/tests/test_utils.js b/jstests/tests/test_utils.js index 07171eee7..8b0da38aa 100644 --- a/jstests/tests/test_utils.js +++ b/jstests/tests/test_utils.js @@ -3,6 +3,6 @@ define(function (require) { QUnit.module("Utils"); QUnit.test("url_path_join", function (assert) { - assert.equal(utils.url_path_join("foo", "bar", "baz"), "foo/bar/baz") + assert.equal(utils.url_path_join("foo", "bar", "baz"), "foo/bar/baz"); }); }); diff --git a/jstests/testsuite.js b/jstests/testsuite.js index 3313dd95b..33d06a1a8 100644 --- a/jstests/testsuite.js +++ b/jstests/testsuite.js @@ -16,9 +16,15 @@ }); require([ - "tests/test_remoteappapi.js", - "tests/test_utils.js" + "tests/home/test_models.js", + "tests/home/test_views.js", + "tests/test_remoteappapi.js", + "tests/test_utils.js" ], function() { + window.apidata = { + base_url: "/", + prefix: "/" + }; QUnit.load(); QUnit.start(); }); diff --git a/remoteappmanager/static/js/home.js b/remoteappmanager/static/js/home.js deleted file mode 100644 index 551fcc95e..000000000 --- a/remoteappmanager/static/js/home.js +++ /dev/null @@ -1,183 +0,0 @@ -/*globals: require, console*/ -require( - ["jquery", "jhapi", "utils", "remoteappapi"], - function($, JHAPI, utils, RemoteAppAPI) { - "use strict"; - - var base_url = window.apidata.base_url; - var appapi = new RemoteAppAPI(base_url); - - // This model keeps the retrieved content from the REST query locally. - // It is only synchronized at initial load. - var application_model = []; - - var report_error = function (jqXHR, status, error) { - // Writes an error message resulting from an incorrect - // ajax operation. Parameters are from the resulting ajax. - var msg = utils.log_ajax_error(jqXHR, status, error); - $(".spawn-error-msg").text(msg).show(); - }; - - var render_applist_entry = function (index, info) { - // Returns a HTML snippet for a single application entry - // index: - // a progressive index for the entry. - // info: - // A dictionary containing the retrieved data about the application - // and (possibly) the container. - var html = '
'; - - if (info.image.icon_128 === '') { - html += ''; - } else { - html += ''; - } - - var name; - if (info.image.ui_name !== '') { - name = info.image.ui_name; - } else { - name = info.image.name; - } - html += '

'+name+'

'; - - var policy = info.image.policy; - var mount_html = ''; - - if (policy.allow_home) { - mount_html += "
  • Workspace
  • "; - } - if (policy.volume_source && policy.volume_target && policy.volume_mode) { - mount_html += "
  • "+ policy.volume_source + - " → " + - policy.volume_target + - " " + - "(" + policy.volume_mode + ")
  • "; - } - if (mount_html !== '') { - html += ""; - } - - html += '
    '; - - var cls, text, stop_style; - if (info.container !== null) { - cls = "view-button btn-success"; - text = " View"; - stop_style = ""; - } else { - cls = "start-button btn-primary"; - text = " Start"; - stop_style = 'style="visibility: hidden;"'; - } - html += '
    '; - html += ''; - html += '
    '; - html += '
    '; - html += ''; - html += '
    '; - html += '
    '; - return html; - }; - - var reset_buttons_to_start = function (index) { - // Used to revert the buttons to their "start" state when the - // User clicks on "stop". - $("#bnx_"+index) - .removeClass("view-button btn-success") - .addClass("start-button btn-primary"); - $("#bnx_"+index+" > span").text(" Start"); - $("#bny_"+index).hide(); - }; - - var render_applist = function () { - // Renders the full application list and adds it to the DOM. - var html = ""; - for (var i = 0; i < application_model.length; i++) { - var info = application_model[i]; - html += render_applist_entry(i, info); - } - $("#applist").html(html); - }; - - var x_button_clicked = function () { - // Triggered when the button X (left side) is clicked - var button = this; - var index = $(button).data("index"); - $(button).find(".fa-spinner").show(); - var app_info = application_model[index]; - - if (app_info.container !== null) { - // The container is already running, this is a View button - window.location = utils.url_path_join( - base_url, - "containers", - app_info.container.url_id); - } else { - // The container is not running. This is a start button. - var mapping_id = application_model[index].mapping_id; - appapi.start_application(mapping_id, { - error: function(jqXHR, status, error) { - report_error(jqXHR, status, error); - $(button).find(".fa-spinner").hide(); - }, - statusCode: { - 201: function (data, textStatus, request) { - var location = request.getResponseHeader('Location'); - var url = utils.parse_url(location); - var arr = url.pathname.replace(/\/$/, "").split('/'); - var url_id = arr[arr.length-1]; - $(button).find(".fa-spinner").hide(); - - window.location = utils.url_path_join( - base_url, - "containers", - url_id - ); - } - } - }); - } - }; - - var y_button_clicked = function () { - // Triggered when the button Y (right side) is clicked - var button = this; - var index = $(button).data("index"); - $(button).find(".fa-spinner").show(); - var app_info = application_model[index]; - - var url_id = app_info.container.url_id; - appapi.stop_application(url_id, { - success: function () { - $(button).find(".fa-spinner").hide(); - reset_buttons_to_start(index); - app_info.container = null; - }, - error: function (jqXHR, status, error) { - report_error(jqXHR, status, error); - $(button).find(".fa-spinner").hide(); - } - }); - }; - - var register_button_eventhandlers = function () { - // Registers the event handlers on the buttons after addition - // of the new entries to the list. - $(".bnx").click(x_button_clicked); - $(".bny").click(y_button_clicked); - }; - - var sync_local_model = function (app_data) { - application_model = app_data; - }; - - $.when(appapi.available_applications_info()) - .done(sync_local_model) - .done(render_applist) - .done(register_button_eventhandlers); - - } -); diff --git a/remoteappmanager/static/js/home/home.js b/remoteappmanager/static/js/home/home.js new file mode 100644 index 000000000..1cf9d3d27 --- /dev/null +++ b/remoteappmanager/static/js/home/home.js @@ -0,0 +1,72 @@ +/*globals: require, console*/ +require( + ["jquery", "utils", "remoteappapi", "home/models", "home/views"], + function($, utils, RemoteAppAPI, models, views) { + "use strict"; + + var base_url = window.apidata.base_url; + var appapi = new RemoteAppAPI(base_url); + + // This model keeps the retrieved content from the REST query locally. + // It is only synchronized at initial load. + var model = new models.ApplicationListModel(appapi); + var view = new views.ApplicationListView(model); + + var report_error = function (jqXHR, status, error) { + // Writes an error message resulting from an incorrect + // ajax operation. Parameters are from the resulting ajax. + var msg = utils.log_ajax_error(jqXHR, status, error); + $(".spawn-error-msg").text(msg).show(); + }; + + view.view_button_clicked = function (index) { + var app_info = model.data[index]; + + window.location = utils.url_path_join( + base_url, + "containers", + app_info.container.url_id); + }; + + view.stop_button_clicked = function (index) { + var app_info = model.data[index]; + + var url_id = app_info.container.url_id; + return appapi.stop_application(url_id, { + success: function () { + view.reset_buttons_to_start(index); + app_info.container = null; + }, + error: function (jqXHR, status, error) { + report_error(jqXHR, status, error); + }}); + }; + + view.start_button_clicked = function (index) { + // The container is not running. This is a start button. + var mapping_id = model.data[index].mapping_id; + return appapi.start_application(mapping_id, { + error: function(jqXHR, status, error) { + report_error(jqXHR, status, error); + }, + statusCode: { + 201: function (data, textStatus, request) { + var location = request.getResponseHeader('Location'); + var url = utils.parse_url(location); + var arr = url.pathname.replace(/\/$/, "").split('/'); + var url_id = arr[arr.length-1]; + + window.location = utils.url_path_join( + base_url, + "containers", + url_id + ); + } + } + }); + }; + + $.when(model.update()).done(function () { view.render(); }); + + } +); diff --git a/remoteappmanager/static/js/home/models.js b/remoteappmanager/static/js/home/models.js new file mode 100644 index 000000000..3324542d5 --- /dev/null +++ b/remoteappmanager/static/js/home/models.js @@ -0,0 +1,32 @@ +define(['jquery'], function ($) { + "use strict"; + + var ApplicationListModel = function(remote_app_api) { + // (constructor) Model for the application list. + // Parameters + // remote_app_api : RemoteAppAPI + // The remote application API stub object + this._appapi = remote_app_api; + + // Contains the data retrieved from the remote API + this.data = []; + }; + + ApplicationListModel.prototype.update = function() { + // Requests an update of the object internal data. + // This method returns a jQuery Promise object. + // When resolved, this.data will contain a list of the retrieved + // data. Note that, in error conditions, this routine resolves + // successfully in any case, and the data is set to empty list + var self = this; + return $.when( + self._appapi.available_applications_info() + ).done(function (app_data) { + self.data = app_data; + }); + }; + + return { + ApplicationListModel: ApplicationListModel + }; +}); diff --git a/remoteappmanager/static/js/home/views.js b/remoteappmanager/static/js/home/views.js new file mode 100644 index 000000000..a7c7d9955 --- /dev/null +++ b/remoteappmanager/static/js/home/views.js @@ -0,0 +1,149 @@ +define(["jquery", "utils"], function ($, utils) { + "use strict"; + + var ApplicationListView = function(model) { + // (Constructor) Represents the application list. In charge of + // rendering in on the div with id #applist + // + // Parameters + // model : ApplicationListModel + // The data model. + this.model = model; + var self = this; + + self.base_url = window.apidata.base_url; + + // Handlers for button clicking. + // replace them to override default behavior (doing nothing). + // Can return a value or a promise. + this.stop_button_clicked = function(index) {}; + this.start_button_clicked = function(index) {}; + this.view_button_clicked = function(index) {}; + + this._x_button_clicked = function () { + // Triggered when the button X (left side) is clicked + var button = this; + var index = $(button).data("index"); + $(button).find(".fa-spinner").show(); + + var app_info = self.model.data[index]; + + var hide_spinner = function () { + $(button).find(".fa-spinner").hide(); + }; + + if (app_info.container !== null) { + self.view_button_clicked(index).done(hide_spinner); + } else { + self.start_button_clicked(index).done(hide_spinner); + } + }; + + this._y_button_clicked = function () { + var button = this; + var index = $(button).data("index"); + $(button).find(".fa-spinner").show(); + self.stop_button_clicked(index).done(function () { + $(button).find(".fa-spinner").hide(); + }); + }; + }; + + ApplicationListView.prototype.render = function () { + // Renders the full application list and adds it to the DOM. + var html = ""; + for (var i = 0; i < this.model.data.length; i++) { + var info = this.model.data[i]; + html += this.render_applist_entry(i, info); + } + $("#applist").html(html); + this.register_button_eventhandlers(); + }; + + ApplicationListView.prototype.render_applist_entry = function (index, info) { + // Returns a HTML snippet for a single application entry + // index: + // a progressive index for the entry. + // info: + // A dictionary containing the retrieved data about the application + // and (possibly) the container. + var html = '
    '; + + if (info.image.icon_128) { + html += ''; + } else { + html += ''; + } + + var name; + if (info.image.ui_name) { + name = info.image.ui_name; + } else { + name = info.image.name; + } + html += '

    '+name+'

    '; + + var policy = info.image.policy; + var mount_html = ''; + + if (policy.allow_home) { + mount_html += "
  • Workspace
  • "; + } + if (policy.volume_source && policy.volume_target && policy.volume_mode) { + mount_html += "
  • "+ policy.volume_source + + " → " + + policy.volume_target + + " " + + "(" + policy.volume_mode + ")
  • "; + } + if (mount_html !== '') { + html += ""; + } + + html += '
    '; + + var cls, text, stop_style; + if (info.container !== null) { + cls = "view-button btn-success"; + text = " View"; + stop_style = ""; + } else { + cls = "start-button btn-primary"; + text = " Start"; + stop_style = 'style="visibility: hidden;"'; + } + html += '
    '; + html += ''; + html += '
    '; + html += '
    '; + html += ''; + html += '
    '; + html += '
    '; + return html; + }; + + ApplicationListView.prototype.reset_buttons_to_start = function (index) { + // Used to revert the buttons to their "start" state when the + // User clicks on "stop". + $("#bnx_"+index) + .removeClass("view-button btn-success") + .addClass("start-button btn-primary"); + $("#bnx_"+index+" > span").text(" Start"); + $("#bny_"+index).hide(); + }; + + ApplicationListView.prototype.register_button_eventhandlers = function () { + // Registers the event handlers on the buttons after addition + // of the new entries to the list. + $(".bnx").click(this._x_button_clicked); + $(".bny").click(this._y_button_clicked); + }; + + return { + ApplicationListView : ApplicationListView + }; + + +}); diff --git a/remoteappmanager/static/js/remoteappapi.js b/remoteappmanager/static/js/remoteappapi.js index 29ced8c73..e865fae2c 100644 --- a/remoteappmanager/static/js/remoteappapi.js +++ b/remoteappmanager/static/js/remoteappapi.js @@ -114,14 +114,14 @@ define(['jquery', 'utils'], function ($, utils) { .done(function (promises) { // Fills the local application model with the results of the // retrieve promises. - var data = []; - for (var i = 0; i < promises.length; i++) { - var result = promises[i]; - if (result[2].status === 200) { - data.push(result[0]); - } + var data = []; + for (var i = 0; i < promises.length; i++) { + var result = promises[i]; + if (result[2].status === 200) { + data.push(result[0]); } - promise.resolve(data); + } + promise.resolve(data); }); }); diff --git a/remoteappmanager/templates/home.html b/remoteappmanager/templates/home.html index d7065422b..4caf90400 100644 --- a/remoteappmanager/templates/home.html +++ b/remoteappmanager/templates/home.html @@ -29,6 +29,6 @@

    Available Applications

    {% block script %} {% endblock %} diff --git a/remoteappmanager/templates/page.html b/remoteappmanager/templates/page.html index ec4bcbc41..8ecee5c09 100644 --- a/remoteappmanager/templates/page.html +++ b/remoteappmanager/templates/page.html @@ -51,7 +51,7 @@ bootstrap: { deps: ["jquery"], exports: "bootstrap" - }, + } } });