Pages

General Information

For this blog application we need some pages and modules. The font we use must be placed in the fonts folder. Also, the images we will use must be included in the img folder.

Notice

  • Every projects need to be handle fonts, img, lang, and lib folders. For more information about structure of these folders click here.
  • You can download used font and images from here.

This application have general and common informations that we need.

Notice

Pay attention

  • All html codes should be into index.html file.
  • All CSS files should placed into CSS folder and included in head section of index.html.
  • All JS files should placed into JS folder and included in bottom section of index.html.
  • You can download the completed source of the blog application from here.
  • HTML

    Into index.html should add the follow codes at the head section.

    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <title>Blog</title>
        <link rel="stylesheet" type="text/css" href="lib/framework.css" />
        <link rel="stylesheet" type="text/css" href="lib/mdtoast.css" />
    
        <!-- add your modules styles here -->
        <!-- <link rel="stylesheet" type="text/css" href="modules/" /> -->
        <link rel="stylesheet" type="text/css" href="modules/ui/css/style.css" />
    
        <!-- add your page styles here -->
        <!-- <link rel="stylesheet" type="text/css" href="css/" /> -->
        <link rel="stylesheet" type="text/css" href="css/common.css" />
    </head>

    Also at the end of this page, into body section we need to have some Script as a below.

    <script src="lib/jquery-2.2.0.min.js"></script>
    <script src="lib/mdtoast.js"></script>
    <script src="lib/cloadio.min.js"></script>
    <script src="lib/framework-a.min.js"></script>
    <script src="js/app.js"></script>
    <script src="lib/framework-b.min.js"></script>
    
    <!-- add your module scripts here -->
    <!-- <script src="modules/"></script> -->
    <script src="modules/ui/js/script.js"></script>
    <script src="modules/controls/js/script.js"></script>
    <script src="modules/form/js/script.js"></script>
    <script src="modules/table/js/script.js"></script>
    
    <!-- add your page scripts here -->
    <!-- <script src="js/"></script> -->
    <script src="js/home.js"></script>
  • CSS

    We have a file that named common.css, which should have the default stylesheet as follow.

    /* font */
    /* Webfont: poppins-regular */
    @font-face {
        font-family: 'poppins';
        src: url('../font/poppins-regular.woff2') format('woff2'), /* Modern Browsers */
                url('../font/poppins-regular.woff') format('woff'); /* Modern Browsers */
        font-style: normal;
        font-weight: 400;
    }
    /* Webfont: poppins-medium */
    @font-face {
        font-family: 'poppins';
        src: url('../font/poppins-medium.woff2') format('woff2'), /* Modern Browsers */
                url('../font/poppins-medium.woff') format('woff'); /* Modern Browsers */
        font-style: normal;
        font-weight: 500;
    }
    /* Webfont: poppins-semi-bold */
    @font-face {
        font-family: 'poppins';
        src: url('../font/poppins-bold.woff2') format('woff2'), /* Modern Browsers */
                url('../font/poppins-bold.woff') format('woff');
        font-style: normal;
        font-weight: 600;
    }
    
    /* general */
    * {
        box-sizing: border-box;
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        -webkit-tap-highlight-color: transparent;
    }
    *:focus {
        outline: none;
    }
    *:not(input):not(textarea) {
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }
    ::-webkit-scrollbar {
        display: none;
    }
    html {
        height: 100%;
    }
    body {
        background-color: #ffffff;
        height: 100%;
        font-family: 'poppins';
        font-size: 14px;
    }
    input[type='text'], input[type='number'], input[type='password'] {
        display: block;
        width: 100%;
        font-size: 16px;
        font-family: 'poppins';
        box-sizing: border-box;
        border-radius: 8px;
    }
    input::-webkit-outer-spin-button,
    input::-webkit-inner-spin-button {
        display: none;
    }
    input[type='password'] {
        padding: 6px 62px 6px 14px;
    }
    input::placeholder {
        font-size: 14px;
        font-family: 'poppins';
        font-weight: 400;
    }
    #overlay, #menuOverlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 471;
        animation: opacity 0.5s ease;
        display: none;
        background-color: #0000001a;
    }
    #overlay:not(.active), #menuOverlay:not(.active) {
        opacity: 0;
    }
    #overlay.active, #menuOverlay.active {
        opacity: 1;
    }
    #text > textarea{
        font-size: 16px;
        font-family: 'poppins';
    }
    #text .button {
        height: 54px;
        background-color: #1a8917;
        color: #ffffff;
        line-height: 54px;
        transition: opacity 0.7s ease;
        text-align: center;
        border-radius: 8px;
        font-size: 16px;
        font-weight: bold;
    }
    #box {
        background-color: #ffffff;
    }
    #box > .buttons, #options > .buttons {
        text-align: center;
        font-weight: bold;
    }
    #box > .buttons > .confirm > div {
        text-align: center;
        width: 49%;
        display: inline-block;
        padding-top: 20px;
        cursor: pointer;
    }
    #box > .buttons > .alert {
        padding-top: 20px;
        cursor: pointer;
    }
    #options > .buttons > .button {
        margin-top: 22px;
    }
    .page {
        height: 100%;
    }
  • App Events

    Into app.onDocumentReady section in app.js file, we should add the following codes in general. This codes are for handling the screen, connection config and something else.

    app.onDocumentReady = function () {
        // config connection
        ref.config('ping', {interval: 20000, retries: 2});
    
        if (platform.type === 'android') {
            platform.device.screen.width = Math.ceil(platform.device.screen.width);
            platform.device.screen.height = Math.ceil(platform.device.screen.height);
        }
        // apply screen safe area
        var jHtml = $('html');
        if (platform.device.screen.safeArea) {
            if (platform.type === 'android') {
                platform.device.screen.safeArea.width = Math.ceil(platform.device.screen.safeArea.width);
                platform.device.screen.safeArea.height = Math.ceil(platform.device.screen.safeArea.height);
                platform.device.screen.safeArea.origin.x = Math.ceil(platform.device.screen.safeArea.origin.x);
                platform.device.screen.safeArea.origin.y = Math.ceil(platform.device.screen.safeArea.origin.y);
            }
            app.local.safeWindow = platform.device.screen.safeArea;
            jHtml.css('margin-top', platform.device.screen.safeArea.origin.y + 'px');
            jHtml.css('max-height', platform.device.screen.safeArea.height + 'px');
            var hd = platform.device.screen.height - platform.device.screen.safeArea.height; // height difference
            jHtml.css('height', 'calc(100% - ' + hd + 'px)');
    
            app.local.sab = hd - platform.device.screen.safeArea.origin.y; // safe area bottom
        }
        else {
            app.local.safeWindow = platform.device.screen;
            jHtml.css('height', '100%');
        }
    };

    The app.onSynced codes are for after syncing user and CloadIO, which events and methods should be running.

    // on sync event
    // called when time synced with cloadio
    app.onSynced = function () {
        var next = function () {
            if (app.local.onSyncCompleted.length) {
                for (var i in app.local.onSyncCompleted)
                    app.local.onSyncCompleted[i]();
                app.local.onSyncCompleted = [];
            }
            app.local.synced = true;
        };
    };

    The app.auth.onCompleted after user authenticated, this event will be called and the codes run.

    // called when user authenticated
    app.auth.onCompleted = function () {
        if (app.auth.user.key)
            homePage.show();
    };

Loading Page

This page is when the app is loading and this page will be appear at first.

  • HTML

    Into index.html, at the body secction, should add the follow codes. This page is first page will be load in the application.

    <div class="page loading">
        <span>loading...</span>
    </div>
  • Notice

    Pay attention the page class name is a FrameworkJS keyword and should be used for every new page that we want to have in the project. For more information about page please see the Page API.

  • CSS

    We should make a sepreted stylesheet file into CSS folder with loading name and include that into index.html file.

    So we will add the loading styles into loading.css file as you can see in the follow.

    .page.loading {
        position: absolute;
        top: 50%;
        right: 50%;
        transform: translate(50%, -100%);
        height: 23px;
    }
    .page.loading > span {
        font-size: 16px;
        font-weight: bold;
        color: #1a8917;
    }
  • JS

    There is no JS codes for this page, just we need to add default JS page codes.

    var loadingPage = new Page('loading', function () {
        // after page shown
        
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });
  • App Events

    Into app.onDocumentReady section in app.js file, we should add the following code. This code will shown the loading page after run the application for the first time.

    app.onDocumentReady = function () {
        loadingPage.show();
    };

Sign In Page

This page is for sign in with the email account in the app.

Notice

The adding HTML codes order is important, try to obey it.

  • HTML

    In order to make sign in page, add the below HTML codes into body section of index.htmlfile.

    <div class="page signIn">
        <div class="header">
            <div class="title">Sign In</div>
        </div>
        <div class="content">
            <div class="email">
                <input type="text" placeholder="Email Address">
                <div class="message"></div>
            </div>
            <div class="pass">
                <input type="password" placeholder="Password">
                <div class="display">
                    <img src="img/showPass.svg">
                </div>
                <div class="message">
                    <div class="info">At least 4 characters password</div>
                </div>
            </div>
            <div class="button">Sign in</div>
        </div>
        <div class="footer">
            <div class="noAccount">Don't have an account?</div>
            <div class="signUp">Sign Up</div>
        </div>
    </div>
  • CSS

    Add the following CSS codes into signIn.css file.

    .page.signIn > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 68px;
    }
    .page.signIn > .header > .title {
        line-height: 68px;
        font-size: 18px;
        font-weight: 600;
        text-align: center;
    }
    .page.signIn > .content {
        position: absolute;
        top: 68px;
        right: 0;
        left: 0;
        bottom: 80px;
        padding-top: 32px;
        padding-right: 32px;
        padding-left: 32px;
        overflow-y: auto;
    }
    .page.signIn > .content > .pass {
        position: relative;
    }
    .page.signIn > .content > div > input {
        margin-bottom: 16px;
        padding: 13.5px 24px;
        border: 1px solid #b6bcb6;
    }
    .page.signIn > .content > .pass > input {
        padding-right: 68px;
    }
    .page.signIn > .content > div > input:focus {
        box-shadow: 0px 0px 0px 1px #878b87 inset;
    }
    .page.signIn > .content > div > input::placeholder {
        font-size: 16px;
        color: #b6bcb6;
    }
    .page.signIn > .content > .pass > .display {
        position: absolute;
        top: 0;
        right: 0;
        padding: 19px 24px;
        cursor: pointer;
    }
    .page.signIn > .content > .pass > .display > img {
        width: 20px;
        display: block;
    }
    .page.signIn > .content > div > .message {
        text-align: left;
        padding-left: 24px;
        line-height: 22px;
        font-size: 14px;
    }
    .page.signIn > .content > div > .message > div:first-child {
        margin-top: -8px;
    }
    .page.signIn > .content > div > .message > div:last-child {
        margin-bottom: 16px;
    }
    .page.signIn > .content > div > .message > .error {
        color: #f23002;
    }
    .page.signIn > .content > div > .message > .info {
        color: #c0c3d2;
    }
    .page.signIn > .content > .button {
        height: 54px;
        background-color: #1a8917;
        color: #ffffff;
        line-height: 54px;
        transition: opacity 0.7s ease;
        text-align: center;
        border-radius: 8px;
        font-weight: bold;
    }
    .page.signIn > .footer {
        position: absolute;
        right: 32px;
        bottom: 32px;
        left: 32px;
        text-align: center;
    }
    .page.signIn > .footer > .noAccount {
        margin-bottom: 8px;
    }
    .page.signIn > .footer > .signUp {
        font-weight: bold;
    }
  • Images

    Two suitable icons for hiding and showing the password should be added to images folder.
    You can download them form here.

  • Modules

    In this page we use the ui module, it should be placed in the modules folder and then included in index.html, as we did it in the General Information section.

    Notice

    For more information about ui module, visit the ui module.

  • JS

    Add the following JS codes into signIn.js file. It is saved in the JS folder.

    var signInPage = new Page('signIn', function () {
        // after page shown
    
        // define variables
        var jPage = $('.page.signIn'),
        jContent = jPage.children('.content'),
        jEmail = jContent.children('.email',)
        jPass = jContent.children('.pass'),
        jFooter = jPage.children('.footer');
        
        jPass.children('.display').click(function () {
            var jInput = jPass.children('input');
            if (jInput.attr('type') === 'password') {
                jInput.attr('type', 'text');
                jPass.find(' > .display > img').attr('src', 'img/hidePass.svg');
            }
            else {
                jInput.attr('type', 'password');
                jPass.find(' > .display > img').attr('src', 'img/showPass.svg');
            }
        });
    
        jContent.find(' > .email > input').off('keyup paste').on('keyup paste', function () {
            var jSelf = $(this);
            setTimeout(function () {
                if (jSelf.val() && jContent.find(' > .pass > input').val())
                    jContent.children('.button').addClass('active');
                else
                    jContent.children('.button').removeClass('active');
            }, 1);
        });
    
        jContent.find(' > .pass > input').off('keyup paste').on('keyup paste', function () {
            var jSelf = $(this);
            setTimeout(function () {
                if (jSelf.val() && jContent.find(' > .email > input').val())
                    jContent.children('.button').addClass('active');
                else
                    jContent.children('.button').removeClass('active');
            }, 1);
        });
    
        // sign in button
        app.ui.button(jContent, 'ripple', {
            onPush: function (jButton) {
                // reset
                jContent.find('div > .message').html('');
                
                var go = true, email = jEmail.children('input').val().toLowerCase().trim();
                if (!email) {
                    jEmail.children('.message').html('<div class="error">Email address cannot be empty.</div>');
                    go = false;
                }
                
                var pass = jPass.children('input').val();
                if (!pass) {
                    jPass.children('.message').html('<div class="error">Password cannot be empty.</div>');
                    go = false;
                }
                if (go)
                    ref.run('signIn', {appVersion: app.version.current, email: email, pass: pass}, function (error, credential) {
                        if (error) {
                            if (error === 'username or password isn’t correct')
                                mdtoast('Username or password isn’t correct.', {type: mdtoast.ERROR});
                            else {
                                console.error('running sign in cloud side function failed', error);
                                mdtoast('Sign in failed. Please try again.', {type: mdtoast.ERROR});
                            }
                        }
                        else
                            ref.authWithToken(credential.t, true, function (error) {
                                if (error) {
                                    if (error === 'credential failed')
                                        mdtoast('Email or password is incorrect. Please try again.', {type: mdtoast.ERROR});
                                    else
                                        mdtoast('An error occured. Please try again.', {type: mdtoast.ERROR});
                                }
                                else {
                                    var userKey = credential.p.sub.split('@')[0];
                                    app.auth.syncUser(userKey, function (error, value) {
                                        if (error)
                                            mdtoast('User synchronization failed.', {type: mdtoast.ERROR});
                                        else {
                                            // save email and username on device
                                            var user = localStorage.user ? JSON.parse(localStorage.user) : {};
                                            user.status = 'authenticated';
                                            user.key = app.auth.user.key;
                                            if (app.auth.user.email)
                                                user.email = app.auth.user.email;
                                            localStorage.user = JSON.stringify(user);
                                            
                                            var user = $.extend(true, {}, app.auth.user);
                                            delete user.key;
                                            ref.child(app.cloadio.puip + '/' + app.auth.user.key).set(user, function (error) {
                                                if (error)
                                                    console.error('set user failed', error);
                                                else
                                                    app.auth.onCompleted && app.auth.onCompleted();
                                            });
                                        }
                                    });
                                }
                            });
                    });
            }
        });
    
        // sign up
        jFooter.children('.signUp').click(function() {
            signUpPage.show();
        });
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });
  • CloadIO Function

    It should be placed in CloadIO side and the name of this is signIn.js, we can save it in the src folder siblings the client folder.

    // sign in
    database.connect(function (error, mongodb) {
        if (error)
            send('connect to database failed');
        else {
            var or = [{username: args.email}], o = {};
            o['email.value'] = args.email;
            or.push(o);
            mongodb.collection('users').find({
                $or: or,
                pass: args.pass
            }).toArray(function (error, users) {
                if (error)
                    send('check users failed');
                else if (users.length)
                    // get application secret value
                    settings.get('secret', function (error, secret) {
                        if (error)
                            send('sign user failed');
                        else {
                            // make payload
                            var payload = {iss: 'cloadio', sub: users[0]['#k'] + '@' + appName, iat: Date.now()};
    
                            // send token and payload to client
                            send(null, {t: jwt.encode(payload, secret), p: payload});
                        }
                    });
                else
                    send('username or password isn’t correct');
            });
        }
    });

    Notice

    For more information about CloadIO functions, You can see the CloadIO Function section.

Sign Up Page

This page is for sign up users intoapp.

  • HTML

    In order to make sign up page, add the below HTML codes into body section of index.htmlfile.

    <div class="page signUp">
        <div class="header">
            <div class="left">
                <div class="button"></div>
            </div>
            <div class="right">
                <div class="title">Sign Up</div>
            </div>
        </div>
        <div class="content">
            <div class="email">
                <input type="text" placeholder="Email Address">
                <div class="message"></div>
            </div>
            <div class="pass">
                <input type="password" placeholder="Password">
                <div class="display">
                    <img src="img/showPass.svg">
                </div>
                <div class="message">
                    <div class="info">At least 4 characters password</div>
                </div>
            </div>
            <div class="button">Sign up</div>
        </div>
    </div>
  • CSS

    Add the following CSS codes into signUp.css file.

    .page.signUp > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 68px;
        overflow: hidden;
    }
    .page.signUp > .header > .left {
        float: left;
        height: 68px;
    }
    .page.signUp > .header > .left > .button {
        padding: 26px 25px;
    }
    .page.signUp > .header > .left > .button > img {
        display: block;
        height: 16px;
    }
    .page.signUp > .header > .right {
        padding-left: 68px;
        padding-right: 68px;
    }
    .page.signUp > .header > .right > .title {
        line-height: 68px;
        font-size: 18px;
        font-weight: 600;
        text-align: center;
    }
    .page.signUp > .content {
        position: absolute;
        top: 68px;
        right: 0;
        left: 0;
        bottom: 0;
        padding: 32px;
        overflow-y: auto;
    }
    .page.signUp > .content > .pass {
        position: relative;
    }
    .page.signUp > .content > div > input {
        margin-bottom: 16px;
        padding: 13.5px 24px;
        border: 1px solid #b6bcb6;
    }
    .page.signUp > .content > .pass > input {
        padding-right: 68px;
    }
    .page.signUp > .content > div > input:focus {
        box-shadow: 0px 0px 0px 1px #878b87 inset;
    }
    .page.signUp > .content > div > input::placeholder {
        font-size: 16px;
        color: #b6bcb6;
    }
    .page.signUp > .content > .pass > .display {
        position: absolute;
        top: 0;
        right: 0;
        padding: 19px 24px;
        cursor: pointer;
    }
    .page.signUp > .content > .pass > .display > img {
        width: 20px;
        display: block;
    }
    .page.signUp > .content > div > .message {
        text-align: left;
        padding-left: 24px;
        line-height: 22px;
        font-size: 14px;
    }
    .page.signUp > .content > div > .message > div:first-child {
        margin-top: -8px;
    }
    .page.signUp > .content > div > .message > div:last-child {
        margin-bottom: 16px;
    }
    .page.signUp > .content > div > .message > .error {
        color: #f23002;
    }
    .page.signUp > .content > div > .message > .info {
        color: #c0c3d2;
    }
    .page.signUp > .content > .button {
        height: 54px;
        background-color: #1a8917;
        color: #ffffff;
        line-height: 54px;
        transition: opacity 0.7s ease;
        text-align: center;
        border-radius: 8px;
        font-weight: bold;
    }
  • Images

    For make a back button, and show or hide password we need to add suitable icons in img folder.
    You can download them form here.

  • Modules

    In this page we use the ui module, it should be placed in the modules folder and then included in index.html, as we did it in the General Information section.

    Notice

    For more information about ui module, visit the ui module.

  • JS

    Add the following JS codes into signUp.js file.

    var signUpPage = new Page('signUp', function () {
        // after page shown
    
        // define variables
        var jPage = $('.page.signUp'),
        jHeader = jPage.children('.header'),
        jContent = jPage.children('.content'),
        jPass = jContent.children('.pass'),
        model = {
            email: {
                inputType:'email',
                text:'Email',
                syntax: {
                    value: '^\\\\S+@\\\\S+\\\\.\\\\S+$'
                },
                display: 'none'
            },
            pass: {
                text:'Password',
                display: 'none',
                syntax: {
                    value: '^.{4,64}$',
                    message: 'Password must contain at least 4 characters.'
                }
            }
        };
    
        // back
        app.ui.button(jHeader.children('.left'), 'ripple', {
            html: '<img src="img/backBlack.svg">',
            color: '#b6bcb6',
            onPush: function (jButton) {
                history.back();
            }
        });
    
        jPass.children('.display').click(function () {
            var jInput = jPass.children('input');
            if (jInput.attr('type') === 'password') {
                jInput.attr('type', 'text');
                jPass.find(' > .display > img').attr('src', 'img/hidePass.svg');
            }
            else {
                jInput.attr('type', 'password');
                jPass.find(' > .display > img').attr('src', 'img/showPass.svg');
            }
        });
    
        jContent.find(' > .email > input').off('keyup paste').on('keyup paste', function () {
            var jSelf = $(this);
            setTimeout(function () {
                if (jSelf.val() && jContent.find(' > .pass > input').val())
                    jContent.children('.button').addClass('active');
                else
                    jContent.children('.button').removeClass('active');
            }, 1);
        });
    
        jContent.find(' > .pass > input').off('keyup paste').on('keyup paste', function () {
            var jSelf = $(this);
            setTimeout(function () {
                if (jSelf.val() && jContent.find(' > .email > input').val())
                    jContent.children('.button').addClass('active');
                else
                    jContent.children('.button').removeClass('active');
            }, 1);
        });
    
        // next button
        app.ui.button(jContent, 'ripple', {
            onPush: function (jButton) {
                // reset
                jContent.find(' > div > .message').html('');
    
                var go = true, jEmail = jContent.children('.email'), email = jEmail.children('input').val().toLowerCase().trim();
                // if (model.email.syntax && !new RegExp(tools.decodeHtml(model.email.syntax.value)).test(email)) {
                    // jEmail.children('.message').html('<div class="error">Incorrect email address.</div>\
                    // <div class="info">example: email@sample.com</div>');
                    // go = false;
                // }
                
                var pass = jPass.children('input').val();
                if (model.pass.syntax && !new RegExp(tools.decodeHtml(model.pass.syntax.value)).test(pass)) {
                    jPass.children('.message').html('<div class="error">At least 4 characters password is required.</div>');
                    go = false;
                }
                if (go)
                    ref.run('signUp', {appVersion: app.version.current, email: email, pass: pass}, function (error, credential) {
                        if (error) {
                            if (error === 'email is taken before')
                                mdtoast('Entered email isn’t available. Please choose another one.', {type: mdtoast.ERROR});
                            else {
                                console.error('running sign in cloud side function failed', error);
                                mdtoast('Sign in failed. Please try again.', {type: mdtoast.ERROR});
                            }
                        }
                        else
                            ref.authWithToken(credential.t, true, function (error) {
                                if (error) {
                                    if (error === 'credential failed')
                                        mdtoast('Email or password is incorrect. Please try again.', {type: mdtoast.ERROR});
                                    else
                                        mdtoast('An error occured. Please try again.', {type: mdtoast.ERROR});
                                }
                                else {
                                    var userKey = credential.p.sub.split('@')[0];
                                    app.auth.syncUser(userKey, function (error, value) {
                                        if (error)
                                            mdtoast('User synchronization failed.', {type: mdtoast.ERROR});
                                        else {
                                            // save email on device
                                            var user = localStorage.user ? JSON.parse(localStorage.user) : {};
                                            user.status = 'authenticated';
                                            user.key = app.auth.user.key;
                                            if (app.auth.user.email)
                                                user.email = app.auth.user.email;
                                            localStorage.user = JSON.stringify(user);
                                            
                                            // save user on cloadio
                                            var user = $.extend(true, {}, app.auth.user);
                                            delete user.key;
                                            ref.child(app.cloadio.puip + '/' + app.auth.user.key).set(user, function (error) {
                                                if (error)
                                                    console.error('set user failed', error);
                                                else 
                                                    app.auth.onCompleted && app.auth.onCompleted();
                                            });
                                        }
                                    });
                                }
                            });
                    });
            }
        });
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });
  • CloadIO Function

    It should be placed in CloadIO side and the name of this is signUp.js, we can save it in the src folder siblings the client folder.

    // sign up
    database.connect(function (error, mongodb) {
        if (error)
            send('connect to database failed');
        else {
            // assign username
            var u = args.email.split('@')[0], check = function (username) {
                database.get(['clients'], {ob: 'v', c: 'username', e: username}, function (error, client) {
                    if (error)
                        send('get client failed');
                    else if (client)
                        check(u + (Math.floor(Math.random() * (9000) + 1000)));
                    else {
                        var query = {};
                        query['email.value'] = args.email;
                        mongodb.collection('users').find(query).count(function (error, count) {
                            if (error)
                                send('check email failed');
                            else if (count)
                                send('email is taken before');
                            else {
                                var verificationCode = Math.floor(1000 + Math.random() * 9000);
                                database.push(['users'], {
                                    authType: 'username',
                                    username: username,
                                    pass: args.pass,
                                    email: {
                                        value: args.email
                                    },
                                    createdAt: Date.now(),
                                    verificationCode: verificationCode
                                }, function (error, userKey) {
                                    if (error)
                                        send('push user failed');
                                    else
                                        database.set(['clients', userKey], {
                                            username: username,
                                            email: args.email
                                        }, function (error) {
                                            if (error)
                                                send('push client failed');
                                            else {
                                                // get application secret value
                                                settings.get('secret', function (error, secret) {
                                                    if (error)
                                                        send('sign user failed');
                                                    else {
                                                        // make payload
                                                        var payload = {iss: 'cloadio', sub: userKey + '@' + appName, iat: Date.now()};
    
                                                        // send token and payload to client
                                                        send(null, {t: jwt.encode(payload, secret), p: payload});
                                                    }
                                                });
                                            }
                                        });
                                });
                            }
                        });
                    }
                });
            };
            check(u);
        }
    });

    Notice

    For more information about how upload CloadIO functions, visit the CloadIO Function.

Home Page

This page is first page of the app after user login, and also its contains the list of uploaded posts.

  • HTML

    In order to make this page, add the below HTML codes into body section of index.htmlfile.

    <div class="page home">
        <div id="menuOverlay"></div>
        <div class="menu">
            <div class="header">
                <img src="img/headerMenu.jpg">
            </div>
            <div class="content">
                <ul>
                    <li class="button" key="profile">Profile</li>
                    <li class="button" key="addPost">Add Post</li>
                    <li class="button" key="signOut">Sign Out</li>
                </ul>
            </div>
            <div class="footer">
                <img src="img/codinicLogo.png">
            </div>
        </div>
        <div class="header">
            <div class="left">
                <div class="button"></div>
            </div>
            <div class="right">
                <div class="title">All Posts</div>
            </div>
        </div>
        <div class="content">
            <div class="tableWrapper"></div>
        </div>
        <div class="footer">
            <div class="button navi" key="home">Home</div>
            <div class="button navi" key="addPost">Add Post</div>
            <div class="button navi" key="profile">Profile</div>
        </div>
    </div>
  • CSS

    In the home.css file, we add the below styles and include it into index.html file.

    .page.home > .menu {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 270px;
        left: -280px;
        background: #fff;
        color: #6d6c6c;
        z-index: 1003;
        box-shadow: 0 0 11px 0 rgb(0 0 0 / 47%);
        -webkit-box-shadow: 0 0 11px 0 rgb(0 0 0 / 47%);
        -moz-box-shadow: 0 0 11px 0 rgba(0, 0, 0, 0.47);
        transition: transform 400ms ease-out;
    }
    .page.home > .menu.active { 
        transform: translateX(280px);
    }
    .page.page.home > .menu > .header {
        width: 270px;
        height: 140px;
    }
    .page.page.home > .menu > .header > img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }
    .page.page.home > .menu > .content {
        position: absolute;
        top: 140px;
        right: 0;
        bottom: 92px;
        left: 0;
        border-top: 1px solid #cdcdcd;
    }
    .page.page.home > .menu > .content > ul {
        list-style: none;
        padding: 0;
        margin: 0;
    }
    .page.page.home > .menu > .content > ul > li {
        font-weight: bold;
        padding-right: 32px;
        padding-left: 32px;
        height: 53px;
        line-height: 53px;
        border-bottom: 1px solid #eeeeee;
    }
    .page.home > .menu > .footer {
        position: absolute;
        right: 0;
        bottom: 0;
        left: 0;
    }
    .page.home > .menu > .footer > img {
        width: 150px;
        object-fit: cover;
        margin: 32px auto;
        display: block;
    }
    .page.home > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 50px;
        overflow: hidden;
        box-shadow: 0px 0px 6px 0px #ddd;
    }
    .page.home > .header > .left {
        float: left;
        height: 50px;
    }
    .page.home > .header > .left > .button {
        padding: 17px 14.1px;
    }
    .page.home > .header > .left > .button > img {
        display: block;
        height: 16px;
    }
    .page.home > .header > .right {
        float: right;
        padding-right: 22px;
    }
    .page.home > .header > .right > .title {
        line-height: 50px;
        font-size: 17px;
        font-weight: 600;
        text-align: center;
        color: #1a8917;
    }
    .page.home > .content {
        position: absolute;
        top: 50px;
        right: 0;
        left: 0;
        bottom: 0;
        overflow-y: auto;
    }
    .page.home > .content > .tableWrapper > .fTable > .content > table {
        padding-top: 7.5px;
        padding-bottom: 57.5px;
    }
    .page.home > .content > .tableWrapper > .fTable > .content > table > thead {
        display: none;
    }
    .page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td {
        padding: 7.5px 15px;
    }
    .page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .left {
        position: relative;
        background-color: #efefef;
        float: left;
        width: 104px;
        height: 104px;
        border-radius: 8px;
    }
    .page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .left > img[src='img/imageWhite.svg'] {
        width: 24px;
        margin: 61px auto;
        display: block;
    }
    .page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .left > img:not([src='img/imageWhite.svg']) {
        object-fit: cover;
        opacity: 0;
        transition: opacity 0.6s ease;
        width: 100%;
        height: 100%;
        border-radius: 8px;
    }
    .page.page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .right {
        margin-top: 9px;
        margin-left: 16px;
        float: left;
        width: calc(100% - 120px);
    }
    .page.page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .right > .title {
        font-weight: bold;
        font-size: 16px;
    }
    .page.page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .right > .description {
        color: #454545;
    }
    .page.page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .right > .info {
        display: table;
        width: 100%;
        font-size: 12px;
        color: #454545;
    }
    .page.page.home > .content > .tableWrapper > .fTable > .content > table > tbody > tr > td > .right > .info > div {
        display: table-cell;
    }
    .page.home > .footer {
        position: absolute;
        right: 0;
        bottom: 0;
        left: 0;
        box-shadow: 0px 0px 6px 0px #ddd;
        background-color: #ffffff;
    }
    .page.home > .footer > div {
        float: left;
        text-align: center;
        padding-top: 15px;
        padding-bottom: 15px;
        font-weight: bold;
    }
  • Modules

    In this page we used the table and maybe other added modules, these should be placed in the modules folder and then included in index.html, as we did it in the General Information section.

    Notice

    For more information about table module, visit the table module.

  • JS

    Add the js codes into home.js file and include in the script section in the index.html file.

    var homePage = new Page('home', function () {
        // after page shown
    
        var jPage = $('.page.home'),
        jHeader = jPage.children('.header'),
        jContent = jPage.children('.content'),
        jFooter = jPage.children('.footer'),
        model = {
            image: {
                inputType: 'image',
                text: 'Upload image'
            },
            title: {
                inputType: 'text',
                text: 'Title',
                placeholder: 'Enter your title here',
                required: true
            },
            description: {
                inputType: 'longText',
                text: 'Description',
                placeholder: 'Enter your description  here',
                required: true
            },
            createdAt: {
                inputType: 'time',
                text: 'Date',
                display: 'inTable'
            },
            createdBy: {
                text: 'Author',
                display: 'inTable'
            }
        };
    
        // handle menu
        var jMenu = jPage.children('.menu'), jOverlay = $('#menuOverlay');
        app.ui.button(jHeader.children('.left'), 'ripple', {
            html: '<img src="img/menuBlack.svg">',
            color: '#b6bcb6',
            onPush: function () {
                if (jMenu.hasClass('active')) {
                    jMenu.removeClass('active');
                    jOverlay.css('display', 'none');
                    jOverlay.css('opacity', '0');
                }
                else {
                    jMenu.addClass('active');
                    jOverlay.css('display', 'block');
                    jOverlay.css('opacity', '1');
    
                    var jMenuContent = jMenu.find(' > .content > ul');
    
                    if (!app.auth.user.key)
                        jMenuContent.children('li[key="signOut"]').hide();
    
                    jMenuContent.children('.button').each(function (i, o) {
                        var jSelf = $(o), key = jSelf.attr('key');
                        app.ui.button(jMenuContent, 'ripple', {
                            color: '#b6bcb6',
                            key: key,
                            onPush: function () {
                                jOverlay.click();
                                
                                var key = jSelf.attr('key');
                                if (key === 'profile')
                                    profilePage.show();
                                else if (key === 'addPost') {
                                    if (app.auth.user.key)
                                        newPostPage.show();
                                    else {
                                        app.controls.box('confirm', null, 'You are not signed in, do you want to sign up now?', 'center', function(result) {
                                            if (result) {
                                                signUpPage.show();
                                            }
                                        });
                                    }
                                }
                                else if (key === 'signOut') {
                                    app.controls.box('confirm', null, 'Are you sure?', 'center', function(result) {
                                        if (result) {
                                            mdtoast('Checking out...');
                                            setTimeout(function () {
                                                app.auth.clear();
                                            }, 500);
                                        }
                                    });
                                }
                            }
                        });
                    });
                }
            }
        });
        jOverlay.click(function () {
            jMenu.removeClass('active');
            jOverlay.hide();
        });
    
        new Table(model, jContent.children('.tableWrapper'), '/posts', {
            pageCapacity: 25,
            filter: false,
            onRecordsReceived: function (records) {
                // cache
                if (app.local.posts)
                    $.extend(app.local.posts, records);
                else
                    app.local.posts = records;
            },
            onRecordRendered: function (recordKey, record, jRecord) {
                jRecord.html('<td class="button">\
                    <div class="left">\
                        <img onload="this.style.opacity = 1">\
                    </div>\
                    <div class="right">\
                        <div class="title">' + record.title + '</div>\
                        <div class="description">' + (record.description.substr(0, 40) + '...') + '</div>\
                        <div class="info">\
                            <div class="createdBy"><strong>' + model['createdBy'].text + ': </strong><span>...</span></div>\
                            <div class="createdAt"><strong>' + model['createdAt'].text + ': </strong> ' + new Date(parseInt(record.createdAt)).toLocaleDateString() + '</div>\
                        </div>\
                    </div>\
                </td>');
                var jImg = jRecord.find(' > td > .left > img');
                if (record.image) {
                    var url = 'posts/' + recordKey + '/image/binary.' + record.image.ext;
                    if (app.local.images[url])
                        jImg.attr('src', app.local.images[url]);
                    else {
                        var xhr = new XMLHttpRequest();
                        xhr.onload = function () {
                            var reader = new FileReader();
                            reader.onloadend = function () {
                                app.local.images[url] = reader.result;
                                jImg.attr('src', reader.result);
                            };
                            reader.readAsDataURL(xhr.response);
                        };
                        xhr.open('GET', url);
                        xhr.responseType = 'blob';
                        xhr.send();
                    }
                }
    
                var jCreatedBy = jRecord.find(' > td > .right > .info > .createdBy');
                if (record.createdBy) {
                    ref.child('clients/' + record.createdBy).get(function (error, client) {
                        if (error)
                            console.error('get client value failed', error);
                        else if (client)
                            jCreatedBy.children('span').html(client.username.substr(0, 10) + '...');
                    });
                }
    
                app.ui.button(jRecord, 'ripple', {
                    color: '#9c9c9c',
                    onPush: function () {
                        if (record.createdBy === app.auth.user.key)
                            editPostPage.show(recordKey);
                        else
                            postDetailPage.show(recordKey);
                    }
                });
            }
        }).render();
    
        jFooter.children('.navi[key="home"]').css('color', '#1a8917');
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });
  • App Functions

    Because the footer section is common in some pages, we are going to add the codes into app.js file, on the onDocumentReady section.

    It is better to add the below codes after screen codes and before showing the loading page.

    // render footer buttons
    for (var pageKey in app.pages) {
        var jPage = $('.page.' + app.pages[pageKey].name);
        var jFooter = jPage.children('.footer'), l = jFooter.children('.navi').length, w = jFooter.find(' > .navi:first-child > img').width(), p = (app.local.safeWindow.width - (l * w)) / ((l * 2) + 2);
        jPage.find(' > .footer > .navi').each(function (i, o) {
            var jSelf = $(o), key = jSelf.attr('key');
    
            if (jSelf.hasClass('button'))
                app.ui.button(jFooter, 'none', {
                    classes: 'navi',
                    key: key,
                    color: '#b6bcb6',
                    onPush: function () {
                        if (key === 'home')
                            homePage.show();
                        else if (key === 'profile') {
                            if (app.auth.user.key)
                                profilePage.show();
                            else {
                                app.controls.box('confirm', null, 'You are not signed in, do you want to sign in now?', 'center', function(result) {
                                    if (result) {
                                        signInPage.show();
                                    }
                                });
                            }
                        }
                        else if (key === 'addPost') {
                            if (app.auth.user.key)
                                newPostPage.show();
                            else {
                                app.controls.box('confirm', null, 'You are not signed in, do you want to sign up now?', 'center', function(result) {
                                    if (result) {
                                        signInPage.show();
                                    }
                                });
                            }
                        }
                    }
                });
    
            var width;
            if (i === 0) {
                width = 3 * p;
                jSelf.css('padding-left', p);
            }
            else if (i === (l - 1)) {
                width = 3 * p;
                jSelf.css('padding-right', p);
            }
            else
                width = 2 * p;
            width += w;
            jSelf.css('width', width);
            jSelf.children('.circle').css('width', jSelf.height());
        });
    }

Profile Page

This page is for viewing user information in the application.

  • HTML

    In order to make profile page, add the below HTML codes into body section of index.htmlfile.

    <div class="page profile">
        <div class="header">
            <div class="left">
                <div class="button"></div>
            </div>
            <div class="right">
                <div class="title">Profile</div>
            </div>
        </div>
        <div class="content">
            <div class="formWrapper"></div>
        </div>
        <div class="footer">
            <div class="button navi" key="home">Home</div>
            <div class="button navi" key="addPost">Add Post</div>
            <div class="button navi" key="profile">Profile</div>
        </div>
    </div>
  • CSS

    This codes will add into profile.css file, into CSS folder. Also it should be included into index.html, at the head section.

    .page.profile > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 50px;
        overflow: hidden;
        box-shadow: 0px 0px 6px 0px #ddd;
    }
    .page.profile > .header > .left {
        float: left;
        height: 68px;
    }
    .page.profile > .header > .left > .button {
        padding: 17px 16px;
    }
    .page.profile > .header > .left > .button > img {
        display: block;
        height: 16px;
    }
    .page.profile > .header > .right {
        float: right;
        padding-right: 22px;
    }
    .page.profile > .header > .right > .title {
        line-height: 50px;
        font-size: 17px;
        font-weight: 600;
        text-align: center;
        color: #1a8917;
    }
    .page.profile > .content {
        position: absolute;
        top: 50px;
        right: 0;
        left: 0;
        bottom: 50px;
        overflow-y: auto;
        padding-top: 32px;
        padding-right: 32px;
        padding-left: 32px;
    }
    .page.profile > .content > .formWrapper > .fForm > .fields > .field > .key {
        font-size: 16px;
        display: inline-block;
        padding-bottom: 15px;
        font-weight: bold;
    }
    .page.profile > .content > .formWrapper > .fForm > .fields > .field > .value {
        margin-bottom: 32px;
        padding: 13.5px 24px;
        border: 1px solid #ececec;
        border-radius: 8px;
        background-color: #ececec;
    }
    .page.profile > .footer {
        position: absolute;
        right: 0;
        bottom: 0;
        left: 0;
        box-shadow: 0px 0px 6px 0px #ddd;
        background-color: #ffffff;
    }
    .page.profile > .footer > div {
        float: left;
        text-align: center;
        padding-top: 15px;
        padding-bottom: 15px;
        font-weight: bold;
    }
  • Images

    The needed icons should be added to img folder. You can download them form here.

  • Modules

    In this page we used the form and ui modules, these should be placed in the modules folder and then included in index.html, as we did it in the General Information section.

    Notice

    • For more information about form module, visit the form module.
    • For more information about ui module, visit the ui module.

  • JS

    This codes will add into profile.js file, into JS folder. Also it should be included into index.html, in the bottom section.

    var profilePage = new Page('profile', function () {
        // after page shown
    
        // define variables
        var jPage = $('.page.profile'),
        jHeader = jPage.children('.header'),
        jContent = jPage.children('.content'),
        jEmail = jContent.children('.email'),
        jPass = jContent.children('.pass'),
        jFooter = jPage.children('.footer'),
        model = {
            email: {
                inputType:'email',
                text:'Email',
                syntax: {
                    value: '^\\\\S+@\\\\S+\\\\.\\\\S+$'
                }
            },
            username: {
                text:'user Name'
            }
        };
    
        // back
        app.ui.button(jHeader.children('.left'), 'ripple', {
            html: '<img src="img/backBlack.svg">',
            color: '#b6bcb6',
            onPush: function (jButton) {
                history.back();
            }
        });
    
        // render form
        new Form ('view', model, jContent.children('.formWrapper'), app.auth.user, '/clients/' + app.auth.user.key).render();
    
        jFooter.children('.navi[key="profile"]').css('color', '#1a8917');
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });

New Post Page

This page is for add new post by user.

  • HTML

    In order to make newPost page, add the below HTML codes into body section of index.htmlfile.

    <div class="page newPost">
        <div class="header">
            <div class="left">
                <div class="button"></div>
            </div>
            <div class="right">
                <div class="title">New Post</div>
            </div>
        </div>
        <div class="content">
            <div class="formWrapper"></div>
            <div class="button" key="save">Save</div>
        </div>
        <div class="footer">
            <div class="button navi" key="home">Home</div>
            <div class="button navi" key="addPost">Add Post</div>
            <div class="button navi" key="profile">Profile</div>
        </div>
    </div>
  • CSS

    The styles should be added into newPost.css file, and also add to head section of index.html.

    .page.newPost > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 50px;
        overflow: hidden;
        box-shadow: 0px 0px 6px 0px #ddd;
    }
    .page.newPost > .header > .left {
        float: left;
        height: 68px;
    }
    .page.newPost > .header > .left > .button {
        padding: 17px 16px;
    }
    .page.newPost > .header > .left > .button > img {
        display: block;
        height: 16px;
    }
    .page.newPost > .header > .right {
        float: right;
        padding-right: 22px;
    }
    .page.newPost > .header > .right > .title {
        line-height: 50px;
        font-size: 17px;
        font-weight: 600;
        text-align: center;
        color: #1a8917;
    }
    .page.newPost > .content {
        position: absolute;
        top: 50px;
        right: 0;
        left: 0;
        bottom: 50px;
        overflow-y: auto;
    }
    .page.newPost > .content > .formWrapper {
        padding-top: 15px;
        padding-bottom: 40px;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field {
        padding-right: 32px;
        padding-left: 32px;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field > img[src='img/imageWhite.svg'] {
        width: 24px;
        margin: 61px auto;
        display: block;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field > img:not([src='img/imageWhite.svg']) {
        object-fit: cover;
        opacity: 0;
        transition: opacity 0.6s ease;
        width: 100%;
        height: 100%;
        border-radius: 8px;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field > label {
        font-size: 16px;
        display: inline-block;
        padding-bottom: 15px;
        font-weight: bold;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field > input {
        margin-bottom: 32px;
        padding: 13.5px 24px;
        border: 1px solid #b6bcb6;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field.image > .buttons {
        margin-bottom: 32px;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field.image > .buttons > .large {
        position: relative;
    }
    .page.newPost > .content > .formWrapper > .fForm > .fields > .field.image > .buttons > .large > .remove {
        color: #ffffff;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-weight: bold;
        background-color: red;
        border-radius: 5px;
        padding: 2px 6px;
    }
    .page.newPost > .content > .button {
        background-color: #1a8917;
        height: 54px;
        color: #ffffff;
        line-height: 54px;
        transition: opacity 0.7s ease;
        text-align: center;
        border-radius: 8px;
        font-weight: bold;
        margin-right: 32px;
        margin-bottom: 32px;
        margin-left: 32px;
    }
    .page.newPost > .footer {
        position: absolute;
        right: 0;
        bottom: 0;
        left: 0;
        box-shadow: 0px 0px 6px 0px #ddd;
        background-color: #ffffff;
    }
    .page.newPost > .footer > div {
        float: left;
        text-align: center;
        padding-top: 15px;
        padding-bottom: 15px;
        font-weight: bold;
    }
  • JS

    The JS codes should be added into newPost.js file, and also add to bottom section of index.html.

    var newPostPage = new Page('newPost', function () {
        // after page shown
    
        // define variables
        var path, data, jPage = $('.page.newPost'),
        jHeader = jPage.children('.header'),
        jContent = jPage.children('.content'),
        jFooter = jPage.children('.footer'),
        model = {
            image: {
                inputType: 'image',
                text: 'Upload image'
            },
            title: {
                inputType: 'text',
                text: 'Title',
                placeholder: 'Enter your title here',
                required: true
            },
            description: {
                inputType: 'longText',
                text: 'Description',
                placeholder: 'Enter your description  here',
                required: true
            },
            createdAt: {
                inputType: 'time',
                text: 'Date',
                display: 'inTable'
            },
            createdBy: {
                text: 'Author',
                display: 'inTable'
            }
        };
    
        // back
        app.ui.button(jHeader.children('.left'), 'ripple', {
            html: '<img src="img/backBlack.svg">',
            color: '#b6bcb6',
            onPush: function (jButton) {
                history.back();
            }
        });
    
        // render form
        var form = new Form('create', model, jContent.children('.formWrapper'), null, '/posts');
        form.render();
    
        // save
        app.ui.button(jContent, 'ripple', {
            color: '#b6bcb6',
            onPush: function (jButton) {
                form.save(function (error) {
                    if (error) {
                        if (error.on === 'empty')
                            mdtoast(error.field.text + ' cannot be empty.', {type: mdtoast.ERROR});
                        else if (error.on === 'validation' || error.on === 'execution')
                            mdtoast(error.remote ? error.text : error.field.text + ' isn’t valid.', {type: mdtoast.ERROR});
                        else {
                            if (error.on === 'save')
                                console.error('save failed', error.text);
                            mdtoast('Save failed.', {type: mdtoast.ERROR});
                        }
                    }
                    else {
                        mdtoast('Save blog successful.', {type: mdtoast.SUCCESS});
                        homePage.show();
                    }
                });
            }
        });
    
        jFooter.children('.navi[key="addPost"]').css('color', '#1a8917');
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });

Edit Post Page

This page is for editing user-added posts.

  • HTML

    In order to make editPost page, add the below HTML codes into body section of index.htmlfile.

    <div class="page editPost">
        <div class="header">
            <div class="left">
                <div class="button"></div>
            </div>
            <div class="right">
                <div class="title">Edit Post</div>
            </div>
        </div>
        <div class="content">
            <div class="formWrapper"></div>
            <div class="btns">
                <div class="button" key="save">Save</div>
                <div class="button" key="delete">Delete</div>
            </div>
        </div>
        <div class="footer">
            <div class="button navi" key="home">Home</div>
            <div class="button navi" key="addPost">Add Post</div>
            <div class="button navi" key="profile">Profile</div>
        </div>
    </div>
  • CSS

    The styles should be added into editPost.css file, and also add to head section of index.html.

    .page.editPost > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 50px;
        overflow: hidden;
        box-shadow: 0px 0px 6px 0px #ddd;
    }
    .page.editPost > .header > .left {
        float: left;
        height: 68px;
    }
    .page.editPost > .header > .left > .button {
        padding: 17px 16px;
    }
    .page.editPost > .header > .left > .button > img {
        display: block;
        height: 16px;
    }
    .page.editPost > .header > .right {
        float: right;
        padding-right: 22px;
    }
    .page.editPost > .header > .right > .title {
        line-height: 50px;
        font-size: 17px;
        font-weight: 600;
        text-align: center;
        color: #1a8917;
    }
    .page.editPost > .content {
        position: absolute;
        top: 50px;
        right: 0;
        left: 0;
        bottom: 50px;
        overflow-y: auto;
    }
    .page.editPost > .content > .formWrapper {
        padding-top: 15px;
        padding-bottom: 40px;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field {
        padding-right: 32px;
        padding-left: 32px;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field > img[src='img/imageWhite.svg'] {
        width: 24px;
        margin: 61px auto;
        display: block;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field > img:not([src='img/imageWhite.svg']) {
        object-fit: cover;
        opacity: 0;
        transition: opacity 0.6s ease;
        width: 100%;
        height: 100%;
        border-radius: 8px;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field > label {
        font-size: 16px;
        display: inline-block;
        padding-bottom: 15px;
        font-weight: bold;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field > input {
        margin-bottom: 32px;
        padding: 13.5px 24px;
        border: 1px solid #b6bcb6;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field.image > .buttons {
        margin-bottom: 32px;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field.image > .buttons > .large {
        position: relative;
    }
    .page.editPost > .content > .formWrapper > .fForm > .fields > .field.image > .buttons > .large > .remove {
        color: #ffffff;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-weight: bold;
        background-color: red;
        border-radius: 5px;
        padding: 2px 6px;
    }
    .page.editPost > .content > .btns {
        padding-right: 32px;
        padding-left: 32px;
        padding-bottom: 32px;
        overflow: hidden;
    }
    .page.editPost > .content > .btns > .button {
        display: table-cell;
        height: 54px;
        color: #ffffff;
        line-height: 54px;
        transition: opacity 0.7s ease;
        text-align: center;
        border-radius: 8px;
        font-weight: bold;
        width: 45%;
    }
    .page.editPost > .content > .btns > .button[key='save'] {
        background-color: #1a8917;
        float: left;
    }
    .page.editPost > .content > .btns > .button[key='delete'] {
        background-color: #f44336;
        float: right;
    }
    .page.editPost > .footer {
        position: absolute;
        right: 0;
        bottom: 0;
        left: 0;
        box-shadow: 0px 0px 6px 0px #ddd;
        background-color: #ffffff;
    }
    .page.editPost > .footer > div {
        float: left;
        text-align: center;
        padding-top: 15px;
        padding-bottom: 15px;
        font-weight: bold;
    }
  • JS

    The JS codes should be added into editPost.js file, and also add to bottom section of index.html.

    var editPostPage = new Page('editPost', function (postKey) {
        // after page shown
    
        // define variables
        var path, data, jPage = $('.page.editPost'),
        jHeader = jPage.children('.header'),
        jContent = jPage.children('.content'),
        jBtns = jContent.children('.btns'),
        model = {
            image: {
                inputType: 'image',
                text: 'Upload image'
            },
            title: {
                inputType: 'text',
                text: 'Title',
                placeholder: 'Enter your title here',
                required: true
            },
            description: {
                inputType: 'longText',
                text: 'Description',
                placeholder: 'Enter your description  here',
                required: true
            },
            createdAt: {
                inputType: 'time',
                text: 'Date',
                display: 'inTable'
            },
            createdBy: {
                text: 'Author',
                display: 'inTable'
            }
        };
    
        // back
        app.ui.button(jHeader.children('.left'), 'ripple', {
            html: '<img src="img/backBlack.svg">',
            color: '#b6bcb6',
            onPush: function (jButton) {
                history.back();
            }
        });
    
        // render form
        var form = new Form('edit', model, jContent.children('.formWrapper'), app.local.posts[postKey], '/posts/' + postKey);
        form.render();
    
        // save
        app.ui.button(jBtns, 'ripple', {
            key: 'save',
            color: '#b6bcb6',
            onPush: function (jButton) {
                form.save(function (error) {
                    if (error) {
                        if (error.on === 'empty')
                            mdtoast(error.field.text + ' cannot be empty.', {type: mdtoast.ERROR});
                        else if (error.on === 'validation' || error.on === 'execution')
                            mdtoast(error.remote ? error.text : error.field.text + ' isn’t valid.', {type: mdtoast.ERROR});
                        else {
                            if (error.on === 'save')
                                console.error('save failed', error.text);
                            mdtoast('Save failed.', {type: mdtoast.ERROR});
                        }
                    }
                    else {
                        mdtoast('Save blog successful.', {type: mdtoast.SUCCESS});
                        homePage.show();
                    }
                });
            }
        });
    
        // delete
        app.ui.button(jBtns, 'ripple', {
            key: 'delete',
            color: '#b6bcb6',
            onPush: function (jButton) {
                app.controls.box('confirm', null, 'Are you sure you want to delete this post?', 'center', function(result) {
                    if (result) {
                        ref.child('posts/' + postKey).del(function (error) {
                            if (error) {
                                console.error('delete blog failed', error);
                                toast('Delete blog failed. Please try again.', {type: mdtoast.ERROR});
                            }
                            else {
                                delete app.local.posts[postKey];
                                mdtoast('Delete blog successful.', {type: mdtoast.SUCCESS});
                                homePage.show();
                            }
                        });
                    }
                });
            }
        });
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });

Post Detail Page

This page is to showing the detail of every added post.

  • HTML

    In order to make postDetail page, add the below HTML codes into body section of index.htmlfile.

    <div class="page postDetail">
        <div class="header">
            <div class="left">
                <div class="button"></div>
            </div>
            <div class="right">
                <div class="title">Post Detail</div>
            </div>
        </div>
        <div class="content">
            <div class="formWrapper"></div>
        </div>
        <div class="footer">
            <div class="button navi" key="home">Home</div>
            <div class="button navi" key="addPost">Add Post</div>
            <div class="button navi" key="profile">Profile</div>
        </div>
    </div>
  • CSS

    The styles should be added into postDetail.css file, and also add to head section of index.html.

    .page.postDetail > .header {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        height: 50px;
        overflow: hidden;
        box-shadow: 0px 0px 6px 0px #ddd;
    }
    .page.postDetail > .header > .left {
        float: left;
        height: 68px;
    }
    .page.postDetail > .header > .left > .button {
        padding: 17px 16px;
    }
    .page.postDetail > .header > .left > .button > img {
        display: block;
        height: 16px;
    }
    .page.postDetail > .header > .right {
        float: right;
        padding-right: 22px;
    }
    .page.postDetail > .header > .right > .title {
        line-height: 50px;
        font-size: 17px;
        font-weight: 600;
        text-align: center;
        color: #1a8917;
    }
    .page.postDetail > .content {
        position: absolute;
        top: 50px;
        right: 0;
        left: 0;
        bottom: 50px;
        overflow-y: auto;
    }
    .page.postDetail > .content > .formWrapper {
        padding-bottom: 32px;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field:not([key='image']) {
        padding-right: 32px;
        padding-left: 32px;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field > .key {
        display: none;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field[key='title'] > .value {
        font-size: 18px;
        font-weight: bold;
        padding-bottom: 15px;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field[key='description'] > .value {
        color: #454545;
        padding-bottom: 15px;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field[key='createdAt'],
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field[key='createdBy'] {
        display: table-cell;
        width: 50%;
        font-weight: bold;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field[key='image'] {
        background-color: #efefef;
        height: 200px;
        margin-bottom: 15px;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field > img[src='img/imageWhite.svg'] {
        width: 24px;
        margin: 61px auto;
        display: block;
    }
    .page.postDetail > .content > .formWrapper > .fForm > .fields > .field > img:not([src='img/imageWhite.svg']) {
        object-fit: cover;
        opacity: 0;
        transition: opacity 0.6s ease;
        width: 100%;
        height: 100%;
    }
    .page.postDetail > .footer {
        position: absolute;
        right: 0;
        bottom: 0;
        left: 0;
        box-shadow: 0px 0px 6px 0px #ddd;
        background-color: #ffffff;
    }
    .page.postDetail > .footer > div {
        float: left;
        text-align: center;
        padding-top: 15px;
        padding-bottom: 15px;
        font-weight: bold;
    }
  • JS

    The JS codes should be added into postDetail.js file, and also add to bottom section of index.html.

    var postDetailPage = new Page('postDetail', function (postKey) {
        // after page shown
    
        // define variables
        var jPage = $('.page.postDetail'),
        jHeader = jPage.children('.header'),
        jContent = jPage.children('.content'),
        model = {
            image: {
                inputType: 'image',
                text: 'Upload image'
            },
            title: {
                inputType: 'text',
                text: 'Title',
                placeholder: 'Enter your title here',
                required: true
            },
            description: {
                inputType: 'longText',
                text: 'Description',
                placeholder: 'Enter your description  here',
                required: true
            },
            createdAt: {
                inputType: 'time',
                text: 'Date'
            },
            createdBy: {
                text: 'Author'
            }
        };
    
        // back
        app.ui.button(jHeader.children('.left'), 'ripple', {
            html: '<img src="img/backBlack.svg">',
            color: '#b6bcb6',
            onPush: function (jButton) {
                history.back();
            }
        });
    
        // render form
        new Form('view', model, jContent.children('.formWrapper'), app.local.posts[postKey], '/posts/' + postKey, {
            onItemRendered: function (fieldKey, field, jField, value) {
                if (fieldKey === 'image') {
                    if (value) {
                        jField.html('<img onload="this.style.opacity = 1">');
                    
                        var jImg = jField.children('img');
                        if (field.image) {
                            var url = 'posts/' + postKey + '/image/binary.' + field.image.ext;
                            if (app.local.images[url])
                                jImg.attr('src', app.local.images[url]);
                            else {
                                var xhr = new XMLHttpRequest();
                                xhr.onload = function () {
                                    var reader = new FileReader();
                                    reader.onloadend = function () {
                                        app.local.images[url] = reader.result;
                                        jImg.attr('src', reader.result);
                                    };
                                    reader.readAsDataURL(xhr.response);
                                };
                                xhr.open('GET', url);
                                xhr.responseType = 'blob';
                                xhr.send();
                            }
                        }
                    }
                    else
                        jField.remove();
                }
                else if (fieldKey === 'createdBy') {
                    ref.child('clients/' + value).get(function (error, client) {
                        if (error)
                            console.error('get client value failed', error);
                        else if (client)
                            jField.children('.value').val(client.username);
                    });
                }
            }
        }).render();
    }, function () {
        // before page shown
    }, function () {
        // after page closed
    });