(js) Replace vs-repeat by md-virtual-repeat

There're some issues with mdVirtualRepeat that required some changes to
our app:

1. Having multiple directives with md-virtual-repeat doesn't work well.
That why I added a dedicated div for the md-virtual-repeat directive.
2. ui-sref-active doesn't work well for child nodes of the
md-virtual-repeat element. The class was applied repetitively to a
visible child. I now save which message or card is selected and use the
ng-class directive to highlight the selected list item.
This commit is contained in:
Francis Lachapelle
2015-07-15 13:44:17 -04:00
parent dbb8acca9a
commit 730b803ad6
18 changed files with 149 additions and 153 deletions
@@ -304,68 +304,67 @@
</div>
</md-toolbar>
<div layout="row" class="md-flex" layout-align="space-between">
<div class="view-list" layout="column" flex="true" style="position: relative;">
<md-content id="contactsList">
<header class="sg-md-subheader sg-md-subheader--fixed">
<h2 class="sg-md-subhead-solo fg-sogoBlue-700">Contacts</h2>
</header>
<!-- Search field & special results
<input type="text" placeholder="Search" ng-model="search.filter" ng-keyup="doSearch($event)" />
<div ng-switch="search.status">
<div ng-switch-when="min-char" class="alert-bg">
<i class="icon-warning"></i><var:string label:value="Please enter at least three characters"/>
</div>
<div ng-switch-when="no-result" class="alert-bg">
<i class="icon-ion-search"></i><var:string label:value="No matching card"/>
</div>
<div ng-switch-when="remote-addressbook" ng-show="addressbook.cards.length == 0" class="alert-bg">
<i class="icon-ion-search"></i><var:string label:value="Initiate a search"/>
</div>
</div>
-->
<md-list vs-repeat="72" vs-scroll-parent="#contactsList">
<md-list-item
ng-repeat="currentCard in addressbook.selectedFolder.cards track by currentCard.id"
ng-click="addressbook.selectCard(currentCard)"
ui-sref-active="sg-active"
ui-sref="app.addressbook.card.view({addressbookId: addressbook.selectedFolder.id, cardId: currentCard.id})">
<div class="sg-selected-avatar" ng-show="currentCard.selected"
ng-click="currentCard.selected = !currentCard.selected">
<!-- selected avatar -->
</div>
<sg-gravatar-image class="md-tile-left"
ng-show="addressbook.notSelectedComponent(currentCard,'vcard')"
ng-click="currentCard.selected = !currentCard.selected"
email="{{currentCard.$preferredEmail(addressbook.selectedFolder.$query)}}"
size="48">
<!-- contact avatar -->
</sg-gravatar-image>
<div class="sg-list-avatar"
ng-show="addressbook.notSelectedComponent(currentCard, 'vlist')"
ng-click="currentCard.selected = !currentCard.selected">
<!-- list avatar -->
</div>
<div class="sg-tile-content">
<div class="sg-md-subhead-multi" ng-bind-html="currentCard.$fullname()"><!-- cn --></div>
<div class="sg-md-body-multi">{{currentCard.$preferredEmail(addressbook.selectedFolder.$query)}}</div>
</div>
</md-list-item>
</md-list>
</md-content>
<md-button class="md-fab md-fab-bottom-right md-accent"
label:aria-label="New Contact"
ng-click="addressbook.newComponent($event)">
<md-icon>add</md-icon>
</md-button>
<div class="view-list" layout="column">
<md-content id="contactsList" layout="column" class="md-flex">
<header class="sg-md-subheader sg-md-subheader--fixed">
<h2 class="md-default-theme sg-md-subhead-solo fg-sogoBlue-700">Contacts</h2>
</header>
<!-- Search field & special results
<input type="text" placeholder="Search" ng-model="search.filter" ng-keyup="doSearch($event)" />
<div ng-switch="search.status">
<div ng-switch-when="min-char" class="alert-bg">
<i class="icon-warning"></i><var:string label:value="Please enter at least three characters"/>
</div>
<div ng-switch-when="no-result" class="alert-bg">
<i class="icon-ion-search"></i><var:string label:value="No matching card"/>
</div>
<div ng-switch-when="remote-addressbook" ng-show="addressbook.cards.length == 0" class="alert-bg">
<i class="icon-ion-search"></i><var:string label:value="Initiate a search"/>
</div>
</div>
-->
<md-virtual-repeat-container class="md-flex">
<md-list>
<div md-virtual-repeat="currentCard in addressbook.selectedFolder.cards">
<md-list-item
ng-class="{'sg-active': currentCard.id == addressbook.selectedFolder.selectedCard}"
ng-click="addressbook.selectCard(currentCard)"
ui-sref="app.addressbook.card.view({addressbookId: addressbook.selectedFolder.id, cardId: currentCard.id})">
<!-- ui-sref-active="sg-active" -->
<div class="sg-selected-avatar" ng-show="currentCard.selected"
ng-click="currentCard.selected = !currentCard.selected">
<!-- selected avatar -->
</div>
<sg-gravatar-image class="md-tile-left"
ng-show="addressbook.notSelectedComponent(currentCard,'vcard')"
ng-click="currentCard.selected = !currentCard.selected"
email="{{currentCard.$preferredEmail(addressbook.selectedFolder.$query)}}"
size="48">
<!-- contact avatar -->
</sg-gravatar-image>
<div class="sg-list-avatar"
ng-show="addressbook.notSelectedComponent(currentCard, 'vlist')"
ng-click="currentCard.selected = !currentCard.selected">
<!-- list avatar -->
</div>
<div class="sg-tile-content">
<div class="sg-md-subhead-multi" ng-bind-html="currentCard.$fullname()"><!-- cn --></div>
<div class="sg-md-body-multi">{{currentCard.$preferredEmail(addressbook.selectedFolder.$query)}}</div>
</div>
</md-list-item>
</div>
</md-list>
</md-virtual-repeat-container>
<md-button class="md-fab md-fab-bottom-right md-accent"
label:aria-label="New Contact"
ng-click="addressbook.newComponent($event)">
<md-icon>add</md-icon>
</md-button>
</md-content>
</div>
<!-- This extra container is used to animate views transitions
double quotes in ng-animate is not a typo -->
<div id="detailView" class="view-detail ng-cloak" layout="column">
<md-card class="viewer" ui-view="card"><!-- card view --></md-card>
</div>
<div id="detailView" class="view-detail ng-cloak" layout="column" ui-view="card"
ng-show="addressbook.selectedFolder.selectedCard"><!-- card view --></div>
</div>
</script>
@@ -5,11 +5,11 @@
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:label="OGo:label"
>
<md-card class="viewer md-flex">
<md-card-content>
<md-button class="sg-icon-button show-sm"
label:aria-label="Close"
ng-click="editor.toggleDetailView()"><md-icon>close</md-icon></md-button>
ng-click="toggleDetailView()"><md-icon>close</md-icon></md-button>
<header class="msg-header">
<div ng-show="editor.card.tag == 'vcard'">
<sg-gravatar-image class="md-tile-left"
@@ -145,6 +145,6 @@
</div>
</section>
</md-card-content>
</md-card>
</container>
+48 -55
View File
@@ -2,62 +2,55 @@
<container
xmlns="http://www.w3.org/1999/xhtml"
xmlns:label="OGo:label">
<div class="view-list md-layout-fill md-flex" layout="column" style="position: relative;">
<md-content id="messagesList">
<style>
.vs-repeat-repeated-element {
width: 100%;
}
</style>
<md-progress-linear ng-show="mailbox.selectedFolder.$isLoading" md-mode="indeterminate"><!-- progress --></md-progress-linear>
<header class="sg-md-subheader sg-md-subheader--fixed">
<h2 class="sg-md-subhead-solo fg-sogoBlue-700">Messages</h2>
</header>
<md-list vs-repeat="72"
vs-scroll-parent="#messagesList">
<md-list-item
ng-repeat="currentMessage in mailbox.selectedFolder.$messages track by currentMessage.id"
ng-class="{unread: !currentMessage.isread}"
ng-click="mailbox.selectMessage(currentMessage)"
ui-sref="mail.account.mailbox.message({accountId: mailbox.account.id, mailboxId: (mailbox.selectedFolder.path | encodeUri), messageId: currentMessage.uid})"
ui-sref-active="sg-active">
<div class="sg-selected-avatar" ng-show="currentMessage.selected"
ng-click="currentMessage.selected = !currentMessage.selected">
<!-- selected avatar -->
</div>
<sg-gravatar-image class="md-tile-left"
ng-show="!currentMessage.selected"
ng-click="currentMessage.selected = !currentMessage.selected"
email="{{currentMessage['from'][0].email}}"
size="48">
<!-- gravatar -->
</sg-gravatar-image>
<div class="sg-tile-content">
<span class="msg-date"
ng-bind-html="currentMessage.relativedate"><!-- date --></span>
<div class="sg-md-subhead-multi">{{currentMessage.$shortAddress('from')}}</div>
<div class="sg-md-body-multi">{{currentMessage.subject}}</div>
<i class="icon-ion-refresh"
ng-hide="mailbox.selectedFolder.$loadMessage(currentMessage.uid)"><!-- loading --></i>
</div>
<div class="sg-tile-icons">
<md-icon ng-show="currentMessage.isflagged">star</md-icon>
<md-icon ng-show="currentMessage.isanswered">reply</md-icon>
<md-icon ng-show="currentMessage.isforwarded">forward</md-icon>
<md-icon ng-show="currentMessage.hasattachment">attach_file</md-icon>
</div>
</md-list-item>
</md-list>
</md-content>
<md-button class="md-fab md-fab-bottom-right md-accent"
label:aria-label="Write a new message"
ui-sref="mail.account.mailbox.newMessage({accountId: mailbox.account.id, mailboxId: (mailbox.selectedFolder.path | encodeUri)})">
<md-icon>add</md-icon>
</md-button>
<div class="view-list" layout="column">
<md-content id="messagesList" layout="column" class="md-flex">
<md-progress-linear ng-show="mailbox.selectedFolder.$isLoading" md-mode="indeterminate"><!-- progress --></md-progress-linear>
<header class="sg-md-subheader sg-md-subheader--fixed">
<h2 class="md-default-theme sg-md-subhead-solo fg-sogoBlue-700">Messages</h2>
</header>
<md-virtual-repeat-container class="md-flex">
<md-list>
<div md-virtual-repeat="currentMessage in mailbox.selectedFolder.$messages">
<md-list-item
ng-class="{'sg-active': currentMessage.uid == mailbox.selectedFolder.selectedMessage, unread: !currentMessage.isread}"
ng-click="mailbox.selectMessage(currentMessage)"
ui-sref="mail.account.mailbox.message({accountId: mailbox.account.id, mailboxId: (mailbox.selectedFolder.path | encodeUri), messageId: currentMessage.uid})">
<!-- ui-sref-active="sg-active"> -->
<div class="sg-selected-avatar"
ng-show="currentMessage.selected"
ng-click="currentMessage.selected = !currentMessage.selected"><!-- selected avatar --></div>
<sg-gravatar-image class="md-tile-left"
ng-show="!currentMessage.selected"
ng-click="currentMessage.selected = !currentMessage.selected"
email="{{currentMessage['from'][0].email}}"
size="48"><!-- avatar --></sg-gravatar-image>
<div class="sg-tile-content">
<span class="msg-date"
ng-bind-html="currentMessage.relativedate"><!-- date --></span>
<div class="sg-md-subhead-multi">{{currentMessage.$shortAddress('from')}}</div>
<div class="sg-md-body-multi">{{currentMessage.subject}}</div>
<i class="icon-ion-refresh"
ng-hide="mailbox.selectedFolder.$loadMessage(currentMessage.uid)"><!-- loading --></i>
</div>
<div class="sg-tile-icons">
<md-icon ng-show="currentMessage.isflagged">star</md-icon>
<md-icon ng-show="currentMessage.isanswered">reply</md-icon>
<md-icon ng-show="currentMessage.isforwarded">forward</md-icon>
<md-icon ng-show="currentMessage.hasattachment">attach_file</md-icon>
</div>
</md-list-item>
</div>
</md-list>
</md-virtual-repeat-container>
<md-button class="md-fab md-fab-bottom-right md-accent"
label:aria-label="Write a new message"
ui-sref="mail.account.mailbox.newMessage({accountId: mailbox.account.id, mailboxId: (mailbox.selectedFolder.path | encodeUri)})">
<md-icon>add</md-icon>
</md-button>
</md-content>
</div>
<!-- This extra container is used to animate views transitions
double quotes in ng-animate is not a typo -->
<div id="detailView" class="view-detail ng-cloak" layout="column" ui-view="message" ng-animate="'view'"><!-- message view --></div>
<div id="detailView" class="view-detail ng-cloak" layout="column" ui-view="message"
ng-show="mailbox.selectedFolder.selectedMessage"><!-- message view --></div>
</container>
@@ -7,10 +7,9 @@
xmlns:label="OGo:label"
xmlns:uix="OGo:uix">
<md-content md-scroll-y="md-scroll-y" class="md-padding bg-sogoPaper-50 md-whiteframe-z1">
<md-button class="sg-icon-button show-sm" ng-click="toggleDetailView()">
<md-icon>close</md-icon>
</md-button>
<md-content md-scroll-y="md-scroll-y" class="viewer md-padding md-flex bg-sogoPaper-50 md-whiteframe-z1">
<md-button class="sg-icon-button show-sm"
ng-click="toggleDetailView()"><md-icon>close</md-icon></md-button>
<header class="msg-header">
<div class="msg-header-content">
<div layout="row" layout-align="start center">
-1
View File
@@ -236,7 +236,6 @@
<script type="text/javascript" rsrc:src="js/vendor/angular-ui-router.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/ct-ui-router-extras.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-recursion.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-vs-repeat.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/Common/utils.js"><!-- space --></script>
<var:if condition="hasProductSpecificJavaScript">
-1
View File
@@ -106,7 +106,6 @@ module.exports = function(grunt) {
'<%= src %>/angular-ui-router/release/angular-ui-router{,.min}.js',
'<%= src %>/ui-router-extras/release/ct-ui-router-extras{,.min}.js',
'<%= src %>/angular-recursion/angular-recursion{,.min}.js',
'<%= src %>/angular-vs-repeat/src/angular-vs-repeat{,.min}.js',
'<%= src %>/angular-file-upload/angular-file-upload{,.min}.js{,map}',
//'<%= src %>/ng-file-upload/ng-file-upload{,.min}.js{,map}',
'<%= src %>/underscore/underscore-min.{js,map}'
-1
View File
@@ -8,7 +8,6 @@
"angular-ui-router": "latest",
"ui-router-extras": "latest",
"angular-recursion": "latest",
"angular-vs-repeat": "latest",
"angular-file-upload": "latest",
"angular-material": "master",
"compass-mixins": "latest",
+2 -1
View File
@@ -6,7 +6,7 @@
angular.module('SOGo.Common', []);
angular.module('SOGo.ContactsUI', ['ngSanitize', 'ui.router', 'vs-repeat', 'SOGo.Common'])
angular.module('SOGo.ContactsUI', ['ngSanitize', 'ui.router', 'SOGo.Common'])
.constant('sgSettings', {
baseURL: ApplicationBaseURL,
@@ -135,6 +135,7 @@
*/
stateCard.$inject = ['$stateParams', 'stateAddressbook'];
function stateCard($stateParams, stateAddressbook) {
stateAddressbook.selectedCard = $stateParams.cardId;
return stateAddressbook.$getCard($stateParams.cardId);
}
@@ -6,8 +6,8 @@
/**
* @ngInject
*/
AddressBookController.$inject = ['$state', '$scope', '$rootScope', '$stateParams', '$timeout', '$mdDialog', 'sgFocus', 'Card', 'AddressBook', 'Dialog', 'sgSettings', 'stateAddressbooks', 'stateAddressbook'];
function AddressBookController($state, $scope, $rootScope, $stateParams, $timeout, $mdDialog, focus, Card, AddressBook, Dialog, Settings, stateAddressbooks, stateAddressbook) {
AddressBookController.$inject = ['$state', '$mdDialog', 'sgFocus', 'Card', 'AddressBook', 'Dialog', 'sgSettings', 'stateAddressbooks', 'stateAddressbook'];
function AddressBookController($state, $mdDialog, focus, Card, AddressBook, Dialog, Settings, stateAddressbooks, stateAddressbook) {
var vm = this;
AddressBook.selectedFolder = stateAddressbook;
@@ -63,7 +63,7 @@
}
function notSelectedComponent(currentCard, type) {
return (currentCard.tag == type && !currentCard.selected);
return (currentCard && currentCard.tag == type && !currentCard.selected);
}
function unselectCards() {
+6 -3
View File
@@ -6,7 +6,7 @@
angular.module('SOGo.ContactsUI', []);
angular.module('SOGo.MailerUI', ['ngSanitize', 'ui.router', 'vs-repeat', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'ngAnimate'])
angular.module('SOGo.MailerUI', ['ngSanitize', 'ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'ngAnimate'])
.constant('sgSettings', {
baseURL: ApplicationBaseURL,
@@ -204,11 +204,14 @@
return messageObject.uid == $stateParams.messageId;
});
if (message)
if (message) {
stateMailbox.selectedMessage = $stateParams.messageId;
return message.$reload();
else
}
else {
// Message not found
$state.go('mail.account.mailbox', { accountId: stateMailbox.$account.id, mailboxId: encodeUriFilter(stateMailbox.path) });
}
}
/**
@@ -20,7 +20,7 @@
vm.confirmDeleteSelectedMessages = confirmDeleteSelectedMessages;
vm.copySelectedMessages = copySelectedMessages;
// vm.moveSelectedMessages = moveSelectedMessages;
function selectMessage(message) {
$state.go('mail.account.mailbox.message', {accountId: stateAccount.id, mailboxId: encodeUriFilter(stateMailbox.path), messageId: message.uid});
}
@@ -6,8 +6,8 @@
/**
* @ngInject
*/
MailboxesController.$inject = ['$scope', '$rootScope', '$stateParams', '$state', '$timeout', '$mdDialog', 'sgFocus', 'encodeUriFilter', 'Dialog', 'sgSettings', 'Account', 'Mailbox', 'User', 'stateAccounts'];
function MailboxesController($scope, $rootScope, $stateParams, $state, $timeout, $mdDialog, focus, encodeUriFilter, Dialog, Settings, Account, Mailbox, User, stateAccounts) {
MailboxesController.$inject = ['$state', '$timeout', '$mdDialog', 'sgFocus', 'encodeUriFilter', 'Dialog', 'sgSettings', 'Account', 'Mailbox', 'User', 'stateAccounts'];
function MailboxesController($state, $timeout, $mdDialog, focus, encodeUriFilter, Dialog, Settings, Account, Mailbox, User, stateAccounts) {
var vm = this,
account,
mailbox;
@@ -179,8 +179,8 @@
/**
* @ngInject
*/
MailboxACLController.$inject = ['$scope', '$mdDialog', 'usersWithACL', 'User', 'folder'];
function MailboxACLController($scope, $mdDialog, usersWithACL, User, folder) {
MailboxACLController.$inject = ['$mdDialog', 'usersWithACL', 'User', 'folder'];
function MailboxACLController($mdDialog, usersWithACL, User, folder) {
var vm = this;
vm.users = usersWithACL; // ACL users
+1 -1
View File
@@ -8,7 +8,7 @@
angular.module('SOGo.MailerUI', []);
angular.module('SOGo.PreferencesUI', []);
angular.module('SOGo.SchedulerUI', ['ngSanitize', 'ui.router', 'ct.ui.router.extras.sticky', 'ct.ui.router.extras.previous', 'vs-repeat', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.MailerUI', 'SOGo.PreferencesUI'])
angular.module('SOGo.SchedulerUI', ['ngSanitize', 'ui.router', 'ct.ui.router.extras.sticky', 'ct.ui.router.extras.previous', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.MailerUI', 'SOGo.PreferencesUI'])
.constant('sgSettings', {
baseURL: ApplicationBaseURL,
@@ -1,22 +1,10 @@
/// autoScrollList.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
@import 'extends';
// The only selector for vs-repeat lists is this id, we use attributes selector to
// avoid overspecifying
// todo: create new classes and refactor templates markup
// ----------------------------------------------------------------------------
.vs-repeat-repeated-element {
width: 100%;
}
[id='messagesList'],
[id='contactsList'] {
border-top: 44px solid transparent; // padding for the header.subheader,
border-bottom: 2px solid transparent; //close to a hack
md-list-item {
// dirty fix for vs-repeat damages
position: absolute;
}
border-bottom: 2px solid transparent; // close to a hack
md-list-item {
transition: background-color $swift-ease-in-duration $swift-ease-in-timing-function,
@@ -0,0 +1,2 @@
/*! virtualRepeat/_extends.scss - */
@import '../../../angular-material/src/components/virtualRepeat/virtualRepeater.scss';
@@ -0,0 +1,9 @@
/// virtualRepeater.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
@import 'extends';
// Override some md styles
.md-virtual-repeat-container {
.md-virtual-repeat-offsetter {
right: 0; // no need to keep space for the scrollbar
}
}
+6 -1
View File
@@ -26,6 +26,7 @@
// Original angular-material components
// ------------------------------------------------------------------------------
@import 'components/autocomplete/autocomplete';
@import 'components/autoScrollList/autoScrollList';
@import 'components/backdrop/backdrop';
@import 'components/bottomSheet/bottomSheet';
@import 'components/button/button';
@@ -35,13 +36,15 @@
@import 'components/content/content';
@import 'components/dialog/dialog';
@import 'components/divider/divider';
//@import 'components/favActions/fabActions';
@import 'components/fabSpeedDial/fabSpeedDial';
//@import 'components/fabToolbar/fabToolbar';
//@import 'components/fabTrigger/fabTrigger';
@import 'components/gridList/gridList';
@import 'components/icon/icon';
@import 'components/input/input';
@import 'components/list/list';
@import 'components/menu/menu';
@import 'components/autoScrollList/autoScrollList';
//@import 'components/progressCircular/progressCircular';
@import 'components/progressLinear/progressLinear';
@import 'components/radioButton/radio-button';
@@ -50,11 +53,13 @@
@import 'components/slider/slider';
@import 'components/sticky/sticky';
@import 'components/subheader/subheader';
//@import 'components/swipe/swipe';
@import 'components/switch/switch';
@import 'components/tabs/tabs';
//@import 'components/toast/toast';
@import 'components/toolbar/toolbar';
@import 'components/tooltip/tooltip';
@import 'components/virtualRepeat/virtualRepeat';
@import 'components/whiteframe/whiteframe';
// Theme
+1 -1
View File
@@ -52,7 +52,7 @@ $detailView-width: grid-step(8) !global;
.view-detail {
z-index: $z-index-view;
overflow-x: hidden;
background-color: $background-base-color;
background-color: transparent;
@include at(md) {
@include flex-col(md, 9);
margin: 0;