Adds most of the widgets admin section.

Project: http://git-wip-us.apache.org/repos/asf/rave/repo
Commit: http://git-wip-us.apache.org/repos/asf/rave/commit/fd95ab85
Tree: http://git-wip-us.apache.org/repos/asf/rave/tree/fd95ab85
Diff: http://git-wip-us.apache.org/repos/asf/rave/diff/fd95ab85

Branch: refs/heads/angular
Commit: fd95ab85bcae49c4461f481f42815ece7b9fde51
Parents: b0a4091
Author: Jmeas <[email protected]>
Authored: Fri Aug 22 17:16:35 2014 -0400
Committer: Jmeas <[email protected]>
Committed: Wed Aug 27 09:58:20 2014 -0400

----------------------------------------------------------------------
 .jshintrc-prod                                  |   1 -
 rave-portal-ng/mock-api/bootstrap.js            |   4 +
 .../mock-api/database/import-data/widgets.js    | 485 +++++++++++++++++++
 rave-portal-ng/mock-api/database/install.js     |   3 +-
 .../mock-api/modules/widget/endpoint.js         |  96 ++++
 .../mock-api/modules/widget/widget-util.js      |  55 +++
 .../mock-api/modules/widgets/endpoint.js        |  23 +
 .../mock-api/modules/widgets/widgets-util.js    |  64 +++
 .../src/subapps/admin/users/routes.js           |   2 +-
 .../subapps/admin/users/templates/users.html    |   2 +-
 rave-portal-ng/src/subapps/admin/users/users.js |   2 +-
 .../admin/widgets/controllers/search-form.js    |  74 +++
 .../subapps/admin/widgets/controllers/widget.js | 103 ++++
 .../admin/widgets/controllers/widgets.js        |  44 ++
 .../subapps/admin/widgets/resources/widget.js   |  21 +
 .../subapps/admin/widgets/resources/widgets.js  |  17 +
 .../src/subapps/admin/widgets/routes.js         |  54 +--
 .../admin/widgets/services/widgets-messages.js  |  48 ++
 .../subapps/admin/widgets/templates/detail.html | 154 ------
 .../subapps/admin/widgets/templates/widget.html | 137 ++++++
 .../admin/widgets/templates/widgets.html        | 136 +++---
 .../src/subapps/admin/widgets/widgets.js        |   8 +
 22 files changed, 1269 insertions(+), 264 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/.jshintrc-prod
----------------------------------------------------------------------
diff --git a/.jshintrc-prod b/.jshintrc-prod
index 3db027a..1a0a6fb 100644
--- a/.jshintrc-prod
+++ b/.jshintrc-prod
@@ -29,7 +29,6 @@
   "trailing"      : true,
   "maxdepth"      : 2,
   "maxstatements" : 30,
-  "maxcomplexity" : 6,
   "maxlen"        : 120,
 
   "asi"           : false,

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/bootstrap.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/bootstrap.js 
b/rave-portal-ng/mock-api/bootstrap.js
index 0c24cd8..048f91f 100644
--- a/rave-portal-ng/mock-api/bootstrap.js
+++ b/rave-portal-ng/mock-api/bootstrap.js
@@ -34,6 +34,10 @@ define(function(require) {
        api.registerEndpoint(require('./modules/users/endpoint'));
        api.registerEndpoint(require('./modules/user/endpoint'));
 
+       // widgets
+       api.registerEndpoint(require('./modules/widget/endpoint'));
+       api.registerEndpoint(require('./modules/widgets/endpoint'));
+
        // status
        api.registerEndpoint(require('./modules/status/endpoint'));
 

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/database/import-data/widgets.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/database/import-data/widgets.js 
b/rave-portal-ng/mock-api/database/import-data/widgets.js
new file mode 100644
index 0000000..b36c7d2
--- /dev/null
+++ b/rave-portal-ng/mock-api/database/import-data/widgets.js
@@ -0,0 +1,485 @@
+define(function(require) {
+  'use strict';
+
+  return [{
+    'title': 'Activity Stream',
+    'titleUrl': null,
+    'url': 
'https://raw.github.com/OpenSocial/examples/master/gadgets/activitystream/gadget.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'OpenSocial Foundation',
+    'authorEmail': null,
+    'description': 'OpenSocial Activity Stream gadget with support for 
Embedded Experiences',
+    'categories': ['Communications', 'Test Category 1'],
+    'status': 'PREVIEW',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': true
+  },
+  {
+    'title': 'Actions Example',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/actions_contributions.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Rave Project',
+    'authorEmail': null,
+    'description': 'Actions contributions examples',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Activity Streams',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/my_activity.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Anthony Carlucci',
+    'authorEmail': null,
+    'description': 'Static widget of activities for demoing on the Person 
Profile page',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Album Viewer',
+    'titleUrl': null,
+    'url': 
'http://localhost:8080/samplecontainer/examples/embeddedexperiences/AlbumViewer.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Apache Shindig',
+    'authorEmail': null,
+    'description': 'Sample Embedded Experience gadget from Shindig',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Current Schedule',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/schedule.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Anthony Carlucci',
+    'authorEmail': null,
+    'description': 'Static widget of a schedule for demoing on the Person 
Profile page',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'EE YouTube Example',
+    'titleUrl': null,
+    'url': 
'http://opensocial2.org:8080/collabapp/pages/home/embeddedExperiences/YouTube.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'OpenSocial Foundation',
+    'authorEmail': null,
+    'description': 'Example Youtube Embedded Experience Gadget',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'EE jQuery Photo Example',
+    'titleUrl': null,
+    'url': 
'http://accelerator.opensocial2.org/gadgets/photos/jquery_photo_ee.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'OpenSocial Foundation',
+    'authorEmail': null,
+    'description': 'Example jQuery Photo Embedded Experience Gadget',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Favorite Websites',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/favorite_websites.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Anthony Carlucci',
+    'authorEmail': null,
+    'description': 'Static widget of favorite websites for demoing on the 
Person Profile page',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Friends',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/friends.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Viknes Balasubramanee',
+    'authorEmail': null,
+    'description': 'This gadget display the list of friends for a particular 
user',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Gadget View Type',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/canvas-nav.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': null,
+    'authorEmail': null,
+    'description': null,
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Google News Gadget',
+    'titleUrl': null,
+    'url': 'http://www.gstatic.com/ig/modules/tabnews/tabnews.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': null,
+    'authorEmail': null,
+    'description': null,
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Herbie Hamster Virtual Pet',
+    'titleUrl': null,
+    'url': 
'http://hosting.gmodules.com/ig/gadgets/file/109548057311228444554/hamster.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Naj',
+    'authorEmail': null,
+    'description': 'A cute little hamster for you to feed and look after. 
Watch him follow your cursor around. Click on the more tab to treat him to a 
strawberry. Click him then put him on the wheel and watch him play! ***NEW: 
make Herbie hamster your very own!',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'List of CTSS Resources - Map View',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/CTSSResourcesMapView.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Suresh Deivasigamani',
+    'authorEmail': null,
+    'description': 'This is a gadget developed for Teragrid - OGCE project. 
Used Google gadgets API to retrieve the information from the Information 
Services REST Web Service and display the information using Google Maps API. 
This is a list of available CTSS resources and its details',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'My Experience',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/my_experience.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Anthony Carlucci',
+    'authorEmail': null,
+    'description': 'Static widget of experience for demoing on the Person 
Profile page',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'My Groups',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/my_groups.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Anthony Carlucci',
+    'authorEmail': null,
+    'description': 'Static widget of groups for demoing on the Person Profile 
page',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'NYTimes.com - Top Stories',
+    'titleUrl': null,
+    'url': 'http://widgets.nytimes.com/packages/html/igoogle/topstories.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': null,
+    'authorEmail': null,
+    'description': null,
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'OAuth2 Facebook',
+    'titleUrl': null,
+    'url': 
'http://localhost:8080/samplecontainer/examples/oauth2/oauth2_facebook.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Apache Shindig',
+    'authorEmail': null,
+    'description': 'Sample Facebook gadget from Shindig',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Ohloh Apache Rave COCOMO Estimates',
+    'titleUrl': null,
+    'url': 'http://www.ohloh.net/p/521520/widgets/project_cocomo.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Ohloh',
+    'authorEmail': null,
+    'description': 'Ohloh is an open source network that connects people 
through the software they create and use. Ohloh gadgets provide detailed 
statistics about open source software projects and developers.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Ohloh Apache Rave Factoids',
+    'titleUrl': null,
+    'url': 'http://www.ohloh.net/p/521520/widgets/project_factoids.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Ohloh',
+    'authorEmail': null,
+    'description': 'Ohloh is an open source network that connects people 
through the software they create and use. Ohloh gadgets provide detailed 
statistics about open source software projects and developers.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Ohloh Apache Rave Languages',
+    'titleUrl': null,
+    'url': 'http://www.ohloh.net/p/521520/widgets/project_languages.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Ohloh',
+    'authorEmail': null,
+    'description': 'Ohloh is an open source network that connects people 
through the software they create and use. Ohloh gadgets provide detailed 
statistics about open source software projects and developers.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Ohloh Apache Rave Stats',
+    'titleUrl': null,
+    'url': 'http://www.ohloh.net/p/521520/widgets/project_basic_stats.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Ohloh',
+    'authorEmail': null,
+    'description': 'Ohloh is an open source network that connects people 
through the software they create and use. Ohloh gadgets provide detailed 
statistics about open source software projects and developers.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Open Views Demo',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/open_views_demo.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Erin Noe-Payne',
+    'authorEmail': null,
+    'description': 'A demo gadget to show open views popups in action.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'OpenAJAX Hub Publisher',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/container/sample-pubsub-2-publisher.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Apache Shindig',
+    'authorEmail': null,
+    'description': 'OpenAJAX Hub publisher gadget from Shindig',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'OpenAJAX Hub Subscriber',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/container/sample-pubsub-2-subscriber.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Apache Shindig',
+    'authorEmail': null,
+    'description': 'OpenAJAX Hub subscriber gadget from Shindig',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Pet Hamster',
+    'titleUrl': null,
+    'url': 
'http://hosting.gmodules.com/ig/gadgets/file/112581010116074801021/hamster.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': null,
+    'authorEmail': null,
+    'description': null,
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Photo List',
+    'titleUrl': null,
+    'url': 
'http://localhost:8080/samplecontainer/examples/embeddedexperiences/PhotoList.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Apache Shindig',
+    'authorEmail': null,
+    'description': 'Sample Embedded Experience gadget from Shindig',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Translate Gadget',
+    'titleUrl': 'http://translate.google.com/',
+    'url': 'http://www.gstatic.com/ig/modules/dictionary/dictionary.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Google Taiwan',
+    'authorEmail': '[email protected]',
+    'description': 'Google Translation gadget.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'User Prefs Demo',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/user_prefs_demo.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'Anthony Carlucci',
+    'authorEmail': null,
+    'description': 'An example gadget which demos some of the different 
capabilities of user preferences.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Wikipedia',
+    'titleUrl': 'http://en.wikipedia.org/wiki/Main_Page',
+    'url': 'http://www.widget-dico.com/wikipedia/google/wikipedia.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'WidgetMe',
+    'authorEmail': '[email protected]',
+    'description': 'A Wikipedia Search and Go widget. Language choice.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  },
+  {
+    'title': 'Youtube',
+    'titleUrl': null,
+    'url': 'http://localhost:8080/demogadgets/youtubesearch.xml',
+    'thumbnailUrl': null,
+    'screenshotUrl': null,
+    'type': 'OpenSocial',
+    'author': 'David Olsen',
+    'authorEmail': null,
+    'description': 'A search module, which searches YouTube by tags like 
Politics News Life Music Family Photography Art Random Travel Personal Religion 
Movies Business Thoughts Media Humor Culture Poetry Christmas Writing Books 
Food Friends.',
+    'status': 'PUBLISHED',
+    'properties': null,
+    'disable': false,
+    'disabledMessage': null,
+    'featured': false
+  }];
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/database/install.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/database/install.js 
b/rave-portal-ng/mock-api/database/install.js
index 628770f..eba8c71 100644
--- a/rave-portal-ng/mock-api/database/install.js
+++ b/rave-portal-ng/mock-api/database/install.js
@@ -11,7 +11,8 @@ define(function(require) {
        var dataImport = {
                users: require('./import-data/users.js'),
                categories: require( './import-data/categories.js' ),
-               preferences: require( './import-data/preferences.js' )
+               preferences: require( './import-data/preferences.js' ),
+               widgets: require( './import-data/widgets.js' )
        };
 
        function importTables() {

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/modules/widget/endpoint.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/modules/widget/endpoint.js 
b/rave-portal-ng/mock-api/modules/widget/endpoint.js
new file mode 100644
index 0000000..41a25bb
--- /dev/null
+++ b/rave-portal-ng/mock-api/modules/widget/endpoint.js
@@ -0,0 +1,96 @@
+/*
+ * endpoint
+ * The /widget endpoint
+ *
+ */
+
+define(function(require) {
+  var Endpoint = require('../../util/endpoint');
+  var ErrorResponse = require('../../util/error-response');
+  var widgetUtil = require('./widget-util');
+  var categoriesUtil = require('../categories/categories-util');
+
+  var endpoint = new Endpoint({
+
+    url: '/widget/:id',
+
+    // Return our paginated data
+    get: function(url, data, headers, params, currentUser) {
+
+      // Get the widget's ID from the url
+      var widgetId = widgetUtil.idFromUrl(url);
+
+      // If the ID is invalid, throw an error
+      if (!_.isNumber(widgetId) || _.isNaN(widgetId)) {
+        return new ErrorResponse(400, 'Invalid widget ID');
+      }
+
+      // Retrieve the widget from the database
+      var widget = widgetUtil.get({ID:widgetId});
+
+      // If there's no widget then we return the 404
+      if (!widget) {
+        return new ErrorResponse(404, 'User does not exist.');
+      }
+
+      widget.categoriesList = categoriesUtil.getAllCategories();
+
+      // Return the widget
+      return [200, widget];
+    },
+
+    put: function(url, data, headers, params, currentUser) {
+
+      // Get the widget's ID from the url
+      var widgetId = widgetUtil.idFromUrl(url);
+
+      // If the ID is invalid, throw an error
+      if (!_.isNumber(widgetId) || _.isNaN(widgetId)) {
+        return new ErrorResponse(400, 'Invalid widget ID.');
+      }
+
+      // Return a 404 if the widget doesn't exist
+      if (!widgetUtil.widgetExists({ID:widgetId})) {
+        return new ErrorResponse(404, 'User does not exist.');
+      }
+
+      // Actually make the update
+      widgetUtil.updateUser({ID: widgetId}, data);
+
+      // We want to return the widget, so we retrieve them from the database
+      var widget = widgetUtil.get({ID:widgetId});
+
+      // If there's no widget then we return the 404
+      if (!widget) {
+        return new ErrorResponse(404, 'User does not exist.');
+      }
+
+      // Make the return
+      return [200, widget];
+    },
+
+    delete: function(url, data, headers, params, currentUser) {
+      
+      // Get the widget's ID from the url
+      var widgetId = widgetUtil.idFromUrl(url);
+
+      // If the ID is invalid, throw an error
+      if (!_.isNumber(widgetId) || _.isNaN(widgetId)) {
+        return new ErrorResponse(400, 'Invalid widget ID.');
+      }
+
+      // Return a 404 if the widget doesn't exist
+      if (!widgetUtil.widgetExists({ID:widgetId})) {
+        return new ErrorResponse(404, 'User does not exist.');
+      }
+
+      // Actually delete the widget
+      widgetUtil.deleteUser({ID:widgetId});
+
+      // Share that the widget was deleted
+      return [200, null];
+    }
+  });
+
+  return endpoint;
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/modules/widget/widget-util.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/modules/widget/widget-util.js 
b/rave-portal-ng/mock-api/modules/widget/widget-util.js
new file mode 100644
index 0000000..b9b1593
--- /dev/null
+++ b/rave-portal-ng/mock-api/modules/widget/widget-util.js
@@ -0,0 +1,55 @@
+/*
+ * widget-util
+ * Utilities for widget data
+ *
+ */
+
+define(function(require) {
+  var api = require('../../core');
+
+  var widgetUtil = {
+
+    // Retrieve from the widget's ID from the URL
+    idFromUrl: function(url) {
+      return parseInt(url.replace('/api/v1/widget/', ''), 10);
+    },
+
+    // Whether a particular widget exists or not
+    widgetExists: function(identifier) {
+      return !!widgetUtil.get(identifier);
+    },
+
+    // Retrieve the widget by an identifier.
+    get: function(identifier) {
+
+      var results = api.db.query('widgets', identifier);
+
+      var widget = results[0];
+
+      // If we don't have a widget, then we return false
+      if (!widget || results.length > 1) {
+        return false;
+      }
+
+      return widget;
+    },
+
+    // Update the widget's information in the database
+    updateUser: function(identifier, data) {
+
+      api.db.update('widgets', identifier, function(row) {
+        _.extend(row, data);
+        return row;
+      });
+      api.db.commit();
+    },
+
+    // Delete a widget by identifier
+    deleteUser: function(identifier) {
+      api.db.deleteRows('widgets', identifier);
+      api.db.commit();
+    }
+  };
+
+  return widgetUtil;
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/modules/widgets/endpoint.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/modules/widgets/endpoint.js 
b/rave-portal-ng/mock-api/modules/widgets/endpoint.js
new file mode 100644
index 0000000..76912c3
--- /dev/null
+++ b/rave-portal-ng/mock-api/modules/widgets/endpoint.js
@@ -0,0 +1,23 @@
+/*
+ * endpoint
+ * The /widgets endpoint
+ *
+ */
+
+define(function(require) {
+  var Endpoint = require('../../util/endpoint');
+  var widgetsUtil = require('./widgets-util');
+
+  var endpoint = new Endpoint({
+
+    url: '/widgets',
+
+    // Return our paginated data
+    get: function(url, data, headers, params, currentUser) {
+      var currentPage = params.page || 1;
+      return [200, widgetsUtil.getPage(currentPage, params.filter)];
+    }
+  });
+
+  return endpoint;
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/mock-api/modules/widgets/widgets-util.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/mock-api/modules/widgets/widgets-util.js 
b/rave-portal-ng/mock-api/modules/widgets/widgets-util.js
new file mode 100644
index 0000000..44eae51
--- /dev/null
+++ b/rave-portal-ng/mock-api/modules/widgets/widgets-util.js
@@ -0,0 +1,64 @@
+/*
+ * widgetsUtil
+ * Methods for interacting with the widgets data
+ *
+ */
+
+define(function(require) {
+  var api = require('../../core');
+  var preferencesUtil = require('../preferences/preferences-util');
+
+  var widgetsUtil = {
+
+    // Get every single user from the database.
+    getAll: function() {
+      return api.db.query('widgets');
+    },
+
+    // Get the list of all users
+    getPage: function(currentPage, filter) {
+
+      // Get the page size from the database
+      var pageSize = preferencesUtil.getPreference('pageSize');
+
+      var allWidgets = widgetsUtil.getAll();
+
+      // Filter them, if filter is passed
+      if (filter) {
+        allWidgets = _.filter(allWidgets, function(widget) {
+          return widget.title.toLowerCase().indexOf(filter.toLowerCase()) > -1;
+        });
+      }
+
+      // The first index to start from
+      var startIndex = (currentPage - 1) * pageSize;
+
+      // The naive end index. We may have overshot this.
+      var endIndex = currentPage * pageSize;
+
+      // Truncate our end index if it's too long. Slice only goes UP TO
+      // this index, which is why we don't use (length - 1)
+      if (endIndex > allWidgets.length) {
+        endIndex = allWidgets.length;
+      }
+
+      var results = allWidgets.slice(startIndex, endIndex);
+
+      var returnObj = {};
+
+      returnObj.data = results;
+      returnObj.metadata = {
+        pageSize: pageSize,
+        currentPage: currentPage,
+        start: startIndex+1,
+        end: endIndex,
+        pageCount: Math.ceil(allWidgets.length / pageSize),
+        totalUsers: allWidgets.length
+      };
+
+      return returnObj;
+    }
+  };
+
+  return widgetsUtil;
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/users/routes.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/users/routes.js 
b/rave-portal-ng/src/subapps/admin/users/routes.js
index b87dbdb..8d4aa54 100644
--- a/rave-portal-ng/src/subapps/admin/users/routes.js
+++ b/rave-portal-ng/src/subapps/admin/users/routes.js
@@ -36,7 +36,7 @@ define(function(require) {
 
         // Show a particular user's profile
         .state('portal.admin.users.detail', {
-          url: '/users/detail-:id',
+          url: '/detail-:id',
           templateUrl: '/subapps/admin/users/templates/user.html',
           authenticate: true,
           controller: userCtrl,

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/users/templates/users.html
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/users/templates/users.html 
b/rave-portal-ng/src/subapps/admin/users/templates/users.html
index 2ef9b78..2374a01 100644
--- a/rave-portal-ng/src/subapps/admin/users/templates/users.html
+++ b/rave-portal-ng/src/subapps/admin/users/templates/users.html
@@ -1,5 +1,5 @@
 <article ui-view>
-  <form class="form-horizontal search-form" ng-controller="searchCtrl">
+  <form class="form-horizontal search-form" ng-controller="userSearchCtrl">
     <div class="alert" ng-class="messageClassName()" 
ng-bind-html="messageHtml()" ng-show="showMessage()"></div>
     <h2>
       Showing {{ usersMeta.start }} - {{ usersMeta.end }} of {{ 
usersMeta.totalUsers }} results

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/users/users.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/users/users.js 
b/rave-portal-ng/src/subapps/admin/users/users.js
index a2320fb..b7d9876 100644
--- a/rave-portal-ng/src/subapps/admin/users/users.js
+++ b/rave-portal-ng/src/subapps/admin/users/users.js
@@ -25,7 +25,7 @@ define(function(require) {
   users.controller('usersCtrl', require('./controllers/users'));
   users.controller('userCtrl', require('./controllers/user'));
   users.controller('createUserCtrl', require('./controllers/create-user'));
-  users.controller('searchCtrl', require('./controllers/search-form'));
+  users.controller('userSearchCtrl', require('./controllers/search-form'));
 
   // Register the routes
   var routes = require('./routes');

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/controllers/search-form.js
----------------------------------------------------------------------
diff --git 
a/rave-portal-ng/src/subapps/admin/widgets/controllers/search-form.js 
b/rave-portal-ng/src/subapps/admin/widgets/controllers/search-form.js
new file mode 100644
index 0000000..483d5b7
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/controllers/search-form.js
@@ -0,0 +1,74 @@
+/*
+ * searchForm
+ * A controller for a widgets page. It keeps our
+ * data up-to-date as the user filters things.
+ *
+ */
+
+define(function(require) {
+
+  // Return the categories resource
+  return ['$scope', 'widgetsResource', 'pagination', '$state',
+  function($scope, widgetsResource, pagination, $state) {
+
+    // Our paginationPages
+    $scope.paginationPages = pagination.paginationPages;
+
+    $scope.prevPageDisabled = function() {
+      if (!$scope.currentPage) {
+        return 'disabled';
+      }
+      return $scope.currentPage === 1 ? 'disabled' : '';
+    };
+
+    $scope.nextPageDisabled = function() {
+      if (!$scope.widgetsMeta) {
+        return 'disabled';
+      }
+      var maxPage = $scope.currentPage === $scope.widgetsMeta.pageCount;
+      var noPages = $scope.widgetsMeta.pageCount === 0;
+      var onePage = $scope.widgetsMeta.pageCount === 1;
+      return maxPage || noPages || onePage ? 'disabled' : '';
+    };
+
+    // Whether or not we show the table
+    $scope.showResults = function() {
+
+      // If there aren't any users, we assume that there will be.
+      // So we still show the table.
+      if (!$scope.widgets) {
+        return true;
+      }
+
+      // If there are users, we won't show the table if the
+      // length is 0.
+      else {
+        return !!$scope.widgets.length;
+      }
+    };
+
+    $scope.search = function(options) {
+      var widgetsList = widgetsResource.get(options);
+
+      widgetsList.$promise
+        .then(function(response) {
+          $scope.widgets = response.data;
+          if ($scope.resetFilter) {
+            $scope.filter = '';
+          }
+
+          var widgetsMeta = widgetsList.metadata;
+
+          // Coerce each piece of metadata to a number.
+          _.each(widgetsMeta, function(val, key) {
+            widgetsMeta[key] = +val;
+          });
+
+          $scope.widgetsMeta = widgetsMeta;
+          $state.transitionTo('portal.admin.widgets', {page:1, 
filter:$scope.filter});
+        })
+        .catch(function() {
+        });
+    };
+  }];
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/controllers/widget.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/controllers/widget.js 
b/rave-portal-ng/src/subapps/admin/widgets/controllers/widget.js
new file mode 100644
index 0000000..d9a49a2
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/controllers/widget.js
@@ -0,0 +1,103 @@
+/*
+ * widget
+ * Handles submission of our form
+ *
+ */
+
+define(function(require) {
+  var $ = require('jquery');
+
+  return ['$scope', 'widgetResource', '$state', '$stateParams', 'widget', 
'widgetsMessages',
+  function($scope, widgetResource, $state, $stateParams, widget, 
widgetsMessages) {
+
+    $scope.widget = widget;
+
+    // The values to merge onto this scope once the promise resolves
+    var keys = [
+      'title',
+      'titleUrl',
+      'url',
+      'thumbnailUrl',
+      'screenshotUrl',
+      'type',
+      'author',
+      'authorEmail',
+      'description',
+      'status',
+      'properties',
+      'disable',
+      'disabledMessage',
+      'featured',
+      'categoriesList',
+      'categories'
+    ];
+
+    widget.$promise.then(function(res) {
+      _.extend($scope, _.pick(res, keys));
+
+      // This sets the selected categories on the scope based on what's 
returned
+      // by the server.
+      $scope.selectedCategories = _.filter($scope.categoriesList, 
function(category) {
+        return _.contains($scope.categories, category.text);
+      });
+
+    }).catch(function(err) {
+    });
+
+    // Remove the widget from the list of widget in the scope
+    this.removeFromList = function() {
+      var oldUser = _.findWhere($scope.widget, {ID:+$stateParams.id});
+      var oldIndex = _.indexOf($scope.widget, oldUser);
+      $scope.widgets.splice(oldIndex, 1);
+    };
+
+    // Replace the old item in the list with the new
+    this.updateList = function(newResource) {
+      var oldUser = _.findWhere($scope.widgets, {ID:+$stateParams.id});
+      var oldIndex = _.indexOf($scope.widgets, oldUser);
+      $scope.widgets[oldIndex] = newResource;
+      $scope.widget = newResource;
+    };
+
+    var ctrl = this;
+
+    // Keys that are present in what we receive from the server
+    // that we do not want to send back to the server
+    var blacklistedKeys = [
+      'categoriesList'
+    ];
+
+    $scope.onSave = function() {
+      var data = _.pick($scope, _.difference(keys, blacklistedKeys));
+      data.categories = _.pluck($scope.selectedCategories, 'text');
+      data.id = $stateParams.id;
+      
+      var savedResource = widgetResource.update(data);
+      
+      savedResource.$promise
+        .then(function(response) {
+          ctrl.updateList(response);
+          widgetsMessages.updateMessage(response.widgetname);
+          $state.transitionTo('portal.admin.widgets');
+        })
+        .catch(function(err) {
+        });
+    };
+
+    $scope.onDelete = function() {
+      var deletedResource = widgetResource.delete({
+        id: $stateParams.id
+      });
+
+      deletedResource.$promise
+        .then(function() {
+          ctrl.removeFromList();
+          $('#confirm-modal').modal('hide');
+          widgetsMessages.deleteMessage($scope.widget.widgetname);
+          $state.transitionTo('portal.admin.widgets');
+        })
+        .catch(function() {
+        });
+    };
+  }];
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/controllers/widgets.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/controllers/widgets.js 
b/rave-portal-ng/src/subapps/admin/widgets/controllers/widgets.js
new file mode 100644
index 0000000..4e113b9
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/controllers/widgets.js
@@ -0,0 +1,44 @@
+/*
+ * widgetsController
+ * Sets up our data & pagination.
+ *
+ */
+
+define(function(require) {
+  return ['$scope', '$stateParams', 'pagination', 'widgetsList', '$rootScope',
+  function($scope, $stateParams, pagination, widgetsList, $rootScope) {
+
+    $scope.currentPage = +$stateParams.page || 0;
+    $scope.filter = $stateParams.filter || '';
+
+    widgetsList.$promise.then(function() {
+      $scope.widgets = widgetsList.data;
+
+      var widgetsMeta = widgetsList.metadata;
+
+      // Coerce each piece of metadata to a number.
+      _.each(widgetsMeta, function(val, key) {
+        widgetsMeta[key] = +val;
+      });
+
+      $scope.widgetsMeta = widgetsMeta;
+    });
+
+    // Our paginationPages
+    $scope.paginationPages = pagination.paginationPages;
+
+    $scope.prevPageDisabled = function() {
+      if (!$scope.currentPage) {
+        return 'disabled';
+      }
+      return $scope.currentPage === 1 ? 'disabled' : '';
+    };
+
+    $scope.nextPageDisabled = function() {
+      if (!$scope.widgetsMeta) {
+        return 'disabled';
+      }
+      return $scope.currentPage === $scope.widgetsMeta.pageCount ? 'disabled' 
: '';
+    };
+  }];
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/resources/widget.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/resources/widget.js 
b/rave-portal-ng/src/subapps/admin/widgets/resources/widget.js
new file mode 100644
index 0000000..2ec0cfc
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/resources/widget.js
@@ -0,0 +1,21 @@
+/*
+ * widgetResource
+ * A resource for a single widget
+ *
+ */
+
+define(function(require) {
+
+  // The API endpoint for categories
+  var URL = '/api/v1/widget/:id';
+
+  // Return the categories resource
+  return ['$resource',
+  function($resource) {
+    return $resource(URL, {id: '@id'}, {
+      update: {
+        method: 'PUT'
+      }
+    });
+  }];
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/resources/widgets.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/resources/widgets.js 
b/rave-portal-ng/src/subapps/admin/widgets/resources/widgets.js
new file mode 100644
index 0000000..5783ba2
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/resources/widgets.js
@@ -0,0 +1,17 @@
+/*
+ * widgetsResource
+ * The resource for the widgets list
+ *
+ */
+
+define(function(require) {
+
+  // The API endpoint for categories
+  var URL = '/api/v1/widgets';
+
+  // Return the categories resource
+  return ['$resource',
+  function($resource) {
+    return $resource(URL);
+  }];
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/routes.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/routes.js 
b/rave-portal-ng/src/subapps/admin/widgets/routes.js
index 76af9f3..9eed602 100644
--- a/rave-portal-ng/src/subapps/admin/widgets/routes.js
+++ b/rave-portal-ng/src/subapps/admin/widgets/routes.js
@@ -7,45 +7,39 @@
  */
 
 define(function(require) {
+  var widgetsCtrl = require('./controllers/widgets');
+  var widgetCtrl = require('./controllers/widget');
+
   return ['$stateProvider', '$urlRouterProvider',
     function($stateProvider, $urlRouterProvider) {
       $stateProvider
         .state('portal.admin.widgets', {
-          url: '/widgets',
+          url: '/widgets?page&filter',
           templateUrl: '/subapps/admin/widgets/templates/widgets.html',
           authenticate: true,
-          controller: function($scope) {
-            $scope.currentPage = 0;
-            $scope.listSize = 5;
-            $scope.firstItem = function() {
-              return $scope.currentPage * $scope.listSize + 1;
-            };
-            $scope.lastItem = function() {
-              return $scope.firstItem() + $scope.listSize - 1;
-            };
-            $scope.numberOfPages = function() {
-              return Math.ceil( $scope.widgets.length / $scope.listSize );     
           
-            };
-            $scope.widgets = [
-              {name:'one', id:28},
-              {name:'two', id:29},
-              {name:'three', id:30},
-              {name:'four', id:31},
-              {name:'five', id:31},
-              {name:'six', id:31},
-              {name:'seven', id:31},
-              {name:'eight', id:31},
-              {name:'nine', id:31},
-              {name:'ten', id:31},
-              {name:'eleven', id:31},
-              {name:'twelve', id:31},
-            ];
+          controller: widgetsCtrl,
+          resolve: {
+            widgetsList: ['widgetsResource', '$stateParams',
+              function(widgetsResource, $stateParams) {
+                return widgetsResource.get({
+                  page: $stateParams.page,
+                  filter: $stateParams.filter
+                });
+              }
+            ]
           }
         })
         .state('portal.admin.widgets.detail', {
-          url: '/widgets/detail-:id',
-          templateUrl: '/subapps/admin/widgets/templates/detail.html',
-          authenticate: true
+          url: '/widget/:id',
+          templateUrl: '/subapps/admin/widgets/templates/widget.html',
+          authenticate: true,
+          controller: widgetCtrl,
+          resolve: {
+            widget: ['widgetResource', '$stateParams',
+              function(widgetResource, $stateParams) {
+                return widgetResource.get({id: $stateParams.id});
+              }]
+          }
         });
     }
   ];

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/services/widgets-messages.js
----------------------------------------------------------------------
diff --git 
a/rave-portal-ng/src/subapps/admin/widgets/services/widgets-messages.js 
b/rave-portal-ng/src/subapps/admin/widgets/services/widgets-messages.js
new file mode 100644
index 0000000..1316337
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/services/widgets-messages.js
@@ -0,0 +1,48 @@
+/*
+ * widgetsMessages
+ * A service to show messages on the widgets page. Because other
+ * states can cause messages to display (namely the category state),
+ * we need a service that's shared between the two.
+ *
+ */
+
+define(function() {
+  return function() {
+    var html = '';
+    var className = '';
+
+    return {
+      createMessage: function(username) {
+        html = 'Created widget "<b>' + username + '</b>"';
+        className = 'alert-success';
+      },
+
+      updateMessage: function(username) {
+        html = 'Updated widget "<b>' + username + '</b>"';
+        className = 'alert-success';
+      },
+
+      deleteMessage: function(username) {
+        html = 'Deleted widget "<b>' + username + '</b>"';
+        className = 'alert-success';
+      },
+
+      clearMessage: function() {
+        html = null;
+        className = '';
+      },
+
+      showMessage: function() {
+        return html ? true : false;
+      },
+
+      messageHtml: function() {
+        return html;
+      },
+
+      messageClassName: function() {
+        return className;
+      }
+    };
+  };
+});

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/templates/detail.html
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/templates/detail.html 
b/rave-portal-ng/src/subapps/admin/widgets/templates/detail.html
deleted file mode 100644
index 8b1aaa0..0000000
--- a/rave-portal-ng/src/subapps/admin/widgets/templates/detail.html
+++ /dev/null
@@ -1,154 +0,0 @@
-<a ui-sref="portal.admin.widgets">
-  « Back to widgets
-</a>
-<h2>
-  Activity Stream
-</h2>
-<section class="formbox">
-  <form id="updateWidget" class="form-horizontal" action="update" 
method="POST">
-    <legend>
-      Edit widget data
-      <div class="control-group pull-right">
-        <div class="controls">
-          <a href="#" class="btn btn-warning storeItemButton" 
id="fetchMetadataButton">
-            Update widget metadata
-          </a>
-        </div>
-      </div>
-    </legend>
-    <p>
-      Field marked with * are required
-    </p>
-    <div class="control-group">
-      <label for="title" class="control-label">
-        Title *
-      </label>
-      <div class="controls">
-        <input id="title" name="title" class="long" autofocus="autofocus" 
required="required" type="text" value="Activity Stream">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="url" class="control-label">
-        Location (URL) *
-      </label>
-      <div class="controls">
-        <input type="url" name="url" id="url" 
placeholder="http://example.com/widget.xml"; required="required" class="long" 
value="https://raw.github.com/OpenSocial/examples/master/gadgets/activitystream/gadget.xml";>
-      </div>
-    </div>
-    <div class="control-group">
-        <label for="type1" class="control-label">
-          Type *
-        </label> 
-        <div class="controls">
-          <label for="type1" class="radio">
-            <input id="type1" name="type" type="radio" value="OpenSocial" 
checked="checked">
-            &nbsp; OpenSocial
-          </label> 
-          <label for="type2" class="radio">
-            <input id="type2" name="type" type="radio" value="W3C">
-            &nbsp; W3C Widget
-          </label>
-        </div>
-    </div>
-    <div class="control-group">
-      <label for="description" class="control-label">
-        Description *
-      </label>
-      <div class="controls">
-        <textarea id="description" name="description" class="long" 
required="required">
-          OpenSocial Activity Stream gadget with support for Embedded 
Experiences
-        </textarea>
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="featured" class="control-label">
-        Featured
-      </label>
-      <div class="controls">
-        <input id="featured" name="featured" type="checkbox" value="true" 
checked="checked">
-        <input type="hidden" name="_featured" value="on">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="disableRendering" class="control-label">
-        Disable Widget
-      </label>
-      <div class="controls">
-        <input id="disableRendering" name="disableRendering" type="checkbox" 
value="true">
-        <input type="hidden" name="_disableRendering" value="on">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="disableRenderingMessage" class="control-label">
-        Disable Widget Message
-      </label>
-      <div class="controls">
-        <input id="disableRenderingMessage" name="disableRenderingMessage" 
class="long" autofocus="autofocus" type="text" value="">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="widgetStatus" class="control-label">
-        Status
-      </label>
-      <div class="controls">
-        <select id="widgetStatus" name="widgetStatus">
-          <option value="PUBLISHED" selected="selected">PUBLISHED</option>
-          <option value="PREVIEW">PREVIEW</option>
-        </select>
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="categories" class="control-label">Categories:</label>
-        <div class="controls">
-          <select id="categories" name="categories" multiple="multiple" 
size="10">
-            <option value="5">Communications</option>
-            <option value="2">News</option>
-            <option value="4">Projects</option>
-            <option value="1">Technology</option>
-            <option value="3">Travel</option>
-          </select>
-        <input type="hidden" name="_categories" value="1">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="thumbnailUrl" class="control-label">
-        Thumbnail</label>
-      <div class="controls">
-        <input type="url" name="thumbnailUrl" id="thumbnailUrl" 
placeholder="http://example.com/thumbnail.png"; class="long" value="">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="screenshotUrl" class="control-label">
-        Screenshot
-      </label>
-      <div class="controls">
-        <input type="url" name="screenshotUrl" id="screenshotUrl" 
placeholder="http://example.com/screenshot.png"; class="long" value="">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="titleUrl" class="control-label">
-        URL the widget title links to
-      </label>
-      <div class="controls">
-        <input type="url" name="titleUrl" id="titleUrl" class="long" value="">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="author" class="control-label">Author</label>
-      <div class="controls">
-        <input id="author" name="author" class="long" type="text" 
value="OpenSocial Foundation">
-      </div>
-    </div>
-    <div class="control-group">
-      <label for="authorEmail" class="control-label">
-        Author's email address
-      </label>
-      <div class="controls">
-        <input type="email" name="authorEmail" id="authorEmail" class="long" 
value="">
-      </div>
-    </div>
-    <fieldset>
-      <button class="btn btn-primary" type="submit" value="Update 
widget">Update widget</button>
-    </fieldset>
-  </form>
-</section>

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/templates/widget.html
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/templates/widget.html 
b/rave-portal-ng/src/subapps/admin/widgets/templates/widget.html
new file mode 100644
index 0000000..c74ead9
--- /dev/null
+++ b/rave-portal-ng/src/subapps/admin/widgets/templates/widget.html
@@ -0,0 +1,137 @@
+<a ui-sref="portal.admin.widgets">
+  « Back to widgets
+</a>
+<h2>
+  Activity Stream
+</h2>
+<section class="formbox">
+  <form id="updateWidget" class="form-horizontal">
+    <legend>
+      Edit widget data
+    </legend>
+    <div class="alert alert-info">
+      Field marked with * are required
+    </div>
+    <div class="control-group">
+      <label for="title" class="control-label">
+        Title *
+      </label>
+      <div class="controls">
+        <input id="title" name="title" class="long" autofocus="autofocus" 
required="required" type="text" ng-model="title">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="url" class="control-label">
+        Location (URL) *
+      </label>
+      <div class="controls">
+        <input type="url" name="url" id="url" 
placeholder="http://example.com/widget.xml"; required="required" class="long" 
ng-model="url">
+      </div>
+    </div>
+    <div class="control-group">
+        <label for="type1" class="control-label">
+          Type *
+        </label> 
+        <div class="controls">
+          <label for="type1" class="radio">
+            <input id="type1" name="type" type="radio" value="OpenSocial" 
ng-model="type">
+            &nbsp; OpenSocial
+          </label> 
+          <label for="type2" class="radio">
+            <input id="type2" name="type" type="radio" value="W3C" 
ng-model="type">
+            &nbsp; W3C Widget
+          </label>
+        </div>
+    </div>
+    <div class="control-group">
+      <label for="description" class="control-label">
+        Description *
+      </label>
+      <div class="controls">
+        <textarea id="description" name="description" class="long" 
required="required" ng-model="description"></textarea>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="featured" class="control-label">
+        Featured
+      </label>
+      <div class="controls">
+        <input id="featured" name="featured" type="checkbox" 
ng-model="featured">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="disableRendering" class="control-label">
+        Disable Widget
+      </label>
+      <div class="controls">
+        <input id="disableRendering" name="disableRendering" type="checkbox" 
ng-model="disable">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="disableRenderingMessage" class="control-label">
+        Disable Widget Message
+      </label>
+      <div class="controls">
+        <input id="disableRenderingMessage" name="disableRenderingMessage" 
class="long" autofocus="autofocus" type="text" ng-model="disabledMessage">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="widgetStatus" class="control-label">
+        Status
+      </label>
+      <div class="controls">
+        <select id="widgetStatus" name="widgetStatus" ng-model="status">
+          <option value="PUBLISHED">PUBLISHED</option>
+          <option value="PREVIEW">PREVIEW</option>
+        </select>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="categories" class="control-label">Categories:</label>
+        <div class="controls">
+          <select id="categories" name="categories" multiple 
ng-model="selectedCategories" ng-options="category.text for category in 
categoriesList">
+          </select>
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="thumbnailUrl" class="control-label">
+        Thumbnail</label>
+      <div class="controls">
+        <input type="url" name="thumbnailUrl" id="thumbnailUrl" 
placeholder="http://example.com/thumbnail.png"; class="long" 
ng-model="thumbnailUrl">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="screenshotUrl" class="control-label">
+        Screenshot
+      </label>
+      <div class="controls">
+        <input type="url" name="screenshotUrl" id="screenshotUrl" 
placeholder="http://example.com/screenshot.png"; class="long" 
ng-model="screenshotUrl">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="titleUrl" class="control-label">
+        URL the widget title links to
+      </label>
+      <div class="controls">
+        <input type="url" name="titleUrl" id="titleUrl" class="long" 
ng-model="titleUrl">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="author" class="control-label">Author</label>
+      <div class="controls">
+        <input id="author" name="author" class="long" type="text" 
ng-model="author">
+      </div>
+    </div>
+    <div class="control-group">
+      <label for="authorEmail" class="control-label">
+        Author's email address
+      </label>
+      <div class="controls">
+        <input type="email" name="authorEmail" id="authorEmail" class="long" 
ng-model="authorEmail">
+      </div>
+    </div>
+    <fieldset>
+      <button class="btn" type="submit" value="Update widget" 
ng-click="onSave()">Save Changes</button>
+    </fieldset>
+  </form>
+</section>

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/templates/widgets.html
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/templates/widgets.html 
b/rave-portal-ng/src/subapps/admin/widgets/templates/widgets.html
index 8a23395..9ce731d 100644
--- a/rave-portal-ng/src/subapps/admin/widgets/templates/widgets.html
+++ b/rave-portal-ng/src/subapps/admin/widgets/templates/widgets.html
@@ -1,81 +1,67 @@
 <article ui-view>
-  <h2>
-    Showing {{ firstItem() }} - {{ lastItem() }} of {{ widgets.length }} 
results
-  </h2>
-  <div class="searchHeading paginationHeading">
+  <form class="form-horizontal search-form" ng-controller="widgetSearchCtrl">
+    <h2>
+      Showing {{ widgetsMeta.start }} - {{ widgetsMeta.end }} of {{ 
widgetsMeta.totalUsers }} results
+    </h2>
+    <div class="searchHeading paginationHeading clearfix">
+      <div class="pagination">
+        <ul>
+          <li ng-switch on="prevPageDisabled()" ng-class="prevPageDisabled()">
+          <a ng-switch-when="disabled">&lt;</a>
+          <a ng-switch-default 
ui-sref="portal.admin.widgets({page:widgetsMeta.currentPage-1})">&lt;</a>
+        </li>
+        <li ng-repeat="n in paginationPages(widgetsMeta.currentPage, 
widgetsMeta.pageCount)" ng-class="{ active: n == widgetsMeta.currentPage }">
+          <a ui-sref="portal.admin.widgets({page:n})">{{ n }}</a>
+        </li>
+        <li ng-switch on="nextPageDisabled()" ng-class="nextPageDisabled()">
+          <a ng-switch-when="disabled">&gt;</a>
+          <a ng-switch-default 
ui-sref="portal.admin.widgets({page:widgetsMeta.currentPage+1})">&gt;</a>
+        </li>
+        </ul>
+      </div>
+      <form class="form-horizontal search-form" 
action="/portal/app/admin/widgets/search" method="get">
+        <fieldset>
+          <div class="input-append">
+            <input class="input-medium" type="search" id="searchTerm" 
name="searchTerm" value="" placeholder="Search Widgets" ng-model="filter">
+            <button class="btn btn-primary" type="submit" value="Search" 
ng-click="search({filter:filter})">Search</button>
+          </div>
+        </fieldset>
+      </form>
+    </div>
+    <table class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th>Title</th>
+          <th>Type</th>
+          <th>Status</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="widget in widgets">
+          <td>
+            <a ui-sref="portal.admin.widgets.detail({id: {{ widget.ID }}, 
page: null})">
+              {{ widget.title }}
+            </a>
+          </td>
+          <td>{{ widget.type }}</td>
+          <td>published</td>
+        </tr>
+      </tbody>
+    </table>
     <div class="pagination">
       <ul>
-        <li class="active"><a href="#">1</a></li>
-        <li><a href="?offset=10&amp;referringPageId=1">2</a></li>
-        <li><a href="?offset=20&amp;referringPageId=1">3</a></li>
-        <li><a href="?offset=10&amp;referringPageId=1">&gt;</a></li>
+        <li ng-switch on="prevPageDisabled()" ng-class="prevPageDisabled()">
+          <a ng-switch-when="disabled">&lt;</a>
+          <a ng-switch-default 
ui-sref="portal.admin.widgets({page:widgetsMeta.currentPage-1})">&lt;</a>
+        </li>
+        <li ng-repeat="n in paginationPages(widgetsMeta.currentPage, 
widgetsMeta.pageCount)" ng-class="{ active: n == widgetsMeta.currentPage }">
+          <a ui-sref="portal.admin.widgets({page:n})">{{ n }}</a>
+        </li>
+        <li ng-switch on="nextPageDisabled()" ng-class="nextPageDisabled()">
+          <a ng-switch-when="disabled">&gt;</a>
+          <a ng-switch-default 
ui-sref="portal.admin.widgets({page:widgetsMeta.currentPage+1})">&gt;</a>
+        </li>
       </ul>
     </div>
-    <form class="form-horizontal search-form" 
action="/portal/app/admin/widgets/search" method="get">
-      <fieldset>
-        <div class="input-append">
-          <input class="input-medium" type="search" id="searchTerm" 
name="searchTerm" value="" placeholder="Search Widgets">
-          <button class="btn btn-primary" type="submit" 
value="Search">Search</button>
-        </div>
-        <p>
-          <a href="#" data-toggle="collapse" data-target="#searchFilters">
-            Search Options...
-          </a>
-        </p>
-        <div id="searchFilters" class="collapse">
-          <select name="widgettype" id="widgettype" class="input-medium">
-            <option value="">
-              Choose Type...
-            </option>
-            <option value="OpenSocial">
-              OpenSocial
-            </option>
-            <option value="W3C">
-              W3C Widget
-            </option>
-          </select>
-          <select name="widgetstatus" id="widgetstatus" class="input-medium">
-            <option value="">
-              Choose Status...
-            </option>
-            <option value="published">
-              published
-            </option>
-            <option value="preview">
-              review
-            </option>
-          </select>
-        </div>
-      </fieldset>
-    </form>
-  </div>
-  <table class="table table-striped table-bordered table-condensed">
-    <thead>
-      <tr>
-        <th>Title</th>
-        <th>Type</th>
-        <th>Status</th>
-      </tr>
-    </thead>
-    <tbody>
-      <tr ng-repeat="widget in widgets | startFrom:currentPage*listSize | 
limitTo:listSize">
-        <td>
-          <a ui-sref="portal.admin.widgets.detail({id: {{ widget.id }}})">
-            {{ widget.name }}
-          </a>
-        </td>
-        <td>OpenSocial</td>
-        <td>published</td>
-      </tr>
-    </tbody>
-  </table>
-  <div class="pagination">
-    <ul>
-      <li class="active"><a href="#">1</a></li>
-      <li><a href="?offset=10&amp;referringPageId=1">2</a></li>
-      <li><a href="?offset=20&amp;referringPageId=1">3</a></li>
-      <li><a href="?offset=10&amp;referringPageId=1">&gt;</a></li>
-    </ul>
-  </div>
-
+  </form>
 </article>

http://git-wip-us.apache.org/repos/asf/rave/blob/fd95ab85/rave-portal-ng/src/subapps/admin/widgets/widgets.js
----------------------------------------------------------------------
diff --git a/rave-portal-ng/src/subapps/admin/widgets/widgets.js 
b/rave-portal-ng/src/subapps/admin/widgets/widgets.js
index 785bb84..1d4b1f4 100644
--- a/rave-portal-ng/src/subapps/admin/widgets/widgets.js
+++ b/rave-portal-ng/src/subapps/admin/widgets/widgets.js
@@ -18,6 +18,14 @@ define(function(require) {
   // Create our module
   var widgets = ng.module('admin.widgets', widgetsDependencies);
 
+  // Register our providers
+  widgets.factory('widgetsMessages', require('./services/widgets-messages'));
+  widgets.factory('widgetsResource', require('./resources/widgets'));
+  widgets.factory('widgetResource', require('./resources/widget'));
+  widgets.controller('widgetsCtrl', require('./controllers/widgets'));
+  widgets.controller('widgetCtrl', require('./controllers/widget'));
+  widgets.controller('widgetSearchCtrl', require('./controllers/search-form'));
+
   // Register the routes
   var routes = require('./routes');
   widgets.config(routes);

Reply via email to