Tutorials > Creating Application Tutorial 1 - Case Study: "My Notes" Application

Creating Application Tutorial 1 - "My Notes" Application

This tutorial studies the design and implementation of "My Notes" application

  1. Objective
  2. Usability Goals
  3. Design
  4. Implementation - Server Side (PHP)
  5. Implementation - Server Side (template, javascipt)

Objective


Create application that will allow user to store his notes conveniently on the homepage.


Usability goals


The application should be intuitive and easy for user to use so that it will be enjoyable and useful

  • There should not be a save button.
    Reasons:
    1. To simplify user interface
    2. User can forget to save and might loose his notes.

  • The text box should be convenient to type and show large text without using the scrollbars

  • The text data will be saved when the text box looses focus and periodically while the focus is on the text box

  • The text box can be personalized and styled using various background colors

  • There should be a welcome note explaining what a user can do

Administrative Notes: Admin should be able to limit maximum text size


Design



We need to have a unique application name and language variables, the following parameters will identify the application:

  • Application Name/Key: mynotes
  • Application Hash: 23cd54a4d76be7274fe1b76d60ef9582
  • Application Language Range: 101001000 - 101001024 (25 language variables)

Application global parameters:

  • max_size - Maximum Text Size
  • save_period - Period to autosave the text while user is typing
  • welcome_note - Welcome note that explains what user can do.


Design Goal: The application should be able to support any "view", such as "user home", "superpage" or any other view.

Implementation - Server Side (PHP)


The PHP application class inherits from the base application class 'semods_app` which has the following useful functions, for this tutorial:

      
  // GLOBAL settings are stored per each application
  
  // Get global application setting for key $key. If no setting with $key is found, $default will be returned        
  function get_global_setting($key, $default = null);

  // Set global application setting for key $key with value $value. Arrays and Objects that can be serialized are welcome
  function set_global_setting($key, $value);

  // Set function saves the modified global settings
  function save_global_settings();



   // PRIVATE settings are stored for each "view", which can be "profile" or "user_home" (shared settings) or userpage, etc

  // Get private application setting for key $key. If no setting with $key is found, $default will be returned        
  function get_private_setting($key, $default = null);

  // Set global application setting for key $key with value $value. Arrays and Objects that can be serialized are welcome
  function set_private_setting($key, $value);

  // Set function saves the modified global settings
  function save_private_settings();


      
      
      
  



Implementation



<?php

/*
 * Application: My Notes
 * Author: SocialEngineMods
 *
 * 
 */

class app_mynotes extends semods_myapp {


  /***** VIEW SWITCHER *****/
  /*
   * $view can be "user_home", "profile", "userpage", "group", etc
   * $view_id is the view identifier, for example page_id for "userpage", event_id for "event", etc
   * $params are additinal passed parameters
   *
   * The function should return "true" if there is any content or "false" if no content is available
   *
   */

  function handleview( $view, $view_id, &$params ) {

    switch($view) {

      case "userpage":
      case "user_home":
        return $this->any_view( $view, $view_id, &$params );

      default:
        return false;

    }

  }




  /***** USER HOME VIEW *****/


  /*
   * This function handles user_home view and also any other view
   */
   
  function any_view( $view, $view_id, &$params ) {
    global $task;

    $max_size = $this->app->get_global_setting('max_size', 2048);
    $save_period = $this->app->get_global_setting('save_period', 60);  // 1 minute

    // Loads default text 'Type any text here and it will be auto saved.'
    $welcome_text_default = semods::get_language_text( 101001014 );
    $welcome_note = $this->app->get_global_setting('welcome_note', $welcome_text_default);  
    
    // some background colors
    $color = $this->app->get_private_setting('color', 'white');
  
    $color_codes = array('red'    => array('title'  => 101001002,
                                           'code'   => '#FFE0E1'
                                          ),
                         'orange' => array('title'  => 101001003,
                                           'code'   => '#FEE8BD'
                                          ),
                         'yellow' => array('title'  => 101001004,
                                           'code'   => '#FFFFE0'
                                          ),
                         'green'  =>  array('title'  => 101001005,
                                            'code'   => '#EEFFE0'
                                           ),
                         'blue'   =>  array('title'  => 101001006,
                                            'code'   => '#EFF5FF'
                                           ),
                         'white'  =>  array('title'  => 101001007,
                                            'code'   => '#FFFFFF'
                                           )
                         );

    // AJAX EDIT SETTINGS - LOAD
    // This task is called when user selects "edit" menu option from the box properties menu
    if($task == "editcontent") {

      // preload languages. sigh.
      foreach($color_codes as $color_code) {
        SE_Language::_preload($color_code['title']);
      }
      
      $params['ajax_response'] = true;
      $params['color'] = $color;
      $params['color_codes'] = $color_codes;
      $params['template'] = 'mynotes_edit';
      return true;

    }


    // AJAX EDIT SETTINGS - SAVE
    // This task is called when user clicks "save" on the form loaded with "editcontent" task
    if($task == "doajaxsave") {
      $color = semods::getpost('color');

      $this->app->set_private_setting('color', $color);
      $this->app->save_private_settings();

      // save stuff and fallback, make it ajax
      $params['ajax_response'] = true;
    }



    // AJAX SAVE text task
    // This task is called using javascript side
    if($task == "dosave") {
      
      $text = semods::getpost('text','');

      // make sure the text size is allowed
      // TODO: notify user if the text is too large and is being cut.
      if(strlen($text) > $max_size) {
        $text = substr($text,0,$max_size);
      }

      $this->app->set_private_setting('text', $text);
      $this->app->save_private_settings();
      
      // this is ajax request/response
      $params['ajax_response'] = true;
      
      // by default 'status' is '0', i.e. everything is ok. we can overide it here
      // $params['response'] = array( 'status' => 1 );
      // if there is a `template` parameter it will be inserted into response['html']
      
      return true;

    }


    /* LOAD STUFF */

    $text = $this->app->get_private_setting('text',$welcome_note);
    $color_code_item = semods::g($color_codes, $color, $color_codes['white']);

    
    // optional - do not show link to app in directory, in the box title
    //$params['ui_noheaderlink'] = true;

    $params['text'] = $text;
    $params['max_size'] = $max_size;
    $params['save_period'] = $save_period;
    $params['color_code'] = $color_code_item['code'];

    $params['template'] = 'mynotes';

    return true;
  }

}

?>

            



Implementation - Client Side (Template,Javascript)




The Javascript application class inherits from the base application class 'semods_app` which has the following useful functions:

  
  /* This function will make an ajax request with parameters, automatically appending
   * application id and placement
   */
  function request (params, onDone, onFail);
  

  /* This function will create a timer and periodically call the 'on_timer' function, if defined */
  function start_timer(timer_period);
  

  /* Directly stop the timer */
  function start_timer(timer_period);


  /* Define the timer period, in seconds */
  function set_timer_period_in_seconds(timer_period);


  /* This will ask the API to call us when the 'box' is dropped after being dragged from another location
   * The function 'on_drop' will be called with first parameter being identity and placement of the box
   *
   */
  function register_on_drop();


  /* This will ask the API to call us when the 'box' content is loaded using ajax 
   * The function 'on_ajax_load' will be called 
   *
   */
  functon register_on_ajax_load();


  /* This function will submit a form using ajax, posting all the form fields (input, textarea, select) 
   *
   */
  functon submit_form (form_id);



  /* This function will load content using ajax
   *
   */
  funcion load_content();

   


Implementation:

          

<!--
  
  THIS DEFINES OUR STYLES
  In the future API version it will be possible to separate the styles from the template
  
-->

{literal}
<style>
/* disable padding for content box */
#appcontent_app_{/literal}{$appdata.app_id}{literal} {
  padding: 0px;
}

DIV.app_mynotes_content {
  padding: 2px;
  text-align: center;
}

#app_mynotes_text {
  overflow-x:auto;
}

.app_mynotes_progress {
  height:18px;
  padding-left: 2px;
  padding-bottom: 2px;
  position: absolute;
  top: 6px;
  right: 8px;
}

.app_mynotes_text_readonly {
  text-align: left;
  padding: 5px;
}
</style>
{/literal}



<!--
  
  THIS IS OUR JAVASCRIPT APP PART `se_app_mynotes` inherits from `semods_app`


-->



{literal}
<script type='text/javascript'>

var se_app_mynotes = semods_app.extend({
  max_size : 0,
  
  constructor : function( app_id, page_id, view_id, instance_id ) {
    this._super( app_id, page_id, view_id, instance_id );

    this.register_on_drop();
    this.register_on_ajax_load();
  },

  // save the current text
  save_text : function() {
    
    var text = $('app_mynotes_text').value;
    if(text != '') {
      
      // show "saving..." indicator
      $('app_mynotes_progress').setStyle('display','block');
    
      // ajax request to save content
      this.request( 'task=dosave&text='+text, this.on_text_saved.bind(this),this.on_save_failed.bind(this) );
    }
    
  },
  
  // automatically adjust the textarea width to the content box size
  auto_size : function() {
    if($('app_mynotes_text')) {
      $('app_mynotes_text').style.width = $('app_mynotes_content').offsetWidth - 10 + 'px';
    }
  },
  
  // auto resize the text box after dropping the box onto another column 
  on_drop : function(identity) {
    this.auto_size();
  },
  
  // auto resize the text box after content is loaded via ajax
  on_ajax_load : function(identity,editing) {
    if(!editing) {
      this.auto_size();
      textarea_autogrow("app_mynotes_text")
    }
  },
  
  // periodic timer function
  on_timer : function() {
    this.save_text();
    this.start_timer();
  },
  
  // when text box looses focus, save the text and stop the timer
  save_and_finish : function() {
    this.stop_timer();
    this.save_text();
  },
  
  // ajax request success function
  on_text_saved : function( ajax_obj, response_text ) {

    $('app_mynotes_progress').setStyle('display','none');

  },
  
  // ajax request failure 
  on_save_failed : function( response ) {

    $('app_mynotes_progress').setStyle('display','none');

    // this API function will show notification about error. The first parameter can be custom error message text.
    apps_show_error_message();

  }

  
});

// after browser loads, autosize the text area and autogrow it
SEMods.Browser.register_onload( function() { app_mynotes.auto_size(); textarea_autogrow("app_mynotes_text"); } );

</script>


{/literal}

<-- required only if owner is viewing the content -->
{if $appdata.is_owner}

<script type='text/javascript'>
var app_mynotes = new se_app_mynotes( '{$appdata.app_id}', '{$appdata.page_id}', '{$appdata.view_id}', '{$appdata.instance_id}' );
app_mynotes.max_size = '{$appdata.max_size}';
app_mynotes.set_timer_period_in_seconds('{$appdata.save_period}');
</script>

{/if}


<div style="position:relative">
<div class="app_mynotes_content" id="app_mynotes_content" name="app_mynotes_content">
{if $appdata.is_owner}
  <textarea onfocus="app_mynotes.start_timer()" onblur="app_mynotes.save_and_finish()" id="app_mynotes_text"
  name="app_mynotes_text" style="background-color: {$appdata.color_code}">{$appdata.text}</textarea>
{else}
  <div class="app_mynotes_text_readonly" style="background-color: {$appdata.color_code};">{$appdata.text|nl2br}</div>
{/if}
</div>

  <div class="app_mynotes_progress" id="app_mynotes_progress" name="app_mynotes_progress" style="display:none;">
    <img src="{$appdata.app_dir}/images/ajaxprogress.gif">   {lang_print id=101000010}
  </div>
</div>

          
          
          



Installer






<?xml version="1.0" encoding="UTF-8"?>
<application>
  <version>1.00</version>
  <title>My Notes</title>
  <author>SocialEngineMods</author>
  <author>
    <name>SocialEngineMods</name>
    <email>info@socialenginemods.net</email>
    <website>http://www.socialenginemods.net/</website>
  </author>
  <hash>23cd54a4d76be7274fe1b76d60ef9582</hash>
  <type>app</type>
  <description><![CDATA[My notes on page]]></description>
  <pic></pic>
  <icon>icon_note.png</icon>
  <settings><![CDATA[]]></settings>
  <requires_install>0</requires_install>
  <show_in_directory>1</show_in_directory>
  <requires_add>0</requires_add>
  <tags><![CDATA[notes]]></tags>
  <user_menu_file></user_menu_file>
  <user_menu_title>101001000</user_menu_title>
  <global_profile_menu>0</global_profile_menu>
  <layout_options>15</layout_options>
  <pages_canvas></pages_canvas>
  <pages_admin>101000012</pages_admin>
  <inplace_edit>1</inplace_edit>
  <show_in_user_menu>0</show_in_user_menu>
  <after_add>user_home</after_add>
  <pages list="true">
    <page name="user_home">
      <default_stripe>w</default_stripe>
      <default_container>root</default_container>
      <stripecontentvaries>0</stripecontentvaries>
    </page>
    <page name="userpage">
      <default_stripe>w</default_stripe>
      <default_container>tabs</default_container>
      <stripecontentvaries>0</stripecontentvaries>
    </page>
  </pages>
  <install>
    <sql><![CDATA[
    ]]></sql>
    <sqlignore><![CDATA[
    ]]></sqlignore>
  </install>
  <unnstall>
    <sql><![CDATA[
    ]]></sql>
    <sqlignore><![CDATA[
    ]]></sqlignore>
  </unnstall>
  <languages list="true">
    <language code="en" name="English" list="true">
      <languagevar id="101001000"><![CDATA[My Notes]]></languagevar>
      <languagevar id="101001001"><![CDATA[Background Color:]]></languagevar>
      <languagevar id="101001002"><![CDATA[Red]]></languagevar>
      <languagevar id="101001003"><![CDATA[Orange]]></languagevar>
      <languagevar id="101001004"><![CDATA[Yellow]]></languagevar>
      <languagevar id="101001005"><![CDATA[Green]]></languagevar>
      <languagevar id="101001006"><![CDATA[Blue]]></languagevar>
      <languagevar id="101001007"><![CDATA[White]]></languagevar>
      <languagevar id="101001008"><![CDATA[Application Settings]]></languagevar>
      <languagevar id="101001009"><![CDATA[Global Settings]]></languagevar>
      <languagevar id="101001010"><![CDATA[Maximum note size]]></languagevar>
      <languagevar id="101001011"><![CDATA[characters]]></languagevar>
      <languagevar id="101001012"><![CDATA[Autosave period - while the text area is focused and the user is typing.]]></languagevar>
      <languagevar id="101001013"><![CDATA[seconds]]></languagevar>
      <languagevar id="101001014"><![CDATA[Type any text here and it will be auto saved]]></languagevar>
    </language>
  </languages>
</application>