Preparation to bower release
diff --git a/src/index.ngdoc.js b/src/index.ngdoc.js
new file mode 100644
index 0000000..6f6a77c
--- /dev/null
+++ b/src/index.ngdoc.js
@@ -0,0 +1,13 @@
+/**
+* @ngdoc overview
+* @name ngXosLib
+* @id index
+* @description
+* # Welcome to the ngXosLib documentation! <br/>
+* This is the module that group all the helpers service and UI components for XOS.
+* <br/><br/>
+* You can find all the documentation related to the UI Component Library here: <a href="#/module/xos.uiComponents"> xos.uiComponents</a> <br/>
+* and the documentation related to all the other helpers here: <a href="#/module/xos.helpers"> xos.helpers</a> <br/>
+* ## Issues
+* Please report issues at https://jira.opencord.org
+**/
\ No newline at end of file
diff --git a/src/services/csrfToken.interceptor.js b/src/services/csrfToken.interceptor.js
new file mode 100644
index 0000000..5f87de5
--- /dev/null
+++ b/src/services/csrfToken.interceptor.js
@@ -0,0 +1,24 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.helpers.SetCSRFToken
+ * @description This factory is automatically loaded trough xos.helpers and will add an $http interceptor that will the CSRF-Token to your request headers
+ **/
+
+ angular
+ .module('xos.helpers')
+ .factory('SetCSRFToken', setCSRFToken);
+
+ function setCSRFToken($cookies) {
+ return {
+ request: function(request){
+ if(request.method !== 'GET'){
+ request.headers['X-CSRFToken'] = $cookies.get('xoscsrftoken');
+ }
+ return request;
+ }
+ };
+ }
+})();
diff --git a/src/services/helpers/ui/comparator.service.js b/src/services/helpers/ui/comparator.service.js
new file mode 100644
index 0000000..24bf9f9
--- /dev/null
+++ b/src/services/helpers/ui/comparator.service.js
@@ -0,0 +1,96 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.uiComponents.Comparator
+ * @description
+ * This factory define a function that replace the native angular.filter comparator.
+ *
+ * It is done to allow the comparation between (0|1) values with booleans.
+ * >Note that this factory return a single function, not an object.
+ *
+ * The tipical usage of this factory is inside an `ng-repeat`
+ * @example
+ * <example module="comparator">
+ * <file name="index.html">
+ * <div ng-controller="sample as vm">
+ * <div class="row">
+ * <div class="col-xs-6">
+ * <label>Filter by name:</label>
+ * <input class="form-control" type="text" ng-model="vm.query.name"/>
+ * </div>
+ * <div class="col-xs-6">
+ * <label>Filter by status:</label>
+ * <select
+ * ng-model="vm.query.status"
+ * ng-options="i for i in [true, false]">
+ * </select>
+ * </div>
+ * </div>
+ * <div ng-repeat="item in vm.data | filter:vm.query:vm.comparator">
+ * <div class="row">
+ * <div class="col-xs-6">{{item.name}}</div>
+ * <div class="col-xs-6">{{item.status}}</div>
+ * </div>
+ * </div>
+ * </div>
+ * </file>
+ * <file name="script.js">
+ * angular.module('comparator', ['xos.uiComponents'])
+ * .controller('sample', function(Comparator){
+ * this.comparator = Comparator;
+ * this.data = [
+ * {name: 'Jhon', status: 1},
+ * {name: 'Jack', status: 0},
+ * {name: 'Mike', status: 1},
+ * {name: 'Scott', status: 0}
+ * ];
+ * });
+ * </file>
+ * </example>
+ **/
+
+ angular
+ .module('xos.uiComponents')
+ .factory('Comparator', comparator);
+
+ function comparator(_) {
+
+ return function(actual, expected){
+
+ if (angular.isUndefined(actual)) {
+ // No substring matching against `undefined`
+ return false;
+ }
+ if ((actual === null) || (expected === null)) {
+ // No substring matching against `null`; only match against `null`
+ return actual === expected;
+ }
+ if (angular.isObject(expected) || (angular.isObject(actual))){
+ return angular.equals(expected, actual);
+ }
+
+ if(_.isBoolean(actual) || _.isBoolean(expected)){
+ if(actual === 0 || actual === 1){
+ actual = !!actual;
+ }
+ return angular.equals(expected, actual);
+ }
+
+ if(!angular.isString(actual) || !angular.isString(expected)){
+ if(angular.isDefined(actual.toString) && angular.isDefined(expected.toString)){
+ actual = actual.toString();
+ expected = expected.toString();
+ }
+ else {
+ return actual === expected;
+ }
+ }
+
+ actual = actual.toLowerCase() + '';
+ expected = expected.toLowerCase() + '';
+ return actual.indexOf(expected) !== -1;
+ };
+ }
+})();
diff --git a/src/services/helpers/ui/form.helpers.js b/src/services/helpers/ui/form.helpers.js
new file mode 100644
index 0000000..582cb8a
--- /dev/null
+++ b/src/services/helpers/ui/form.helpers.js
@@ -0,0 +1,152 @@
+(function () {
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc service
+ * @name xos.uiComponents.XosFormHelpers
+ * @requires xos.uiComponents.LabelFormatter
+ * @requires xos.helpers._
+ **/
+
+ .service('XosFormHelpers', function(_, LabelFormatter){
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.XosFormHelpers#_isEmail
+ * @methodOf xos.uiComponents.XosFormHelpers
+ * @description
+ * Return true if the string is an email address
+ * @param {string} text The string to be evaluated
+ * @returns {boolean} If the string match an email format
+ **/
+
+ this._isEmail = (text) => {
+ const re = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
+ return re.test(text);
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.XosFormHelpers#_getFieldFormat
+ * @methodOf xos.uiComponents.XosFormHelpers
+ * @description
+ * Return the type of the input
+ * @param {mixed} value The data to be evaluated
+ * @returns {string} The type of the input
+ **/
+
+ this._getFieldFormat = (value) => {
+
+ if(angular.isArray(value)){
+ return 'array';
+ }
+
+ // check if is date
+ if (_.isDate(value) || (!Number.isNaN(Date.parse(value)) && new Date(value).getTime() > 631180800000)){
+ return 'date';
+ }
+
+ // check if is boolean
+ // isNaN(false) = false, false is a number (0), true is a number (1)
+ if(typeof value === 'boolean'){
+ return 'boolean';
+ }
+
+ // check if a string is an email
+ if(this._isEmail(value)){
+ return 'email';
+ }
+
+ // if null return string
+ if(angular.isString(value) || value === null){
+ return 'text';
+ }
+
+ return typeof value;
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.XosFormHelpers#buildFormStructure
+ * @methodOf xos.uiComponents.XosFormHelpers
+ * @description
+ * Return the type of the input
+ * @param {object} modelField An object containing one property for each field of the model
+ * @param {object} customField An object containing one property for each field custom field
+ * @param {object} model The actual model on wich build the form structure (it is used to determine the type of the input)
+ * @returns {object} An object describing the form structure in the form of:
+ * ```
+ * {
+ * 'field-name': {
+ * label: 'Label',
+ * type: 'number', //typeof field
+ * validators: {}, // see xosForm for more details
+ * hint: 'A Custom hint for the field'
+ * }
+ * }
+ * ```
+ **/
+
+ this.buildFormStructure = (modelField, customField, model) => {
+
+ modelField = angular.extend(modelField, customField);
+ customField = customField || {};
+
+ return _.reduce(Object.keys(modelField), (form, f) => {
+
+ form[f] = {
+ label: (customField[f] && customField[f].label) ? `${customField[f].label}:` : LabelFormatter.format(f),
+ type: (customField[f] && customField[f].type) ? customField[f].type : this._getFieldFormat(model[f]),
+ validators: (customField[f] && customField[f].validators) ? customField[f].validators : {},
+ hint: (customField[f] && customField[f].hint)? customField[f].hint : '',
+ };
+
+ if(customField[f] && customField[f].options){
+ form[f].options = customField[f].options;
+ }
+ if(customField[f] && customField[f].properties){
+ form[f].properties = customField[f].properties;
+ }
+ if(form[f].type === 'date'){
+ model[f] = new Date(model[f]);
+ }
+
+ if(form[f].type === 'number'){
+ model[f] = parseInt(model[f], 10);
+ }
+
+ return form;
+ }, {});
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.XosFormHelpers#parseModelField
+ * @methodOf xos.uiComponents.XosFormHelpers
+ * @description
+ * Helpers for buildFormStructure, convert a list of model properties in an object used to build the form structure, eg:
+ * ```
+ * // input:
+ * ['id', 'name'm 'mail']
+ *
+ * // output
+ * {
+ * id: {},
+ * name: {},
+ * mail: {}
+ * }
+ * ```
+ * @param {array} fields An array of fields representing the model properties
+ * @returns {object} An object containing one property for each field of the model
+ **/
+
+ this.parseModelField = (fields) => {
+ return _.reduce(fields, (form, f) => {
+ form[f] = {};
+ return form;
+ }, {});
+ }
+
+ });
+})();
\ No newline at end of file
diff --git a/src/services/helpers/ui/label_formatter.service.js b/src/services/helpers/ui/label_formatter.service.js
new file mode 100644
index 0000000..f23fea4
--- /dev/null
+++ b/src/services/helpers/ui/label_formatter.service.js
@@ -0,0 +1,90 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.uiComponents.LabelFormatter
+ * @description This factory define a set of helper function to format label started from an object property
+ **/
+
+ angular
+ .module('xos.uiComponents')
+ .factory('LabelFormatter', labelFormatter);
+
+ function labelFormatter() {
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.LabelFormatter#_formatByUnderscore
+ * @methodOf xos.uiComponents.LabelFormatter
+ * @description
+ * Convert a `snake_case` string to readable string.<br/>
+ * Eg: `this_string` will became `this string`
+ * @param {string} string The string to be converted
+ * @returns {string} The converten string
+ **/
+
+ const _formatByUnderscore = string => string.split('_').join(' ').trim();
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.LabelFormatter#_formatByUppercase
+ * @methodOf xos.uiComponents.LabelFormatter
+ * @description
+ * Convert a `camelCase` string to readable string.<br/>
+ * Eg: `thisString` will became `this string`
+ * @param {string} string The string to be converted
+ * @returns {string} The converten string
+ **/
+
+ const _formatByUppercase = string => string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.LabelFormatter#_capitalize
+ * @methodOf xos.uiComponents.LabelFormatter
+ * @description
+ * Capitalize the first letter of a string.<br/>
+ * Eg: `this string` will became `This string`
+ * @param {string} string The string to be converted
+ * @returns {string} The converten string
+ **/
+
+ const _capitalize = string => string.slice(0, 1).toUpperCase() + string.slice(1);
+
+ /**
+ * @ngdoc method
+ * @name xos.uiComponents.LabelFormatter#format
+ * @methodOf xos.uiComponents.LabelFormatter
+ * @description
+ * Apply in order:
+ * - _formatByUnderscore
+ * - _formatByUppercase
+ * - _capitalize
+ * - replace multiple space with a single one
+ * - append `:` at the end
+ * <br/>
+ * Eg: `this_string` will became `This string:`<br/>
+ * Eg: `thisString` will became `This string:`
+ * @param {string} string The string to be converted
+ * @returns {string} The converten string
+ **/
+
+ const format = (string) => {
+ string = _formatByUnderscore(string);
+ string = _formatByUppercase(string);
+
+ string = _capitalize(string).replace(/\s\s+/g, ' ') + ':';
+ return string.replace('::', ':');
+ };
+
+ return {
+ // test export
+ _formatByUnderscore: _formatByUnderscore,
+ _formatByUppercase: _formatByUppercase,
+ _capitalize: _capitalize,
+ // export to use
+ format: format
+ };
+ }
+})();
diff --git a/src/services/helpers/user-prefs.service.js b/src/services/helpers/user-prefs.service.js
new file mode 100644
index 0000000..f3f1f19
--- /dev/null
+++ b/src/services/helpers/user-prefs.service.js
@@ -0,0 +1,154 @@
+(function () {
+
+ angular.module('xos.helpers')
+
+ /**
+ * @ngdoc service
+ * @name xos.helpers.XosUserPrefs
+ * @description
+ * This service is used to store the user preferences in cookies, so that they survive to page changes.
+ * The structure of the user preference is:
+ * ```
+ * {
+ * synchronizers: {
+ * notification: {
+ * 'volt': boolean,
+ * 'openstack': boolean,
+ * ...
+ * }
+ * }
+ * userData: {
+ * current_user_site_id: Number,
+ * current_user_site_user_names: Array[1],
+ * ...
+ * }
+ * }
+ * ```
+ **/
+
+ .service('XosUserPrefs', function($cookies, Me, $q){
+
+ let userPrefs = $cookies.get('xosUserPrefs') ? angular.fromJson($cookies.get('xosUserPrefs')) : {};
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.XosUserPrefs#getAll
+ * @methodOf xos.helpers.XosUserPrefs
+ * @description
+ * Return all the user preferences stored in cookies
+ * @returns {object} The user preferences
+ **/
+ this.getAll = () => {
+ userPrefs = $cookies.get('xosUserPrefs') ? angular.fromJson($cookies.get('xosUserPrefs')) : {};
+ return userPrefs;
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.XosUserPrefs#setAll
+ * @methodOf xos.helpers.XosUserPrefs
+ * @description
+ * Override all user preferences
+ * @param {object} prefs The user preferences
+ **/
+ this.setAll = (prefs) => {
+ $cookies.put('xosUserPrefs', angular.toJson(prefs));
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.XosUserPrefs#getSynchronizerNotificationStatus
+ * @methodOf xos.helpers.XosUserPrefs
+ * @description
+ * Return the synchronizer notification status, if name is not provided return the status for all synchronizers
+ * @param {string=} prefs The synchronizer name
+ * @returns {object | string} The synchronizer status
+ **/
+ this.getSynchronizerNotificationStatus = (name = false) => {
+ if(name){
+ return this.getAll().synchronizers.notification[name];
+ }
+ return this.getAll().synchronizers.notification;
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.XosUserPrefs#getUserDetailsCookie
+ * @methodOf xos.helpers.XosUserPrefs
+ * @description
+ * Return all the user details stored in cookies or call the service
+ * @returns {object} The user details
+ **/
+ this.getUserDetailsCookie = () => {
+ const defer = $q.defer();
+ let localPref = this.getAll();
+ if(!localPref.userData){
+ this.setUserDetailsCookie().$promise.then((data)=>{
+ defer.resolve(data);
+ });
+ }
+ else{
+ defer.resolve(localPref.userData);
+ }
+ return {$promise: defer.promise};
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.XosUserPrefs#setUserDetailsCookie
+ * @methodOf xos.helpers.XosUserPrefs
+ * @description
+ * Save the user details in the cookie
+ * @param {object} details stored in cookie
+ * @param {objects} returns the user details as a promise
+ **/
+ this.setUserDetailsCookie = (userData = null)=> {
+
+ const defer = $q.defer();
+ let cookies = this.getAll();
+ if (!userData){
+ Me.get().then((user)=> {
+ cookies.userData = user;
+ this.setAll(cookies);
+ defer.resolve(user);
+ })
+ .catch ((e) => {
+ defer.reject(e);
+ });
+ }
+ else {
+ cookies.userData = userData;
+ this.setAll(cookies);
+ defer.resolve(userData);
+ }
+ return {$promise: defer.promise};
+ }
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.XosUserPrefs#setSynchronizerNotificationStatus
+ * @methodOf xos.helpers.XosUserPrefs
+ * @description
+ * Update the notification status for a single synchronizer
+ * @param {string} name The synchronizer name
+ * @param {boolean} value The notification status (true means that it has been sent)
+ **/
+
+ this.setSynchronizerNotificationStatus = (name = false, value) => {
+ if(!name){
+ throw new Error('[XosUserPrefs] When updating a synchronizer is mandatory to provide a name.')
+ }
+
+ let cookies = this.getAll();
+
+ if(!cookies.synchronizers){
+ cookies.synchronizers = {
+ notification: {}
+ }
+ }
+ cookies.synchronizers.notification[name] = value;
+ this.setAll(cookies);
+ }
+ });
+})();
\ No newline at end of file
diff --git a/src/services/log.decorator.js b/src/services/log.decorator.js
new file mode 100644
index 0000000..37d36f9
--- /dev/null
+++ b/src/services/log.decorator.js
@@ -0,0 +1,60 @@
+// TODO write tests for log
+
+/* eslint-disable angular/ng_window_service*/
+
+angular.module('xos.helpers')
+.config([ '$provide', function( $provide )
+{
+ // Use the `decorator` solution to substitute or attach behaviors to
+ // original service instance; @see angular-mocks for more examples....
+
+ $provide.decorator( '$log', [ '$delegate', function( $delegate )
+ {
+
+ const isLogEnabled = () => {
+ return window.location.href.indexOf('debug=true') >= 0;
+ };
+ // Save the original $log.debug()
+ let logFn = $delegate.log;
+ let infoFn = $delegate.info;
+ let warnFn = $delegate.warn;
+ //let errorFn = $delegate.error;
+ let debugFn = $delegate.debug;
+
+ // create the replacement function
+ const replacement = (fn) => {
+ return function(){
+ //console.log(`Is Log Enabled: ${isLogEnabled()}`)
+ if(!isLogEnabled()){
+ // console.log('logging is disabled');
+ return;
+ }
+
+ let args = [].slice.call(arguments);
+ let now = new Date();
+
+ // Prepend timestamp
+ args[0] = `[${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}] ${args[0]}`;
+
+ // HACK awfull fix for angular mock implementation whithin jasmine test failing issue
+ if (angular.isFunction($delegate.reset) && !($delegate.debug.logs instanceof Array)) {
+ // if we are within the mock and did not reset yet, we call it to avoid issue
+ // console.log('mock log impl fix to avoid logs array not existing...');
+ $delegate.reset();
+ }
+
+ // Call the original with the output prepended with formatted timestamp
+
+ return fn.apply(null, args)
+ };
+ };
+
+ $delegate.info = replacement(infoFn);
+ $delegate.log = replacement(logFn);
+ $delegate.warn = replacement(warnFn);
+ //$delegate.error = replacement(errorFn); // note this will prevent errors to be printed
+ $delegate.debug = replacement(debugFn);
+
+ return $delegate;
+ }]);
+}]);
\ No newline at end of file
diff --git a/src/services/noHyperlinks.interceptor.js b/src/services/noHyperlinks.interceptor.js
new file mode 100644
index 0000000..af3dd63
--- /dev/null
+++ b/src/services/noHyperlinks.interceptor.js
@@ -0,0 +1,24 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.helpers.NoHyperlinks
+ * @description This factory is automatically loaded trough xos.helpers and will add an $http interceptor that will add ?no_hyperlinks=1 to your api request, that is required by django
+ **/
+
+ angular
+ .module('xos.helpers')
+ .factory('NoHyperlinks', noHyperlinks);
+
+ function noHyperlinks() {
+ return {
+ request: function(request){
+ if(request.url.indexOf('.html') === -1){
+ request.url += '?no_hyperlinks=1';
+ }
+ return request;
+ }
+ };
+ }
+})();
\ No newline at end of file
diff --git a/src/services/notification.service.js b/src/services/notification.service.js
new file mode 100644
index 0000000..9a1598e
--- /dev/null
+++ b/src/services/notification.service.js
@@ -0,0 +1,62 @@
+/* eslint-disable angular/ng_window_service*/
+(function() {
+ 'use strict';
+
+ angular
+ .module('xos.helpers')
+ .factory('Notification', function(){
+ return window.Notification;
+ })
+ /**
+ * @ngdoc service
+ * @name xos.helpers.xosNotification
+ * @description This factory define a set of helper function to trigger desktop notification
+ **/
+ .service('xosNotification', function($q, $log, Notification) {
+
+ this.checkPermission = () => {
+ const deferred = $q.defer();
+ Notification.requestPermission()
+ .then(permission => {
+ if (permission === 'granted') {
+ deferred.resolve(permission);
+ }
+ else {
+ deferred.reject(permission);
+ }
+ });
+ return deferred.promise;
+ };
+
+ this.sendNotification = (title, options) => {
+ const notification = new Notification(title, options);
+ notification.onerror = function(err){
+ $log.error(err);
+ };
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.helpers.xosNotification#notify
+ * @methodOf xos.helpers.xosNotification
+ * @description
+ * This method will check for user permission and if granted will send a browser notification.
+ * @param {string} title The notification title
+ * @param {object} options The notification options: `{icon: 'url', body: 'Notification body'}`
+ **/
+
+ this.notify = (title, options) => {
+ if (!('Notification' in window)) {
+ $log.info('This browser does not support desktop notification');
+ }
+ else if (Notification.permission !== 'granted') {
+ this.checkPermission()
+ .then(() => this.sendNotification(title, options));
+ }
+ else if (Notification.permission === 'granted') {
+ this.sendNotification(title, options);
+ }
+ }
+
+ })
+})();
diff --git a/src/services/rest/Dashboards.js b/src/services/rest/Dashboards.js
new file mode 100644
index 0000000..42af90b
--- /dev/null
+++ b/src/services/rest/Dashboards.js
@@ -0,0 +1,31 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Dashboards
+ * @description Angular resource to fetch /api/core/dashboardviews/:id/
+ **/
+ .service('Dashboards', function($resource, $q, $http){
+ const r = $resource('/api/core/dashboardviews/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+
+ r.prototype.$save = function(){
+ const d = $q.defer();
+
+ $http.put(`/api/core/dashboardviews/${this.id}/`, this)
+ .then((res) => {
+ d.resolve(res.data);
+ })
+ .catch(e => {
+ d.reject(e.data);
+ });
+
+ return d.promise;
+ }
+
+ return r;
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Deployments.js b/src/services/rest/Deployments.js
new file mode 100644
index 0000000..4fecbc2
--- /dev/null
+++ b/src/services/rest/Deployments.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Deployments
+ * @description Angular resource to fetch /api/core/deployments/:id/
+ **/
+ .service('Deployments', function($resource){
+ return $resource('/api/core/deployments/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Example.js b/src/services/rest/Example.js
new file mode 100644
index 0000000..b13ccda
--- /dev/null
+++ b/src/services/rest/Example.js
@@ -0,0 +1,13 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Example-Services-Collection
+ * @description Angular resource to fetch /api/service/exampleservice/
+ **/
+ .service('Example-Services-Collection', function($resource){
+ return $resource('/api/service/exampleservice/');
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Flavors.js b/src/services/rest/Flavors.js
new file mode 100644
index 0000000..348b770
--- /dev/null
+++ b/src/services/rest/Flavors.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Flavors
+ * @description Angular resource to fetch /api/core/flavors/:id/
+ **/
+ .service('Flavors', function($resource){
+ return $resource('/api/core/flavors/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Images.js b/src/services/rest/Images.js
new file mode 100644
index 0000000..4fe9cb3
--- /dev/null
+++ b/src/services/rest/Images.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Images
+ * @description Angular resource to fetch /api/core/images/
+ **/
+ .service('Images', function($resource){
+ return $resource('/api/core/images/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Instances.js b/src/services/rest/Instances.js
new file mode 100644
index 0000000..f1e8521
--- /dev/null
+++ b/src/services/rest/Instances.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Instances
+ * @description Angular resource to fetch /api/core/instances/:id/
+ **/
+ .service('Instances', function($resource){
+ return $resource('/api/core/instances/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Me.js b/src/services/rest/Me.js
new file mode 100644
index 0000000..5dd1043
--- /dev/null
+++ b/src/services/rest/Me.js
@@ -0,0 +1,26 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Me
+ * @description Http read-only api to fetch /api/utility/me/
+ **/
+ .service('Me', function($q,$http){
+
+ this.get = () => {
+ let deferred = $q.defer();
+
+ $http.get('/api/utility/me/')
+ .then(res => {
+ deferred.resolve(res.data);
+ })
+ .catch(e => {
+ deferred.reject(e);
+ });
+ return deferred.promise;
+
+ };
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Networks.js b/src/services/rest/Networks.js
new file mode 100644
index 0000000..74b73b6
--- /dev/null
+++ b/src/services/rest/Networks.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Networks
+ * @description Angular resource to fetch /api/core/networks/:id/
+ **/
+ .service('Networks', function($resource){
+ return $resource('/api/core/networks/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
diff --git a/src/services/rest/Networkstemplates.js b/src/services/rest/Networkstemplates.js
new file mode 100644
index 0000000..961d5d8
--- /dev/null
+++ b/src/services/rest/Networkstemplates.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Networkstemplates
+ * @description Angular resource to fetch /api/core/networktemplates/:id/
+ **/
+ .service('Networkstemplates', function($resource){
+ return $resource('/api/core/networktemplates/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
diff --git a/src/services/rest/Nodes.js b/src/services/rest/Nodes.js
new file mode 100644
index 0000000..bf2e387
--- /dev/null
+++ b/src/services/rest/Nodes.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Nodes
+ * @description Angular resource to fetch /api/core/nodes/:id/
+ **/
+ .service('Nodes', function($resource){
+ return $resource('/api/core/nodes/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/ONOS-Apps.js b/src/services/rest/ONOS-Apps.js
new file mode 100644
index 0000000..8d4c104
--- /dev/null
+++ b/src/services/rest/ONOS-Apps.js
@@ -0,0 +1,13 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.ONOS-App-Collection
+ * @description Angular resource to fetch /api/tenant/onos/app/
+ **/
+ .service('ONOS-App-Collection', function($resource){
+ return $resource('/api/tenant/onos/app/');
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/ONOS-Services.js b/src/services/rest/ONOS-Services.js
new file mode 100644
index 0000000..19270e1
--- /dev/null
+++ b/src/services/rest/ONOS-Services.js
@@ -0,0 +1,13 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.ONOS-Services-Collection
+ * @description Angular resource to fetch /api/service/onos/
+ **/
+ .service('ONOS-Services-Collection', function($resource){
+ return $resource('/api/service/onos/');
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Services.js b/src/services/rest/Services.js
new file mode 100644
index 0000000..eda57e4
--- /dev/null
+++ b/src/services/rest/Services.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Services
+ * @description Angular resource to fetch /api/core/services/:id/
+ **/
+ .service('Services', function($resource){
+ return $resource('/api/core/services/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Sites.js b/src/services/rest/Sites.js
new file mode 100644
index 0000000..818d741
--- /dev/null
+++ b/src/services/rest/Sites.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Sites
+ * @description Angular resource to fetch /api/core/sites/:id/
+ **/
+ .service('Sites', function($resource){
+ return $resource('/api/core/sites/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Slices.js b/src/services/rest/Slices.js
new file mode 100644
index 0000000..5a0da11
--- /dev/null
+++ b/src/services/rest/Slices.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Slices
+ * @description Angular resource to fetch /api/core/slices/:id/
+ **/
+ .service('Slices', function($resource){
+ return $resource('/api/core/slices/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Slices_plus.js b/src/services/rest/Slices_plus.js
new file mode 100644
index 0000000..8213b11
--- /dev/null
+++ b/src/services/rest/Slices_plus.js
@@ -0,0 +1,40 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.SlicesPlus
+ * @description Angular resource to fetch /api/utility/slicesplus/
+ * This is a read-only API and only the `query` method is currently supported.
+ **/
+ .service('SlicesPlus', function($http, $q){
+ this.query = (params) => {
+ let deferred = $q.defer();
+
+ $http.get('/api/utility/slicesplus/', {params: params})
+ .then(res => {
+ deferred.resolve(res.data);
+ })
+ .catch(res => {
+ deferred.reject(res.data);
+ });
+
+ return {$promise: deferred.promise};
+ }
+
+ this.get = (id, params) => {
+ let deferred = $q.defer();
+
+ $http.get(`/api/utility/slicesplus/${id}`, {params: params})
+ .then(res => {
+ deferred.resolve(res.data);
+ })
+ .catch(res => {
+ deferred.reject(res.data);
+ });
+ return {$promise: deferred.promise};
+
+ }
+ })
+})();
diff --git a/src/services/rest/Subscribers.js b/src/services/rest/Subscribers.js
new file mode 100644
index 0000000..945e1e8
--- /dev/null
+++ b/src/services/rest/Subscribers.js
@@ -0,0 +1,147 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Subscribers
+ * @description Angular resource to fetch Subscribers
+ **/
+ .service('Subscribers', function($resource){
+ return $resource('/api/tenant/cord/subscriber/:id/', { id: '@id' }, {
+ update: { method: 'PUT' },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#View-a-Subscriber-Features-Detail
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * View-a-Subscriber-Features-Detail
+ **/
+ 'View-a-Subscriber-Features-Detail': {
+ method: 'GET',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Read-Subscriber-uplink_speed
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Read-Subscriber-uplink_speed
+ **/
+ 'Read-Subscriber-uplink_speed': {
+ method: 'GET',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/uplink_speed/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Update-Subscriber-uplink_speed
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Update-Subscriber-uplink_speed
+ **/
+ 'Update-Subscriber-uplink_speed': {
+ method: 'PUT',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/uplink_speed/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Read-Subscriber-downlink_speed
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Read-Subscriber-downlink_speed
+ **/
+ 'Read-Subscriber-downlink_speed': {
+ method: 'GET',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/downlink_speed/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Update-Subscriber-downlink_speed
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Update-Subscriber-downlink_speed
+ **/
+ 'Update-Subscriber-downlink_speed': {
+ method: 'PUT',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/downlink_speed/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Read-Subscriber-cdn
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Read-Subscriber-cdn
+ **/
+ 'Read-Subscriber-cdn': {
+ method: 'GET',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/cdn/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Update-Subscriber-cdn
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Update-Subscriber-cdn
+ **/
+ 'Update-Subscriber-cdn': {
+ method: 'PUT',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/cdn/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Read-Subscriber-uverse
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Read-Subscriber-uverse
+ **/
+ 'Read-Subscriber-uverse': {
+ method: 'GET',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/uverse/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Update-Subscriber-uverse
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Update-Subscriber-uverse
+ **/
+ 'Update-Subscriber-uverse': {
+ method: 'PUT',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/uverse/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Read-Subscriber-status
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Read-Subscriber-status
+ **/
+ 'Read-Subscriber-status': {
+ method: 'GET',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/status/'
+ },
+ /**
+ * @ngdoc method
+ * @name xos.helpers.Subscribers#Update-Subscriber-status
+ * @methodOf xos.helpers.Subscribers
+ * @description
+ * Update-Subscriber-status
+ **/
+ 'Update-Subscriber-status': {
+ method: 'PUT',
+ isArray: false,
+ url: '/api/tenant/cord/subscriber/:id/features/status/'
+ }
+ })
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Tenant.js b/src/services/rest/Tenant.js
new file mode 100644
index 0000000..8feb102
--- /dev/null
+++ b/src/services/rest/Tenant.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Tenant
+ * @description Angular resource to fetch /api/core/tenant/:id/
+ **/
+ .service('Tenants', function($resource){
+ return $resource('/api/core/tenants/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Truckroll.js b/src/services/rest/Truckroll.js
new file mode 100644
index 0000000..7af9016
--- /dev/null
+++ b/src/services/rest/Truckroll.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Truckroll
+ * @description Angular resource to fetch /api/tenant/truckroll/:id/
+ **/
+ .service('Truckroll', function($resource){
+ return $resource('/api/tenant/truckroll/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
diff --git a/src/services/rest/Users.js b/src/services/rest/Users.js
new file mode 100644
index 0000000..8be0fdd
--- /dev/null
+++ b/src/services/rest/Users.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Users
+ * @description Angular resource to fetch /api/core/users/:id/
+ **/
+ .service('Users', function($resource){
+ return $resource('/api/core/users/:id/', { id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/Utility.js b/src/services/rest/Utility.js
new file mode 100644
index 0000000..a735c46
--- /dev/null
+++ b/src/services/rest/Utility.js
@@ -0,0 +1,21 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Login
+ * @description Angular resource to fetch /api/utility/login/
+ **/
+ .service('Login', function($resource){
+ return $resource('/api/utility/login/');
+ })
+ /**
+ * @ngdoc service
+ * @name xos.helpers.Logout
+ * @description Angular resource to fetch /api/utility/logout/
+ **/
+ .service('Logout', function($resource){
+ return $resource('/api/utility/logout/');
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/vOLT.js b/src/services/rest/vOLT.js
new file mode 100644
index 0000000..424c48d
--- /dev/null
+++ b/src/services/rest/vOLT.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.vOLT-Collection
+ * @description Angular resource to fetch /api/tenant/cord/volt/:volt_id/
+ **/
+ .service('vOLT-Collection', function($resource){
+ return $resource('/api/tenant/cord/volt/:volt_id/', { volt_id: '@id' }, {
+ update: { method: 'PUT' }
+ });
+ })
+})();
\ No newline at end of file
diff --git a/src/services/rest/vSG.js b/src/services/rest/vSG.js
new file mode 100644
index 0000000..121b5a3
--- /dev/null
+++ b/src/services/rest/vSG.js
@@ -0,0 +1,13 @@
+(function() {
+ 'use strict';
+
+ angular.module('xos.helpers')
+ /**
+ * @ngdoc service
+ * @name xos.helpers.vSG-Collection
+ * @description Angular resource to fetch /api/service/vsg/
+ **/
+ .service('vSG-Collection', function($resource){
+ return $resource('/api/service/vsg/');
+ })
+})();
\ No newline at end of file
diff --git a/src/services/service_graph.service.js b/src/services/service_graph.service.js
new file mode 100644
index 0000000..8a732a2
--- /dev/null
+++ b/src/services/service_graph.service.js
@@ -0,0 +1,44 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.helpers.ServiceGraph
+ * @description This factory define a set of helper function to query the service tenancy graph
+ **/
+
+ angular
+ .module('xos.helpers')
+ .service('GraphService', function($q, Tenants, Services) {
+
+ this.loadCoarseData = () => {
+
+ let services;
+
+ let deferred = $q.defer();
+
+ Services.query().$promise
+ .then((res) => {
+ services = res;
+ return Tenants.query({kind: 'coarse'}).$promise;
+ })
+ .then((tenants) => {
+ deferred.resolve({
+ tenants: tenants,
+ services: services
+ });
+ })
+
+ return deferred.promise;
+ }
+
+ this.getCoarseGraph = () => {
+ this.loadCoarseData()
+ .then((res) => {
+ console.log(res);
+ })
+ return 'ciao';
+ };
+
+ })
+})();
diff --git a/src/styles/animations.scss b/src/styles/animations.scss
new file mode 100644
index 0000000..f565ff7
--- /dev/null
+++ b/src/styles/animations.scss
@@ -0,0 +1,44 @@
+@keyframes slideInRight {
+ from {
+ transform: translate3d(100%, 0, 0);
+ visibility: visible;
+ }
+
+ to {
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+@keyframes slideOutRight {
+ from {
+ transform: translate3d(0, 0, 0);
+ }
+
+ to {
+ visibility: hidden;
+ transform: translate3d(100%, 0, 0);
+ }
+}
+
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translate3d(0, 100%, 0);
+ }
+
+ to {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+@keyframes fadeOutDown {
+ from {
+ opacity: 1;
+ }
+
+ to {
+ opacity: 0;
+ transform: translate3d(0, 100%, 0);
+ }
+}
\ No newline at end of file
diff --git a/src/styles/loader.scss b/src/styles/loader.scss
new file mode 100644
index 0000000..66297df
--- /dev/null
+++ b/src/styles/loader.scss
@@ -0,0 +1,51 @@
+.loader {
+ font-size: 10px;
+ margin: 0 auto;
+ text-indent: -9999em;
+ width: 11em;
+ height: 11em;
+ border-radius: 50%;
+ background: #ffffff;
+ background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ position: relative;
+ animation: loaderSpinner 1.4s infinite linear;
+ transform: translateZ(0);
+}
+.loader:before {
+ width: 50%;
+ height: 50%;
+ background: $brand-primary;
+ border-radius: 100% 0 0 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ content: '';
+}
+.loader:after {
+ background: #fff;
+ width: 75%;
+ height: 75%;
+ border-radius: 50%;
+ content: '';
+ margin: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+@keyframes loaderSpinner {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/src/styles/main.scss b/src/styles/main.scss
new file mode 100644
index 0000000..3dbdc24
--- /dev/null
+++ b/src/styles/main.scss
@@ -0,0 +1,20 @@
+@import './animations.scss';
+@import '../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss';
+@import './loader.scss';
+
+@import '../ui_components/dumbComponents/table/table.scss';
+@import '../ui_components/dumbComponents/alert/alert.scss';
+@import '../ui_components/dumbComponents/validation/validation.scss';
+@import '../ui_components/dumbComponents/field/field.scss';
+@import '../ui_components/dumbComponents/form/form.scss';
+
+@import '../ui_components/smartComponents/smartTable/smartTable.scss';
+
+[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
+ display: none !important;
+}
+
+.row + .row {
+ /* TODO move in xos.scss*/
+ margin-top: $form-group-margin-bottom;
+}
\ No newline at end of file
diff --git a/src/ui_components/dumbComponents/alert/alert.component.js b/src/ui_components/dumbComponents/alert/alert.component.js
new file mode 100644
index 0000000..9b60def
--- /dev/null
+++ b/src/ui_components/dumbComponents/alert/alert.component.js
@@ -0,0 +1,145 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 4/15/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosAlert
+ * @restrict E
+ * @description The xos-alert directive
+ * @param {Object} config The configuration object
+ * ```
+ * {
+ * type: 'danger', //info, success, warning
+ * closeBtn: true, //default false
+ * autoHide: 3000 //delay to automatically hide the alert
+ * }
+ * ```
+ * @param {Boolean=} show Binding to show and hide the alert, default to true
+ * @element ANY
+ * @scope
+ * @example
+ <example module="sampleAlert1">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl1 as vm">
+ <xos-alert config="vm.config1">
+ A sample alert message
+ </xos-alert>
+ <xos-alert config="vm.config2">
+ A sample alert message (with close button)
+ </xos-alert>
+ <xos-alert config="vm.config3">
+ A sample info message
+ </xos-alert>
+ <xos-alert config="vm.config4">
+ A sample success message
+ </xos-alert>
+ <xos-alert config="vm.config5">
+ A sample warning message
+ </xos-alert>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleAlert1', ['xos.uiComponents'])
+ .controller('SampleCtrl1', function(){
+ this.config1 = {
+ type: 'danger'
+ };
+
+ this.config2 = {
+ type: 'danger',
+ closeBtn: true
+ };
+
+ this.config3 = {
+ type: 'info'
+ };
+
+ this.config4 = {
+ type: 'success'
+ };
+
+ this.config5 = {
+ type: 'warning'
+ };
+ });
+ </file>
+ </example>
+
+ <example module="sampleAlert2" animations="true">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm" class="row">
+ <div class="col-sm-4">
+ <a class="btn btn-default btn-block" ng-show="!vm.show" ng-click="vm.show = true">Show Alert</a>
+ <a class="btn btn-default btn-block" ng-show="vm.show" ng-click="vm.show = false">Hide Alert</a>
+ </div>
+ <div class="col-sm-8">
+ <xos-alert config="vm.config1" show="vm.show">
+ A sample alert message, not displayed by default.
+ </xos-alert>
+ </div>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleAlert2', ['xos.uiComponents', 'ngAnimate'])
+ .controller('SampleCtrl', function(){
+ this.config1 = {
+ type: 'success'
+ };
+
+ this.show = false;
+ });
+ </file>
+ </example>
+ **/
+
+ .directive('xosAlert', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ config: '=',
+ show: '=?'
+ },
+ template: `
+ <div ng-cloak class="alert alert-{{vm.config.type}}" ng-hide="!vm.show">
+ <button type="button" class="close" ng-if="vm.config.closeBtn" ng-click="vm.dismiss()">
+ <span aria-hidden="true">×</span>
+ </button>
+ <p ng-transclude></p>
+ </div>
+ `,
+ transclude: true,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function($timeout){
+
+ if(!this.config){
+ throw new Error('[xosAlert] Please provide a configuration via the "config" attribute');
+ }
+
+ // default the value to true
+ this.show = this.show !== false;
+
+ this.dismiss = () => {
+ this.show = false;
+ }
+
+ if(this.config.autoHide){
+ let to = $timeout(() => {
+ this.dismiss();
+ $timeout.cancel(to);
+ }, this.config.autoHide);
+ }
+ }
+ }
+ })
+})();
diff --git a/src/ui_components/dumbComponents/alert/alert.scss b/src/ui_components/dumbComponents/alert/alert.scss
new file mode 100644
index 0000000..f031ba6
--- /dev/null
+++ b/src/ui_components/dumbComponents/alert/alert.scss
@@ -0,0 +1,12 @@
+@import '../../../styles/animations.scss';
+
+xos-alert {
+ margin-top: $form-group-margin-bottom;
+ display: block;
+
+ /* when hiding */
+ .ng-hide-add { animation:0.5s fadeOutDown ease-in-out; }
+
+ /* when showing */
+ .ng-hide-remove { animation:0.5s fadeInUp ease-in-out; }
+}
\ No newline at end of file
diff --git a/src/ui_components/dumbComponents/field/field.component.js b/src/ui_components/dumbComponents/field/field.component.js
new file mode 100644
index 0000000..21b2f4e
--- /dev/null
+++ b/src/ui_components/dumbComponents/field/field.component.js
@@ -0,0 +1,279 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 5/25/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosField
+ * @restrict E
+ * @description The xos-field directive.
+ * This component decide, give a field wich kind of input it need to print.
+ * @param {string} name The field name
+ * @param {object} field The field configuration:
+ * ```
+ * {
+ * label: 'Label',
+ * type: 'number', //typeof field
+ * validators: {} // see xosForm for more details
+ * }
+ * ```
+ * @param {mixed} ngModel The field value
+ *
+ * @example
+
+ # Basic Example
+
+ <example module="sampleField1">
+ <file name="script.js">
+ angular.module('sampleField1', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl', function(){
+ this.name = 'input-name';
+ this.field = {label: 'My String Value:', type: 'string'};
+ this.model = 'my string';
+ });
+ </file>
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-field ng-model="vm.model" name="vm.name" field="vm.field"></xos-field>
+ </div>
+ </file>
+ </example>
+
+ # Possible Values
+
+ <example module="sampleField2">
+ <file name="script.js">
+ angular.module('sampleField2', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl', function(){
+ this.field1 = {
+ name: 'number-field',
+ field: {label: 'My Number Value:', type: 'number'},
+ model: 2
+ };
+
+ this.field2 = {
+ name: 'date-field',
+ field: {label: 'My Date Value:', type: 'date'},
+ model: new Date()
+ };
+
+ this.field3 = {
+ name: 'boolean-field',
+ field: {label: 'My Boolean Value:', type: 'boolean'},
+ model: true
+ };
+
+ this.field4 = {
+ name: 'email-field',
+ field: {label: 'My Email Value:', type: 'email'},
+ model: 'sample@domain.us'
+ };
+ });
+ </file>
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-field ng-model="vm.field1.model" name="vm.field1.name" field="vm.field1.field"></xos-field>
+ <xos-field ng-model="vm.field2.model" name="vm.field2.name" field="vm.field2.field"></xos-field>
+ <xos-field ng-model="vm.field3.model" name="vm.field3.name" field="vm.field3.field"></xos-field>
+ <xos-field ng-model="vm.field4.model" name="vm.field4.name" field="vm.field4.field"></xos-field>
+ </div>
+ </file>
+ </example>
+
+ # This element is recursive
+
+ <example module="sampleField3">
+ <file name="script.js">
+ angular.module('sampleField3', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl', function(){
+ this.name1 = 'input-name';
+ this.field1 = {label: 'My Object Field:', type: 'object'};
+ this.model1 = {
+ name: 'Jhon',
+ age: '25',
+ email: 'jhon@thewall.ru',
+ active: true
+ };
+
+ this.name2 = 'another-name';
+ this.field2 = {
+ label: 'Empty Object Field',
+ type: 'object',
+ properties: {
+ foo: {
+ label: 'FooLabel:',
+ type: 'string',
+ validators: {
+ required: true
+ }
+ },
+ bar: {
+ type: 'number'
+ }
+ }
+ }
+ });
+ </file>
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <h4>Autogenerated object field</h4>
+ <xos-field ng-model="vm.model1" name="vm.name1" field="vm.field1"></xos-field>
+
+ <h4>Configured object field</h4>
+ <xos-field ng-model="vm.model2" name="vm.name2" field="vm.field2"></xos-field>
+ </div>
+ </file>
+ </example>
+ */
+ .directive('xosField', function(RecursionHelper){
+ return {
+ restrict: 'E',
+ scope: {
+ name: '=',
+ field: '=',
+ ngModel: '='
+ },
+ template: `
+ <label ng-if="vm.field.type !== 'object'">{{vm.field.label}}</label>
+ <input
+ xos-custom-validator custom-validator="vm.field.validators.custom || null"
+ ng-if="vm.field.type !== 'boolean' && vm.field.type !== 'object' && vm.field.type !== 'select'"
+ type="{{vm.field.type}}"
+ name="{{vm.name}}"
+ class="form-control"
+ ng-model="vm.ngModel"
+ ng-minlength="vm.field.validators.minlength || 0"
+ ng-maxlength="vm.field.validators.maxlength || 2000"
+ ng-required="vm.field.validators.required || false" />
+ <select class="form-control" ng-if ="vm.field.type === 'select'"
+ name = "{{vm.name}}"
+ ng-options="item.id as item.label for item in vm.field.options"
+ ng-model="vm.ngModel"
+ ng-required="vm.field.validators.required || false">
+ </select>
+ <span class="boolean-field" ng-if="vm.field.type === 'boolean'">
+ <a href="#"
+ class="btn btn-success"
+ ng-show="vm.ngModel"
+ ng-click="vm.ngModel = false">
+ <i class="glyphicon glyphicon-ok"></i>
+ </a>
+ <a href="#"
+ class="btn btn-danger"
+ ng-show="!vm.ngModel"
+ ng-click="vm.ngModel = true">
+ <i class="glyphicon glyphicon-remove"></i>
+ </a>
+ </span>
+ <div
+ class="panel panel-default object-field"
+ ng-if="vm.field.type == 'object' && (!vm.isEmptyObject(vm.ngModel) || !vm.isEmptyObject(vm.field.properties))"
+ >
+ <div class="panel-heading">{{vm.field.label}}</div>
+ <div class="panel-body">
+ <div ng-if="!vm.field.properties" ng-repeat="(k, v) in vm.ngModel">
+ <xos-field
+ name="k"
+ field="{label: vm.formatLabel(k), type: vm.getType(v)}"
+ ng-model="v">
+ </xos-field>
+ </div>
+ <div ng-if="vm.field.properties" ng-repeat="(k, v) in vm.field.properties">
+ <xos-field
+ name="k"
+ field="{
+ label: v.label || vm.formatLabel(k),
+ type: v.type,
+ validators: v.validators
+ }"
+ ng-model="vm.ngModel[k]">
+ </xos-field>
+ </div>
+ </div>
+ </div>
+ `,
+ bindToController: true,
+ controllerAs: 'vm',
+ // the compile cicle is needed to support recursion
+ compile: function (element) {
+ return RecursionHelper.compile(element);
+ },
+ controller: function($attrs, XosFormHelpers, LabelFormatter){
+
+ if(!this.name){
+ throw new Error('[xosField] Please provide a field name');
+ }
+ if(!this.field){
+ throw new Error('[xosField] Please provide a field definition');
+ }
+ if(!this.field.type){
+ throw new Error('[xosField] Please provide a type in the field definition');
+ }
+ if(!$attrs.ngModel){
+ throw new Error('[xosField] Please provide an ng-model');
+ }
+ this.getType = XosFormHelpers._getFieldFormat;
+ this.formatLabel = LabelFormatter.format;
+
+ this.isEmptyObject = o => o ? Object.keys(o).length === 0 : true;
+ }
+ }
+ })
+
+/**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosCustomValidator
+ * @restrict A
+ * @description The xosCustomValidator directive.
+ * This component apply a custom validation function
+ * @param {function} customValidator The function that execute the validation.
+ *
+ * You should do your validation here and return true | false,
+ * or alternatively you can return an array [errorName, true|false]
+ */
+ .directive('xosCustomValidator', function(){
+ return {
+ restrict: 'A',
+ scope: {
+ fn: '=customValidator'
+ },
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl){
+ if(!angular.isFunction(scope.fn)){
+ return;
+ }
+
+ function customValidatorWrapper(ngModelValue) {
+ const valid = scope.fn(ngModelValue);
+ if(angular.isArray(valid)){
+ // ES6 spread rocks over fn.apply()
+ ctrl.$setValidity(...valid);
+ }
+ else{
+ ctrl.$setValidity('custom', valid);
+ }
+ return ngModelValue;
+ }
+
+ ctrl.$parsers.push(customValidatorWrapper);
+ }
+ };
+ });
+})();
\ No newline at end of file
diff --git a/src/ui_components/dumbComponents/field/field.scss b/src/ui_components/dumbComponents/field/field.scss
new file mode 100644
index 0000000..8dd7ca4
--- /dev/null
+++ b/src/ui_components/dumbComponents/field/field.scss
@@ -0,0 +1,3 @@
+xos-field {
+ display: block;
+}
\ No newline at end of file
diff --git a/src/ui_components/dumbComponents/form/form.component.js b/src/ui_components/dumbComponents/form/form.component.js
new file mode 100644
index 0000000..4d9169b
--- /dev/null
+++ b/src/ui_components/dumbComponents/form/form.component.js
@@ -0,0 +1,285 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 4/18/16.
+ */
+
+(function () {
+ 'use strict';
+
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosForm
+ * @restrict E
+ * @description The xos-form directive.
+ * This components have two usage, given a model it is able to autogenerate a form or it can be configured to create a custom form.
+ * @param {Object} config The configuration object
+ * ```
+ * {
+ * exclude: ['id', 'validators', 'created', 'updated', 'deleted'], //field to be skipped in the form, the provide values are concatenated
+ * actions: [ // define the form buttons with related callback
+ * {
+ label: 'save',
+ icon: 'ok', // refers to bootstraps glyphicon
+ cb: (user) => { // receive the model
+ console.log(user);
+ },
+ class: 'success'
+ }
+ * ],
+ * feedback: {
+ show: false,
+ message: 'Form submitted successfully !!!',
+ type: 'success' //refers to bootstrap class
+ },
+ * fields: {
+ * field_name: {
+ * label: 'Field Label',
+ * type: 'string' // options are: [date, boolean, number, email, string, select],
+ * validators: {
+ * minlength: number,
+ maxlength: number,
+ required: boolean,
+ min: number,
+ max: number,
+ custom: (value) => {
+ // do your validation here and return true | false
+ // alternatively you can return an array [errorName, true|false]
+ }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ * @element ANY
+ * @scope
+ * @requires xos.uiComponents.directive:xosField
+ * @requires xos.uiComponents.XosFormHelpers
+ * @requires xos.helpers._
+ * @example
+
+ Autogenerated form
+
+ <example module="sampleForm">
+ <file name="script.js">
+ angular.module('sampleForm', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl', function(){
+ this.model = {
+ first_name: 'Jhon',
+ last_name: 'Doe',
+ email: 'jhon.doe@sample.com',
+ active: true,
+ birthDate: '2015-02-17T22:06:38.059000Z'
+ }
+ this.config = {
+ exclude: ['password', 'last_login'],
+ formName: 'sampleForm',
+ actions: [
+ {
+ label: 'Save',
+ icon: 'ok', // refers to bootstraps glyphicon
+ cb: (user) => { // receive the model
+ console.log(user);
+ },
+ class: 'success'
+ }
+ ]
+ };
+ });
+ </file>
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-form ng-model="vm.model" config="vm.config"></xos-form>
+ </div>
+ </file>
+ </example>
+
+ Configuration defined form
+
+ <example module="sampleForm1">
+ <file name="script.js">
+ angular.module('sampleForm1', ['xos.uiComponents','ngResource', 'ngMockE2E'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl1', function(SampleResource){
+
+
+ this.model = {
+ };
+
+ this.config = {
+ exclude: ['password', 'last_login'],
+ formName: 'sampleForm1',
+ feedback: {
+ show: false,
+ message: 'Form submitted successfully !!!',
+ type: 'success'
+ },
+ actions: [
+ {
+ label: 'Save',
+ icon: 'ok', // refers to bootstraps glyphicon
+ cb: (user) => { // receive the model
+ console.log(user);
+ this.config.feedback.show = true;
+ this.config.feedback.type='success';
+ },
+ class: 'success'
+ }
+ ],
+ fields: {
+ first_name: {
+ type: 'string',
+ validators: {
+ required: true
+ }
+ },
+ last_name: {
+ label: 'Surname',
+ type: 'string',
+ validators: {
+ required: true,
+ minlength: 10
+ }
+ },
+ age: {
+ type: 'number',
+ validators: {
+ required: true,
+ min: 21
+ }
+ },
+
+ site: {
+ label: 'Site',
+ type: 'select',
+ validators: { required: true},
+ hint: 'The Site this Slice belongs to',
+ options: []
+ },
+ }
+ };
+ SampleResource.query().$promise
+ .then((users) => {
+ //this.users_site = users;
+ //console.log(users);
+ this.optionVal = users;
+ this.config.fields['site'].options = this.optionVal;
+ //= this.optionVal;
+
+ })
+ .catch((e) => {
+ throw new Error(e);
+ });
+
+ });
+ </file>
+ <file name="backend.js">
+ angular.module('sampleForm1')
+ .run(function($httpBackend, _){
+ let datas = [{id: 1, label: 'site1'},{id: 4, label: 'site4'},{id: 3, label: 'site3'}];
+ let paramsUrl = new RegExp(/\/test\/(.+)/);
+ $httpBackend.whenGET('/test').respond(200, datas)
+ })
+ .service('SampleResource', function($resource){
+ return $resource('/test/:id', {id: '@id'});
+ });
+
+ </file>
+ <file name="index.html">
+ <div ng-controller="SampleCtrl1 as vm">
+ <xos-form ng-model="vm.model" config="vm.config"></xos-form>
+ </div>
+ </file>
+ </example>
+
+ **/
+
+ .directive('xosForm', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ config: '=',
+ ngModel: '='
+ },
+ template: `
+ <form name="vm.{{vm.config.formName || 'form'}}" novalidate>
+ <div class="form-group" ng-repeat="(name, field) in vm.formField">
+ <xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>
+ <xos-validation field="vm[vm.config.formName || 'form'][name]" form = "vm[vm.config.formName || 'form']"></xos-validation>
+ <div class="alert alert-info" ng-show="(field.hint).length >0" role="alert">{{field.hint}}</div>
+ </div>
+ <div class="form-group" ng-if="vm.config.actions">
+ <xos-alert config="vm.config.feedback" show="vm.config.feedback.show">{{vm.config.feedback.message}}</xos-alert>
+
+ <button role="button" href=""
+ ng-repeat="action in vm.config.actions"
+ ng-click="action.cb(vm.ngModel, vm[vm.config.formName || 'form'])"
+ class="btn btn-{{action.class}}"
+ title="{{action.label}}">
+ <i class="glyphicon glyphicon-{{action.icon}}"></i>
+ {{action.label}}
+ </button>
+ </div>
+ </form>
+ `,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function($scope, $log, _, XosFormHelpers){
+
+ if(!this.config){
+ throw new Error('[xosForm] Please provide a configuration via the "config" attribute');
+ }
+
+ if(!this.config.actions){
+ throw new Error('[xosForm] Please provide an action list in the configuration');
+ }
+
+ if(!this.config.feedback){
+ this.config.feedback = {
+ show: false,
+ message: 'Form submitted successfully !!!',
+ type: 'success'
+ }
+ }
+
+ this.excludedField = ['id', 'validators', 'created', 'updated', 'deleted', 'backend_status'];
+ if(this.config && this.config.exclude){
+ this.excludedField = this.excludedField.concat(this.config.exclude);
+ }
+
+ this.formField = [];
+
+ $scope.$watch(() => this.config, ()=> {
+ if(!this.ngModel){
+ return;
+ }
+ let diff = _.difference(Object.keys(this.ngModel), this.excludedField);
+ let modelField = XosFormHelpers.parseModelField(diff);
+ this.formField = XosFormHelpers.buildFormStructure(modelField, this.config.fields, this.ngModel);
+ }, true);
+
+ $scope.$watch(() => this.ngModel, (model) => {
+ // empty from old stuff
+ this.formField = {};
+ if(!model){
+ return;
+ }
+ let diff = _.difference(Object.keys(model), this.excludedField);
+ let modelField = XosFormHelpers.parseModelField(diff);
+ this.formField = XosFormHelpers.buildFormStructure(modelField, this.config.fields, model);
+ });
+
+ }
+ }
+ });
+})();
diff --git a/src/ui_components/dumbComponents/form/form.scss b/src/ui_components/dumbComponents/form/form.scss
new file mode 100644
index 0000000..cf977af
--- /dev/null
+++ b/src/ui_components/dumbComponents/form/form.scss
@@ -0,0 +1,12 @@
+@import '../../../styles/animations.scss';
+@import '../../../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss';
+
+xos-form {
+ button {
+ margin-bottom: $form-group-margin-bottom;
+ }
+
+ button + button {
+ margin-left: $form-group-margin-bottom;
+ }
+}
\ No newline at end of file
diff --git a/src/ui_components/dumbComponents/pagination/pagination.component.js b/src/ui_components/dumbComponents/pagination/pagination.component.js
new file mode 100644
index 0000000..ddb1eb9
--- /dev/null
+++ b/src/ui_components/dumbComponents/pagination/pagination.component.js
@@ -0,0 +1,122 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 4/15/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosPagination
+ * @restrict E
+ * @description The xos-table directive
+ * @param {Number} pageSize Number of elements per page
+ * @param {Number} totalElements Number of total elements in the collection
+ * @param {Function} change The callback to be triggered on page change.
+ * * @element ANY
+ * @scope
+ * @example
+ <example module="samplePagination">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl1 as vm">
+ <xos-pagination
+ page-size="vm.pageSize"
+ total-elements="vm.totalElements"
+ change="vm.change">
+ </xos-pagination>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('samplePagination', ['xos.uiComponents'])
+ .controller('SampleCtrl1', function(){
+ this.pageSize = 10;
+ this.totalElements = 35;
+ this.change = (pageNumber) => {
+ console.log(pageNumber);
+ }
+ });
+ </file>
+ </example>
+ **/
+
+ .directive('xosPagination', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ pageSize: '=',
+ totalElements: '=',
+ change: '='
+ },
+ template: `
+ <div class="row" ng-if="vm.pageList.length > 1">
+ <div class="col-xs-12 text-center">
+ <ul class="pagination">
+ <li
+ ng-click="vm.goToPage(vm.currentPage - 1)"
+ ng-class="{disabled: vm.currentPage == 0}">
+ <a href="" aria-label="Previous">
+ <span aria-hidden="true">«</span>
+ </a>
+ </li>
+ <li ng-repeat="i in vm.pageList" ng-class="{active: i === vm.currentPage}">
+ <a href="" ng-click="vm.goToPage(i)">{{i + 1}}</a>
+ </li>
+ <li
+ ng-click="vm.goToPage(vm.currentPage + 1)"
+ ng-class="{disabled: vm.currentPage == vm.pages - 1}">
+ <a href="" aria-label="Next">
+ <span aria-hidden="true">»</span>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ `,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function($scope){
+
+ this.currentPage = 0;
+
+ this.goToPage = (n) => {
+ if(n < 0 || n === this.pages){
+ return;
+ }
+ this.currentPage = n;
+ this.change(n);
+ }
+
+ this.createPages = (pages) => {
+ let arr = [];
+ for(let i = 0; i < pages; i++){
+ arr.push(i);
+ }
+ return arr;
+ }
+
+ // watch for data changes
+ $scope.$watch(() => this.totalElements, () => {
+ if(this.totalElements){
+ this.pages = Math.ceil(this.totalElements / this.pageSize);
+ this.pageList = this.createPages(this.pages);
+ }
+ });
+ }
+ }
+ })
+ .filter('pagination', function(){
+ return function(input, start) {
+ if(!input || !angular.isArray(input)){
+ return input;
+ }
+ start = parseInt(start, 10);
+ return input.slice(start);
+ };
+ });
+})();
diff --git a/src/ui_components/dumbComponents/table/table.component.js b/src/ui_components/dumbComponents/table/table.component.js
new file mode 100644
index 0000000..1e60458
--- /dev/null
+++ b/src/ui_components/dumbComponents/table/table.component.js
@@ -0,0 +1,549 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosTable
+ * @restrict E
+ * @description The xos-table directive
+ * @param {Object} config The configuration for the component.
+ * ```
+ * {
+ * columns: [
+ * {
+ * label: 'Human readable name',
+ * prop: 'Property to read in the model object',
+ * type: 'boolean'| 'array'| 'object'| 'custom'| 'date' | 'icon' // see examples for more details
+ formatter: fn(), // receive the whole item if tipe is custom and return a string
+ link: fn() // receive the whole item and return an url
+ * }
+ * ],
+ * classes: 'table table-striped table-bordered',
+ * actions: [ // if defined add an action column
+ {
+ label: 'delete',
+ icon: 'remove', // refers to bootstraps glyphicon
+ cb: (user) => { // receive the model
+ console.log(user);
+ },
+ color: 'red'
+ }
+ ],
+ filter: 'field', // can be by `field` or `fulltext`
+ order: true | {field: 'property name', reverse: true | false} // whether to show ordering arrows, or a configuration for a default ordering
+ * }
+ * ```
+ * @param {Array} data The data that should be rendered
+ * @element ANY
+ * @scope
+ * @example
+
+ # Basic usage
+ <example module="sampleTable1">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl1 as vm">
+ <xos-table data="vm.data" config="vm.config"></xos-table>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleTable1', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl1', function(){
+ this.config = {
+ columns: [
+ {
+ label: 'First Name', // column title
+ prop: 'name' // property to read in the data array
+ },
+ {
+ label: 'Last Name',
+ prop: 'lastname'
+ }
+ ]
+ };
+
+ this.data = [
+ {
+ name: 'John',
+ lastname: 'Doe'
+ },
+ {
+ name: 'Gili',
+ lastname: 'Fereydoun'
+ }
+ ]
+ });
+ </file>
+ </example>
+
+ # Filtering
+ <example module="sampleTable2" animations="true">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl2 as vm">
+ <xos-table data="vm.data" config="vm.config"></xos-table>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleTable2', ['xos.uiComponents', 'ngAnimate'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl2', function(){
+ this.config = {
+ columns: [
+ {
+ label: 'First Name', // column title
+ prop: 'name' // property to read in the data array
+ },
+ {
+ label: 'Last Name',
+ prop: 'lastname'
+ }
+ ],
+ classes: 'table table-striped table-condensed', // table classes, default to `table table-striped table-bordered`
+ actions: [ // if defined add an action column
+ {
+ label: 'delete', // label
+ icon: 'remove', // icons, refers to bootstraps glyphicon
+ cb: (user) => { // callback, get feeded with the full object
+ console.log(user);
+ },
+ color: 'red' // icon color
+ }
+ ],
+ filter: 'field', // can be by `field` or `fulltext`
+ order: true
+ };
+
+ this.data = [
+ {
+ name: 'John',
+ lastname: 'Doe'
+ },
+ {
+ name: 'Gili',
+ lastname: 'Fereydoun'
+ }
+ ]
+ });
+ </file>
+ </example>
+
+ # Pagination
+ <example module="sampleTable3">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl3 as vm">
+ <xos-table data="vm.data" config="vm.config"></xos-table>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleTable3', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl3', function(){
+ this.config = {
+ columns: [
+ {
+ label: 'First Name', // column title
+ prop: 'name' // property to read in the data array
+ },
+ {
+ label: 'Last Name',
+ prop: 'lastname'
+ }
+ ],
+ pagination: {
+ pageSize: 2
+ }
+ };
+
+ this.data = [
+ {
+ name: 'John',
+ lastname: 'Doe'
+ },
+ {
+ name: 'Gili',
+ lastname: 'Fereydoun'
+ },
+ {
+ name: 'Lucky',
+ lastname: 'Clarkson'
+ },
+ {
+ name: 'Tate',
+ lastname: 'Spalding'
+ }
+ ]
+ });
+ </file>
+ </example>
+
+ # Field formatter
+ <example module="sampleTable4">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-table data="vm.data" config="vm.config"></xos-table>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleTable4', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl', function(){
+ this.config = {
+ columns: [
+ {
+ label: 'First Name',
+ prop: 'name',
+ link: item => `https://www.google.it/#q=${item.name}`
+ },
+ {
+ label: 'Enabled',
+ prop: 'enabled',
+ type: 'boolean'
+ },
+ {
+ label: 'Services',
+ prop: 'services',
+ type: 'array'
+ },
+ {
+ label: 'Details',
+ prop: 'details',
+ type: 'object'
+ },
+ {
+ label: 'Created',
+ prop: 'created',
+ type: 'date'
+ },
+ {
+ label: 'Icon',
+ type: 'icon',
+ formatter: item => item.icon //note that this refer to [Bootstrap Glyphicon](http://getbootstrap.com/components/#glyphicons)
+ }
+ ]
+ };
+
+ this.data = [
+ {
+ name: 'John',
+ enabled: true,
+ services: ['Cdn', 'IpTv'],
+ details: {
+ c_tag: '243',
+ s_tag: '444'
+ },
+ created: new Date('December 17, 1995 03:24:00'),
+ icon: 'music'
+ },
+ {
+ name: 'Gili',
+ enabled: false,
+ services: ['Cdn', 'IpTv', 'Cache'],
+ details: {
+ c_tag: '675',
+ s_tag: '893'
+ },
+ created: new Date(),
+ icon: 'camera'
+ }
+ ]
+ });
+ </file>
+ </example>
+
+ # Custom formatter
+ <example module="sampleTable5">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-table data="vm.data" config="vm.config"></xos-table>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleTable5', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrl', function(){
+ this.config = {
+ columns: [
+ {
+ label: 'Username',
+ prop: 'username'
+ },
+ {
+ label: 'Features',
+ type: 'custom',
+ formatter: (val) => {
+
+ let cdnEnabled = val.features.cdn ? 'enabled' : 'disabled';
+ return `
+ Cdn is ${cdnEnabled},
+ uplink speed is ${val.features.uplink_speed}
+ and downlink speed is ${val.features.downlink_speed}
+ `;
+ }
+ }
+ ]
+ };
+
+ this.data = [
+ {
+ username: 'John',
+ features: {
+ "cdn": false,
+ "uplink_speed": 1000000000,
+ "downlink_speed": 1000000000,
+ "uverse": true,
+ "status": "enabled"
+ }
+ },
+ {
+ username: 'Gili',
+ features: {
+ "cdn": true,
+ "uplink_speed": 3000000000,
+ "downlink_speed": 2000000000,
+ "uverse": true,
+ "status": "enabled"
+ }
+ }
+ ]
+ });
+ </file>
+ </example>
+ **/
+
+ .directive('xosTable', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ data: '=',
+ config: '='
+ },
+ template: `
+ <div ng-show="vm.data.length > 0 && vm.loader == false">
+ <div class="row" ng-if="vm.config.filter == 'fulltext'">
+ <div class="col-xs-12">
+ <input
+ class="form-control"
+ placeholder="Type to search.."
+ type="text"
+ ng-model="vm.query"/>
+ </div>
+ </div>
+ <table ng-class="vm.classes" ng-hide="vm.data.length == 0">
+ <thead>
+ <tr>
+ <th ng-repeat="col in vm.columns">
+ {{col.label}}
+ <span ng-if="vm.config.order">
+ <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">
+ <i class="glyphicon glyphicon-chevron-up"></i>
+ </a>
+ <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">
+ <i class="glyphicon glyphicon-chevron-down"></i>
+ </a>
+ </span>
+ </th>
+ <th ng-if="vm.config.actions">Actions:</th>
+ </tr>
+ </thead>
+ <tbody ng-if="vm.config.filter == 'field'">
+ <tr>
+ <td ng-repeat="col in vm.columns">
+ <input
+ ng-if="col.type !== 'boolean' && col.type !== 'array' && col.type !== 'object' && col.type !== 'custom'"
+ class="form-control"
+ placeholder="Type to search by {{col.label}}"
+ type="text"
+ ng-model="vm.query[col.prop]"/>
+ <select
+ ng-if="col.type === 'boolean'"
+ class="form-control"
+ ng-model="vm.query[col.prop]">
+ <option value="">-</option>
+ <option value="true">True</option>
+ <option value="false">False</option>
+ </select>
+ </td>
+ <td ng-if="vm.config.actions"></td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr ng-repeat="item in vm.data | filter:vm.query:vm.comparator | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">
+ <td ng-repeat="col in vm.columns" xos-link-wrapper>
+ <span ng-if="!col.type">{{item[col.prop]}}</span>
+ <span ng-if="col.type === 'boolean'">
+ <i class="glyphicon"
+ ng-class="{'glyphicon-ok': item[col.prop], 'glyphicon-remove': !item[col.prop]}">
+ </i>
+ </span>
+ <span ng-if="col.type === 'date'">
+ {{item[col.prop] | date:'H:mm MMM d, yyyy'}}
+ </span>
+ <span ng-if="col.type === 'array'">
+ {{item[col.prop] | arrayToList}}
+ </span>
+ <span ng-if="col.type === 'object'">
+ <dl class="dl-horizontal">
+ <span ng-repeat="(k,v) in item[col.prop]">
+ <dt>{{k}}</dt>
+ <dd>{{v}}</dd>
+ </span>
+ </dl>
+ </span>
+ <span ng-if="col.type === 'custom'">
+ {{col.formatter(item)}}
+ </span>
+ <span ng-if="col.type === 'icon'">
+ <i class="glyphicon glyphicon-{{col.formatter(item)}}">
+ </i>
+ </span>
+ </td>
+ <td ng-if="vm.config.actions">
+ <a href=""
+ ng-repeat="action in vm.config.actions"
+ ng-click="action.cb(item)"
+ title="{{action.label}}">
+ <i
+ class="glyphicon glyphicon-{{action.icon}}"
+ style="color: {{action.color}};"></i>
+ </a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <xos-pagination
+ ng-if="vm.config.pagination"
+ page-size="vm.config.pagination.pageSize"
+ total-elements="vm.data.length"
+ change="vm.goToPage">
+ </xos-pagination>
+ </div>
+ <div ng-show="(vm.data.length == 0 || !vm.data) && vm.loader == false">
+ <xos-alert config="{type: 'info'}">
+ No data to show.
+ </xos-alert>
+ </div>
+ <div ng-show="vm.loader == true">
+ <div class="loader"></div>
+ </div>
+ `,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function(_, $scope, Comparator){
+
+ this.comparator = Comparator;
+
+ this.loader = true;
+
+ $scope.$watch(() => this.data, data => {
+ if(angular.isDefined(data)){
+ this.loader = false;
+ }
+ });
+
+ if(!this.config){
+ throw new Error('[xosTable] Please provide a configuration via the "config" attribute');
+ }
+
+ if(!this.config.columns){
+ throw new Error('[xosTable] Please provide a columns list in the configuration');
+ }
+
+ // handle default ordering
+ if(this.config.order && angular.isObject(this.config.order)){
+ this.reverse = this.config.order.reverse || false;
+ this.orderBy = this.config.order.field || 'id';
+ }
+
+ // if columns with type 'custom' are provided
+ // check that a custom formatte3 is provided too
+ let customCols = _.filter(this.config.columns, {type: 'custom'});
+ if(angular.isArray(customCols) && customCols.length > 0){
+ _.forEach(customCols, (col) => {
+ if(!col.formatter || !angular.isFunction(col.formatter)){
+ throw new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.');
+ }
+ })
+ }
+
+ // if columns with type 'icon' are provided
+ // check that a custom formatte3 is provided too
+ let iconCols = _.filter(this.config.columns, {type: 'icon'});
+ if(angular.isArray(iconCols) && iconCols.length > 0){
+ _.forEach(iconCols, (col) => {
+ if(!col.formatter || !angular.isFunction(col.formatter)){
+ throw new Error('[xosTable] You have provided an icon field type, a formatter function should provided too.');
+ }
+ })
+ }
+
+ // if a link property is passed,
+ // it should be a function
+ let linkedColumns = _.filter(this.config.columns, col => angular.isDefined(col.link));
+ if(angular.isArray(linkedColumns) && linkedColumns.length > 0){
+ _.forEach(linkedColumns, (col) => {
+ if(!angular.isFunction(col.link)){
+ throw new Error('[xosTable] The link property should be a function.');
+ }
+ })
+ }
+
+ this.columns = this.config.columns;
+ this.classes = this.config.classes || 'table table-striped table-bordered';
+
+ if(this.config.actions){
+ // TODO validate action format
+ }
+ if(this.config.pagination){
+ this.currentPage = 0;
+ this.goToPage = (n) => {
+ this.currentPage = n;
+ };
+ }
+
+ }
+ }
+ })
+ // TODO move in separate files
+ // TODO test
+ .filter('arrayToList', function(){
+ return (input) => {
+ if(!angular.isArray(input)){
+ return input;
+ }
+ return input.join(', ');
+ }
+ })
+ // TODO test
+ .directive('xosLinkWrapper', function() {
+ return {
+ restrict: 'A',
+ transclude: true,
+ template: `
+ <a ng-if="col.link" href="{{col.link(item)}}">
+ <div ng-transclude></div>
+ </a>
+ <div ng-transclude ng-if="!col.link"></div>
+ `
+ };
+ });
+})();
diff --git a/src/ui_components/dumbComponents/table/table.scss b/src/ui_components/dumbComponents/table/table.scss
new file mode 100644
index 0000000..d9830d8
--- /dev/null
+++ b/src/ui_components/dumbComponents/table/table.scss
@@ -0,0 +1,44 @@
+@import '../../../styles/animations.scss';
+
+xos-table {
+
+ display: block;
+
+ tr.ng-move,
+ tr.ng-enter,
+ tr.ng-leave {
+ transition:all linear 0.5s;
+ }
+
+ tr.ng-leave.ng-leave-active,
+ tr.ng-move,
+ tr.ng-enter {
+ opacity:0;
+ animation: 0.5s slideOutRight ease-in-out;
+ }
+
+ tr.ng-leave,
+ tr.ng-move.ng-move-active,
+ tr.ng-enter.ng-enter-active {
+ opacity:1;
+ animation: 0.5s slideInRight ease-in-out;
+ }
+
+ td dl {
+ margin-bottom: 0;
+
+ dt {
+ width: auto !important;
+ margin-right: 10px;
+ }
+
+ dt:after {
+ /*display: block;*/
+ content: ':';
+ }
+
+ dd {
+ margin-left: 0 !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ui_components/dumbComponents/validation/validation.component.js b/src/ui_components/dumbComponents/validation/validation.component.js
new file mode 100644
index 0000000..91610e1
--- /dev/null
+++ b/src/ui_components/dumbComponents/validation/validation.component.js
@@ -0,0 +1,112 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 4/15/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosValidation
+ * @restrict E
+ * @description The xos-validation directive
+ * @param {Object} errors The error object
+ * @element ANY
+ * @scope
+ * @example
+ <example module="sampleValidation">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <div class="row">
+ <div class="col-xs-12">
+ <label>Set an error type:</label>
+ </div>
+ <div class="col-xs-2">
+ <a class="btn"
+ ng-click="vm.field.$error.required = !vm.field.$error.required"
+ ng-class="{'btn-default': !vm.field.$error.required, 'btn-success': vm.field.$error.required}">
+ Required
+ </a>
+ </div>
+ <div class="col-xs-2">
+ <a class="btn"
+ ng-click="vm.field.$error.email = !vm.field.$error.email"
+ ng-class="{'btn-default': !vm.field.$error.email, 'btn-success': vm.field.$error.email}">
+ Email
+ </a>
+ </div>
+ <div class="col-xs-2">
+ <a class="btn"
+ ng-click="vm.field.$error.minlength = !vm.field.$error.minlength"
+ ng-class="{'btn-default': !vm.field.$error.minlength, 'btn-success': vm.field.$error.minlength}">
+ Min Length
+ </a>
+ </div>
+ <div class="col-xs-2">
+ <a class="btn"
+ ng-click="vm.field.$error.maxlength = !vm.field.$error.maxlength"
+ ng-class="{'btn-default': !vm.field.$error.maxlength, 'btn-success': vm.field.$error.maxlength}">
+ Max Length
+ </a>
+ </div>
+ </div>
+ <xos-validation field ="vm.field" form = "vm.form"></xos-validation>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleValidation', ['xos.uiComponents'])
+ .controller('SampleCtrl', function(){
+ this.field = {
+ $error: {}
+ };
+ this.form= {
+ $submitted:true
+ }
+ });
+ </file>
+ </example>
+ */
+
+ .directive('xosValidation', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ field: '=',
+ form: '='
+ },
+ template: `
+ <div ng-cloak>
+ <xos-alert config="vm.config" show="vm.field.$error.required !== undefined && vm.field.$error.required !== false && (vm.field.$touched || vm.form.$submitted)">
+ Field required
+ </xos-alert>
+ <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">
+ This is not a valid email
+ </xos-alert>
+ <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">
+ Too short
+ </xos-alert>
+ <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">
+ Too long
+ </xos-alert>
+ <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">
+ Field invalid
+ </xos-alert>
+ </div>
+ `,
+ transclude: true,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function(){
+ this.config = {
+ type: 'danger'
+ }
+ }
+ }
+ })
+})();
diff --git a/src/ui_components/dumbComponents/validation/validation.scss b/src/ui_components/dumbComponents/validation/validation.scss
new file mode 100644
index 0000000..588853f
--- /dev/null
+++ b/src/ui_components/dumbComponents/validation/validation.scss
@@ -0,0 +1,7 @@
+@import '../../../styles/animations.scss';
+@import '../../../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss';
+
+input + xos-validation {
+ margin-top: $form-group-margin-bottom;
+ display: block;
+}
\ No newline at end of file
diff --git a/src/ui_components/smartComponents/smartPie/smartPie.component.js b/src/ui_components/smartComponents/smartPie/smartPie.component.js
new file mode 100644
index 0000000..f8a3985
--- /dev/null
+++ b/src/ui_components/smartComponents/smartPie/smartPie.component.js
@@ -0,0 +1,248 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosSmartPie
+ * @restrict E
+ * @description The xos-table directive
+ * @param {Object} config The configuration for the component,
+ * it is composed by the name of an angular [$resource](https://docs.angularjs.org/api/ngResource/service/$resource)
+ * and a field name that is used to group the data.
+ * ```
+ * {
+ resource: 'Users',
+ groupBy: 'fieldName',
+ classes: 'my-custom-class',
+ labelFormatter: (labels) => {
+ // here you can format your label,
+ // you should return an array with the same order
+ return labels;
+ }
+ }
+ * ```
+ * @scope
+ * @example
+
+ Displaying Local data
+
+ <example module="sampleSmartPieLocal">
+ <file name="index.html">
+ <div ng-controller="SampleCtrlLocal as vm">
+ <xos-smart-pie config="vm.configLocal"></xos-smart-pie>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleSmartPieLocal', ['xos.uiComponents'])
+ .factory('_', function($window){
+ return $window._;
+ })
+ .controller('SampleCtrlLocal', function($timeout){
+
+ this.datas = [
+ {id: 1, first_name: 'Jon', last_name: 'aaa', category: 2},
+ {id: 2, first_name: 'Danaerys', last_name: 'Targaryen', category: 1},
+ {id: 3, first_name: 'Aria', last_name: 'Stark', category: 2}
+ ];
+
+ this.configLocal = {
+ data: [],
+ groupBy: 'category',
+ classes: 'local',
+ labelFormatter: (labels) => {
+ return labels.map(l => l === '1' ? 'North' : 'Dragon');
+ }
+ };
+
+ $timeout(() => {
+ // this need to be triggered in this way just because of ngDoc,
+ // otherwise you can assign data directly in the config
+ this.configLocal.data = this.datas;
+ }, 1)
+ });
+ </file>
+ </example>
+
+ Fetching data from API
+
+ <example module="sampleSmartPieResource">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-smart-pie config="vm.config"></xos-smart-pie>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleSmartPieResource', ['xos.uiComponents', 'ngResource', 'ngMockE2E'])
+ .controller('SampleCtrl', function(){
+ this.config = {
+ resource: 'SampleResource',
+ groupBy: 'category',
+ classes: 'resource',
+ labelFormatter: (labels) => {
+ return labels.map(l => l === '1' ? 'North' : 'Dragon');
+ }
+ };
+ });
+ </file>
+ <file name="backendPoll.js">
+ angular.module('sampleSmartPieResource')
+ .run(function($httpBackend, _){
+ let datas = [
+ {id: 1, first_name: 'Jon', last_name: 'Snow', category: 1},
+ {id: 2, first_name: 'Danaerys', last_name: 'Targaryen', category: 2},
+ {id: 3, first_name: 'Aria', last_name: 'Stark', category: 1}
+ ];
+
+ $httpBackend.whenGET('/test').respond(200, datas)
+ })
+ .factory('_', function($window){
+ return $window._;
+ })
+ .service('SampleResource', function($resource){
+ return $resource('/test/:id', {id: '@id'});
+ })
+ </file>
+ </example>
+
+ Polling data from API
+
+ <example module="sampleSmartPiePoll">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-smart-pie config="vm.config"></xos-smart-pie>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleSmartPiePoll', ['xos.uiComponents', 'ngResource', 'ngMockE2E'])
+ .controller('SampleCtrl', function(){
+ this.config = {
+ resource: 'SampleResource',
+ groupBy: 'category',
+ poll: 2,
+ labelFormatter: (labels) => {
+ return labels.map(l => l === '1' ? 'Active' : 'Banned');
+ }
+ };
+ });
+ </file>
+ <file name="backend.js">
+ angular.module('sampleSmartPiePoll')
+ .run(function($httpBackend, _){
+ let mock = [
+ [
+ {id: 1, first_name: 'Jon', last_name: 'Snow', category: 1},
+ {id: 2, first_name: 'Danaerys', last_name: 'Targaryen', category: 2},
+ {id: 3, first_name: 'Aria', last_name: 'Stark', category: 1},
+ {id: 3, first_name: 'Tyrion', last_name: 'Lannister', category: 1}
+ ],
+
+ [
+ {id: 1, first_name: 'Jon', last_name: 'Snow', category: 1},
+ {id: 2, first_name: 'Danaerys', last_name: 'Targaryen', category: 2},
+ {id: 3, first_name: 'Aria', last_name: 'Stark', category: 2},
+ {id: 3, first_name: 'Tyrion', last_name: 'Lannister', category: 2}
+ ],
+
+ [
+ {id: 1, first_name: 'Jon', last_name: 'Snow', category: 1},
+ {id: 2, first_name: 'Danaerys', last_name: 'Targaryen', category: 2},
+ {id: 3, first_name: 'Aria', last_name: 'Stark', category: 1},
+ {id: 3, first_name: 'Tyrion', last_name: 'Lannister', category: 2}
+ ]
+ ];
+ $httpBackend.whenGET('/test').respond(function(method, url, data, headers, params) {
+ return [200, mock[Math.round(Math.random() * 3)]];
+ });
+ })
+ .factory('_', function($window){
+ return $window._;
+ })
+ .service('SampleResource', function($resource){
+ return $resource('/test/:id', {id: '@id'});
+ })
+ </file>
+ </example>
+ */
+ .directive('xosSmartPie', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ config: '='
+ },
+ template: `
+ <canvas
+ class="chart chart-pie {{vm.config.classes}}"
+ chart-data="vm.data" chart-labels="vm.labels"
+ chart-legend="{{vm.config.legend}}">
+ </canvas>
+ `,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function($injector, $interval, $scope, $timeout, _){
+
+ if(!this.config.resource && !this.config.data){
+ throw new Error('[xosSmartPie] Please provide a resource or an array of data in the configuration');
+ }
+
+ const groupData = (data) => _.groupBy(data, this.config.groupBy);
+ const formatData = (data) => _.reduce(Object.keys(data), (list, group) => list.concat(data[group].length), []);
+ const formatLabels = (data) => angular.isFunction(this.config.labelFormatter) ? this.config.labelFormatter(Object.keys(data)) : Object.keys(data);
+
+ const prepareData = (data) => {
+ // group data
+ let grouped = groupData(data);
+ this.data = formatData(grouped);
+ // create labels
+ this.labels = formatLabels(grouped);
+ }
+
+ if(this.config.resource){
+
+ this.Resource = $injector.get(this.config.resource);
+ const getData = () => {
+ this.Resource.query().$promise
+ .then((res) => {
+
+ if(!res[0]){
+ return;
+ }
+
+ prepareData(res);
+ });
+ }
+
+ getData();
+
+ if(this.config.poll){
+ $interval(() => {getData()}, this.config.poll * 1000)
+ }
+ }
+ else {
+ $scope.$watch(() => this.config.data, (data) => {
+ if(data){
+ prepareData(this.config.data);
+ }
+ }, true);
+ }
+
+ $scope.$on('create', function (event, chart) {
+ console.log(`create: ${chart.id}`);
+ });
+
+ $scope.$on('destroy', function (event, chart) {
+ console.log(`destroy: ${chart.id}`);
+ });
+
+ }
+ };
+ });
+})();
diff --git a/src/ui_components/smartComponents/smartTable/smartTable.component.js b/src/ui_components/smartComponents/smartTable/smartTable.component.js
new file mode 100644
index 0000000..622952f
--- /dev/null
+++ b/src/ui_components/smartComponents/smartTable/smartTable.component.js
@@ -0,0 +1,269 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ angular.module('xos.uiComponents')
+
+ /**
+ * @ngdoc directive
+ * @name xos.uiComponents.directive:xosSmartTable
+ * @link xos.uiComponents.directive:xosTable xosTable
+ * @link xos.uiComponents.directive:xosForm xosForm
+ * @restrict E
+ * @description The xos-table directive
+ * @param {Object} config The configuration for the component,
+ * it is composed by the name of an angular [$resource](https://docs.angularjs.org/api/ngResource/service/$resource)
+ * and an array of fields that shouldn't be printed.
+ * ```
+ * {
+ resource: 'Users',
+ hiddenFields: []
+ }
+ * ```
+ * @scope
+ * @example
+
+ <example module="sampleSmartTable">
+ <file name="index.html">
+ <div ng-controller="SampleCtrl as vm">
+ <xos-smart-table config="vm.config"></xos-smart-table>
+ </div>
+ </file>
+ <file name="script.js">
+ angular.module('sampleSmartTable', ['xos.uiComponents', 'ngResource', 'ngMockE2E'])
+ // This is only for documentation purpose
+ .run(function($httpBackend, _){
+ let datas = [{id: 1, name: 'Jhon', surname: 'Doe'}];
+ let count = 1;
+
+ let paramsUrl = new RegExp(/\/test\/(.+)/);
+
+ $httpBackend.whenDELETE(paramsUrl, undefined, ['id']).respond((method, url, data, headers, params) => {
+ data = angular.fromJson(data);
+ let id = url.match(paramsUrl)[1];
+ _.remove(datas, (d) => {
+ return d.id === parseInt(id);
+ })
+ return [204];
+ });
+
+ $httpBackend.whenGET('/test').respond(200, datas)
+ $httpBackend.whenPOST('/test').respond((method, url, data) => {
+ data = angular.fromJson(data);
+ data.id = ++count;
+ datas.push(data);
+ return [201, data, {}];
+ });
+ })
+ .factory('_', function($window){
+ return $window._;
+ })
+ .service('SampleResource', function($resource){
+ return $resource('/test/:id', {id: '@id'});
+ })
+ // End of documentation purpose, example start
+ .controller('SampleCtrl', function(){
+ this.config = {
+ resource: 'SampleResource'
+ };
+ });
+ </file>
+ </example>
+ */
+
+ .directive('xosSmartTable', function(){
+ return {
+ restrict: 'E',
+ scope: {
+ config: '='
+ },
+ template: `
+ <div class="row" ng-show="vm.data.length > 0">
+ <div class="col-xs-12 text-right">
+ <a href="" class="btn btn-success" ng-click="vm.createItem()">
+ Add
+ </a>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12 table-responsive">
+ <xos-table config="vm.tableConfig" data="vm.data"></xos-table>
+ </div>
+ </div>
+ <div class="panel panel-default" ng-show="vm.detailedItem">
+ <div class="panel-heading">
+ <div class="row">
+ <div class="col-xs-11">
+ <h3 class="panel-title" ng-show="vm.detailedItem.id">Update {{vm.config.resource}} {{vm.detailedItem.id}}</h3>
+ <h3 class="panel-title" ng-show="!vm.detailedItem.id">Create {{vm.config.resource}} item</h3>
+ </div>
+ <div class="col-xs-1">
+ <a href="" ng-click="vm.cleanForm()">
+ <i class="glyphicon glyphicon-remove pull-right"></i>
+ </a>
+ </div>
+ </div>
+ </div>
+ <div class="panel-body">
+ <xos-form config="vm.formConfig" ng-model="vm.detailedItem"></xos-form>
+ </div>
+ </div>
+ <xos-alert config="{type: 'success', closeBtn: true}" show="vm.responseMsg">{{vm.responseMsg}}</xos-alert>
+ <xos-alert config="{type: 'danger', closeBtn: true}" show="vm.responseErr">{{vm.responseErr}}</xos-alert>
+ `,
+ bindToController: true,
+ controllerAs: 'vm',
+ controller: function($injector, LabelFormatter, _, XosFormHelpers){
+
+ // TODO
+ // - Validate the config (what if resource does not exist?)
+
+ // NOTE
+ // Corner case
+ // - if response is empty, how can we generate a form ?
+
+ this.responseMsg = false;
+ this.responseErr = false;
+
+ this.tableConfig = {
+ columns: [
+ ],
+ actions: [
+ {
+ label: 'delete',
+ icon: 'remove',
+ cb: (item) => {
+ this.Resource.delete({id: item.id}).$promise
+ .then(() => {
+ _.remove(this.data, (d) => d.id === item.id);
+ this.responseMsg = `${this.config.resource} with id ${item.id} successfully deleted`;
+ })
+ .catch(err => {
+ this.responseErr = err.data.detail || `Error while deleting ${this.config.resource} with id ${item.id}`;
+ });
+ },
+ color: 'red'
+ },
+ {
+ label: 'details',
+ icon: 'search',
+ cb: (item) => {
+ this.detailedItem = item;
+ }
+ }
+ ],
+ classes: 'table table-striped table-bordered table-responsive',
+ filter: 'field',
+ order: true,
+ pagination: {
+ pageSize: 10
+ }
+ };
+
+ this.formConfig = {
+ exclude: this.config.hiddenFields,
+ fields: {},
+ formName: `${this.config.resource}Form`,
+ actions: [
+ {
+ label: 'Save',
+ icon: 'ok',
+ cb: (item) => {
+ let p;
+ let isNew = true;
+
+ if(item.id){
+ p = item.$update();
+ isNew = false;
+ }
+ else {
+ p = item.$save();
+ }
+
+ p.then((res) => {
+ if(isNew){
+ this.data.push(angular.copy(res));
+ }
+ delete this.detailedItem;
+ this.responseMsg = `${this.config.resource} with id ${item.id} successfully saved`;
+ })
+ .catch((err) => {
+ this.responseErr = err.data.detail || `Error while saving ${this.config.resource} with id ${item.id}`;
+ })
+ },
+ class: 'success'
+ }
+ ]
+ };
+
+ this.cleanForm = () => {
+ delete this.detailedItem;
+ };
+
+ this.createItem = () => {
+ this.detailedItem = new this.Resource();
+ };
+
+ this.Resource = $injector.get(this.config.resource);
+
+ const getData = () => {
+ this.Resource.query().$promise
+ .then((res) => {
+
+ if(!res[0]){
+ this.data = res;
+ return;
+ }
+
+ let item = res[0];
+ let props = Object.keys(item);
+
+ _.remove(props, p => {
+ return p === 'id' || p === 'validators'
+ });
+
+ // TODO move out cb, non sense triggering a lot of times
+ if(angular.isArray(this.config.hiddenFields)){
+ props = _.difference(props, this.config.hiddenFields)
+ }
+
+ let labels = props.map(p => LabelFormatter.format(p));
+
+ props.forEach((p, i) => {
+ let fieldConfig = {
+ label: labels[i],
+ prop: p
+ };
+
+ if(angular.isString(item[p]) && typeof item[p] !== 'undefined'){
+ fieldConfig.type = typeof item[p];
+ }
+
+ this.tableConfig.columns.push(fieldConfig);
+ });
+
+ // build form structure
+ // TODO move in a pure function for testing purposes
+ props.forEach((p, i) => {
+ this.formConfig.fields[p] = {
+ label: LabelFormatter.format(labels[i]).replace(':', ''),
+ type: XosFormHelpers._getFieldFormat(item[p])
+ };
+ });
+
+ this.data = res;
+ });
+ }
+
+ getData();
+ }
+ };
+ });
+})();
\ No newline at end of file
diff --git a/src/ui_components/smartComponents/smartTable/smartTable.scss b/src/ui_components/smartComponents/smartTable/smartTable.scss
new file mode 100644
index 0000000..fc63fdf
--- /dev/null
+++ b/src/ui_components/smartComponents/smartTable/smartTable.scss
@@ -0,0 +1,3 @@
+xos-smart-table{
+
+}
\ No newline at end of file
diff --git a/src/ui_components/ui-components.module.js b/src/ui_components/ui-components.module.js
new file mode 100644
index 0000000..f8bc222
--- /dev/null
+++ b/src/ui_components/ui-components.module.js
@@ -0,0 +1,31 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc overview
+ * @name xos.uiComponents
+ * @description
+ * # xos.uiComponents
+ * A collection of UI components useful for Dashboard development. <br/>
+ * Currently available components are:
+ * - [xosAlert](/#/module/xos.uiComponents.directive:xosAlert)
+ * - [xosForm](/#/module/xos.uiComponents.directive:xosForm)
+ * - [xosPagination](/#/module/xos.uiComponents.directive:xosPagination)
+ * - [xosSmartTable](/#/module/xos.uiComponents.directive:xosSmartTable)
+ * - [xosTable](/#/module/xos.uiComponents.directive:xosTable)
+ * - [xosValidation](/#/module/xos.uiComponents.directive:xosValidation)
+ **/
+
+ angular.module('xos.uiComponents', [
+ 'chart.js',
+ 'RecursionHelper'
+ ])
+})();
diff --git a/src/xosHelpers.module.js b/src/xosHelpers.module.js
new file mode 100644
index 0000000..25c8354
--- /dev/null
+++ b/src/xosHelpers.module.js
@@ -0,0 +1,43 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc overview
+ * @name xos.helpers
+ * @description
+ * # xos.Helpers
+ * A collection of helpers to work with XOS <br/>
+ * Currently available components are:
+ * - [NoHyperlinks](/#/module/xos.helpers.NoHyperlinks)
+ * - [SetCSRFToken](/#/module/xos.helpers.SetCSRFToken)
+ * - [xosNotification](/#/module/xos.helpers.xosNotification)
+ * - [XosUserPrefs](/#/module/xos.helpers.XosUserPrefs)
+ * <br/><br/>
+ * A set of angular [$resource](https://docs.angularjs.org/api/ngResource/service/$resource) is provided to work with the API.<br>
+ * You can find the documentation [here](#/rest-api)
+ **/
+
+ angular
+ .module('xos.helpers', [
+ 'ngCookies',
+ 'ngResource',
+ 'ngAnimate',
+ 'xos.uiComponents'
+ ])
+ .config(config)
+
+ /**
+ * @ngdoc service
+ * @name xos.helpers._
+ * @description Wrap [lodash](https://lodash.com/docs) in an Angular Service
+ **/
+
+ .factory('_', $window => $window._ );
+
+ function config($httpProvider, $interpolateProvider, $resourceProvider) {
+ $httpProvider.interceptors.push('SetCSRFToken');
+
+ // NOTE http://www.masnun.com/2013/09/18/django-rest-framework-angularjs-resource-trailing-slash-problem.html
+ $resourceProvider.defaults.stripTrailingSlashes = false;
+ }
+})();
\ No newline at end of file