Building CMS with Meteor

Vladimir Minkin

dqvsra

This talk about magic

GOALS

  • Works locally

  • Serve multiple screens

  • Preview content

  • Organize content

  • Render text to image

  • Month to build

Libraries:

  • Blaze + Semantic UI + ViewModel
     

  • ValueObjects + Comman + Q
     

  • Collection FS
     

  • Account UI + Iron Router

DESIGN

It's about:

  • How
  • When
  • Where
  • Usability

PRODUCT =

IDEA

+ DESIGN

+ REALIZATION

<template name="layout">
  {{>loadfonts}}
  {{>logo}}
  {{>logout}}
  {{>header}}
  {{>menu}}
  {{>content}}
</template>
ContentTypes = {
      VIDEO       : "video"
   ,  IMAGE       : "image"
   ,  TEXT        : "text"
   ,  PACK        : "pack"
};
MeteorMethods = {
   ADD_SECTION          : "meteor_method_add_section"
,  ADD_CONTENT          : "meteor_method_add_content"
,  ADD_IMAGE            : "meteor_method_add_image"
,  ADD_TEXT_RENDER      : "meteor_method_add_text_render"

,  EDIT_CONTENT         : "meteor_method_edit_content"
,  EDIT_SECTION         : "meteor_method_edit_section"

,  GET_FIRST_SECTION    : "meteor_method_get_first_section"

,  REMOVE_SECTION       : "meteor_method_remove_section"
,  REMOVE_SUB_SECTIONS  : "meteor_method_remove_sub_section"
,  REMOVE_CONTENT       : "meteor_method_remove_content"
,  REMOVE_IMAGE         : "meteor_method_remove_image"
,  REMOVE_TEXT_RENDER   : "meteor_method_remove_text_render"

,  REPOSTION_CONTENT    : "meteor_method_reposition_content"

,  STORE_USER_SETTING   : "meteor_method_store_user_settings"
,  STORE_USER_NAV       : "meteor_method_store_user_navigation"

,  RENAME_SECTION       : "meteor_methods_rename_section"
,  SORT_SECTIONS        : "meteor_methods_sort_sections"

,  SELECT_SECTION       : "meteor_methods_select_section"
,  DESELECT_SECTION     : "meteor_methods_deselect_section"
};
ModalCommands = {
   SHOW_MODAL        : "modal_command_show_modal"

,  CREATE_SECTION    : "modal_commands_show_create_section"
,  REMOVE_SECTION    : "modal_commands_show_remove_section"

,  REMOVE_CONTENT    : "modal_commands_show_remove_content"
};

CODE: Constants

CODE: Value Objects

Content = new Mongo.Collection("Content", {
   transform: function (doc) { return new ContentVO(doc); }
});

Sections = new Mongo.Collection("Sections", {
   transform: function (doc) { return new SectionVO(doc); }
});
ViewModel.share({
   active: {
      section: null
   }
});

ViewModel

ViewModel is a view layer for Meteor.

 

You can think of it as Angular, Knockout, Aurelia, Vue, etc. but without the boilerplate code required to make those work.

https://viewmodel.org/

CODE: Components

CODE: Content binding

CODE: Select section

COMMAN

- Based on idea of Command Pattern

- Business logic extraction

- Can be "pure functions"

- Composition

Comman = (function(){
   const comman    = {};
   const commands  = {};

   comman.handle = function(commandName, handlerFunction){
      let commandHandler = {
         handlerFunction: handlerFunction
      };
      commands[commandName] = commandHandler;
   };

   comman.execute = function(){
      let args = Array.prototype.slice.call(arguments);
      const name = args.shift();
      const cmd = commands[name];
      if (cmd) {
         let defer = Q.defer();
         cmd.handlerFunction.apply(defer, args);
         return defer.promise;
      } else {
         throw new Error("No command with name:", name);
         return null;
      }
   };
   return comman;
})();
collection.insert(file, function (error, fileDoc) {
    // console.log("ContentCommands.INSERT_MEDIA_FILE insert:", !error, collection);
    if(fileDoc) {
       let cursor = collection.find(fileDoc._id);
       let progressStep = 1 / fileDoc.chunkSum;
       let liveQuery = cursor.observe({
          changed: function(newFileDoc, oldFileDoc) {
             // console.log("ContentCommands.INSERT_MEDIA_FILE upload:", NProgress.status);
             if(needToShowProgress) NProgress.inc(progressStep);
             if (newFileDoc.isUploaded()) {
                if(needToShowProgress) {
                   SuccessMessages.SAVE_COMPLETE(title);
                   NProgress.set(1);
                }
                liveQuery.stop();
                liveQuery = null;
                cursor = null;
                // console.log("ContentCommands.INSERT_MEDIA_FILE complete");
                that.resolve(newFileDoc);
             }
          }
       });
    } else {
       if(needToShowProgress){
          ErrorMessages.SAVE_FAILED(title);
          NProgress.set(1);
       }
       that.reject(new Error("Problem when insert file"));
    }
 });
Images = new FS.Collection("SeverstalImages", {
  stores: [new FS.Store.FileSystem("SeverstalImageStore", {path: pathToContent+"/images"})]
});

Iron Router

Accounts UI

<template name="login">
  <div class="ui login middle aligned center aligned grid">
    <div class="column">
      {{> atForm state='signIn' hideSignUpLink='true' showForgotPasswordLink='true'}}
    </div>
  </div>
</template>
AccountsTemplates.removeField('password');
AccountsTemplates.removeField('email');

AccountsTemplates.addFields([
   {
      _id: "username",
      type: "text",
      required: true,
      minLength: 5,
      hasIcon: true,
      iconClass: "user",
      placeholder: "Имя вводить здесь",
      errStr: 'Обязательно ввести имя пользователя',
      displayName: "Имя пользователя"
   },
...
]);

Ask questions

Live your love

 

THANK YOU!

Made with Slides.com