I am not sure how to approach this for my AngularJS site...
I need to load a login form to any page on the site (without leaving the page) then asynchronously pass user login info and get success or fail back and then load a div html template to the same page if login is successful...
Seeing a full example would be amazing but I do not expect that of course. However I can't find a single simple example of this on the web (Nothing that would help a newbie).
Can someone point at good solutions or at least understandable examples? .. or something similar that is not cluttered with unrelated code..
I use a directive for my login, kind of like this. You can get a general idea of how it works.
.directive('login', function($http, $rootScope) {
return {
restrict: 'E',
template: " <form> " +
"<label>Username</label>" +
"<input type='text' ng-model='username'>" +
"<label>Password</label>" +
"<input type='password' ng-model='password'>" +
"<br>" +
"<input type='submit'>" +
"</form>",
link: function(scope, elem, attrs) {
elem.bind('submit', function() {
var user_data = {
"username": scope.username,
"password": scope.password,
};
$http.post("http://localhost:8001/api-token-auth/", user_data)
.success(function(response) {
$http.defaults.headers.common['Authorization'] = 'Token ' + response.token;
$rootScope.$broadcast('event:login-confirmed');
elem.slideUp();
});
scope.$on('event:auth-loginRequired', function () {
var main = document.getElementById("main");
main.hide();
elem.slideDown();
});
});
}
}
});
I found a solution that does exactly what I asked about and can be adjusted to my needs fairly easily.
https://gist.github.com/clouddueling/6191173
It is created by Michael Calkins who I find to be one of the most clear and easy to understand sources for AngularJs tutoring. I found his videos on youtube http://www.youtube.com/watch?v=ZHMVP5aLAPM
Related
I am writing a small code with JavaScript (using jQuery and Knockout) and HTML that takes user input (GitHub username), checks if the input is valid against a GitHub api, and displays the user's GitHub avatar and username (linked to the matching profile on GitHub). The display replaces the form in which the user entered the username.The original HTML before the user inputs is:
<div id="inputSection">
<form>
<p>
GitHub Username:
<input type="text" name="username" value="" placeholder="username" id="un"/>
<button type="button" id="submitButton">Login</button>
</p>
</form>
</div>
And the code to replace it is this:
$("#submitButton").click(function() {
var username = document.getElementById('un').value;
var inputForm = $(document.getElementById('inputSection'));
$.ajax( {
...
success: function () {
alert("Welcome, " + username);
var userURL = 'https://github.com/' + username;
var inputContent = $('<a data-bind="attr: {href: userURL}"><img data-bind="attr: {src: avatar_url}" height=\"30\" width=\"30\"/>' + username + '</a>');
$(inputForm.replaceWith(inputContent));
}
});
});
It seems to work for the most part. After the alert welcomes the user by username, the form disappears from the webpage. It is replaced by the username, which is formatted like a link. However, it does not function as one. Clicking it does not do anything. Also, the user's avatar, despite showing a box of the set size on the webpage, does not appear.The solution is likely very simple and obvious, but as I only started learning these languages and libraries this week, I am not sure what is going wrong. Knockout should be running on the HTML page that calls the JavaScript page, and the ajax is working with regards to other functions, so I assume that's fine. The value "avatar_url" is a part of the api requested with ajax at https://api.github.com/users.I've tried all sorts of different things to no effect. If you want any more information or have a suggestion to make this question better, please comment. I'm new to coding and Stack Overflow, but I want to make both my programs and my questions as good as possible. Thank you for your time.
EDIT: 1. I originally failed to set a size for the image, resulting in a 0x0 image. This has been corrected, though the image itself still does not display. 2. When I first put in my code, I tried to make it easier to read by excluding where some variables had been renamed for other, unrelated portions and just making all the names match between the two relevant snippets. I did not catch them all. They should all match now.
Short answer:
You’re inserting an html element with data-binds without explicitly initializing its bindings. Use ko.applyBindings(vm, node) on the newly injected part of the DOM.
Long answer:
If you're new to coding and to both jQuery and knockout, I'd suggest not using both libraries at once. Here’s why:
If you want to use knockout, you'll have to stick to a certain kind of software architecture:
Simplify dynamic JavaScript UIs with the Model-View-View Model (MVVM) (http://knockoutjs.com/)
jQuery, on the other hand, is more of a toolbox. It doesn't dictate an architectural pattern.
It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. (https://jquery.com/)
This might sound a bit lame, and isn't really an answer, but I'll show you the differences between solving your problem the “knockout way”, and “the jQuery way”. I’ll start with the latter, since it’s closest to your current approach:
The jQuery approach (note that I'm skipping the Ajax part)
Find the elements you need to make your UI interactive. Attach event listeners to buttons, modify the DOM when new data is available.
$(document).ready(function() {
// Notice that you don't need document.getElementById
var submitButton = $("#loginButton");
var userNameInput = $("#un");
var inputSection = $("#inputSection");
var getContentString = function(userName) {
var userUrl = "https://github.com/" + userName;
var avatarUrl = "...";
// Inject the user specific attributes
return "<a href=`" + userUrl + "`><img src=`" + avatarUrl + "` height='30' width='30'/>" + userName + "</a>";
};
var onSubmitClick = function(event) {
var userName = userNameInput.val();
var onSuccess = function() {
// Create new <a> element and replace the form with the new HTML
var inputContent = $(getContentString(userName));
inputSection.replaceWith(inputContent);
};
/*
$.ajax({
success: onSuccess
});
*/
//Just call onSuccess to circumvent unimplemented ajax:
onSuccess();
};
submitButton.click(onSubmitClick);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="inputSection">
<p>
GitHub Username:
<input type="text" name="username" value="" placeholder="username" id="un" />
<button type="button" id="loginButton">Login</button>
</p>
</form>
The knockout approach
Create a viewmodel for your user. Bind the input and compute the other properties automatically. Attach event listeners through data-binds. Use if, visible or template bindings to swap out parts of the UI.
var UserViewModel = function() {
this.userName = ko.observable("");
this.confirmed = ko.observable(false);
this.userUrl = ko.computed(function() {
return "https://github.com/" + this.userName();
}, this);
this.avatarUrl = ko.computed(function() {
return "???" + this.userName();
}, this);
};
UserViewModel.prototype.confirm = function() {
/* ajax (disabled for example)
$.ajax({
success: this.confirmed.bind(null, true)
});
*/
this.confirmed(true);
};
var viewModel = {
user: new UserViewModel()
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with: user">
<!-- ko ifnot: confirmed -->
<form>
<p>
GitHub Username:
<input data-bind="value: userName" type="text" placeholder="username" />
<button data-bind="click: confirm">Login</button>
</p>
</form>
<!-- /ko -->
<!-- ko if: confirmed -->
<a data-bind="attr: { href: userUrl }">
<img data-bind="attr: {src: avatarUrl }" />
<span data-bind="text: userName"></span>
</a>
<!-- /ko -->
</div>
With help from user3297291's jQuery answer above, I eventually came to this. The answer was good and necessary for this progression; some parts just did not work for this situation (mostly simple compatibility issues with other code not included in this sample). Though this is a very specific question, I thought the solution should be included. Note that I have decided to stay away from Knockout for now.
The HTML suggestion to attach the id to the form rather than the div is a good move.
$("#submitButton").click(function inputForm() {
var username = $("#un").val();
function makeUserContent(user, profile, avatar) { //arguments are data from ajax
//writes a string without the messy quotes within quotes within quotes problem
//much clearer than trying to handle the jQuery and string all at once
return "<img src=" + avatar + " height='30' width='30' />" + user + "";
}
function submitUsername() {
$.ajax({
...
success: function correntInformation(data) {
//data is what the ajax gets, which is passed for use
alert("Welcome, " + username + ".");
//calls to make the string with the data gotten by ajax
var inputContent = $(makeUserContent(data.login, data.html_url, data.avatar_url));
$("#inputSection").replaceWith(inputContent);
}
})
}
submitUsername();
})
The biggest things I took away from this problem is this: simplify strings, hold and use data, work with one library at a time (until experienced with both).
In my app I have a really simple controller whose job is to display messages:
angular.module("app.components.messages", ['ngSanitize'])
.controller("MessageBoxController", ['$scope', function($scope)
{
$scope.messageType = 'info';
$scope.hidden = true;
$scope.$on('message.error', function(event, message)
{
$scope.message = message;
$scope.messageType = 'error';
$scope.hidden = false;
});
$scope.$on('message.warning', function(event, message)
{
$scope.message = message;
$scope.messageType = 'warning';
$scope.hidden = false;
});
}]);
Then the message box itself is pretty straight forward:
<div id="message-box" ng-controller="MessageBoxController"></div>
I'm using ngSanitize because sometimes I want to put links in the messages. For example in another controller I may do:
$rootScope.$broadcast('message.warning', 'You are about to download ' + file.name + ' which is ' + file.size + '. Your current quota is ' + user.quotaRemaining + ' click here to confirm.');
And this works, and displays the link and everything. However in this particular case, after the user has clicked the link, I would like it to then fire another function to update the user's quota information. I naively tried putting an ng-click on my tag, but it turns out that ngSanitize filters out certain attributes, so that didn't work.
How can I get stuff to happen after the link in the message is clicked? I don't necessarily have to use ngSanitize, I just used it because it seemed convenient.
Here solved similar problem. Just rename directive and pass to the directive you message.
For example:
<div message="message"></div>
For a project I'm working on, I created a simplified version of the UI Bootstrap Calendar widget.
Plunker of my Simplified Calendar, and how I'm using it, is here.
One interesting aspect of the UI Bootstrap calendar, is that even though it goes onto a input[text], it still produces a date validation in the $error dictionary for a form controller, just as if I had specified an input[date] element in my DOM.
However, there's a catch with numerous sub-catches. One thing you'll notice right away in my plunker's DOM is that I've specified error spans for times when the given date fields are not actually dates (try entering something ridiculous like 'cat' for a value!) If you enter something that isn't a date, those should appear, but they don't.
I've tried a few things to expose the markup being created to the name field of the parent:
$transclude set to false, such that the <calendar></calendar> tags get replaced with the contents of the calendar directive's template, with a name attribute specified. This "works", except that said input is wrapped in a span that has a class necessary to look correct using the Bootstrap styling framework.
Directly creating a name attribute in the calendar directive's input field with a binding, like so*:
app.directive('mustPrecedeDate', [
function () {
return {
restrict: 'E',
template: '<input type="text" name="{{ someName }}" />',
scope: {},
controller: 'calendarCtrl',
link: function () {}
};
}
};
Writing link code to explicitly find the input that is a child of the calendar generated markup, and assign it a name attribute. Both 2 and 3 failed, because apparently that's not really something that can be done (I can't find the SO question that was the source of that discovery.)
This leads to my Question: in what way can I get a name down to the input element, such that validation results can be reported to the $error dictionary, so that I can give my users helpful validation messages?
*: Apparently, code blocks with the 'four spaces from the left' formatting don't behave well with numbered lists, so I had to use back-quote code notation to get the text to format halfway correctly. Please feel free to correct my formatting, if I haven't found a bug in the markdown setup SO uses.
The #3 thing needed to be tried a bit harder!
I was able to get a name on the input by adding the following code into my link function:
var inputElement = elem.find('input');
inputElement.attr('name', inputName);
...Where inputName is scraped from the attributes list. I was able to get the inputName down to the generated input[text] field by using a compile function as below.
app.directive('calendar', [
function() {
return {
restrict: 'E',
transclude: false,
scope: {},
template:
'<span class="input-group">'
+ '<input class="form-control" required '
+ 'type="text" placeholder="MM/dd/yyyy" '
+ 'data-ng-model="dt" data-ng-click="toggle($event)" '
+ 'data-ng-change="updateParentProperty()" '
+ 'datepicker-popup="MM/dd/yyyy" is-open="isOpen" />'
+ '<span class="input-group-btn">'
+ '<button type="button" class="btn btn-default" data-ng-click="toggle($event)">'
+ '<i class="fa fa-calendar"></i>'
+ '</button>'
+ '</span>'
+ '</span>',
controller: 'calendarCtrl',
compile: function(elem, attrs) {
var inputName = attrs.inputName;
var inputElement = elem.find('input');
inputElement.attr('name', inputName);
// Compile returns a Link function!
return function(scope, elem, attrs, ctrl) {
var modelName = attrs.ngModel;
scope.parentProperty = modelName;
scope.dt = scope.$parent[modelName];
};
}
};
}
]);
Edit:
Plunker is working, actual code isn't:
http://plnkr.co/edit/5oVWGCVeuTwTARhZDVMl?p=preview
The service is contains typical getter\setter stuff, beside that, it functions fine, so I didn't post it's code to avoid TLDR.
TLDR version: trying to ng-init a value fetched with AJAX into the ngModel of the text-area, the request resolves with the correct value, but the textarea remain empty.
parent controller function(talks to the service):
$scope.model.getRandomStatus = function(){
var deffered = $q.defer();
var cid = authService.getCompanyId();
var suggestions = companyService.getStatusSuggestions(cid);
if(suggestions && suggestions.length > 0){
deffered.resolve(suggestions[Math.floor(Math.random(suggestions.length) + 1)].message);
return deffered.promise;//we already have a status text, great!
}
//no status, we'll have to load the status choices from the API
companyService.loadStatusSuggestions(cid).then(function(data){
companyService.setStatusSuggestions(cid, data.data);
var result = data.data[Math.floor(Math.random(data.data.length) + 1)];
deffered.resolve(result.message);
},
function(data){
_root.inProgress = false;
deffered.resolve('');
//failed to fetch suggestions, will try again the next time the compnay data is reuqired
});
return deffered.promise;
}
child controller:
.controller('shareCtrl', function($scope){
$scope.layout.toggleStatusSuggestion = function(){
$scope.model.getRandomStatus().then(function(data){
console.log(data);//logs out the correct text
//$scope.model.formData.shareStatus = data;//also tried this, no luck
return data.message;
});
$scope.model.formData.shareStatus = $scope.layout.toggleStatusSuggestion();//Newly edited
}
});
HTML:
<div class="shareContainer" data-ng-controller="shareCtrl">
<textarea class="textAreaExtend" name="shareStatus" data-ng-model="model.formData.shareStatus" data-ng-init="model.formData.shareStatus = layout.toggleStatusSuggestion()" cols="4"></textarea>
</div>
I believe what you are wanting is :
$scope.model.getRandomStatus().then(function(data){
$scope.model.formData.shareStatus = data.message;
});
Returning something from within then does not return anything from the function wrapping it and therefore does nothing
Turns out that I had a custom validation directive that was watching the changes in the model via $formatters, and limting it to 80 chars(twitter), it was failing silently as I didn't expect to progmatically insert invalid values into my forms, very stupid, but could happen to anyone.
Had to make some changes to it, so it's worth to remember in case it happens to anyone else.
I'd like to add a section to my website that shows the social media profiles I manage.
Instead of updating some HTML each time the profile picture or username changes, I'd like it to stay in sync, so to speak.
Is there a simple way to link to the profiles information, without download individual widgets and somehow modifying them?
Thanks
You'd have to write the code, but have you checked the various network's Javascript APIs? They all seem quite simple.
Twitter: https://dev.twitter.com/docs/anywhere/welcome
Facebook: https://developers.facebook.com/docs/reference/javascript/
LinkedIn: http://developer.linkedin.com/javascript
Tumblr: http://www.tumblr.com/docs/en/api/v2
Google+: http://code.google.com/apis/libraries/
This particular example from the twitter api seems to be what you would need:
<span id="twitter-connect-placeholder"></span>
<script type="text/javascript">
twttr.anywhere(function (T) {
var currentUser,
screenName,
profileImage,
profileImageTag;
if (T.isConnected()) {
currentUser = T.currentUser;
screenName = currentUser.data('screen_name');
profileImage = currentUser.data('profile_image_url');
profileImageTag = "<img src='" + profileImage + "'/>";
$('#twitter-connect-placeholder').append("Logged in as " + profileImageTag + " " + screenName);
} else {
T("#twitter-connect-placeholder").connectButton();
};
});
</script>
Based on your comments you may just want to direct link this way:
https://api.twitter.com/1/users/profile_image?screen_name=twitterapi&size=bigger