simplifying javascript projects with reactjs

49
Simplifying JavaScript Projects with ReactJS and Friends Kevin Dangoor, Sr. Computer Scientist, Adobe 1DevDay Detroit 2014

Upload: kevin-dangoor

Post on 13-Jul-2015

915 views

Category:

Software


2 download

TRANSCRIPT

Page 1: Simplifying JavaScript Projects with ReactJS

Simplifying JavaScript Projects with ReactJS and Friends

Kevin Dangoor, Sr. Computer Scientist, Adobe 1DevDay Detroit 2014

Page 2: Simplifying JavaScript Projects with ReactJS

A modern, open source text editor that understands web design.

Page 3: Simplifying JavaScript Projects with ReactJS

Brackets• MIT-licensed open source

• Sponsored by Adobe with hundreds of contributors and a number of non-Adobe committers

• 13th most starred project

• Hundreds of extensions are available

• 1.0 just released 10 days ago, with Extract for Brackets

Page 4: Simplifying JavaScript Projects with ReactJS
Page 5: Simplifying JavaScript Projects with ReactJS

function  _documentSelectionFocusChange()  {          var  curFile  =  EditorManager.getCurrentlyViewedPath();          if  (curFile  &&  _hasFileSelectionFocus())  {  

Page 6: Simplifying JavaScript Projects with ReactJS

function  _documentSelectionFocusChange()  {          var  curFile  =  EditorManager.getCurrentlyViewedPath();          if  (curFile  &&  _hasFileSelectionFocus())  {                  var  nodeFound  =  $("#project-­‐files-­‐container  li").is(function  (index)  {                          var  $treeNode  =  $(this),                                  entry  =  $treeNode.data("entry");                          if  (entry  &&  entry.fullPath  ===  curFile)  {    

Page 7: Simplifying JavaScript Projects with ReactJS

function  _documentSelectionFocusChange()  {          var  curFile  =  EditorManager.getCurrentlyViewedPath();          if  (curFile  &&  _hasFileSelectionFocus())  {                  var  nodeFound  =  $("#project-­‐files-­‐container  li").is(function  (index)  {                          var  $treeNode  =  $(this),                                  entry  =  $treeNode.data("entry");                          if  (entry  &&  entry.fullPath  ===  curFile)  {                                  if  (!_projectTree.jstree("is_selected",  $treeNode))  {                                          if  ($treeNode.parents(".jstree-­‐closed").length)  {                                                  //don't  auto-­‐expand  tree  to  show  file  -­‐  but  remember  it  if  parent  is  manually  expanded  later                                                  _projectTree.jstree("deselect_all");                                                  _lastSelected  =  $treeNode;                                          }  else  {    

Page 8: Simplifying JavaScript Projects with ReactJS

if  (!_projectTree.jstree("is_selected",  $treeNode))  {          if  ($treeNode.parents(".jstree-­‐closed").length)  {                  //don't  auto-­‐expand  tree  to  show  file  -­‐  but  remember  it  if  parent  is  manually  expanded  later                  _projectTree.jstree("deselect_all");                  _lastSelected  =  $treeNode;          }  else  {                  //we  don't  want  to  trigger  another  selection  change  event,  so  manually  deselect                  //and  select  without  sending  out  notifications                  _projectTree.jstree("deselect_all");                  _projectTree.jstree("select_node",  $treeNode,  false);  //  sets  _lastSelected          }  }    

Page 9: Simplifying JavaScript Projects with ReactJS

If it’s hard to test, it won’t be tested.

Page 10: Simplifying JavaScript Projects with ReactJS

http://www.infoq.com/presentations/Simple-Made-Easy

Page 11: Simplifying JavaScript Projects with ReactJS

simple!

composed of one thing, not combined

complect!

interweave

Page 12: Simplifying JavaScript Projects with ReactJS

function  _documentSelectionFocusChange()  {          var  curFile  =  EditorManager.getCurrentlyViewedPath();          if  (curFile  &&  _hasFileSelectionFocus())  {                  var  nodeFound  =  $("#project-­‐files-­‐container  li").is(function  (index)  {                          var  $treeNode  =  $(this),                                  entry  =  $treeNode.data("entry");                          if  (entry  &&  entry.fullPath  ===  curFile)  {                                  if  (!_projectTree.jstree("is_selected",  $treeNode))  {                                          if  ($treeNode.parents(".jstree-­‐closed").length)  {                                                  //don't  auto-­‐expand  tree  to  show  file  -­‐  but  remember  it  if  parent  is  manually  expanded  later                                                  _projectTree.jstree("deselect_all");                                                  _lastSelected  =  $treeNode;                                          }  else  {    

Simple?

Page 13: Simplifying JavaScript Projects with ReactJS

easy!

near at hand

Page 14: Simplifying JavaScript Projects with ReactJS

React

Page 15: Simplifying JavaScript Projects with ReactJS

https://www.destroyallsoftware.com/talks/boundaries

Page 16: Simplifying JavaScript Projects with ReactJS

Functional core, Integration shell

Page 17: Simplifying JavaScript Projects with ReactJS

function  _documentSelectionFocusChange()  {          var  curFile  =  EditorManager.getCurrentlyViewedPath();          if  (curFile  &&  _hasFileSelectionFocus())  {                  var  nodeFound  =  $("#project-­‐files-­‐container  li").is(function  (index)  {                          var  $treeNode  =  $(this),                                  entry  =  $treeNode.data("entry");                          if  (entry  &&  entry.fullPath  ===  curFile)  {                                  if  (!_projectTree.jstree("is_selected",  $treeNode))  {                                          if  ($treeNode.parents(".jstree-­‐closed").length)  {                                                  //don't  auto-­‐expand  tree  to  show  file  -­‐  but  remember  it  if  parent  is  manually  expanded  later                                                  _projectTree.jstree("deselect_all");                                                  _lastSelected  =  $treeNode;                                          }  else  {    

Page 18: Simplifying JavaScript Projects with ReactJS

       function  _documentSelectionFocusChange()  {                  var  curFullPath  =  MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE);                  if  (curFullPath  &&  _hasFileSelectionFocus())  {                          actionCreator.setSelected(curFullPath,  true);                  }  else  {                          actionCreator.setSelected(null);                  }                  _fileViewControllerChange();          }    

Page 19: Simplifying JavaScript Projects with ReactJS
Page 20: Simplifying JavaScript Projects with ReactJS

http://futurice.com/blog/reactive-mvc-and-the-virtual-dom/

Page 21: Simplifying JavaScript Projects with ReactJS

ProjectModel

FileTreeViewModel

FileTreeView ActionCreator

React

Page 22: Simplifying JavaScript Projects with ReactJS

       function  render(element,  viewModel,  projectRoot,  actions,  forceRender,  platform)  {                  if  (!projectRoot)  {                          return;                  }                    React.renderComponent(fileTreeView({                          treeData:  viewModel.treeData,                          selectionViewInfo:  viewModel.selectionViewInfo,                          sortDirectoriesFirst:  viewModel.sortDirectoriesFirst,                          parentPath:  projectRoot.fullPath,                          actions:  actions,                          extensions:  _extensions,                          platform:  platform,                          forceRender:  forceRender                  }),                              element);          }

Page 23: Simplifying JavaScript Projects with ReactJS

       var  fileTreeView  =  React.createClass({                    /**                    *  Update  for  any  change  in  the  tree  data  or  directory  sorting  preference.                    */                  shouldComponentUpdate:  function  (nextProps,  nextState)  {                          return  nextProps.forceRender  ||                                  this.props.treeData  !==  nextProps.treeData  ||                                  this.props.sortDirectoriesFirst  !==  nextProps.sortDirectoriesFirst  ||                                  this.props.extensions  !==  nextProps.extensions  ||                                  this.props.selectionViewInfo  !==  nextProps.selectionViewInfo;                  },

Page 24: Simplifying JavaScript Projects with ReactJS

               render:  function  ()  {                          var  contents  =  directoryContents({                                          isRoot:  true,                                          parentPath:  this.props.parentPath,                                          sortDirectoriesFirst:  this.props.sortDirectoriesFirst,                                          contents:  this.props.treeData,                                          extensions:  this.props.extensions,                                          actions:  this.props.actions,                                          forceRender:  this.props.forceRender,                                          platform:  this.props.platform                                  });                                                    return  DOM.div(                                  null,                                  selectionBackground,                                  contextBackground,                                  extensionForSelection,                                  extensionForContext,                                  contents                          );                  }          });

Page 25: Simplifying JavaScript Projects with ReactJS

       directoryContents  =  React.createClass({                  render:  function  ()  {                          var  extensions  =  this.props.extensions,                                  iconClass  =  extensions  &&  extensions.get("icons")  ?  "jstree-­‐icons"  :  "jstree-­‐no-­‐icons",                                  ulProps  =  this.props.isRoot  ?  {                                          className:  "jstree-­‐brackets  jstree-­‐no-­‐dots  "  +  iconClass                                  }  :  null;                            var  contents  =  this.props.contents,                                  namesInOrder  =  _sortDirectoryContents(contents,  this.props.sortDirectoriesFirst);

Page 26: Simplifying JavaScript Projects with ReactJS

                       return  DOM.ul(ulProps,  namesInOrder.map(function  (name)  {                                  var  entry  =  contents.get(name);                                    if  (FileTreeViewModel.isFile(entry))  {                                          return  fileNode({                                                  parentPath:  this.props.parentPath,                                                  name:  name,                                                  entry:  entry,                                                  actions:  this.props.actions,                                                  extensions:  this.props.extensions,                                                  forceRender:  this.props.forceRender,                                                  platform:  this.props.platform,                                                  key:  name                                          });                                  }  else  {                                          return  directoryNode({

Page 27: Simplifying JavaScript Projects with ReactJS

       directoryNode  =  React.createClass({                  mixins:  [contextSettable,  pathComputer,  extendable],                  render:  function  ()  {                          var  entry  =  this.props.entry,                          if  (entry.get("rename"))  {                                  renameInput  =  directoryRenameInput({                                          actions:  this.props.actions,                                          entry:  this.props.entry,                                          name:  this.props.name,                                          parentPath:  this.props.parentPath                                  });                          }

Page 28: Simplifying JavaScript Projects with ReactJS

       directoryNode  =  React.createClass({                  render:  function  ()  {                          return  DOM.li({                                  className:  this.getClasses("jstree-­‐"  +  nodeClass),                                  onClick:  this.handleClick,                                  onMouseDown:  this.handleMouseDown                          },                                  DOM.ins({                                          className:  "jstree-­‐icon"                                  },  "  "),                                  renameInput,                                  nameDisplay,                                  childNodes);                  }          });

Page 29: Simplifying JavaScript Projects with ReactJS

               handleClick:  function  (event)  {                          var  isOpen  =  this.props.entry.get("open"),                                  setOpen  =  isOpen  ?  false  :  true;                            if  (event.metaKey  ||  event.ctrlKey)  {                                  //  ctrl-­‐alt-­‐click  toggles  this  directory  and  its  children                                  if  (event.altKey)  {                                          if  (setOpen)  {                                                  //  when  opening,  we  only  open  the  immediate  children  because                                                  //  opening  a  whole  subtree  could  be  really  slow  (consider                                                  //  a  `node_modules`  directory,  for  example).                                                  this.props.actions.toggleSubdirectories(this.myPath(),  setOpen);                                                  this.props.actions.setDirectoryOpen(this.myPath(),  setOpen);                                          }  else  {                                                  //  When  closing,  we  recursively  close  the  whole  subtree.                                                  this.props.actions.closeSubtree(this.myPath());                                          }                                  }  else  {

Page 30: Simplifying JavaScript Projects with ReactJS

       ActionCreator.prototype.toggleSubdirectories  =  function  (path,  openOrClose)  {                  this.model.toggleSubdirectories(path,  openOrClose).then(_saveTreeState);          };

Page 31: Simplifying JavaScript Projects with ReactJS

       ProjectModel.prototype.toggleSubdirectories  =  function  (path,  openOrClose)  {                  var  self  =  this,                          d  =  new  $.Deferred();                    this.setDirectoryOpen(path,  true).then(function  ()  {                          var  projectRelativePath  =  self.makeProjectRelativeIfPossible(path),                                  childNodes  =  self._viewModel.getChildDirectories(projectRelativePath);                                                    Async.doInParallel(childNodes,  function  (node)  {                                  return  self.setDirectoryOpen(path  +  node,  openOrClose);                          },  true).then(function  ()  {                                  d.resolve();                          },  function  (err)  {                                  d.reject(err);                          });                  });                                    return  d.promise();          };

Page 32: Simplifying JavaScript Projects with ReactJS

       ProjectModel.prototype.setDirectoryOpen  =  function  (path,  open)  {                  var  projectRelative  =  this.makeProjectRelativeIfPossible(path),                          needsLoading        =  !this._viewModel.isPathLoaded(projectRelative),                          d                              =  new  $.Deferred(),                          self                        =  this;                  if  (open  &&  needsLoading)  {                          var  parentDirectory  =  FileUtils.getDirectoryPath(FileUtils.stripTrailingSlash(path));                          this.setDirectoryOpen(parentDirectory,  true).then(function  ()  {                                  self._getDirectoryContents(path).then(onSuccess).fail(function  (err)  {                                          d.reject(err);                                  });                          },  function  (err)  {                                  d.reject(err);                          });                  }  else  {                          onSuccess();                  }

Page 33: Simplifying JavaScript Projects with ReactJS

               function  onSuccess(contents)  {                          //  Update  the  view  model                          if  (contents)  {                                  self._viewModel.setDirectoryContents(projectRelative,  contents);                          }                            if  (open)  {                                  self._viewModel.openPath(projectRelative);                                  if  (self._focused)  {                                          var  currentPathInProject  =  self.makeProjectRelativeIfPossible(self._currentPath);                                          if  (self._viewModel.isFilePathVisible(currentPathInProject))  {                                                  self.setSelected(self._currentPath,  true);                                          }  else  {                                                  self.setSelected(null);                                          }                                  }                          }  else  {

branches to test

Page 34: Simplifying JavaScript Projects with ReactJS

       FileTreeViewModel.prototype.openPath  =  function  (path)  {                  this._commit(_openPath(this._treeData,  path));          };

Page 35: Simplifying JavaScript Projects with ReactJS

{          "subdir":  {                  open:  true,                  children:  {                          "afile.js":  {},                          "subsubdir":  {                                  children:  {                                          "thirdsub":  {                                                  children:  {}                                          }                                  }                          }                  }          }  }

subdir/subsubdir/thirdsub/

Page 36: Simplifying JavaScript Projects with ReactJS

{          "subdir":  {                  open:  true,                  children:  {                          "afile.js":  {},                          "subsubdir":  {                                  children:  {                                          "thirdsub":  {                                                  children:  {}                                          }                                  }                          }                  }          }  }

subdir/subsubdir/thirdsub/

treeData.subdir.subsubdir.open  =  true;  treeData.subdir.subsubdir.thirdsub.open  =  true;

Page 37: Simplifying JavaScript Projects with ReactJS

thirdsub  =  treeData.subdir.subsubdir.thirdsub;  newThirdSub  =  thirdsub.set("open",  true);    thirdsub  !==  newThirdSub;  //  true  treeData.subdir.subsubdir.thirdsub  !==  newThirdSub  //  true  treeData.subdir.subsubdir.thirdsub.open  ===  undefined;  //  true

Page 38: Simplifying JavaScript Projects with ReactJS

Attack of the clones

https://www.flickr.com/photos/hjmediastudios/7910348016/

Page 39: Simplifying JavaScript Projects with ReactJS

thirdsub  =  treeData.subdir.subsubdir.thirdsub;  newThirdSub  =  thirdsub.set("open",  true);    thirdsub  !==  newThirdSub;  //  true  treeData.subdir.subsubdir.thirdsub  !==  newThirdSub  //  true  treeData.subdir.subsubdir.thirdsub.open  ===  undefined;  //  true

Page 40: Simplifying JavaScript Projects with ReactJS

thirdsub  =  treeData.subdir.subsubdir.thirdsub;  newThirdSub  =  thirdsub.set("open",  true);  newSubSubDir  =  subsubdir.set("thirdsub",  newThirdSub);  newSubDir  =  subdir.set("subsubdir",  newSubSubDir);  treeData  =  treeData.set("subdir",  newSubDir);

Page 41: Simplifying JavaScript Projects with ReactJS

       function  _openPath(treeData,  path)  {                  var  objectPath  =  _filePathToObjectPath(treeData,  path);                  function  setOpen(node)  {                          return  node.set("open",  true);                  }                  while  (objectPath  &&  objectPath.length)  {                          var  node  =  treeData.getIn(objectPath);                          if  (isFile(node))  {                                  objectPath.pop();                          }  else  {                                  if  (!node.get("open"))  {                                          treeData  =  treeData.updateIn(objectPath,  setOpen);                                  }                                  objectPath.pop();                                  if  (objectPath.length)  {                                          objectPath.pop();                                  }                          }                  }                  return  treeData;          }

mutable?

Page 42: Simplifying JavaScript Projects with ReactJS

       FileTreeViewModel.prototype.openPath  =  function  (path)  {                  this._commit(_openPath(this._treeData,  path));          };

Page 43: Simplifying JavaScript Projects with ReactJS

       FileTreeViewModel.prototype._commit  =  function  (treeData,  selectionViewInfo)  {                  var  changed  =  false;                  if  (treeData  &&  treeData  !==  this._treeData)  {                          this._treeData  =  treeData;                          changed  =  true;                  }                                    if  (selectionViewInfo  &&  selectionViewInfo  !==  this._selectionViewInfo)  {                          this._selectionViewInfo  =  selectionViewInfo;                          changed  =  true;                  }                  if  (changed)  {                          $(this).trigger(EVENT_CHANGE);                  }          };

Page 44: Simplifying JavaScript Projects with ReactJS

–C.A.R. Hoare

“There are two ways of constructing a software design: One way is to make it so simple that there are obviously no

deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far

more difficult.”

Page 45: Simplifying JavaScript Projects with ReactJS

Simple?

Page 46: Simplifying JavaScript Projects with ReactJS

simple!

composed of one thing, not combined

Page 47: Simplifying JavaScript Projects with ReactJS

http://www.shaffner.us/cs/papers/tarpit.pdf

Page 48: Simplifying JavaScript Projects with ReactJS

Simple Architecture

• Each part does one thing with side effects in few, known places

• React lets you generate a whole UI functionally

• Immutable-JS allows you to control when data updates occur

• Every part of your application can have a consistent state

• Object identity tells you when something has changed

Page 49: Simplifying JavaScript Projects with ReactJS

A modern, open source text editor that understands web design.

http://brackets.io