I have a .net web service containg two methods getUserTasks & getAllTasks(noOfDays) both return valid Json Data. Have verified it with JSONLINT.
var app = angular.module('tasksApp', []);
app.controller('taskController', function($scope, $http, taskGroupFactory) {
$scope.LoadTasks = function(taskFilter, dateFilter) {
console.log(taskFilter + '-' + dateFilter);
if (taskFilter == 'Self') {
$scope.enableFilters = true;
//$scope.taskGroups = taskGroupFactory.getUserTasks();
var getUTasks = taskGroupFactory.getUserTasks();
getUTasks.then(
function(tasks) {
console.log("success");
$scope.taskGroups = tasks.data;
}, function(errorData) {
console.log("Error: " + errorData);
});
};
if (taskFilter == 'All') {
$scope.enableFilters = false;
var getAllTasks = taskGroupFactory.getAllUserTasks(dateFilter);
getAllTasks.then(
function(tasks) {
console.log("success");
$scope.taskGroups = tasks.data;
}, function(errorData) {
console.log("Error: " + errorData);
});
//$scope.taskGroups = taskGroupFactory.getAllUserTasks(dateFilter);
// $scope.taskGroups = taskGroupFactory.getAllUserTasks(dateFilter)
// .success(function (tasks) {
// console.log('success');
// $scope.taskGroups = tasks.data;
// })
// .error(function (error) {
// console.log(error);
// });
};
};
});
app.factory('taskGroupFactory', function($http) {
var factory = {};
var serviceUrl = '/_layouts/SP.TaskDashboard/WebService/TaskService.asmx';
factory.getUserTasks = function() {
//return $http.post(serviceUrl + '/GetMyTasks');
return $http({
method: 'post',
url: serviceUrl + '/GetMyTasks',
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
};
factory.getAllUserTasks = function(taskDateFilter) {
//var serviceUrl = '/_layouts/SP.TaskDashboard/WebService/TaskService.asmx';
// return $http.post(serviceUrl + '/GetAllTasks', { dateFilter: taskDateFilter })
// .success(function (tasks) {
// return tasks;
// })
// .error(function(errorData) {
// return errorData;
// });
// return $http({
// method: 'post',
// url: serviceUrl + '/GetAllTasks',
// headers: { "Content-Type": "application/json", "Accept": "application/json" }
// });
return $http.post(serviceUrl + '/GetAllTasks', {
dateFilter: taskDateFilter
});
//[{"WebTitle": "Press Releases","WorkflowName": "","Tasks": [{"ID": 1,"Title": "Test","WebTitle": "Press Releases","Status": "Not Started","EncodedAbsoluteUrl": "http://sukcw-vwi-sps02:2010/","ItemLink": "PressReleases/WorkflowTasks/1_.000","WorkflowLink": "","WorkflowName": "","ListId": "9F8054AD-E8C4-47B7-B9DB-24FA05853F31","DueDate": "/Date(1422316800000)/","Created": "/Date(1421327050000)/","ParentItemTitle": "","Classification": "overdue"},{"ID": 2,"Title": "Another Test","WebTitle": "Press Releases","Status": "Not Started","EncodedAbsoluteUrl": "http://sukcw-vwi-sps02:2010/","ItemLink": "PressReleases/WorkflowTasks/2_.000","WorkflowLink": "","WorkflowName": "","ListId": "9F8054AD-E8C4-47B7-B9DB-24FA05853F31","DueDate": "/Date(1422057600000)/","Created": "/Date(1421746613000)/","ParentItemTitle": "","Classification": "overdue"}]}, {"WebTitle": "Test","WorkflowName": "test apprvl workflow","Tasks": [{"ID": 29,"Title": "Please approve 05live-ego","WebTitle": "Test","Status": "Not Started","EncodedAbsoluteUrl": "http://sukcw-vwi-sps02:2010/","ItemLink": "sites/responsive/test/WorkflowTasks/29_.000","WorkflowLink": "http://sukcw-vwi-sps02:2010/sites/responsive/test/Documents/05live-ego.jpg","WorkflowName": "test apprvl workflow","ListId": "6A288BE5-51DD-43BE-B036-4B4A73BFAA5C","DueDate": null,"Created": "/Date(1426854267000)/","ParentItemTitle": " 05live-ego","Classification": "upcoming"}]}]
};
return factory;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="tasksApp">
<div ng-controller="taskController">
<button type="button" class="btn btn-info" ng-click="toggle()">Show Pending Tasks</button>
<div ng-hide="myVar">
<input type="radio" name="taskType" value="Self" ng-model="taskFilter" ng-change="LoadTasks(taskFilter, null)" />My Tasks
<input type="radio" name="taskType" value="All" ng-model="taskFilter" ng-change="LoadTasks(taskFilter,list_day_option)" />All Tasks
<span ng-hide="enableFilters"> show tasks created in last <select ng-options="o.id as o.name for o in list_day_options.data" ng-model="list_day_option" ng-change="LoadTasks(taskFilter,list_day_option)"></select></span>
<div class="btn-group legendWrapper" ng-init="classificationFilter='duetoday'">
Overdue
Due today
Upcoming
Show all
</div>
<div class="panel-group">
<div ng-repeat="t in taskGroups" ng-click="itemClicked($index)" class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">{{t.WebTitle + '-' + '(' + filtered.length + ')'}}</h4>
</div>
<div ng-class="applyToggleClass($index)">
<div ng-repeat="tsk in t.Tasks | filter:t.Classification=getClassificationFilter() as filtered" ng-class="applyTskClass(tsk)">
<span>{{tsk.Title}} </span><span ng-class="applyStatusStyle(tsk)">{{tsk.Status}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Method call to getUserTasks from factory works fine, where as method call to getAllUserTasks(dateFilter) raises syntax error. No error in fiddler, shows the valid json data!! with status 200 but when I debug the script control goes into error block doesnot reach success block :( I have different ways to invoke the service method with parameter but nothing works. As you can see in the comments, feel it is to do with the way I am calling the webservice or passing the parameter to $http.post, please help.
Thanks
Related
I have a controller that returns an array from my django view, i am trying to display each object in a different line in my html but it just returns json.
my controller.js
mainApp.controller('hsController', function($scope, $http, $compile, $timeout, $location) {
$scope.data = {};
$scope.init = function() {
console.log("angular loaded!")
}
$scope.data.form = {
hs_search: "",
result: {},
};
$scope.data.formStyle = {
hs_search: ""
};
$scope.submitForm = function() {
//console.log($scope.data.form)
var error = 0;
if(!$scope.data.form.hs_search) {
$scope.data.formStyle.hs_search = "is_invalid";
error++;
} else {
$scope.data.formStyle.hs_search = "";
}
if(error==0) {
var jsonCall = $http({
method: 'POST',
url:'/theapp/hs-code-search',
data: $scope.data.form,
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
});
jsonCall.success(function(data, status, headers, config) {
if(data.status==1) {
$scope.data.form.result = data.data
console.log($scope.data.form.result)
}
});
jsonCall.error(function(data, status, headers, config) {
if(data.status==0) {
$.growl.error({ message: data.error});
}
});
console.log($scope.data.form)
} else {
$.growl.error({ message: "Please fill the form correctly!"});
}
}
});
my html
<div class="row">
<div class="col-lg-11 col-md-11 mx-auto">
<h3>Search Result For </h3>
<div ng-repeat="chapter_code in data.form.result">
{[{chapter_code}]}
</div>
<div ng-repeat="chapter_desc in data.form.result">
{[{chapter_desc}]}
</div>
</div>
</div>
Result that am getting
What i wanted instead is just the value of each object shown.
Try to convert the JSON to an Object using JSON.parse() and then you can use ng-repeat
myController.js
if(data.status==1) {
$scope.data.form.result = JSON.parse(data.data);
console.log($scope.data.form.result);
// Console shall return an Object
}
HTML
<div class="row">
<div class="col-lg-11 col-md-11 mx-auto">
<h3>Search Result For </h3>
<div ng-repeat="chapterCode in data.form.result.chapter_code">
{{chapterCode}}
</div>
<div ng-repeat="chapterDesc in data.form.result.chapter_desc">
{{chapterDesc}}
</div>
</div>
</div>
I have the following requirement: I have one drop-down list containing Employee records and which is getting filled on page load (JSON format). In that data list I have one value as Approval Level which is an integer value.
Now I want that when I select employee ,as per his Approval Level value I want that no of drop down list get filled with employee data.
Means when I select employee whose Approval Level is 5, I want 5 drop down list below that get filled with Employee records
HTML:-
<div class="col-md-6">
<select class="form-control crl dropdown" placeholder="Approval Level" name="ApprovalLevel" id="ApprovalLevelDDL" required ng-model="insert.Approval_Level" ng-change="makeArray()">
<option value="">Employee Name</option>
<option value="{{list.Approval_Level}}" ng-repeat="list in EmpList">{{list.Emp_Name}}</option>
</select>
<span style="color:red" class="error" ng-show="myForm.DesignationName.$error.required || myForm.DesignationName.touched.required">
Require
</span>
</div>
</div>
<div class="row col-md-10 col-md-offset-1 mypadd" ng-repeat="o in arr">
<div class="col-md-6">
<label for="DepartmentHead">Approval Head</label>
</div>
<div class="col-md-6">
<select class="form-control crl dropdown" placeholder="Approval Level" name="ApprovalLevel_{{$index}}" id="ApprovalLevelDDL_{{$index}}" required ng-model="selectedItem" ng-change="HeadEmployeeList({{$index}})" ng-options="list.Emp_ID as list.Emp_Name for list in ddlArr">
</select>
</div>
</div>
Angular Controller:-
function EmployeeList() {
$http({
method: "POST",
url: "/Employee/GetEmployeeList/",
datatype: "json",
headers: { "ContentType": "application/json" },
}).then(function (data) {
$scope.EmpList = data.data.EmpList;
$rootScope.DataList = data.data.EmpList;
$scope.edatalist = data.data.EmpList;
console.log($rootScope.DataList);
}).catch(function (data) {
$scope.error = data.data;
console.log($scope.error);
});
};
EmployeeList();
$scope.EmployeeDataList = function () {
$http({
method: "POST",
url: "/Employee/GetEmployeeList/",
datatype: "json",
headers: { "ContentType": "application/json" },
}).then(function (data) {
//$rootScope.DataList = data.data.EmpList;
$scope.edatalist = data.data.EmpList;
console.log($scope.edatalist);
}).catch(function (data) {
$scope.error = data.data;
console.log($scope.error);
});
};
$scope.insert = $rootScope.DataList;
$scope.arr = [];
$scope.newardata = [];
$scope.ddlArr = [];
$scope.makeArray = function () {
console.log($scope.insert);
$scope.arr.length = 0;
$scope.EmpList.Emp_ID = parseInt($scope.EmpList.Emp_ID, 10);
$scope.EmployeeDataList();
for (var i = 0; i <= parseInt($scope.insert.Approval_Level) ; i++) {
$scope.arr.push(i);
$scope.EmpList[i].Emp_ID = parseInt($scope.EmpList[i].Emp_ID, 10);
$scope.ddlArr.push($scope.EmpList[i]);
}
$scope.newardata = $scope.arr;
console.log($scope.arr);
console.log($scope.ddlArr);
}
console.log($scope.newardata);
var Emplist = [];
var EDATA = [];
$scope.HeadEmployeeList = function (id) {
console.log($scope.newardata);
var eid = {};
console.log(Emplist);
var data = parseInt(this.EmpList.Emp_ID);
console.log(data);
var index = Emplist.indexOf(parseInt(this.insert.Emp_ID));
var subindex = EDATA.indexOf(parseInt($scope.EmpList.Emp_ID,10));
console.log(index);
console.log(subindex);
if (index < 0){
Emplist.push(this.insert.Emp_ID);
EDATA.push($scope.EmpList.Emp_ID,10);
}
console.log(Emplist.indexOf(this.insert.Emp_ID));
console.log(Emplist);
console.log(EDATA.indexOf(parseInt($scope.EmpList.Emp_ID,10)));
console.log(EDATA);
$scope.EDATA = EDATA;
};
I have been able to successfully upload an image with a custom field template calling a function that handles the upload. It then gathers the return url and from there, i have no idea how to insert it back into the field for saving with the form.
Any help would be much appreciated :)
Here is my code:
var myApp = angular.module('myApp', ['ng-admin', 'backand', 'ngFileUpload']);
//myApp.directive('dashboardSummary', require('./dashboards/dashboardSummary'));
myApp.config(['BackandProvider', function(BackandProvider) {
BackandProvider.setAppName('');
BackandProvider.setSignUpToken('');
BackandProvider.setAnonymousToken('');
}]);
myApp.config(function(RestangularProvider) {
// add a response interceptor
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var extractedData;
// .. to look for getList operations
if (operation === "getList") {
// .. and handle the data and meta data
extractedData = data.data;
extractedData.meta = data.data.__metadata;
} else {
extractedData = data;
}
return extractedData;
});
});
myApp.config(['NgAdminConfigurationProvider','BackandProvider', function (nga, BackandProvider, $scope) {
// create an admin application
BackandProvider.setAppName('');
BackandProvider.setSignUpToken('');
BackandProvider.setAnonymousToken('');
var admin = nga.application('Pocket Release Admin Manager')
.baseApiUrl('https://api.backand.com/1/objects/'); // main API endpoint#
// Users
var user = nga.entity('users');
user.listView().fields([
nga.field('firstName').isDetailLink(true),
nga.field('lastName'),
nga.field('email')
]);
user.creationView().fields([
nga.field('firstName'),
nga.field('lastName'),
nga.field('email', 'email')
]);
user.editionView().fields(user.creationView().fields());
// add the user entity to the admin application
admin.addEntity(user);
// Platforms
var platforms = nga.entity('platforms');
platforms.listView().fields([
nga.field('id'),
nga.field('platform_name'),
]);
platforms.creationView().fields([
nga.field('id'),
nga.field('platform_name'),
nga.field('platform_id')
]);
platforms.editionView().fields(platforms.creationView().fields());
admin.addEntity(platforms);
var data = {};
// Games
var games = nga.entity('games');
games.listView().fields([
nga.field('id'),
nga.field('game_title').isDetailLink(true),
nga.field('game_url'),
nga.field('platforms', 'reference')
.targetEntity(platforms)
.targetField(nga.field('platform_name'))
.label('Platform')
]);
games.creationView().fields([
nga.field('game_title'),
nga.field('image').template('<img ng-src="{{game_url}}" ng-show="game_url">'),
nga.field('game_url', 'file').uploadInformation({'url':'', 'apifilename':'game_url'}).template('<div ng-controller="main"><label class="btn btn-default btn-file">Browse<input id="fileInput" field="::field" value="entry.values[{{main}}]" entry="entry" entity="::entity" form="formController.form" datastore="::formController.dataStore" type="file" style="display: none;" accept="*/*" ng-click="initUpload()" /></label></div>', true),
nga.field('platforms'),
nga.field('game_description'),
nga.field('game_boxart'),
nga.field('game_release_uk', 'date'),
nga.field('game_release_us', 'date'),
nga.field('game_release_eu', 'date')
]);
games.editionView().fields(games.creationView().fields());
admin.addEntity(games);
// Dash
admin.dashboard(nga.dashboard()
.addCollection(nga.collection(games)
.name('total_games')
.title('Total Games')
.fields([
nga.field('game_title')
])
.sortField('game_title')
.sortDir('DESC')
.order(1)
).template(`
<div class="row dashboard-starter"></div>
<dashboard-summary></dashboard-summary>
<div class="row dashboard-content">
<div class="col-lg-6">
<div class="panel panel-default">
<ma-dashboard-panel collection="dashboardController.collections.total_games" entries="dashboardController.entries.total_games" datastore="dashboardController.datastore"></ma-dashboard-panel>
</div>
</div>
</div>
`));
// Menu Customize
// customize menu
admin.menu(nga.menu()
.addChild(nga.menu(user).icon('<span class="glyphicon glyphicon-file"></span> ')) // customize the entity menu icon
.addChild(nga.menu(games).icon('<span class="glyphicon glyphicon-folder-open"></span> ')) // you can even use utf-8 symbols!
.addChild(nga.menu(platforms).icon('<span class="glyphicon glyphicon-tags"></span> '))
);
// attach the admin application to the DOM and execute it
nga.configure(admin);
}]);
myApp.controller('main', function ($scope, $rootScope, $location, $http, Backand) {
// Image upload stuff
$scope.initUpload = function(){
var fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function(e) {
imageChanged(fileInput);
});
}
function imageChanged(fileInput) {
//read file content
var file = fileInput.files[0];
var reader = new FileReader();
reader.onload = function(e) {
upload(file.name, e.currentTarget.result).then(function(res) {
$scope.imageUrl = res.data.url;
$scope.filename = file.name;
return res.data.url;
}, function(err){
alert(err.data);
});
};
reader.readAsDataURL(file);
};
function upload(filename, filedata) {
// By calling the files action with POST method in will perform
// an upload of the file into Backand Storage
return $http({
method: 'POST',
url : Backand.getApiUrl()+'/1/objects/action/games',
params:{
"name": 'files'
},
headers: {
'Content-Type': 'application/json'
},
// you need to provide the file name and the file data
data: {
"filename": Math.floor(Date.now() / 1000) + ''+ filename.match(/\.[0-9a-z]+$/i),
"filedata": filedata.substr(filedata.indexOf(',') + 1, filedata.length) //need to remove the file prefix type
}
}).success(function(data, status, headers, config) {
//$scope.game_url = data.url;
//$("#game_url").addClass("ng-dirty ng-valid-parse ng-touched").removeClass("ng-pristine ng-untouched");
//$("#game_boxart").val(data.url).addClass("ng-dirty ng-valid-parse ng-touched").removeClass("ng-pristine ng-untouched");
return data.url;
});
};
});
Have you tried making a PUT call to backand in order to save this url in the relevant column?
return $http({
method: 'PUT',
url : Backand.getApiUrl()+'/1/objects/games/YOUR_GAME_ID',
headers: {
'Content-Type': 'application/json'
},
data: {
urlColumn:imageUrl
}
}).success(function(data, status, headers, config) {
.....
});
Hi I am trying to display my data but when i tried to display it on my html using angular data wont pass through. checked the console and data was there. here is my html.
<ion-view view-title="">
<ion-content>
<!-- <div class="cards">
<div class="item item-image">
<img src="img/banner-children.jpg"></img>
</div>
</div> -->
<div class="list card padding" ng-repeat="charity in charityList">
<a href="#/app/charitypage/{{charity.charity_id}}" class="positive ">
<img class="char-logo img-thumb" ng-src="{{charity.logo}}">
<div class="char-info">
<h3 class="char-name text-pink">
<i class="ion-chevron-right text-pink btn-category"></i>
{{charity.charity_name}}</h3>
<p class="dark">{{charity.description}}</p>
</div>
</a>
</div>
</ion-content>
and here is my controller
angular.module('subCategory.controllers', [])
.controller('subCatCtrl', function($scope, $state, $http) {
$scope.getCategoryList = function(category){
$scope.charityList = {};
var categoryListData = {
charityCategory : category
}
$http({
method: 'POST',
header: {'Content-Type' : 'application/x-www-form-urlencoded'},
url: 'http://localhost/filantrome/Main/getCategoryList',
data: categoryListData
}).then(
function success( response ) {
$scope.charityList= response.data;
console.log($scope.charityList);
$state.go('app.subcat');
},
function error( response ) {
$scope.charityList = response.data;
console.log($scope.charityList);
// handle error
}
);
console.log($scope.charityList);
};
});
i can see the data that i was requesting on the console.log() inside the success function. but when i get out of the .then(); function $scope.charityList is empty.
what am i missing here? thanks!
Seems problem is in state change. To fix this you can use service to store received JSON
angular.module('subCategory.controllers', []).service(charityService, charityService);
/* #ngInject */
function charityService($http) {
var charityList = [];
var service = {
getData: getData,
getCharityList: getCharityList
};
return service;
function getCharityList() {
return charityList ;
}
function getData(categoryListData) {
$http({
method: 'POST',
header: {'Content-Type' : 'application/x-www-form-urlencoded'},
url: 'http://localhost/filantrome/Main/getCategoryList',
data: categoryListData
}).then(
function success( response ) {
charityList = response.data;
console.log(charityList );
$state.go('app.subcat'); // you can also change state here
},
function error( response ) {
$scope.charityList = response.data;
console.log($scope.charityList);
// handle error
}
);
}
}
then post data with controller function
$scope.getCategoryList = function(category){
var categoryListData = {
charityCategory : category
}
charityService.getData(categoryListData);
}
after state changes controller will get charityList from service
angular.module('subCategory.controllers', []) .controller('subCatCtrl',
function($scope, $state, $http, charityService) {
$scope.charityList = charityService.getCharityList();
// ...
As you have posted your console.log output, I can see that you have a single object receiving and assigning to charityList but you have an ng-repeat on the element. So, I think you should change $scope.charityList = {}; to $scope.charityList = []; or you can output this variable in the html to see whats going on like {{charityList}}.
I don't know why orderBy is not working with trackBy, it is making me crazy :(
Here is my HTML code:
<div class="blocks-container" ng-init="loadProjects()" ng-controller="buildMonitorController">
<div class="row">
<!-- <div> -->
<div class="col-xs-12 col-sm-6 col-md-3 col-lg-2 block animate"
ng-if="!errorDialogActive"
ng-repeat="build in builds.builds.build track by build._id | orderBy:'lastBuildDetails.startDate' : true"
ng-class="{'running': project.running ,'block-green': build._status ==='SUCCESS','block-red': build._status==='FAILURE'}"
id="{{build._id}}">
<div class="title-container"><p>{{build._buildTypeId}}</p></div>
<div class="update-container col-xs-12">
<time>{{ build.lastBuildDetails.startDate | date : 'dd.MM.yyyy H:mm:s'}}</time>
</div>
</div>
</div>
<!--</div>-->
<!-- Start error state dialog -->
<div ng-include src="'views/main/error-dialog.html'"></div>
Everytime give me the same result even if i change the order to reverse
And Here my AngularJS code:
$scope.refreshBuilds = function () {
$scope.errorList.length = 0
//#TODO remove this part right after the API is working
//Init
var suffix = '';
var randomnumber = Math.floor(Math.random() * 3);
//simulate mock by random number
switch (randomnumber) {
case 1:
suffix = '-success';
break;
case 2:
suffix = '-less';
break;
default:
break;
}
var url = 'mock/builds'+suffix+'.xml';
console.log('url: ' + url)
$http({
method: 'GET',
url: url,
headers: {
Authorization: 'Basic AAA'
}
}).success(function (data, status) {
//Recive builds from xml and reset scope
var buildsToFilter = new X2JS().xml_str2json(data);
$scope.errorDialogActive = false;
//filter builds which have a no build API detail status
if (buildsToFilter.builds.build !== undefined) {
angular.forEach(buildsToFilter.builds.build, function (build, index) {
$http({
method: 'GET',
url: 'mock/build-'+build._id+'.xml',
headers: {
Authorization: 'Basic AAA'
}
}).success(function (buildDetailData) {
$scope.errorDialogActive = false;
//push detail data into build array
buildsToFilter.builds.build[index].lastBuildDetails = new X2JS().xml_str2json(buildDetailData).build;
console.log(buildsToFilter.builds.build[index]);
}).error(function (data, status) {
$scope.errorDialogActive = true;
//remove build from index if no build detail was found
buildsToFilter.builds.build.splice(index, 1);
$scope.setError(status, '', '');
}).then(function () {
//after filtering builds to display, setup builds $scope for FrontEnd
$scope.builds = buildsToFilter;
});
});
} else {
}
}).error(function (data, status) {
//active dialog if error happens & set error
$scope.errorDialogActive = true;
$scope.setError(status, 'builds', '');
}).then(function () {
$timeout(function () {
$scope.finishedRequest = true;
}, 5000);
//refresh right after proceed and a little delay
$timeout(function () {
console.log('Starting refresh');
$scope.refreshBuilds();
}, 21000);
})
};
If You need more code please let me know
Try reading the docs. It doesn't appear you are using it correctly.
https://docs.angularjs.org/api/ng/filter/orderBy
The last argument can only be "reverse".
Move 'track by build._id' to the end of the expression (after orderBy).
ng-repeat="build in builds.builds.build track by build._id | orderBy:'lastBuildDetails.startDate'