Bert Carremans
Bert Carremans

Reputation: 1733

Issue with Ionic (v1) app using listView and PouchDb

In my Ionic app there is a listView with cards on which users can click. After clicking they should see a page with more details. All content is in a pouchDB database (and also remotely in Firebase). The code to generate this list is below:

        <div class="card" ng-repeat="challenge in vm.challenges track by $index" ng-class="{'bounceInLeft': $index % 2 !== 0, 'bounceInRight': $index % 2 === 0}" ng-click="vm.goToChallenge(challenge)">
        <div class="item item-text-wrap">
            <div class="resource">
                <img ng-src="{{ challenge.resources[0].image }}" ng-attr-alt="{{ challenge.resources[0].caption }}" ng-srcset="{{ challenge.resources[0].srcset }}" ng-if="challenge.resources && challenge.resources[0].type === 'img'">
                <img ng-src="{{ challenge.resources[0].url | getYouTubeImage }}" ng-attr-alt="{{ challenge.resources[0].caption }}" ng-if="challenge.resources && challenge.resources[0].type === 'video'">
                <i class="ion-play" ng-if="challenge.resources && challenge.resources[0].type === 'video'"></i>
            </div>
            <p class="category">{{ challenge.categoryName }}</p>
            <p class="name">{{ challenge.name }}</p>
        </div>
        <div class="item footer item-divider">
            <div class="row">
                <div class="col col-50">
                    <i class="ion-calendar"></i> {{ challenge.seasonsObjects.join(', ') }}
                </div>
                <div class="col col-50">
                    <div>
                        <i class="ion-bookmark" ng-repeat="bookmark in (challenge.ranking + 1) | range track by $index"></i>
                    </div>
                    {{ challenge.rankingName }}
                </div>
            </div>
        </div>
    </div>

Unfortunately, on an iPhone 5 (iOS 10.2) and Samsung Galaxy S4 Mini (Android 4.4) clicking on a card does not show the details of that card (see image below). On an iPhone 5s and Samsung Galaxy S4 (Android 5) this works correctly (see second image below).

Empty screen - Details not shown

Working example

The function goToChallenge is defined as follows in the controller:

vm.goToChallenge = (challenge) => {
    NavigationService.go('home.challenge', {
        challenge: challenge._id
    });
};

The NavigationService has the following code:

angular.module('starter').factory('NavigationService', ['$window', '$q', 'ionic', '$state', '$ionicHistory', '$ionicLoading', '$ionicPopup', '$cordovaNativeAudio', '$cordovaSocialSharing', '$cordovaNetwork', '$ionicSideMenuDelegate', '$ionicNavBarDelegate', '$ionicTabsDelegate', NavigationService]);

function NavigationService($window, $q, ionic, $state, $ionicHistory, $ionicLoading, $ionicPopup, $cordovaNativeAudio, $cordovaSocialSharing, $cordovaNetwork, $ionicSideMenuDelegate, $ionicNavBarDelegate, $ionicTabsDelegate) {
let factory = {};
// Set root of history stack to the registration page with historyRoot: true
// Disable animation from slider to registration page to avoid weird transition
// in which the header bar pops down. This is because we do not have a header bar in the slider
factory.setRootAndGo = (state, params = {}) => {
    $ionicHistory.nextViewOptions({
        historyRoot: true,
        disableAnimate: true
    });
    factory.go(state, params);
};
factory.go = (state, params = {}) => {
    $state.go(state, params, {
        location: 'replace',
        reload: true
    });
};
factory.replace = (state, params = {}) => {
    $ionicHistory.currentView($ionicHistory.backView());
    factory.go(state, params);
};
factory.toggleMenu = () => $ionicSideMenuDelegate.toggleRight();
factory.showTabs = () => {
    $ionicTabsDelegate.showBar(true);
};
factory.hideTabs = () => {
    $ionicTabsDelegate.showBar(false);
};
factory.setTitle = (title) => $ionicNavBarDelegate.title(title);
factory.getParams = () => $state.params;
factory.getNavHistory = () => $ionicHistory.viewHistory();
factory.showLoading = (options) => $ionicLoading.show(options);
factory.hideLoading = () => $ionicLoading.hide();
factory.isWebView = () => ionic.Platform.isWebView();
factory.isOffline = () => factory.isWebView() && $cordovaNetwork.isOffline();
factory.goToWebPage = (url) => $window.open(url, '_system');
factory.shareFacebook = (text, img, url) => {
    let defer = $q.defer();
    $cordovaSocialSharing.shareViaFacebook(text, img, url)
        .then(defer.resolve, defer.reject);
    return defer.promise;
};
factory.shareTwitter = (text, img, url) => {
    let defer = $q.defer();
    $cordovaSocialSharing.shareViaTwitter(text, img, url)
        .then(defer.resolve, defer.reject);
    return defer.promise;
};
factory.playSound = (name) => $cordovaNativeAudio.play(name);
factory.alert = (title, text) => $ionicPopup.alert({
    title: title,
    template: text
});
return factory;
};

Does anyone know how to solve this issue, please? I have been looking for a few days now and can't figure out why. Thanks!

EDIT

When I check with chrome://inspect in a virtual device (via Genymotion), I get the following error in the console

TypeError: Object # has no method 'forEach' at ChallengeCtrl.vm.onAfterEnter (file:///android_asset/www/build/app.js:226:12) at m.$emit (file:///android_asset/www/lib/ionic/js/ionic.bundle.min.js:178:407) at Object.q.emit (file:///android_asset/www/lib/ionic/js/ionic.bundle.min.js:470:20592) at m (file:///android_asset/www/lib/ionic/js/ionic.bundle.min.js:470:19062) at HTMLElement.g (file:///android_asset/www/lib/ionic/js/ionic.bundle.min.js:470:18921) at Pf (file:///android_asset/www/lib/ionic/js/ionic.bundle.min.js:70:477) at HTMLElement.Of.d (file:///android_asset/www/lib/ionic/js/ionic.bundle.min.js:70:424) ionic.bundle.min.js:150

The onAfterEnter code block can be found below.

    vm.onAfterEnter = () => {
    //let ionViewDOM = $window.document.querySelectorAll('.challenge.pane .scroll-content')[0];
    let ionViewDOM = $window.innerHeight - 43;  // innerHeight minus the heigh of the title bar
    let popovers = $window.document.querySelectorAll('.message-popover');
    popovers.forEach((popover) => {
        //popover.style.height = `${ionViewDOM.clientHeight}px`;
        popover.style.height = ionViewDOM + `px`;
        if (ionViewDOM.clientHeight < 450) {
            popover.classList.add('small-screen');
        } else if (ionViewDOM.clientHeight < 550) {
            popover.classList.add('medium-screen');
        } else {
            popover.classList.remove('small-screen');
            popover.classList.remove('medium-screen');
        }
    });

    let params = NavigationService.getParams();
    ChallengeService.get(params.challenge)
        .then((result) => {
            vm.challenge = result;

            switch (vm.challenge.status) {
                case 'accepted':
                    vm.challengeAccepted = true;
                    break;
                case 'completed':
                    vm.challengeCompleted = true;
                    break;
            }
            NavigationService.setTitle(vm.challenge.gardenType.name);
            vm.challengeLoaded = true;

            ChallengeService.listRandomAvailableForUser(params.challenge, vm.challenge.gardenType.challenges)
                .then((result) => vm.randomChallenges = result);
        });
};

When I check in an iPhone 5 simulator (via XCode) I get the following similar error

Error: popovers.forEach is not a function. (In 'popovers.forEach', 'popovers.forEach' is undefined)

So it has to do with the popovers. Could it be that $window.document.querySelectorAll('.message-popover') does not return anything? The div with the message-popover class are initially hidden on the page and only shown when a certain condition is met (with ng-show).

Upvotes: 5

Views: 186

Answers (1)

Davide Castronovo
Davide Castronovo

Reputation: 1396

It seems that your page is not completely rendered in those case you reach the foreach error. have you tryed to set a timeout or similar before execute the querySelectorAll on the page?

Upvotes: 0

Related Questions