Статьи

Начните строить свой блог с Parse.js: Refactor

Конечный продукт
Что вы будете создавать

В предыдущих шести сессиях вы создали систему блогов с нуля. Все работает, и это здорово! Тем не менее, сам код довольно грязный — мы вместе занимались начальной загрузкой и оставили много повторяющегося кода и временных решений. Эта сессия будет посвящена тому, как вы можете очистить и исправить некоторые проблемы, которые у нас были.

Прежде всего, поскольку у нас теперь есть маршрутизатор (если вы пропустили этот сеанс, ознакомьтесь с частью 5: Маршрутизатор ), нам больше не нужны два отдельных файла .html и .js . Давайте объединим их вместе.

На этом этапе я бы предложил объединить admin.html и admin.js и переименовать их в index.html и blog.js потому что они содержат больше логики и кода, но вы можете сделать это любым способом. Это должно быть довольно просто.

Если вы переименовываете файлы, просто убедитесь, что вы blog.js на blog.js в новом index.html (ранее admin.html ). Кроме того, не забудьте скопировать более #blogs-tpl из старого файла index.html в новый и скопировать BlogsView из старого файла blog.js в новый.

Теперь посетите http: // localhost / your-directory /, и вы должны увидеть экран входа по умолчанию (или экран администратора, если вы уже вошли в систему).

переименовать страницу администратора

Затем мы можем добавить новый шаблон URL в маршрутизаторе, чтобы сопоставить корневой URL с новой функцией; мы можем назвать это index() :

1
2
3
4
routes: {
    »: ‘index’,
    …
},

Эта функция index() должна отображать то, что было ранее на главной странице.

01
02
03
04
05
06
07
08
09
10
11
12
index: function() {
    this.blogs.fetch({
        success: function(blogs) {
            var blogsView = new BlogsView({ collection: blogs });
            blogsView.render();
            $(‘.main-container’).html(blogsView.el);
        },
        error: function(blogs, error) {
            console.log(error);
        }
    });
}

И чтобы увидеть, как это работает, давайте по умолчанию перенаправим на этот URL при запуске маршрутизатора:

1
2
3
4
start: function(){
    Parse.history.start({pushState: true});
    this.navigate(», { trigger: true });
}

Следующее, что нужно обновить панель навигации вверху. Давайте изменим эти HTML-файлы на URL-адреса здесь:

1
2
3
4
<nav class=»blog-nav»>
    <a class=»blog-nav-item active» href=»»>Home</a>
    <a class=»blog-nav-item» href=»admin»>Admin</a>
</nav>

И чтобы они работали, нам нужно добавить событие в .blog-nav-item чтобы использовать blogRouter.navigate() а не событие ссылки по умолчанию:

1
2
3
4
5
$(document).on(‘click’, ‘.blog-nav-item’, function(e) {
    e.preventDefault();
    var href = $(e.target).attr(‘href’);
    blogRouter.navigate(href, { trigger: true });
});

И давайте добавим логику для переключения класса .active :

1
2
3
4
5
6
$(document).on(‘click’, ‘.blog-nav-item’, function(e) {
    e.preventDefault();
    var href = $(e.target).attr(‘href’);
    blogRouter.navigate(href, { trigger: true });
    $(this).addClass(‘active’).siblings().removeClass(‘active’);
});

Теперь, если вы нажмете вокруг, все должно работать!

Двигаясь дальше, мы видим, что AddBlogView и EditBlogView очень похожи. Как и функции update() и create() в классе Blog . Давайте объединить их.

Во-первых, давайте объединим два шаблона в index.html чтобы они были #write-tpl .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<script id=»write-tpl» type=»text/x-handlebars-template»>
    <h2>{{form_title}}</h2>
 
    <form class=»form-write» role=»form»>
        <div class=»form-group»>
            <label for=»title»>Title</label>
            <input name=»title» type=»text» class=»form-control» id=»title» value=»{{title}}»></input>
        </div>
        <div class=»form-group»>
            <label for=»content»>Content</label>
            <textarea name=»content» class=»form-control» rows=»20″>{{{content}}}</textarea>
        </div>
        <button class=»btn btn-lg btn-primary btn-block» type=»submit»>Submit</button>
    </form>
</script>

Вы можете видеть, что это в основном #edit-tpl с изменениями имени класса и динамическим заголовком формы. Мы просто передадим "" в title и content при добавлении нового блога.

Далее, давайте объединим функции update() и create() в классе Blog. Мы можем this.set().save() для update() и create() . Для полей, которые не должны быть затронуты функцией update() , мы можем заполнить текущим значением:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
update: function(title, content) {
    this.set({
        ‘title’: title,
        ‘content’: content,
        // Set author to the existing blog author if editing, use current user if creating
        // The same logic goes into the following three fields
        ‘author’: this.get(‘author’) ||
        ‘authorName’: this.get(‘authorName’) ||
        ‘time’: this.get(‘time’) ||
    }).save(null, {
        success: function(blog) {
            alert(‘You updated a new blog: ‘ + blog.get(‘title’));
        },
        error: function(blog, error) {
            console.log(blog);
            console.log(error);
        }
    });
}

Теперь пришло время объединить два представления:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
WriteBlogView = Parse.View.extend({
    template: Handlebars.compile($(‘#write-tpl’).html()),
    events: {
        ‘submit .form-write’: ‘submit’
    },
    submit: function(e) {
        e.preventDefault();
        var data = $(e.target).serializeArray();
        // If there’s no blog data, then create a new blog
        this.model = this.model ||
        this.model.update(data[0].value, data[1].value);
    },
    render: function(){
        var attributes;
        // If the user is editing a blog, that means there will be a blog set as this.model
        // therefore, we use this logic to render different titles and pass in empty strings
        if (this.model) {
            attributes = this.model.toJSON();
            attributes.form_title = ‘Edit Blog’;
        } else {
            attributes = {
                form_title: ‘Add a Blog’,
                title: »,
                content: »
            }
        }
        this.$el.html(this.template(attributes)).find(‘textarea’).wysihtml5();
    }
})

Обратите внимание, как вы можете использовать if(this.model) для if(this.model) между функциями добавления и редактирования.

Наконец, давайте WriteBlogView с этим новым WriteBlogView в маршрутизаторе. Просто измените оба представления на WriteBlogView и он все равно должен работать как обычно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
add: function() {
    // Check login
    if (!Parse.User.current()) {
        this.navigate(‘login’, { trigger: true });
    } else {
        var writeBlogView = new WriteBlogView();
        writeBlogView.render();
        $container.html(writeBlogView.el);
    }
},
edit: function(id) {
    // Check login
    if (!Parse.User.current()) {
        this.navigate(‘login’, { trigger: true });
    } else {
        var query = new Parse.Query(Blog);
        query.get(id, {
            success: function(blog) {
                var writeBlogView = new WriteBlogView({ model: blog });
                writeBlogView.render();
                $container.html(writeBlogView.el);
            },
            error: function(blog, error) {
                console.log(error);
            }
        });
    }
}

Обратите внимание, что вы также должны отправлять посетителей обратно на страницу входа, если они не вошли в систему.

Теперь, когда мы удалили весь повторяющийся код, теперь мы можем перейти к некоторым функциям, которые можно улучшить.

Многие из вас спрашивали, как мы можем обеспечить безопасность данных, если API находится в коде. Parse.js предоставляет списки контроля доступа (ACL) как на уровне классов, так и на уровне элементов, которые помогают управлять доступом пользователей. Мы говорили о ACL на уровне класса в Части 3: Вход пользователя . Сегодня я покажу вам, как добавить ACL уровня элемента.

В качестве примера, давайте предположим, что мы хотим, чтобы каждый блог мог редактироваться только его первоначальным автором.

Чтобы это произошло, нам нужно установить поле ACL в функции update() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
update: function(title, content) {
 
    // Only set ACL if the blog doesn’t have it
    if ( !this.get(‘ACL’) ) {
        // Create an ACL object to grant access to the current user
        // (also the author of the newly created blog)
        var blogACL = new Parse.ACL(Parse.User.current());
        // Grant read-read only access to the public so everyone can see it
        blogACL.setPublicReadAccess(true);
        // Set this ACL object to the ACL field
        this.setACL(blogACL);
    }
 
    this.set({
        …
    });
}

Еще одна проблема, которая может возникнуть у многих из вас, заключается в том, что довольно сложно протестировать созданную вами систему блогов. Каждый раз, когда вы тестируете, вы должны возвращаться к http: // localhost / your-directory / для запуска маршрутизатора.

Давайте сначала решим эту проблему.

Parse.js делает это довольно легко, поэтому давайте просто изменим BlogRouter.start() чтобы установить корень файла.

1
2
3
4
5
6
start: function(){
    Parse.history.start({
        // put in your directory below
        root: ‘/tutorial_blog/’
    });
}

Обратите внимание, что теперь мы можем this.navigate() функцию this.navigate() .

Еще одна проблема с URL-адресами, которые у нас есть сейчас, заключается в том, что их нельзя добавить в закладки или пересмотреть. Все, что вы хотите сделать, нужно начинать с основного URL. Например, если вы посещаете http: // localhost / blog / admin , маршрутизатор настроен на прием этого шаблона URL, но сервер по-прежнему возвращает 404. Это потому, что когда вы посещаете /admin , ваш сервер не знает, что он должен идти в index.html чтобы запустить маршрутизатор в первую очередь.

Одним из способов решения этой проблемы является настройка вашего сервера таким образом, чтобы он перенаправлял все URL-адреса в index.html . Но это не совсем в рамках этого класса. Мы собираемся попробовать другой метод: добавить #/ перед всеми нашими URL.

URL-адрес панели администратора будет выглядеть следующим образом: http: // localhost / blog / # / admin . Это не совсем идеально, но это легкий путь. Когда браузер встречает /# , он не будет обрабатывать оставшуюся часть URL как путь к файлу; вместо этого он направит пользователя на index.html, чтобы наш маршрутизатор мог подобрать остальное.

Теперь давайте продолжим и изменим атрибут href всех тегов <a> в index.html примерно так:

1
<a class=»app-link» href=»edit/{{url}}»>Edit</a>

что-то вроде этого:

1
<a class=»app-link» href=»#/edit/{{url}}»>Edit</a>

Точно так же давайте изменим все BlogApp.navigate() в blog.js примерно так:

1
BlogApp.navigate(‘admin’, { trigger: true });

что-то вроде этого:

1
BlogApp.navigate(‘#/admin’, { trigger: true });

Вы также можете удалить некоторые из событий, чтобы использовать тег <a> .

Например, кнопка « Добавить новый блог » используется для использования события:

1
2
3
4
5
6
events: {
    ‘click .add-blog’: ‘add’
},
add: function(){
    blogRouter.navigate(‘#/add’, { trigger: true });
}

Мы можем вынуть их и заменить на ссылку в index.html :

1
<a href=»#/add» class=»add-blog btn btn-lg btn-primary»>Add a New Blog</a>

Вы также можете отключить эту функцию, так как URL будут работать сами по себе:

1
2
3
4
5
6
$(document).on(‘click’, ‘.blog-nav-item’, function(e) {
    e.preventDefault();
    var href = $(e.target).attr(‘href’);
    blogRouter.navigate(href, { trigger: true });
    $(this).addClass(‘active’).siblings().removeClass(‘active’);
});

Давайте также пока удалим active класс, но мы добавим его обратно и сделаем так, чтобы он работал в последующих сеансах другим способом.

1
2
3
4
<nav class=»blog-nav»>
    <a class=»blog-nav-item» href=»»>Home</a>
    <a class=»blog-nav-item» href=»#/admin»>Admin</a>
</nav>

Хорошо, зайдите в свой блог, протестируйте и убедитесь, что все ссылки теперь на http: // localhost / # / …, кроме домашней страницы.

Теперь у вас есть URL, которые вы можете обновить и просмотреть. Надеюсь, это сделает вашу жизнь намного проще!

Если вы не возражаете против сверхдлинного учебника и хотели бы внести еще несколько улучшений, вот несколько других исправлений и улучшений, которые вы можете сделать.

Вы также можете заметить, что блоги сортируются от самых ранних до самых последних. Обычно мы ожидаем увидеть последние блоги в первую очередь. Итак, давайте изменим коллекцию Blogs чтобы отсортировать их в следующем порядке:

1
2
3
4
Blogs = Parse.Collection.extend({
    model: Blog,
    query: (new Parse.Query(Blog)).descending(‘createdAt’)
})

Вот еще одна вещь, которую мы можем улучшить. Вместо появления окна с предупреждением после обновления блога, давайте просто перенаправим на страницу /admin :

01
02
03
04
05
06
07
08
09
10
this.set({
    …
}).save(null, {
    success: function(blog) {
        blogRouter.navigate(‘#/admin’, { trigger: true });
    },
    error: function(blog, error) {
        …
    }
});

Если вы приступите к очистке, вы также можете объединить AdminView и WelcomeView в одно — нет необходимости иметь два отдельных представления.

Опять же, сначала HTML-шаблон:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script id=»admin-tpl» type=»text/x-handlebars-template»>
    <h2>Welcome, {{username}}!</h2>
    <a href=»#/add» class=»add-blog btn btn-lg btn-primary»>Add a New Blog</a>
    <table>
        <thead>
            <tr>
                <th>Title</th>
                <th>Author</th>
                <th>Time</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            {{#each blog}}
            <tr>
                <td><a class=»app-link» href=»#/edit/{{objectId}}»>{{title}}</a></td>
                <td>{{authorName}}</td>
                <td>{{time}}</td>
                <td>
                    <a class=»app-link app-edit» href=»#/edit/{{objectId}}»>Edit</a> |
                    <a class=»app-link» href=»#»>Delete</a>
                </td>
            </tr>
            {{/each}}
        </tbody>
    </table>
</script>

Затем давайте изменим BlogRouter.admin() для передачи username в AdminView :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
admin: function() {
 
    var currentUser = Parse.User.current();
 
    // Check login
    if (!currentUser) BlogApp.navigate(‘#/login’, { trigger: true });
 
    this.blogs.fetch({
        success: function(blogs) {
            var blogsAdminView = new BlogsAdminView({
                // Pass in current username to be rendered in #admin-tpl
                username: currentUser.get(‘username’),
                collection: blogs
            });
            blogsAdminView.render();
            $(‘.main-container’).html(blogsAdminView.el);
        },
        error: function(blogs, error) {
            console.log(error);
        }
    });
}

А затем передайте username которое будет отображено в #admin-tpl :

01
02
03
04
05
06
07
08
09
10
11
BlogsAdminView = Parse.View.extend({
    template: Handlebars.compile($(‘#admin-tpl’).html()),
    render: function() {
        var collection = {
            // Pass in username as variable to be used in the template
            username: this.options.username,
            blog: this.collection.toJSON()
        };
        this.$el.html(this.template(collection));
    }
})

Наконец, мы можем хранить $('.main-container') как переменную, чтобы избежать нескольких запросов.

1
var $container = $(‘.main-container’);

И просто замените все $('.main-container') на $container .

Прежде всего, поздравляем с завершением! Это была длинная сессия, но вы очистили весь проект. Кроме того, вы также добавили ACL в блоги, внедрили статические URL-адреса и внесли множество других исправлений. Теперь это действительно солидный проект.

На следующем занятии мы ускорим процесс и добавим три новые функции: просмотр одного блога, удаление блога и выход из системы, потому что теперь вы хорошо разбираетесь в Parse.js и можете двигаться вперед гораздо быстрее. Я бы рекомендовал подумать о том, как написать эти функции заранее, чтобы вы могли немного проверить свои знания. Кроме этого, следите за обновлениями!