Cordova/AngularJS アプリでこの奇妙なエラーが発生しました。
ときどき (まれに) AngularJS がコントローラーを見つけられずにクラッシュし、ユーザーに半分ロードされた無応答のビューが残ります。
エラーは、電話がしばらくロックされている場合にのみ発生するようで、定期的に再現することはほとんど不可能です. それは時々起こります。
ユーザーがアプリを閉じて再起動すると、すべて正常に動作します。
これまでのところ、iOS デバイスではなく、Android デバイスでのこのエラーに関する苦情のみを受け取りました。
編集:iOSデバイスでもエラーが発生しました。
誰かがこの問題に光を当てることができることを本当に願っています。
私のindex.htmlは次のようになります
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="guideApp">
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-inline'; script-src * 'self' 'unsafe-eval' 'unsafe-inline' https://api.tiles.mapbox.com/mapbox.js/v2.1.6/mapbox.js http://connect.facebook.net/en_GB/sdk.js;">
<link rel="stylesheet" href="css/snap.css" />
<link rel='stylesheet' href='css/mapbox.css' />
<link rel="stylesheet" href="css/leaflet.css" />
<link rel="stylesheet" href="css/guideapp.css" />
<link rel="stylesheet" href="css/xpengineicons.css" />
<!--<link rel="stylesheet" href="css/guideappTest.css" />-->
<title></title>
<link rel="icon" type="image/png" href="img/favicon.png"/>
<meta name="application-name" content="GuideApp"/>
<meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-TileImage" content="img/favicons/mstile-144x144.png" />
<meta name="msapplication-square70x70logo" content="img/favicons/mstile-70x70.png" />
<meta name="msapplication-square150x150logo" content="img/favicons/mstile-150x150.png" />
<meta name="msapplication-wide310x150logo" content="img/favicons/mstile-310x150.png" />
<meta name="msapplication-square310x310logo" content="img/favicons/mstile-310x310.png" />
</head>
<body ng-controller="globalCtrl" data-snap-ignore="true">
<div class="snap-drawers">
<div class="snap-drawer snap-drawer-left sideMenu">
<a href="#" ng-click="goTo('/home')">{{::langPack.startPage[lang]}}</a>
<a href="#" ng-click="goTo('/tourSelect')">{{::langPack.selectTour[lang]}}</a>
<a href="#" ng-click="goTo('/map')">{{::langPack.map[lang]}}</a>
<a href="#" ng-click="goTo('/news')">{{::langPack.news[lang]}}</a>
<a href="#" ng-click="toggleFoto($event)" class="fotoToggle" ng-class="{'redFotoToggle': photosInUploadQue > 0}">{{::langPack.photoMenu[lang]}}<i class="icon-angle-down fotoToggleAngle"></i></a>
<div class="fotoMenu" id="fotoMenu">
<a href="#" ng-click="toggleUploadPopup()" class="noUploadMenuItem" ng-class="{'uploadMenuItem': photosInUploadQue > 0}" compile="langPack.uploadPhotos[lang]"></a>
<a href="#" ng-click="goTo('/commonTours')">{{::langPack.followAnothersTour[lang]}}</a>
<a href="#" ng-click="broadcastResetPhotoTour();">{{::langPack.endPhototour[lang]}}</a>
<a href="#" ng-click="goTo('/viewtours')">{{::langPack.seePreviousTour[lang]}}</a>
<!--<a href="#" ng-click="selectPicture();">{{::langPack.photoFromGallery[lang]}}</a>-->
<a href="#" ng-click="broadcastSelectPicture();">{{::langPack.selectFromGallery[lang]}}</a>
<!--<a href="#" ng-if="actionRadius>100" ng-click="broadcastToggleActionRadius();">[Smaller radius]</a>-->
<!--<a href="#" ng-if="actionRadius<100" ng-click="broadcastToggleActionRadius();">[Larger radius]</a>-->
<!--<a href="#" ng-click="broadcastToggleActionRadius();">[{{langPack.toggleActionRadius[lang]}}]</a>-->
<!--<a href="#" ng-click="goTo('/end')">[End waypoint tour]</a> -->
</div>
<a href="#" ng-click="goTo('/about')">{{::langPack.about[lang]}}</a>
<a href="#" ng-click="sync.checkUpdates();">
{{::langPack.sync[lang]}}
<span id="updateCheck" class="updateCheck"><div class="circle"></div>{{::langPack.checkForUpdates[lang]}}</span>
<span id="uptodate" class="updateCheck uptodate"><i class="icon-ok"></i>{{::langPack.upToDate[lang]}}</span>
</a>
<a href="#" ng-click="showIntroOverlay()">{{::langPack.showIntroOverlay[lang]}}</a>
</div>
</div>
<div id="content" class="snap-content content" ng-style="customBg">
<nav id="header" ng-controller="cameraCtrl">
<a href="" ng-click="toggleMenu()" class="menuBtn show" id="menuBtn"><img src="img/menu-regular.svg"><!--<div class="menuNotification" ng-if="photosInUploadQue > 0">{{photosInUploadQue}}</div>--></a>
<a href="#" ng-click="goTo(backDirection)" class="backBtn" id="backBtn"><i class="icon-angle-up"></i></a>
<h1></h1>
<img ng-src="{{customLogo}}" id="logo">
<!-- Kamera når det virker -->
<a href="#" ng-click="takeGuidePicture()" class="cameraBtn" ng-if="showCam === true"><i class="icon-camera"></i></a>
<!-- <a href="#" ng-click="selectPicture()" class="galleryPhotoBtn" ng-if="showCam === true"><i class="icon-angle-down"></i></a> -->
<!-- <a href="#" ng-click="openUploadPopup($event)" id="photoUpload" class="uploadBtn"><i class="icon-angle-up"></i></a> -->
<!-- <a onclick = "window.open('http://guideapp.tmts.dk/', '_blank', 'location=yes'); return false;" id="viewInBrowser" class="uploadBtn"><i class="icon-angle-down"></i></a> -->
<div id="uploadPopupOverlay" class="popupOverlay" ng-click="closeUploadPopup();"></div>
<section id="uploadPopup">
<p>{{::langPack.uploadAndShare[lang]}}</p>
<form novalidate ng-submit="uploadPhotosSubmittedByPopup($event)">
<label for="emailInput" ng-show="showEmail === true">
{{::langPack.yourEmail[lang]}}
<div class="error" ng-if="emailMsg">{{::emailMsg}}</div>
</label>
<input type="email" value="{{emailAdress}}" required name="emailInput" ng-model="emailAdress" ng-show="showEmail === true" id="emailAdress">
<label for="titleInput">
{{::langPack.yourTitel[lang]}}
<div class="error" ng-if="titleMsg">{{::titleMsg}}</div>
</label>
<input type="text" value="{{tourTitle}}" name="titleInput" ng-model="tourTitle" id="uploadTitle" required>
<p class="emailExists" ng-show="showEmail === false"><span compile="langPack.sendTo[lang]"></span> <a href="#" ng-click="editEmail($event)">[{{::langPack.editEmail[lang]}}]</a></p>
<button class="btn left" ng-click="closeUploadPopup();">{{::langPack.cancel[lang]}}</button>
<button type="submit" class="btn right">{{::langPack.ok[lang]}}</button>
</form>
</section>
</nav>
<div class="slideInLeft" ng-view></div>
</div>
<aside ng-class="{active: showIntro}" id="intro" ng-swipe-left="nextSlide()" class="" ng-swipe-right="prevSlide()">
<div>
<div id="introSwipe">
<section id="intro{{$index+1}}" class="introSlide" ng-class="{'active': $index === 0}" ng-repeat="slide in introSlides" style="background-image: url('{{slide}}')"></section>
</div>
<section id="noShow">
<div class="slideNavigation">
<button ng-click="prevSlide()" ng-class="{disabled: firstSlide}">{{::langPack.prevSlide[lang]}}</button>
<div class="slideProgress">{{slideProgress}}/{{introSlides.length}}</div>
<button ng-click="nextSlide()" ng-class="{disabled: lastSlide}">{{::langPack.nextSlide[lang]}}</button>
</div>
</section>
</div>
<a id="closeIntro" href="#" ng-click="toggleIntro(); $event.preventDefault()">{{::langPack.toggleIntroText[lang]}}</a>
</aside>
<section id="confirmOverlay" class="popupOverlay" ng-click="confirmValue = false;">
<section class="checkPopup confirmPopup" id="confirmPopup">
<p id="confirmMessage"></p>
<button class="btn right" ng-click="confirmValue = true;">{{::langPack.ok[lang]}}</button>
<button class="btn left" ng-click="confirmValue = false;">{{::langPack.cancel[lang]}}</button>
</section>
</section>
<section id="spinnerOverlay" class="popupOverlay">
<section class="checkPopup spinnerPopup" id="spinnerPopup">
<div class="circle"></div>
<div class="spinnerText" id="spinnerText"></div>
</section>
</section>
<section id="statusOverlay" class="popupOverlay" ng-click="status.hide()">
<section class="checkPopup statusPopup" id="statusPopup">
<p id="statusText"></p>
<button class="btn" ng-click="status.hide()">Ok</button>
</section>
</section>
<!-- JavaScript libraries -->
<script src="cordova.js"></script>
<script src="js/libs/angular.min.js"></script>
<script src="js/libs/angular-locale_da-dk.js"></script>
<script src="js/libs/angular-route.min.js"></script>
<script src="js/libs/angular-animate.min.js"></script>
<script src="js/libs/angular-touch.min.js"></script>
<script src="js/libs/snap.min.js"></script>
<script src="js/libs/md5.js"></script>
<script src="js/libs/angular-listview.min.js"></script>
<!-- Mapbox -->
<!--<script src='https://api.tiles.mapbox.com/mapbox.js/v2.1.6/mapbox.js'></script>-->
<!-- <script src='https://api.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.2.0/leaflet-omnivore.min.js'></script> Draw GPX 20151014 -->
<!-- leaflet -->
<script src="js/libs/leaflet.js"></script>
<script src='js/libs/leaflet-omnivore.min.js'></script> <!-- Draw GPX 20151014 -->
<script src='js/libs/leaflet-tilelayer-cordova.js'></script> <!-- Offline maps -->
<!-- Custom script files -->
<script src="js/app.js"></script>
<script src="js/index.js"></script>
<!-- included controllers -->
<script src="controllers/globalCtrl.js"></script>
<script src="controllers/homeCtrl.js"></script>
<script src="controllers/aboutCtrl.js"></script>
<script src="controllers/tourSelectCtrl.js"></script>
<script src="controllers/mapCtrl.js"></script>
<script src="controllers/cameraCtrl.js"></script>
<script src="controllers/viewtoursCtrl.js"></script>
<script src="controllers/commonToursCtrl.js"></script>
<script src="controllers/highscoreCtrl.js"></script>
<script src="controllers/downloadCtrl.js"></script>
<script src="controllers/waypointCtrl.js"></script>
<script src="controllers/waypointGalleryCtrl.js"></script>
<script src="controllers/endCtrl.js"></script>
<script src="controllers/newsCtrl.js"></script>
</body>
</html>
AngularJS は app.js でロードされます。
// Init angular.js
var guideApp = angular.module('guideApp', ['ngRoute', 'ngAnimate', 'ngTouch']);
guideApp.config(['$routeProvider', '$compileProvider', function ($routeProvider, $compileProvider) {
$routeProvider.when('/', {
controller: 'homeCtrl',
templateUrl: 'views/home.html'
}).when('/home', {
controller: 'homeCtrl',
templateUrl: 'views/home.html'
}).when('/tourSelect', {
controller: 'tourSelectCtrl',
templateUrl: 'views/tourSelect.html'
}).when('/map', {
controller: 'mapCtrl',
templateUrl: 'views/map.html'
}).when('/about', {
controller: 'aboutCtrl',
templateUrl: 'views/about.html'
}).when('/viewtours', {
controller: 'viewtoursCtrl',
templateUrl: 'views/viewtours.html'
}).when('/commonTours', {
controller: 'commonToursCtrl',
templateUrl: 'views/commonTours.html'
}).when('/highscore', {
controller: 'highscoreCtrl',
templateUrl: 'views/highscore.html'
}).when('/download', {
controller: 'downloadCtrl',
templateUrl: 'views/download.html'
}).when('/waypoint', {
controller: 'waypointCtrl',
templateUrl: 'views/waypoint.html'
}).when('/waypointGallery', {
controller: 'waypointGalleryCtrl',
templateUrl: 'views/waypointGallery.html'
}).when('/end', {
controller: 'endCtrl',
templateUrl: 'views/end.html'
}).when('/news', {
controller: 'newsCtrl',
templateUrl: 'views/news.html'
}).otherwise({
redirectTo: '/'
});
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|cdvfile|file):/);
}]);
guideApp.directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}])
// custom on touchstart directive
/*guideApp.directive('guideappTouchstart', [function () {
return function(scope, element, attr) {
element.on('touchstart', function (event) {
scope.$apply(function () {
scope.$eval(attr.guideappTouchstart);
});
});
};
}]);*/
index.js ファイルは次のようになります。
var http,
app = {
initialize: function () {
this.bindEvents();
},
bindEvents: function () {
if (!window.cordova) {
this.onDeviceReady();
} else {
document.addEventListener('deviceready', this.onDeviceReady, false);
}
},
onDeviceReady: function () {
app.receivedEvent('deviceready');
console.log('Device ready');
if (window.cordova) {
// find and set the filesystem root
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.requestFileSystem(1, 0, function (fileSystem) {
console.log("got filesystem: "+fileSystem.name); // displays "persistent"
//console.log(fileSystem.root.fullPath); // displays "/"
window.rootFS = fileSystem.root;
});
}
},
receivedEvent: function (id) {
//angularInit();
this.platform();
},
server: function (wwwroot) {
if (httpd) {
httpd.getURL(function (url) {
if(url.length > 0) {
document.getElementById('url').innerHTML = "server is up: <a href='" + url + "' target='_blank'>" + url + "</a>";
} else {
httpd.startServer({
'www_root' : wwwroot,
'port' : 8080
}, function (url) {
//updateStatus();
}, function (error) {
console.log('server error ', error);
});
}
}, function () {});
} else {
console.log('CorHttpd plugin not available/ready');
}
},
platform: function () {
var deviceType;
if (navigator.userAgent.match(/Android/i)) { // Android
if (navigator.userAgent.match(/Mobile/i)) { // Android smartphone
console.log('Android smartphone');
deviceType = 'mobile';
} else { // Android tablets
//console.log('Android tablet');
deviceType = 'tablet';
}
} else if (navigator.userAgent.match(/iPad/i)) { // IOS ipad
//console.log('IOS tablet');
deviceType = 'tablet';
} else if (navigator.userAgent.match(/iPhone/i)) { //IOS smartphone
//console.log('IOS smartphone');
deviceType = 'mobile';
} else { // Dekstop as default
//console.log('desktop');
deviceType = 'tablet';
}
}
};
app.initialize();