Directive doesn't load correctly after ngroute
I have an isolated scope issue in my custom directive.
I created a custom directive. It worked when I didn't set the scope attribute on the directive using the parent scope.
What is it? This created its own JavaScript control. Each time the site was loaded, the control was initialized and the data for the control was inserted into the parent area.
Why do I need an isolated area? Because I wanted to put the directive on the page twice. And for data that I would like to put in different variables.
What's the problem? Every time I load the page everything works fine. Then go to another site using ng-route. There I use $ Location.Path to reset the route to the first page. My control is initializing, but it doesn't seem to display it. For example, crash doesn't work.
Here is the code: directive:
<people-picker instancename="peoplePicker" labeltext="Mitarbeiter" singleusermode="true" useonlyallowedusers="true"
allowedusers="AllowedUsers" selectedusers="selectedUsers" instance="instance"
callbackwhenuserchanged="dataChanged(currentSelectedUsers)">
</people-picker>
directive:
.directive('peoplePicker', function () {
return {
restrict: 'E',
templateUrl: '../DirectiveTemplates/PeoplePicker.html',
scope: {
instancename: '@',
labeltext: '@',
singleusermode: '@',
selectedusers: '=',
useonlyallowedusers: '@',
allowedusers: '=',
callbackwhenuserchanged: '&',
instance: '='
},
controller: function ($scope, utilities, spContextProvider) {
$scope.singleusermode = $scope.singleusermode !== undefined && $scope.singleusermode.toUpperCase() === 'TRUE';
$scope.useonlyallowedusers = $scope.useonlyallowedusers !== undefined && $scope.useonlyallowedusers.toUpperCase() === 'TRUE';
$scope.peoplePicker = {};
$scope.idSpanAdministrators = 'spanAdministrators' + $scope.instancename;
$scope.idInputAdministrators = 'inputAdministrators' + $scope.instancename;
$scope.idDivAdministratorsSearch = 'divAdministratorsSearch' + $scope.instancename;
$scope.idHdnAdministrators = 'hdnAdministrators' + $scope.instancename;
...
$scope.initializePeoplePicker = function () {
var context = spContextProvider.GetSharePointContext();
$scope.peoplePicker = new CAMControl.PeoplePicker(
$scope.instancename,
context,
$('#' + $scope.idSpanAdministrators),
$('#' + $scope.idInputAdministrators),
$('#' + $scope.idDivAdministratorsSearch),
$('#' + $scope.idHdnAdministrators));
...
$scope.peoplePicker.Initialize();
$scope.addSelectedUsers($scope.selectedusers);
}
$scope.addSelectedUsers = function (selectedUsers) {
if (selectedUsers !== null) {
angular.forEach(selectedUsers, function (item) {
$scope.peoplePicker.RecipientSelected(item.Login, item.Name, item.Email);
});
}
}
$scope.$watchCollection($scope.selectedusers, function () {
$scope.addSelectedUsers($scope.selectedusers);
}, true);
spContextProvider.CallSharePointWithFunction($scope.initializePeoplePicker);
}
This peoplepicker uses a control that works with 4 html elements. The will must be initialized.
For complete code, I'll post the control (removed some lines due to length limitation).
var CAMControl;
(function (CAMControl) {
var PeoplePicker = (function () {
// Constructor
function PeoplePicker(InstanceName, SharePointContext, PeoplePickerControl, PeoplePickerEdit, PeoplePickerDisplay, PeoplePickerData) {
//public properties
this.SharePointContext = SharePointContext;
this.PeoplePickerControl = PeoplePickerControl;
this.PeoplePickerEdit = PeoplePickerEdit;
this.PeoplePickerDisplay = PeoplePickerDisplay;
this.PeoplePickerData = PeoplePickerData;
this.InstanceName = InstanceName;
// optionally show more/less entries in the people picker dropdown, 5 is the default
...
window.document[this.InstanceName] = this;
}
// Property wrapped in function to allow access from event handler
PeoplePicker.prototype.GetPrincipalType = function () {
return this.PrincipalType;
}
...
// HTML encoder
PeoplePicker.prototype.HtmlEncode = function (html) {
return document.createElement('a').appendChild(document.createTextNode(html)).parentNode.innerHTML;
}
// HTML decoder
PeoplePicker.prototype.HtmlDecode = function (html) {
var a = document.createElement('a');
a.innerHTML = html;
return a.textContent;
}
...
PeoplePicker.prototype.LoadScript = function (url, callback) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = url;
// Attach handlers for all browsers
var done = false;
script.onload = script.onreadystatechange = function () {
if (!done && (!this.readyState
|| this.readyState == "loaded"
|| this.readyState == "complete")) {
done = true;
// Continue your code
callback();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
head.removeChild(script);
}
};
head.appendChild(script);
}
...
// Generates the html for a resolved user
PeoplePicker.prototype.ConstructResolvedUserSpan = function (login, name) {
var login = login.replace(/\\/g, '\\\\');
resultDisplay = 'Remove person or group {0}';
if (typeof deleteUser != 'undefined') {
resultDisplay = deleteUser;
}
resultDisplay = this.Format(resultDisplay, name);
var userDisplaySpanTemplate = '<span class="peoplepicker-userSpan"><span class="entity-resolved">{0}</span><a title="{3}" class="peoplepicker-delImage" onclick="{1}.DeleteProcessedUser({2}); return false;" href="#">x</a></span>';
return this.Format(userDisplaySpanTemplate, name, this.InstanceName, "'" + login + "'", resultDisplay);
}
// Create a html representation of the resolved user array
PeoplePicker.prototype.ResolvedUsersToHtml = function () {
var userHtml = '';
for (var i = 0; i < this._ResolvedUsers.length; i++) {
userHtml += this.ConstructResolvedUserSpan(this._ResolvedUsers[i].Login, this._ResolvedUsers[i].Name);
}
return userHtml;
}
// Returns a resolved user object
PeoplePicker.prototype.ResolvedUser = function (login, name, email) {
var user = new Object();
user.Login = login;
user.Name = name;
user.Email = email;
return user;
}
// Add resolved user to array and updates the hidden field control with a JSON string
PeoplePicker.prototype.PushResolvedUser = function (resolvedUser) {
if (this.AllowDuplicates) {
this._ResolvedUsers.push(resolvedUser);
} else {
var duplicate = false;
for (var i = 0; i < this._ResolvedUsers.length; i++) {
if (this._ResolvedUsers[i].Login == resolvedUser.Login) {
duplicate = true;
}
}
if (!duplicate) {
this._ResolvedUsers.push(resolvedUser);
}
}
this.PeoplePickerData.val(JSON.stringify(this._ResolvedUsers));
}
// Function called then the clientPeoplePickerSearchUser succeeded
PeoplePicker.prototype.QuerySuccess = function (queryNumber, searchResult) {
var results = this.SharePointContext.parseObjectFromJsonString(searchResult.get_value());
var txtResults = '';
var baseDisplayTemplate = '<div class=\'ms-bgHoverable\' style=\'width: 400px; padding: 4px;\' onclick=\'javascript:{0}.RecipientSelected(\"{1}\", \"{2}\", \"{3}\")\'>{4}';
var displayTemplate = '';
if (this.ShowLoginName && this.ShowTitle) {
displayTemplate = baseDisplayTemplate + ' ({5})<br/>{6}</div>';
} else if (this.ShowLoginName) {
displayTemplate = baseDisplayTemplate + ' ({5})</div>';
} else if (this.ShowTitle) {
displayTemplate = baseDisplayTemplate + ' ({6})</div>';
} else {
displayTemplate = baseDisplayTemplate + '</div>';
}
if (results) {
...
if (results.length > 0) {
// if this function is not the callback from the last issued query then just ignore it. This is needed to ensure a matching between
// what the user entered and what is shown in the query feedback window
if (queryNumber < this._lastQueryID) {
return;
}
displayCount = results.length;
if (displayCount > this.MaxEntriesShown) {
displayCount = this.MaxEntriesShown;
}
for (var i = 0; i < displayCount; i++) {
var item = results[i];
var oldLoginName = item['Key'];
var loginName = oldLoginName.replace(/\\/g, '\\\\');
var displayLoginName = oldLoginName.split('|')[1].replace(/\\/g, '\\');
var displayName = item['DisplayText'];
var title = item['EntityData']['Title'];
var email = item['EntityData']['Email'];
txtResults += this.Format(displayTemplate, this.InstanceName, loginName, this.HtmlEncode(displayName), email, displayName, displayLoginName, title);
}
var resultDisplay = '';
txtResults += '<div class=\'ms-emphasisBorder\' style=\'width: 400px; padding: 4px; border-left: none; border-bottom: none; border-right: none; cursor: default;\'>';
if (results.length == 1) {
resultDisplay = 'Showing {0} result';
if (typeof resultsSingle != 'undefined') {
resultDisplay = resultsSingle;
}
txtResults += this.Format(resultDisplay, results.length) + '</div>';
} else if (displayCount != results.length) {
resultDisplay = "Showing {0} of {1} results. <B>Please refine further<B/>";
if (typeof resultsTooMany != 'undefined') {
resultDisplay = resultsTooMany;
}
txtResults += this.Format(resultDisplay, displayCount, results.length) + '</div>';
} else {
resultDisplay = "Showing {0} results";
if (typeof resultsMany != 'undefined') {
resultDisplay = resultsMany;
}
txtResults += this.Format(resultDisplay, results.length) + '</div>';
}
this.PeoplePickerDisplay.html(txtResults);
//display the suggestion box
this.ShowSelectionBox();
}
else {
var searchbusy = '<div class=\'ms-emphasisBorder\' style=\'width: 400px; padding: 4px; border-left: none; border-bottom: none; border-right: none; cursor: default;\'>No results found</div>';
this.PeoplePickerDisplay.html(searchbusy);
//display the suggestion box
this.ShowSelectionBox();
}
}
else {
//hide the suggestion box since results are null
this.HideSelectionBox();
}
}
// Initialize
PeoplePicker.prototype.Initialize = function () {
var scriptUrl = "";
var scriptRevision = "";
$('script').each(function (i, el) {
if (el.src.toLowerCase().indexOf('peoplepickercontrol.js') > -1) {
scriptUrl = el.src;
scriptRevision = scriptUrl.substring(scriptUrl.indexOf('.js') + 3);
scriptUrl = scriptUrl.substring(0, scriptUrl.indexOf('.js'));
}
})
// Load translation files
var resourcesFile = scriptUrl + "_resources." + this.Language.substring(0, 2).toLowerCase() + ".js";
if (scriptRevision.length > 0) {
resourcesFile += scriptRevision;
}
this.LoadScript(resourcesFile, function () {
});
// is there data in the hidden control...if so show it
if (this.PeoplePickerData.val() !== undefined && this.PeoplePickerData.val().length > 0) {
// Deserialize JSON string into list of resolved users
this._ResolvedUsers = JSON.parse(this.PeoplePickerData.val());
// update the display of resolved users
this.PeoplePickerControl.html(this.ResolvedUsersToHtml());
}
var parent = this;
this.PeoplePickerEdit.keydown(function (event) {
var keynum = event.which;
//backspace
if (keynum == 8) {
//hide the suggestion box when backspace has been pressed
parent.HideSelectionBox();
// do we have text entered
var unvalidatedText = parent.PeoplePickerEdit.val();
if (unvalidatedText.length > 0) {
// delete the last entered character...meaning do nothing as this delete will happen as part of the keypress
}
else {
// are there resolved users, if not there nothing to delete
if (parent._ResolvedUsers.length > 0) {
// remove the last added user
parent.PopResolvedUser();
// update the display
parent.PeoplePickerControl.html(parent.ResolvedUsersToHtml());
// focus back to input control
parent.PeoplePickerEdit.focus();
// Eat the backspace key
return false;
}
}
}
// An ascii character or a space has been pressed
else if (keynum >= 48 && keynum <= 90 || keynum == 32) {
// get the text entered before the keypress processing (so the last entered key is missing here)
var txt = parent.PeoplePickerEdit.val();
// keynum is not taking in account shift key and always results inthe uppercase value
if (event.shiftKey == false && keynum >= 65 && keynum <= 90) {
keynum += 32;
}
// Append the last entered character: since we're handling a keydown event this character has not yet been added hence the returned value misses the last character
txt += String.fromCharCode(keynum);
// we should have at least 1 character
if (txt.length > 0) {
var searchText = txt;
//ensure that MinimalCharactersBeforeSearching >= 1
if (parent.GetMinimalCharactersBeforeSearching() < 1) {
parent.SetMinimalCharactersBeforeSearching(1);
}
// only perform a query when we at least have two chars and we do not have a query running already
if (searchText.length >= parent.GetMinimalCharactersBeforeSearching()) {
resultDisplay = 'Searching...';
if (typeof resultsSearching != 'undefined') {
resultDisplay = resultsSearching;
}
var searchbusy = parent.Format('<div class=\'ms-emphasisBorder\' style=\'width: 400px; padding: 4px; border-left: none; border-bottom: none; border-right: none; cursor: default;\'>{0}</div>', resultDisplay);
parent.PeoplePickerDisplay.html(searchbusy);
//display the suggestion box
parent.ShowSelectionBox();
var query = new SP.UI.ApplicationPages.ClientPeoplePickerQueryParameters();
query.set_allowMultipleEntities(false);
query.set_maximumEntitySuggestions(2000);
query.set_principalType(parent.GetPrincipalType());
query.set_principalSource(15);
query.set_queryString(searchText);
var searchResult = SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser(parent.SharePointContext, query);
// update the global queryID variable so that we can correlate incoming delegate calls later on
parent._queryID = parent._queryID + 1;
var queryIDToPass = parent._queryID;
parent._lastQueryID = queryIDToPass;
// make the SharePoint request
parent.SharePointContext.executeQueryAsync(Function.createDelegate(this, function () { parent.QuerySuccess(queryIDToPass, searchResult); }),
Function.createDelegate(this, function (a, arguments) { parent.QueryFailure(queryIDToPass, arguments); }));
}
}
}
//tab or escape
else if (keynum == 9 || keynum == 27) {
//hide the suggestion box
parent.HideSelectionBox();
}
});
}
return PeoplePicker;
})();
CAMControl.PeoplePicker = PeoplePicker;
})(CAMControl || (CAMControl = {}));
Routeprovider:
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/summary', {
templateUrl: 'PresenceSummary.html'
})
.when('/details/:workingDay', {
templateUrl: 'PresenceDetails.html'
})
.otherwise({ redirectTo: '/summary' });
}]);
On the details page, I'll go back to the pivot page with ng-click. In the gateway called, I use:
$location.path("/");
If it matters, the button is in a different directive.
The parentcontroller element of the people-picker directive looks like this:
angular.module('presenceSummary', [])
.controller('presenceSummary', function ($scope, $location, hrDbService, hrUserService, hrUiControlService) {
...
});
source to share
I found the answer. The directive is displayed before the dom is ready. My controls are not available.
I have set a timeout. This article gave me this hint: http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering
Thank you for your help!
source to share