Google Authentication Email Scope using Firebase - firebase

I am using the Firebase Polymer Component to get users to login using Google Oauth. I cannot though seem to find a way to get their email addresses. I cannot seem to be able to add email to scope.
There seems to be an issue in Github regarding the element with this.
https://github.com/GoogleWebComponents/firebase-element/issues/39
I used firebase-auth polymer element and added the email scope. I have also created an element called hi9-login with this in.
https://github.com/HackITtoday/hi9-login
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../firebase-element/firebase-auth.html">
<link rel="import" href="../firebase-element/firebase-document.html">
<link rel="import" href="../paper-button/paper-button.html">
<dom-module id="hi9-login">
<template>
<firebase-auth id="firebaseLogin" user="{{user}}" status-known="{{statusKnown}}" location="https://hi9site.firebaseio.com" provider="google" on-error="errorHandler" on-user-created="userSuccessHandler" on-password-changed="userSuccessHandler" on-password-reset="userSuccessHandler" on-user-removed="userSuccessHandler"></firebase-auth>
<!-- <firebase-document location="https://hi9site.firebaseio.com/data" id="firebaseDocument"></firebase-document> -->
<firebase-document location="{{userDataUrl}}" data="{{userData}}" id="firebaseUser"></firebase-document>
<firebase-document location="{{ownerDataUrl}}" data="{{ownerData}}" id="firebaseOwner"></firebase-document>
<paper-button on-tap="logout" hidden$="{{computeLogoutHidden(statusKnown, user)}}"> Logout </paper-button>
<paper-button on-tap="login" hidden$="{{computeLoginHidden(statusKnown, user)}}"> Login </paper-button>
</template>
</dom-module>
<script>
var that = {};
Polymer({
is: 'hi9-login',
properties: {
params: {
scope: "email"
},
provider: {
type: String, value: 'anonymous'
},
message: {
type: String, value: ''
},
email: {
type: String, value: ''
},
password: {
type: String, value: ''
},
user: {
type: Object, value: null,
notify: true
},
uid: {
computed:'returnVal(user.uid)'
},
owner: {
computed:'returnVal(user.owner)'
},
userDataUrl:{
computed:'getUserDataUrl(uid)'
},
ownerDataUrl:{
computed:'getOwnerDataUrl(owner, uid)'
},
statusKnown: {
type: Boolean
},
show_model: {
type: Boolean, notify: true,
computed: "computeLogoutHidden(statusKnown, user)"
},
role: {
computed: 'getRole(user,userData)',
notify: true
},
admin: {
computed: 'isAdmin(role)',
notify: true
}
},
returnVal: function(val) {
if (val !== undefined && val !== null) {
return val;
} else {
return undefined;
}
},
getOwnerDataUrl: function(owner,uid) {
return "https://hi9site.firebaseio.com/owner/" + owner + "/users/" + uid;
},
getUserDataUrl: function(uid) {
that = this;
setTimeout(function(){ that.whenReady(); }, 7000);
return "https://hi9site.firebaseio.com/users/" + uid;
},
login: function() {
var params;
try {
params = JSON.parse(this.params);
} catch (e) {
params = null;
}
if (this.provider == 'password') {
params = params || {};
params.email = this.email;
params.password = this.password;
}
this.$.firebaseLogin.login(params);
this.log("Login");
},
logout: function() {
this.log("Logout");
this.$.firebaseLogin.logout();
},
errorHandler: function(e) {
this.log("Login Status");
this.message = 'Error: ' + e.detail.message;
},
userSuccessHandler: function(e) {
this.log("Login Status");
this.message = e.type + ' success!';
},
createUserHandler: function(e) {
this.log("createUserHandler");
this.$.firebaseLogin.createUser(this.email, this.password);
},
changePasswordHandler: function(e) {
this.log("changePasswordHandler");
this.$.firebaseLogin.changePassword(this.email, this.password, this.newPassword);
},
resetPasswordHandler: function(e) {
this.log("resetPasswordHandler");
this.$.firebaseLogin.sendPasswordResetEmail(this.email);
},
computePasswordHidden: function(provider) {
this.log("Login Status");
return provider !== 'password';
},
computeCreateUserDisabled: function(email, password) {
this.log("computeCreateUserDisabled");
return !email || !password;
},
computeChangePasswordDisabled: function(email, password, newPassword) {
this.log("computeChangePasswordDisabled");
return !email || !password || !newPassword;
},
computeResetPasswordDisabled: function(email, password) {
this.log("computeResetPasswordDisabled");
return !email || !password;
},
computeRemoveUserDisabled: function(email, password) {
this.log("computeRemoveUserDisabled");
return !email || !password;
},
computeLoginHidden: function(statusKnown, user) {
this.log("computeLoginHidden");
return !statusKnown || !!user;
},
computeLogoutHidden: function(statusKnown, user) {
this.log("computeLogoutHidden");
return !statusKnown || !user;
},
computeLoginStatus: function(statusKnown, user) {
var d = new Date();
var n = d.getTime();
this.log("Login Status");
if (statusKnown && user) {
return 'Logged in';
}
if (statusKnown) {
return 'Logged out';
}
return 'Unknown (checking status...)';
},
log: function(log) {
// var d = new Date();
// var n = d.getTime();
// this.$.firebaseDocument.query.ref().push({log: log,time:n,user:this.user});
},
getRole: function(user, userData) {
if (userData !== undefined && userData !== null && user !== null) {
if (this.userSet === undefined && userData.owner === undefined) { // Stops Looping.
this.userSet = true;
if (userData.num === undefined ) {
userData.num = prompt("Hi "+user.google.displayName+"\nPlease tell me your mobile phone number?", "07");
}
if (userData.owner === undefined) {
userData.owner = prompt("What is your housing association?", "Yarlington");
}
if (userData.email === undefined) {
userData.email = prompt("and lastly you email address?", "");
}
if (userData.log === undefined) {
var d = new Date();
var n = d.getTime();
userData.log = [{first: n}]
}
userData.user = user;
this.userData = clone(userData);
this.ownerData = clone(userData);
}
// user
if (!userData.hasOwnProperty("role")) {
userData.role = "User"
}
return userData.role;
} else {
return 'no data'
}
},
isAdmin: function(role) {
return role = 'admin';
},
whenReady: function() {
var userDataTest = clone(this.$.firebaseUser.data);
delete(this.$.firebaseUser.data);
if (userDataTest === undefined || userDataTest === null){
userDataTest = {placeholder:true}
};
this.$.firebaseUser.data = userDataTest;
var ownerDataTest = clone(this.$.firebaseOwner.data);
delete(this.$.firebaseOwner.data);
if (ownerDataTest === undefined || ownerDataTest === null){
ownerDataTest = {placeholder:true}
};
if (this.ownerDataUrl) {
this.$.firebaseOwner.data = ownerDataTest;
}
}
});
function clone(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
</script>
Here also is a screenshot in JSFiddle showing it's not working

You can add a scope to the authWithOAuthRedirect API.
For example,
ref.authWithOAuthRedirect('google', errorHandler, {
scope: "email"
});
Then, in your onAuth handler you can get the "authData.google.email" property:
ref.onAuth(function(authData) {
if (authData) {
console.log("Authenticated with email: ", authData.google.email);
} else {
console.log("Client unauthenticated.")
}
});

Related

SwiftUI Firebase Phone Auth Change DisplayName

The displayName in the user parameter of the addStateDidChangeListener function in the listener function inside the SessionStore Class returns nil. But I am changing the displayName with the createProfileChangeRequest function. I have one problem. When I change the DisplayName, when I log in with the phone number for the first time, the displayName returns empty. However, when I log in with the application closed in the background, the displayName is full. What is the reason of this ? Where am I doing wrong?
Model:
struct User {
var uid: String
var displayName: String?
var phoneNumber: String?
var isAdmin: Bool
}
Session Store:
class SessionStore : ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
var handle: AuthStateDidChangeListenerHandle?
#Published var user: User?
#Published var uid: String = ""
#Published var errorMessage: String = ""
#Published var showAlert: Bool = false
#Published var isOpenHomePage: Bool = false
#Published var code: String = ""
func listener() {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.user = User(uid: user.uid, displayName: user.displayName, phoneNumber: user.phoneNumber ?? "", isAdmin: false)
} else {
self.user = nil
}
}
}
func verifyPhoneNumber(phoneNumber: String) {
PhoneAuthProvider.provider().verifyPhoneNumber("+90\(phoneNumber)", uiDelegate: nil) { ID, error in
if error != nil {
self.errorMessage = error?.localizedDescription ?? ""
self.showAlert = true
print("erroroo: \(self.errorMessage)")
return
}
self.uid = ID ?? ""
}
}
func credentialProviderPhoneNumber() {
let credential = PhoneAuthProvider.provider().credential(withVerificationID: uid, verificationCode: code)
Auth.auth().signIn(with: credential) { result, error in
if error != nil {
self.errorMessage = error?.localizedDescription ?? ""
print("error mesajı: \(self.errorMessage)")
return
}
self.isOpenHomePage = true
}
}
func signOut () {
do {
try Auth.auth().signOut()
self.user = nil
} catch {
}
}
func unbind () {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
func createProfileChangeRequest() {
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = "Test 123"
changeRequest?.commitChanges(completion: { error in
print("hata: \(error?.localizedDescription)")
})
}
}
SignIn View:
struct SignInView: View {
#State var phoneNumber: String = ""
#State var code: String = ""
#State var didGetCode: Bool = false
#EnvironmentObject var session: SessionStore
var body: some View {
NavigationView {
VStack {
TextField("Telefon Numarası", text: $phoneNumber)
.padding(15)
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(15)
.padding(.horizontal)
.keyboardType(.numberPad)
.opacity(didGetCode ? 0.5 : 1)
.disabled(didGetCode ? true : false)
TextField("Kod", text: $code)
.padding(15)
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(15)
.padding(.horizontal)
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
.opacity(didGetCode ? 1 : 0.5)
.disabled(didGetCode ? false : true)
Button(action: {
if code == "" && phoneNumber != "" {
session.verifyPhoneNumber(phoneNumber: phoneNumber)
didGetCode = true
} else {
session.code = code
session.credentialProviderPhoneNumber()
session.createProfileChangeRequest()
}
}) {
Text(didGetCode ? "Doğrula" : "Kod Al")
}
NavigationLink(
destination: HomeView(),
isActive: session.user != nil ? .constant(true) : $session.isOpenHomePage,
label: {
Text("")
})
}
.onAppear {
session.listener()
}
}
}
}
Home View:
struct HomeView: View {
#EnvironmentObject var session: SessionStore
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Hello User Id: \(session.user?.uid ?? "")")
Text("Tel No: \(session.user?.phoneNumber ?? "")")
Text("Display Name: \(session.user?.displayName ?? "")")
Button(action: {
session.signOut()
presentationMode.wrappedValue.dismiss()
}) {
Text("Çıkış Yap")
}
}
.onAppear {
session.createProfileChangeRequest() -> here
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}

Return created item key

My app creates a new item, and I want to retrieve the key to use in a server script. The data variable returns null though. This is what I have:
function addItem(addButton) {
var addItemPage = addButton.root;
if (!addItemPage.validate()) {
return;
}
var props = addItemPage.properties;
var itemDs = addItemPage.datasource;
props.Creating = true;
itemDs.saveChanges({
success: function(key) {
props.Creating = false;
if (app.currentPage !== app.pages.EditItem) {
return;
}
var newProjectItem = itemDs.item;
newProjectItem._loadHistory();
gotoEditItemPage(newProjectItem._key, true);
return newProjectItem;
},
failure: function(error) {
props.Creating = false;
console.error(error);
}
});
gotoEditItemPage();
var data = app.datasources.ProjectItems.item._key;
google.script.run.withSuccessHandler(function(value){
alert("Created");
}).createDoco(data);
}
This is not neat by any means, but I fixed it by creating a new function:
function addItem(addButton, key) {
var addItemPage = addButton.root;
if (!addItemPage.validate()) {
return;
}
var props = addItemPage.properties;
var itemDs = addItemPage.datasource;
props.Creating = true;
itemDs.saveChanges({
success: function() {
props.Creating = false;
if (app.currentPage !== app.pages.EditItem) {
return;
}
var newProjectItem = itemDs.item;
newProjectItem._loadHistory();
gotoEditItemPage(newProjectItem._key, true);
var key = newProjectItem._key;
value(key);
},
failure: function(error) {
props.Creating = false;
console.error(error);
}
});
gotoEditItemPage();
function value(record){
var data = record;
google.script.run.withSuccessHandler(function(value){
alert("Created");
}).createDoco(data);
}
}

LoginHandler with ldapjs and Meteor.methods

I try to implement a logIn in Meteor 0.9.2.1 with LDAPJS and Meteor methods. The code for the server-side is:
var Future = Meteor.npmRequire('fibers/future');
var ldap = Meteor.npmRequire('ldapjs');
LDAP = {};
LDAP.ldap = ldap;
LDAP.serverIP = 'xxx';
LDAP.serverPort = 'xxx';
LDAP.searchOu = 'ou=xxx,dc=xxx,dc=xxx';
LDAP.searchQuery = function(user) {
return{
filter: '(uid=username)',
scope: 'sub'
}
};
LDAP.checkAccount = function (options) {
LDAP.client = ldap.createClient({
url: 'ldap://' + LDAP.serverIP + ':' + LDAP.serverPort
});
options = options || {};
var dn = [];
future = new Future;
if (options.hasOwnProperty('username') && options.hasOwnProperty('password')) {
LDAP.client.search(LDAP.searchOu, LDAP.searchQuery(options.username), function (err, search) {
search.on('searchEntry', function(entry){
//console.log('entry: ' + JSON.stringify(entry.object));
dn.push(entry.object.uid);
dn.push(entry.object.userPassword)
});
search.on('error', function (err) {
throw new Meteor.Error(500, "LDAP server error");
});
search.on('end', function () {
if (dn.length === 0) {
future['return'](false);
return false;
}
var testBind = LDAP.ldap.createClient({
url: 'ldap://' + LDAP.serverIP + ':' + LDAP.serverPort
});
testBind.bind(dn[10], options.password, function (err) {
future['return'](!err);
});
client.unbind(function (err) {
assert.ifError(err);
future['return'](!err);
});
});
});
} else {
throw new Meteor.Error(400, "Missing Parameter");
}
};
var loginHandler = function (username, password) {
Accounts.registerLoginHandler("ldapjs",function(loginRequest) {
if (LDAP.checkAccount(loginRequest)) {
var user = Meteor.users.findOne({ username: loginRequest.username });
if(err){
console.log(err)
}
return {
userId: uid
}
}
});
};
Meteor.methods({
setSignIn: function(username, password) {
loginHandler(username,password)
}
});
My Problem is, that when I want to log in it starts with the loginHandler. But than the console throws back that Object has no method checkAccount. I changed today a lot and I'm already totally confused.
You need to instantiate the empty object as var LDAP = {}. Rest will be solved magically :)
I finally got to work it. Referneces:
http://notjoshmiller.com/using-ldaps-in-meteor/, https://github.com/emgee3/meteor-accounts-ldap
server-side:
var Future = Meteor.npmRequire('fibers/future');
var ldap = Meteor.npmRequire('ldapjs');
var LDAP = {};
LDAP.ldap = ldap;
//provides the variables, needed for the connection
LDAP.serverIP = 'xxx';
LDAP.serverPort = 'xxx';
LDAP.searchOu = 'ou=xxx,dc=xxx,dc=xxx';
//is needed for the searchQuery, which delivers the Filter so that only the uid with
//the given username get searched
LDAP.searchQuery = function(username) {
return{
filter: '(uid=' + username + ')',
scope: 'sub'
}
};
LDAP.checkAccount = function (options) {
//connects the client, nginx is here not necessary
LDAP.client = ldap.createClient({
url: 'ldap://' + LDAP.serverIP + ':' + LDAP.serverPort
});
options = options || {};
var dn = [];
future = new Future;
if (options.hasOwnProperty('username') && options.hasOwnProperty('password')) {
//create the connection
LDAP.client.search(LDAP.searchOu, LDAP.searchQuery(options.username), function (err, search) {
if(err){
console.log(err)
}
//uses the class searchEntry, which is node-specific
search.on('searchEntry', function (entry) {
dn.push(entry.objectName);
LDAP.displayName = entry.object.displayName
});
search.on('error', function (err) {
throw new Meteor.Error(500, "LDAP server error");
});
//uses the end class to 'fulfill' the connection by binding
search.on('end', function () {
if (dn.length === 0) {
future['return'](false);
return false;
}
LDAP.client.bind(dn[0], options.password, function (err) {
future['return'](!err);
});
});
});
return future.wait();
} else {
throw new Meteor.Error(400, "Missing Parameter");
}
};
Meteor.startup(function(){
Accounts.registerLoginHandler("ldapjs", function (loginRequest) {
if (LDAP.checkAccount(loginRequest)) {
var userId;
var user = Meteor.users.findOne({
username : loginRequest.username
//'profile.name': LDAP.displayName
});
if (user) {
userId = user._id;
} else {
// If no Meteor Account is found for a valid LDAP logon,
// you can either prevent logon by passing 'undefined' or
// you can automatically create the new account.
// return undefined;
userId = Meteor.users.insert({ username : loginRequest.username });
}
return {
userId: userId
}
}
return undefined;
});
});
client side:
Meteor.ldapLogin = function (username, password, callback) {
var loginRequest = {
username: username,
password: password
};
Accounts.callLoginMethod({
methodArguments: [loginRequest],
userCallback: function (err) {
if (err) {
console.log(err);
Session.set('alert', 'No valid inputs!');
} else {
Router.go('/Home');
}
}
});
};
//handles LogIn-Button, by using LDAPJS
Template.signIn.events({
"submit #box-login": function (e, t) {
e.preventDefault();
var signInForm = $(e.currentTarget),
username = trimInput(signInForm.find('#emailSignIn').val().toLowerCase()),
password = signInForm.find('#passwordSignIn').val();
if(isNotEmpty(username)&& isNotEmpty(password)) {
Meteor.ldapLogin(username, password, function (err) {
if (err) {
console.log(err)
Session.set('alert', 'Sorry, something went wrong.');
}
});
} else {
Session.set('alert','Please insert your username and password!')
}
return false;
}
});
PS: No Meteor.methods and Meteor.call is needed! It might change with every new Meteor version and package, but I guess u're aware of that ;)

How to get Ember.js's bindAttr to refresh?

I am trying to create "sort by" buttons which change sorting and css-classes when clicked on using Ember.js.
The sorting part and the initial class assignments work, however, the class assignments are not refreshed when I update the dependant properties.
What am I missing?
This is in my HTML:
<script type="text/x-handlebars" data-template-name="sort-option-item">
<dd {{bindAttr class="IsActive:active IsDesc:reversed"}}
{{action sortBy on="click"}}>{{Name}}</dd>
</script>
<script type="text/x-handlebars">
{{#each option in controller.sortOptions}}
{{view App.SortOptionView controllerBinding="option"}}
{{/each}}
</script>
An this is in my Javascript:
var App = null;
$(function () {
App = Ember.Application.create();
// Define Types:
App.SortOptionCtrl = Ember.Controller.extend({
Name: null,
Predicate: null,
Controller: null,
IsActive: false,
IsDesc: false,
sortBy: function () {
if (this.Controller != null)
this.Controller.sortBy(this.Predicate);
},
Check: function () {
this.IsActive = this.Controller != null
&& this.Controller.isSortedBy(this.Predicate);
this.IsDesc = this.Controller != null
&& this.Controller.isSortedDescBy(this.Predicate);
// adding an alert(this.IsActive); here
// proves that the function is indeed called and works as expected
}
});
App.ProductsController = Ember.ArrayController.extend({
initialized: false,
content: [],
viewContent: [],
sortProperties: ['Order'],
sortAscending: true,
sortOptions: [],
initialize: function () {
if (this.initialized == true)
return;
this.initialized = true;
var ctrl = this;
this.sortOptions.pushObject(App.SortOptionCtrl.create({
Name: 'Unsorted',
Predicate: null,
Controller: ctrl,
}));
this.sortOptions.pushObject(App.SortOptionCtrl.create({
Name: 'By Name',
Predicate: 'Name',
Controller: ctrl,
}));
this.sortOptions.pushObject(App.SortOptionCtrl.create({
Name: 'By Date',
Predicate: 'Date',
Controller: ctrl,
}));
this.sortOptions.forEach(function (opt) { opt.Check(); });
},
load: function () {
this.initialize();
// ....
},
sortBy: function (predicate) {
var prevPredicate = this.sortProperties[0];
if (predicate == prevPredicate && predicate != null) {
this.sortAscending = !(this.sortAscending);
}
else {
this.sortAscending = true;
}
this.sortProperties.length = 0;
if (predicate)
this.sortProperties.pushObject(predicate);
else
this.sortProperties.pushObject('Order');
this.sortOptions.forEach(function (opt) { opt.Check(); });
},
isSortedBy: function (predicate)
{
if (predicate == null)
predicate = 'Order';
var activePredicate = this.sortProperties[0];
if (predicate == activePredicate) {
return true;
}
else {
return false;
}
},
isSortedDescBy: function (predicate) {
if (predicate == null)
predicate = 'Order';
var activePredicate = this.sortProperties[0];
if (predicate == activePredicate) {
if (this.sortAscending)
return false;
else
return true;
}
else {
return false;
}
},
});
App.SortOptionView = Ember.View.extend({
templateName: 'sort-option-item'
});
// Create Instances:
App.productsController = App.ProductsController.create({
});
App.productsController.load();
App.initialize();
});
Versions: Ember: 1.0.0-rc.2, handlebars: 1.0.0-rc.3
If you want your views to react to whatever happens in the controller, you should create computed properties (via fn(){}.property('dependency')). However, in order for computed properties to work properly, you need to use Ember's get() and set() property accessors.
In your code you are doing things like
this.IsActive = this.Controller != null &&
this.Controller.isSortedBy(this.Predicate);
When you should be doing something like this:
this.set('active',
this.get('controller') != null &&
this.get('controller').isSortedBy(this.get('Predicate'))
);
You might have noticed that this code is setting a value into active, but the template is listening to isActive. That property has been changed into a computed property:
isActive: function() {
return this.get('active');
}.property('active')
It will listen for changes in the active property, and whenever that happens, it will cache the new value, and notify all subscriber objects to refresh/update.
Using Ember's get and set accessors is indicated in order to properly use the observables that make this chain of events possible.
I have modified your sample applying get and set where appropriate.
You can see it in this fiddle: http://jsfiddle.net/schawaska/fRMYu/
After Joe's help, more reading of Ember's manual and clean up of my code, I got to this sorter solution:
Sort option controller:
App.SortOptionCtrl = Em.Controller.extend({
Name: null,
Predicate: null,
_isActive: false,
isActive: function () {
return this.get('_isActive');
}.property('_isActive'),
_isDesc: false,
isDesc: function () {
return this.get('_isDesc');
}.property('_isDesc'),
controller: null,
updateState: function () {
if (!this.Predicate) {
this.set('_isActive', (this.get('controller')
.get('activePredicate') == null));
this.set('_isDesc', false);
}
else {
this.set('_isActive', (this.get('controller')
.get('activePredicate') == this.Predicate));
this.set('_isDesc', (this.get('_isActive')
&& !this.get('controller').get('sortAscending')));
}
}.observes('controller.activePredicate', 'controller.sortAscending'),
sortBy: function () {
if (this.get('controller') != null) {
this.get('controller').sortBy(this.Predicate);
}
},
});
Products controller:
App.ProductsController = Ember.ArrayController.extend({
content: [],
viewContent: [],
activePredicate: null,
sortProperties: ['Order'],
sortAscending: true,
sortOptions: [],
filter: function (obj) {
return true;
},
init: function () {
this._super();
var ctrl = this;
this.sortOptions.pushObject(App.SortOptionCtrl.create({
Name: 'Unsorted',
Predicate: null,
controller: ctrl,
}));
this.sortOptions.pushObject(App.SortOptionCtrl.create({
Name: 'By Name',
Predicate: 'Name',
controller: ctrl,
}));
this.sortOptions.pushObject(App.SortOptionCtrl.create({
Name: 'By Date',
Predicate: 'Date',
controller: ctrl,
}));
this.sortOptions.forEach(function (opt) {
opt.updateState();
});
},
sortBy: function (predicate) {
var prevPredicate = this.sortProperties[0];
if (predicate == prevPredicate && predicate != null) {
this.set('sortAscending', !(this.get('sortAscending')));
}
else {
this.set('sortAscending', true);
}
this.set('activePredicate', predicate);
this.set('sortProperties.length', 0);
if (predicate)
this.get('sortProperties').pushObject(predicate);
else
this.get('sortProperties').pushObject('Order');
},
});

Kendo UI bug with jQuery versions

I have a Kendo UI grid and a Kendo UI Window on the same page. The window contains form elements for record insertion, a record being represented by a row in the grid. But for reasons not known by me, when I opened the window and closed it again and then reopened, Kendo UI scaled it to be 100x smaller. I didn't want to hack the window, so I've looked for alternative solution.
I've used jQuery 1.7.2. I've updated jQuery to version 1.8.0. and window opening, closing and reopening worked. I was very happy until I realized that now the grid filters are not working. When I click on a grid filter, nothing happens, no popup, nothing. What is the cause of this and what would be the solution?
EDIT:
This is my code (I've replaced the values of the Urls). Grid filters are working with jQuery 1.7.2. and window reopen works with new versions of jQuery. Also, if I remove the sort hack, the grid filter popup still doesn't show up.
var hshflt = {};
var addWindow;
var editWindow;
var init = false;
//Sort Hack
/*
Changes all dataSources to case insensitive sorting (client side sorting).
This snipped enable case insensitive sorting on Kendo UI grid, too.
The original case sensitive comparer is a private and can't be accessed without modifying the original source code.
tested with Kendo UI version 2012.2.710 (Q2 2012 / July 2012).
*/
var CaseInsensitiveComparer = {
getterCache: {},
getter: function (expression) {
return this.getterCache[expression] = this.getterCache[expression] || new Function("d", "return " + kendo.expr(expression));
},
selector: function (field) {
return jQuery.isFunction(field) ? field : this.getter(field);
},
asc: function (field) {
var selector = this.selector(field);
return function (a, b) {
if ((selector(a).toLowerCase) && (selector(b).toLowerCase)) {
a = selector(a).toLowerCase(); // the magical part
b = selector(b).toLowerCase();
}
return a > b ? 1 : (a < b ? -1 : 0);
};
},
desc: function (field) {
var selector = this.selector(field);
return function (a, b) {
if ((selector(a).toLowerCase) && (selector(b).toLowerCase)) {
a = selector(a).toLowerCase(); // the magical part
b = selector(b).toLowerCase();
}
return a < b ? 1 : (a > b ? -1 : 0);
};
},
create: function (descriptor) {
return this[descriptor.dir.toLowerCase()](descriptor.field);
},
combine: function (comparers) {
return function (a, b) {
var result = comparers[0](a, b),
idx,
length;
for (idx = 1, length = comparers.length; idx < length; idx++) {
result = result || comparers[idx](a, b);
}
return result;
};
}
};
kendo.data.Query.prototype.normalizeSort = function (field, dir) {
if (field) {
var descriptor = typeof field === "string" ? { field: field, dir: dir} : field,
descriptors = jQuery.isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []);
return jQuery.grep(descriptors, function (d) { return !!d.dir; });
}
};
kendo.data.Query.prototype.sort = function (field, dir, comparer) {
var idx,
length,
descriptors = this.normalizeSort(field, dir),
comparers = [];
comparer = comparer || CaseInsensitiveComparer;
if (descriptors.length) {
for (idx = 0, length = descriptors.length; idx < length; idx++) {
comparers.push(comparer.create(descriptors[idx]));
}
return this.orderBy({ compare: comparer.combine(comparers) });
}
return this;
};
kendo.data.Query.prototype.orderBy = function (selector) {
var result = this.data.slice(0),
comparer = jQuery.isFunction(selector) || !selector ? CaseInsensitiveComparer.asc(selector) : selector.compare;
return new kendo.data.Query(result.sort(comparer));
};
kendo.data.Query.prototype.orderByDescending = function (selector) {
return new kendo.data.Query(this.data.slice(0).sort(CaseInsensitiveComparer.desc(selector)));
};
//Sort Hack
$("#refresh-btn").click(function () {
refreshGrid();
});
var grid;
function getPageIndex() {
if (!(grid)) {
return 0;
}
return grid.pager.page() - 1;
}
function getPageSize() {
if (!(grid)) {
return 10;
}
return grid.pager.pageSize();
}
function getFilters() {
if (!(grid)) {
return "";
}
return grid.dataSource.filter();
}
function getSorts() {
if (!(grid)) {
return "";
}
var arr = grid.dataSource.sort();
if ((arr) && (arr.length == 0)) {
return "";
}
var returnValue = "";
for (var index in arr) {
var type = "";
for (var col in grid.columns) {
if (grid.columns[col].field === arr[index].field) {
type = grid.columns[col].type;
}
}
returnValue += ((returnValue.length > 0) ? (";") : ("")) + arr[index].field + "," + (arr[index].dir === "asc") + "," + type;
}
return returnValue;
}
function getColumns() {
if (!(grid)) {
return "";
}
var columns = "";
for (var col in grid.columns) {
if (columns.length > 0) {
columns += ";";
}
columns += grid.columns[col].field + "," + grid.columns[col].type;
}
return columns;
}
var initGrid = true;
var grid2Data;
function getDataSource() {
$.ajax({
type: 'POST',
url: 'mydsurl' + getParams(),
data: "filter=" + JSON.stringify(getFilters()) + "&columns=" + getColumns(),
success: function (param) { grid2Data = param; },
//dataType: dataType,
async: false
});
return grid2Data.Data;
}
var shouldClickOnRefresh = false;
function refreshGrid() {
shouldClickOnRefresh = false;
$.ajax({
type: 'POST',
url: 'mydsurl' + getParams(),
data: "filter=" + JSON.stringify(getFilters()) + "&columns=" + getColumns(),
success: function (param) { grid2Data = param; },
//dataType: dataType,
async: false
});
grid.dataSource.total = function () {
return grid2Data.Total;
}
for (var col in grid.columns) {
if ((grid.columns[col].type) && (grid.columns[col].type === "Date")) {
for (var row in grid2Data.Data) {
grid2Data.Data[row][grid.columns[col].field] = new Date(parseInt((grid2Data.Data[row][grid.columns[col].field] + "").replace("/Date(", "").replace(")/", "")));
}
}
}
grid.dataSource.data(grid2Data.Data);
shouldClickOnRefresh = true;
}
function getParams() {
return getPageSize() + "|" + getPageIndex() + "|" + getSorts();
}
function bindGrid() {
var editUrl = 'myediturl';
if (!(editWindow)) {
editWindow = $("#edit-window");
}
$(".k-button.k-button-icontext.k-grid-edit").each(function (index) {
$(this).click(function () {
if (!editWindow.data("kendoWindow")) {
editWindow.kendoWindow({
title: "Edit User",
width: "60%",
height: "60%",
close: onClose,
open: onEditOpen,
content: editUrl + $("#grid").data().kendoGrid.dataSource.view()[index]["ID"]
});
}
else {
editWindow.data("kendoWindow").refresh(editUrl + $("#grid").data().kendoGrid.dataSource.view()[index]["ID"]);
editWindow.data("kendoWindow").open();
}
editWindow.data("kendoWindow").center();
return false;
})
});
$(".k-button.k-button-icontext.k-grid-delete").each(function (index) {
$(this).click(function () {
var r = confirm("Are you sure you want to delete this user?");
if (r == true) {
$.ajax({
type: 'POST',
url: 'mydelurl' + $("#grid").data().kendoGrid.dataSource.view()[index]["ID"],
success: function (param) { refreshGrid(); },
async: false
});
}
return false;
});
});
}
function onDataBound() {
if (!(shouldClickOnRefresh)) {
shouldClickOnRefresh = true;
bindGrid();
}
else {
refreshGrid();
}
}
$(function () {
$("#grid").kendoGrid({
dataBound: onDataBound,
dataSource: {
autoSync: true,
data: getDataSource(),
serverPaging: true,
schema: {
model: {
fields: {
Email: { type: "string" },
FullName: { type: "string" },
LogCreateDate: { type: "date" },
RoleName: { type: "string" },
UserName: { type: "string" }
}
},
total: function (response) {
return grid2Data.Total;
}
},
pageSize: 10
},
toolbar: ["create"],
scrollable: true,
sortable: true,
filterable: true,
pageable: {
input: true,
numeric: false,
pageSizes: true
},
columns: [
{
command: ["edit", "destroy"],
title: " "
},
{
field: "Email",
title: "Email",
type: "String"
},
{
field: "FullName",
title: "Full Name",
type: "String"
},
{
field: "LogCreateDate",
title: "Created",
type: "Date",
template: '#= kendo.toString(LogCreateDate,"MM/dd/yyyy") #'
},
{
field: "RoleName",
title: "Role",
type: "Custom"
},
{
field: "UserName",
type: "String"
}
],
editable: "popup"
});
grid = $("#grid").data("kendoGrid");
function onAddOpen() {
}
addWindow = $("#add-window");
$(".k-button.k-button-icontext.k-grid-add").click(function () {
if (!addWindow.data("kendoWindow")) {
addWindow.kendoWindow({
title: "Add User",
width: "60%",
height: "60%",
close: onClose,
open: onAddOpen,
content: 'myaddurl'
});
}
else {
addWindow.data("kendoWindow").open();
}
addWindow.data("kendoWindow").center();
addWindow.data("kendoWindow").refresh();
return false;
});
});
function onClose() {
$("#refresh-btn").click();
}
function onEditOpen() {
//editWindow.data("kendoWdinow").center();
}
I've hacked Kendo UI for the second time, this time I've solved its incompatibility with jQuery 1.8.3. using the following hack:
$(".k-grid-filter").each(function(index) {
$(this).click(function() {
$($(".k-filter-menu.k-popup.k-group.k-reset")[index]).offset({
left: $($(".k-grid-filter")[index]).offset().left - $($(".k-filter-menu.k-popup.k-group.k-reset")[index]).width(),
top: $($(".k-grid-filter")[index]).offset().top + $($(".k-grid-filter")[index]).height()})
})
});
I've put this hack into the document load event of the page an voila, it works. It surely looks ugly as hell with this hack, but after designing it will look good as new. I'm happy I found a work around, but I'm unhappy I had to hack Kendo UI twice. It is a very nice tool except the bugs.
jQuery 1.8.# is only compatible with Kendo UI - Q2 2012 SP1 (2012.2 913) or later..
If your kendo UI version is earlier, you should update it.

Resources