Simplifying JavaScript Projects with ReactJS

Post on 13-Jul-2015

916 views 2 download

Transcript of Simplifying JavaScript Projects with ReactJS

Simplifying JavaScript Projects with ReactJS and Friends

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

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

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

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

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)  {    

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  {    

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          }  }    

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

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

simple!

composed of one thing, not combined

complect!

interweave

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?

easy!

near at hand

React

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

Functional core, Integration shell

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  {    

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

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

ProjectModel

FileTreeViewModel

FileTreeView ActionCreator

React

       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);          }

       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;                  },

               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                          );                  }          });

       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);

                       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({

       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                                  });                          }

       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);                  }          });

               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  {

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

       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();          };

       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();                  }

               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

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

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

subdir/subsubdir/thirdsub/

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

subdir/subsubdir/thirdsub/

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

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

Attack of the clones

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

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

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);

       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?

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

       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);                  }          };

–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.”

Simple?

simple!

composed of one thing, not combined

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

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

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

http://brackets.io