commit:654c2f97dcada0914617a5cb993a2e2decd629b9
author:Chip Black
committer:Chip Black
date:Mon Aug 13 02:20:53 2012 -0500
parents:f846b5bd0606c02eb222a1a17371dae364d98c80
Make userlisting load data; add more style
diff --git a/source/API.js b/source/API.js
line changes: +266/-0
index 0000000..4a0602b
--- /dev/null
+++ b/source/API.js
@@ -0,0 +1,266 @@
+var baseURL = '';
+
+// The API state is static so that any instance can use login-dependent API
+// calls
+enyo.kind({
+    name: "blerg.API",
+    kind: "Component",
+    statics: {
+        apiInitialized: false,
+        loggedIn: false,
+        username: "",
+    },
+    create: function() {
+        this.inherited(arguments);
+        if (blerg.API.apiInitialized) {
+            if (blerg.API.loggedIn) {
+                setTimeout(function() {
+                    this.bubble('onLoginSuccessful', {username: blerg.API.username});
+                }.bind(this), 0);
+            }
+            return;
+        }
+
+        if (enyo.getCookie('auth') && enyo.getCookie('username')) {
+            blerg.API.loggedIn = true;
+            blerg.API.username = enyo.getCookie('username');
+            // Defer the signal until everything's initialized
+            setTimeout(function() {
+                this.bubble('onLoginSuccessful', {username: blerg.API.username});
+            }.bind(this), 0);
+        }
+
+        blerg.API.apiInitialized = true;
+    },
+    signup: function(username, password) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/create',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            if (inResponse.status == 'success') {
+                this.bubble('onSignupSuccess', {username: username});
+            } else {
+                this.bubble('onSignupFailure', {username: username});
+            }
+        }.bind(this));
+        req.error(function() {
+            this.bubble('onSignupFailure', {username: username});
+        }.bind(this));
+        req.go({
+            username: username,
+            password: password,
+        });
+    },
+    login: function(username, password) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/login',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            if (inResponse.status == 'success') {
+                blerg.API.loggedIn = true;
+                blerg.API.username = username;
+                enyo.setCookie('username', username);
+                this.bubble('onLoginSuccessful', {username: username});
+            } else {
+                enyo.setCookie('username', '', {"Max-Age": 0});
+                this.bubble('onLoginFailed');
+            }
+        }.bind(this));
+        req.go({
+            username: username,
+            password: password
+        });
+    },
+    logout: function() {
+        var req = new enyo.Ajax({
+            url: baseURL + '/logout',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            blerg.API.loggedIn = false;
+            enyo.setCookie('auth', '', {"Max-Age": 0});
+            this.bubble('onLogoutSuccessful');
+        }.bind(this));
+        req.go({
+            username: blerg.API.username
+        });
+        enyo.setCookie('username', '', {"Max-Age": 0});
+    },
+    changePassword: function(oldpassword, newpassword) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/passwd',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            if (inResponse.status == 'success') {
+                this.bubble('onPasswordChangeSuccessful');
+            } else {
+                this.bubble('onPasswordChangeFailed');
+            }
+        }.bind(this));
+        req.go({
+            username: blerg.API.username,
+            password: oldpassword,
+            new_password: newpassword
+        });
+    },
+    loadUserRecords: function(username, from ,to) {
+        var url;
+        if (from != undefined && to != undefined) {
+            url = baseURL +  '/get/' + username + '/' + from + '-' + to;
+        } else {
+            url = baseURL +  '/get/' + username;
+        }
+
+        var req = new enyo.Ajax({
+            url: url
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onItemsLoaded', {
+                type: 'user',
+                username: username,
+                from: from,
+                to: to,
+                entries: inResponse
+            });
+        }.bind(this));
+        req.error(function(inSender, inResponse) {
+            if (inResponse == 404)
+                this.bubble('onUserNotFound');
+            else
+                this.bubble('onAPIError', {response: inResponse});
+        }.bind(this));
+        req.go();
+    },
+    loadTagRecords: function(type, tag) {
+        var url;
+        switch(type) {
+            case 'tag':
+                // Apache eats the hash, even encoded.  Probably a security
+                // feature.
+                url = baseURL + '/tag/H' + tag;
+                break;
+            case 'ref':
+                url = baseURL + '/tag/%40' + tag;
+                break;
+            default:
+                throw new Error("Invalid tag type: " + type);
+                return;
+        }
+        var req = new enyo.Ajax({
+            url: url
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onItemsLoaded', {
+                type: 'tag',
+                tagType: type,
+                tag: tag,
+                entries: inResponse
+            });
+        }.bind(this));
+        req.go();
+    },
+    getFeedInfo: function() {
+        if (!blerg.API.loggedIn)
+            throw new Error('Cannot request feed status when not logged in');
+
+        var req = new enyo.Ajax({
+            url: baseURL + '/feedinfo',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onFeedInfo', inResponse);
+        }.bind(this));
+        req.go({
+            username: blerg.API.username
+        });
+    },
+    loadFeed: function() {
+        if (!blerg.API.loggedIn)
+            throw new Error('Cannot request feed status when not logged in');
+
+        var req = new enyo.Ajax({
+            url: baseURL + '/feed',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onItemsLoaded', {
+                type: "feed",
+                entries: inResponse
+            });
+        }.bind(this));
+        req.go({
+            username: blerg.API.username
+        });
+    },
+    getSubscriptionStatus: function(username) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/feedinfo/' + username,
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onSubscriptionStatus', {
+                username: username,
+                subscribed: inResponse.subscribed
+            });
+        }.bind(this));
+        req.go({
+            username: blerg.API.username
+        });
+    },
+    subscribe: function(username) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/subscribe/' + username,
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onSubscriptionStatus', {
+                username: username,
+                subscribed: inResponse.status == "success"
+            });
+        }.bind(this));
+        req.go({
+            username: blerg.API.username
+        });
+    },
+    unsubscribe: function(username) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/unsubscribe/' + username,
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            this.bubble('onSubscriptionStatus', {
+                username: username,
+                subscribed: inResponse.status != "success"
+            });
+        }.bind(this));
+        req.go({
+            username: blerg.API.username
+        });
+    },
+    post: function(data) {
+        var req = new enyo.Ajax({
+            url: baseURL + '/put',
+            method: 'POST'
+        });
+        req.response(function(inSender, inResponse) {
+            if (inResponse && inResponse.status == 'success') {
+                this.bubble('onPostSuccessful', {
+                    username: blerg.API.username,
+                    data: data
+                });
+            } else {
+                this.bubble('onPostFailed', {
+                    username: blerg.API.username,
+                    data: data
+                });
+            }
+        }.bind(this));
+        req.go({
+            username: blerg.API.username,
+            data: data
+        });
+    }
+});

diff --git a/source/Blerg.css b/source/Blerg.css
line changes: +55/-4
index 159bc39..c94b056
--- a/source/Blerg.css
+++ b/source/Blerg.css
@@ -1,4 +1,5 @@
 body {
+	font: Prelude, sans-serif;
 }
 
 .navigator-breadcrumbs {
@@ -10,7 +11,10 @@ body {
 	display: block;
 	width: 100%;
 	padding: 8pt;
-	margin: 8pt 0;
+}
+
+.vertical-button + .vertical-button {
+	margin-top: 8pt;
 }
 
 .crumb-button {
@@ -41,9 +45,56 @@ body {
 }
 
 .record {
-	margin-bottom: 8pt;
+	margin: 8pt 0 8pt 0;
+	font-size: 12pt;
+}
+
+.record + hr {
+	width: 50%;
+	border: none;
+	border-bottom: 1px solid #AAB;
+	margin: 12pt auto;
+}
+
+.record p {
+	margin: 8pt 0 0 0;
+}
+
+.record .info {
+	color: #8F8F8F;
+	font-size: small;
+	font-style: italic;
+	margin-top: 2pt;
+}
+
+.record .info a {
+	color: #8F8F8F;
 }
 
-.record > p {
-	margin-top: 0;
+.record h1 {
+	font-size: 18pt;
+	margin: 8pt 0 1em 0;
+}
+
+.record h2 {
+	font-size: 16pt;
+	margin: 8pt 0 1em 0;
+}
+
+.record h3 {
+	font-size: 14pt;
+	margin: 8pt 0 1em 0;
+}
+
+.record h4 {
+	font-size: 14pt;
+	font-weight: normal;
+	margin: 8pt 0 1em 0;
+}
+
+.record h5 {
+	font-size: 14pt;
+	font-weight: normal;
+	font-style: italic;
+	margin: 8pt 0 1em 0;
 }

diff --git a/source/PasswdDialog.js b/source/PasswdDialog.js
line changes: +72/-0
index 0000000..449ef37
--- /dev/null
+++ b/source/PasswdDialog.js
@@ -0,0 +1,72 @@
+enyo.kind({
+    name: "blerg.PasswdDialog",
+    kind: "onyx.Popup",
+    classes: "blerg-dialog",
+    autoDismiss: true,
+    centered: true,
+    floating: true,
+    modal: true,
+    components: [
+        {tag: "h2", content: "Change Password"},
+        {name: "spinner", kind: "OldSchoolSpinner", showing: false, style: "position: absolute; top: 8px; right: 8px;"},
+        {kind: "onyx.Groupbox", components: [
+            {kind: "onyx.InputDecorator", components: [
+                {name: "oldpassword", kind: "onyx.Input", placeholder: "Old Password", type: "password"}
+            ]}
+        ]},
+        {kind: "onyx.Groupbox", components: [
+            {kind: "onyx.InputDecorator", components: [
+                {name: "password1", kind: "onyx.Input", placeholder: "New Password", type: "password"}
+            ]},
+            {kind: "onyx.InputDecorator", components: [
+                {name: "password2", kind: "onyx.Input", placeholder: "New Password (again)", type: "password"}
+            ]}
+        ]},
+        {name: "changePasswordError", tag: "p", showing: false, classes: "blerg-error", content: "I couldn't change your password. Are you sure you have the right old password?"},
+        {name: "passwordMatchError", tag: "p", showing: false, classes: "blerg-error", content: "Your new passwords don't match."},
+        {name: "changeButton", kind: "onyx.Button", content: "Change", onclick: "changeClick", classes: "onyx.affirmative"},
+        {kind: "onyx.Button", content: "Cancel", onclick: "cancelClick", classes: "onyx-negative"},
+        {name: "api", kind: "blerg.API",
+         onPasswordChangeSuccessful: "passwordChangeSuccessful",
+         onPasswordChangeFailed: "passwordChangeFailed"}
+    ],
+    hideErrors: function() {
+        this.$.changePasswordError.hide();
+        this.$.passwordMatchError.hide();
+    },
+    changeClick: function() {
+        this.hideErrors();
+        if (this.$.oldpassword.getValue() == '') {
+            this.$.changePasswordError.show();
+            return;
+        }
+        if (this.$.password1.getValue() != this.$.password2.getValue()) {
+            this.$.passwordMatchError.show();
+            return;
+        }
+        this.$.changeButton.setDisabled(true);
+        this.$.spinner.show();
+        this.$.spinner.start();
+        this.$.api.changePassword(this.$.oldpassword.getValue(), this.$.password1.getValue());
+    },
+    cancelClick: function() {
+        this.hide();
+    },
+    passwordChangeSuccessful: function(inSender, inEvent) {
+        this.$.oldpassword.setValue('');
+        this.$.password1.setValue('');
+        this.$.password2.setValue('');
+        this.$.changeButton.setDisabled(false);
+        this.$.spinner.hide();
+        this.$.spinner.stop();
+        this.cancelClick();
+        alert("Password Changed Successfully");
+    },
+    passwordChangeFailed: function(inSender, inEvent) {
+        this.$.changeButton.setDisabled(false);
+        this.$.spinner.hide();
+        this.$.spinner.stop();
+        this.hideErrors();
+        this.$.changePasswordError.show();
+    }
+});

diff --git a/source/SignupDialog.js b/source/SignupDialog.js
line changes: +58/-0
index 0000000..acae24f
--- /dev/null
+++ b/source/SignupDialog.js
@@ -0,0 +1,58 @@
+enyo.kind({
+    name: "blerg.SignupDialog",
+    kind: "onyx.Popup",
+    classes: "blerg-dialog",
+    autoDismiss: true,
+    centered: true,
+    floating: true,
+    modal: true,
+    components: [
+        {tag: "h2", content: "Sign Up"},
+        {name: "spinner", kind: "OldSchoolSpinner", showing: false, style: "position: absolute; top: 8px; right: 8px;"},
+        {kind: "onyx.Groupbox", components: [
+            {kind: "onyx.InputDecorator", components: [
+                {name: "username", kind: "onyx.Input", placeholder: "Username"}
+            ]},
+            {kind: "onyx.InputDecorator", components: [
+                {name: "password", kind: "onyx.Input", placeholder: "Password", type: "password"}
+            ]}
+        ]},
+        {name: "message", tag: "p", content: "No, really, that's all I need. I don't want to send you email. It's too much work to program in email updates. :-P"},
+        {name: "signupError", tag: "p", showing: false, classes: "blerg-error", content: "I couldn't sign you up. That username is already in use, or something went wrong on the backend. *shrug*"},
+        {name: "signupButton", kind: "onyx.Button", content: "Signup", onclick: "signupClick", classes: "onyx-affirmative"},
+        {kind: "onyx.Button", content: "Cancel", onclick: "cancelClick", classes: "onyx-negative"},
+        {name: "api", kind: "blerg.API",
+         onSignupSuccess: "signupSuccess",
+         onSignupFailure: "signupFailure"}
+    ],
+    signupClick: function() {
+        this.$.signupButton.setDisabled(true);
+        this.$.spinner.show();
+        this.$.spinner.start();
+        this.$.api.signup(this.$.username.getValue(), this.$.password.getValue());
+    },
+    cancelClick: function() {
+        this.$.signupError.hide();
+        this.$.message.show();
+        this.$.username.setValue('');
+        this.$.password.setValue('');
+        this.hide();
+    },
+    signupSuccess: function(inSender, inEvent) {
+        this.$.signupButton.setDisabled(false);
+        this.$.spinner.hide();
+        this.$.spinner.stop();
+        this.bubble('onTryLogin', {
+            username: this.$.username.getValue(),
+            password: this.$.password.getValue()
+        });
+        this.cancelClick();
+    },
+    signupFailure: function(inSender, inEvent) {
+        this.$.signupButton.setDisabled(false);
+        this.$.spinner.hide();
+        this.$.spinner.stop();
+        this.$.message.hide();
+        this.$.signupError.show();
+    }
+});

diff --git a/source/UserListing.js b/source/UserListing.js
line changes: +42/-10
index 7dfb7a5..ab27269
--- a/source/UserListing.js
+++ b/source/UserListing.js
@@ -4,26 +4,58 @@ enyo.kind({
     published: {
         user: null
     },
+    items: [],
+    lastItem: null,
     components: [
         {name: "title", content: "Listing", tag: "h2"},
-        {name: "list", kind: "List", classes: "listing-content", onSetupItem: "setupItem", count:10, fit: true, components: [
-            {name: "record", kind: "blerg.Record", ontap: "itemTap"}
-        ]}
+        {name: "list", kind: "List", classes: "listing-content", onSetupItem: "setupItem", fit: true, components: [
+            {name: "record", kind: "blerg.Record", ontap: "itemTap"},
+            {name: "separator", tag: "hr"}
+        ]},
+        {name: "api", kind: "blerg.API",
+         onItemsLoaded: "itemsLoaded"}
     ],
     create: function() {
         this.inherited(arguments);
         this.userChanged();
     },
+    setupItem: function(inSender, inEvent) {
+        var r = this.items[inEvent.index];
+        this.$.record.setTimestamp(r.timestamp);
+        this.$.record.setData(r.data);
+        this.$.record.setRecord(r.record);
+        if (inEvent.index == this.items.length - 1) {
+            this.$.separator.hide();
+        } else {
+            this.$.separator.show();
+        }
+    },
     userChanged: function() {
-        this.title = "@" + this.user
-        this.$.title.setContent(this.title);
+        if (this.user != null) {
+            this.title = "@" + this.user
+            this.$.title.setContent(this.title);
+            this.items = [];
+            this.lastItem = null;
+            this.loadItems();
+        }
     },
-    setupItem: function(inSender, inEvent) {
-        this.$.record.setAuthor("Blerger");
-        this.$.record.setTimestamp(new Date().getTime() / 1000);
-        this.$.record.setData("Blerg " + inEvent.index);
+    loadItems: function() {
+        if (this.lastItem == null) {
+            this.$.api.loadUserRecords(this.user);
+        } else {
+            var to = this.lastItem - 1;
+            if (to >= 0) {
+                var from = to > 50 ? to - 50 : 0;
+                this.$.api.loadUserRecords(this.user, from, to);
+            }
+        }
+    },
+    itemsLoaded: function(inSender, inEvent) {
+        this.items = this.items.concat(inEvent.entries);
+        this.lastItem = inEvent.from;
+        this.$.list.setCount(this.items.length);
+        this.$.list.refresh();
     },
     itemTap: function(inSender, inEvent) {
-        this.bubble('onNavigate', {kind: "UserListing", user: inEvent.originator.getAuthor()});
     }
 });

diff --git a/source/Welcome.js b/source/Welcome.js
line changes: +2/-2
index 5f11018..96f4b6c
--- a/source/Welcome.js
+++ b/source/Welcome.js
@@ -3,14 +3,14 @@ enyo.kind({
     kind: "Listing",
     title: "Blërg",
     components: [
+        {tag: 'h2', content: "Welcome to Blërg!"},
         {kind: "Scroller", classes: "listing-content", components: [
-            {content: "Welcome to Blërg!"},
             {kind: "onyx.Button", classes: "onyx-affirmative vertical-button", content: "Create an account"},
             {kind: "onyx.Button", classes: "vertical-button", content: "Log In"},
             {kind: "onyx.Button", classes: "vertical-button", content: "Listing", ontap: "showListing"},
         ]}
     ],
     showListing: function() {
-        this.bubble('onNavigate', {kind: "UserListing", user: "user" + Math.floor(Math.random() * 1000)});
+        this.bubble('onNavigate', {kind: "UserListing", user: "bytex64"});
     }
 });

diff --git a/source/package.js b/source/package.js
line changes: +3/-0
index e03ac77..913ce39
--- a/source/package.js
+++ b/source/package.js
@@ -1,7 +1,10 @@
 enyo.depends(
     '$lib/layout',
     '$lib/onyx',
+    'API.js',
     'Util.js',
+    'SignupDialog.js',
+    'PasswdDialog.js',
     'Blerg.css',
     'Blerg.js',
     'Record.js',