Skip to content

Instantly share code, notes, and snippets.

@zshannon
Last active August 29, 2015 14:06

Revisions

  1. Zane Shannon revised this gist Sep 12, 2014. 6 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
    File renamed without changes.
    File renamed without changes.
    File renamed without changes.
    File renamed without changes.
  2. Zane Shannon revised this gist Sep 12, 2014. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  3. Zane Shannon renamed this gist Sep 12, 2014. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. Zane Shannon revised this gist Sep 12, 2014. 2 changed files with 7 additions and 3 deletions.
    7 changes: 7 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    Couple abstract ideas:

    - Angular apps are little separate kingdoms.
    - You can't get into them from outside, and it's really frowned upon to use jQuery within them to access the DOM.
    - Angular Controllers extend this concept. They can only control the DOM nodes at and below the node with `ng-controller` designated.

    The loading order is somewhat important. I set it up like this: [jQuery, jQueryUI, Bootstrap, Underscore, Angular Library, *any Angular Plugins*, Angular Application init file, *rest of angular files*].
    3 changes: 0 additions & 3 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +0,0 @@
    Angular apps are little separate kingdoms. You can't get into them from outside, and it's really frowned upon to use jQuery within them to access the DOM.

    The loading order is somewhat important. I set it up like this: [jQuery, jQueryUI, Bootstrap, Underscore, Angular Library, *any Angular Plugins*, Angular Application init file, *rest of angular files*].
  5. Zane Shannon revised this gist Sep 12, 2014. 1 changed file with 35 additions and 0 deletions.
    35 changes: 35 additions & 0 deletions team_form_modal.html.erb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    <!-- angular will connect this up to TeamForm -->
    <div ng-controller="TeamForm">
    <!--
    here's some more indirect DOM manipulation magic: `ng-class`. each key is a class that's added
    to this element if the JS evals true
    -->
    <div class="md-modal colored-header custom-width md-effect-9" id="team-form-modal" ng-class="{'md-show': modalShowing }">
    <div class="md-content">
    <div class="modal-header">
    <!-- call $scope.title() -->
    <h3>{{title()}}</h3>
    <button type="button" class="close md-close" ng-click="close()" aria-hidden="true">&times;</button>
    </div>
    <!-- catch the submit event here with `ng-submit` and call $scope.save() -->
    <form name="teamForm" ng-submit="save()">
    <div class="modal-body form">
    <div class="form-group" ng-class="{ 'has-error' : teamForm.name.$invalid && !teamForm.name.$pristine }">
    <label>Name</label>
    <input type="name" class="form-control" placeholder="Equipment Repair 1" required ng-model="team.name" ng-minlength="3">
    <p ng-show="teamForm.name.$invalid" class="help-block ng-hide">Team name required.</p>
    </div>
    <div class="form-group">
    <label>Description</label>
    <textarea type="description" class="form-control" placeholder="Optional description of what this team does..." ng-model="team.description"></textarea>
    </div>
    </div>
    <div class="modal-footer">
    <button type="button" class="btn btn-default btn-flat md-close" ng-click="close()">Cancel</button>
    <button type="submit" class="btn btn-primary btn-flat md-close">Save</button>
    </div>
    </form>
    </div>
    </div>
    <div class="md-overlay" ng-click="close()"></div>
    </div>
  6. Zane Shannon revised this gist Sep 12, 2014. 2 changed files with 46 additions and 4 deletions.
    45 changes: 45 additions & 0 deletions team_form.coffee
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,45 @@
    # Here's the other controller from this module
    @dispatched.controller 'TeamForm', ['Team', '$http', '$scope', '$rootScope', (Team, $http, $scope, $rootScope) ->
    $scope.team = {}
    # this is another example of controlling the view without manipulating the DOM directly
    $scope.modalShowing = false

    $scope.init = () ->
    # create the observer
    $rootScope.$on 'Teams.presentForm', (event, team) ->
    $scope.team = team
    # create a snopshot in case we don't save our changes
    $scope.team.snapshot()
    # show ourselves
    $scope.modalShowing = true

    $scope.title = () ->
    # are we editing a specific Team?
    if $scope.team.id?
    "Edit Team"
    else
    "New Team"

    $scope.close = () ->
    # "undo" our changes
    $scope.team.rollback()
    # hide ourselves
    $scope.modalShowing = false
    true

    $scope.save = () ->
    return false unless $scope.teamForm.$valid
    willAdd = $scope.team.isNew()
    NProgress.start()
    $scope.team.save().then (team) ->
    if willAdd
    # we only want to do this if we want a new team added to the view
    # otherwise changes will re-render automatically
    $rootScope.$emit 'Teams.addedTeam', $scope.team
    $scope.close()
    NProgress.done()
    true
    # we don't call `ng-init` from this view, so we call it here
    $scope.init()

    ]
    5 changes: 1 addition & 4 deletions teams.coffee
    Original file line number Diff line number Diff line change
    @@ -37,6 +37,7 @@
    # There's another controller that presents a form to edit Teams that uses these
    $rootScope.$on 'Teams.addedTeam', (event, team) ->
    $scope.teams.push team
    # these two are used by another controller not in this gist
    $rootScope.$on 'Teams.addedMember', (event, team, member) ->
    console.log 'addedMember', team, member
    team.members.push member
    @@ -63,8 +64,4 @@
    NProgress.done()
    true

    $scope.editMembers = (team) ->
    $rootScope.$emit 'Teams.presentMemberForm', team
    true

    ])
  7. Zane Shannon revised this gist Sep 12, 2014. 1 changed file with 10 additions and 1 deletion.
    11 changes: 10 additions & 1 deletion teams.html.erb
    Original file line number Diff line number Diff line change
    @@ -28,17 +28,26 @@
    <th class="text-right"><button class="btn btn-primary btn-sm btn-flat" ng-click="newTeam()">+Team</button></th>
    </tr>
    </thead>
    <!--
    the `infinite-scroll` attrs are used by an external angular library
    -->
    <tbody id="teams" class="no-border-y" infinite-scroll="loadMore()" infinite-scroll-distance="3">
    <!--
    here's the main angular magic show. `ng-repeat` will re-render that node and its children
    for each element in $scope.teams
    -->
    <tr ng-repeat="team in teams">
    <td>{{ team.id }}</td>
    <td>{{ team.id }}</td> <!-- pretty standard JS templating style here. -->
    <td>{{ team.name }}</td>
    <td>
    <span class="label label-primary" ng-repeat="member in team.members" style="margin-right:3px;" ng-click="editMembers(team)">{{ member.name }} &lt;{{member.email}}&gt;</span>
    <!-- `ng-show` is an example of how we update the view without directly manipulating the DOM. plain JS in there -->
    <span class="label label-default" ng-show="! team.members.length">No members yet.</span>
    <a class="label label-primary" ng-click="editMembers(team)"><i class="fa fa-plus"></i></a>
    </td>
    <td class="text-right">
    <a class="label label-default" ng-click="editTeam(team)"><i class="fa fa-pencil"></i></a>
    <!-- $index is a special variable that will be rendered as the index of this `ng-repeat` -->
    <a class="label label-danger" ng-click="deleteTeam(team, $index)"><i class="fa fa-times"></i></a>
    </td>
    </tr>
  8. Zane Shannon revised this gist Sep 12, 2014. 1 changed file with 53 additions and 0 deletions.
    53 changes: 53 additions & 0 deletions teams.html.erb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    <div class="page-head">
    <h2>Manage Teams</h2>
    <ol class="breadcrumb">
    <li class="active"><%= @Company.name %></li>
    <li class="active">Teams</li>
    </ol>
    </div>
    <div class="row-fluid">
    <div class="col-sm-12">
    <!--
    This is the $$ part of angular. You can use the Angular router if you want to be sad, or just use
    whatever routing mechanism's already in your app and sprinkle `ng-controller="XyzController"`
    throughout your app views. Angular will automatically link this view to the Controller!
    `ng-init` gets called automatically when the view renders. Notice inside it we specify `init`
    again because we can call whatever $scope function we want, the name's incidental.
    -->
    <div class="content" ng-controller="Teams" ng-init="init(<%= @Teams.to_json %>)">
    <div class="table-responsive">
    <table class="table no-border hover">
    <thead class="no-border">
    <tr>
    <th><strong>ID</strong></th>
    <th><strong>Name</strong></th>
    <th><strong>Members</strong></th>
    <!--
    Notice `ng-click` here will call `$scope.newTeam()`
    -->
    <th class="text-right"><button class="btn btn-primary btn-sm btn-flat" ng-click="newTeam()">+Team</button></th>
    </tr>
    </thead>
    <tbody id="teams" class="no-border-y" infinite-scroll="loadMore()" infinite-scroll-distance="3">
    <tr ng-repeat="team in teams">
    <td>{{ team.id }}</td>
    <td>{{ team.name }}</td>
    <td>
    <span class="label label-primary" ng-repeat="member in team.members" style="margin-right:3px;" ng-click="editMembers(team)">{{ member.name }} &lt;{{member.email}}&gt;</span>
    <span class="label label-default" ng-show="! team.members.length">No members yet.</span>
    <a class="label label-primary" ng-click="editMembers(team)"><i class="fa fa-plus"></i></a>
    </td>
    <td class="text-right">
    <a class="label label-default" ng-click="editTeam(team)"><i class="fa fa-pencil"></i></a>
    <a class="label label-danger" ng-click="deleteTeam(team, $index)"><i class="fa fa-times"></i></a>
    </td>
    </tr>
    <tr ng-show="! teams.length">
    <td colspan="4"><p class="lead">No Teams yet. Add one with the blue button above.</p></td>
    </tr>
    </tbody>
    </table>
    </div>
    </div>
    </div>
    </div>
  9. Zane Shannon revised this gist Sep 12, 2014. 1 changed file with 15 additions and 2 deletions.
    17 changes: 15 additions & 2 deletions teams.coffee
    Original file line number Diff line number Diff line change
    @@ -2,28 +2,39 @@
    # Every property of $scope is available as a variable in the View, including the functions
    # The init syntax is similar to factory, except here we're depending on the Team model from team.coffee
    @dispatched.controller('Teams', ['Team', '$scope', '$rootScope', (Team, $scope, $rootScope) ->
    # initialize the scope
    $scope.page = 1
    $scope.teams = []
    $scope.hasMore = true
    $scope.loadingMore = false

    # this gets called by our inifinite scrolling library
    $scope.loadMore = ->
    return true if ! $scope.hasMore || $scope.loadingMore
    $scope.loadingMore = true
    # NProgress is a library that shows the loading bar thing at the top of the page
    # It's loaded as an external JS thing available globally (not related to angular)
    NProgress.start()
    Team.query({page: $scope.page}).then (teams) ->
    # Team is from team.coffee. Query is a native function to RailsResource. Just calls index.
    Team.query({page: $scope.page}).then (teams) -># it returns a promise . teams will be an array from the API
    # I read somewhere this is a faster way to merge arrays in JS. Dunno.
    for team in teams
    $scope.teams.push team
    # normal loading loop stuff
    $scope.page++
    $scope.hasMore = teams.length >= 25
    $scope.loadingMore = false
    NProgress.done()

    # this gets called from the HTML view
    # the HTML renderer already fetches page 1, so we load it here via JSON from the DOM
    $scope.init = (teams) ->
    for team in teams
    $scope.teams.push new Team(team)
    $scope.page++
    $scope.hasMore = teams.length >= 25
    # We use the Observer Pattern here to receive notes from other controllers
    # There's another controller that presents a form to edit Teams that uses these
    $rootScope.$on 'Teams.addedTeam', (event, team) ->
    $scope.teams.push team
    $rootScope.$on 'Teams.addedMember', (event, team, member) ->
    @@ -33,8 +44,10 @@
    console.log 'removedMember', team, member
    team.members.splice _.indexOf(member), 1

    # functions on $scope are available in the View
    $scope.newTeam = () ->
    team = new Team()
    # this is observed by that other controller
    $rootScope.$emit 'Teams.presentForm', team
    true

    @@ -45,7 +58,7 @@
    $scope.deleteTeam = (team, $index) ->
    return false unless team.id?
    NProgress.start()
    team.delete().then ->
    team.delete().then -># wait for railsResource to finish, then update the UI
    $scope.teams.splice $index, 1
    NProgress.done()
    true
  10. Zane Shannon revised this gist Sep 12, 2014. 2 changed files with 57 additions and 0 deletions.
    File renamed without changes.
    57 changes: 57 additions & 0 deletions teams.coffee
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    # Here's an example Controller. In Angular, Controllers and Controller/View hybrids in MVC-speak
    # Every property of $scope is available as a variable in the View, including the functions
    # The init syntax is similar to factory, except here we're depending on the Team model from team.coffee
    @dispatched.controller('Teams', ['Team', '$scope', '$rootScope', (Team, $scope, $rootScope) ->
    $scope.page = 1
    $scope.teams = []
    $scope.hasMore = true
    $scope.loadingMore = false

    $scope.loadMore = ->
    return true if ! $scope.hasMore || $scope.loadingMore
    $scope.loadingMore = true
    NProgress.start()
    Team.query({page: $scope.page}).then (teams) ->
    for team in teams
    $scope.teams.push team
    $scope.page++
    $scope.hasMore = teams.length >= 25
    $scope.loadingMore = false
    NProgress.done()

    $scope.init = (teams) ->
    for team in teams
    $scope.teams.push new Team(team)
    $scope.page++
    $scope.hasMore = teams.length >= 25
    $rootScope.$on 'Teams.addedTeam', (event, team) ->
    $scope.teams.push team
    $rootScope.$on 'Teams.addedMember', (event, team, member) ->
    console.log 'addedMember', team, member
    team.members.push member
    $rootScope.$on 'Teams.removedMember', (event, team, member) ->
    console.log 'removedMember', team, member
    team.members.splice _.indexOf(member), 1

    $scope.newTeam = () ->
    team = new Team()
    $rootScope.$emit 'Teams.presentForm', team
    true

    $scope.editTeam = (team) ->
    $rootScope.$emit 'Teams.presentForm', team
    true

    $scope.deleteTeam = (team, $index) ->
    return false unless team.id?
    NProgress.start()
    team.delete().then ->
    $scope.teams.splice $index, 1
    NProgress.done()
    true

    $scope.editMembers = (team) ->
    $rootScope.$emit 'Teams.presentMemberForm', team
    true

    ])
  11. Zane Shannon revised this gist Sep 12, 2014. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions service.coffee
    Original file line number Diff line number Diff line change
    @@ -17,7 +17,10 @@
    name: 'team'
    extensions: ['snapshots'] # snapshots let us do undo



    # I can add functions to the model here, too
    # for example:
    Team.prototype.helloName = (name) ->
    "Hello, #{name}!"

    Team
    ]
  12. Zane Shannon revised this gist Sep 12, 2014. 1 changed file with 23 additions and 0 deletions.
    23 changes: 23 additions & 0 deletions service.coffee
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    # Factory is angular for Model in MVC-speak
    # @dispatched gets the application we setup in _init_app.coffee; 'Team' is the name of this model;
    # the array param has the first n elements as the dependencies that become params for the last element,
    # which defines and returns the model
    @dispatched.factory 'Team', ['railsResourceFactory', 'railsSerializer', (railsResourceFactory, railsSerializer) ->
    # initialize the object
    Team = railsResourceFactory # using RailsResource for now, but plan to switch to [Angular-Data](http://angular-data.pseudobry.com/)
    url: (context) -> # this gets called to generate every request URL
    # if we have an instance, use its ID for Show/Update/Delete/etc.
    if context[@idAttribute]?
    "/teams/#{context[@idAttribute]}.json"
    # otherwise we're working on the collection for Index/Create/Search/etc.
    else
    "/teams.json#{window.location.search}" # window.location.search is the query params like `?page=3&filter=pasta`
    # team is the root node for JSON serialization from the API
    # like: {'team':{name:'new orleans saints'}}
    name: 'team'
    extensions: ['snapshots'] # snapshots let us do undo



    Team
    ]
  13. Zane Shannon created this gist Sep 12, 2014.
    3 changes: 3 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    Angular apps are little separate kingdoms. You can't get into them from outside, and it's really frowned upon to use jQuery within them to access the DOM.

    The loading order is somewhat important. I set it up like this: [jQuery, jQueryUI, Bootstrap, Underscore, Angular Library, *any Angular Plugins*, Angular Application init file, *rest of angular files*].