Prototyping a Project Management App - Part 4

Now that we have some functions that cover creating boxes and arrows, let's take a step back and think about how these functions will get called.

Keeping with the recipe theme, when we first enter the screen, we are either going to be beginning a new recipe from scratch, or we are going to want to show the logic for an existing recipe. This means we need to provide a way for a user to add a new recipe step (perhaps a button named "add box"), but also have code that will create any boxes and arrows already described by a previously saved recipe. Ideally, it would be preferable to be able to re-use as much of the same code as possible to handle both situations.

We already have a function that creates a box, but that function expects to be told where to put the box and what size to make it, so let's think about how to handle those aspects. If we are starting from scratch then we probably just want to put a new box at the top left of the screen. However, if we already have boxes on the screen, then we need to make sure we don't overlap any of those. We also need to know if the new box is supposed to go after an existing one (e.g. we're adding Step 2, which comes after Step 1), or if it's the start of some new logic (e.g. we're creating Step 1 of some process), or if the new box is a sub-step of another step (remember in our earlier examples we had a high level step called "Pre Ingredients" containing sub-steps such as "cut steak into cubes").

Let's think through the logic and again write a function containing comments that describe the logical steps we'll need to address in our code.

var createNewBox = function (boxType) {
 // If boxType == "Beginning Step"
  // Render a box at the top left of the screen
  // but without overlapping any existing boxes
 // If boxType == "Next Step"
  // Render a box directly to the right of the box representing
  // the previous step without overlapping any existing boxes
 // If boxType == "Sub-Step"
  // Render a box inside of the box that we are adding
  // a sub-step to. Remember to change the size of the parent
  // box so that the new box fits inside of it
}

Here we have a new function "createNewBox" and we're going to tell it the type of new box we're adding via the variable "boxType" (we'll rely on the user to tell us what type of box they are adding). Depending on the value of "boxType", we'll create a new box following a different set of logic. Looking over our comments, two of our box types require us to know the position and/or size of the prior or parent step that our new box relates to. We then also have the general logic of "don't overlap any other boxes". So, let's extend our function and give it another input variable containing details of the box where we are coming from.


var createNewBox = function (boxType, boxComingFrom) {
 // If boxType == "Beginning Step"
  // Render a box at the top left of the screen
  // but without overlapping any existing boxes
 // If boxType == "Next Step"
  // Render a box directly to the right of boxComingFrom                    
   // Render a new box using the following values
   // for x and y
   // x = boxComingFrom.x + boxComingFrom.width + (some fixed space between boxes)
   // y = boxComingFrom.y
  // without overlapping any existing boxes
 // If boxType == "Sub-Step"
  // Render a box inside of boxComingFrom
   // Render a new box using the following values
   // for x and y
   // x = boxComingFrom.x + (some fixed padding amount)
   // y = boxComingFrom.y + (some fixed padding amount)
  // Remember to change the size of the parent
  // box so that the new box fits inside of it
}

So we're getting closer, but we still need to figure out how to handle "don't overlap any other boxes". Let's visualize things so we can figure out what we need to account for. A new beginning step could look something like this:


The important thing to note here is that we can't just look at other beginning steps, we need to look at all other steps and make sure to position our box below them all. We might be able to get away from looking at all boxes by just looking at the currently lowest beginning box and then checking all other boxes that follow it (in this example that would be boxes 2a and 2b). This is looking like we might want to create a standalone function to calculate the full dimensions of a complete sequence of steps. We know we'll need height for this problem, but I can see where we might also need width down the road, so creating a function to return full dimensions seems like a good idea.

Let's consider the other cases as there may be some overlap. Adding a "next step" might look something like this:


This new box clearly needs to know the positions and dimensions of any boxes directly above it (we might call these sibling steps). Having a function that returns the dimensions of all subsequent steps from any other step seems like it would be useful here. Comparing this to our conclusion above about needing a function to get the full dimensions of a full set of steps, this would appear to be a subset of that. In fact, my recursion spidey sense is tingling. Getting the full dimensions of a sequence of steps can probably be written as "get the dimensions of the next set of steps and then do this again for the next set of steps from each of those steps and so on".

Musing on this a little more, I'm starting to see that setting the correct size and position of a box actually requires knowing all about the steps that follow on from it. We are probably going to need a function that iterates down a full series of steps and then moves back along the sequence updating the size and position of each box as it goes.

Let's take a look at our final case and see if we need anything more.


This step needs to know the position of its "parent" step as well as the positions and dimensions of any other steps directly above it (again, "sibling" steps). I don't think there is anything too new here except obviously we're going to need to remember to account for boxes sitting within boxes. Also, note how the dimensions of "Step 2b" are obviously much larger than our other steps so as to fit all of its sub-steps inside of itself. So the other function we probably need to write is a "re-size box" function that makes a box bigger when we add a new sub-step to it.

While I'm at it, there is one more thing to consider. If we start adding sub-steps earlier in our flow, we're going to need to re-position our other boxes, to make sure that things don't overlap. Here's a simple example:



In this example, our new box forces us to make "Step 2a" larger. This in turn means we need to tell "Step 2b" to move down or risk getting hidden behind our expanding 2a. So let's add a function that will take care of re-positioning our boxes as well.

So, it looks like we need to write a few new functions to complete our logic. Let's create our next set of functions and again describe the logical steps using comments. Don't worry, we'll get to writing some code in just a sec (or at least the next post)!

var resizeBox = function (boxToResize) {
 // Update box width and height with new values
 // Making this box a different size:
  // will affect the size of:
   // this box's parent node
  // will affect the positioning of: 
   // boxes below
   // boxes to the right of this box
}

var repositionBox = function (boxToReposition, newX, newY) {
 // Update box x and y coordinates 
            // Mirror the logic used by "createNewBox" to 
            // determine the x and y coordinates for this box
 // Moving this box to a new location:
  // will affect the position of:
   // all sub steps
   // all next steps
}

var getDimensionsOfSubSteps = function (currentStep) {
 // define variables to track width and height
 var width = 0;
 var height = 0;
 // for each sub step
  // call getDimensionsOfSubSteps and pass it the sub step
  // update width and height by adding the width and height
  // from the sub step
 // return width and height
 return {
  width: width,
  height: height
 }
}

var getDimensionsOfNextSteps = function (currentStep) {
 // define variables to track width and height
 var width = 0;
 var height = 0;
 // for each next step
  // call getDimensionsOfNextSteps and pass it the next step
  // update width and height values
                // if widthOfNextStep > width
                    // width = widthOfNextStep
                // height = height + heightOfNextStep
 // return width and height
 return {
  width: width,
  height: height
 }
}   

var getDimensionsOfCurrentStepAndAllNextSteps = function (currentStep) {
 // define variables to track width and height and set them
 // to the current values of the currentStep width and height
 var width = currentStep.width;
 var height = currentStep.height;
 // call getDimensionsOfNextSteps
 // update the width and height values
 // width = width + (some fixed space between boxes) + widthOfNextSteps
 // if heightOfNextSteps > height
  // height = heightOfNextSteps
 // return width and height
 return {
  width: width,
  height: height
 } 
}

Ok, this looks like a good first set of functions from which to get started. Time to write some code!


Comments

Popular Posts