missing colon
[tine20] / tine20 / Tinebase / js / tine20-loginbox.js
1
2 /*
3 Example html code to include Tine 2.0 login box on an external webpage
4
5 <html>
6 <head>
7     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8             
9     <!-- ext-core library -->
10     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/ext-core/3/ext-core.js"></script>
11     
12     <!-- use protocol and fqdn -->
13     <script type="text/javascript" src="http://localhost/Tinebase/js/tine20-loginbox.js"></script>
14                             
15 </head>
16 <body>
17
18 <div id="tine20-login" style="width:100px;"></div>
19
20 </body>
21 </html>
22 */
23
24 Ext.ns('Tine20.login');
25
26 /**
27  * @namespace   Tine20
28  * @class       Tine20.login
29  * @author      Cornelius Weiss <c.weiss@metaways.de>
30  * 
31  * Simple login form for remote Tine 2.0 logins
32  */
33 Tine20.login = {
34     /**
35      * detect users language (fallback en)
36      * 
37      * @return {String} language code
38      */
39     detectBrowserLanguage : function () {
40         var result = 'en';
41         var userLanguage ='';
42
43         if (navigator.userLanguage) {// Explorer
44             userLanguage = navigator.userLanguage;
45         } else if (navigator.language) {// FF
46             userLanguage = navigator.language;
47         }
48         
49         // some browser have a locale string as language
50         if(Tine20.login.translations[userLanguage]) {
51             result = userLanguage;
52         } else if (userLanguage.match('-')) {
53             userLanguage = userLanguage.split('-')[0];
54             if(Tine20.login.translations[userLanguage]) {
55                 result = userLanguage;
56             }
57         }
58
59         return result;
60     },
61     
62     /**
63      * gets config for this login-box
64      * 
65      * @return {Object}
66      */
67     getConfig: function() {
68         if (! this.config) {
69             var src = Ext.DomQuery.selectNode('script[src*=tine20-loginbox.js]').src;
70             var tine20Url = src.match('Tinebase') ? 
71                     src.substring(0, src.indexOf('Tinebase'))  + 'index.php' :
72                     src.substring(0, src.indexOf('tine20-loginbox.js')) + 'index.php';
73             
74             var tine20ProxyUrl = src.substring(0, src.indexOf('tine20-loginbox.js')) + 'ux/data/windowNameConnection.html';
75                     
76             var config = {
77                 userLanguage: Tine20.login.detectBrowserLanguage(),
78                 tine20Url: tine20Url,
79                 tine20ProxyUrl: tine20ProxyUrl
80             };
81             
82             /* parse additional params here */
83             var parts = src.split('?');
84             
85             this.config = config;
86         }
87         
88         return this.config;
89     },
90     
91     /**
92      * gets template for login form
93      * 
94      * @return {Ext.Template}
95      */
96     getLoginTemplate: function () {
97         if (! this.loginTemplate) {
98             this.loginTemplate = new Ext.Template(
99                 '<form name="{formId}" id="{formId}" method="POST">',
100                     '<div class="tine20login-fields">',
101                         '<div class="tine20login-field-username">',
102                             '<label>{loginname}:</label>',
103                             '<input type="text" name="username">',
104                         '</div>',
105                         '<div class="tine20login-field-password">',
106                             '<label>{password}:</label>',
107                             '<input type="password" name="password">',
108                         '</div>',
109                         '<input type="hidden" name="method" value="{method}">',
110                     '</div>',
111                     '<div class="tine20login-progess"></div>',
112                     '<div class="tine20login-message"></div>',
113                     '<div class="tine20login-button">{login}</div>',
114                 '</form>'
115             ).compile();
116         }
117         
118         return this.loginTemplate;
119     },
120     
121     /**
122      * checks authentication from server
123      * 
124      * @param  {Object}   config
125      * @param  {String}   username
126      * @param  {String}   password
127      * @param  {Function} cb       callback function
128      * @return void
129      */
130     checkAuth: function(config, username, password, cb) {
131         var ua = navigator.userAgent.toLowerCase(),
132             isIE = ua.match(/msie (\d+)/),
133             useCOSR = !isIE || isIE[1] > 9,
134             conn = useCOSR ? Ext.Ajax : new Ext.ux.data.windowNameConnection({
135                 proxyUrl: config.tine20ProxyUrl
136             });
137         
138         conn.request({
139             url: config.tine20Url,
140             headers: {
141                 'X-Tine20-Request-Type' : 'JSON'
142             },
143             jsonData: Ext.encode({
144                 jsonrpc: '2.0',
145                 method: 'Tinebase.authenticate',
146                 id: ++Ext.Ajax.requestId,
147                 params: {
148                     username: username,
149                     password: password
150                 }
151             }),
152             success: cb
153         });
154     },
155     
156     /**
157      * processes login response data
158      *  - updates message box
159      *  - posts form for login on success
160      * 
161      * @param  {Object} data
162      * @return void
163      */
164     onLoginResponse: function(response) {
165         try {
166             var data = Ext.decode(response.responseText).result;
167         } catch (e) {
168             var data = {};
169         }
170         
171         var config = this.getConfig();
172         if (data.status == 'success') {
173             // redirect?
174             if (data.loginUrl) {
175                 config.tine20BkpUrl = config.tine20BkpUrl ? config.tine20BkpUrl : config.tine20Url;
176                 
177                 config.tine20Url = data.loginUrl;
178                 return this.onLoginPress();
179             }
180             
181             // show success message
182             this.messageBoxEl.update(this.translations[config.userLanguage].authsuccess);
183             this.setCssClass('loginSuccess');
184             
185             // post data
186             this.loginBoxEl.dom.action = data.loginUrl || config.tine20Url;
187             this.loginBoxEl.dom.submit();
188         } else {
189             // show fail message
190             this.messageBoxEl.update(this.translations[config.userLanguage].authfailed);
191             this.setCssClass('loginFaild');
192             
193             config.tine20Url = config.tine20BkpUrl ? config.tine20BkpUrl : config.tine20Url;
194             
195             this.passwordEl.focus(100);
196             this.passwordEl.dom.select();
197         }
198     },
199     
200     /**
201      * login button handler
202      * 
203      * @return void
204      */
205     onLoginPress: function() {
206         var config = this.getConfig();
207         
208         var username = this.usernameEl.dom.value;
209         var password = this.passwordEl.dom.value;
210         
211         this.messageBoxEl.update(this.translations[config.userLanguage].authwait);
212         this.setCssClass('onLogin');
213         
214         this.checkAuth(config, username, password, this.onLoginResponse.createDelegate(this));
215     },
216     
217     /**
218      * renders login form and initializes elements and listeners
219      * 
220      * @return void
221      */
222     renderLoginForm: function() {
223         var t = this.getLoginTemplate();
224         var config = this.getConfig();
225         
226         // render template
227         this.loginBoxEl = t.append('tine20-login', {
228             formId: 'tine20loginform',
229             method: 'Tinebase.loginFromPost',
230             loginname: Tine20.login.translations[config.userLanguage].loginname,
231             password: Tine20.login.translations[config.userLanguage].password,
232             login: Tine20.login.translations[config.userLanguage].login
233         }, true);
234         
235         // init Elements
236         var E = Ext.Element;
237         this.usernameEl   = new E(Ext.DomQuery.selectNode('input[name=username]', this.loginBoxEl.dom));
238         this.passwordEl   = new E(Ext.DomQuery.selectNode('input[name=password]', this.loginBoxEl.dom));
239         this.buttonEl     = new E(Ext.DomQuery.selectNode('div[class=tine20login-button]', this.loginBoxEl.dom));
240         this.messageBoxEl = this.loginBoxEl.child('div[class=tine20login-message]');
241         //this.progressEl   = this.loginBoxEl.child('div[class=tine20loginmessage]');
242         
243         // init listeners
244         this.buttonEl.on('click', this.onLoginPress, this);
245         this.loginBoxEl.on('keydown', function(e, target) {
246             switch(e.getKey()) {
247                 case 10:
248                 case 13:
249                     e.preventDefault();
250                     this.onLoginPress();
251                     break;
252                 default:
253                     // nothing
254                     break;
255             }
256         }, this);
257         
258         // focus username field
259         this.usernameEl.focus(500);
260     },
261     
262     /**
263      * sets css class of outer form el according to 
264      * login state
265      * 
266      * @param {string} state
267      */
268     setCssClass: function(state) {
269         var allStates = [
270             'onLogin',
271             'loginFaild',
272             'loginSuccess'
273         ];
274         Ext.each(allStates, function(s){
275             var method = s === state ? 'addClass' : 'removeClass';
276             this.loginBoxEl[method]('tine20login-' + s);
277         }, this);
278         
279     },
280     
281     /**
282      * static translations array
283      * 
284      * @type Object
285      */
286     translations: {
287         'en' : {
288             'loginname'   : 'Username',
289             'password'    : 'Password',
290             'login'       : 'Login',
291             'authwait'    : 'Authenticating...',
292             'authfailed'  : 'Username or password wrong',
293             'authsuccess' : 'Successful authentication, login in now...'
294         },
295         'de' : {
296             'loginname'   : 'Benutzername',
297             'password'    : 'Passwort',
298             'login'       : 'Anmelden',
299             'authwait'    : 'Authentifizierung...',
300             'authfailed'  : 'Benutzername oder Passwort falsch',
301             'authsuccess' : 'Authentifizierung erfolgreich, anmeldung erfolgt...'
302         }
303     }
304 }
305
306 // register onReady listener
307 Ext.onReady(Tine20.login.renderLoginForm, Tine20.login);
308
309
310 Ext.ns('Ext.ux.data');
311
312 /**
313  * @namespace   Ext.ux.data
314  * @class       Ext.ux.data.windowNameConnection
315  * @extends     Ext.util.Observable
316  * @author      Cornelius Weiss <c.weiss@metaways.de>
317  * 
318  * @param {Object} config
319  * 
320  * window name communication class
321  * 
322  */
323 Ext.ux.data.windowNameConnection = function(config) {
324     Ext.ux.data.windowNameConnection.superclass.constructor.call(this, config);
325     
326     Ext.apply(this, config);
327     
328     if (! this.blankUrl) {
329         this.blankUrl = window.location.href.replace(window.location.pathname.substring(1, window.location.pathname.length), '') + 'blank.html';
330     }
331     
332     if (! this.proxyUrl) {
333         var src = Ext.DomQuery.selectNode('script[src*=windowNameConnection.js]').src;
334         this.proxyUrl = src.substring(0, src.length -2) + 'html';
335     }
336 };
337 Ext.ux.data.windowNameConnection.TRANSACTIONID = 1000;
338
339 Ext.extend(Ext.ux.data.windowNameConnection, Ext.util.Observable, {
340     
341     /**
342      * @cfg {String} url (Optional) The default URL to be used for requests to the server. Defaults to undefined.
343      * The url config may be a function which returns the URL to use for the Ajax request. The scope
344      * (this reference) of the function is the scope option passed to the {@link #request} method.
345      */
346     
347     /**
348      * @cfg {String} blankUrl The default URL to a blank page on the page of the same origin (SOP) defaults to
349      * blank.html on the SOP server.
350      */
351     
352     /**
353      * @cfg {String} proxyUrl The default URL to the external proxy html (windowNameConnection.html)
354      */
355     
356     /**
357      * create callback fn
358      * 
359      * @private
360      * @param {Object} transaction
361      * @return {Function}
362      */
363     createCallback : function(transaction) {
364         var self = this;
365         return function() {
366             try {
367                 var frame = transaction.frame;
368                 if (frame.contentWindow.location.href === transaction.blankUrl) {
369                     self.onData.call(self, transaction, frame.contentWindow.name);
370                     self.destroyTransaction(transaction, true);
371                 }
372             } catch(e){}
373         };
374     },
375     
376     /**
377      * cleanup 
378      * 
379      * @private
380      * @param {Object} transaction
381      */
382     destroyTransaction: function(transaction) {
383         transaction.frame.contentWindow.onload = null;
384         try {
385             // we have to do this to stop the wait cursor in FF 
386             var innerDoc = transaction.frame.contentWindow.document;
387             innerDoc.write(" ");
388             innerDoc.close();
389         }catch(e){}
390         
391         Ext.fly(transaction.frame).remove();
392         delete transaction.frame;
393         
394         window[transaction.cb] = undefined;
395         try{
396             delete Ext.ux.data.windowNameConnection[transaction.id];
397         }catch(e){}
398     },
399     
400     /**
401      * called when data arrived
402      * 
403      * @private
404      * @param {Object} transaction
405      * @param {mixed} res
406      */
407     onData: function(transaction, res) {
408         var resultData = Ext.decode(res);
409         if (transaction.options.callback) {
410             transaction.options.callback.call(transaction.scope, transaction.options, resultData.success, resultData.response);
411         } else {
412             var fn = resultData.success ? 'success' : 'fail';
413             if (transaction.options[fn]) {
414                 transaction.options[fn].call(transaction.scope, resultData.response, transaction.options);
415             }
416         }
417     },
418     
419     /**
420      * performs request
421      * 
422      * @param {} options
423      */
424     request: function(options) {
425         var transactionId = 'Ext.ux.data.windowNameConnection' + (++Ext.ux.data.windowNameConnection.TRANSACTIONID);
426         var doc = document;
427         
428         var blankUrl = options.blankUrl || this.blankUrl;
429         
430         var url = options.url || this.url;
431         if (Ext.isFunction(url)) {
432             url = url.call(options.scope || WINDOW, options);
433         }
434         
435         var requestData = Ext.encode({
436             blankUrl: blankUrl,
437             options: { // just a subset and ext-core has no copyTo :-(
438                 url:      url,
439                 method:   options.method,
440                 params:   options.params,
441                 timeout:  options.timeout,
442                 headers:  options.headers,
443                 xmlData:  options.xmlData,
444                 jsonData: options.jsonData
445             }
446         });
447         
448         var frame = doc.createElement(Ext.isIE ? "<iframe name='" + requestData + "' onload='Ext.ux.data.windowNameConnection[\"" + transactionId + "\"]()'>" : 'iframe');
449         
450         var transaction = {
451             id         : transactionId,
452             options    : options,
453             scope      : options.scope || window,
454             frame      : frame,
455             blankUrl   : blankUrl
456         };
457         
458         Ext.ux.data.windowNameConnection[transactionId] = frame.onload = this.createCallback(transaction);
459         
460         frame.id = transactionId;
461         frame.name = requestData;
462         frame.style.position = 'absolute';
463         frame.style.top = '-10000px';
464         frame.style.left = '-10000px';
465         frame.style.visability = 'hidden';
466         frame.src = this.proxyUrl + '?' + new Date().getTime();
467         
468         doc.body.appendChild(frame);
469     }
470 });
471
472 /**
473  * proxy request
474  * - reads request data from window.name
475  * - performs ajax request with proxy domain
476  * - writes respponse to window.name
477  * - navigates window back to same domain (blankUrl) of requestors page
478  * 
479  */
480 Ext.ux.data.windowNameConnection.doProxyRequest = function() {
481     var requestOptions = Ext.decode(window.name);
482     
483     Ext.Ajax.request(Ext.apply(requestOptions.options, {
484         callback: function(options, success, response) {
485             window.name = Ext.encode({
486                 success: success,
487                 response: {
488                     status:       response.status || 200,
489                     statusText:   response.statusText,
490                     responseText: response.responseText/*,
491                     responseXML:  response.responseXML crahes in IE???*/
492                 }
493             });
494             
495             window.location.href = requestOptions.blankUrl;
496         }
497     }));
498     
499 };