Skip to content

Commit 1437ed8

Browse files
authored
Merge pull request #886 from dpalou/MOBILE-1959
Mobile 1959
2 parents 75df59d + e0a8f09 commit 1437ed8

File tree

10 files changed

+220
-25
lines changed

10 files changed

+220
-25
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// (C) Copyright 2015 Martin Dougiamas
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
angular.module('mm.core.sidemenu')
16+
17+
/**
18+
* Controller of the iframe viewer.
19+
*
20+
* @module mm.core.sidemenu
21+
* @ngdoc controller
22+
* @name mmSideMenuIframeViewCtrl
23+
*/
24+
.controller('mmSideMenuIframeViewCtrl', function($scope, $stateParams, $sce) {
25+
$scope.title = $stateParams.title;
26+
$scope.url = $sce.trustAsResourceUrl($stateParams.url);
27+
});

www/core/components/sidemenu/controllers/menu.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ angular.module('mm.core.sidemenu')
2727
$mmSideMenu.setScope($scope);
2828
$scope.handlers = $mmSideMenuDelegate.getNavHandlers();
2929
$scope.areNavHandlersLoaded = $mmSideMenuDelegate.areNavHandlersLoaded;
30-
$scope.siteinfo = $mmSite.getInfo();
31-
loadLogoutLabel();
30+
loadSiteInfo();
3231

3332
$scope.logout = function() {
3433
$mmSitesManager.logout().finally(function() {
@@ -40,25 +39,29 @@ angular.module('mm.core.sidemenu')
4039
$scope.docsurl = docsurl;
4140
});
4241

42+
function loadSiteInfo() {
43+
var config = $mmSite.getStoredConfig();
44+
45+
$scope.siteinfo = $mmSite.getInfo();
46+
$scope.logoutLabel = 'mm.sidemenu.' + (config && config.tool_mobile_forcelogout == "1" ? 'logout': 'changesite');
47+
48+
$mmSite.getDocsUrl().then(function(docsurl) {
49+
$scope.docsurl = docsurl;
50+
});
51+
52+
$mmSideMenu.getCustomMenuItems().then(function(items) {
53+
$scope.customItems = items;
54+
});
55+
}
56+
4357
function updateSiteInfo() {
4458
// We need to use $timeout to force a $digest and make $watch notice the variable change.
4559
$scope.siteinfo = undefined;
4660
$timeout(function() {
47-
$scope.siteinfo = $mmSite.getInfo();
48-
loadLogoutLabel();
49-
50-
// Update docs URL, maybe the Moodle release has changed.
51-
$mmSite.getDocsUrl().then(function(docsurl) {
52-
$scope.docsurl = docsurl;
53-
});
61+
loadSiteInfo();
5462
});
5563
}
5664

57-
function loadLogoutLabel() {
58-
var config = $mmSite.getStoredConfig();
59-
$scope.logoutLabel = 'mm.sidemenu.' + (config && config.tool_mobile_forcelogout == "1" ? 'logout': 'changesite');
60-
}
61-
6265
var langObserver = $mmEvents.on(mmCoreEventLanguageChanged, updateSiteInfo);
6366
var updateSiteObserver = $mmEvents.on(mmCoreEventSiteUpdated, function(siteid) {
6467
if ($mmSite.getId() === siteid) {

www/core/components/sidemenu/main.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ angular.module('mm.core.sidemenu', [])
3333
$state.go('mm_login.init');
3434
}
3535
}
36+
})
37+
38+
.state('site.iframe-view', {
39+
url: '/iframe-view',
40+
params: {
41+
title: null,
42+
url: null
43+
},
44+
views: {
45+
'site': {
46+
templateUrl: 'core/components/sidemenu/templates/iframe.html',
47+
controller: 'mmSideMenuIframeViewCtrl'
48+
}
49+
}
3650
});
3751

3852
})

www/core/components/sidemenu/services/sidemenu.js

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,102 @@ angular.module('mm.core.sidemenu')
2121
* @ngdoc service
2222
* @name $mmSideMenu
2323
*/
24-
.factory('$mmSideMenu', function($log) {
24+
.factory('$mmSideMenu', function($log, $mmLang, $mmSitesManager, mmCoreConfigConstants) {
2525
$log = $log.getInstance('$mmSideMenu');
2626

2727
var self = {},
2828
scope;
2929

30+
/**
31+
* Get a list of custom menu items for a certain site.
32+
*
33+
* @module mm.core.sidemenu
34+
* @ngdoc method
35+
* @name $mmSideMenu#getCustomMenuItems
36+
* @param {String} [siteId] Site ID. If not defined, current site.
37+
* @return {Object[]} List of custom menu items.
38+
*/
39+
self.getCustomMenuItems = function(siteId) {
40+
return $mmSitesManager.getSite(siteId).then(function(site) {
41+
var itemsString = site.getStoredConfig('tool_mobile_custommenuitems'),
42+
items,
43+
position = 0, // Position of each item, to keep the same order as it's configured.
44+
map = {},
45+
result = [];
46+
47+
if (!itemsString || typeof itemsString != 'string') {
48+
// Setting not valid.
49+
return result;
50+
}
51+
52+
// Add items to the map.
53+
items = itemsString.split(/(?:\r\n|\r|\n)/);
54+
angular.forEach(items, function(item) {
55+
var values = item.split('|'),
56+
id,
57+
label = values[0] ? values[0].trim() : values[0],
58+
url = values[1] ? values[1].trim() : values[1],
59+
type = values[2] ? values[2].trim() : values[2],
60+
lang = (values[3] ? values[3].trim() : values[3]) || 'none',
61+
icon = values[4] ? values[4].trim() : values[4];
62+
63+
if (!label || !url || !type) {
64+
// Invalid item, ignore it.
65+
return;
66+
}
67+
68+
id = url + '#' + type;
69+
if (!icon) {
70+
// Icon not defined, use default one.
71+
icon = type == 'embedded' ? 'ion-qr-scanner' : 'ion-link';
72+
}
73+
74+
if (!map[id]) {
75+
// New entry, add it to the map.
76+
map[id] = {
77+
url: url,
78+
type: type,
79+
position: position,
80+
labels: {}
81+
};
82+
position++;
83+
}
84+
85+
map[id].labels[lang.toLowerCase()] = {
86+
label: label,
87+
icon: icon
88+
};
89+
});
90+
91+
if (!position) {
92+
// No valid items found, stop.
93+
return result;
94+
}
95+
96+
return $mmLang.getCurrentLanguage().then(function(currentLang) {
97+
var fallbackLang = mmCoreConfigConstants.default_lang || 'en';
98+
99+
// Get the right label for each entry and add it to the result.
100+
angular.forEach(map, function(entry) {
101+
var data = entry.labels[currentLang] || entry.labels.none || entry.labels[fallbackLang];
102+
if (!data) {
103+
// No valid label found, get the first one.
104+
data = entry.labels[Object.keys(entry.labels)[0]];
105+
}
106+
107+
result[entry.position] = {
108+
url: entry.url,
109+
type: entry.type,
110+
label: data.label,
111+
icon: data.icon
112+
};
113+
});
114+
115+
return result;
116+
});
117+
});
118+
};
119+
30120
/**
31121
* Hide the right side menu.
32122
*
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<ion-view>
2+
<ion-nav-title>{{ title }}</ion-nav-title>
3+
<ion-content mm-state-class>
4+
<mm-iframe src="url"></mm-iframe>
5+
</ion-content>
6+
</ion-view>

www/core/components/sidemenu/templates/menu.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ <h2>{{siteinfo.fullname}}</h2>
4444
<ion-spinner ng-if="loading" class="icon"></ion-spinner>
4545
</a>
4646
</li>
47+
<li ng-repeat="item in customItems" class="mm-sidemenu-customitem">
48+
<a ng-if="item.type != 'embedded'" menu-close class="item item-icon-left" ng-href="{{item.url}}" mm-link in-app="{{item.type == 'inappbrowser'}}" title="{{item.label}}">
49+
<i class="icon {{item.icon}}"></i>{{item.label}}
50+
</a>
51+
<a ng-if="item.type == 'embedded'" menu-close class="item item-icon-left" ui-sref="site.iframe-view({title: item.label, url: item.url})" title="{{item.label}}">
52+
<i class="icon {{item.icon}}"></i>{{item.label}}
53+
</a>
54+
</li>
4755
<li>
4856
<a menu-close class="item item-icon-left" ng-href="{{siteinfo.siteurl}}" mm-link auto-login="yes" title="{{ 'mm.sidemenu.website' | translate}}">
4957
<i class="icon ion-earth"></i>{{ 'mm.sidemenu.website' | translate}}

www/core/directives/iframe.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
angular.module('mm.core')
1616

17+
.constant('mmCoreIframeTimeout', 15000)
18+
1719
/**
1820
* Directive to display content in an iframe.
1921
*
@@ -27,7 +29,7 @@ angular.module('mm.core')
2729
* @param {Mixed} [width=100%] Width of the iframe. If not defined, use 100%.
2830
* @param {Mixed} [height=100%] Height of the iframe. If not defined, use 100%.
2931
*/
30-
.directive('mmIframe', function($log, $mmUtil, $mmText, $mmSite, $mmFS) {
32+
.directive('mmIframe', function($log, $mmUtil, $mmText, $mmSite, $mmFS, $timeout, mmCoreIframeTimeout) {
3133
$log = $log.getInstance('mmIframe');
3234

3335
var tags = ['iframe', 'frame', 'object', 'embed'];
@@ -195,17 +197,38 @@ angular.module('mm.core')
195197

196198
return {
197199
restrict: 'E',
198-
template: '<div class="iframe-wrapper"><iframe class="mm-iframe" ng-style="{\'width\': width, \'height\': height}" ng-src="{{src}}"></iframe></div>',
200+
templateUrl: 'core/templates/iframe.html',
199201
scope: {
200202
src: '='
201203
},
202204
link: function(scope, element, attrs) {
205+
var url = (scope.src && scope.src.toString()) || '', // Convert $sce URLs to string URLs.
206+
iframe = angular.element(element.find('iframe')[0]);
207+
203208
scope.width = $mmUtil.formatPixelsSize(attrs.iframeWidth) || '100%';
204209
scope.height = $mmUtil.formatPixelsSize(attrs.iframeHeight) || '100%';
205210

206-
var iframe = angular.element(element.find('iframe')[0]);
211+
// Show loading only with external URLs.
212+
scope.loading = !!url.match(/^https?:\/\//i);
213+
207214
treatFrame(iframe);
208215

216+
if (scope.loading) {
217+
iframe.on('load', function() {
218+
scope.loading = false;
219+
$timeout(); // Use $timeout to force a digest and update the view.
220+
});
221+
222+
iframe.on('error', function() {
223+
scope.loading = false;
224+
$mmUtil.showErrorModal('mm.core.errorloadingcontent', true);
225+
$timeout(); // Use $timeout to force a digest and update the view.
226+
});
227+
228+
$timeout(function() {
229+
scope.loading = false;
230+
}, mmCoreIframeTimeout);
231+
}
209232
}
210233
};
211234
});

www/core/directives/link.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ angular.module('mm.core')
2222
* @name mmLink
2323
*
2424
* @param {Boolean} [captureLink=false] If the link needs to be captured by the app.
25+
* @param {Boolean} [inApp=false] True to open in embedded browser, false to open in system browser.
2526
* @param {String} [autoLogin=check] If the link should be open with auto-login. Accepts the following values:
2627
* "yes" -> Always auto-login.
2728
* "no" -> Never auto-login.
@@ -33,9 +34,11 @@ angular.module('mm.core')
3334
* Convenience function to correctly navigate, open file or url in the browser.
3435
*
3536
* @param {String} href HREF to be opened.
37+
* @param {Mixed} [inApp] True to open in embedded browser, false to open in system browser.
3638
* @param {String} [autoLogin=check] Whether to auto-login. "yes", "no" or "check".
3739
*/
38-
function navigate(href, autoLogin) {
40+
function navigate(href, inApp, autoLogin) {
41+
inApp = inApp && inApp !== 'false';
3942
autoLogin = autoLogin || 'check';
4043

4144
if (href.indexOf('cdvfile://') === 0 || href.indexOf('file://') === 0) {
@@ -56,13 +59,29 @@ angular.module('mm.core')
5659
// It's an external link, we will open with browser. Check if we need to auto-login.
5760
if (!$mmSite.isLoggedIn()) {
5861
// Not logged in, cannot auto-login.
59-
$mmUtil.openInBrowser(href);
62+
if (inApp) {
63+
$mmUtil.openInApp(href);
64+
} else {
65+
$mmUtil.openInBrowser(href);
66+
}
6067
} else if (autoLogin == 'yes') {
61-
$mmSite.openInBrowserWithAutoLogin(href);
68+
if (inApp) {
69+
$mmSite.openInAppWithAutoLogin(href);
70+
} else {
71+
$mmSite.openInBrowserWithAutoLogin(href);
72+
}
6273
} else if (autoLogin == 'no') {
63-
$mmUtil.openInBrowser(href);
74+
if (inApp) {
75+
$mmUtil.openInApp(href);
76+
} else {
77+
$mmUtil.openInBrowser(href);
78+
}
6479
} else {
65-
$mmSite.openInBrowserWithAutoLoginIfSameSite(href);
80+
if (inApp) {
81+
$mmSite.openInAppWithAutoLoginIfSameSite(href);
82+
} else {
83+
$mmSite.openInBrowserWithAutoLoginIfSameSite(href);
84+
}
6685
}
6786
}
6887
}
@@ -82,11 +101,11 @@ angular.module('mm.core')
82101
if (attrs.captureLink && attrs.captureLink !== 'false') {
83102
$mmContentLinksHelper.handleLink(href).then(function(treated) {
84103
if (!treated) {
85-
navigate(href, attrs.autoLogin);
104+
navigate(href, attrs.inApp, attrs.autoLogin);
86105
}
87106
});
88107
} else {
89-
navigate(href, attrs.autoLogin);
108+
navigate(href, attrs.inApp, attrs.autoLogin);
90109
}
91110
}
92111
}

www/core/lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"errorfileexistssamename": "There's already a file with this name.",
6161
"errorinvalidform": "The form contains invalid data. Please make sure to fill all required fields and that the data is valid.",
6262
"errorinvalidresponse": "Invalid response received. Please contact your Moodle site administrator if the error persists.",
63+
"errorloadingcontent": "Error loading content.",
6364
"erroropenfilenoapp": "Error opening the file: no app found to open this kind of file.",
6465
"erroropenfilenoextension": "Error opening the file: the file doesn't have extension.",
6566
"erroropenpopup": "This activity is trying to open a popup. This is not supported in this app.",

www/core/templates/iframe.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="iframe-wrapper" ng-class="{'mm-loading-container': loading}">
2+
<iframe ng-show="!loading" class="mm-iframe" ng-style="{'width': width, 'height': height}" ng-src="{{src}}"></iframe>
3+
<ion-spinner ng-if="loading"></ion-spinner>
4+
</div>

0 commit comments

Comments
 (0)