Skip to content

Commit

Permalink
feat(uibootstrap-modal): add basic modal service and template when us…
Browse files Browse the repository at this point in the history
…ing uibootstrap

Changes: (only if ui-bootstrap is selected)
- add `client/components/modal` folder
- modal folder contains service, markup template, and stylesheet
- modal service is intended to be extended, comes with `Modal.confirm.delete()` method
- admin and main page will both use `Modal.confirm.delete()`

Todo:
- review code for cleanliness and correctness
- possibly extend the modal service to include a basic alert class?
- write test for `Modal` service?
  • Loading branch information
kingcody committed Aug 15, 2014
1 parent c9f80bc commit 7c14bed
Show file tree
Hide file tree
Showing 16 changed files with 277 additions and 14 deletions.
2 changes: 1 addition & 1 deletion app/templates/client/app/admin(auth)/admin(html).html
Expand Up @@ -6,7 +6,7 @@
<li class="list-group-item" ng-repeat="user in users">
<strong>{{user.name}}</strong><br>
<span class="text-muted">{{user.email}}</span>
<a ng-click="delete(user)" class="trash"><span class="glyphicon glyphicon-trash pull-right"></span></a>
<a ng-click="delete(<% if(filters.uibootstrap) { %>user.name + ' (' + user.email + ')', <% } %>user)" class="trash"><span class="glyphicon glyphicon-trash pull-right"></span></a>
</li>
</ul>
</div>
2 changes: 1 addition & 1 deletion app/templates/client/app/admin(auth)/admin(jade).jade
Expand Up @@ -7,5 +7,5 @@ div(ng-include='"components/navbar/navbar.html"')
strong {{user.name}}
br
span.text-muted {{user.email}}
a.trash(ng-click='delete(user)')
a.trash(ng-click='delete(<% if(filters.uibootstrap) { %>user.name + " (" + user.email + ")", <% } %>user)')
span.glyphicon.glyphicon-trash.pull-right
@@ -1,12 +1,12 @@
'use strict'

angular.module '<%= scriptAppName %>'
.controller 'AdminCtrl', ($scope, $http, Auth, User) ->
.controller 'AdminCtrl', ($scope, $http, Auth, User<% if(filters.uibootstrap) { %>, Modal<% } %>) ->

# Use the User $resource to fetch all users
$scope.users = User.query()

$scope.delete = (user) ->
$scope.delete = <% if(filters.uibootstrap) { %>Modal.confirm.delete <% } %>(user) ->
User.remove id: user._id
angular.forEach $scope.users, (u, i) ->
$scope.users.splice i, 1 if u is user
6 changes: 3 additions & 3 deletions app/templates/client/app/admin(auth)/admin.controller(js).js
@@ -1,17 +1,17 @@
'use strict';

angular.module('<%= scriptAppName %>')
.controller('AdminCtrl', function ($scope, $http, Auth, User) {
.controller('AdminCtrl', function ($scope, $http, Auth, User<% if(filters.uibootstrap) { %>, Modal<% } %>) {

// Use the User $resource to fetch all users
$scope.users = User.query();

$scope.delete = function(user) {
$scope.delete = <% if(filters.uibootstrap) { %>Modal.confirm.delete(<% } %>function(user) {
User.remove({ id: user._id });
angular.forEach($scope.users, function(u, i) {
if (u === user) {
$scope.users.splice(i, 1);
}
});
};
}<% if(filters.uibootstrap) { %>)<% } %>;
});
2 changes: 1 addition & 1 deletion app/templates/client/app/main/main(html).html
Expand Up @@ -13,7 +13,7 @@ <h1>'Allo, 'Allo!</h1>
<div class="col-lg-12">
<h1 class="page-header">Features:</h1>
<ul class="nav nav-tabs nav-stacked col-md-4 col-lg-4 col-sm-6" ng-repeat="thing in awesomeThings">
<li><a href="#" tooltip="{{thing.info}}">{{thing.name}}<% if(filters.socketio) { %><button type="button" class="close" ng-click="deleteThing(thing)">&times;</button><% } %></a></li>
<li><a href="#" tooltip="{{thing.info}}">{{thing.name}}<% if(filters.socketio) { %><button type="button" class="close" ng-click="deleteThing(<% if(filters.uibootstrap) { %>thing.name, <% } %>thing)">&times;</button><% } %></a></li>
</ul>
</div>
</div><% if(filters.socketio) { %>
Expand Down
2 changes: 1 addition & 1 deletion app/templates/client/app/main/main(jade).jade
Expand Up @@ -14,7 +14,7 @@ header#banner.hero-unit
li
a(href='#', tooltip='{{thing.info}}')
| {{thing.name}}<% if(filters.socketio) { %>
button.close(type='button', ng-click='deleteThing(thing)') ×<% } %><% if(filters.socketio) { %>
button.close(type='button', ng-click='deleteThing(<% if(filters.uibootstrap) { %>thing.name, <% } %>thing)') ×<% } %><% if(filters.socketio) { %>

form.thing-form
label Syncs in realtime across clients
Expand Down
4 changes: 2 additions & 2 deletions app/templates/client/app/main/main.controller(coffee).coffee
@@ -1,7 +1,7 @@
'use strict'

angular.module '<%= scriptAppName %>'
.controller 'MainCtrl', ($scope, $http<% if(filters.socketio) { %>, socket<% } %>) ->
.controller 'MainCtrl', ($scope, $http<% if(filters.socketio) { %>, socket<% } %><% if(filters.uibootstrap && filters.mongoose) { %>, Modal<% } %>) ->
$scope.awesomeThings = []

$http.get('/api/things').success (awesomeThings) ->
Expand All @@ -15,7 +15,7 @@ angular.module '<%= scriptAppName %>'

$scope.newThing = ''

$scope.deleteThing = (thing) ->
$scope.deleteThing = <% if(filters.uibootstrap) { %>Modal.confirm.delete <% } %>(thing) ->
$http.delete '/api/things/' + thing._id<% } %><% if(filters.socketio) { %>

$scope.$on '$destroy', ->
Expand Down
6 changes: 3 additions & 3 deletions app/templates/client/app/main/main.controller(js).js
@@ -1,7 +1,7 @@
'use strict';

angular.module('<%= scriptAppName %>')
.controller('MainCtrl', function ($scope, $http<% if(filters.socketio) { %>, socket<% } %>) {
.controller('MainCtrl', function ($scope, $http<% if(filters.socketio) { %>, socket<% } %><% if(filters.uibootstrap && filters.mongoose) { %>, Modal<% } %>) {
$scope.awesomeThings = [];

$http.get('/api/things').success(function(awesomeThings) {
Expand All @@ -17,9 +17,9 @@ angular.module('<%= scriptAppName %>')
$scope.newThing = '';
};

$scope.deleteThing = function(thing) {
$scope.deleteThing = <% if(filters.uibootstrap) { %>Modal.confirm.delete(<% } %>function(thing) {
$http.delete('/api/things/' + thing._id);
};<% } %><% if(filters.socketio) { %>
}<% if(filters.uibootstrap) { %>)<% } %>;<% } %><% if(filters.socketio) { %>

$scope.$on('$destroy', function () {
socket.unsyncUpdates('thing');
Expand Down
23 changes: 23 additions & 0 deletions app/templates/client/components/modal(uibootstrap)/modal(css).css
@@ -0,0 +1,23 @@
.modal-primary .modal-header,
.modal-info .modal-header,
.modal-success .modal-header,
.modal-warning .modal-header,
.modal-danger .modal-header {
color: #fff;
border-radius: 5px 5px 0 0;
}
.modal-primary .modal-header {
background: #428bca;
}
.modal-info .modal-header {
background: #5bc0de;
}
.modal-success .modal-header {
background: #5cb85c;
}
.modal-warning .modal-header {
background: #f0ad4e;
}
.modal-danger .modal-header {
background: #d9534f;
}
@@ -0,0 +1,11 @@
<div class="modal-header">
<button ng-if="modal.dismissable" type="button" ng-click="$dismiss()" class="close">&times;</button>
<h4 ng-if="modal.title" ng-bind="modal.title" class="modal-title"></h4>
</div>
<div class="modal-body">
<p ng-if="modal.text" ng-bind="modal.text"></p>
<div ng-if="modal.html" ng-bind-html="modal.html"></div>
</div>
<div class="modal-footer">
<button ng-repeat="button in modal.buttons" ng-class="button.classes" ng-click="button.click($event)" ng-bind="button.text" class="btn"></button>
</div>
@@ -0,0 +1,8 @@
.modal-header
button.close(ng-if='modal.dismissable', type='button', ng-click='$dismiss()') &times;
h4.modal-title(ng-if='modal.title', ng-bind='modal.title')
.modal-body
p(ng-if='modal.text', ng-bind='modal.text')
div(ng-if='modal.html', ng-bind-html='modal.html')
.modal-footer
button.btn(ng-repeat='button in modal.buttons', ng-class='button.classes', ng-click='button.click($event)', ng-bind='button.text')
@@ -0,0 +1,25 @@
.modal-primary,
.modal-info,
.modal-success,
.modal-warning,
.modal-danger {
.modal-header {
color: #fff;
border-radius: 5px 5px 0 0;
}
}
.modal-primary .modal-header {
background: @brand-primary;
}
.modal-info .modal-header {
background: @brand-info;
}
.modal-success .modal-header {
background: @brand-success;
}
.modal-warning .modal-header {
background: @brand-warning;
}
.modal-danger .modal-header {
background: @brand-danger;
}
@@ -0,0 +1,25 @@
.modal-primary,
.modal-info,
.modal-success,
.modal-warning,
.modal-danger {
.modal-header {
color: #fff;
border-radius: 5px 5px 0 0;
}
}
.modal-primary .modal-header {
background: $brand-primary;
}
.modal-info .modal-header {
background: $brand-info;
}
.modal-success .modal-header {
background: $brand-success;
}
.modal-warning .modal-header {
background: $brand-warning;
}
.modal-danger .modal-header {
background: $brand-danger;
}
@@ -0,0 +1,23 @@
.modal-primary
.modal-info
.modal-success
.modal-warning
.modal-danger
.modal-header
color #fff
border-radius 5px 5px 0 0

.modal-primary .modal-header
background #428bca

.modal-info .modal-header
background #5bc0de

.modal-success .modal-header
background #5cb85c

.modal-warning .modal-header
background #f0ad4e

.modal-danger .modal-header
background #d9534f
@@ -0,0 +1,71 @@
'use strict'

angular.module '<%= scriptAppName %>'
.factory 'Modal', ($rootScope, $modal) ->

###
Opens a modal
@param {Object} scope - an object to be merged with modal's scope
@param {String} modalClass - (optional) class(es) to be applied to the modal
@return {Object} - the instance $modal.open() returns
###
openModal = (scope, modalClass) ->
modalScope = $rootScope.$new()
scope = scope or {}
modalClass = modalClass or 'modal-default'
angular.extend modalScope, scope
$modal.open
templateUrl: 'components/modal/modal.html'
windowClass: modalClass
scope: modalScope


# Public API here

# Confirmation modals
confirm:

###
Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
@param {Function} del - callback, ran when delete is confirmed
@return {Function} - the function to open the modal (ex. myModalFn)
###
delete: (del) ->
del = del or angular.noop

###
Open a delete confirmation modal
@param {String} name - name or info to show on modal
@param {All} - any additional args are passed staight to del callback
###
->
args = Array::slice.call arguments
name = args.shift()
deleteModal = undefined
deleteModal = openModal(
modal:
dismissable: true
title: 'Confirm Delete'
html: '<p>Are you sure you want to delete <strong>' + name + '</strong> ?</p>'
buttons: [
{
classes: 'btn-danger'
text: 'Delete'
click: (e) ->
deleteModal.close e
return
}
{
classes: 'btn-default'
text: 'Cancel'
click: (e) ->
deleteModal.dismiss e
return
}
]
, 'modal-danger')
deleteModal.result.then (event) ->
del.apply event, args
return

return
@@ -0,0 +1,77 @@
'use strict';

angular.module('<%= scriptAppName %>')
.factory('Modal', function ($rootScope, $modal) {
/**
* Opens a modal
* @param {Object} scope - an object to be merged with modal's scope
* @param {String} modalClass - (optional) class(es) to be applied to the modal
* @return {Object} - the instance $modal.open() returns
*/
function openModal(scope, modalClass) {
var modalScope = $rootScope.$new();
scope = scope || {};
modalClass = modalClass || 'modal-default';

angular.extend(modalScope, scope);

return $modal.open({
templateUrl: 'components/modal/modal.html',
windowClass: modalClass,
scope: modalScope
});
}

// Public API here
return {

/* Confirmation modals */
confirm: {

/**
* Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
* @param {Function} del - callback, ran when delete is confirmed
* @return {Function} - the function to open the modal (ex. myModalFn)
*/
delete: function(del) {
del = del || angular.noop;

/**
* Open a delete confirmation modal
* @param {String} name - name or info to show on modal
* @param {All} - any additional args are passed staight to del callback
*/
return function() {
var args = Array.prototype.slice.call(arguments),
name = args.shift(),
deleteModal;

deleteModal = openModal({
modal: {
dismissable: true,
title: 'Confirm Delete',
html: '<p>Are you sure you want to delete <strong>' + name + '</strong> ?</p>',
buttons: [{
classes: 'btn-danger',
text: 'Delete',
click: function(e) {
deleteModal.close(e);
}
}, {
classes: 'btn-default',
text: 'Cancel',
click: function(e) {
deleteModal.dismiss(e);
}
}]
}
}, 'modal-danger');

deleteModal.result.then(function(event) {
del.apply(event, args);
});
};
}
}
};
});

0 comments on commit 7c14bed

Please sign in to comment.