/beetsplug/static/js/edna2.js
JavaScript | 973 lines | 691 code | 189 blank | 93 comment | 72 complexity | f866f396a791ae8c5d9421b3df84ccce MD5 | raw file
- //override the backbone sync method
- Backbone.sync = function(method, model) {
- //We don't have local storage enabled, forget it
- if (!localStorage)
- return;
- //console.log(method + ": " + JSON.stringify(model));
- //First check if we have the edna2-playlist bucket
- if (!localStorage.getItem(edna2.LOCAL_STORAGE_BUCKET)) {
- data = edna2.LOCAL_STORAGE_INITIAL
- localStorage.setItem(edna2.LOCAL_STORAGE_BUCKET, JSON.stringify(data));
- }
- //Ok, so now we know the bucket is there, the next part depends
- //on what type of request it is
- if (method === "read") {
- data = localStorage.getItem(edna2.LOCAL_STORAGE_BUCKET);
- return JSON.parse(data);
- }
- else if (method === "create") {
- //we will be accepting a collection model and replacing the current
- //data in playlist with that
- data = edna2.LOCAL_STORAGE_INITIAL;
- data.playlist = model;
- localStorage.setItem(edna2.LOCAL_STORAGE_BUCKET, JSON.stringify(data));
- }
- else {
- //Has to be a save for this application
- data = JSON.parse(localStorage.getItem(edna2.LOCAL_STORAGE_BUCKET));
- found = false;
- //Check to see if this is already in the array
- for (var i = data["playlist"].length - 1; i >= 0; i--) {
- if (data["playlist"][i].id == model.get("id")) {
- found = true;
- break;
- }
- }
- if (!found)
- data["playlist"].push(model);
- localStorage.setItem(edna2.LOCAL_STORAGE_BUCKET, JSON.stringify(data));
- }
- };
- var edna2 = {
- initialize: function() {
- //Playlist Id for keeping track of stuff on playlists
- playlistId = 0;
- //Bucket to use for localstorage
- edna2.LOCAL_STORAGE_BUCKET = "edna2-playlist";
- edna2.LOCAL_STORAGE_INITIAL = {"playlist": []};
- edna2.trackCollection = new EntryCollection();
- edna2.albumCollection = new AlbumCollection();
- edna2.artistCollection = new ArtistCollection();
- edna2.views = {
- loadingView: new LoadingView(),
- contentView: new ContentView()
- }
- edna2.playlistCollection = new PlaylistCollection();
- localPlaylist = edna2.playlistCollection.fetch();
- edna2.playlistCollection.reset(localPlaylist.playlist);
- edna2.playlistView = new PlaylistCollectionView({collection: edna2.playlistCollection});
- //The playlist will be empty unless we have loaded some stuff from local storage so we need
- //to re-render it here to make sure that we have all the lastest stuff in the playlist
- edna2.playlistView.render();
- //Initialize playlist drag and drop sorting
- $("#playlist").sortable({
- update: function(event, ui) {
- models = []
- $("#playlist li").each(function(index) {
- id = $(this).attr("data-id");
- if (typeof id !== "undefined" && id !== false) {
- //Get the associated model
- models.push(edna2.trackCollection.get(id).toJSON());
- }
- });
- //This is kind of a pain because it will cause a re-render
- //but it makes sure everything is in sync, so it works out
- edna2.playlistCollection.reset(models);
- edna2.playlistCollection.sync("create", edna2.playlistCollection);
- }
- });
- //Initialize the size of #playlist (the playlist view)
- //and .searchable (the columns)
- edna2.resizePlaylistAndSearchable();
- $(window).resize(function() {
- edna2.resizePlaylistAndSearchable();
- });
- //Initialize mousetrap keyboard shortcuts
- Mousetrap.bind("space", function(e) {
- if (e.preventDefault)
- e.preventDefault();
- else
- e.returnValue = false;
- edna2.PlayerView.toggle();
- });
- Mousetrap.bind("/", function(e) {
- if (e.preventDefault)
- e.preventDefault();
- else
- e.returnValue = false;
- $("#query").focus();
- });
- Mousetrap.bind("ctrl+right", function(e) {
- if (e.preventDefault)
- e.preventDefault();
- else
- e.returnValue = false;
- edna2.PlayerView.playNext();
- });
- Mousetrap.bind("ctrl+left", function(e) {
- if (e.preventDefault)
- e.preventDefault();
- else
- e.returnValue = false;
- edna2.PlayerView.playPrevious();
- });
- Mousetrap.bind("ctrl+up", function(e) {
- if (e.preventDefault)
- e.preventDefault();
- else
- e.returnValue = false;
-
- edna2.PlayerView.modifyVolume(10);
- });
- Mousetrap.bind("ctrl+down", function(e) {
- if (e.preventDefault)
- e.preventDefault();
- else
- e.returnValue = false;
-
- edna2.PlayerView.modifyVolume(-10);
- });
- Mousetrap.bind("ctrl+shift+l", function(e) {
- edna2.playlistControlView.toggleRender();
- state = edna2.playlistControlView.autoloopOn ? "on" : "off"
- edna2.notify("Looping is " + state);
- });
- Mousetrap.bind("ctrl+shift+z", function(e) {
- edna2.playlistControlView.shufflePlaylist();
- edna2.notify("Shuffling Playlist", "info");
- });
- Mousetrap.bind("ctrl+shift+x", function(e) {
- edna2.playlistControlView.clearPlaylist();
- //This may not have visible results, so
- //we want to notify the user
- edna2.notify("Clearing playlist", "danger");
- });
- Mousetrap.bind("ctrl+shift+h", function(e) {
- if (e.preventDefault())
- e.preventDefault();
- else
- e.returnValue = false;
- if ($(".bootbox").length == 0) {
- message = $("#help-text").html();
- bootbox.alert(message);
- }
- });
- //Set the focus on the text input
- $("#query").focus();
- },
- resizePlaylistAndSearchable: function() {
- //This is some dynamic styling, it seems to work well
- //with these numbers, but it might need to be adjusted in
- //the future if other views change
- h = $(window).height() - 200;
- $(edna2.playlistView.el).height(h + "px");
- $(".searchable").height( (h - 40) + "px");
- },
- getPlaylist: function() {
- return edna2.playlistCollection;
- },
- showView: function(view) {
- if (edna2.views.current != undefined) {
- $(edna2.views.current.el).hide();
- }
- edna2.views.current = view;
- $(edna2.views.current.el).show();
- },
- indexById: function(collection, id) {
- for (var i = 0; i < collection.length; i++) {
- if (id === collection.at(i).get("id"))
- return i;
- }
- return -1;
- },
-
- //Provide an easy way to notify the user of something
- notify: function(msg, type) {
- if (!type)
- type = "success"
- $("#notifications").notify({
- message: {text: msg},
- type: type
- }).show();
- },
- }
- LoadingView = Backbone.View.extend({
- el: "#content-loading",
- initialize: function() {
- $(this.el).hide();
- },
- });
- ContentView = Backbone.View.extend({
- el: "#content",
- initialize: function() {
- this.render();
- this.libraryView = new LibraryView();
- },
- render: function() {
- $(this.el).hide();
- }
- });
- LibraryView = Backbone.View.extend({
- el: "#search_library",
- initialize: function() {
- //Initialize new Track, Album, and Artist Views
- //and render them
- this.render();
- this.tracksView = new EntryCollectionView({el:"#tracks", collection:edna2.trackCollection});
- this.albumsView = new AlbumCollectionView({el: "#albums", collection:edna2.albumCollection});
- this.artistsView = new ArtistCollectionView({el: "#artists", collection:edna2.artistCollection});
- },
- });
- //Renders individual artists
- ArtistCollectionView = Backbone.View.extend({
- initialize: function() {
- this.collection.on("add", this.addAll, this);
- this.collection.on("reset", this.addAll, this);
- },
- addItem: function(item) {
- view = new ArtistView({model: item});
- this.$el.append(view.render().el);
- },
- addAll: function(item) {
- $(this.el).html('');
- if (window.filter !== undefined)
- _.each(this.collection.match(), this.addItem, this);
- else
- this.collection.each(this.addItem, this);
- }
- });
- ArtistView = Backbone.View.extend({
- tagName: "li",
- className: "artist",
- template: _.template($("#artist-template").html()),
- events: {
- 'click .icon-plus': 'addToPlaylist',
- 'click .title': 'addToPlaylistAndPlay'
- },
- render: function() {
- $(this.el).html(this.template(this.model.toJSON()));
- return this;
- },
- addToPlaylist: function(notify) {
- notifyPartialFailureToAdd = false;
- albums = this.model.get("albums");
- for (var i = 0; i < albums.length; i++) {
- tracks = albums[i].get("tracks");
- for (var j = 0; j < tracks.length; j++) {
- if (edna2.playlistCollection.get(tracks[j])) {
- notifyPartialFailureToAdd = true;
- continue;
- }
- edna2.playlistCollection.add(tracks[j]);
- }
- }
-
- if (notifyPartialFailureToAdd)
- edna2.notify("Unable to add some items to the playlist", "danger");
- if (notify && !notifyPartialFailureToAdd)
- edna2.notify("Added " + this.model.get("artist") + " to playlist");
- edna2.playlistView.render();
- },
- addToPlaylistAndPlay: function() {
- //Play the first item in this collection
- firstTrack = this.model.get("albums")[0].get("tracks")[0]
- edna2.PlayerView.play(firstTrack);
- //Add to playlist after for highlighting
- this.addToPlaylist(false);
- edna2.InfoView.render(firstTrack);
- },
- });
- //Renders individual albums
- AlbumCollectionView = Backbone.View.extend({
- initialize: function() {
- this.collection.on("add", this.addAll, this);
- this.collection.on("reset", this.addAll, this);
- },
- addItem: function(item) {
- view = new AlbumView({model: item});
- this.$el.append(view.render().el);
- },
- addAll: function() {
- $(this.el).html('');
- if (window.filter !== undefined)
- _.each(this.collection.match(), this.addItem, this);
- else
- this.collection.each(this.addItem, this);
- }
- });
- AlbumView = Backbone.View.extend({
- tagName: "li",
- className: "album",
- template: _.template($("#album-template").html()),
- events: {
- 'click .icon-plus': 'addToPlaylist',
- 'click .title': 'addToPlaylistAndPlay'
- },
- render: function() {
- $(this.el).html(this.template(this.model.toJSON()));
- return this;
- },
- addToPlaylist: function(notify) {
- //If there were some items in the playlist already, notify the user
- notifyPartialFailureToAdd = false;
- trackModels = this.model.get("tracks")
- for (var i = 0; i < trackModels.length; i++) {
- if (edna2.playlistCollection.get(trackModels[i])) {
- notifyPartialFailureToAdd = true;
- continue;
- }
- edna2.playlistCollection.add(trackModels[i]);
- }
- // Notify the user of any failures (even if the notify flag was not set)
- if (notifyPartialFailureToAdd)
- edna2.notify("Unable to add some items to the playlist", "danger");
- // Otherwise everything went fine and we only want to notify the user if
- // their music isn't already playing
- if (notify && !notifyPartialFailureToAdd)
- edna2.notify("Added " + this.model.get("album") + " to playlist");
- //Take care of highlighting
- edna2.playlistView.render();
- },
- addToPlaylistAndPlay: function() {
- //Play the first one in this collection
- firstTrack = this.model.get("tracks")[0]
- edna2.PlayerView.play(firstTrack);
- //Add to playlist after to make highlighting
- //correct
- this.addToPlaylist(false);
- edna2.InfoView.render(firstTrack);
- },
- });
- //Renders individual tracks
- EntryCollectionView = Backbone.View.extend({
- initialize: function() {
- this.collection.on("add", this.addAll, this);
- this.collection.on("reset", this.addAll, this);
- },
- addItem: function(item) {
- view = new EntryView({model: item});
- this.$el.append(view.render().el);
- },
- addAll: function() {
- $(this.el).html('');
- if (window.filter !== undefined)
- _.each(this.collection.match(), this.addItem, this);
- else
- this.collection.each(this.addItem, this);
- },
- });
- EntryView = Backbone.View.extend({
- tagName: "li",
- className: "entry",
- template: _.template($("#entry-template").html()),
- initialize: function() {
- },
- events: {
- 'click .icon-plus': 'addToPlaylist',
- 'click .title': 'addToPlaylistAndPlay'
- },
- render: function() {
- $(this.el).html(this.template(this.model.toJSON()));
- return this;
- },
- addToPlaylist: function(notify) {
- if (notify) {
- // If it exists, give a warning that it wasn't added
- if (!edna2.playlistCollection.get(this.model))
- edna2.notify("Added " + this.model.get("title") + " to playlist");
- else
- edna2.notify(this.model.get("title") + " is already in the playlist", "danger")
- }
- edna2.playlistCollection.add(this.model);
- edna2.playlistView.render();
- },
- addToPlaylistAndPlay: function() {
- edna2.PlayerView.play(this.model);
- //Have to do this second so that it will
- //trigger highlighing
- this.addToPlaylist(false);
- edna2.InfoView.render(this.model);
- },
- });
- PlaylistCollectionView = Backbone.View.extend({
- el: "#playlist",
- initialize: function() {
- this.collection.on("add", this.addAll, this);
- this.collection.on("reset", this.addAll, this);
- },
- render: function() {
- this.addAll();
- //Count up all the items in the current playlist
- amount = $(this.el).children().length;
- $("#playlist-amount").text(amount);
- },
- addItem: function(item) {
- var view = new PlaylistView({model: item});
- this.$el.append(view.render().el);
- //Compare by id for right now, this will likely change
- //in the future once you can add songs with the same id
- //to the playlist
- if (edna2.PlayerView.nowPlaying !== undefined &&
- item.get("id") === edna2.PlayerView.nowPlaying.get("id")) {
- view.highlight();
- }
- //Save the item
- item.save();
- //Increment the amount of things that are on the view tab
- },
- addAll: function() {
- this.$el.html('');
- this.collection.each(this.addItem, this);
- },
- clear: function() {
- this.$el.html('');
- this.collection.reset();
- edna2.PlayerView.clear();
- //Clear out local storage
- if (localStorage)
- localStorage.setItem(
- edna2.LOCAL_STORAGE_BUCKET, JSON.stringify(
- edna2.LOCAL_STORAGE_INITIAL));
- //Clear out the number on the tab
- $("#playlist-amount").text("0");
- },
- });
- PlaylistView = Backbone.View.extend({
- tagName: "li",
- className: "playlistEntry",
- template: _.template($("#playlist-item-template").html()),
- initialize: function() {
- this.render();
- },
- events: {
- "click .play": "playItem",
- "click .icon-remove": "removeItem"
- },
- render: function() {
- $(this.el).html(this.template(this.model.toJSON()));
- return this;
- },
- highlight: function() {
- $(this.el).addClass("highlight");
- $(this.el).siblings().removeClass("highlight");
- },
- playItem: function() {
- this.highlight();
- edna2.PlayerView.play(this.model);
- edna2.InfoView.render(this.model);
- },
- removeItem: function() {
- //Logic for removing from player
- edna2.PlayerView.remove(this.model);
- //We have to remove from collection second because
- //we still want the item to be in the collection so
- //we know what item to play next
- edna2.playlistCollection.remove(this.model);
- //Sync with local storage
- edna2.playlistCollection.sync("create", edna2.playlistCollection);
- //Update the playlist number
- edna2.playlistView.render();
- $(this.el).html('');
- }
- });
- PlaylistControlView = Backbone.View.extend({
- el: "#playlist-controls",
- templateAutoloopOn: _.template($("#autoloop-on-template").html()),
- templateAutoloopOff: _.template($("#autoloop-off-template").html()),
- initialize: function() {
- this.autoloopOn = false;
- this.renderOn();
- },
- events: {
- 'click #autoloop-status': 'toggleRender',
- 'click #clear-playlist': 'clearPlaylist',
- 'click #shuffle-playlist': 'shufflePlaylist'
- },
- renderOn: function() {
- $("#autoloop-status").html(this.templateAutoloopOn());
- },
- renderOff: function() {
- $("#autoloop-status").html(this.templateAutoloopOff());
- },
- toggleRender: function() {
- this.autoloopOn = !this.autoloopOn;
- //If it's on already, we want the
- //option to turn if off, otherwise we
- //want to turn it off
- if (this.autoloopOn)
- this.renderOff();
- else
- this.renderOn();
- },
- clearPlaylist: function() {
- edna2.playlistView.clear();
- },
- shufflePlaylist: function() {
- edna2.playlistCollection.reset(_.shuffle(edna2.playlistCollection.toJSON()));
- edna2.playlistCollection.sync("create", edna2.playlistCollection);
- }
- });
- edna2.SidebarView = new(Backbone.View.extend({
- el: "#sidebar",
- initialize: function() {
- this.time = 200;
- },
- events: {
- "keyup .input-large": "startTimer",
- "keydown .input-large": "clearTimer",
- "focus .input-large": "focusLibrary"
- },
- startTimer: function(e) {
- window.typingTimer = setTimeout(this.filter, this.time);
- },
- clearTimer: function(e) {
- clearTimeout(window.typingTimer);
- },
- filter: function(e) {
- window.filter = $("#query").val().toLowerCase();
- edna2.trackCollection.trigger('reset');
- edna2.albumCollection.trigger('reset');
- edna2.artistCollection.trigger('reset');
- },
- focusLibrary: function() {
- $("#navigation a:first").tab('show');
- }
- }));
- edna2.InfoView = new(Backbone.View.extend({
- el: "#info",
- template: _.template($("#info-template").html()),
- render: function(model) {
- $(this.el).html(this.template(model.toJSON()));
- },
- clear: function() {
- $(this.el).html("");
- }
- }));
- edna2.PlayerView = new(Backbone.View.extend({
- el: "#player",
- nowPlaying: undefined,
-
- initialize: function() {
- },
- //Plays a model
- play: function(model) {
- if (ednoise.events.trackEnded === undefined) {
- //install the new trackended here
- ednoise.events.trackEnded = this.playNext;
- }
- ednoise.play("/item/" + model.id + "/file");
- this.nowPlaying = model;
- //Change the document title to the title of the song
- document.title = model.get("title") + " by " + model.get("artist") + " on " + model.get("album");
- },
-
- //This is a helper function for the next two
- getCurrentIndex: function() {
- //Had to do this because .indexOf fails
- currentIndex = edna2.indexById(edna2.playlistCollection, edna2.PlayerView.nowPlaying.get("id"))
- if (currentIndex < 0) {
- //This means that the current playing item wasn't in the playlist
- //collection at all. it should NEVER hedna2en.
- console.log("ERROR: Current item NOT IN playlist");
- }
- return currentIndex;
- },
- playPrevious: function() {
- currentIndex = edna2.PlayerView.getCurrentIndex();
- nextIndex = currentIndex - 1;
- if (nextIndex < 0) {
- if (edna2.playlistControlView.autoloopOn) {
- nextIndex = edna2.playlistCollection.length - 1;
- }
- else {
- edna2.PlayerView.pause();
- edna2.InfoView.clear();
- return;
- }
- }
-
- nextTrack = edna2.playlistCollection.at(nextIndex);
- edna2.PlayerView.play(nextTrack);
- edna2.InfoView.render(nextTrack);
- edna2.playlistView.render();
- },
- playNext: function() {
- currentIndex = edna2.PlayerView.getCurrentIndex();
- nextIndex = currentIndex + 1
- if (nextIndex >= edna2.playlistCollection.length) {
- if (edna2.playlistControlView.autoloopOn) {
- nextIndex = 0;
- }
- else {
- //If we have nothing left to do, pause it
- edna2.PlayerView.pause();
- edna2.InfoView.clear();
- return;
- }
- }
-
- nextTrack = edna2.playlistCollection.at(nextIndex);
- //console.log("Next playing is: ");
- //console.log(nextTrack.toJSON());
- //Play and render
- edna2.PlayerView.play(nextTrack);
- edna2.InfoView.render(nextTrack);
- //Advance highlighting
- edna2.playlistView.render();
- },
- //We have to have this function because if we
- //want to remove something and it is currently
- //playing we want to go to the next item
- remove: function(model) {
- if (this.nowPlaying != null && this.nowPlaying.id === model.id)
- edna2.PlayerView.playNext();
- },
- pause: function() {
- ednoise.pause();
- },
- toggle: function() {
- ednoise.playPause();
- },
- setVolume: function(amount) {
- if (amount <= 0)
- amount = 0;
- else if (amount >= 100)
- amount = 100;
- ednoise.setVolume(amount);
- },
- getVolume: function() {
- return ednoise.getVolume();
- },
- modifyVolume: function(amount) {
- v = this.getVolume() + amount;
- this.setVolume(v);
- },
- clear: function() {
- ednoise.reset();
- }
- }));
- EntryModel = Backbone.Model.extend({
- defaults: {
- title: "Track Title",
- album: "Album Title",
- artist: "Artist Name",
- album_id: 45,
- length: 0,
- track: 0,
- tracktotal: 0,
- mb_trackid: 0,
- mb_albumid: 0,
- mb_artistid: 0
- }
- });
- EntryCollection = Backbone.Collection.extend({
- model: EntryModel,
- match: function() {
- if (window.filter != undefined) {
- return this.filter(function(item) {
- //return item.get("title").toLowerCase().indexOf(window.filter) > -1;
- title = item.get("title").toLowerCase();
- album = item.get("album").toLowerCase();
- artist = item.get("artist").toLowerCase();
- return title.indexOf(window.filter) > -1 ||
- album.indexOf(window.filter) > -1 ||
- artist.indexOf(window.filter) > -1;
- });
- }
- }
- });
- AlbumModel = Backbone.Model.extend({
- defaults: {
- album: "Album Title",
- tracktotal: 0,
- tracks: []
- }
- });
- AlbumCollection = Backbone.Collection.extend({
- model: AlbumModel,
- match: function() {
- if (window.filter != undefined) {
- return this.filter(function(item) {
- //return item.get("album").toLowerCase().indexOf(window.filter) > -1;
- album = item.get("album").toLowerCase();
- artist = item.get("albumartist").toLowerCase();
- return album.indexOf(window.filter) > -1 || artist.indexOf(window.filter) > -1;
- });
- }
- }
- });
- ArtistModel = Backbone.Model.extend({
- defaults: {
- artist: "Artist Title",
- albums: []
- }
- });
- ArtistCollection = Backbone.Collection.extend({
- model: ArtistModel,
- match: function() {
- if (window.filter != undefined) {
- return this.filter(function(item) {
- console.log(item.toJSON());
- return item.get("artist").toLowerCase().indexOf(window.filter) > -1;
- });
- }
- }
- });
- PlaylistCollection = Backbone.Collection.extend({
- model: EntryModel,
- });
- edna2.initialize()
- edna2.showView(edna2.views.loadingView);
- //Initialize Playlist Controls
- edna2.playlistControlView = new PlaylistControlView();
- //Global objects to be used in the countdown latch
- //callback and construction of objects
- window.tracks = undefined;
- window.albums = undefined;
- window.artists = undefined;
- //Provide a countdown latch for syncronizing the
- //callbacks so we don't have to do that in the getJSON
- //callbacks
- CountdownLatch = function() {
- this.initialize = function(n, callback) {
- this.n = n;
- this.callback = callback;
- }
- this.countDown = function() {
- this.n--;
- if (this.n === 0)
- this.callback();
- }
- }
- edna2.loadLatch = new CountdownLatch()
- edna2.loadLatch.initialize(4, function() {
- //Go through each album in the data and
- //construct the object from item models
- for (var i = 0; i < window.albums.length; i++) {
- // Store the tracks temporarily
- temp = albums[i].tracks.slice();
- window.albums[i].tracks = []
- for (var j = 0; j < temp.length; j++) {
- window.albums[i].tracks.push(
- edna2.trackCollection.get(temp[j])
- );
- }
- //Sort based on track number
- window.albums[i].tracks = _.sortBy(window.albums[i].tracks, function(m) {
- return m.get("track");
- });
- }
- edna2.albumCollection.reset(window.albums);
-
- //Go through each artist in the data and construct
- //the object from album models
- for (var i = 0; i < window.artists.length; i++) {
- temp = window.artists[i].albums.slice();
- window.artists[i].albums = [];
- for (var j = 0; j < temp.length; j++) {
- window.artists[i].albums.push(
- edna2.albumCollection.get(temp[j])
- );
- }
- }
- edna2.artistCollection.reset(window.artists);
- edna2.showView(edna2.views.contentView);
- ednoise.create("#player", imgs = "static/ednoise/imgs");
- $("#query").focus();
- });
- //Actually do the requests here
- $.getJSON('/items', function(data) {
- edna2.trackCollection.reset(data.items);
- }).complete(function() {
- edna2.loadLatch.countDown();
- });
- $.getJSON('/albums', function(data) {
- window.albums = data.albums;
- }).complete(function() {
- edna2.loadLatch.countDown();
- });
- $.getJSON('/artists', function(data) {
- window.artists = data.artists;
- }).complete(function() {
- edna2.loadLatch.countDown();
- });