Recent Changes - Search:

Administrators

Developers

Articles

Cookbook

edit

RecipeManagerTutorial

Developers.RecipeManagerTutorial History

Hide minor edits - Show changes to markup

April 20, 2010, at 03:38 AM by 92.37.143.174 - Initial update of the tutorial.
Changed lines 40-44 from:
  __slots__ = ()
  containment = (
    'org.innoscript.recipemanager.schema.RecipeContainer?',
    'org.innoscript.recipemanager.schema.CookingRecipe?'
  )
to:
    containment = (
      'org.innoscript.recipemanager.schema.RecipeContainer?',
      'org.innoscript.recipemanager.schema.CookingRecipe?')
Changed lines 46-47 from:

"porcupine.systemObjects.Container" class). The "containment" class attribute is a tuple of strings, containing all the types of the Porcupine objects that this container type can accept. In this case, the specified container can either accept new containers of the same type or new recipes. It is worth mentioning that the special class attribute "__slots__" should not be omitted from any custom content class or data type, since instances of such types consume considerably less memory.

to:

"porcupine.systemObjects.Container" class). The "containment" class attribute is a tuple of strings, containing all the types of the Porcupine objects that this container type can accept. In this case, the specified container can either accept new containers of the same type or new recipes.

Changed lines 99-100 from:
  __slots__ = ()
  isRequired = True
to:
    isRequired = True

Changed lines 103-104 from:
  __slots__ = ()
  isRequired = True
to:
    isRequired = True

Changed lines 107-108 from:
  __slots__ = ()
  isRequired = True
to:
    isRequired = True
Changed lines 115-119 from:
  "The recipe object"
  __slots__ = (
    'categories','rating','preparationTime',
    'servings','ingredients','instructions'
  )
to:
    "The recipe object"

    def __init__(self):
        systemObjects.Item.__init__(self)
Changed line 122 from:
  __props__ = systemObjects.Item.__props__ + __slots__
to:
        self.categories = properties.Categories()
Changed lines 126-127 from:
  def __init__(self):
    systemObjects.Item.__init__(self)
to:
        self.rating = datatypes.Integer()
        self.rating.value = 5
        self.preparationTime = datatypes.Integer()
        self.preparationTime.value = 30
        self.servings = Servings()
        self.servings.value = 4
        self.ingredients = Ingredients()
        self.instructions = Instructions()
Changed lines 135-138 from:
    self.categories = properties.Categories()
to:

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "CookingRecipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "org/innoscript/desktop/schema/common.py" file.

Added lines 139-150:
...
class Category(system.Container):
  ...
  def __init__(self):
    ...
    self.category_objects = properties.CategoryObjects()

The objects' references that belong in a specified category are stored inside the "category_objects" attribute. The data type of this attribute is the "CategoryObjects" class.

Now let's take a closer look at the "CategoryObjects" data type:

Changed lines 152-159 from:
    self.rating = datatypes.Integer()
    self.rating.value = 5
    self.preparationTime = datatypes.Integer()
    self.preparationTime.value = 30
    self.servings = Servings()
    self.servings.value = 4
    self.ingredients = Ingredients()
    self.instructions = Instructions()
to:

class CategoryObjects?(RelatorN?):

  ...
  relCc = (
    'org.innoscript.desktop.schema.common.Document',
    'org.innoscript.desktop.schema.collab.Contact',
    'org.innoscript.recipemanager.schema.CookingRecipe?',
  )
Changed lines 160-177 from:

The "__props__" class attribute is a special attribute used by the framework. It should be defined only in each new content class that has additional data type attributes. This attribute is a tuple of strings containing the names of all such attributes, including those of the super classes.

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "CookingRecipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "org/innoscript/desktop/schema/common.py" file.

...
class Category(system.Container):
  ...
  def __init__(self):
    ...
    self.category_objects = properties.CategoryObjects()

The objects' references that belong in a specified category are stored inside the "category_objects" attribute. The data type of this attribute is the "CategoryObjects" class.

Now let's take a closer look at the "CategoryObjects" data type:

to:

Changed lines 162-168 from:

class CategoryObjects?(RelatorN?):

  ...
  relCc = (
    'org.innoscript.desktop.schema.common.Document',
    'org.innoscript.desktop.schema.collab.Contact',
    'org.innoscript.recipemanager.schema.CookingRecipe?',
  )
to:
  relAttr = 'categories'
Changed lines 164-168 from:
  relAttr = 'categories'
to:
Changed lines 173-174 from:

temporarily edit the containment of the "RootFolder" content class, located inside "common.py".

to:

temporarily edit the containment of the "RootFolder" content class, located inside "org/innoscript/desktop/schema/common.py".

Changed lines 192-195 from:

Restart the server and login using an administrative account. Open the root folder, right click on the list view and select "New". Notice that the "RecipeContainer" type is added to the list of the allowed types:

to:

Restart the server and login using an administrative account (admin/admin). Open the root folder (marked as Porcupine Server), right click on the list view and select "Create". Notice that the "RecipeContainer" type is added to the list of the allowed types:

April 20, 2010, at 03:08 AM by 92.37.143.174 - Links to installation page have been added.
Added lines 21-22:

Before proceeding further, please install the project as described in Installation page. Linux users can take a look to the InstallationLinux page.

January 07, 2009, at 09:07 PM by 91.132.157.103 -
Changed lines 3-4 from:

Download the Recipe Manager Porcupine package file

to:

Download the Recipe Manager Porcupine package file

August 10, 2008, at 08:55 PM by Tassos Koutsovassilis - Updated to reflect the 0.5 API changes
Changed lines 3-8 from:

This tutorial is outdated. Please check back again for a new updated version.

Download the Recipe Manager Porcupine package file

to:

Download the Recipe Manager Porcupine package file

August 10, 2008, at 02:20 PM by Tassos Koutsovassilis -
Changed lines 50-51 from:

"systemObjects.Container" class). The "containment" class attribute is a tuple of strings, containing all the types of the Porcupine objects that this container type can accept. In this case, the specified container can either accept new containers of the same type or new recipes. It is worth mentioning that the special class attribute "__slots__" should not be omitted from any custom content class or data type, since instances of such types consume considerably less memory.

to:

"porcupine.systemObjects.Container" class). The "containment" class attribute is a tuple of strings, containing all the types of the Porcupine objects that this container type can accept. In this case, the specified container can either accept new containers of the same type or new recipes. It is worth mentioning that the special class attribute "__slots__" should not be omitted from any custom content class or data type, since instances of such types consume considerably less memory.

Changed lines 69-71 from:

The "Categories" data type class is already defined; it is used for the categorization of the documents. Thus, we just need to import the following module:

to:

The "Categories" data type class is already defined; it is used for the categorization of the documents. Thus, we just need to import the following module:

Changed lines 151-152 from:

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "CookingRecipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "org/innoscript/desktop/schema/common.py" file.

to:

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "CookingRecipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "org/innoscript/desktop/schema/common.py" file.

Changed lines 161-164 from:
to:

The objects' references that belong in a specified category are stored inside the "category_objects" attribute. The data type of this attribute is the "CategoryObjects" class.

Now let's take a closer look at the "CategoryObjects" data type:

Changed line 181 from:

should have an attribute named "categories". If the "categories"attribute is a subclass of "Relator1", then we have established an one-to-many relationship. On the other hand, if the

to:

should have an attribute named "categories". If the "categories" attribute is a subclass of "Relator1", then we have established an one-to-many relationship. On the other hand, if the

Changed lines 188-189 from:

temporarily edit the containment of the "RootFolder" content class, located inside "common.py".

to:

temporarily edit the containment of the "RootFolder" content class, located inside "common.py".

Changed line 225 from:

("porcupine.systemObjects.Container") content class has some

to:

"porcupine.systemObjects.Container" content class has some

Changed lines 233-235 from:

Therefore, we have to create a new recipe form using a new web method. We start by creating the

to:

Therefore, we have to create a new recipe form using a new Web method. We start by creating the

Changed lines 237-238 from:

It is recommended to maintain a separate Python file for the web methods of each content type.

to:

It is recommended to maintain a separate Python file for the Web methods of each content type.

Changed lines 240-241 from:

package. The following imports are required:

to:

folder. The following imports are required:

Changed line 280 from:

As you propably already have guessed, the server formats the contents of the quix file defined in the

to:

As you probably already have guessed, the server formats the contents of the quix file defined in the

Changed line 284 from:

This web method is invoked only when the user needs to create a new recipe. This is achieved by the use

to:

This Web method is invoked only when the user needs to create a new recipe. This is achieved by the use

Changed lines 287-288 from:

If not then the framework invokes the aforementioned base container's "new" web method.

to:

If not then the framework invokes the aforementioned base container's "new" Web method.

Changed lines 373-376 from:

This form also uses a custom widget used for editing "RelatorN" data types. This custom widget is contained inside the "org/innoscript/desktop/widgets.js" JavaScript file. The "depends" attribute defines the dependencies required for the custom widgets contained in this file. This value contains the indices of the QuiX's modules as these are defined in the QuiX.modules array in the "quix/compat.js" file.

The form's "action" parameter is the URL where the form data get posted. This is usually the HTTP address of a Porcupine object whose type has a remote web method with the same name as this defined in the "method" form attribute (visit http://wiki.innoscript.org/index.php/Articles/RequestProcessingPipeline to see how each object is accessible over HTTP).

to:

This form also uses a custom widget used for editing "RelatorN" data types. This custom widget is contained inside the "org/innoscript/desktop/widgets.js" JavaScript file. The "depends" attribute defines the dependencies required by the custom widgets contained in this file. This value contains the indices of the QuiX's modules as these are defined in the QuiX.modules array in the "quix/compat.js" file.

The form's "action" parameter is the URL where the form data get posted. This is usually the HTTP address of a Porcupine object whose type has a remote Web method with the same name as this defined in the "method" form attribute (visit http://wiki.innoscript.org/index.php/Articles/RequestProcessingPipeline to see how each object is accessible over HTTP).

Changed lines 478-479 from:

Respectively, when editing an existing recipe, we call the "update" method of the "porcupine.systemObjects.Item" content type as defined in the "org/innoscript/desktop/webmethods/baseitem.py".

to:

Respectively, when editing an existing recipe, we call the "update" Web method of the "Item" content type as defined in the "org/innoscript/desktop/webmethods/baseitem.py".

Changed line 557 from:
to:

Do not forget to replace the tree node's ID with the ID of the "Recipes" folder given by the system.

Deleted line 562:

<?xml version="1.0" encoding="UTF-8"?>

Changed lines 763-764 from:

The "recipemanager.loadrecipes" handler is called once whenever the user presses the "Refresh" button. This handler loads the recipes from the selected container and displays them in the list view. The "getParentByType" QuiX widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

to:

The "recipemanager.loadrecipes" handler is called once whenever the user presses the "Refresh" button. This handler loads the recipes from the selected container and displays them in the list view. The "getParentByType" QuiX widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

Changed lines 958-959 from:
to:

The first event handler is called whenever the context menu is displayed. The parent widget of every context menu is the desktop. Thus, there must be a way to get the menu's enclosing widget. The enclosing widget is accessible through the "owner" menu property.

Changed lines 1033-1034 from:
to:
August 10, 2008, at 01:10 PM by Tassos Koutsovassilis -
Changed lines 162-163 from:
to:
Changed lines 317-318 from:
      <spinbutton name="preparationTime" top="55" left="110" width="50" max="240" value="$PREPARATION_TIME" editable="true" />
to:
      <spinbutton name="preparationTime" top="55" left="110" width="50" max="240"
        value="$PREPARATION_TIME" editable="true" />
Changed lines 320-321 from:
      <spinbutton name="servings" top="55" left="250" width="40" max="12" editable="true" value="$SERVINGS" />
to:
      <spinbutton name="servings" top="55" left="250" width="40" max="12"
        editable="true" value="$SERVINGS" />
Changed lines 323-324 from:
      <spinbutton name="rating" top="55" left="360" width="40" max="10" editable="true" value="$RATING" />
to:
      <spinbutton name="rating" top="55" left="360" width="40" max="10" editable="true"
        value="$RATING" />
Changed lines 590-591 from:
              <treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true" img="desktop/images/folder.gif" caption="Recipes"/>
to:
              <treenode id="[Put here the ID of the Recipes Porcupine folder]"
                haschildren="true" img="desktop/images/folder.gif" caption="Recipes"/>
Changed lines 623-625 from:
            <column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
            <column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
            <column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
to:
            <column width="140" caption="Recipe title" name="displayName"
              bgcolor="#EFEFEF" sortable="true"/>
            <column width="60" caption="Servings" type="int" name="servings"
              sortable="true"/>
            <column width="100" caption="Preparation time" type="int"
              name="preparationTime" sortable="true"/>
Changed lines 630-631 from:
            <column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
to:
            <column width="160" caption="Date modified" type="date"
              name="modified" sortable="true"/>
August 10, 2008, at 12:48 PM by Tassos Koutsovassilis -
Changed line 348 from:
    <a:dlgbutton onclick="generic.submitForm" width="70" height="22"
to:
    <dlgbutton onclick="generic.submitForm" width="70" height="22"
Changed line 353 from:
    <a:dlgbutton onclick="__closeDialog__" width="70" height="22"
to:
    <dlgbutton onclick="__closeDialog__" width="70" height="22"
Changed line 355 from:

</a:dialog>

to:

</dialog>

Changed lines 759-760 from:

The "recipemanager.loadrecipes" handler is called once whenever the user presses the "Refresh" button. This handler loads the recipes from the selected container and displays them in the list view. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

to:

The "recipemanager.loadrecipes" handler is called once whenever the user presses the "Refresh" button. This handler loads the recipes from the selected container and displays them in the list view. The "getParentByType" QuiX widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

Changed line 1046 from:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects" class). To be precise, it adds the "org.innoscript.recipemanager.schema.CookingRecipe?" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type).

to:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects" class). To be precise, it adds the "org.innoscript.recipemanager.schema.CookingRecipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type).

August 10, 2008, at 11:40 AM by Tassos Koutsovassilis -
Changed line 982 from:

version=0.0.4

to:

version=0.0.5

Changed lines 985-986 from:

The first section of the package definition file has information about the package (its name and its version). Please notice that the name of the package and the value of the name attribute of the package node in the "store.xml" configuration file must be identical.

to:

The first section of the package definition file has information about the package (its name and its version).

Changed lines 1007-1008 from:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section, the package will contain all the files that need to be distributed.

to:

The fourth section contains the published directories that are usually contain all the files required by our application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section, the package will contain all the files that need to be distributed.

Changed lines 1014-1015 from:

recipe_categories_folder=[Put here the ID of the "/Categories/Recipe categories" folder]

to:

recipe_categories_folder=[Put here the ID of the "/Categories/Recipe categories" folder]

Changed line 1039 from:

ce.relCc.append('org.innoscript.recipemanager.schema.Recipe')

to:

ce.relCc.append('org.innoscript.recipemanager.schema.CookingRecipe?')

Changed line 1046 from:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects" class). To be precise, it adds the "org.innoscript.recipemanager.schema.Recipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type).

to:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects" class). To be precise, it adds the "org.innoscript.recipemanager.schema.CookingRecipe?" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type).

Changed line 1057 from:

ce.relCc.remove('org.innoscript.recipemanager.schema.Recipe')

to:

ce.relCc.remove('org.innoscript.recipemanager.schema.CookingRecipe?')

Changed lines 1068-1070 from:
to:
August 10, 2008, at 11:30 AM by Tassos Koutsovassilis -
Changed lines 498-500 from:
  1. The porcupine.webmethods.quixui decorator is used for serving QuiX markup files defined using the "template" argument. This kind of web method is invoked over HTTP using the URL "http://SERVER_NAME/porcupine.py/OBJECT_ID?cmd=METHOD_NAME"
  2. The porcupine.webmethods.remotemethod decorator is used for serving RPC requests. Since the name of the method is incorporated in the request body these kind of methods are invoked by POSTing? to "http://SERVER_NAME/porcupine.py/OBJECT_ID".
to:
  1. The porcupine.webmethods.quixui decorator is used for serving QuiX markup files defined using the "template" argument. This kind of web method is invoked over HTTP using the URL "http://SERVER_NAME/porcupine.py/OBJECT_ID?cmd=METHOD_NAME"
  2. The porcupine.webmethods.remotemethod decorator is used for serving RPC requests. The method is incorporated in the request body and these kind of methods are invoked by POSTing to "http://SERVER_NAME/porcupine.py/OBJECT_ID".
August 10, 2008, at 11:25 AM by Tassos Koutsovassilis -
Changed line 289 from:

of the request contains the string "cc=org.innoscript.recipemanager.schema.CookingRecipe?".

to:

of the request contains the string "cc=org.innoscript.recipemanager.schema.CookingRecipe".

Changed lines 373-374 from:

This form also uses a custom widget used for editing "RelatorN?" data types. This custom widget is contained inside the "org/innoscript/desktop/widgets.js" JavaScript file. The "depends" attribute defines the dependencies required for the custom widgets contained in this file. This value contains the indexes of the QuiX's modules as these are defined in the QuiX.modules array in the "quix/compat.js" file.

to:

This form also uses a custom widget used for editing "RelatorN" data types. This custom widget is contained inside the "org/innoscript/desktop/widgets.js" JavaScript file. The "depends" attribute defines the dependencies required for the custom widgets contained in this file. This value contains the indices of the QuiX's modules as these are defined in the QuiX.modules array in the "quix/compat.js" file.

Changed lines 480-481 from:

The "CATEGORIES" key of the dictionary returned contains a semicolon delimited string of "image;id;name" triplets; this format is required by the "RelatorN?" editor custom widget.

to:

The "CATEGORIES" key of the dictionary returned contains a semicolon delimited string of "image;id;name" triplets; this format is required by the "RelatorN" editor custom widget.

Changed lines 489-490 from:

The edit the "org/innoscript/recipemanager/webmethods/__init__.py" file:

to:

Then edit the "org/innoscript/recipemanager/webmethods/__init__.py" file:

Added lines 496-500:

In this section we explored two different kinds of web methods:

  1. The porcupine.webmethods.quixui decorator is used for serving QuiX markup files defined using the "template" argument. This kind of web method is invoked over HTTP using the URL "http://SERVER_NAME/porcupine.py/OBJECT_ID?cmd=METHOD_NAME"
  2. The porcupine.webmethods.remotemethod decorator is used for serving RPC requests. Since the name of the method is incorporated in the request body these kind of methods are invoked by POSTing? to "http://SERVER_NAME/porcupine.py/OBJECT_ID".
Changed lines 563-582 from:

<a:window xmlns:a="http://www.innoscript.org/quix" title="Recipe manager" resizable="true" close="true" minimize="true" maximize="true" img="" width="640" height="480" left="center" top="center">

  <a:script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <a:wbody>
    <a:box width="100%" height="100%" orientation="v" spacing="0">
      <a:toolbar height="28">
        <a:tbbutton caption="Create recipe folder" width="120">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </a:tbbutton>
        <a:tbbutton caption="Create recipe" width="80">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.Recipe"/>
        </a:tbbutton>
        <a:tbsep/>
        <a:tbbutton caption="Refresh" width="60"/>
      </a:toolbar>
      <a:splitter height="-1" orientation="v" interactive="true">
        <a:pane length="200">
          <a:outlookbar width="100%" height="100%">
            <a:tool caption="Recipes">
to:

<?xml version="1.0" encoding="UTF-8"?> <window xmlns="http://www.innoscript.org/quix" title="Recipe manager" resizable="true"

    close="true" minimize="true" maximize="true" width="640" height="480"
    left="center" top="center">
  <script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <wbody>
    <box width="100%" height="100%" orientation="v" spacing="0">
      <toolbar id="toolbar" height="28">
        <tbbutton caption="Create recipe folder" width="120" disabled="true">
          <prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </tbbutton>
        <tbbutton caption="Create recipe" width="80" disabled="true">
          <prop name="cc" value="org.innoscript.recipemanager.schema.CookingRecipe?"/>
        </tbbutton>
        <tbsep/>
        <tbbutton caption="Refresh" width="60"/>
      </toolbar>
      <splitter>
        <outlookbar width="200">
          <tool caption="Recipes" bgcolor="white">
Changed lines 586-588 from:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
                <a:treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true"
                img="desktop/images/folder.gif" caption="Recipes"/>
to:
            <foldertree id="tree" method="getSubtree" padding="4,4,4,4">
              <treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true" img="desktop/images/folder.gif" caption="Recipes"/>
            </foldertree>
Changed lines 592-613 from:
              </a:foldertree>
            </a:tool>
            <a:tool caption="Search" bgcolor="gray">
              <a:label caption="Recipe title contains:"/>
              <a:field id="title" left="5" top="20" width="160"/>

              <a:label top="50" caption="Preparation time is less than"/>
              <a:field id="preparationTime" left="5" top="70" width="30"/>
              <a:label top="72" left="40" caption="min."/>

              <a:label top="100" caption="The recipe's rating is greater than"/>
              <a:spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

              <a:label top="150" caption="Recipe ingredients contain:"/>
              <a:field id="ingredients" top="170" left="5" width="160"/>
              <a:label top="190" caption="(comma separated list)" style="font-style:italic"/>

              <a:button top="220" left="center" width="60" height="28" caption="Search"/>
            </a:tool>
          </a:outlookbar>
        </a:pane>
        <a:pane length="-1">
to:
          </tool>
          <tool caption="Search" bgcolor="white">
            <label caption="Recipe title contains:"/>
            <field id="title" left="5" top="20" width="160"/>

            <label top="50" caption="Preparation time is less than"/>
            <field id="preparationTime" left="5" top="70" width="30"/>
            <label top="72" left="40" caption="min."/>

            <label top="100" caption="The recipe's rating is greater than"/>
            <spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

            <label top="150" caption="Recipe ingredients contain:"/>
            <field id="ingredients" top="170" left="5" width="160"/>
            <label top="190" caption="(comma separated list)" style="font-style:italic"/>

            <button top="220" left="center" width="60" height="28" caption="Search"/>
          </tool>
        </outlookbar>
Changed line 614 from:
          <a:listview width="100%" height="100%" id="list">
to:
        <listview id="list">
Changed lines 618-630 from:
            <a:listheader>
              <a:column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
              <a:column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
              <a:column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
              <a:column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
              <a:column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
            </a:listheader>
          </a:listview>
        </a:pane>
      </a:splitter>
    </a:box>
  </a:wbody>

</a:window>

to:
          <listheader>
            <column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
            <column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
            <column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
            <column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
            <column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
          </listheader>
        </listview>
      </splitter>
    </box>
  </wbody>

</window>

Changed lines 635-636 from:

Let's start by adding some event handlers.

to:

Let's start by adding some event handlers for the toolbar's buttons:

Changed lines 640-641 from:

<a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
to:

<toolbar id="toolbar" height="28">

  <tbbutton caption="Create recipe folder" width="120" disabled="true"
Changed lines 650-651 from:
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
to:
  </tbbutton>
  <tbbutton caption="Create recipe" width="80" disabled="true"
Changed lines 660-662 from:
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
to:
  </tbbutton>
  <tbsep />
  <tbbutton caption="Refresh" width="60"
Changed lines 670-672 from:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

to:

</toolbar>

Changed lines 672-675 from:

to:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 677-681 from:
  onload="recipemanager.loadRecipes">
to:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
Changed line 683 from:

to:

Changed line 685 from:
  ...
to:
  var sCC = w.attributes.cc;
Deleted lines 686-688:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 689-693 from:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
to:
  var id = oTree.getSelection().getId();
Changed lines 693-697 from:
  var sCC = w.attributes.cc;
to:
  oWin.showWindow(id + '?cmd=new&cc=' + sCC,
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );
Deleted lines 700-711:
  var id = oTree.getSelection().getId();

@]

  oWin.showWindow(id + '?cmd=new&cc=' + sCC,
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

[@

Changed lines 725-729 from:
  var id = oTree.getSelection().getId();
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
to:
  var selection = oTree.getSelection();
  if (selection) {
    var id = selection.getId();
    var listView = appWin.getWidgetById('list');
    var sOql = "select id, displayName, servings, preparationTime, rating, " +
               "modified from '" + id + "' where contentclass = " +
               "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
Changed lines 735-737 from:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
to:
    var xmlrpc = new XMLRPCRequest?(QuiX?.root);
    xmlrpc.oncomplete = recipemanager.updateList;
    xmlrpc.callback_info = listView;
Changed lines 741-742 from:
  xmlrpc.callmethod('executeOqlCommand', sOql);
to:
    xmlrpc.callmethod('executeOqlCommand', sOql);
  }
Changed lines 757-760 from:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "a:prop" nodes. The location of the new object is determined by the current tree selection. The "showWindow" method loads an XML UI definition for a new window or dialog from a specified URL, and opens it as a child window. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the dialog created. In this event, the root widget - the form dialog - will be added to the child windows of our application's main window.

The "recipemanager.loadrecipes" handler is called once when the list view is loaded. This handler loads the recipes from the selected container and displays them in the list view. The "onload" event handler, unlike the other events, does not accept the two common event handler arguments, the event object and the widget, but instead accepts only the widget that fires the event. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

to:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "prop" nodes. The location of the new object is determined by the current tree selection. The "showWindow" method loads an XML UI definition for a new window or dialog from a specified URL, and opens it as a child window. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the dialog created. In this event, the root widget - the form dialog - will be added to the child windows of our application's main window.

The "recipemanager.loadrecipes" handler is called once whenever the user presses the "Refresh" button. This handler loads the recipes from the selected container and displays them in the list view. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

Changed lines 762-763 from:

contained inside the selected tree container. Inside this function we send a new XMLRPC request to the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This function is always called having the request object as the first argument.

to:

contained inside the selected tree container. Inside this function we send a new XML-RPC request to the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This callback function is always called having the request object as the first argument.

Changed lines 767-769 from:

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XMLRPC call.

Afterwards, we must synchronize the tree with the list view. This means that for each new tree

to:

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XML-RPC call.

Afterwards, we must synchronize the tree with the list view. For each new tree

Changed line 776 from:

<a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

to:

<foldertree id="tree" method="getSubtree" padding="4,4,4,4"

Changed line 785 from:

</a:foldertree>

to:

</foldertree>

Changed line 794 from:

<a:button top="220" left="center" width="60" height="28" caption="Search"

to:

<button top="220" left="center" width="60" height="28" caption="Search"

Changed lines 854-855 from:

<a:listview multiple="true" width="100" height="100" id="list"

  onload="recipemanager.loadRecipes"
to:

<listview id="list"

Changed lines 877-879 from:

The final enhancement is the addition of a context menu. Open the application object and add the following lines to the interface definition:

to:

The final enhancement is the addition of a context menu. Add the following lines to the interface definition inside the list view widget:

Changed line 882 from:

<a:pane length="-1">

to:

<listview id="list" ondblclick="recipemanager.loadItem">

Changed lines 886-889 from:
  <a:contextmenu onshow="recipemanager.contextmenu_onshow">
    <a:menuoption caption="Open" onclick="recipemanager.openRecipe" />
    <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe" />
  </a:contextmenu>
to:
  <contextmenu onshow="recipemanager.contextmenu_onshow">
    <menuoption caption="Open" onclick="recipemanager.openRecipe" />
    <menuoption caption="Delete" onclick="recipemanager.deleteRecipe" />
  </contextmenu>
Changed lines 893-897 from:
  <a:listview width="100" height="100" id="list"
    onload="recipemanager.loadRecipes" ondblclick="recipemanager.loadItem">
    ...
  </a:listview>

</a:pane>

to:
   ...
 </listview>
Changed line 905 from:
  var oList = menu.owner.getWidgetById('list');
to:
  var oList = menu.owner;
Changed line 914 from:
  var oList = w.parent.owner.getWidgetById('list');
to:
  var oList = w.parent.owner;
Changed line 925 from:
  var oList = w.parent.owner.getWidgetById('list');
to:
  var oList = w.parent.owner;
Changed line 956 from:

The "desktop.msgbox" is used for displaying message boxes. The arguments accepted by this

to:

The "document.desktop.msgbox" is used for displaying message boxes. The arguments accepted by this

August 10, 2008, at 12:20 AM by Tassos Koutsovassilis -
Deleted lines 71-74:

from org.innoscript.desktop.schema import properties

In order to define the relation both ways, we have to somehow add the "CookingRecipe" object type to the list of object types that can be categorized. This is accomplished by editing the "properties.py" file inside the "org/innoscript/desktop/schema" folder. The highlighted line should be added:

Added lines 74-80:

from org.innoscript.desktop.schema import properties @]

In order to define the relation both ways, we have to somehow add the "CookingRecipe" object type to the list of object types that can be categorized. This is accomplished by editing the "properties.py" file inside the "org/innoscript/desktop/schema" folder. The highlighted line should be added:

[@

August 10, 2008, at 12:17 AM by Tassos Koutsovassilis -
Changed lines 1074-1075 from:
to:
Changed line 1078 from:
to:
August 10, 2008, at 12:16 AM by Tassos Koutsovassilis -
Changed line 1078 from:
to:
August 10, 2008, at 12:11 AM by Tassos Koutsovassilis -
Changed line 45 from:
    'org.innoscript.recipemanager.schema.Recipe'
to:
    'org.innoscript.recipemanager.schema.CookingRecipe?'
Changed lines 74-75 from:

In order to define the relation both ways, we have to somehow add the "Recipe" object to the list of objects that can be categorized. This is accomplished by editing the "properties.py" file inside the "org/innoscript/desktop/schema" folder. The highlighted line should be added:

to:

In order to define the relation both ways, we have to somehow add the "CookingRecipe" object type to the list of object types that can be categorized. This is accomplished by editing the "properties.py" file inside the "org/innoscript/desktop/schema" folder. The highlighted line should be added:

Changed line 87 from:
      'org.innoscript.recipemanager.schema.Recipe',
to:
      'org.innoscript.recipemanager.schema.CookingRecipe?',
Changed lines 111-112 from:

Having defined all of our required data types, we proceed to the "Recipe" content class definition:

to:

Having defined all of our required data types, we proceed to the "CookingRecipe" content class definition:

Changed line 115 from:

class Recipe(systemObjects.Item):

to:

class CookingRecipe?(systemObjects.Item):

Changed lines 149-150 from:

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "Recipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "common.py" file.

to:

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "CookingRecipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "org/innoscript/desktop/schema/common.py" file.

Changed line 170 from:
    'org.innoscript.recipemanager.schema.Recipe',
to:
    'org.innoscript.recipemanager.schema.CookingRecipe?',
Changed lines 211-221 from:

Select the "RecipeContainer" type and create a new object named "Recipes". If you double click on the new folder you should get the following error:

This is because we have not yet defined a valid registration (servlet binding) for the "RecipeContainer" content class. Hence, we have to edit the "store.xml" file inside the "conf" folder. Since these are the first servlet registrations we create for our application, we must enclose it inside a new package node. Locate the "packages" node at the top of the afore-mentioned file and append the following nodes:

to:

Select the "RecipeContainer" type and create a new object named "Recipes".

Finally, we also need to create the recipe categories container. Double click the "Categories" folder and create a new category folder named "Recipe categories". Please note that in case your session has expired, you just have to login again.

Before restarting the server, you can safely delete the "RecipeContainer" entry from the containment list of the "RootFolder" content type.

4. Designing a new recipe form

The current implementation of the "new" web method bound to the base container ("porcupine.systemObjects.Container") content class has some limitations that force us to design a new form for the "CookingRecipe" content type. Looking at the form that is automatically generated by the specific web method as defined in the "org/innoscript/desktop/webmethods/basecontainer.py" file, we notice the absence of the integer data types. This is happening because the web method, in its current state, ignores the numeric data types (a QuiX numeric input control, other than the spin button, is in the wish list).

Therefore, we have to create a new recipe form using a new web method. We start by creating the Python package that will host our application's web methods. Create a new folder named "webmethods" inside the "org/innoscript/recipemanager" folder and then add an empty "__init__.py" file in order to make it a Python package. It is recommended to maintain a separate Python file for the web methods of each content type.

Consequently, we create and edit a new Python file named "recipecontainer.py" inside the "webmethods" package. The following imports are required:

Changed lines 244-247 from:

<package name="RecipeManager?">

  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    method="POST"
    param=""
to:

from porcupine import webmethods from porcupine import HttpContext?

from org.innoscript.recipemanager.schema import CookingRecipe? from org.innoscript.recipemanager.schema import RecipeContainer?

Changed lines 250-254 from:

to:

In order to overwrite the default form generated, when creating a new recipe, we must create a new web method as shown below:

Changed lines 256-277 from:
    client="vcXMLRPC"
to:

@webmethods.quixui(of_type=RecipeContainer?,

                   qs="cc=org.innoscript.recipemanager.schema.CookingRecipe?",
                   template='ui.RecipeForm?.quix',
                   max_age=1200)

def new(self):

    oRecipe = CookingRecipe?()
    return {
        'URI': HttpContext?.current().request.SCRIPT_NAME + '/' + self.id,
        'METHOD': 'create',
        'TITLE': 'Create new recipe',
        'HIDDEN': '<field name="CC" type="hidden" value="org.innoscript.recipemanager.schema.CookingRecipe?" />',
        'DISPLAYNAME': oRecipe.displayName.value,
        'DESCRIPTION': oRecipe.description.value,
        'RATING': oRecipe.rating.value,
        'PREPARATION_TIME': oRecipe.preparationTime.value,
        'SERVINGS': oRecipe.servings.value,
        'INGREDIENTS': oRecipe.ingredients.value,
        'INSTRUCTIONS': oRecipe.instructions.value,
        'CATEGORIES': '',
        'ICON': oRecipe.__image__,
        'ACTION': 'Create',
    }
Added lines 279-291:

As you propably already have guessed, the server formats the contents of the quix file defined in the "template" argument using the dictionary returned along with the Python's built-in "string.Template" module.

This web method is invoked only when the user needs to create a new recipe. This is achieved by the use of the "qs" argument. This argument ensures that this web method is called only when the query string of the request contains the string "cc=org.innoscript.recipemanager.schema.CookingRecipe?". If not then the framework invokes the aforementioned base container's "new" web method.

The QuiX XML definition of the form is a new file named "ui.RecipeForm.quix" inside the "org/innoscript/recipemanager/webmethods" folder.

Changed lines 294-299 from:
    lang=".*"
to:

<?xml version="1.0" ?> <dialog xmlns="http://www.innoscript.org/quix" title="$TITLE" img="$ICON"

	close="true" width="480" height="380" left="30%" top="10%">
  <script name="Desktop Generic Functions" src="desktop/generic.js" /> 
  <module name="Desktop Widgets" src="desktop/widgets.js" depends="10,14,15" />
  <wbody>
Changed line 303 from:
    action="org.innoscript.desktop.XMLRPC.ContainerGeneric?"/>
to:
    <form action="%(URI)s" method="%(METHOD)s" padding="4,4,4,4">
Changed lines 307-309 from:
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    method="GET"
    param="new"
to:
      $HIDDEN 
      <label caption="Recipe name:" />
      <field left="80" width="380" name="displayName" value="$DISPLAYNAME" />
      <label caption="Description:" top="25" />
      <field left="80" top="24" width="380" name="description" value="$DESCRIPTION" />
      <hr top="50" width="100%" />
      <label top="58" caption="Preparation time (min):" />
      <spinbutton name="preparationTime" top="55" left="110" width="50" max="240" value="$PREPARATION_TIME" editable="true" />
      <label top="58" left="200" caption="Servings:" />
      <spinbutton name="servings" top="55" left="250" width="40" max="12" editable="true" value="$SERVINGS" />
      <label top="58" left="320" caption="Rating:" />
      <spinbutton name="rating" top="55" left="360" width="40" max="10" editable="true" value="$RATING" />
      <tabpane top="90" width="100%" height="220">
        <tab caption="Ingredients">
          <field type="textarea" name="ingredients" width="100%" height="100%">$INGREDIENTS</field>
        </tab>
        <tab caption="Instructions">
          <field type="textarea" name="instructions" width="100%" height="100%">$INSTRUCTIONS</field>
        </tab>
        <tab caption="Categories">
Changed lines 330-334 from:
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
to:
          <custom classname="ReferenceN?" width="100%" height="100%"
            root="Categories/Recipe categories"
            cc="org.innoscript.desktop.schema.common.Category"
            name="categories"
            value="$CATEGORIES"/>
Changed lines 338-341 from:
    lang=".*"
to:
        </tab>
      </tabpane>
    </form>
  </wbody>
Changed line 345 from:
    action="org.innoscript.desktop.ui.Frm_AutoNew"/>
to:
    <a:dlgbutton onclick="generic.submitForm" width="70" height="22"
Changed lines 349-352 from:

</package>

to:
      caption="$ACTION" default="true" />
    <a:dlgbutton onclick="__closeDialog__" width="70" height="22"
      caption="Cancel" />

</a:dialog>

Changed lines 355-387 from:
to:

Initially, this form imports "generic.js"; one of the desktop's JavaScript files. From this script ("org/innoscript/desktop/generic.js") we reuse the "generic.submitForm" function. This function simply submits the first QuiX form found inside the current dialog.

Changed lines 359-361 from:

from porcupine.core.servlet import XULServlet?

from org.innoscript.recipemanager import schema

to:

... generic.submitForm = function(evt, w) {

  var oDialog = w.getParentByType(Dialog);
  var oForm = oDialog.getWidgetsByType(Form)[0];
  oForm.submit(__closeDialog__);

} ...

Changed lines 368-376 from:
to:

The "submit" method of a QuiX form takes one optional argument; the function to call asynchronously, once the browser has the submission response. In this case the dialog is simply closed.

This form also uses a custom widget used for editing "RelatorN?" data types. This custom widget is contained inside the "org/innoscript/desktop/widgets.js" JavaScript file. The "depends" attribute defines the dependencies required for the custom widgets contained in this file. This value contains the indexes of the QuiX's modules as these are defined in the QuiX.modules array in the "quix/compat.js" file.

The form's "action" parameter is the URL where the form data get posted. This is usually the HTTP address of a Porcupine object whose type has a remote web method with the same name as this defined in the "method" form attribute (visit http://wiki.innoscript.org/index.php/Articles/RequestProcessingPipeline to see how each object is accessible over HTTP).

Since this template will be used both for creating and editing recipes the name of the method is diferrent in each aspect. When creating a new recipe we call the "create" web method bound the "porcupine.systemObjects.Container" base class. This method can be found in the "org/innoscript/desktop/webmethods/basecontainer.py" file:

Changed lines 378-384 from:

<package name="RecipeManager?">

  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    method="POST"
    param=""
    client="vcXMLRPC"
    lang=".*"
    action="org.innoscript.desktop.XMLRPC.ContainerGeneric?"/>
to:

...

Changed lines 382-404 from:
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    qs="org.innoscript.recipemanager.schema.RecipeContainer?"
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
    lang=".*"
    method="GET"
    param="new"
    action="org.innoscript.desktop.ui.Frm_AutoNew">
      <filter type="porcupine.filters.postProcessing.multilingual.Multilingual"
	      using="org.innoscript.desktop.strings.resources"/>
  </reg>
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
     qs="org.innoscript.recipemanager.schema.Recipe"
     client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
     lang=".*"
     method="GET"
     param="new"
     action="org.innoscript.recipemanager.ui.RecipeForm?"/>
  <reg cc="org.innoscript.recipemanager.schema.Recipe$"
    method="GET"
    param="properties"
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
    lang=".*"
    action="org.innoscript.recipemanager.ui.RecipeForm?"/>
to:

@webmethods.remotemethod(of_type=Container) def create(self, data):

Changed lines 387-388 from:

<package>

to:
    "Creates a new item"
    context = HttpContext?.current()
Changed lines 390-412 from:

The first highlighted registration assigns the "Frm_AutoNew" as the servlet to be executed when the browser GETs on a "RecipeContainer" object with the "cmd" query parameter set to "new" and the query string contains the string "org.innoscript.recipemanager.schema.RecipeContainer" (this is the new "qs" parameter).

The second highlighted registration assigns the "RecipeForm" as the servlet to be executed when the browser GETs on a "RecipeContainer" object with the "cmd" query parameter set to "new" but this time when the query string contains the string "org.innoscript.recipemanager.schema.Recipe".

The last registration is for for the "Recipe" type, and renders the recipe form whenever a compatible browser GETs? on an object of this type with the parameter set to "properties".

Remember that every QuiX servlet (an instance of the XULServlet class) must always be accompanied with a quix file in the same directory. This file usually contains the XML definition of the interface, which abides to the Python string formatting rules. The file name of this file is of the following form:

[Name of Python file].[Name of the servlet class name].quix

Now let's write the "RecipeForm" servlet:

to:

Changed lines 392-432 from:

class RecipeForm?(XULServlet?):

  def setParams(self):
    sCC = self.item.contentclass
    sCategories = ''
    sHiddenField = ''
    if sCC == 'org.innoscript.recipemanager.schema.RecipeContainer?':
      # we create a new recipe
      sTitle = 'Create new recipe'
      oRecipe = schema.Recipe()
      # in this case we need an extra hidden field with the type of
      # object we are about to create
      sHiddenField = '<a:field name="CC" type="hidden" ' +         'value="schemas.org.innoscript.recipemanager.Recipe" />'
      sMethod = 'create'
      sAction = 'Create'
    elif sCC == 'org.innoscript.recipemanager.schema.Recipe':
      # we editing an existing one
      oRecipe = self.item
      sMethod = 'update'
      sTitle = 'Recipe properties'
      sAction = 'Update'
    # build the categories options list
    for category in oRecipe.categories.getItems():
      sCategories += '<a:option caption="s" img="%s" />' %
        (category.displayName.value, category.id, category.__image__)
    self.params = {
      'URI': self.request.serverVariables['SCRIPT_NAME'] + '/' + self.item.id,
      'METHOD': sMethod,
      'TITLE': sTitle,
      'HIDDEN': sHiddenField,
      'DISPLAYNAME': oRecipe.displayName.value,
      'DESCRIPTION': oRecipe.description.value,
      'RATING': oRecipe.rating.value,
      'PREPARATION_TIME': oRecipe.preparationTime.value,
      'SERVINGS': oRecipe.servings.value,
      'INGREDIENTS': oRecipe.ingredients.value,
      'INSTRUCTIONS': oRecipe.instructions.value,
      'CATEGORIES': sCategories,
      'ICON': oRecipe.__image__,
      'ACTION': sAction,
    }
to:
    oNewItem = misc.getClassByName(data.pop('CC'))()
Deleted lines 393-403:

As you propably already have guessed, the server formats the contents of the quix file using the "params" attribute of the servlet.

This servlet is called either when the user needs to create a new recipe, or when the user wants to display the properties of an existing recipe. In the first case, the type of the object "called" is the "RecipeContainer" unlike the second case, in which the type of the object is the "Recipe" type.

The QuiX XML definition of the form is a new file named "ui.RecipeForm.quix" inside the "org/innoscript/recipemanager" folder.

Changed lines 396-400 from:

<?xml version="1.0" ?> <a:dialog xmlns:a="http://www.innoscript.org/quix" title="%(TITLE)s" img="%(ICON)s" close="true" width="480" height="380" left="30" top="10">

    <a:script name="Generic Functions" src="desktop/generic.js" />
    <a:wbody>
to:
    # get user role
    iUserRole = objectAccess.getAccess(self, context.session.user)
    if data.has_key('__rolesinherited') and iUserRole == objectAccess.COORDINATOR:
        oNewItem.inheritRoles = data.pop('__rolesinherited')
        if not oNewItem.inheritRoles:
            acl = data.pop('__acl')
            if acl:
                security = {}
                for descriptor in acl:
                    security[descriptor['id']] = int(descriptor['role'])
                oNewItem.security = security

    # set props
    for prop in data:
        oAttr = getattr(oNewItem, prop)
        if isinstance(oAttr, datatypes.File):
            if data[prop]['tempfile']:
                oAttr.filename = data[prop]['filename']
                sPath = context.server.temp_folder + '/' + data[prop]['tempfile']
                oAttr.loadFromFile(sPath)
                os.remove(sPath)
        elif isinstance(oAttr, datatypes.Date):
            oAttr.value = data[prop].value
        elif isinstance(oAttr, datatypes.Integer):
            oAttr.value = int(data[prop])
        else:
            oAttr.value = data[prop]

    txn = db.getTransaction()
    oNewItem.appendTo(self, txn)
    txn.commit()
Changed line 430 from:
        <a:form action="%(URI)s" method="%(METHOD)s" padding="4,4,4,4">
to:
    return oNewItem.id
Changed lines 434-468 from:
            %(HIDDEN)s
            <a:label caption="Recipe name:"/>
            <a:field left="80" width="380" name="displayName"
              value="%(DISPLAYNAME)s" />
            <a:label caption="Description:" top="25" />
            <a:field left="80" top="24" width="380" name="description"
              value="%(DESCRIPTION)s" />
            <a:hr top="50" width="100" />
            <a:label top="58" caption="Preparation time (min):" />
            <a:spinbutton name="preparationTime" top="55" left="110" width="50" 
              max="240" value="%(PREPARATION_TIME)d" editable="true" />
            <a:label top="58" left="200" caption="Servings:" />
            <a:spinbutton name="servings" top="55" left="250" width="40" max="12"
              editable="true" value="%(SERVINGS)d" />
            <a:label top="58" left="320" caption="Rating:" />
            <a:spinbutton name="rating" top="55" left="360" width="40" max="10"
              editable="true" value="%(RATING)d" />
            <a:tabpane top="90" width="100" height="220">
                <a:tab caption="Ingredients">
                    <a:field type="textarea" name="ingredients" width="100"
                      height="100">%(INGREDIENTS)s</a:field>
                </a:tab>
                <a:tab caption="Instructions">
                    <a:field type="textarea" name="instructions" width="100"
                      height="100">%(INSTRUCTIONS)s</a:field>
                </a:tab>
                <a:tab caption="Categories">
                    <a:box width="100" height="100" orientation="v">
                        <a:selectlist name="categories" multiple="true" posts="all" height="-1">
                            <a:prop name="SelectFrom?" value="Categories/Recipe categories"></a:prop>
                            <a:prop name="RelatedCC?" value="org.innoscript.desktop.schema.common.Category"/>
                            %(CATEGORIES)s
                        </a:selectlist>
                        <a:rect height="24">
                            <a:flatbutton width="70" height="22" caption="Add"
to:

...

Changed lines 436-443 from:

to:

The "data" argument contains the form data. It is a Python dictionary containing the field names and values.

This method requires an additional field posted named "CC". This field contains the type of the object that is going to be appended to the calling container. Each XML-RPC method should always return a value. This remote method returns the ID of the newly created object.

Apart from creating recipes we should also be able to edit them. In order to override the default edit form, we need to override the "properties" web method of the "CookingRecipe" content class. Create a new Python module named "cookingrecipe.py" inside the "org/innoscript/recipemanager/webmethods" folder and add the following code:

Changed lines 445-472 from:
                              onclick="generic.selectItems"></a:flatbutton>
to:

from porcupine import webmethods from porcupine import HttpContext?

from org.innoscript.recipemanager.schema import CookingRecipe?

@webmethods.quixui(of_type=CookingRecipe?,

                   template='ui.RecipeForm?.quix')

def properties(self):

    categories = []
    # build the categories
    for category in self.categories.getItems():
        categories.extend([category.__image__, category.id, category.displayName.value])
    return {
      'URI': HttpContext?.current().request.SCRIPT_NAME + '/' + self.id,
      'METHOD': 'update',
      'TITLE': 'Recipe properties',
      'HIDDEN': '',
      'DISPLAYNAME': self.displayName.value,
      'DESCRIPTION': self.description.value,
      'RATING': self.rating.value,
      'PREPARATION_TIME': self.preparationTime.value,
      'SERVINGS': self.servings.value,
      'INGREDIENTS': self.ingredients.value,
      'INSTRUCTIONS': self.instructions.value,
      'CATEGORIES': ';'.join(categories),
      'ICON': self.__image__,
      'ACTION': 'Update',
    }
Added lines 474-480:

Respectively, when editing an existing recipe, we call the "update" method of the "porcupine.systemObjects.Item" content type as defined in the "org/innoscript/desktop/webmethods/baseitem.py".

The "CATEGORIES" key of the dictionary returned contains a semicolon delimited string of "image;id;name" triplets; this format is required by the "RelatorN?" editor custom widget.

Before restarting the server we need to import our newly created web methods for these to take effect. First edit the "org/innoscript/recipemanager/__init__.py" file by adding the following import:

Changed line 483 from:
                            <a:flatbutton left="80" width="70" height="22" caption="Remove"
to:

from org.innoscript.recipemanager.webmethods import *

Changed lines 485-488 from:

to:

The edit the "org/innoscript/recipemanager/webmethods/__init__.py" file:

Changed line 490 from:
                              onclick="generic.removeSelectedItems"></a:flatbutton>
to:

__all__ = ['cookingrecipe', 'recipecontainer']

Added lines 492-504:

Restart the server. If everything is in place, you should see the following form when choosing to create a new recipe:

5. The Recipe Manager application

Apart from the custom forms and the custom content types, we will also need a custom UI for our application tailored to the given requirements. This is accomplished by creating a new application object. The Porcupine desktop provides generic interfaces and almost in every case, will not meet the requirements analysis.

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

Changed lines 507-512 from:
                        </a:rect>
                    </a:box>
                </a:tab>
            </a:tabpane>
        </a:form>
    </a:wbody>
to:

<config>

	<dirs>
		<dir name="__quix" path="quix"/>
		<dir name="desktop" path="org/innoscript/desktop"/>
		<dir name="hypersearch" path="org/innoscript/hypersearch"/>
		<dir name="usermgmnt" path="org/innoscript/usermgmnt"/>
		<dir name="queryperformer" path="org/innoscript/queryperformer"/>
Changed line 517 from:
    <a:dlgbutton onclick="generic.submitForm" width="70" height="22"
to:
		<dir name="recipemanager" path="org/innoscript/recipemanager"/>
Changed lines 521-524 from:
      caption="%(ACTION)s" default="true" />
    <a:dlgbutton onclick="__closeDialog__" width="70" height="22"
      caption="Cancel" />

</a:dialog>

to:
	</dirs>

</config>

Changed lines 525-526 from:

Initially, this form imports "generic.js"; one of the QuiX desktop's JavaScript files. From this script (org/innoscript/desktop/generic.js) we reuse the "generic.submitForm" function. This function simply submits the first QuiX form found inside the current dialog.

to:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

Changed lines 529-535 from:

... generic.submitForm = function(evt, w) {

	var oDialog = w.getParentByType(Dialog);
	var oForm = oDialog.getWidgetsByType(Form)[0];
	oForm.submit(__closeDialog__);

} ...

to:

<config>

	<context path="recipemanager.quix"
		method="GET"
		client=".*"
		lang=".*"
		action="recipemanager.quix"/>
	<context path="recipemanager.js"
		method="GET"
		client=".*"
		lang=".*"
		action="recipemanager.js"/>

</config>

Changed lines 543-549 from:

The "submit" method of a QuiX form takes one argument; the function to call asynchronously, once the browser has the submission response. In this case the dialog is simply closed.

The form's "action" parameter is the URL where the form data get posted. This is usually the HTTP address of a Porcupine object whose type has an XMLRPC servlet registered in the "store.xml" configuration file (visit http://wiki.innoscript.org/index.php/Articles/RequestProcessingPipeline to see how each object is accessible over HTTP).

The form's "method" parameter is the name of the method of the XMLRPC servlet to call. Since this form is used for both creating and editing recipes the name of the method is diferrent in each aspect. When creating a new recipe we call the "create" method of the "org.innoscript.desktop.XMLRPC.ContainerGeneric" XMLRPC servlet.

to:
Changed lines 554-574 from:

...

to:

<?xml version="1.0" encoding="UTF-8"?> <a:window xmlns:a="http://www.innoscript.org/quix" title="Recipe manager" resizable="true" close="true" minimize="true" maximize="true" img="" width="640" height="480" left="center" top="center">

  <a:script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <a:wbody>
    <a:box width="100%" height="100%" orientation="v" spacing="0">
      <a:toolbar height="28">
        <a:tbbutton caption="Create recipe folder" width="120">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </a:tbbutton>
        <a:tbbutton caption="Create recipe" width="80">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.Recipe"/>
        </a:tbbutton>
        <a:tbsep/>
        <a:tbbutton caption="Refresh" width="60"/>
      </a:toolbar>
      <a:splitter height="-1" orientation="v" interactive="true">
        <a:pane length="200">
          <a:outlookbar width="100%" height="100%">
            <a:tool caption="Recipes">
Changed lines 578-579 from:

class ContainerGeneric?(ItemGeneric?):

  def create(self, data):
to:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
                <a:treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true"
                img="desktop/images/folder.gif" caption="Recipes"/>
Changed lines 584-605 from:
    # create new item
to:
              </a:foldertree>
            </a:tool>
            <a:tool caption="Search" bgcolor="gray">
              <a:label caption="Recipe title contains:"/>
              <a:field id="title" left="5" top="20" width="160"/>

              <a:label top="50" caption="Preparation time is less than"/>
              <a:field id="preparationTime" left="5" top="70" width="30"/>
              <a:label top="72" left="40" caption="min."/>

              <a:label top="100" caption="The recipe's rating is greater than"/>
              <a:spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

              <a:label top="150" caption="Recipe ingredients contain:"/>
              <a:field id="ingredients" top="170" left="5" width="160"/>
              <a:label top="190" caption="(comma separated list)" style="font-style:italic"/>

              <a:button top="220" left="center" width="60" height="28" caption="Search"/>
            </a:tool>
          </a:outlookbar>
        </a:pane>
        <a:pane length="-1">
Changed line 609 from:
    oNewItem = misc.getClassByName(data.pop('CC'))()
to:
          <a:listview width="100%" height="100%" id="list">
Changed lines 613-641 from:
    # get user role
    iUserRole = objectAccess.getAccess(self.item, self.session.user)
    if data.has_key('__rolesinherited') and iUserRole ==     objectAccess.COORDINATOR:
      oNewItem.inheritRoles = data.pop('__rolesinherited')
      if not oNewItem.inheritRoles:
        acl = data.pop('__acl')
        if acl:
          security = {}
          for descriptor in acl:
            security[descriptor['id']] = int(descriptor['role'])
          oNewItem.security = security
    # set props
    for prop in data:
      oAttr = getattr(oNewItem, prop)
      if isinstance(oAttr, datatypes.File):
        if data[prop]['tempfile']:
          oAttr.filename = data[prop]['filename']
          sPath = self.server.temp_folder + '/' + data[prop]['tempfile']
          oAttr.loadFromFile(sPath)
          os.remove(sPath)
      elif isinstance(oAttr, datatypes.Date):
        oAttr.value = data[prop].value
      else:
        oAttr.value = data[prop]

    txn = self.server.store.getTransaction()
    oNewItem.appendTo(self.item.id, txn)
    txn.commit()
to:
            <a:listheader>
              <a:column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
              <a:column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
              <a:column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
              <a:column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
              <a:column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
            </a:listheader>
          </a:listview>
        </a:pane>
      </a:splitter>
    </a:box>
  </a:wbody>

</a:window>

Changed lines 627-633 from:

to:

The folder tree widget and the list view have been given an ID for them to become easily accessible from our scripts. The "method" attribute is the XML-RPC method to call whenever the user expands a tree node. Notice that the interface includes a JavaScript file with a "script" node right after the window's opening tag. Thus, create an empty JavaScript file named "recipemanager.js" inside the application folder.

Let's start by adding some event handlers.

Deleted lines 634-637:
    return True

@] [@

Added lines 636-637:

<a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
Changed lines 639-650 from:

The "data" argument contains the form data. It is a Python dictionary containing the field names and values.

This method requires an additional field posted named "CC". This field contains the type of the object that is going to be appended to the calling container. Each XML-RPC method should always return a value. This method simply returns "True" only if the object is created and appended to the container successfully. This way, the browser knows that the request has been succefully completed.

Also notice, that the "ContainerGeneric" servlet is a subclass of the "ItemGeneric" servlet. Besides the container specific methods defined, a container can also be updated, copied or renamed just like any other Porcupine object.

Respectively, when editing an existing recipe, we call the "update" method of the "org.innoscript.desktop.XMLRPC.ItemGeneric" XMLRPC servlet.

From the "generic.js" file we also reuse two JavaScript functions named "generic.selectItems" and "generic.removeSelectedItems".

to:

Changed lines 641-646 from:

... generic.selectItems = function(evt, w) {

  var oDialog = w.getParentByType(Dialog);
  var oTarget = w.parent.parent.getWidgetsByType(SelectList?)[0];
  var sFolderURI = oTarget.attributes.SelectFrom?;
  var sCC = oTarget.attributes.RelatedCC?;
to:
    onclick="recipemanager.createItem">
Changed line 643 from:

to:

Changed lines 645-646 from:
  generic.selectObjects(oDialog, oTarget, generic.addSelectionToList,
    sFolderURI, sCC, 'true');
to:
    ...
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
Changed line 649 from:

to:

Changed lines 651-662 from:

} ... generic.selectObjects = function(win, target, select_func, startFrom, contentclass, multiple) {

  ...

} ... generic.removeSelectedItems = function(evt, w) {

  var oSelectList = w.parent.parent.getWidgetsByType(SelectList?)[0];
  oSelectList.removeSelected();

} ...

to:
    onclick="recipemanager.createItem">
Deleted lines 652-672:

The "generic.selectItems" function displays the object select dialog by calling the "generic.selectObjects" function. The auto-generated form servlet uses this function for "ReferenceN" or "RelatorN" data types. The arguments given to the "generic.selectObjects" function are:

  • win: the opener
  • target: the target widget (in this case, the select list included in the recipe form)
  • select_func: the function to call when the user presses the "Select" button
  • startFrom: The ID of the root node of the tree (in this case, the "SelectFrom" custom attribute of the select list on the recipe form)
  • contentclass: The type of the Porcupine objects that we can select
  • multiple: "true" if multiple selection is allowed, else "false"

Restart the server. If everything is in place, you should see the following form when choosing to create a new recipe:

5. The Recipe Manager application

Apart from the custom forms and the custom content types, we will also need a custom UI for our application tailored to the given requirements. This is accomplished by creating a new application object. The Porcupine desktop provides generic interfaces and almost in every case, will not meet the requirements analysis.

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

Changed lines 655-661 from:

<config>

	<dirs>
		<dir name="__quix" path="quix"/>
		<dir name="desktop" path="org/innoscript/desktop"/>
		<dir name="hypersearch" path="org/innoscript/hypersearch"/>
		<dir name="usermgmnt" path="org/innoscript/usermgmnt"/>
		<dir name="queryperformer" path="org/innoscript/queryperformer"/>
to:
    ...
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
Changed line 662 from:
		<dir name="recipemanager" path="org/innoscript/recipemanager"/>
to:
    onclick="recipemanager.refresh_onclick" />
Changed lines 666-667 from:
	</dirs>

</config>

to:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

Changed lines 670-673 from:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

to:

Changed lines 672-684 from:

<config>

	<context path="recipemanager.quix"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.quix"/>
	<context path="recipemanager.js"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.js"/>

</config>

to:
  onload="recipemanager.loadRecipes">
Deleted lines 673-682:
Changed lines 676-696 from:

<?xml version="1.0" encoding="UTF-8"?> <a:window xmlns:a="http://www.innoscript.org/quix" title="Recipe manager" resizable="true" close="true" minimize="true" maximize="true" img="" width="640" height="480" left="center" top="center">

  <a:script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <a:wbody>
    <a:box width="100%" height="100%" orientation="v" spacing="0">
      <a:toolbar height="28">
        <a:tbbutton caption="Create recipe folder" width="120">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </a:tbbutton>
        <a:tbbutton caption="Create recipe" width="80">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.Recipe"/>
        </a:tbbutton>
        <a:tbsep/>
        <a:tbbutton caption="Refresh" width="60"/>
      </a:toolbar>
      <a:splitter height="-1" orientation="v" interactive="true">
        <a:pane length="200">
          <a:outlookbar width="100%" height="100%">
            <a:tool caption="Recipes">
to:
  ...
Changed lines 678-681 from:

to:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 683-685 from:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
                <a:treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true"
                img="desktop/images/folder.gif" caption="Recipes"/>
to:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
Changed line 689 from:

to:

Changed lines 691-712 from:
              </a:foldertree>
            </a:tool>
            <a:tool caption="Search" bgcolor="gray">
              <a:label caption="Recipe title contains:"/>
              <a:field id="title" left="5" top="20" width="160"/>

              <a:label top="50" caption="Preparation time is less than"/>
              <a:field id="preparationTime" left="5" top="70" width="30"/>
              <a:label top="72" left="40" caption="min."/>

              <a:label top="100" caption="The recipe's rating is greater than"/>
              <a:spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

              <a:label top="150" caption="Recipe ingredients contain:"/>
              <a:field id="ingredients" top="170" left="5" width="160"/>
              <a:label top="190" caption="(comma separated list)" style="font-style:italic"/>

              <a:button top="220" left="center" width="60" height="28" caption="Search"/>
            </a:tool>
          </a:outlookbar>
        </a:pane>
        <a:pane length="-1">
to:
  var sCC = w.attributes.cc;
Changed line 693 from:

to:

Changed line 695 from:
          <a:listview width="100%" height="100%" id="list">
to:
  var id = oTree.getSelection().getId();
Changed line 697 from:

to:

Changed lines 699-711 from:
            <a:listheader>
              <a:column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
              <a:column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
              <a:column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
              <a:column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
              <a:column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
            </a:listheader>
          </a:listview>
        </a:pane>
      </a:splitter>
    </a:box>
  </a:wbody>

</a:window>

to:
  oWin.showWindow(id + '?cmd=new&cc=' + sCC,
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );
Deleted lines 704-709:

The folder tree widget and the list view have been given an ID for them to become easily accessible from our scripts. The "method" attribute is the XML-RPC method to call whenever the user expands a tree node. Notice that the interface includes a JavaScript file with a "script" node right after the window's opening tag. Thus, create an empty JavaScript file named "recipemanager.js" inside the application folder.

Let's start by adding some event handlers.

Changed lines 707-709 from:

... <a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
to:

}

recipemanager.dialogClose = function(dlg) {

  if (dlg.buttonIndex == 0) {
    recipemanager.refreshList(dlg.opener);
  }

}

recipemanager.refresh_onclick = function(evt, w) {

  recipemanager.loadRecipes(w);

}

Changed line 721 from:
    onclick="recipemanager.createItem">
to:

recipemanager.loadRecipes = function(w) {

Changed lines 725-727 from:
    ...
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
to:
  var appWin = w.getParentByType(Window);
  recipemanager.refreshList(appWin);

}

recipemanager.refreshList = function(appWin) {

  var oTree = appWin.getWidgetById('tree');
  var id = oTree.getSelection().getId();
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
Changed lines 739-741 from:
    onclick="recipemanager.createItem">
to:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
Changed lines 745-748 from:
    ...
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
to:
  xmlrpc.callmethod('executeOqlCommand', sOql);

}

recipemanager.updateList = function(req) {

Changed lines 752-753 from:
    onclick="recipemanager.refresh_onclick" />
to:
  req.callback_info.dataSet = req.response;
  req.callback_info.refresh();
Changed lines 757-759 from:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

to:

}

Changed lines 759-776 from:

to:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "a:prop" nodes. The location of the new object is determined by the current tree selection. The "showWindow" method loads an XML UI definition for a new window or dialog from a specified URL, and opens it as a child window. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the dialog created. In this event, the root widget - the form dialog - will be added to the child windows of our application's main window.

The "recipemanager.loadrecipes" handler is called once when the list view is loaded. This handler loads the recipes from the selected container and displays them in the list view. The "onload" event handler, unlike the other events, does not accept the two common event handler arguments, the event object and the widget, but instead accepts only the widget that fires the event. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

The "recipemanager.refreshList" function executes the OQL query that returns the recipes contained inside the selected tree container. Inside this function we send a new XMLRPC request to the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This function is always called having the request object as the first argument. The "callback_info" attribute is a placeholder for anything we would like to access inside the callback function. In this case, the "callback_info" is a reference to the list view widget.

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XMLRPC call.

Afterwards, we must synchronize the tree with the list view. This means that for each new tree selection we should update the list view accordingly. This is accomplished by adding the following event handler:

Changed lines 778-779 from:
  onload="recipemanager.loadRecipes">
to:

... <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

Changed line 781 from:

to:

Changed line 783 from:
  ...
to:
  onselect="recipemanager.loadRecipes">
Deleted lines 784-786:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 787-791 from:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
to:
  ...

</a:foldertree> ...

Changed lines 791-794 from:

to:

Next, we implement the search functionality. We start by adding a new "onclick" event handler on the search button:

Changed lines 796-797 from:
  var sCC = w.attributes.cc;
to:

... <a:button top="220" left="center" width="60" height="28" caption="Search"

Changed line 799 from:

to:

Changed line 801 from:
  var id = oTree.getSelection().getId();
to:
  onclick="recipemanager.search" />
Changed line 803 from:

to:

Changed lines 805-809 from:
  oWin.showWindow(id + '?cmd=new&cc=' + sCC,
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );
to:

...

Added lines 807-809:

Add the following function inside the "recipemanager.js" file:

Changed lines 812-822 from:

}

recipemanager.dialogClose = function(dlg) {

  if (dlg.buttonIndex == 0) {
    recipemanager.refreshList(dlg.opener);
  }

}

recipemanager.refresh_onclick = function(evt, w) {

  recipemanager.loadRecipes(w);

}

to:

recipemanager.search = function(evt, w) {

  var displayName = w.parent.getWidgetById('title').getValue();
  var preparationTime = w.parent.getWidgetById('preparationTime').getValue();
  var rating = w.parent.getWidgetById('rating').getValue
  var ingredients = w.parent.getWidgetById('ingredients').getValue();
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
Changed line 821 from:

recipemanager.loadRecipes = function(w) {

to:
             "modified from deep('/Recipes')";
Changed lines 825-826 from:
  var appWin = w.getParentByType(Window);
  recipemanager.refreshList(appWin);
to:
  var listView = w.getParentByType(Window).getWidgetById('list');
  var conditions = ["contentclass='schemas.org.innoscript.recipemanager.Recipe'"];
  if (displayName!='') {
    conditions.push("'" + displayName + "' in displayName");
  }
  if (preparationTime!='') {
    conditions.push("preparationTime < " + preparationTime);
  }
  if (rating>0) {
    conditions.push("rating > " + rating);
  }
  if (ingredients!='') {
    var arrIng = ingredients.split(',');
    for (var i=0; i<arrIng.length; i++) {
      conditions.push("'" + arrIng[i] + "' in ingredients");
    }
  }
  if (conditions.length > 0) {
    sOql += ' where ' + conditions.join(' and ');
    var xmlrpc = new XMLRPCRequest?(QuiX?.root);
    xmlrpc.oncomplete = recipemanager.updateList;
    xmlrpc.callback_info = listView;
    xmlrpc.callmethod('executeOqlCommand', sOql);
  }
Deleted lines 849-856:

recipemanager.refreshList = function(appWin) {

  var oTree = appWin.getWidgetById('tree');
  var id = oTree.getSelection().getId();
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
Changed lines 851-855 from:

to:

The above function forms the OQL query according to the criteria set by the user on the search form. Notice that we search all the recipes, by using the "deep" operator at the select scope. Also when the user double clicks on the list view, the selected recipe dialog should open:

Changed lines 857-859 from:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
to:

<a:listview multiple="true" width="100" height="100" id="list"

  onload="recipemanager.loadRecipes"
Changed line 860 from:

to:

Changed lines 862-865 from:
  xmlrpc.callmethod('executeOqlCommand', sOql);

}

recipemanager.updateList = function(req) {

to:
  ondblclick="recipemanager.loadItem">
Changed lines 864-867 from:

to:

The following event handler should be added to the script of the application:

Changed lines 869-870 from:
  req.callback_info.dataSet = req.response;
  req.callback_info.refresh();
to:

recipemanager.loadItem = function(evt, w, recipe) {

  var oWin = w.getParentByType(Window);
  oWin.showWindow(recipe.id + "?cmd=properties",
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

}

Added lines 878-883:

This handler opens the recipe form when a recipe item is double clicked on the list. It also attaches an "onclose" handler to the recipe form. In case of the recipe being updated this handler reloads the list items.

The final enhancement is the addition of a context menu. Open the application object and add the following lines to the interface definition:

Changed line 886 from:

}

to:

<a:pane length="-1">

Changed lines 888-905 from:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "a:prop" nodes. The location of the new object is determined by the current tree selection. The "showWindow" method loads an XML UI definition for a new window or dialog from a specified URL, and opens it as a child window. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the dialog created. In this event, the root widget - the form dialog - will be added to the child windows of our application's main window.

The "recipemanager.loadrecipes" handler is called once when the list view is loaded. This handler loads the recipes from the selected container and displays them in the list view. The "onload" event handler, unlike the other events, does not accept the two common event handler arguments, the event object and the widget, but instead accepts only the widget that fires the event. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

The "recipemanager.refreshList" function executes the OQL query that returns the recipes contained inside the selected tree container. Inside this function we send a new XMLRPC request to the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This function is always called having the request object as the first argument. The "callback_info" attribute is a placeholder for anything we would like to access inside the callback function. In this case, the "callback_info" is a reference to the list view widget.

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XMLRPC call.

Afterwards, we must synchronize the tree with the list view. This means that for each new tree selection we should update the list view accordingly. This is accomplished by adding the following event handler:

to:

Changed lines 890-891 from:

... <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

to:
  <a:contextmenu onshow="recipemanager.contextmenu_onshow">
    <a:menuoption caption="Open" onclick="recipemanager.openRecipe" />
    <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe" />
  </a:contextmenu>
Changed line 895 from:

to:

Changed lines 897-901 from:
  onselect="recipemanager.loadRecipes">
to:
  <a:listview width="100" height="100" id="list"
    onload="recipemanager.loadRecipes" ondblclick="recipemanager.loadItem">
    ...
  </a:listview>

</a:pane>

Added lines 903-905:
Changed lines 908-910 from:
  ...

</a:foldertree> ...

to:

recipemanager.contextmenu_onshow = function(menu) {

Changed lines 910-913 from:

Next, we implement the search functionality. We start by adding a new "onclick" event handler on the search button:

to:

Changed lines 912-913 from:

... <a:button top="220" left="center" width="60" height="28" caption="Search"

to:
  var oList = menu.owner.getWidgetById('list');
Changed line 914 from:

to:

Changed lines 916-944 from:
  onclick="recipemanager.search" />
to:
  menu.options[0].disabled = (oList.selection.length == 0); //open
  menu.options[1].disabled = (oList.selection.length == 0); //properties

}

recipemanager.openRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var oWin = oList.getParentByType(Window);
  var selectedRecipe = oList.getSelection();
  oWin.showWindow(selectedRecipe.id + "?cmd=properties",
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

}

recipemanager.deleteRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var selectedRecipe = oList.getSelection();
  var desktop = document.desktop;
  var _deleteItem = function(evt, w) {
    var oMsgBox = w.getParentByType(Dialog);
    var xmlrpc = new XMLRPCRequest?(QuiX?.root + selectedRecipe.id);
    xmlrpc.oncomplete = function(req) {
      oMsgBox.close();
      recipemanager.loadRecipes(oList);
    }
    xmlrpc.onerror = function(req) { oMsgBox.close(); }
    xmlrpc.callmethod('delete');
  }
Changed line 946 from:

to:

Changed line 948 from:

...

to:
  desktop.msgbox(w.getCaption(),
Deleted lines 949-951:

Add the following function inside the "recipemanager.js" file:

Changed lines 952-957 from:

recipemanager.search = function(evt, w) {

  var displayName = w.parent.getWidgetById('title').getValue();
  var preparationTime = w.parent.getWidgetById('preparationTime').getValue();
  var rating = w.parent.getWidgetById('rating').getValue
  var ingredients = w.parent.getWidgetById('ingredients').getValue();
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
to:
    "Are you sure you want to delete this recipe?",
    [
      [desktop.attributes['YES'], 60, _deleteItem],
      [desktop.attributes['NO'], 60]
    ],
    'desktop/images/messagebox_warning.gif', 'center', 'center', 260, 112);

}

Changed lines 960-985 from:

to:
Changed lines 987-989 from:
             "modified from deep('/Recipes')";
to:

[package] name=RecipeManager? version=0.0.4

Added lines 991-993:

The first section of the package definition file has information about the package (its name and its version). Please notice that the name of the package and the value of the name attribute of the package node in the "store.xml" configuration file must be identical.

Changed lines 996-1020 from:
  var listView = w.getParentByType(Window).getWidgetById('list');
  var conditions = ["contentclass='schemas.org.innoscript.recipemanager.Recipe'"];
  if (displayName!='') {
    conditions.push("'" + displayName + "' in displayName");
  }
  if (preparationTime!='') {
    conditions.push("preparationTime < " + preparationTime);
  }
  if (rating>0) {
    conditions.push("rating > " + rating);
  }
  if (ingredients!='') {
    var arrIng = ingredients.split(',');
    for (var i=0; i<arrIng.length; i++) {
      conditions.push("'" + arrIng[i] + "' in ingredients");
    }
  }
  if (conditions.length > 0) {
    sOql += ' where ' + conditions.join(' and ');
    var xmlrpc = new XMLRPCRequest?(QuiX?.root);
    xmlrpc.oncomplete = recipemanager.updateList;
    xmlrpc.callback_info = listView;
    xmlrpc.callmethod('executeOqlCommand', sOql);
  }

}

to:

[files]

Changed lines 999-1001 from:

The above function forms the OQL query according to the criteria set by the user on the search form. Notice that we search all the recipes, by using the "deep" operator at the select scope. Also when the user double clicks on the list view, the selected recipe dialog should open:

to:

The second section contains the relative paths to any files that need to be included in the package. Leave this section blank.

Changed lines 1003-1004 from:

<a:listview multiple="true" width="100" height="100" id="list"

  onload="recipemanager.loadRecipes"
to:

[dirs]

Changed lines 1005-1008 from:

to:

This section adds directories to the package.

Changed lines 1010-1011 from:
  ondblclick="recipemanager.loadItem">
to:

[pubdir] 1=recipemanager

Changed lines 1014-1015 from:

The following event handler should be added to the script of the application:

to:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section, the package will contain all the files that need to be distributed.

Changed lines 1018-1025 from:

recipemanager.loadItem = function(evt, w, recipe) {

  var oWin = w.getParentByType(Window);
  oWin.showWindow(recipe.id + "?cmd=properties",
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

}

to:

[items] application=[Put here the ID of the Recipe Manager application] recipes_folder=[Put here the ID of the "/Recipes" folder] recipe_categories_folder=[Put here the ID of the "/Categories/Recipe categories" folder]

Changed lines 1025-1029 from:

This handler opens the recipe form when a recipe item is double clicked on the list. It also attaches an "onclose" handler to the recipe form. In case of the recipe being updated this handler reloads the list items.

The final enhancement is the addition of a context menu. Open the application object and add the following lines to the interface definition:

to:

In this section we include all the Porcupine objects to export in the package, including the application object itself. Replace the highlighted portions with the object IDs printed on their properties dialog.

Changed lines 1030-1032 from:

<a:pane length="-1">

to:

[scripts] postinstall=PKG/postinstall.py uninstall=PKG/uninstall.py

Changed lines 1034-1039 from:

to:
Changed lines 1041-1044 from:
  <a:contextmenu onshow="recipemanager.contextmenu_onshow">
    <a:menuoption caption="Open" onclick="recipemanager.openRecipe" />
    <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe" />
  </a:contextmenu>
to:
  1. RecipeManager? post installation script

from porcupine.administration import codegen ce = codegen.DatatypeEditor?('org.innoscript.desktop.schema.properties.CategoryObjects?')

Changed line 1045 from:

to:

Changed lines 1047-1051 from:
  <a:listview width="100" height="100" id="list"
    onload="recipemanager.loadRecipes" ondblclick="recipemanager.loadItem">
    ...
  </a:listview>

</a:pane>

to:

ce.relCc.append('org.innoscript.recipemanager.schema.Recipe')

Deleted lines 1048-1050:
Changed line 1051 from:

recipemanager.contextmenu_onshow = function(menu) {

to:

ce.commitChanges()

Changed lines 1053-1057 from:

to:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects" class). To be precise, it adds the "org.innoscript.recipemanager.schema.Recipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type). Similarly, inside the "PKG" folder create a new Python script named "uninstall.py". Open the file and add the following lines:

Changed lines 1059-1061 from:
  var oList = menu.owner.getWidgetById('list');
to:
  1. RecipeManager? uninstallation script

from porcupine.administration import codegen ce = codegen.DatatypeEditor?('org.innoscript.desktop.schema.properties.CategoryObjects?')

Changed line 1063 from:

to:

Changed lines 1065-1093 from:
  menu.options[0].disabled = (oList.selection.length == 0); //open
  menu.options[1].disabled = (oList.selection.length == 0); //properties

}

recipemanager.openRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var oWin = oList.getParentByType(Window);
  var selectedRecipe = oList.getSelection();
  oWin.showWindow(selectedRecipe.id + "?cmd=properties",
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

}

recipemanager.deleteRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var selectedRecipe = oList.getSelection();
  var desktop = document.desktop;
  var _deleteItem = function(evt, w) {
    var oMsgBox = w.getParentByType(Dialog);
    var xmlrpc = new XMLRPCRequest?(QuiX?.root + selectedRecipe.id);
    xmlrpc.oncomplete = function(req) {
      oMsgBox.close();
      recipemanager.loadRecipes(oList);
    }
    xmlrpc.onerror = function(req) { oMsgBox.close(); }
    xmlrpc.callmethod('delete');
  }
to:

ce.relCc.remove('org.innoscript.recipemanager.schema.Recipe')

Changed line 1067 from:

to:

Changed line 1069 from:
  desktop.msgbox(w.getCaption(),
to:

ce.commitChanges()

Changed lines 1071-1192 from:
to:
May 03, 2008, at 02:53 AM by Tassos Koutsovassilis -
Added lines 3-6:

This tutorial is outdated. Please check back again for a new updated version.

May 29, 2007, at 11:53 PM by Tassos Koutsovassilis -
Changed lines 509-510 from:

Initially, this form imports one of the QuiX desktop JavaScript files. One of the functions we reuse from this script (org/innoscript/desktop/generic.js) is the "generic.submitForm" function. This function simply submits the first QuiX form found inside the current dialog.

to:

Initially, this form imports "generic.js"; one of the QuiX desktop's JavaScript files. From this script (org/innoscript/desktop/generic.js) we reuse the "generic.submitForm" function. This function simply submits the first QuiX form found inside the current dialog.

May 29, 2007, at 11:51 PM by Tassos Koutsovassilis -
Changed lines 157-158 from:
to:
Changed line 1197 from:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects?" class). To be precise, it adds the "org.innoscript.recipemanager.schema.Recipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type).

to:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects" class). To be precise, it adds the "org.innoscript.recipemanager.schema.Recipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type).

May 29, 2007, at 11:50 PM by Tassos Koutsovassilis -
Changed lines 351-352 from:

browser GETs? on a "RecipeContainer" object with the "cmd" query parameter set to "new" and the query string contains the string "org.innoscript.recipemanager.schema.RecipeContainer?" (this is the

to:

browser GETs on a "RecipeContainer" object with the "cmd" query parameter set to "new" and the query string contains the string "org.innoscript.recipemanager.schema.RecipeContainer" (this is the

Changed line 356 from:

browser GETs? on a "RecipeContainer" object with the "cmd" query parameter set to "new" but this

to:

browser GETs on a "RecipeContainer" object with the "cmd" query parameter set to "new" but this

May 29, 2007, at 11:47 PM by Tassos Koutsovassilis -
Changed lines 291-293 from:
to:
Deleted line 296:

from org.innoscript.desktop import ui

Changed lines 301-306 from:

recipe, we must register a new QuiX servlet that acts as a splitter. This is because the "RecipeContainer" type accepts both new recipe containers and recipes. When creating a new recipe container, the splitter must get the XML definition output from the "Frm_Auto" servlet, whereas when creating a new recipe the splitter should return the XML definition output from our custom servlet. This is shown below:

to:
Changed lines 311-313 from:

class RecipeContainerSplitter?(XULServlet?):

  def setParams(self):
    sCC = self.request.queryString['cc'][0]
to:

<package name="RecipeManager?">

  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    method="POST"
    param=""
    client="vcXMLRPC"
    lang=".*"
    action="org.innoscript.desktop.XMLRPC.ContainerGeneric?"/>
Changed lines 321-343 from:
    self.params['FORM'] = ''
to:
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    qs="org.innoscript.recipemanager.schema.RecipeContainer?"
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
    lang=".*"
    method="GET"
    param="new"
    action="org.innoscript.desktop.ui.Frm_AutoNew">
      <filter type="porcupine.filters.postProcessing.multilingual.Multilingual"
	      using="org.innoscript.desktop.strings.resources"/>
  </reg>
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
     qs="org.innoscript.recipemanager.schema.Recipe"
     client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
     lang=".*"
     method="GET"
     param="new"
     action="org.innoscript.recipemanager.ui.RecipeForm?"/>
  <reg cc="org.innoscript.recipemanager.schema.Recipe$"
    method="GET"
    param="properties"
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
    lang=".*"
    action="org.innoscript.recipemanager.ui.RecipeForm?"/>
Changed lines 347-352 from:
    if sCC == 'org.innoscript.recipemanager.schema.RecipeContainer?':
      servlet = ui.Frm_AutoNew(self.server, self.session, self.request)
      self.params['FORM'] = servlet.execute()
    elif sCC == 'org.innoscript.recipemanager.schema.Recipe':
      servlet = RecipeForm?(self.server, self.session, self.request)
      self.params['FORM'] = servlet.execute()
to:

<package>

Added lines 350-362:

The first highlighted registration assigns the "Frm_AutoNew" as the servlet to be executed when the browser GETs? on a "RecipeContainer" object with the "cmd" query parameter set to "new" and the query string contains the string "org.innoscript.recipemanager.schema.RecipeContainer?" (this is the new "qs" parameter).

The second highlighted registration assigns the "RecipeForm" as the servlet to be executed when the browser GETs? on a "RecipeContainer" object with the "cmd" query parameter set to "new" but this time when the query string contains the string "org.innoscript.recipemanager.schema.Recipe".

The last registration is for for the "Recipe" type, and renders the recipe form whenever a compatible browser GETs? on an object of this type with the parameter set to "properties".

Changed lines 369-372 from:

In order for the servlet to execute successfully, a file named "ui.RecipeContainerSplitter.quix" must be created in the same directory. The specific servlet acts as a wrapper; the content of the quix file is simply:

to:

Now let's write the "RecipeForm" servlet:

Changed lines 373-413 from:

%(FORM)s

to:

class RecipeForm?(XULServlet?):

  def setParams(self):
    sCC = self.item.contentclass
    sCategories = ''
    sHiddenField = ''
    if sCC == 'org.innoscript.recipemanager.schema.RecipeContainer?':
      # we create a new recipe
      sTitle = 'Create new recipe'
      oRecipe = schema.Recipe()
      # in this case we need an extra hidden field with the type of
      # object we are about to create
      sHiddenField = '<a:field name="CC" type="hidden" ' +         'value="schemas.org.innoscript.recipemanager.Recipe" />'
      sMethod = 'create'
      sAction = 'Create'
    elif sCC == 'org.innoscript.recipemanager.schema.Recipe':
      # we editing an existing one
      oRecipe = self.item
      sMethod = 'update'
      sTitle = 'Recipe properties'
      sAction = 'Update'
    # build the categories options list
    for category in oRecipe.categories.getItems():
      sCategories += '<a:option caption="s" img="%s" />' %
        (category.displayName.value, category.id, category.__image__)
    self.params = {
      'URI': self.request.serverVariables['SCRIPT_NAME'] + '/' + self.item.id,
      'METHOD': sMethod,
      'TITLE': sTitle,
      'HIDDEN': sHiddenField,
      'DISPLAYNAME': oRecipe.displayName.value,
      'DESCRIPTION': oRecipe.description.value,
      'RATING': oRecipe.rating.value,
      'PREPARATION_TIME': oRecipe.preparationTime.value,
      'SERVINGS': oRecipe.servings.value,
      'INGREDIENTS': oRecipe.ingredients.value,
      'INSTRUCTIONS': oRecipe.instructions.value,
      'CATEGORIES': sCategories,
      'ICON': oRecipe.__image__,
      'ACTION': sAction,
    }
Changed lines 418-420 from:
to:

This servlet is called either when the user needs to create a new recipe, or when the user wants to display the properties of an existing recipe. In the first case, the type of the object "called" is the "RecipeContainer" unlike the second case, in which the type of the object is the "Recipe" type.

The QuiX XML definition of the form is a new file named "ui.RecipeForm.quix" inside the "org/innoscript/recipemanager" folder.

Changed lines 428-439 from:

<package name="RecipeManager?">

  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    method="POST"
    param=""
    client="vcXMLRPC"
    lang=".*"
    action="org.innoscript.desktop.XMLRPC.ContainerGeneric?"/>
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
    method="GET"
    param="new"
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
    lang=".*"
to:

<?xml version="1.0" ?> <a:dialog xmlns:a="http://www.innoscript.org/quix" title="%(TITLE)s" img="%(ICON)s" close="true" width="480" height="380" left="30" top="10">

    <a:script name="Generic Functions" src="desktop/generic.js" />
    <a:wbody>
Changed lines 436-442 from:
    action="org.innoscript.recipemanager.ui.RecipeContainerSplitter?"/>
  <reg cc="org.innoscript.recipemanager.schema.Recipe$"
    method="GET"
    param="properties"
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
    lang=".*"
    action="org.innoscript.recipemanager.ui.RecipeForm?"/>
to:
        <a:form action="%(URI)s" method="%(METHOD)s" padding="4,4,4,4">
Changed lines 440-474 from:

<package>

to:
            %(HIDDEN)s
            <a:label caption="Recipe name:"/>
            <a:field left="80" width="380" name="displayName"
              value="%(DISPLAYNAME)s" />
            <a:label caption="Description:" top="25" />
            <a:field left="80" top="24" width="380" name="description"
              value="%(DESCRIPTION)s" />
            <a:hr top="50" width="100" />
            <a:label top="58" caption="Preparation time (min):" />
            <a:spinbutton name="preparationTime" top="55" left="110" width="50" 
              max="240" value="%(PREPARATION_TIME)d" editable="true" />
            <a:label top="58" left="200" caption="Servings:" />
            <a:spinbutton name="servings" top="55" left="250" width="40" max="12"
              editable="true" value="%(SERVINGS)d" />
            <a:label top="58" left="320" caption="Rating:" />
            <a:spinbutton name="rating" top="55" left="360" width="40" max="10"
              editable="true" value="%(RATING)d" />
            <a:tabpane top="90" width="100" height="220">
                <a:tab caption="Ingredients">
                    <a:field type="textarea" name="ingredients" width="100"
                      height="100">%(INGREDIENTS)s</a:field>
                </a:tab>
                <a:tab caption="Instructions">
                    <a:field type="textarea" name="instructions" width="100"
                      height="100">%(INSTRUCTIONS)s</a:field>
                </a:tab>
                <a:tab caption="Categories">
                    <a:box width="100" height="100" orientation="v">
                        <a:selectlist name="categories" multiple="true" posts="all" height="-1">
                            <a:prop name="SelectFrom?" value="Categories/Recipe categories"></a:prop>
                            <a:prop name="RelatedCC?" value="org.innoscript.desktop.schema.common.Category"/>
                            %(CATEGORIES)s
                        </a:selectlist>
                        <a:rect height="24">
                            <a:flatbutton width="70" height="22" caption="Add"
Changed lines 476-484 from:
to:

Changed lines 478-518 from:

class RecipeForm?(XULServlet?):

  def setParams(self):
    sCC = self.item.contentclass
    sCategories = ''
    sHiddenField = ''
    if sCC == 'org.innoscript.recipemanager.schema.RecipeContainer?':
      # we create a new recipe
      sTitle = 'Create new recipe'
      oRecipe = schema.Recipe()
      # in this case we need an extra hidden field with the type of
      # object we are about to create
      sHiddenField = '<a:field name="CC" type="hidden" ' +         'value="schemas.org.innoscript.recipemanager.Recipe" />'
      sMethod = 'create'
      sAction = 'Create'
    elif sCC == 'org.innoscript.recipemanager.schema.Recipe':
      # we editing an existing one
      oRecipe = self.item
      sMethod = 'update'
      sTitle = 'Recipe properties'
      sAction = 'Update'
    # build the categories options list
    for category in oRecipe.categories.getItems():
      sCategories += '<a:option caption="s" img="%s" />' %
        (category.displayName.value, category.id, category.__image__)
    self.params = {
      'URI': self.request.serverVariables['SCRIPT_NAME'] + '/' + self.item.id,
      'METHOD': sMethod,
      'TITLE': sTitle,
      'HIDDEN': sHiddenField,
      'DISPLAYNAME': oRecipe.displayName.value,
      'DESCRIPTION': oRecipe.description.value,
      'RATING': oRecipe.rating.value,
      'PREPARATION_TIME': oRecipe.preparationTime.value,
      'SERVINGS': oRecipe.servings.value,
      'INGREDIENTS': oRecipe.ingredients.value,
      'INSTRUCTIONS': oRecipe.instructions.value,
      'CATEGORIES': sCategories,
      'ICON': oRecipe.__image__,
      'ACTION': sAction,
    }
to:
                              onclick="generic.selectItems"></a:flatbutton>
Deleted lines 479-486:

This servlet is called either when the user needs to create a new recipe, or when the user wants to display the properties of an existing recipe. In the first case, the type of the object "called" is the "RecipeContainer" unlike the second case, in which the type of the object is the "Recipe" type.

The QuiX XML definition of the form is a new file named "ui.RecipeForm.quix" inside the "org/innoscript/recipemanager" folder.

Changed lines 482-487 from:

<?xml version="1.0" ?> <a:dialog xmlns:a="http://www.innoscript.org/quix" title="%(TITLE)s" img="%(ICON)s" close="true" width="480" height="380" left="30" top="10">

    <a:script name="Generic Form Script" src="desktop/ui.Frm_Auto.js" />
    <a:script name="Generic Functions" src="desktop/generic.js" />
    <a:wbody>
to:
                            <a:flatbutton left="80" width="70" height="22" caption="Remove"
Changed line 486 from:
        <a:form action="%(URI)s" method="%(METHOD)s" padding="4,4,4,4">
to:
                              onclick="generic.removeSelectedItems"></a:flatbutton>
Changed lines 490-510 from:
            %(HIDDEN)s
            <a:label caption="Recipe name:"/>
            <a:field left="80" width="380" name="displayName"
              value="%(DISPLAYNAME)s" />
            <a:label caption="Description:" top="25" />
            <a:field left="80" top="24" width="380" name="description"
              value="%(DESCRIPTION)s" />
            <a:hr top="50" width="100" />
            <a:label top="58" caption="Preparation time (min):" />
            <a:spinbutton name="preparationTime" top="55" left="110" width="50" 
              max="240" value="%(PREPARATION_TIME)d" editable="true" />
            <a:label top="58" left="200" caption="Servings:" />
            <a:spinbutton name="servings" top="55" left="250" width="40" max="12"
              editable="true" value="%(SERVINGS)d" />
            <a:label top="58" left="320" caption="Rating:" />
            <a:spinbutton name="rating" top="55" left="360" width="40" max="10"
              editable="true" value="%(RATING)d" />
            <a:tabpane top="90" width="100" height="220">
                <a:tab caption="Ingredients">
                    <a:field type="textarea" name="ingredients" width="100"
                      height="100">%(INGREDIENTS)s</a:field>
to:
                        </a:rect>
                    </a:box>
Changed lines 493-505 from:
                <a:tab caption="Instructions">
                    <a:field type="textarea" name="instructions" width="100"
                      height="100">%(INSTRUCTIONS)s</a:field>
                </a:tab>
                <a:tab caption="Categories">
                    <a:box width="100" height="100" orientation="v">
                        <a:selectlist name="categories" multiple="true" posts="all" height="-1">
                            <a:prop name="SelectFrom?" value="Categories/Recipe categories"></a:prop>
                            <a:prop name="RelatedCC?" value="org.innoscript.desktop.schema.common.Category"/>
                            %(CATEGORIES)s
                        </a:selectlist>
                        <a:rect height="24">
                            <a:flatbutton width="70" height="22" caption="Add"
to:
            </a:tabpane>
        </a:form>
    </a:wbody>
Changed line 499 from:
                              onclick="generic.selectItems"></a:flatbutton>
to:
    <a:dlgbutton onclick="generic.submitForm" width="70" height="22"
Changed lines 503-506 from:
                            <a:flatbutton left="80" width="70" height="22" caption="Remove"
to:
      caption="%(ACTION)s" default="true" />
    <a:dlgbutton onclick="__closeDialog__" width="70" height="22"
      caption="Cancel" />

</a:dialog>

Changed lines 508-511 from:

to:

Initially, this form imports one of the QuiX desktop JavaScript files. One of the functions we reuse from this script (org/innoscript/desktop/generic.js) is the "generic.submitForm" function. This function simply submits the first QuiX form found inside the current dialog.

Changed lines 513-519 from:
                              onclick="generic.removeSelectedItems"></a:flatbutton>
to:

... generic.submitForm = function(evt, w) {

	var oDialog = w.getParentByType(Dialog);
	var oForm = oDialog.getWidgetsByType(Form)[0];
	oForm.submit(__closeDialog__);

} ...

Added lines 521-528:

The "submit" method of a QuiX form takes one argument; the function to call asynchronously, once the browser has the submission response. In this case the dialog is simply closed.

The form's "action" parameter is the URL where the form data get posted. This is usually the HTTP address of a Porcupine object whose type has an XMLRPC servlet registered in the "store.xml" configuration file (visit http://wiki.innoscript.org/index.php/Articles/RequestProcessingPipeline to see how each object is accessible over HTTP).

The form's "method" parameter is the name of the method of the XMLRPC servlet to call. Since this form is used for both creating and editing recipes the name of the method is diferrent in each aspect. When creating a new recipe we call the "create" method of the "org.innoscript.desktop.XMLRPC.ContainerGeneric" XMLRPC servlet.

Changed lines 531-536 from:
                        </a:rect>
                    </a:box>
                </a:tab>
            </a:tabpane>
        </a:form>
    </a:wbody>
to:

...

Changed lines 535-536 from:
    <a:dlgbutton onclick="autoform.submit" width="70" height="22"
to:

class ContainerGeneric?(ItemGeneric?):

  def create(self, data):
Changed lines 540-543 from:
      caption="%(ACTION)s" default="true" />
    <a:dlgbutton onclick="__closeDialog__" width="70" height="22"
      caption="Cancel" />

</a:dialog>

to:
    # create new item
Changed lines 542-545 from:

Initially, this form imports two QuiX desktop JavaScript files. The first script is the one used by the forms generated automatically by the "Frm_Auto" servlet. The function we reuse from this script (org/innoscript/desktop/ui.Frm_Auto.js) is the "autoform.submit" function. This function simply submits the first QuiX form found inside the current dialog.

to:

Changed lines 544-546 from:

... autoform.submit = function(evt, w) {

  var oForm = w.getParentByType(Dialog).getWidgetsByType(Form)[0];
to:
    oNewItem = misc.getClassByName(data.pop('CC'))()
Changed line 546 from:

to:

Changed lines 548-576 from:
  oForm.submit(autoform.update);
to:
    # get user role
    iUserRole = objectAccess.getAccess(self.item, self.session.user)
    if data.has_key('__rolesinherited') and iUserRole ==     objectAccess.COORDINATOR:
      oNewItem.inheritRoles = data.pop('__rolesinherited')
      if not oNewItem.inheritRoles:
        acl = data.pop('__acl')
        if acl:
          security = {}
          for descriptor in acl:
            security[descriptor['id']] = int(descriptor['role'])
          oNewItem.security = security
    # set props
    for prop in data:
      oAttr = getattr(oNewItem, prop)
      if isinstance(oAttr, datatypes.File):
        if data[prop]['tempfile']:
          oAttr.filename = data[prop]['filename']
          sPath = self.server.temp_folder + '/' + data[prop]['tempfile']
          oAttr.loadFromFile(sPath)
          os.remove(sPath)
      elif isinstance(oAttr, datatypes.Date):
        oAttr.value = data[prop].value
      else:
        oAttr.value = data[prop]

    txn = self.server.store.getTransaction()
    oNewItem.appendTo(self.item.id, txn)
    txn.commit()
Changed line 578 from:

to:

Changed lines 580-582 from:

} autoform.update = function(response, form) {

  var dlg = form.getParentByType(Dialog);
to:
    return True
Changed line 582 from:

to:

Changed line 584 from:
  dlg.attributes.refreshlist();
to:

...

Added lines 586-596:

The "data" argument contains the form data. It is a Python dictionary containing the field names and values.

This method requires an additional field posted named "CC". This field contains the type of the object that is going to be appended to the calling container. Each XML-RPC method should always return a value. This method simply returns "True" only if the object is created and appended to the container successfully. This way, the browser knows that the request has been succefully completed.

Also notice, that the "ContainerGeneric" servlet is a subclass of the "ItemGeneric" servlet. Besides the container specific methods defined, a container can also be updated, copied or renamed just like any other Porcupine object.

Respectively, when editing an existing recipe, we call the "update" method of the "org.innoscript.desktop.XMLRPC.ItemGeneric" XMLRPC servlet.

From the "generic.js" file we also reuse two JavaScript functions named "generic.selectItems" and "generic.removeSelectedItems".

Deleted lines 598-599:
  dlg.close();

}

Added lines 600-604:

generic.selectItems = function(evt, w) {

  var oDialog = w.getParentByType(Dialog);
  var oTarget = w.parent.parent.getWidgetsByType(SelectList?)[0];
  var sFolderURI = oTarget.attributes.SelectFrom?;
  var sCC = oTarget.attributes.RelatedCC?;
Changed lines 606-614 from:
to:

Changed lines 608-609 from:

...

to:
  generic.selectObjects(oDialog, oTarget, generic.addSelectionToList,
    sFolderURI, sCC, 'true');
Changed line 611 from:

to:

Changed lines 613-614 from:

class ContainerGeneric?(ItemGeneric?):

  def create(self, data):
to:

} ... generic.selectObjects = function(win, target, select_func, startFrom, contentclass, multiple) {

  ...

} ... generic.removeSelectedItems = function(evt, w) {

  var oSelectList = w.parent.parent.getWidgetsByType(SelectList?)[0];
  oSelectList.removeSelected();

} ...

Added lines 626-646:

The "generic.selectItems" function displays the object select dialog by calling the "generic.selectObjects" function. The auto-generated form servlet uses this function for "ReferenceN" or "RelatorN" data types. The arguments given to the "generic.selectObjects" function are:

  • win: the opener
  • target: the target widget (in this case, the select list included in the recipe form)
  • select_func: the function to call when the user presses the "Select" button
  • startFrom: The ID of the root node of the tree (in this case, the "SelectFrom" custom attribute of the select list on the recipe form)
  • contentclass: The type of the Porcupine objects that we can select
  • multiple: "true" if multiple selection is allowed, else "false"

Restart the server. If everything is in place, you should see the following form when choosing to create a new recipe:

5. The Recipe Manager application

Apart from the custom forms and the custom content types, we will also need a custom UI for our application tailored to the given requirements. This is accomplished by creating a new application object. The Porcupine desktop provides generic interfaces and almost in every case, will not meet the requirements analysis.

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

Changed lines 649-655 from:
    # create new item
to:

<config>

	<dirs>
		<dir name="__quix" path="quix"/>
		<dir name="desktop" path="org/innoscript/desktop"/>
		<dir name="hypersearch" path="org/innoscript/hypersearch"/>
		<dir name="usermgmnt" path="org/innoscript/usermgmnt"/>
		<dir name="queryperformer" path="org/innoscript/queryperformer"/>
Changed line 659 from:
    oNewItem = misc.getClassByName(data.pop('CC'))()
to:
		<dir name="recipemanager" path="org/innoscript/recipemanager"/>
Changed lines 663-691 from:
    # get user role
    iUserRole = objectAccess.getAccess(self.item, self.session.user)
    if data.has_key('__rolesinherited') and iUserRole ==     objectAccess.COORDINATOR:
      oNewItem.inheritRoles = data.pop('__rolesinherited')
      if not oNewItem.inheritRoles:
        acl = data.pop('__acl')
        if acl:
          security = {}
          for descriptor in acl:
            security[descriptor['id']] = int(descriptor['role'])
          oNewItem.security = security
    # set props
    for prop in data:
      oAttr = getattr(oNewItem, prop)
      if isinstance(oAttr, datatypes.File):
        if data[prop]['tempfile']:
          oAttr.filename = data[prop]['filename']
          sPath = self.server.temp_folder + '/' + data[prop]['tempfile']
          oAttr.loadFromFile(sPath)
          os.remove(sPath)
      elif isinstance(oAttr, datatypes.Date):
        oAttr.value = data[prop].value
      else:
        oAttr.value = data[prop]

    txn = self.server.store.getTransaction()
    oNewItem.appendTo(self.item.id, txn)
    txn.commit()
to:
	</dirs>

</config>

Changed lines 666-669 from:

to:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

Changed lines 671-683 from:
    return True
to:

<config>

	<context path="recipemanager.quix"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.quix"/>
	<context path="recipemanager.js"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.js"/>

</config>

Added lines 685-694:
Changed lines 697-717 from:

...

to:

<?xml version="1.0" encoding="UTF-8"?> <a:window xmlns:a="http://www.innoscript.org/quix" title="Recipe manager" resizable="true" close="true" minimize="true" maximize="true" img="" width="640" height="480" left="center" top="center">

  <a:script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <a:wbody>
    <a:box width="100%" height="100%" orientation="v" spacing="0">
      <a:toolbar height="28">
        <a:tbbutton caption="Create recipe folder" width="120">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </a:tbbutton>
        <a:tbbutton caption="Create recipe" width="80">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.Recipe"/>
        </a:tbbutton>
        <a:tbsep/>
        <a:tbbutton caption="Refresh" width="60"/>
      </a:toolbar>
      <a:splitter height="-1" orientation="v" interactive="true">
        <a:pane length="200">
          <a:outlookbar width="100%" height="100%">
            <a:tool caption="Recipes">
Changed lines 719-730 from:

The "data" argument contains the form data. It is a Python dictionary containing the field names and values.

This method requires an additional field posted named "CC". This field contains the type of the object that is going to be appended to the calling container. Each XML-RPC method should always return a value. This method simply returns "True" only if the object is created and appended to the container successfully. This way, the browser knows that the request has been succefully completed.

Also notice, that the "ContainerGeneric" servlet is a subclass of the "ItemGeneric" servlet. Besides the container specific methods defined, a container can also be updated, copied or renamed just like any other Porcupine object.

Respectively, when editing an existing recipe, we call the "update" method of the "org.innoscript.desktop.XMLRPC.ItemGeneric" XMLRPC servlet.

The recipe form also includes the "org/innoscript/desktop/generic.js" JavaScript file. From this file we reuse two JavaScript functions named "generic.selectItems" and "generic.removeSelectedItems".

to:

Changed lines 721-726 from:

... generic.selectItems = function(evt, w) {

  var oDialog = w.getParentByType(Dialog);
  var oTarget = w.parent.parent.getWidgetsByType(SelectList?)[0];
  var sFolderURI = oTarget.attributes.SelectFrom?;
  var sCC = oTarget.attributes.RelatedCC?;
to:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
                <a:treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true"
                img="desktop/images/folder.gif" caption="Recipes"/>
Changed line 725 from:

to:

Changed lines 727-728 from:
  generic.selectObjects(oDialog, oTarget, generic.addSelectionToList,
    sFolderURI, sCC, 'true');
to:
              </a:foldertree>
            </a:tool>
            <a:tool caption="Search" bgcolor="gray">
              <a:label caption="Recipe title contains:"/>
              <a:field id="title" left="5" top="20" width="160"/>

              <a:label top="50" caption="Preparation time is less than"/>
              <a:field id="preparationTime" left="5" top="70" width="30"/>
              <a:label top="72" left="40" caption="min."/>

              <a:label top="100" caption="The recipe's rating is greater than"/>
              <a:spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

              <a:label top="150" caption="Recipe ingredients contain:"/>
              <a:field id="ingredients" top="170" left="5" width="160"/>
              <a:label top="190" caption="(comma separated list)" style="font-style:italic"/>

              <a:button top="220" left="center" width="60" height="28" caption="Search"/>
            </a:tool>
          </a:outlookbar>
        </a:pane>
        <a:pane length="-1">
Changed line 750 from:

to:

Changed lines 752-763 from:

} ... generic.selectObjects = function(win, target, select_func, startFrom, contentclass, multiple) {

  ...

} ... generic.removeSelectedItems = function(evt, w) {

  var oSelectList = w.parent.parent.getWidgetsByType(SelectList?)[0];
  oSelectList.removeSelected();

} ...

to:
          <a:listview width="100%" height="100%" id="list">
Deleted lines 753-773:

The "generic.selectItems" function displays the object select dialog by calling the "generic.selectObjects" function. The auto-generated form servlet uses this function for "ReferenceN" or "RelatorN" data types. The arguments given to the "generic.selectObjects" function are:

  • win: the opener
  • target: the target widget (in this case, the select list included in the recipe form)
  • select_func: the function to call when the user presses the "Select" button
  • startFrom: The ID of the root node of the tree (in this case, the "SelectFrom" custom attribute of the select list on the recipe form)
  • contentclass: The type of the Porcupine objects that we can select
  • multiple: "true" if multiple selection is allowed, else "false"

Restart the server. If everything is in place, you should see the following form when choosing to create a new recipe:

5. The Recipe Manager application

Apart from the custom forms and the custom content types, we will also need a custom UI for our application tailored to the given requirements. This is accomplished by creating a new application object. The Porcupine desktop provides generic interfaces and almost in every case, will not meet the requirements analysis.

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

Changed lines 756-762 from:

<config>

	<dirs>
		<dir name="__quix" path="quix"/>
		<dir name="desktop" path="org/innoscript/desktop"/>
		<dir name="hypersearch" path="org/innoscript/hypersearch"/>
		<dir name="usermgmnt" path="org/innoscript/usermgmnt"/>
		<dir name="queryperformer" path="org/innoscript/queryperformer"/>
to:
            <a:listheader>
              <a:column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
              <a:column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
              <a:column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
              <a:column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
              <a:column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
            </a:listheader>
          </a:listview>
        </a:pane>
      </a:splitter>
    </a:box>
  </a:wbody>

</a:window>

Changed lines 770-776 from:

to:

The folder tree widget and the list view have been given an ID for them to become easily accessible from our scripts. The "method" attribute is the XML-RPC method to call whenever the user expands a tree node. Notice that the interface includes a JavaScript file with a "script" node right after the window's opening tag. Thus, create an empty JavaScript file named "recipemanager.js" inside the application folder.

Let's start by adding some event handlers.

Changed lines 778-780 from:
		<dir name="recipemanager" path="org/innoscript/recipemanager"/>
to:

... <a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
Changed line 782 from:

to:

Changed lines 784-785 from:
	</dirs>

</config>

to:
    onclick="recipemanager.createItem">
Deleted lines 785-787:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

Changed lines 788-800 from:

<config>

	<context path="recipemanager.quix"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.quix"/>
	<context path="recipemanager.js"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.js"/>

</config>

to:
    ...
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
Changed lines 792-802 from:
to:

Changed lines 794-814 from:

<?xml version="1.0" encoding="UTF-8"?> <a:window xmlns:a="http://www.innoscript.org/quix" title="Recipe manager" resizable="true" close="true" minimize="true" maximize="true" img="" width="640" height="480" left="center" top="center">

  <a:script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <a:wbody>
    <a:box width="100%" height="100%" orientation="v" spacing="0">
      <a:toolbar height="28">
        <a:tbbutton caption="Create recipe folder" width="120">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </a:tbbutton>
        <a:tbbutton caption="Create recipe" width="80">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.Recipe"/>
        </a:tbbutton>
        <a:tbsep/>
        <a:tbbutton caption="Refresh" width="60"/>
      </a:toolbar>
      <a:splitter height="-1" orientation="v" interactive="true">
        <a:pane length="200">
          <a:outlookbar width="100%" height="100%">
            <a:tool caption="Recipes">
to:
    onclick="recipemanager.createItem">
Changed line 796 from:

to:

Changed lines 798-800 from:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
                <a:treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true"
                img="desktop/images/folder.gif" caption="Recipes"/>
to:
    ...
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
Changed line 803 from:

to:

Changed lines 805-826 from:
              </a:foldertree>
            </a:tool>
            <a:tool caption="Search" bgcolor="gray">
              <a:label caption="Recipe title contains:"/>
              <a:field id="title" left="5" top="20" width="160"/>

              <a:label top="50" caption="Preparation time is less than"/>
              <a:field id="preparationTime" left="5" top="70" width="30"/>
              <a:label top="72" left="40" caption="min."/>

              <a:label top="100" caption="The recipe's rating is greater than"/>
              <a:spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

              <a:label top="150" caption="Recipe ingredients contain:"/>
              <a:field id="ingredients" top="170" left="5" width="160"/>
              <a:label top="190" caption="(comma separated list)" style="font-style:italic"/>

              <a:button top="220" left="center" width="60" height="28" caption="Search"/>
            </a:tool>
          </a:outlookbar>
        </a:pane>
        <a:pane length="-1">
to:
    onclick="recipemanager.refresh_onclick" />
Changed line 807 from:

to:

Changed lines 809-811 from:
          <a:listview width="100%" height="100%" id="list">
to:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

Changed line 813 from:

to:

Changed lines 815-827 from:
            <a:listheader>
              <a:column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
              <a:column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
              <a:column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
              <a:column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
              <a:column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
            </a:listheader>
          </a:listview>
        </a:pane>
      </a:splitter>
    </a:box>
  </a:wbody>

</a:window>

to:
  onload="recipemanager.loadRecipes">
Deleted lines 816-821:

The folder tree widget and the list view have been given an ID for them to become easily accessible from our scripts. The "method" attribute is the XML-RPC method to call whenever the user expands a tree node. Notice that the interface includes a JavaScript file with a "script" node right after the window's opening tag. Thus, create an empty JavaScript file named "recipemanager.js" inside the application folder.

Let's start by adding some event handlers.

Changed lines 819-821 from:

... <a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
to:
  ...
Changed lines 821-824 from:

to:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 826-830 from:
    onclick="recipemanager.createItem">
to:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
Changed line 832 from:

to:

Changed lines 834-836 from:
    ...
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
to:
  var sCC = w.attributes.cc;
Changed line 836 from:

to:

Changed line 838 from:
    onclick="recipemanager.createItem">
to:
  var id = oTree.getSelection().getId();
Changed line 840 from:

to:

Changed lines 842-845 from:
    ...
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
to:
  oWin.showWindow(id + '?cmd=new&cc=' + sCC,
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );
Changed line 848 from:

to:

Changed lines 850-860 from:
    onclick="recipemanager.refresh_onclick" />
to:

}

recipemanager.dialogClose = function(dlg) {

  if (dlg.buttonIndex == 0) {
    recipemanager.refreshList(dlg.opener);
  }

}

recipemanager.refresh_onclick = function(evt, w) {

  recipemanager.loadRecipes(w);

}

Changed line 862 from:

to:

Changed lines 864-866 from:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

to:

recipemanager.loadRecipes = function(w) {

Changed line 866 from:

to:

Changed lines 868-878 from:
  onload="recipemanager.loadRecipes">
to:
  var appWin = w.getParentByType(Window);
  recipemanager.refreshList(appWin);

}

recipemanager.refreshList = function(appWin) {

  var oTree = appWin.getWidgetById('tree');
  var id = oTree.getSelection().getId();
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
Changed line 880 from:

to:

Changed lines 882-884 from:
  ...
to:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
Deleted lines 885-887:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 888-892 from:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
to:
  xmlrpc.callmethod('executeOqlCommand', sOql);

}

recipemanager.updateList = function(req) {

Changed lines 895-896 from:
  var sCC = w.attributes.cc;
to:
  req.callback_info.dataSet = req.response;
  req.callback_info.refresh();
Changed line 900 from:
  var id = oTree.getSelection().getId();
to:

}

Changed lines 902-919 from:

to:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "a:prop" nodes. The location of the new object is determined by the current tree selection. The "showWindow" method loads an XML UI definition for a new window or dialog from a specified URL, and opens it as a child window. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the dialog created. In this event, the root widget - the form dialog - will be added to the child windows of our application's main window.

The "recipemanager.loadrecipes" handler is called once when the list view is loaded. This handler loads the recipes from the selected container and displays them in the list view. The "onload" event handler, unlike the other events, does not accept the two common event handler arguments, the event object and the widget, but instead accepts only the widget that fires the event. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

The "recipemanager.refreshList" function executes the OQL query that returns the recipes contained inside the selected tree container. Inside this function we send a new XMLRPC request to the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This function is always called having the request object as the first argument. The "callback_info" attribute is a placeholder for anything we would like to access inside the callback function. In this case, the "callback_info" is a reference to the list view widget.

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XMLRPC call.

Afterwards, we must synchronize the tree with the list view. This means that for each new tree selection we should update the list view accordingly. This is accomplished by adding the following event handler:

Changed lines 921-928 from:
  document.desktop.parseFromUrl(id + '?cmd=new&cc=' + sCC,
    function(w) {
      w.attributes.refreshlist = function() {
        if (sCC=='org.innoscript.recipemanager.schema.Recipe')
          recipemanager.refreshList(oWin);
      }
    }
  );
to:

... <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

Changed line 924 from:

to:

Changed lines 926-930 from:

}

recipemanager.refresh_onclick = function(evt, w) {

  recipemanager.loadRecipes(w);

}

to:
  onselect="recipemanager.loadRecipes">
Changed line 928 from:

to:

Changed lines 930-932 from:

recipemanager.loadRecipes = function(w) {

to:
  ...

</a:foldertree> ...

Added lines 934-936:

Next, we implement the search functionality. We start by adding a new "onclick" event handler on the search button:

Changed lines 939-949 from:
  var appWin = w.getParentByType(Window);
  recipemanager.refreshList(appWin);

}

recipemanager.refreshList = function(appWin) {

  var oTree = appWin.getWidgetById('tree');
  var id = oTree.getSelection().getId();
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
to:

... <a:button top="220" left="center" width="60" height="28" caption="Search"

Changed lines 944-946 from:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
to:
  onclick="recipemanager.search" />
Changed lines 948-951 from:
  xmlrpc.callmethod('executeOqlCommand', sOql);

}

recipemanager.updateList = function(req) {

to:

...

Changed lines 950-953 from:

to:

Add the following function inside the "recipemanager.js" file:

Changed lines 955-956 from:
  req.callback_info.dataSet = req.response;
  req.callback_info.refresh();
to:

recipemanager.search = function(evt, w) {

  var displayName = w.parent.getWidgetById('title').getValue();
  var preparationTime = w.parent.getWidgetById('preparationTime').getValue();
  var rating = w.parent.getWidgetById('rating').getValue
  var ingredients = w.parent.getWidgetById('ingredients').getValue();
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
Changed line 962 from:

to:

Changed line 964 from:

}

to:
             "modified from deep('/Recipes')";
Deleted lines 965-981:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "a:prop" nodes. The location of the new object is determined by the current tree selection. The "parseFromUrl" QuiX widget method loads an XML UI definition from a specified URL, inside the referring widget. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the root widget created. In this event, the root widget - the form dialog - will be appended to the desktop ("document.desktop" is the desktop widget).

The "recipemanager.loadrecipes" handler is called once when the list view is loaded. This handler loads the recipes from the selected container and displays them in the list view. The "onload" event handler, unlike the other events, does not accept the two common event handler arguments, the event object and the widget, but instead accepts only the widget that fires the event. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

The "recipemanager.refreshList" function executes the OQL query that returns the recipes contained inside the selected tree container. Inside this function we send a new XMLRPC request to the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This function is always called having the request object as the first argument. The "callback_info" attribute is a placeholder for anything we would like to access inside the callback function. In this case, the "callback_info" is a reference to the list view widget.

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XMLRPC call.

Afterwards, we must synchronize the tree with the list view. This means that for each new tree selection we should update the list view accordingly. This is accomplished by adding the following event handler:

Changed lines 968-969 from:

... <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

to:
  var listView = w.getParentByType(Window).getWidgetById('list');
  var conditions = ["contentclass='schemas.org.innoscript.recipemanager.Recipe'"];
  if (displayName!='') {
    conditions.push("'" + displayName + "' in displayName");
  }
  if (preparationTime!='') {
    conditions.push("preparationTime < " + preparationTime);
  }
  if (rating>0) {
    conditions.push("rating > " + rating);
  }
  if (ingredients!='') {
    var arrIng = ingredients.split(',');
    for (var i=0; i<arrIng.length; i++) {
      conditions.push("'" + arrIng[i] + "' in ingredients");
    }
  }
  if (conditions.length > 0) {
    sOql += ' where ' + conditions.join(' and ');
    var xmlrpc = new XMLRPCRequest?(QuiX?.root);
    xmlrpc.oncomplete = recipemanager.updateList;
    xmlrpc.callback_info = listView;
    xmlrpc.callmethod('executeOqlCommand', sOql);
  }

}

Changed lines 994-998 from:

to:

The above function forms the OQL query according to the criteria set by the user on the search form. Notice that we search all the recipes, by using the "deep" operator at the select scope. Also when the user double clicks on the list view, the selected recipe dialog should open:

Changed lines 1000-1001 from:
  onselect="recipemanager.loadRecipes">
to:

<a:listview multiple="true" width="100" height="100" id="list"

  onload="recipemanager.loadRecipes"
Changed line 1003 from:

to:

Changed lines 1005-1007 from:
  ...

</a:foldertree> ...

to:
  ondblclick="recipemanager.loadItem">
Changed lines 1008-1009 from:

Next, we implement the search functionality. We start by adding a new "onclick" event handler on the search button:

to:

The following event handler should be added to the script of the application:

Changed lines 1012-1013 from:

... <a:button top="220" left="center" width="60" height="28" caption="Search"

to:

recipemanager.loadItem = function(evt, w, recipe) {

  var oWin = w.getParentByType(Window);
  oWin.showWindow(recipe.id + "?cmd=properties",
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

}

Changed lines 1021-1027 from:

to:

This handler opens the recipe form when a recipe item is double clicked on the list. It also attaches an "onclose" handler to the recipe form. In case of the recipe being updated this handler reloads the list items.

The final enhancement is the addition of a context menu. Open the application object and add the following lines to the interface definition:

Changed line 1029 from:
  onclick="recipemanager.search" />
to:

<a:pane length="-1">

Changed line 1031 from:

to:

Changed lines 1033-1036 from:

...

to:
  <a:contextmenu onshow="recipemanager.contextmenu_onshow">
    <a:menuoption caption="Open" onclick="recipemanager.openRecipe" />
    <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe" />
  </a:contextmenu>
Deleted lines 1037-1039:

Add the following function inside the "recipemanager.js" file:

Changed lines 1040-1045 from:

recipemanager.search = function(evt, w) {

  var displayName = w.parent.getWidgetById('title').getValue();
  var preparationTime = w.parent.getWidgetById('preparationTime').getValue();
  var rating = w.parent.getWidgetById('rating').getValue
  var ingredients = w.parent.getWidgetById('ingredients').getValue();
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
to:
  <a:listview width="100" height="100" id="list"
    onload="recipemanager.loadRecipes" ondblclick="recipemanager.loadItem">
    ...
  </a:listview>

</a:pane>

Changed lines 1046-1049 from:

to:
Changed line 1051 from:
             "modified from deep('/Recipes')";
to:

recipemanager.contextmenu_onshow = function(menu) {

Changed line 1053 from:

to:

Changed lines 1055-1079 from:
  var listView = w.getParentByType(Window).getWidgetById('list');
  var conditions = ["contentclass='schemas.org.innoscript.recipemanager.Recipe'"];
  if (displayName!='') {
    conditions.push("'" + displayName + "' in displayName");
  }
  if (preparationTime!='') {
    conditions.push("preparationTime < " + preparationTime);
  }
  if (rating>0) {
    conditions.push("rating > " + rating);
  }
  if (ingredients!='') {
    var arrIng = ingredients.split(',');
    for (var i=0; i<arrIng.length; i++) {
      conditions.push("'" + arrIng[i] + "' in ingredients");
    }
  }
  if (conditions.length > 0) {
    sOql += ' where ' + conditions.join(' and ');
    var xmlrpc = new XMLRPCRequest?(QuiX?.root);
    xmlrpc.oncomplete = recipemanager.updateList;
    xmlrpc.callback_info = listView;
    xmlrpc.callmethod('executeOqlCommand', sOql);
  }

}

to:
  var oList = menu.owner.getWidgetById('list');
Deleted lines 1056-1059:

The above function forms the OQL query according to the criteria set by the user on the search form. Notice that we search all the recipes, by using the "deep" operator at the select scope. Also when the user double clicks on the list view, the selected recipe dialog should open:

Changed lines 1059-1083 from:

<a:listview multiple="true" width="100" height="100" id="list"

  onload="recipemanager.loadRecipes"

@]

  ondblclick="recipemanager.loadItem">

The following event handler should be added to the script of the application:

recipemanager.loadItem = function(evt, w, recipe) {
  var oWin = w.getParentByType(Window);
  generic.showObjectProperties(null, null, recipe,
    function() {
      recipemanager.refreshList(oWin);
    }

[@

  );
to:
  menu.options[0].disabled = (oList.selection.length == 0); //open
  menu.options[1].disabled = (oList.selection.length == 0); //properties
Added lines 1062-1087:

recipemanager.openRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var oWin = oList.getParentByType(Window);
  var selectedRecipe = oList.getSelection();
  oWin.showWindow(selectedRecipe.id + "?cmd=properties",
    function(dlg) {
      dlg.attachEvent("onclose", recipemanager.dialogClose);
    }
  );

}

recipemanager.deleteRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var selectedRecipe = oList.getSelection();
  var desktop = document.desktop;
  var _deleteItem = function(evt, w) {
    var oMsgBox = w.getParentByType(Dialog);
    var xmlrpc = new XMLRPCRequest?(QuiX?.root + selectedRecipe.id);
    xmlrpc.oncomplete = function(req) {
      oMsgBox.close();
      recipemanager.loadRecipes(oList);
    }
    xmlrpc.onerror = function(req) { oMsgBox.close(); }
    xmlrpc.callmethod('delete');
  }
Changed lines 1089-1095 from:

This event handler reuses the "generic.showObjectProperties" function. The function given as an argument is called when the user updates the item and the list view of the opener must be updated.

The final enhancement is the addition of a context menu. Open the application object and add the following lines to the interface definition:

to:

Changed line 1091 from:

<a:pane length="-1">

to:
  desktop.msgbox(w.getCaption(),
Changed line 1093 from:

to:

Changed lines 1095-1098 from:
  <a:contextmenu onshow="recipemanager.contextmenu_onshow">
    <a:menuoption caption="Open" onclick="recipemanager.openRecipe" />
    <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe" />
  </a:contextmenu>
to:
    "Are you sure you want to delete this recipe?",
    [
      [desktop.attributes['YES'], 60, _deleteItem],
      [desktop.attributes['NO'], 60]
    ],
    'desktop/images/messagebox_warning.gif', 'center', 'center', 260, 112);

}

Added lines 1103-1127:
Changed lines 1130-1134 from:
  <a:listview width="100" height="100" id="list"
    onload="recipemanager.loadRecipes" ondblclick="recipemanager.loadItem">
    ...
  </a:listview>

</a:pane>

to:

[package] name=RecipeManager? version=0.0.4

Changed lines 1135-1136 from:
to:

The first section of the package definition file has information about the package (its name and its version). Please notice that the name of the package and the value of the name attribute of the package node in the "store.xml" configuration file must be identical.

Changed line 1139 from:

recipemanager.contextmenu_onshow = function(menu) {

to:

[files]

Changed lines 1141-1144 from:

to:

The second section contains the relative paths to any files that need to be included in the package. Leave this section blank.

Changed line 1146 from:
  var oList = menu.owner.getWidgetById('list');
to:

[dirs]

Added lines 1148-1150:

This section adds directories to the package.

Changed lines 1153-1176 from:
  menu.options[0].disabled = (oList.selection.length == 0); //open
  menu.options[1].disabled = (oList.selection.length == 0); //properties

}

recipemanager.openRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var selectedRecipe = oList.getSelection();
  recipemanager.loadItem(null, oList, selectedRecipe);

}

recipemanager.deleteRecipe = function(evt, w) {

  var oList = w.parent.owner.getWidgetById('list');
  var selectedRecipe = oList.getSelection();
  var desktop = document.desktop;
  _deleteItem = function(evt, w) {
    var oMsgBox = w.getParentByType(Dialog);
    var xmlrpc = new XMLRPCRequest?(QuiX?.root + selectedRecipe.id);
    xmlrpc.oncomplete = function(req) {
      oMsgBox.close();
      recipemanager.loadRecipes(oList);
    }
    xmlrpc.onerror = function(req) { oMsgBox.close(); }
    xmlrpc.callmethod('delete');
  }
to:

[pubdir] 1=recipemanager

Changed lines 1156-1159 from:

to:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section, the package will contain all the files that need to be distributed.

Changed lines 1161-1165 from:
  desktop.msgbox(w.getCaption(),
to:

[items] application=[Put here the ID of the Recipe Manager application] recipes_folder=[Put here the ID of the "/Recipes" folder] recipe_categories_folder=[Put here the ID of the "/Categories/Recipe categories" folder]

Added lines 1167-1170:

In this section we include all the Porcupine objects to export in the package, including the application object itself. Replace the highlighted portions with the object IDs printed on their properties dialog.

Changed lines 1173-1179 from:
    "Are you sure you want to delete this recipe?",
    [
      [desktop.attributes['YES'], 60, _deleteItem],
      [desktop.attributes['NO'], 60]
    ],
    'desktop/images/messagebox_warning.gif', 'center', 'center', 260, 112);

}

to:

[scripts] postinstall=PKG/postinstall.py uninstall=PKG/uninstall.py

Changed lines 1178-1201 from:
to:
Changed lines 1184-1186 from:

[package] name=RecipeManager? version=0.0.3

to:
  1. RecipeManager? post installation script

from porcupine.administration import codegen ce = codegen.DatatypeEditor?('org.innoscript.desktop.schema.properties.CategoryObjects?')

Changed lines 1188-1191 from:

The first section of the package definition file has information about the package (its name and its version). Please notice that the name of the package and the value of the name attribute of the package node in the "store.xml" configuration file must be identical.

to:

Changed line 1190 from:

[files]

to:

ce.relCc.append('org.innoscript.recipemanager.schema.Recipe')

Deleted lines 1191-1193:

The second section contains the relative paths to any files that need to be included in the package. Leave this section blank.

Changed line 1194 from:

[dirs]

to:

ce.commitChanges()

Changed lines 1197-1198 from:

This section adds directories to the package.

to:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects?" class). To be precise, it adds the "org.innoscript.recipemanager.schema.Recipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type). Similarly, inside the "PKG" folder create a new Python script named "uninstall.py". Open the file and add the following lines:

Changed lines 1202-1203 from:

[pubdir] 1=recipemanager

to:
  1. RecipeManager? uninstallation script

from porcupine.administration import codegen ce = codegen.DatatypeEditor?('org.innoscript.desktop.schema.properties.CategoryObjects?')

Changed lines 1206-1209 from:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section, the package will contain all the files that need to be distributed.

to:

Changed lines 1208-1212 from:

[items] application=[Put here the ID of the Recipe Manager application] recipes_folder=[Put here the ID of the "/Recipes" folder] recipe_categories_folder=[Put here the ID of the "/Categories/Recipe categories" folder]

to:

ce.relCc.remove('org.innoscript.recipemanager.schema.Recipe')

Deleted lines 1209-1212:

In this section we include all the Porcupine objects to export in the package, including the application object itself. Replace the highlighted portions with the object IDs printed on their properties dialog.

Changed lines 1212-1214 from:

[scripts] postinstall=PKG/postinstall.py uninstall=PKG/uninstall.py

to:

ce.commitChanges()

Deleted lines 1214-1250:
November 12, 2006, at 12:32 AM by Tassos Koutsovassilis -
Changed lines 1246-1248 from:
to:
November 12, 2006, at 12:29 AM by Tassos Koutsovassilis -
Deleted lines 2-3:

This tutorial is valid only for Porcupine 0.0.7. We are currently in the process of updating this tutorial to comply with the new 0.0.8 release.

November 12, 2006, at 12:13 AM by Tassos Koutsovassilis -
Changed line 1161 from:

version=0.0.1

to:

version=0.0.3

November 11, 2006, at 11:40 PM by Tassos Koutsovassilis -
Changed line 20 from:
to:

The code sections that begin with three dots ("...") contain existing code, and they are mainly

Changed line 485 from:
                            <a:prop name="RelatedCC?" value="schemas.org.innoscript.common.Category"/>
to:
                            <a:prop name="RelatedCC?" value="org.innoscript.desktop.schema.common.Category"/>
November 11, 2006, at 09:54 PM by Tassos Koutsovassilis -
Changed lines 443-444 from:
  <a:dialog xmlns:a="http://www.innoscript.org/quix" title="%(TITLE)s"
    img="%(ICON)s" close="true" width="480" height="380" left="30" top="10">
to:

<a:dialog xmlns:a="http://www.innoscript.org/quix" title="%(TITLE)s" img="%(ICON)s" close="true" width="480" height="380" left="30" top="10">

Changed line 451 from:
      <a:form action="%(URI)s" method="%(METHOD)s" padding="4,4,4,4">
to:
        <a:form action="%(URI)s" method="%(METHOD)s" padding="4,4,4,4">
Changed lines 455-494 from:
        %(HIDDEN)s
        <a:label caption="Recipe name:"/>
        <a:field left="80" width="380" name="displayName"
          value="%(DISPLAYNAME)s" />
        <a:label caption="Description:" top="25" />
        <a:field left="80" top="24" width="380" name="description"
          value="%(DESCRIPTION)s" />
        <a:hr top="50" width="100" />
        <a:label top="58" caption="Preparation time (min):" />
        <a:spinbutton name="preparationTime" top="55" left="110" width="50" 
          max="240" value="%(PREPARATION_TIME)d" editable="true" />
        <a:label top="58" left="200" caption="Servings:" />
        <a:spinbutton name="servings" top="55" left="250" width="40" max="12"
          editable="true" value="%(SERVINGS)d" />
        <a:label top="58" left="320" caption="Rating:" />
        <a:spinbutton name="rating" top="55" left="360" width="40" max="10"
          editable="true" value="%(RATING)d" />
        <a:tabpane top="90" width="100" height="220">
          <a:tab caption="Ingredients">
            <a:field type="textarea" name="ingredients" width="100"
              height="100">%(INGREDIENTS)s</a:field>
            </a:tab>
            <a:tab caption="Instructions">
              <a:field type="textarea" name="instructions" width="100"
                height="100">%(INSTRUCTIONS)s</a:field>
            </a:tab>
            <a:tab caption="Categories">
            <a:splitter width="100" height="100" orientation="h">
              <a:pane length="-1">
                <a:selectlist name="categories" multiple="true" posts="all"
                  width="100" height="100">
                  <a:prop name="SelectFrom?"
                    value="Categories/Recipe categories"></a:prop>
                  <a:prop name="RelatedCC?" 
                    value="schemas.org.innoscript.common.Category"></a:prop>
                  %(CATEGORIES)s
                </a:selectlist>
              </a:pane>
              <a:pane length="24">
                <a:flatbutton width="70" height="22" caption="Add"
to:
            %(HIDDEN)s
            <a:label caption="Recipe name:"/>
            <a:field left="80" width="380" name="displayName"
              value="%(DISPLAYNAME)s" />
            <a:label caption="Description:" top="25" />
            <a:field left="80" top="24" width="380" name="description"
              value="%(DESCRIPTION)s" />
            <a:hr top="50" width="100" />
            <a:label top="58" caption="Preparation time (min):" />
            <a:spinbutton name="preparationTime" top="55" left="110" width="50" 
              max="240" value="%(PREPARATION_TIME)d" editable="true" />
            <a:label top="58" left="200" caption="Servings:" />
            <a:spinbutton name="servings" top="55" left="250" width="40" max="12"
              editable="true" value="%(SERVINGS)d" />
            <a:label top="58" left="320" caption="Rating:" />
            <a:spinbutton name="rating" top="55" left="360" width="40" max="10"
              editable="true" value="%(RATING)d" />
            <a:tabpane top="90" width="100" height="220">
                <a:tab caption="Ingredients">
                    <a:field type="textarea" name="ingredients" width="100"
                      height="100">%(INGREDIENTS)s</a:field>
                </a:tab>
                <a:tab caption="Instructions">
                    <a:field type="textarea" name="instructions" width="100"
                      height="100">%(INSTRUCTIONS)s</a:field>
                </a:tab>
                <a:tab caption="Categories">
                    <a:box width="100" height="100" orientation="v">
                        <a:selectlist name="categories" multiple="true" posts="all" height="-1">
                            <a:prop name="SelectFrom?" value="Categories/Recipe categories"></a:prop>
                            <a:prop name="RelatedCC?" value="schemas.org.innoscript.common.Category"/>
                            %(CATEGORIES)s
                        </a:selectlist>
                        <a:rect height="24">
                            <a:flatbutton width="70" height="22" caption="Add"
Changed line 493 from:
                  onclick="generic.selectItems"></a:flatbutton>
to:
                              onclick="generic.selectItems"></a:flatbutton>
Changed line 497 from:
                <a:flatbutton left="80" width="70" height="22" caption="Remove"
to:
                            <a:flatbutton left="80" width="70" height="22" caption="Remove"
Changed line 501 from:
                  onclick="generic.removeSelectedItems"></a:flatbutton>
to:
                              onclick="generic.removeSelectedItems"></a:flatbutton>
Changed lines 505-509 from:
              </a:pane>
            </a:splitter>
          </a:tab>
        </a:tabpane>
      </a:form>
to:
                        </a:rect>
                    </a:box>
                </a:tab>
            </a:tabpane>
        </a:form>
Changed line 521 from:
  </a:dialog>
to:

</a:dialog>

November 11, 2006, at 09:40 PM by Tassos Koutsovassilis -
Changed line 1134 from:
    'images/messagebox_warning.gif', 'center', 'center', 260, 112);
to:
    'desktop/images/messagebox_warning.gif', 'center', 'center', 260, 112);
November 11, 2006, at 02:37 AM by Tassos Koutsovassilis -
Changed line 1125 from:
  desktop.msgbox(w.caption,
to:
  desktop.msgbox(w.getCaption(),
November 11, 2006, at 02:35 AM by Tassos Koutsovassilis -
Changed line 881 from:
        if (sCC=='schemas.org.innoscript.recipemanager.Recipe')
to:
        if (sCC=='org.innoscript.recipemanager.schema.Recipe')
Changed line 911 from:
             "'schemas.org.innoscript.recipemanager.Recipe' order by displayName asc";
to:
             "'org.innoscript.recipemanager.schema.Recipe' order by displayName asc";
November 11, 2006, at 02:26 AM by Tassos Koutsovassilis -
Changed line 398 from:
      oRecipe = recipemanager.Recipe()
to:
      oRecipe = schema.Recipe()
November 11, 2006, at 01:59 AM by Tassos Koutsovassilis -
Changed line 247 from:
    client="(MSIE 6)|(Mozilla/5.0.+rv:1.[7-9])"
to:
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
Changed line 363 from:
    client="(MSIE 6)|(Mozilla/5.0.+rv:1.[7-9])"
to:
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
Changed line 372 from:
    client="(MSIE 6)|(Mozilla/5.0.+rv:1.[7-9])"
to:
    client="(MSIE [6-7])|(Mozilla/5.0.+rv:1.[7-9])"
November 11, 2006, at 01:47 AM by Tassos Koutsovassilis -
Changed line 739 from:
    <a:box width="100%" height="100%" orientation="h">
to:
    <a:box width="100%" height="100%" orientation="v" spacing="0">
November 11, 2006, at 01:42 AM by Tassos Koutsovassilis -
Changed line 759 from:
                img="images/folder.gif" caption="Recipes"/>
to:
                img="desktop/images/folder.gif" caption="Recipes"/>
November 11, 2006, at 01:31 AM by Tassos Koutsovassilis -
Changed lines 65-66 from:

The "categories" attribute is of type "RelatorN" since each recipe can be attributed to more than one category (French cuisine, desert etc.). For reasons of simplicity, we will not create a new content class for the categorization of the recipes. Instead, we will use the existing "[[http://www.innoscript.org/api/org.innoscript.desktop.schema.common.Category-class.html | Category" content class.

to:

The "categories" attribute is of type "RelatorN" since each recipe can be attributed to more than one category (French cuisine, desert etc.). For reasons of simplicity, we will not create a new content class for the categorization of the recipes. Instead, we will use the existing "Category" content class.

November 11, 2006, at 01:30 AM by Tassos Koutsovassilis -
Changed line 25 from:

Create a new folder named "recipemanager" inside the "org/innoscript'" folder. Add an empty "__init__.py" file to make it a Python package. Inside this folder create a new python script named "schema.py''". This file will host our custom content classes.

to:

Create a new folder named "recipemanager" inside the "org/innoscript" folder. Add an empty "__init__.py" file to make it a Python package. Inside this folder create a new python script named "schema.py". This file will host our custom content classes.

Changed lines 27-28 from:

Open the blank "schema.py" with your favourite Pyhton editor. First, we need to import the primary Porcupine content classes and the primary data types:

to:

Open the blank "schema.py" with your favourite Pyhton editor. First, we need to import the primary Porcupine content classes and the primary data types:

Changed lines 35-36 from:

Next, we create the recipes container:

to:

Next, we create the recipes' container content class:

Changed lines 48-49 from:
to:

"systemObjects.Container" class). The "containment" class attribute is a tuple of strings, containing all the types of the Porcupine objects that this container type can accept. In this case, the specified container can either accept new containers of the same type or new recipes. It is worth mentioning that the special class attribute "__slots__" should not be omitted from any custom content class or data type, since instances of such types consume considerably less memory.

Changed lines 65-67 from:
to:

The "categories" attribute is of type "RelatorN" since each recipe can be attributed to more than one category (French cuisine, desert etc.). For reasons of simplicity, we will not create a new content class for the categorization of the recipes. Instead, we will use the existing "[[http://www.innoscript.org/api/org.innoscript.desktop.schema.common.Category-class.html | Category" content class.

The "Categories" data type class is already defined; it is used for the categorization of the

Changed lines 72-73 from:
to:

In order to define the relation both ways, we have to somehow add the "Recipe" object to the list of objects that can be categorized. This is accomplished by editing the "properties.py" file inside the "org/innoscript/desktop/schema" folder. The highlighted line should be added:

Changed lines 109-110 from:
to:

Having defined all of our required data types, we proceed to the "Recipe" content class definition:

Changed lines 145-148 from:
to:

The "__props__" class attribute is a special attribute used by the framework. It should be defined only in each new content class that has additional data type attributes. This attribute is a tuple of strings containing the names of all such attributes, including those of the super classes.

The name of the "categories" attribute is obligatory. This is because we have a two-way many-to-many relationship between the "Recipe" and "Category" objects. To clarify this, we need to examine the "Category" content type defined in the "common.py" file.

Changed lines 157-158 from:
to:
Changed lines 177-179 from:
to:

should have an attribute named "categories". If the "categories"attribute is a subclass of "Relator1", then we have established an one-to-many relationship. On the other hand, if the "categories" attribute is a subclass of "RelatorN", then the relationship is considered to be many-to-many.

Changed lines 184-185 from:
to:

temporarily edit the containment of the "RootFolder" content class, located inside "common.py".

Changed line 204 from:
to:

list view and select "New". Notice that the "RecipeContainer" type is added to the list of the

Changed line 209 from:
to:

Select the "RecipeContainer" type and create a new object named "Recipes". If you double click on

Changed line 215 from:
to:

"RecipeContainer" content class. Hence, we have to edit the "store.xml" file inside the "conf"

Changed line 217 from:
to:

inside a new package node. Locate the "packages" node at the top of the afore-mentioned file and

Changed lines 262-265 from:
to:

As you can see, the first registration takes care of the XMLRPC methods exposed by the "RecipeContainer" content type. For the time being, we need no special behavior; the "RecipeContainer" type exposes the same methods as any other container. Also, notice that the client parameter is set to "vcXMLRPC". This should be true for every XMLRPC servlet we publish.

Changed line 267 from:
to:

object inside a "RecipeContainer" folder. Because the "org.innoscript.desktop.ui.Frm_AutoNew" is a

Changed lines 271-272 from:
to:

Finally, we also need to create the recipe categories container. Double click the "Categories" folder and create a new category folder named "Recipe categories". Please note that in case your session

Changed lines 274-276 from:
to:

Before restarting the server, you can safely delete the "RecipeContainer" entry from the containment of the "RootFolder" content type.

Changed lines 279-280 from:
to:

The current implementation of the "org.innoscript.desktop.ui.Frm_AutoNew" servlet has some limitations that force us to design a new form for the "Recipe" content type. Looking at the form

Changed lines 289-293 from:
to:
Changed lines 306-307 from:
to:

"RecipeContainer" type accepts both new recipe containers and recipes. When creating a new recipe container, the splitter must get the XML definition output from the "Frm_Auto" servlet, whereas

Changed line 337 from:
to:

In order for the servlet to execute successfully, a file named "ui.RecipeContainerSplitter.quix" must

Changed lines 347-348 from:
to:

"params" attribute of the servlet. Before proceeding to the recipe form itself, we must modify the "store.xml" configuration file by

Changed lines 381-387 from:
to:
Changed lines 434-439 from:
to:

display the properties of an existing recipe. In the first case, the type of the object "called" is the "RecipeContainer" unlike the second case, in which the type of the object is the "Recipe" type.

The QuiX XML definition of the form is a new file named "ui.RecipeForm.quix" inside the "org/innoscript/recipemanager" folder.

Changed lines 529-530 from:
to:

Initially, this form imports two QuiX desktop JavaScript files. The first script is the one used by the forms generated automatically by the "Frm_Auto" servlet. The function we reuse from this script (org/innoscript/desktop/ui.Frm_Auto.js) is the "autoform.submit" function. This function simply submits the first QuiX form found inside the current dialog.

Changed lines 558-564 from:
to:
Changed lines 623-632 from:
to:

The "data" argument contains the form data. It is a Python dictionary containing the field names and values.

This method requires an additional field posted named "CC". This field contains the type of the object that is going to be appended to the calling container. Each XML-RPC method should always return a value. This method simply returns "True" only if the object is created and appended to the container successfully. This way, the browser knows that the request has been succefully completed.

Also notice, that the "ContainerGeneric" servlet is a subclass of the "ItemGeneric" servlet. Besides the container specific methods defined, a container can also be updated, copied or renamed just like any other Porcupine object.

Respectively, when editing an existing recipe, we call the "update" method of the "org.innoscript.desktop.XMLRPC.ItemGeneric" XMLRPC servlet.

The recipe form also includes the "org/innoscript/desktop/generic.js" JavaScript file. From this file we reuse two JavaScript functions named "generic.selectItems" and "generic.removeSelectedItems".

Changed line 663 from:
to:

The "generic.selectItems" function displays the object select dialog by calling the "generic.selectObjects" function. The auto-generated form servlet uses this function for "ReferenceN" or "RelatorN" data types. The arguments given to the "generic.selectObjects" function are:

Changed lines 666-667 from:
to:
  • select_func: the function to call when the user presses the "Select" button
  • startFrom: The ID of the root node of the tree (in this case, the "SelectFrom" custom attribute of the select list on the recipe form)
Changed lines 669-670 from:
to:
  • multiple: "true" if multiple selection is allowed, else "false"
Changed lines 681-682 from:

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

to:

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

Changed lines 703-704 from:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

to:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

Changed lines 723-727 from:
to:

"Administrative Tools/Applications" folder. Inside the "Launch URL" input box type "recipemanager/recipemanager.quix". Press the "Create" button to create the application object.

Next, create a new text file named "recipemanager.quix" inside the "recipemanager" folder we just published.

Changed lines 807-809 from:
to:

The folder tree widget and the list view have been given an ID for them to become easily accessible from our scripts. The "method" attribute is the XML-RPC method to call whenever the user expands a tree node. Notice that the interface includes a JavaScript file with a "script" node right after the window's opening tag. Thus, create an empty JavaScript file named "recipemanager.js" inside the application folder.

Changed lines 858-859 from:

Insert the following functions inside the empty ""recipemanager.js"" file:

to:

Insert the following functions inside the empty "recipemanager.js" file:

Changed lines 936-940 from:
to:

The "recipemanager.createItem" handler displays the appropriate form based on the type of the object selected by the user. This is detected by the "cc" custom attribute assigned to the toolbar buttons using the "a:prop" nodes. The location of the new object is determined by the current tree selection. The "parseFromUrl" QuiX widget method loads an XML UI definition from a specified URL, inside the referring widget. The second argument of this method is a function called when the interface is loaded. The first argument of this function is the root widget created. In this event, the root widget - the form dialog - will be appended to the desktop ("document.desktop" is the desktop widget).

The "recipemanager.loadrecipes" handler is called once when the list view is loaded. This handler loads the recipes from the selected container and displays them in the list view. The "onload" event handler, unlike the other events, does not accept the two common event handler arguments, the event object and the widget, but instead accepts only the widget that fires the event. The "getParentByType" QuiX? widget method returns the first parent widget of the specified type. The only argument of this method is the constructor function of the parent we are searching for. The "Window" class is defined inside the "quix/windows.js" QuiX module.

The "recipemanager.refreshList" function executes the OQL query that returns the recipes

Changed lines 942-947 from:
to:

the root folder. The "oncomplete" attribute of the request is the callback function to call when the query has completed. This function is always called having the request object as the first argument. The "callback_info" attribute is a placeholder for anything we would like to access inside the callback function. In this case, the "callback_info" is a reference to the list view widget.

Finally, the "recipemanager.updateList" callback function updates the "dataSet" attribute of the list view and then refreshes it. The "dataSet" attribute is an array of arbitrary objects. Such an array is directly returned from the XMLRPC call.

Changed lines 968-969 from:
to:

Next, we implement the search functionality. We start by adding a new "onclick" event handler on the search button:

Changed lines 984-985 from:
to:

Add the following function inside the "recipemanager.js" file:

Changed line 1028 from:
to:

The above function forms the OQL query according to the criteria set by the user on the search form. Notice that we search all the recipes, by using the "deep" operator at the select scope.

Changed lines 1061-1062 from:
to:

This event handler reuses the "generic.showObjectProperties" function. The function given as an argument is called when the user updates the item and the list view of the opener must be updated.

Changed lines 1138-1140 from:
to:
Changed lines 1159-1161 from:
to:
Changed lines 1169-1170 from:
to:

The first section of the package definition file has information about the package (its name and its version). Please notice that the name of the package and the value of the name attribute of the package node in the "store.xml" configuration file must be identical.

Changed lines 1191-1192 from:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section the package will contain all the files that need to be destributed.

to:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section, the package will contain all the files that need to be distributed.

Changed lines 1197-1199 from:
to:

recipes_folder=[Put here the ID of the "/Recipes" folder] recipe_categories_folder=[Put here the ID of the "/Categories/Recipe categories" folder]

Changed lines 1203-1204 from:

application object itself. Replace the highlighted portions with the object IDs? printed on their properties dialog.

to:

application object itself. Replace the highlighted portions with the object IDs printed on their properties dialog.

Changed lines 1212-1215 from:
to:
Changed lines 1231-1233 from:
to:

The "codegen" module provides an essential API for runtime manipulation of the Porcupine objects and data types. This script modifies an existing data type (the "CategoryObjects?" class). To be precise, it adds the "org.innoscript.recipemanager.schema.Recipe" content class to the list of types that can be categorized (i.e. to the "relCc" class attribute of the data type). Similarly, inside the "PKG" folder create a new Python script named "uninstall.py". Open the file and add the following lines:

Changed lines 1249-1250 from:
to:

Before proceeding to the creation of the package, stop the Porcupine service. Afterwards, run the "pakager" utility as follows:

November 11, 2006, at 01:04 AM by Tassos Koutsovassilis -
Added line 725:
Changed line 741 from:
        <a:tbbutton caption="Create recipe folder" width="120" onclick="recipemanager.createItem">
to:
        <a:tbbutton caption="Create recipe folder" width="120">
Changed line 744 from:
        <a:tbbutton caption="Create recipe" width="80" onclick="recipemanager.createItem">
to:
        <a:tbbutton caption="Create recipe" width="80">
Changed line 748 from:
        <a:tbbutton caption="Refresh" width="60" onclick="recipemanager.refresh_onclick"/>
to:
        <a:tbbutton caption="Refresh" width="60"/>
Changed lines 757-758 from:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4" onselect="recipemanager.loadRecipes">
                <a:treenode id="[Put here the ID of the Recipes Porcupine Object]" haschildren="true"
to:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
                <a:treenode id="[Put here the ID of the Recipes Porcupine folder]" haschildren="true"
Changed line 780 from:
              <a:button top="220" left="center" width="60" height="28" caption="Search" onclick="recipemanager.search"/>
to:
              <a:button top="220" left="center" width="60" height="28" caption="Search"/>
Deleted lines 784-787:
          <a:contextmenu onshow="recipemanager.contextmenu_onshow">
            <a:menuoption caption="Open" onclick="recipemanager.openRecipe"/>
            <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe"/>
          </a:contextmenu>
Changed lines 788-789 from:
          <a:listview width="100%" height="100%" id="list" onload="recipemanager.loadRecipes"
          ondblclick="recipemanager.loadItem">
to:
          <a:listview width="100%" height="100%" id="list">
Changed lines 808-809 from:
to:

Notice that the interface includes a JavaScript file with a "script" node right after the window's opening tag. Thus, create an empty JavaScript file named "recipemanager.js" inside the application folder.

Changed lines 858-859 from:

In the application's script tab insert the following code:

to:

Insert the following functions inside the empty ""recipemanager.js"" file:

Changed lines 938-939 from:
to:
Changed lines 946-947 from:
to:
Changed lines 984-985 from:
to:
Changed lines 1160-1161 from:
to:
Deleted line 1173:

1=schemas/org/innoscript/recipemanager.py

Changed lines 1176-1177 from:
to:

The second section contains the relative paths to any files that need to be included in the package. Leave this section blank.

Deleted line 1180:

1=resources/recipemanager

Changed lines 1183-1184 from:

The third section adds directories to the package. This directory is the one that contains the servlets.

to:

This section adds directories to the package.

Added line 1188:

1=recipemanager

Changed lines 1191-1192 from:
to:

The fourth section contains the published directories (usually containing external JavaScript files and images) that are required by the application. In this case, we published the "org/innoscript/recipemanager" directory. This directory also contains all of our applications files. So, by including this section the package will contain all the files that need to be destributed.

Changed line 1220 from:

ce = codegen.DatatypeEditor?('schemas.org.innoscript.properties.category_objects')

to:

ce = codegen.DatatypeEditor?('org.innoscript.desktop.schema.properties.CategoryObjects?')

Changed line 1224 from:

ce.relCc.append('schemas.org.innoscript.recipemanager.Recipe')

to:

ce.relCc.append('org.innoscript.recipemanager.schema.Recipe')

Changed line 1238 from:

ce = codegen.DatatypeEditor?('schemas.org.innoscript.properties.category_objects')

to:

ce = codegen.DatatypeEditor?('org.innoscript.desktop.schema.properties.CategoryObjects?')

Changed line 1242 from:

ce.relCc.remove('schemas.org.innoscript.recipemanager.Recipe')

to:

ce.relCc.remove('org.innoscript.recipemanager.schema.Recipe')

November 11, 2006, at 12:31 AM by Tassos Koutsovassilis -
Changed lines 724-726 from:

Create a new text file named "recipemanager.quix" inside the "recipemanager" folder we just published.

to:
Changed lines 732-750 from:

<a:splitter width="100" height="100" orientation="h" spacing="0">

  <a:pane length="28">
    <a:toolbar width="100" height="100">
      <a:tbbutton caption="Create recipe folder" width="120">
        <a:prop name="cc" value="schemas.org.innoscript.recipemanager.RecipeContainer?"/>
      </a:tbbutton>
      <a:tbbutton caption="Create recipe" width="80">
        <a:prop name="cc" value="schemas.org.innoscript.recipemanager.Recipe"/>
      </a:tbbutton>
      <a:tbsep/>
      <a:tbbutton caption="Refresh" width="60" />
    </a:toolbar>
  </a:pane>
  <a:pane length="-1" bgcolor="white">
    <a:splitter width="100" height="100" orientation="v"
      interactive="true">
      <a:pane length="200">
        <a:outlookbar width="100" height="100">
          <a:tool caption="Recipes">
to:

<?xml version="1.0" encoding="UTF-8"?> <a:window xmlns:a="http://www.innoscript.org/quix" title="Recipe manager" resizable="true" close="true" minimize="true" maximize="true" img="" width="640" height="480" left="center" top="center">

  <a:script name="Recipe manager Script" src="recipemanager/recipemanager.js"/>
  <a:wbody>
    <a:box width="100%" height="100%" orientation="h">
      <a:toolbar height="28">
        <a:tbbutton caption="Create recipe folder" width="120" onclick="recipemanager.createItem">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.RecipeContainer?"/>
        </a:tbbutton>
        <a:tbbutton caption="Create recipe" width="80" onclick="recipemanager.createItem">
          <a:prop name="cc" value="org.innoscript.recipemanager.schema.Recipe"/>
        </a:tbbutton>
        <a:tbsep/>
        <a:tbbutton caption="Refresh" width="60" onclick="recipemanager.refresh_onclick"/>
      </a:toolbar>
      <a:splitter height="-1" orientation="v" interactive="true">
        <a:pane length="200">
          <a:outlookbar width="100%" height="100%">
            <a:tool caption="Recipes">
Changed lines 756-757 from:
            <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
              <a:treenode id="[Put here the ID of the Recipes folder]" haschildren="true"
to:
              <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4" onselect="recipemanager.loadRecipes">
                <a:treenode id="[Put here the ID of the Recipes Porcupine Object]" haschildren="true"
                img="images/folder.gif" caption="Recipes"/>
Changed lines 762-784 from:
                img="images/folder.gif" caption="Recipes">
              </a:treenode>
            </a:foldertree>
          </a:tool>
          <a:tool caption="Search" bgcolor="gray">
            <a:label caption="Recipe title contains:" />
            <a:field id="title" left="5" top="20" width="160" />
            <a:label top="50" caption="Preparation time is less than" />
            <a:field id="preparationTime" left="5" top="70" width="30" />
            <a:label top="72" left="40" caption="min." />
            <a:label top="100" caption="The recipe's rating is greater than" />
            <a:spinbutton top="120" left="5" width="40" max="10" value="0"
              id="rating"/>
            <a:label top="150" caption="Recipe ingredients contain:" />
            <a:field id="ingredients" top="170" left="5" width="160" />
            <a:label top="190" caption="(comma separated list)"
              style="font-style:italic" />
            <a:button top="220" left="center" width="60" height="28"
              caption="Search" />
          </a:tool>
        </a:outlookbar>
      </a:pane>
      <a:pane length="-1">
to:
              </a:foldertree>
            </a:tool>
            <a:tool caption="Search" bgcolor="gray">
              <a:label caption="Recipe title contains:"/>
              <a:field id="title" left="5" top="20" width="160"/>

              <a:label top="50" caption="Preparation time is less than"/>
              <a:field id="preparationTime" left="5" top="70" width="30"/>
              <a:label top="72" left="40" caption="min."/>

              <a:label top="100" caption="The recipe's rating is greater than"/>
              <a:spinbutton top="120" left="5" width="40" max="10" value="0" id="rating"/>

              <a:label top="150" caption="Recipe ingredients contain:"/>
              <a:field id="ingredients" top="170" left="5" width="160"/>
              <a:label top="190" caption="(comma separated list)" style="font-style:italic"/>

              <a:button top="220" left="center" width="60" height="28" caption="Search" onclick="recipemanager.search"/>
            </a:tool>
          </a:outlookbar>
        </a:pane>
        <a:pane length="-1">
          <a:contextmenu onshow="recipemanager.contextmenu_onshow">
            <a:menuoption caption="Open" onclick="recipemanager.openRecipe"/>
            <a:menuoption caption="Delete" onclick="recipemanager.deleteRecipe"/>
          </a:contextmenu>
Changed lines 791-792 from:
        <a:listview id="list" width="100" height="100">
to:
          <a:listview width="100%" height="100%" id="list" onload="recipemanager.loadRecipes"
          ondblclick="recipemanager.loadItem">
Changed lines 796-812 from:
          <a:listheader>
            <a:column width="140" caption="Recipe title" name="displayName"
              bgcolor="#EFEFEF" sortable="true"></a:column>
            <a:column width="60" caption="Servings" type="int" name="servings"
              sortable="true"></a:column>
            <a:column width="100" caption="Preparation time" type="int"
              name="preparationTime" sortable="true"></a:column>
            <a:column width="40" caption="Rating" type="int" name="rating"
              sortable="true"></a:column>
            <a:column width="160" caption="Date modified" type="date"
              name="modified" sortable="true"></a:column>
          </a:listheader>
        </a:listview>
      </a:pane>
    </a:splitter>
  </a:pane>

</a:splitter>

to:
            <a:listheader>
              <a:column width="140" caption="Recipe title" name="displayName" bgcolor="#EFEFEF" sortable="true"/>
              <a:column width="60" caption="Servings" type="int" name="servings" sortable="true"/>
              <a:column width="100" caption="Preparation time" type="int" name="preparationTime" sortable="true"/>
              <a:column width="40" caption="Rating" type="int" name="rating" sortable="true"/>
              <a:column width="160" caption="Date modified" type="date" name="modified" sortable="true"/>
            </a:listheader>
          </a:listview>
        </a:pane>
      </a:splitter>
    </a:box>
  </a:wbody>

</a:window>

Deleted lines 810-812:
November 11, 2006, at 12:20 AM by Tassos Koutsovassilis -
Changed line 287 from:

Therefore, we have to create a new recipe form using a new QuiX? servlet. We start by creating the

to:

Therefore, we have to create a new recipe form using a new QuiX servlet. We start by creating the

Changed line 289 from:

It is recommended to maintain the QuiX? servlets and the XML-RPC servlets in separate Python files

to:

It is recommended to maintain the QuiX servlets and the XML-RPC servlets in separate Python files

Changed lines 331-332 from:

Remember that every QuiX servlet (an instance of the XULServlet class) must always be accompanied with a xul file in the same directory. This file usually contains the XML definition of the interface,

to:

Remember that every QuiX servlet (an instance of the XULServlet class) must always be accompanied with a quix file in the same directory. This file usually contains the XML definition of the interface,

Changed line 437 from:
to:
Changed lines 529-530 from:
to:
Changed lines 631-632 from:
to:
Changed line 663 from:
to:
Changed line 678 from:

Apart from the custom forms and the custom content types, we will also need a custom UI tailored

to:

Apart from the custom forms and the custom content types, we will also need a custom UI for our application tailored

Changed lines 681-684 from:
to:

Before creating our application object we must publish the "org/innoscript/recipemanager" direcotry. This directory will contain our applications XML UI definition and the required JavaScript functions. Edit the "conf/pubdir.xml" configuration file by adding the highlighted node:

Changed lines 685-703 from:

<a:splitter width="100" height="100" orientation="h" spacing="0">

  <a:pane length="28">
    <a:toolbar width="100" height="100">
      <a:tbbutton caption="Create recipe folder" width="120">
        <a:prop name="cc" value="schemas.org.innoscript.recipemanager.RecipeContainer?"/>
      </a:tbbutton>
      <a:tbbutton caption="Create recipe" width="80">
        <a:prop name="cc" value="schemas.org.innoscript.recipemanager.Recipe"/>
      </a:tbbutton>
      <a:tbsep/>
      <a:tbbutton caption="Refresh" width="60" />
    </a:toolbar>
  </a:pane>
  <a:pane length="-1" bgcolor="white">
    <a:splitter width="100" height="100" orientation="v"
      interactive="true">
      <a:pane length="200">
        <a:outlookbar width="100" height="100">
          <a:tool caption="Recipes">
to:

<config>

	<dirs>
		<dir name="__quix" path="quix"/>
		<dir name="desktop" path="org/innoscript/desktop"/>
		<dir name="hypersearch" path="org/innoscript/hypersearch"/>
		<dir name="usermgmnt" path="org/innoscript/usermgmnt"/>
		<dir name="queryperformer" path="org/innoscript/queryperformer"/>
Changed lines 695-696 from:
            <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
              <a:treenode id="[Put here the ID of the Recipes folder]" haschildren="true"
to:
		<dir name="recipemanager" path="org/innoscript/recipemanager"/>
Changed lines 699-721 from:
                img="images/folder.gif" caption="Recipes">
              </a:treenode>
            </a:foldertree>
          </a:tool>
          <a:tool caption="Search" bgcolor="gray">
            <a:label caption="Recipe title contains:" />
            <a:field id="title" left="5" top="20" width="160" />
            <a:label top="50" caption="Preparation time is less than" />
            <a:field id="preparationTime" left="5" top="70" width="30" />
            <a:label top="72" left="40" caption="min." />
            <a:label top="100" caption="The recipe's rating is greater than" />
            <a:spinbutton top="120" left="5" width="40" max="10" value="0"
              id="rating"/>
            <a:label top="150" caption="Recipe ingredients contain:" />
            <a:field id="ingredients" top="170" left="5" width="160" />
            <a:label top="190" caption="(comma separated list)"
              style="font-style:italic" />
            <a:button top="220" left="center" width="60" height="28"
              caption="Search" />
          </a:tool>
        </a:outlookbar>
      </a:pane>
      <a:pane length="-1">
to:
	</dirs>

</config>

Changed lines 702-705 from:

to:

Each published directory must contain a file called "config.xml". This file keeps track of the files accesible over HTTP. This file should be:

Changed lines 707-719 from:
        <a:listview id="list" width="100" height="100">
to:

<config>

	<context path="recipemanager.quix"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.quix"/>
	<context path="recipemanager.js"
			 method="GET"
			 client=".*"
			 lang=".*"
			 action="recipemanager.js"/>

</config>

Added lines 721-727:
Changed lines 730-744 from:
          <a:listheader>
            <a:column width="140" caption="Recipe title" name="displayName"
              bgcolor="#EFEFEF" sortable="true"></a:column>
            <a:column width="60" caption="Servings" type="int" name="servings"
              sortable="true"></a:column>
            <a:column width="100" caption="Preparation time" type="int"
              name="preparationTime" sortable="true"></a:column>
            <a:column width="40" caption="Rating" type="int" name="rating"
              sortable="true"></a:column>
            <a:column width="160" caption="Date modified" type="date"
              name="modified" sortable="true"></a:column>
          </a:listheader>
        </a:listview>
      </a:pane>
    </a:splitter>
to:

<a:splitter width="100" height="100" orientation="h" spacing="0">

  <a:pane length="28">
    <a:toolbar width="100" height="100">
      <a:tbbutton caption="Create recipe folder" width="120">
        <a:prop name="cc" value="schemas.org.innoscript.recipemanager.RecipeContainer?"/>
      </a:tbbutton>
      <a:tbbutton caption="Create recipe" width="80">
        <a:prop name="cc" value="schemas.org.innoscript.recipemanager.Recipe"/>
      </a:tbbutton>
      <a:tbsep/>
      <a:tbbutton caption="Refresh" width="60" />
    </a:toolbar>
Changed lines 743-748 from:

</a:splitter>

to:
  <a:pane length="-1" bgcolor="white">
    <a:splitter width="100" height="100" orientation="v"
      interactive="true">
      <a:pane length="200">
        <a:outlookbar width="100" height="100">
          <a:tool caption="Recipes">
Changed lines 750-758 from:
to:

Changed lines 752-754 from:

... <a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
to:
            <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4">
              <a:treenode id="[Put here the ID of the Recipes folder]" haschildren="true"
Changed line 755 from:

to:

Changed lines 757-779 from:
    onclick="recipemanager.createItem">
to:
                img="images/folder.gif" caption="Recipes">
              </a:treenode>
            </a:foldertree>
          </a:tool>
          <a:tool caption="Search" bgcolor="gray">
            <a:label caption="Recipe title contains:" />
            <a:field id="title" left="5" top="20" width="160" />
            <a:label top="50" caption="Preparation time is less than" />
            <a:field id="preparationTime" left="5" top="70" width="30" />
            <a:label top="72" left="40" caption="min." />
            <a:label top="100" caption="The recipe's rating is greater than" />
            <a:spinbutton top="120" left="5" width="40" max="10" value="0"
              id="rating"/>
            <a:label top="150" caption="Recipe ingredients contain:" />
            <a:field id="ingredients" top="170" left="5" width="160" />
            <a:label top="190" caption="(comma separated list)"
              style="font-style:italic" />
            <a:button top="220" left="center" width="60" height="28"
              caption="Search" />
          </a:tool>
        </a:outlookbar>
      </a:pane>
      <a:pane length="-1">
Changed line 781 from:

to:

Changed lines 783-785 from:
    ...
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
to:
        <a:listview id="list" width="100" height="100">
Changed line 785 from:

to:

Changed lines 787-803 from:
    onclick="recipemanager.createItem">
to:
          <a:listheader>
            <a:column width="140" caption="Recipe title" name="displayName"
              bgcolor="#EFEFEF" sortable="true"></a:column>
            <a:column width="60" caption="Servings" type="int" name="servings"
              sortable="true"></a:column>
            <a:column width="100" caption="Preparation time" type="int"
              name="preparationTime" sortable="true"></a:column>
            <a:column width="40" caption="Rating" type="int" name="rating"
              sortable="true"></a:column>
            <a:column width="160" caption="Date modified" type="date"
              name="modified" sortable="true"></a:column>
          </a:listheader>
        </a:listview>
      </a:pane>
    </a:splitter>
  </a:pane>

</a:splitter>

Added lines 805-812:
Changed lines 815-818 from:
    ...
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
to:

... <a:toolbar width="100" height="100">

  <a:tbbutton caption="Create recipe folder" width="120"
Changed line 821 from:
    onclick="recipemanager.refresh_onclick" />
to:
    onclick="recipemanager.createItem">
Changed lines 825-827 from:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

to:
    ...
  </a:tbbutton>
  <a:tbbutton caption="Create recipe" width="80"
Changed line 831 from:
  onload="recipemanager.loadRecipes">
to:
    onclick="recipemanager.createItem">
Changed lines 835-838 from:
  ...
to:
    ...
  </a:tbbutton>
  <a:tbsep />
  <a:tbbutton caption="Refresh" width="60"
Changed lines 840-843 from:

In the application's script tab insert the following code:

to:

Changed lines 842-846 from:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
to:
    onclick="recipemanager.refresh_onclick" />
Changed line 844 from:

to:

Changed lines 846-848 from:
  var sCC = w.attributes.cc;
to:

</a:toolbar> ... <a:listview id="list" multiple="true" width="100" height="100"

Changed line 850 from:

to:

Changed line 852 from:
  var id = oTree.getSelection().getId();
to:
  onload="recipemanager.loadRecipes">
Changed line 854 from:

to:

Changed lines 856-863 from:
  document.desktop.parseFromUrl(id + '?cmd=new&cc=' + sCC,
    function(w) {
      w.attributes.refreshlist = function() {
        if (sCC=='schemas.org.innoscript.recipemanager.Recipe')
          recipemanager.refreshList(oWin);
      }
    }
  );
to:
  ...
Added lines 858-860:

In the application's script tab insert the following code:

Changed lines 863-867 from:

}

recipemanager.refresh_onclick = function(evt, w) {

  recipemanager.loadRecipes(w);

}

to:

function recipemanager() {}

recipemanager.createItem = function(evt, w) {

  var oWin = w.getParentByType(Window);
  var oTree = oWin.getWidgetById('tree');
Changed line 871 from:

recipemanager.loadRecipes = function(w) {

to:
  var sCC = w.attributes.cc;
Deleted lines 874-879:
  var appWin = w.getParentByType(Window);
  recipemanager.refreshList(appWin);

}

recipemanager.refreshList = function(appWin) {

  var oTree = appWin.getWidgetById('tree');
Deleted lines 875-878:
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'schemas.org.innoscript.recipemanager.Recipe' order by displayName asc";
Changed lines 879-881 from:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
to:
  document.desktop.parseFromUrl(id + '?cmd=new&cc=' + sCC,
    function(w) {
      w.attributes.refreshlist = function() {
        if (sCC=='schemas.org.innoscript.recipemanager.Recipe')
          recipemanager.refreshList(oWin);
      }
    }
  );
Deleted line 889:
  xmlrpc.callmethod('executeOqlCommand', sOql);
Changed lines 892-894 from:

recipemanager.updateList = function(req) {

to:

recipemanager.refresh_onclick = function(evt, w) {

  recipemanager.loadRecipes(w);

}

Changed lines 898-899 from:
  req.callback_info.dataSet = req.response;
  req.callback_info.refresh();
to:

recipemanager.loadRecipes = function(w) {

Added lines 902-903:
  var appWin = w.getParentByType(Window);
  recipemanager.refreshList(appWin);
Added lines 905-912:

recipemanager.refreshList = function(appWin) {

  var oTree = appWin.getWidgetById('tree');
  var id = oTree.getSelection().getId();
  var listView = appWin.getWidgetById('list');
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
             "modified from '" + id + "' where contentclass = " +
             "'schemas.org.innoscript.recipemanager.Recipe' order by displayName asc";
Changed lines 914-931 from:
to:

Changed lines 916-917 from:

... <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

to:
  var xmlrpc = new XMLRPCRequest?(QuiX?.root);
  xmlrpc.oncomplete = recipemanager.updateList;
  xmlrpc.callback_info = listView;
Changed line 920 from:

to:

Changed lines 922-925 from:
  onselect="recipemanager.loadRecipes">
to:
  xmlrpc.callmethod('executeOqlCommand', sOql);

}

recipemanager.updateList = function(req) {

Changed line 927 from:

to:

Changed lines 929-931 from:
  ...

</a:foldertree> ...

to:
  req.callback_info.dataSet = req.response;
  req.callback_info.refresh();
Deleted lines 931-933:
Changed lines 934-935 from:

... <a:button top="220" left="center" width="60" height="28" caption="Search"

to:

}

Changed lines 936-953 from:

to:
Changed lines 955-956 from:
  onclick="recipemanager.search" />
to:

... <a:foldertree id="tree" method="getSubtree" padding="4,4,4,4"

Changed line 958 from:

to:

Changed line 960 from:

...

to:
  onselect="recipemanager.loadRecipes">
Deleted lines 961-963:
Changed lines 964-969 from:

recipemanager.search = function(evt, w) {

  var displayName = w.parent.getWidgetById('title').getValue();
  var preparationTime = w.parent.getWidgetById('preparationTime').getValue();
  var rating = w.parent.getWidgetById('rating').getValue
  var ingredients = w.parent.getWidgetById('ingredients').getValue();
  var sOql = "select id, displayName, servings, preparationTime, rating, " +
to:
  ...

</a:foldertree> ...

Changed lines 968-971 from:

to:
Changed lines 973-974 from:
             "modified from deep('/Recipes')";
to:

... <a:button top="220" left="center" width="60" height="28" caption="Search"

Changed line 976 from:

to:

Added lines 978-1001:
November 09, 2006, at 10:05 PM by Tassos Koutsovassilis -
Deleted line 4:

Download this tutorial in PDF format\\

November 09, 2006, at 10:03 PM by Tassos Koutsovassilis -
Changed line 665 from:
to:
November 09, 2006, at 09:59 PM by Tassos Koutsovassilis -
Changed line 280 from:
to:
Changed line 283 from:

happening because the servlet, in its current state, ignores the numeric data types (a QuiX? numeric

to:

happening because the servlet, in its current state, ignores the numeric data types (a QuiX numeric

Changed lines 289-290 from:
to:

Python module that will host our application servlets.

Changed line 294 from:
to:
Changed lines 300-301 from:

from resources.system import ui from schemas.org.innoscript import recipemanager

to:

from org.innoscript.desktop import ui from org.innoscript.recipemanager import schema

Changed line 306 from:

recipe, we must register a new QuiX? servlet that acts as a splitter. This is because the

to:

recipe, we must register a new QuiX servlet that acts as a splitter. This is because the

Changed line 324 from:
    if sCC == 'schemas.org.innoscript.recipemanager.RecipeContainer?':
to:
    if sCC == 'org.innoscript.recipemanager.schema.RecipeContainer?':
Changed line 327 from:
    elif sCC == 'schemas.org.innoscript.recipemanager.Recipe':
to:
    elif sCC == 'org.innoscript.recipemanager.schema.Recipe':
Changed line 332 from:

Remember that every QuiX? servlet (instance of the XULServlet class) must always be accompanied

to:

Remember that every QuiX servlet (an instance of the XULServlet class) must always be accompanied

Changed lines 337-340 from:
to:
Changed line 348 from:

As you propably already have guessed, the server formats the contents of the xul file using the

to:

As you propably already have guessed, the server formats the contents of the quix file using the

Changed line 356 from:
  <reg cc="schemas.org.innoscript.recipemanager.RecipeContainer?"
to:
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
Changed lines 361-362 from:
    action="resources.system.XMLRPC.ContainerGeneric?"/>
  <reg cc="schemas.org.innoscript.recipemanager.RecipeContainer?"
to:
    action="org.innoscript.desktop.XMLRPC.ContainerGeneric?"/>
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
Changed lines 370-371 from:
    action="resources.recipemanager.ui.RecipeContainerSplitter?"/>
  <reg cc="schemas.org.innoscript.recipemanager.Recipe$"
to:
    action="org.innoscript.recipemanager.ui.RecipeContainerSplitter?"/>
  <reg cc="org.innoscript.recipemanager.schema.Recipe$"
Changed line 376 from:
    action="resources.recipemanager.ui.RecipeForm?"/>
to:
    action="org.innoscript.recipemanager.ui.RecipeForm?"/>
Changed line 397 from:
    if sCC == 'schemas.org.innoscript.recipemanager.RecipeContainer?':
to:
    if sCC == 'org.innoscript.recipemanager.schema.RecipeContainer?':
Changed line 407 from:
    elif sCC == 'schemas.org.innoscript.recipemanager.Recipe':
to:
    elif sCC == 'org.innoscript.recipemanager.schema.Recipe':
Changed line 418 from:
      'URI': self.request.serverVariables['SCRIPT_NAME'] + '/' + self.item.
to:
      'URI': self.request.serverVariables['SCRIPT_NAME'] + '/' + self.item.id,
Changed lines 439-441 from:
to:
Changed lines 447-448 from:
    <a:script name="Generic Form Script" src="scripts/form_auto.js" />
    <a:script name="Generic Functions" src="scripts/generic.js" />
to:
    <a:script name="Generic Form Script" src="desktop/ui.Frm_Auto.js" />
    <a:script name="Generic Functions" src="desktop/generic.js" />
Changed lines 531-532 from:
to:
Changed lines 560-561 from:
to:
Changed lines 565-566 from:
to:
Changed lines 631-634 from:
to:
November 09, 2006, at 09:17 PM by Tassos Koutsovassilis -
Changed line 268 from:
to:
November 09, 2006, at 08:19 PM by Tassos Koutsovassilis -
Changed line 197 from:
    'schemas.org.innoscript.recipemanager.RecipeContainer?',
to:
    'org.innoscript.recipemanager.schema.RecipeContainer?',
Changed line 224 from:
  <reg cc="schemas.org.innoscript.recipemanager.RecipeContainer?"
to:
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
Changed line 238 from:
    action="resources.system.XMLRPC.ContainerGeneric?"/>
to:
    action="org.innoscript.desktop.XMLRPC.ContainerGeneric?"/>
Changed line 242 from:
  <reg cc="schemas.org.innoscript.recipemanager.RecipeContainer?"
to:
  <reg cc="org.innoscript.recipemanager.schema.RecipeContainer?"
Changed line 256 from:
    action="resources.system.ui.Frm_AutoNew"/>
to:
    action="org.innoscript.desktop.ui.Frm_AutoNew"/>
November 07, 2006, at 11:23 PM by Tassos Koutsovassilis -
Changed line 26 from:

Create a new folder named "recipemanager" inside the "org/innoscript'" folder. Add an empty "__init__.py" file to make it a Python package. Inside this folder create a new python script named "schema.py''". This file will host our custom content classes.

to:

Create a new folder named "recipemanager" inside the "org/innoscript'" folder. Add an empty "__init__.py" file to make it a Python package. Inside this folder create a new python script named "schema.py''". This file will host our custom content classes.

Changed lines 66-68 from:
to:
Changed lines 71-74 from:
to:
Changed line 78 from:
  class category_objects(RelatorN?):
to:
Changed lines 81-82 from:
      'schemas.org.innoscript.common.Document',
      'schemas.org.innoscript.collab.Contact',
to:
      'org.innoscript.desktop.schema.common.Document',
      'org.innoscript.desktop.schema.collab.Contact',
Changed line 86 from:
      'schemas.org.innoscript.recipemanager.Recipe',
to:
      'org.innoscript.recipemanager.schema.Recipe',
Changed line 132 from:
    self.categories = properties.categories()
to:
    self.categories = properties.Categories()
Changed lines 156-157 from:
    self.category_objects = properties.category_objects()@]
to:
    self.category_objects = properties.CategoryObjects?()@]
Changed lines 160-161 from:
to:
Changed line 164 from:

class category_objects(RelatorN?):

to:
Changed lines 167-169 from:
    'schemas.org.innoscript.common.Document',
    'schemas.org.innoscript.collab.Contact',
    'schemas.org.innoscript.recipemanager.Recipe',
to:
    'org.innoscript.desktop.schema.common.Document',
    'org.innoscript.desktop.schema.collab.Contact',
    'org.innoscript.recipemanager.schema.Recipe',
November 07, 2006, at 11:09 PM by Tassos Koutsovassilis -
Changed lines 26-29 from:
to:

Create a new folder named "recipemanager" inside the "org/innoscript'" folder. Add an empty "__init__.py" file to make it a Python package. Inside this folder create a new python script named "schema.py''". This file will host our custom content classes. In the case of our sample application, the recipe manager, we will initially need two new content classes. The first content class is a container capable of keeping recipes and the second content class is the recipe itself. Open the blank "schema.py" with your favourite Pyhton editor. First, we need to import the primary Porcupine content classes and the primary data types:

Changed lines 43-44 from:
    'schemas.org.innoscript.recipemanager.RecipeContainer?',
    'schemas.org.innoscript.recipemanager.Recipe'
to:
    'org.innoscript.recipemanager.schema.RecipeContainer?',
    'org.innoscript.recipemanager.schema.Recipe'
October 29, 2006, at 10:15 PM by Tassos Koutsovassilis -
Added lines 2-4:

This tutorial is valid only for Porcupine 0.0.7. We are currently in the process of updating this tutorial to comply with the new 0.0.8 release.

February 24, 2006, at 11:37 PM by Tassos Koutsovassilis -
Changed lines 3-4 from:

Download the Recipe Manager Porcupine package file

to:

Download the Recipe Manager Porcupine package file

February 24, 2006, at 11:15 PM by 213.5.105.143 -
Changed line 831 from:
  var id = oTree.getSelection().id;
to:
  var id = oTree.getSelection().getId();
Changed line 864 from:
  var id = oTree.getSelection().id;
to:
  var id = oTree.getSelection().getId();
February 24, 2006, at 11:06 PM by 213.5.105.143 -
Changed line 287 from:
to:
Changed lines 437-438 from:
to:
Changed line 1139 from:

1=resources/servlets/recipemanager

to:

1=resources/recipemanager

February 24, 2006, at 11:00 PM by 213.5.105.143 -
Changed line 235 from:
    action="resources.servlets.XMLRPC.ContainerGeneric?"/>
to:
    action="resources.system.XMLRPC.ContainerGeneric?"/>
Changed line 253 from:
    action="resources.servlets.ui.Frm_AutoNew"/>
to:
    action="resources.system.ui.Frm_AutoNew"/>
Changed line 265 from:
to:
Changed line 277 from:
to:
Changed line 298 from:

from resources.servlets import ui

to:

from resources.system import ui

Changed line 358 from:
    action="resources.servlets.XMLRPC.ContainerGeneric?"/>
to:
    action="resources.system.XMLRPC.ContainerGeneric?"/>
Changed line 367 from:
    action="resources.servlets.recipemanager.ui.RecipeContainerSplitter?"/>
to:
    action="resources.recipemanager.ui.RecipeContainerSplitter?"/>
Changed line 373 from:
    action="resources.servlets.recipemanager.ui.RecipeForm?"/>
to:
    action="resources.recipemanager.ui.RecipeForm?"/>
Changed lines 562-563 from:
to:
Changed lines 628-629 from:
to:
November 04, 2005, at 12:13 AM by Tassos Koutsovassilis -
Changed lines 559-560 from:
to:
November 03, 2005, at 11:07 PM by Tassos Koutsovassilis -
Changed line 2 from:

Download this tutorial in PDF format

to:

Download this tutorial in PDF format\\

November 03, 2005, at 11:05 PM by Tassos Koutsovassilis -
Changed lines 3-4 from:
to:

Download the Recipe Manager Porcupine package file

Changed line 17 from:
to:
November 01, 2005, at 02:45 PM by Tassos Koutsovassilis -
Changed line 16 from:
to:
November 01, 2005, at 02:43 PM by Tassos Koutsovassilis -
Changed line 291 from:
to:
November 01, 2005, at 02:40 PM by Tassos Koutsovassilis -
Changed lines 265-268 from:

<<<<<<< QuiX? servlet (instance of XULServlet), we should always check for browser compatibility by allowing this servlet to run only on the supported browsers. What this servlet actually does is to ======= QuiX? servlet (instance of XULServlet), we should always check for browser compatibility by

to:

QuiX? servlet (instance of XULServlet), we should always check for browser compatibility by

Deleted line 266:

>>>>>>>

Changed line 291 from:
to:
Changed line 1212 from:
to:
November 01, 2005, at 02:36 PM by Tassos Koutsovassilis -
Added lines 265-267:

<<<<<<< QuiX? servlet (instance of XULServlet), we should always check for browser compatibility by allowing this servlet to run only on the supported browsers. What this servlet actually does is to =======

Added line 270:

>>>>>>>

Changed line 275 from:
to:
Changed line 295 from:
to:
Changed line 332 from:

Remember that every QuiX? servlet (instance of the XULServlet? class) must always be accompanied

to:

Remember that every QuiX? servlet (instance of the XULServlet class) must always be accompanied

Changed line 1216 from:
to:
November 01, 2005, at 02:31 PM by Tassos Koutsovassilis -
Changed line 265 from:

QuiX? servlet (instance of XULServlet?), we should always check for browser compatibility by

to:

QuiX? servlet (instance of XULServlet), we should always check for browser compatibility by

November 01, 2005, at 01:56 PM by Tassos Koutsovassilis -
Changed line 259 from:

As you can see, the first registration takes care of the XML-RPC methods exposed by the

to:

As you can see, the first registration takes care of the XML-RPC methods exposed by the

November 01, 2005, at 01:54 PM by Tassos Koutsovassilis -
Changed line 259 from:

As you can see, the first registration takes care of the XML-RPC methods exposed by the

to:

As you can see, the first registration takes care of the XML-RPC methods exposed by the

November 01, 2005, at 12:43 PM by 212.205.44.230 -
Changed lines 1-5 from:


Download PDF

to:

developers (intermediate) Download this tutorial in PDF format

November 01, 2005, at 12:33 PM by 212.205.44.230 -
Changed lines 1-3 from:
to:


November 01, 2005, at 12:29 PM by 212.205.44.230 -
Changed lines 1-3 from:

Developers (intermediate) Download tutorial in PDF

to:

Download PDF

November 01, 2005, at 12:21 PM by 212.205.44.230 -
Added line 1:

Developers (intermediate)

November 01, 2005, at 12:15 PM by 212.205.44.230 -
Added lines 1-2:

Download tutorial in PDF

November 01, 2005, at 12:06 PM by 212.205.44.230 -
Deleted line 0:

title The Recipe Manager tutorial

November 01, 2005, at 10:27 AM by Tassos Koutsovassilis -
Changed lines 43-44 from:
to:
November 01, 2005, at 10:22 AM by Tassos Koutsovassilis -
Added lines 1-1210:
Page last modified on April 20, 2010, at 03:38 AM