diff --git a/htrace-htraced/BUILDING.txt b/htrace-htraced/BUILDING.txt index abc0113..26ea2ca 100644 --- a/htrace-htraced/BUILDING.txt +++ b/htrace-htraced/BUILDING.txt @@ -13,7 +13,7 @@ The htrace go code consists of 4 main parts: This is the equivalent of the Java HTrace client library, but written in Go. You can build all these parts simply by running "gobuild.sh". -The binaries will be created in bin/. +The binaries will be created in target/. Dependencies ============ diff --git a/htrace-htraced/pom.xml b/htrace-htraced/pom.xml index 446bd5a..2840c4d 100644 --- a/htrace-htraced/pom.xml +++ b/htrace-htraced/pom.xml @@ -109,9 +109,9 @@ language governing permissions and limitations under the License. --> run - + failonerror="true"> @@ -174,6 +174,13 @@ language governing permissions and limitations under the License. --> tests test + + org.apache.htrace + htrace-webapp + ${project.version} + war + provided + commons-logging @@ -198,7 +205,7 @@ language governing permissions and limitations under the License. --> This is new-style jetty client, jetty9 and jdk7 required. It can do async but we will use it synchronously at first. Has nice tutorial: http://www.eclipse.org/jetty/documentation/9.1.5.v20140505/http-client-api.html - --> + --> org.eclipse.jetty jetty-client diff --git a/htrace-htraced/src/go/gobuild.sh b/htrace-htraced/src/go/gobuild.sh index 81c9f7d..c91cc00 100755 --- a/htrace-htraced/src/go/gobuild.sh +++ b/htrace-htraced/src/go/gobuild.sh @@ -22,9 +22,9 @@ # # Builds the HTrace server code. # -# ./build.sh Builds the code. -# ./build.sh test Builds and runs all unit tests. -# ./build.sh bench Builds and runs all benchmarks +# ./gobuild.sh Builds the code. +# ./gobuild.sh test Builds and runs all unit tests. +# ./gobuild.sh bench Builds and runs all benchmarks # die() { @@ -42,7 +42,7 @@ RELEASE_VERSION=${RELEASE_VERSION:-unknown} # Set up directories. The build/ directory is where build dependencies and # build binaries should go. SCRIPT_DIR="$(cd "$( dirname $0 )" && pwd)" -export GOBIN="${SCRIPT_DIR}/build" +export GOBIN="${SCRIPT_DIR}/../../target" mkdir -p "${GOBIN}" || die "failed to mkdir -p ${GOBIN}" cd "${GOBIN}" || die "failed to cd to ${SCRIPT_DIR}" export GOPATH="${GOBIN}:${SCRIPT_DIR}" @@ -92,6 +92,8 @@ install) which godep &> /dev/null if [ $? -ne 0 ]; then echo "Installing godep..." + # Need Godeps over in $GOBIN + cp -r "${SCRIPT_DIR}/Godeps" "${GOBIN}" &> /dev/null go get github.com/tools/godep || die "failed to get godep" fi diff --git a/htrace-htraced/src/web/app/about_view.js b/htrace-htraced/src/web/app/about_view.js deleted file mode 100644 index 7dfe868..0000000 --- a/htrace-htraced/src/web/app/about_view.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; -htrace.AboutView = Backbone.View.extend({ - render: function() { - this.$el.html(_.template($("#about-view-template").html()) - ({ model : this.model })); - console.log("AboutView#render"); - return this; - }, - - close: function() { - console.log("AboutView#close") - this.undelegateEvents(); - } -}); diff --git a/htrace-htraced/src/web/app/modal.js b/htrace-htraced/src/web/app/modal.js deleted file mode 100644 index 91d55fe..0000000 --- a/htrace-htraced/src/web/app/modal.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// Show a modal dialog box with a warning message. -htrace.showModalWarning = function(title, body) { - var html = _.template($("#modal-warning-template").html()) - ({ title: title, body: body }); - htrace.showModal(html); -} - -// Show a modal dialog box. -htrace.showModal = function(html) { - var el = $("#modal"); - el.html(html); - el.modal(); -} diff --git a/htrace-htraced/src/web/app/predicate.js b/htrace-htraced/src/web/app/predicate.js deleted file mode 100644 index 87a5602..0000000 --- a/htrace-htraced/src/web/app/predicate.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.checkStringIsPositiveWholeNumber = function(val) { - if (!val.match(/^[0-9]([0-9]*)$/)) { - if (!val.match(/[^\s]/)) { - throw "You entered an empty string into a numeric field."; - } - throw "Non-numeric characters found."; - } -}; - -htrace.checkStringIsNotEmpty = function(val) { - if (!val.match(/[^\s]/)) { - throw "You entered an empty string into a text field."; - } -}; - -// Predicate type -htrace.PType = Backbone.Model.extend({ - initialize: function(options) { - this.name = options.name; - this.field = options.field; - this.op = options.op; - }, - - // Try to normalize a value of this type into something htraced can accept. - // Returns a string containing the normalized value on success. Throws a - // string explaining the parse error otherwise. - // Dates are represented by milliseconds since the epoch; span ids don't start - // with 0x. - normalize: function(val) { - switch (this.field) { - case "begin": - return htrace.parseDate(val).valueOf().toString(); - case "end": - return htrace.parseDate(val).valueOf().toString(); - case "description": - htrace.checkStringIsNotEmpty(val); - return val; - case "duration": - htrace.checkStringIsPositiveWholeNumber(val); - return val; - case "spanid": - return htrace.normalizeSpanId(val); - default: - return "Normalization not implemented for field '" + this.field + "'"; - } - }, - - getDefaultValue: function() { - switch (this.field) { - case "begin": - return htrace.dateToString(moment()); - case "end": - return htrace.dateToString(moment()); - case "description": - return ""; - case "duration": - return "0"; - case "spanid": - return ""; - default: - return "(unknown)"; - } - } -}); - -htrace.parsePType = function(name) { - switch (name) { - case "Began after": - return new htrace.PType({name: name, field:"begin", op:"gt"}); - case "Began at or before": - return new htrace.PType({name: name, field:"begin", op:"le"}); - case "Ended after": - return new htrace.PType({name: name, field:"end", op:"gt"}); - case "Ended at or before": - return new htrace.PType({name: name, field:"end", op:"le"}); - case "Description contains": - return new htrace.PType({name: name, field:"description", op:"cn"}); - case "Description is exactly": - return new htrace.PType({name: name, field:"description", op:"eq"}); - case "Duration is longer than": - return new htrace.PType({name: name, field:"duration", op:"gt"}); - case "Duration is at most": - return new htrace.PType({name: name, field:"duration", op:"le"}); - case "Span ID is": - return new htrace.PType({name: name, field:"spanid", op:"eq"}); - default: - return null - } -}; - -htrace.Predicate = function(options) { - this.op = options.ptype.op; - this.field = options.ptype.field; - this.val = options.val; - return this; -}; diff --git a/htrace-htraced/src/web/app/predicate_view.js b/htrace-htraced/src/web/app/predicate_view.js deleted file mode 100644 index aefe896..0000000 --- a/htrace-htraced/src/web/app/predicate_view.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.PredicateView = Backbone.View.extend({ - initialize: function(options) { - this.el = options.el; - this.index = options.index; - this.ptype = options.ptype; - this.searchView = options.searchView; - }, - - events: { - "click .closeButton": "remove", - }, - - render: function() { - this.$el.html(_.template($("#predicate-template").html()) - ({ desc: this.ptype.name, id: this.index })) - if (this.getText() === "") { - $(this.$el).find(".form-control").val(this.ptype.getDefaultValue()); - } - console.log(this.toString() + "#render"); - return this; - }, - - // Handle the user removing this predicate. - remove: function() { - this.searchView.removePredicateView(this); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - // Get the text which the user has entered in. - getText: function() { - return $(this.$el).find(".form-control").val().trim(); - }, - - // Get the predicate expressed by this view. - // Throw an exception if the predicate can't be parsed. - getPredicate: function() { - return new htrace.Predicate({ - ptype: this.ptype, - val: this.ptype.normalize(this.getText()) - }); - }, - - toString: function() { - return "PredicateView(this.el=" + this.el + ", this.index=" + - this.index + ", this.ptype='" + this.ptype.name + "')"; - } -}); diff --git a/htrace-htraced/src/web/app/query_results.js b/htrace-htraced/src/web/app/query_results.js deleted file mode 100644 index 6fdde9f..0000000 --- a/htrace-htraced/src/web/app/query_results.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.QueryResults = Backbone.Collection.extend({ - // The query results are spans. - model: htrace.Span, - - initialize: function(options) { - this.queryJson = options.queryJson; - }, - - url: function() { - return "query?query=" + this.queryString(); - }, - - parse: function(response, xhr) { - return response; - }, - - prettyQueryString: function() { - return JSON.stringify(this.queryJson, null, 2); - }, - - queryString: function() { - return JSON.stringify(this.queryJson); - } -}); diff --git a/htrace-htraced/src/web/app/router.js b/htrace-htraced/src/web/app/router.js deleted file mode 100644 index 607da44..0000000 --- a/htrace-htraced/src/web/app/router.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.HTraceRouter = Backbone.Router.extend({ - "routes": { - "": "empty", - "about": "about", - "search": "search", - "*unknown": "unknown" - }, - - empty: function() { - console.log("Redirecting to #about."); - Backbone.history.navigate("about", {"trigger": true, "replace": true}); - }, - - about: function() { - console.log("Visiting #about."); - serverInfo = new htrace.ServerInfo(); - var router = this; - serverInfo.fetch({ - "success": function(model, response, options) { - router.switchView(new htrace.AboutView({model: serverInfo, el: "#app"})); - router.activateNavBarEntry("about") - }, - "error": function(model, response, options) { - window.alert("Failed to fetch htraced server info via GET " + - "/server/info: " + JSON.stringify(response)); - } - }); - }, - - search: function() { - console.log("Visiting #search."); - this.switchView(new htrace.SearchView({el : "#app"})); - htrace.router.activateNavBarEntry("search"); - }, - - unknown: function() { - console.log("Unknown route " + Backbone.history.getFragment() + ".") - }, - - "switchView": function(view) { - this.view && this.view.close(); - this.view = view; - this.view.render(); - }, - - "activateNavBarEntry": function(id) { - $(".nav").find(".active").removeClass("active"); - $(".nav").find("#" + id).addClass("active"); - } -}); - -htrace.router = new htrace.HTraceRouter(); -Backbone.history.start(); diff --git a/htrace-htraced/src/web/app/search_results.js b/htrace-htraced/src/web/app/search_results.js deleted file mode 100644 index d214918..0000000 --- a/htrace-htraced/src/web/app/search_results.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.SearchResults = Backbone.Collection.extend({ - // The search results are spans. - model: htrace.Span -}); diff --git a/htrace-htraced/src/web/app/search_results_view.js b/htrace-htraced/src/web/app/search_results_view.js deleted file mode 100644 index b3473c4..0000000 --- a/htrace-htraced/src/web/app/search_results_view.js +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.SearchResultsView = Backbone.View.extend({ - // The minimum time span we will allow between begin and end. - MINIMUM_TIME_SPAN: 100, - - begin: 0, - - end: this.MINIMUM_TIME_SPAN, - - focused: false, - - initialize: function(options) { - this.model = options.searchResults; - this.el = options.el; - this.listenTo(this.model, 'add remove change reset', this.render); - - // Re-render the canvas when the window size changes. - // Add a debouncer delay to avoid spamming render requests. - var view = this; - $(window).on("resize", _.debounce(function() { - view.render(); - }, 250)); - }, - - // Get the canvas X coordinate of a mouse click from the absolute event - // coordinate. - getCanvasX: function(e) { - return e.pageX - $("#resultsCanvas").offset().left; - }, - - // Get the canvas Y coordinate of a mouse click from the absolute event - // coordinate. - getCanvasY: function(e) { - return e.pageY - $("#resultsCanvas").offset().top; - }, - - handleMouseDown: function(e) { - e.preventDefault(); - var x = this.getCanvasX(e); - var y = this.getCanvasY(e); - var focused = this.widgetManager.handleMouseDown(x, y); - if (focused != this.focused) { - this.draw(); - this.focused = focused; - } - }, - - handleMouseUp: function(e) { - e.preventDefault(); - var x = this.getCanvasX(e); - var y = this.getCanvasY(e); - this.widgetManager.handleMouseUp(x, y); - this.focused = false; - this.draw(); - }, - - // When the mouse leaves the canvas, treat it like a mouse up event at -1, -1 - // if something is focused. - handleMouseOut: function(e) { - if (this.focused) { - this.widgetManager.handleMouseUp(-1, -1); - this.focused = false; - this.draw(); - } - }, - - handleMouseMove: function(e) { - e.preventDefault(); - var x = this.getCanvasX(e); - var y = this.getCanvasY(e); - if (this.focused) { - var mustDraw = false; - if (this.widgetManager.handleMouseMove(x, y)) { - mustDraw = true; - } - } - if (this.timeCursor.handleMouseMove(x, y)) { - mustDraw = true; - } - if (mustDraw) { - this.draw(); - } - }, - - render: function() { - console.log("SearchResultsView#render."); - $(this.el).html(_.template($("#search-results-view-template").html())); - $('#selectedTime').attr('readonly', 'readonly'); - this.canvas = $("#resultsCanvas"); - this.ctx = this.canvas.get(0).getContext("2d"); - this.scaleCanvas(); - this.setupCoordinates(); - this.setupTimeCursor(); - this.setupWidgets(); - this.draw(); - this.attachEvents(); - return this; - }, - - /* - * Compute the ratio to use between the size of the canvas (i.e. - * canvas.ctx.width, canvas.ctx.height) and the size in "HTML5 pixels." Note - * that 'HTML5 pixels" don't actually correspond to screen pixels. A line 1 - * "HTML5 pixel" wide actually takes up multiple scren pixels, etc. - * - * TODO: fix this to be sharper - */ - computeScaleFactor: function() { - var backingStoreRatio = this.ctx.backingStorePixelRatio || - this.ctx.mozBackingStorePixelRatio || - this.ctx.msBackingStorePixelRatio || - this.ctx.webkitBackingStorePixelRatio || - this.ctx.oBackingStorePixelRatio || - this.ctx.backingStorePixelRatio || 1; - return (window.devicePixelRatio || 1) / backingStoreRatio; - }, - - // Sets up the canvas size and scaling. - scaleCanvas: function() { - var cssX = this.canvas.parent().innerWidth(); - var cssY = $(window).innerHeight() - $("#header").innerHeight() - 50; - var ratio = this.computeScaleFactor(); - console.log("scaleCanvas: cssX=" + cssX + ", cssY=" + cssY + ", ratio=" + ratio); - this.maxX = cssX; - this.maxY = cssY; - $('#searchView').css('height', cssY + "px"); - $('#results').css('width', cssX + "px"); - $('#results').css('height', cssY + "px"); - $('#resultsView').css('width', cssX + "px"); - $('#resultsView').css('height', cssY + "px"); - $('#resultsDiv').css('width', cssX + "px"); - $('#resultsDiv').css('height', cssY + "px"); - $('#resultsCanvas').css('width', cssX + "px"); - $('#resultsCanvas').css('height', cssY + "px"); - this.ctx.canvas.width = cssX * ratio; - this.ctx.canvas.height = cssY * ratio; - this.ctx.scale(ratio, ratio); - }, - - // - // Set up the screen coordinates. - // - // 0 buttonX descX scrollX maxX - // +--------------+----------+--------------------+-----------+ - // |ProcessId | Buttons | Span Description | Scrollbar | - // +--------------+----------+--------------------+-----------+ - // - setupCoordinates: function() { - this.buttonX = Math.min(300, Math.floor(this.maxX / 5)); - this.descX = this.buttonX + Math.min(75, Math.floor(this.maxX / 20)); - var scrollBarWidth = Math.min(50, Math.floor(this.maxX / 10)); - this.scrollX = this.maxX - scrollBarWidth; - }, - - setupTimeCursor: function() { - var selectedTime; - if (this.timeCursor != null) { - selectedTime = this.timeCursor.selectedTime; - console.log("setupTimeCursor: selectedTime = (prev) " + selectedTime); - } else { - selectedTime = this.begin; - console.log("setupTimeCursor: selectedTime = (begin) " + selectedTime); - } - this.timeCursor = new htrace.TimeCursor({ - ctx: this.ctx, - x0: this.descX, - xF: this.scrollX, - el: "#selectedTime", - y0: 0, - yF: this.maxY, - begin: this.begin, - end: this.end, - selectedTime: selectedTime - }); - }, - - setupWidgets: function() { - var widgets = []; - var spanWidgetHeight = Math.min(25, Math.floor(this.maxY / 32)); - - // Create a SpanWidget for each span we know about - var numSpans = this.model.size(); - for (var i = 0; i < numSpans; i++) { - var spanWidget = new htrace.SpanWidget({ - ctx: this.ctx, - span: this.model.at(i), - x0: 0, - xB: this.buttonX, - xD: this.descX, - xF: this.scrollX, - y0: i * spanWidgetHeight, - yF: (i * spanWidgetHeight) + (spanWidgetHeight - 1), - begin: this.begin, - end: this.end - }); - widgets.push(spanWidget); - } - - // Create a new root-leve WidgetManager - this.widgetManager = new htrace.WidgetManager({ - widgets: widgets - }); - }, - - draw: function() { - if (this.checkCanvasTooSmall()) { - return; - } - - // Set the background to white. - this.ctx.save(); - this.ctx.fillStyle="#ffffff"; - this.ctx.strokeStyle="#000000"; - this.ctx.fillRect(0, 0, this.maxX, this.maxY); - this.ctx.restore(); - - // Draw all the widgets. - this.widgetManager.draw(); - this.timeCursor.draw(); - }, - - checkCanvasTooSmall: function() { - if ((this.maxX < 200) || (this.maxY < 200)) { - this.ctx.fillStyle="#cccccc"; - this.ctx.strokeStyle="#000000"; - this.ctx.fillRect(0, 0, this.maxX, this.maxY); - this.ctx.font = "24px serif"; - this.ctx.fillStyle="#000000"; - this.ctx.fillText("Canvas too small!", 0, 24); - return true; - } - return false; - }, - - attachEvents: function() { - // Use jquery to capture mouse events on the canvas. - // For some reason using backbone doesn't work for getting these events. - var view = this; - $("#resultsCanvas").off("mousedown"); - $("#resultsCanvas").on("mousedown", function(e) { - view.handleMouseDown(e); - }); - $("#resultsCanvas").off("mouseup"); - $("#resultsCanvas").on("mouseup", function(e) { - view.handleMouseUp(e); - }); - $("#resultsCanvas").off("mouseout"); - $("#resultsCanvas").on("mouseout", function(e) { - view.handleMouseOut(e); - }); - $(window).off("mouseup"); - $(window).on("mouseup"), function(e) { - view.handleGlobalMouseUp(e); - } - $("#resultsCanvas").off("mousemove"); - $("#resultsCanvas").on("mousemove", function(e) { - view.handleMouseMove(e); - }); - }, - - remove: function() { - $(window).off("resize"); - $("#resultsCanvas").off("mousedown"); - $("#resultsCanvas").off("mouseup"); - $("#resultsCanvas").off("mousemove"); - Backbone.View.prototype.remove.apply(this, arguments); - }, - - handleBeginOrEndChange: function(e, type) { - e.preventDefault(); - var text = $(e.target).val().trim(); - var d = null; - try { - d = htrace.parseDate(text); - } catch(err) { - $("#begin").val(htrace.dateToString(this.begin)); - $("#end").val(htrace.dateToString(this.end)); - htrace.showModalWarning("Timeline " + type + " Format Error", - "Please enter a valid time in the timeline " + type + " field.

" + - err); - return null; - } - if (type === "begin") { - this.setBegin(d.valueOf()); - } else if (type === "end") { - this.setEnd(d.valueOf()); - } else { - throw "invalid type for handleBeginOrEndChange: expected begin or end."; - } - }, - - setBegin: function(val) { - if (this.end < val + this.MINIMUM_TIME_SPAN) { - this.begin = val; - this.end = val + this.MINIMUM_TIME_SPAN; - console.log("SearchResultsView#setBegin(begin=" + this.begin + - ", end=" + this.end + ")"); - $("#begin").val(htrace.dateToString(this.begin)); - $("#end").val(htrace.dateToString(this.end)); - } else { - this.begin = val; - console.log("SearchResultsView#setBegin(begin=" + this.begin + ")"); - $("#begin").val(htrace.dateToString(this.begin)); - } - this.render(); - }, - - setEnd: function(val) { - if (this.begin + this.MINIMUM_TIME_SPAN > val) { - this.begin = val; - this.end = this.begin + this.MINIMUM_TIME_SPAN; - console.log("SearchResultsView#setEnd(begin=" + this.begin + - ", end=" + this.end + ")"); - $("#begin").val(htrace.dateToString(this.begin)); - $("#end").val(htrace.dateToString(this.end)); - } else { - this.end = val; - console.log("SearchResultsView#setEnd(end=" + this.end + ")"); - $("#end").val(htrace.dateToString(this.end)); - } - this.render(); - }, - - zoomFitAll: function() { - var numSpans = this.model.size(); - if (numSpans == 0) { - this.setBegin(0); - this.setEnd(this.MINIMUM_TIME_SPAN); - return; - } - var minStart = 4503599627370496; - var maxEnd = 0; - for (var i = 0; i < numSpans; i++) { - var span = this.model.at(i); - if (span.get('begin') < minStart) { - minStart = span.get('begin'); - } - if (span.get('end') > maxEnd) { - maxEnd = span.get('end'); - } - } - this.setBegin(minStart); - this.setEnd(maxEnd); - } -}); diff --git a/htrace-htraced/src/web/app/search_view.js b/htrace-htraced/src/web/app/search_view.js deleted file mode 100644 index 52f9101..0000000 --- a/htrace-htraced/src/web/app/search_view.js +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; -htrace.SearchView = Backbone.View.extend({ - initialize : function() { - this.predicateViews = []; - this.highestPredicateIndex = 0; - this.searchInProgress = false; - this.searchResults = new htrace.SearchResults(); - this.resultsView = new htrace.SearchResultsView({ - searchResults: this.searchResults, - el: "#results" - }); - }, - - events: { - "click #searchButton": "searchHandler", - "click #clearButton": "clearHandler", - "click .add-field": "dropdownHandler", - "blur #begin": "blurBeginHandler", - "blur #end": "blurEndHandler", - "click #zoomButton": "zoomFitAllHandler" - }, - - searchHandler: function(e){ - e.preventDefault(); - - // Do a new search. - this.doSearch(e.ctrlKey); - }, - - clearHandler: function(e){ - e.preventDefault(); - - // Clear existing search results. - this.searchResults.reset(); - }, - - doSearch: function(showDebug){ - if (this.searchInProgress) { - console.log("Can't start a new search while another one is in " + - "progress."); - return false; - } - - // Check if there are no search criteria. - if (this.predicateViews.length == 0) { - htrace.showModalWarning("No Search Criteria Specified", - "You have not specified any search criteria. " + - "Use the 'Add Predicate' button to specify what to search for."); - return false; - } - - // Build the predicate array. - predicates = [] - var predicateViewsLen = this.predicateViews.length; - for (var i = 0; i < predicateViewsLen; i++) { - var predicateView = this.predicateViews[i]; - try { - predicates.push(predicateView.getPredicate()); - } catch(err) { - htrace.showModalWarning("Search Field Validation Error", - "Invalid search string for the '" + predicateView.ptype.name + - "' field.

" + err); - return false; - } - } - var queryJson = { - pred: predicates, - lim: 20 - }; - // If there are existing search results, we want results which "come after" - // those. So pass the last span we saw as a continuation token. - if (this.searchResults.size() > 0) { - queryJson.prev = - this.searchResults.at(this.searchResults.size() - 1).unparse(); - } - var searchView = this; - var queryResults = new htrace.QueryResults({queryJson: queryJson}); - console.log("Starting span query " + queryResults.url()); - this.searchInProgress = true; - queryResults.fetch({ - success: function(model, response, options){ - var firstResults = (searchView.searchResults.size() === 0); - console.log("Success on span query " + queryResults.url() + ": got " + - queryResults.size() + " result(s). firstResults=" + firstResults); - searchView.searchResults.add(queryResults.models); - if (firstResults) { - // After the initial search, zoom to fit everything. - // On subsequent searches, we leave the viewport alone. - searchView.resultsView.zoomFitAll(); - } - searchView.searchInProgress = false; - if (showDebug) { - htrace.showModalWarning("Search Debug", - "This is the search debug box, accessible by holding down the " + - "control key while clicking the search button.

" + - "

Query JSON

" + queryResults.prettyQueryString() +
-            "

Response JSON

" +
-            JSON.stringify(queryResults, null, 2) + "

"); - } else if (queryResults.size() == 0) { - if (firstResults) { - htrace.showModalWarning("No Results Found", - "No results were found for your query.

"); - } else { - htrace.showModalWarning("No Additional Results Found", - "No additional results were found for your query.

"); - } - } - }, - error: function(model, response, options){ - this.searchResults.clear(); - var err = "Error " + JSON.stringify(response) + - " on span query " + query.url(); - console.log(err); - alert(err); - searchView.searchInProgress = false; - } - }); - return false; - }, - - dropdownHandler: function(e){ - e.preventDefault(); - var text = $(e.target).text(); - var ptype = htrace.parsePType(text); - if (!ptype) { - alert("Unable to parse predicate type '" + text + "'"); - return false; - } - var index = this.highestPredicateIndex; - this.highestPredicateIndex++; - var el = "pred" + index; - $("#predicates").append('

'); - predicateView = new htrace.PredicateView({ - el: "#" + el, - index: index, - ptype: ptype, - searchView: this - }); - this.predicateViews.push(predicateView); - predicateView.render(); - return true; - }, - - blurBeginHandler: function(e) { - return this.resultsView.handleBeginOrEndChange(e, "begin"); - }, - - blurEndHandler: function(e) { - return this.resultsView.handleBeginOrEndChange(e, "end"); - }, - - zoomFitAllHandler: function(e) { - e.preventDefault(); - this.resultsView.zoomFitAll(); - }, - - removePredicateView: function(predicateView) { - this.predicateViews = _.without(this.predicateViews, predicateView); - }, - - render: function() { - this.$el.html(_.template($("#search-view-template").html()) - ({ model : this.model })) - this.resultsView.render(); - console.log("SearchView#render"); - return this; - }, - - close: function() { - console.log("SearchView#close") - while (this.predicateViews.length > 0) { - this.predicateViews[0].remove(); - } - this.resultsView.remove(); - this.resultsView = null; - this.undelegateEvents(); - } -}); diff --git a/htrace-htraced/src/web/app/server_info.js b/htrace-htraced/src/web/app/server_info.js deleted file mode 100644 index b03f706..0000000 --- a/htrace-htraced/src/web/app/server_info.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// htraced ServerInfo sent back from /serverInfo. -// See rest.go. -htrace.ServerInfo = Backbone.Model.extend({ - defaults: { - "ReleaseVersion": "unknown", - "GitVersion": "unknown", - }, - - url: function() { - return "server/info"; - } -}); diff --git a/htrace-htraced/src/web/app/span.js b/htrace-htraced/src/web/app/span.js deleted file mode 100644 index 2c06fa0..0000000 --- a/htrace-htraced/src/web/app/span.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// The invalid span ID, which is all zeroes. -htrace.INVALID_SPAN_ID = "0000000000000000"; - -htrace.Span = Backbone.Model.extend({ - // Parse a span sent from htraced. - // We use more verbose names for some attributes. - // Missing attributes are treated as zero or empty. Numerical attributes are - // forced to be numbers. - parse: function(response, options) { - var span = {}; - this.set("spanId", response.s ? response.s : htrace.INVALID_SPAN_ID); - this.set("traceId", response.i ? response.i : htrace.INVALID_SPAN_ID); - this.set("processId", response.r ? response.r : ""); - this.set("parents", response.p ? response.p : []); - this.set("description", response.d ? response.d : ""); - this.set("begin", response.b ? parseInt(response.b, 10) : 0); - this.set("end", response.e ? parseInt(response.e, 10) : 0); - return span; - }, - - // Transform a span model back into a JSON string suitable for sending over - // the wire. - unparse: function() { - var obj = { }; - if (!(this.get("spanId") === htrace.INVALID_SPAN_ID)) { - obj.s = this.get("spanId"); - } - if (!(this.get("traceId") === htrace.INVALID_SPAN_ID)) { - obj.i = this.get("traceId"); - } - if (!(this.get("processId") === "")) { - obj.r = this.get("processId"); - } - if (this.get("parents").length > 0) { - obj.p = this.get("parents"); - } - if (this.get("description").length > 0) { - obj.d = this.get("description"); - } - if (this.get("begin") > 0) { - obj.b = this.get("begin"); - } - if (this.get("end") > 0) { - obj.e = this.get("end"); - } - return obj; - } -}); diff --git a/htrace-htraced/src/web/app/span_details_view.js b/htrace-htraced/src/web/app/span_details_view.js deleted file mode 100644 index 9a37055..0000000 --- a/htrace-htraced/src/web/app/span_details_view.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -htrace.SpanDetailsView = Backbone.View.extend({ - initialize: function(options) { - this.el = options.el; - this.model = options.model; - } - - render: function() { - this.$el.html(_.template($("#about-view-template").html()) - ({ model : this.model })); - console.log("AboutView#render"); - return this; - }, - - close: function() { - console.log("AboutView#close") - this.undelegateEvents(); - } -}); diff --git a/htrace-htraced/src/web/app/span_widget.js b/htrace-htraced/src/web/app/span_widget.js deleted file mode 100644 index f9333d6..0000000 --- a/htrace-htraced/src/web/app/span_widget.js +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// Widget containing the trace span displayed on the canvas. -htrace.SpanWidget = function(params) { - for (var k in params) { - this[k]=params[k]; - } - - this.selected = false; - this.widgetManagerFocused = false; - this.xSize = this.xF - this.x0; - this.ySize = this.yF - this.y0; - this.xDB = this.xD - this.xB; - - var widgets = []; - this.upWidget = new htrace.TriangleButton({ - ctx: this.ctx, - direction: "up", - x0: this.xB + 2, - xF: this.xB + (this.xDB / 2) - 2, - y0: this.y0 + 2, - yF: this.yF - 2, - }); - widgets.push(this.upWidget); - this.downWidget = new htrace.TriangleButton({ - ctx: this.ctx, - direction: "down", - x0: this.xB + (this.xDB / 2) + 2, - xF: this.xD - 2, - y0: this.y0 + 2, - yF: this.yF - 2, - }); - widgets.push(this.downWidget); - this.widgetManager = new htrace.WidgetManager({ - widgets: widgets, - }); - - this.draw = function() { - this.drawBackground(); - this.drawProcessId(); - this.drawDescription(); - this.widgetManager.draw(); - }; - - // Draw the background of this span widget. - this.drawBackground = function() { - this.ctx.save(); - if (this.selected) { - this.ctx.fillStyle="#ffccff"; - } else { - this.ctx.fillStyle="#ffffff"; - } - this.ctx.fillRect(this.x0, this.y0, this.xSize, this.ySize); - this.ctx.restore(); - } - - // Draw process ID text. - this.drawProcessId = function() { - this.ctx.save(); - this.ctx.fillStyle="#000000"; - this.ctx.font = (this.ySize - 2) + "px sans-serif"; - this.ctx.beginPath(); - this.ctx.rect(this.x0, this.y0, this.xB - this.x0, this.ySize); - this.ctx.clip(); - this.ctx.fillText(this.span.get('processId'), this.x0, this.yF - 4); - this.ctx.restore(); - }; - - // Draw the span description - this.drawDescription = function() { - // Draw the light blue bar representing time. - this.ctx.save(); - this.ctx.beginPath(); - this.ctx.rect(this.xD, this.y0, this.xF - this.xD, this.ySize); - this.ctx.clip(); - this.ctx.strokeStyle="#000000"; - this.ctx.fillStyle="#a7b7ff"; - var beginX = this.timeToPosition(this.span.get('begin')); - var endX = this.timeToPosition(this.span.get('end')); - - // If the span is completely off the screen, draw a diamond at either the - // beginning or the end of the bar to indicate whether it's too early or too - // late to be seen. - if (endX < this.x0) { - beginX = this.xD; - endX = this.xD; - } - if (beginX > this.xF) { - beginX = this.xF; - endX = this.xF; - } - - var gapY = 2; - var epsilon = Math.max(2, Math.floor(this.xSize / 1000)); - if (endX - beginX < epsilon) { - // The time interval is too narrow to see. Draw a diamond on the point instead. - this.ctx.beginPath(); - this.ctx.moveTo(beginX, this.y0 + gapY); - this.ctx.lineTo(beginX + (Math.floor(this.ySize / 2) - gapY), - this.y0 + Math.floor(this.ySize / 2)); - this.ctx.lineTo(beginX, this.yF - gapY); - this.ctx.lineTo(beginX - (Math.floor(this.ySize / 2) - gapY), - this.y0 + Math.floor(this.ySize / 2)); - this.ctx.closePath(); - this.ctx.fill(); - } else { - // Draw a bar from the start time to the end time. -// console.log("beginX=" + beginX + ", endX=" + endX + -// ", begin=" + this.span.get('begin') + ", end=" + this.span.get('end')); - this.ctx.fillRect(beginX, this.y0 + gapY, endX - beginX, - this.ySize - (gapY * 2)); - } - - // Draw description text - this.ctx.fillStyle="#000000"; - this.ctx.font = (this.ySize - gapY) + "px sans-serif"; - this.ctx.fillText(this.span.get('description'), this.xD, this.yF - gapY - 2); - - this.ctx.restore(); - }; - - // Convert a time in milliseconds since the epoch to an x position. - this.timeToPosition = function(time) { - return this.xD + - (((time - this.begin) * (this.xF - this.xD)) / - (this.end - this.begin)); - }; - - this.inBoundingBox = function(x, y) { - return ((x >= this.x0) && (x <= this.xF) && (y >= this.y0) && (y <= this.yF)); - }; - - this.handleMouseDown = function(x, y) { - if (!this.inBoundingBox(x, y)) { - return false; - } - if (this.widgetManager.handleMouseDown(x, y)) { - this.widgetManagerFocused = true; - return true; - } - this.selected = !this.selected; - this.fillSpanDetailsView(); - return true; - }; - - this.handleMouseUp = function(x, y) { - if (this.widgetManagerFocused) { - this.widgetManager.handleMouseUp(x, y); - this.widgetManagerFocused = false; - } - }; - - this.handleMouseMove = function(x, y) { - if (!this.widgetManagerFocused) { - return false; - } - return this.widgetManager.handleMouseUp(x, y); - }; - - this.fillSpanDetailsView = function() { - var info = { - spanID: this.span.get("spanID"), - begin: htrace.dateToString(parseInt(this.span.get("begin"), 10)), - end: htrace.dateToString(parseInt(this.span.get("end"), 10)) - }; - var explicitOrder = { - spanId: -3, - begin: -2, - end: -1 - }; - keys = []; - for(k in this.span.attributes) { - keys.push(k); - if (info[k] == null) { - info[k] = this.span.get(k); - } - } - // We sort the keys so that the stuff we want at the top appears at the top, - // and everything else is in alphabetical order. - keys = keys.sort(function(a, b) { - var oa = explicitOrder[a] || 0; - var ob = explicitOrder[b] || 0; - if (oa < ob) { - return -1; - } else if (oa > ob) { - return 1; - } else if (a < b) { - return -1; - } else if (a > b) { - return 1; - } else { - return 0; - } - }); - var len = keys.length; - var h = ''; - for (i = 0; i < len; i++) { - // Make every other row grey to improve visibility. - var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff"; - h += _.template('' + - '' + - '' + - "")({key: keys[i], val: info[keys[i]]}); - } - h += '
<%- key %><%- val %>
'; - $("#spanDetails").html(h); - }; - - return this; -}; diff --git a/htrace-htraced/src/web/app/string.js b/htrace-htraced/src/web/app/string.js deleted file mode 100644 index b0dfb74..0000000 --- a/htrace-htraced/src/web/app/string.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// Parse an ISO8601 date string into a moment.js object. -htrace.parseDate = function(val) { - if (val.match(/^[0-9]([0-9]*)$/)) { - // Treat an all-numeric field as UTC milliseconds since the epoch. - return moment.utc(parseInt(val, 10)); - } - // Look for approved date formats. - var toTry = [ - "YYYY-MM-DDTHH:mm:ss,SSS", - "YYYY-MM-DDTHH:mm:ss", - "YYYY-MM-DDTHH:mm", - "YYYY-MM-DD" - ]; - for (var i = 0; i < toTry.length; i++) { - var m = moment.utc(val, toTry[i], true); - if (m.isValid()) { - return m; - } - } - throw "Please enter the date either as YYYY-MM-DDTHH:mm:ss,SSS " + - "in UTC, or as the number of milliseconds since the epoch."; -}; - -// Convert a moment.js moment into an ISO8601-style date string. -htrace.dateToString = function(val) { - return moment.utc(val).format("YYYY-MM-DDTHH:mm:ss,SSS"); -}; - -// Normalize a span ID into the format the server expects to see -// (no leading 0x). -htrace.normalizeSpanId = function(str) { - // Strip off the 0x prefix, if there is one. - if (str.indexOf("0x") == 0) { - str = str.substring(2); - } - if (str.length != 16) { - throw "The length of '" + str + "' was " + str.length + - ", but span IDs must be 16 characters long."; - } - if (str.search(/[^0-9a-fA-F]/) != -1) { - throw "Span IDs must contain only hexadecimal digits, but '" + str + - "' contained invalid characters."; - } - return str; -}; diff --git a/htrace-htraced/src/web/app/time_cursor.js b/htrace-htraced/src/web/app/time_cursor.js deleted file mode 100644 index 0060abb..0000000 --- a/htrace-htraced/src/web/app/time_cursor.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// Draws a vertical bar selecting a time. -htrace.TimeCursor = function(params) { - this.selectedTime = -1; - for (var k in params) { - this[k]=params[k]; - } - - this.positionToTime = function(x) { - if ((x < this.x0) || (x > this.xF)) { - return -1; - } - return this.begin + - (((x - this.x0) * (this.end - this.begin)) / (this.xF - this.x0)); - }; - - this.timeToPosition = function(time) { - return this.x0 + (((time - this.begin) * - (this.xF - this.x0)) / (this.end - this.begin)); - }; - - this.draw = function() { - if (this.selectedTime != -1) { - this.ctx.save(); - this.ctx.beginPath(); - this.ctx.rect(this.x0, this.y0, - this.xF - this.x0, this.yF - this.y0); - this.ctx.clip(); - this.ctx.strokeStyle="#ff0000"; - var x = this.timeToPosition(this.selectedTime); - this.ctx.beginPath(); - this.ctx.moveTo(x, this.y0); - this.ctx.lineTo(x, this.yF); - this.ctx.stroke(); - this.ctx.restore(); - } - }; - - this.handleMouseMove = function(x, y) { - if ((y >= this.y0) && (y <= this.yF) && - (x >= this.x0) && (x <= this.xF)) { - this.selectedTime = this.positionToTime(x); - if (this.selectedTime < 0) { - $(this.el).val(""); - } else { - $(this.el).val(htrace.dateToString(this.selectedTime)); - } - return true; - } - return false; - }; - - return this; -}; diff --git a/htrace-htraced/src/web/app/triangle_button.js b/htrace-htraced/src/web/app/triangle_button.js deleted file mode 100644 index 89f9514..0000000 --- a/htrace-htraced/src/web/app/triangle_button.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// Triangle button widget. -htrace.TriangleButton = function(params) { - this.fgColor = "#6600ff"; - this.bgColor = "#ffffff"; - this.selected = false; - this.direction = "down"; - - this.draw = function() { - this.ctx.save(); - var fg = this.selected ? this.bgColor : this.fgColor; - var bg = this.selected ? this.fgColor : this.bgColor; - this.ctx.beginPath(); - this.ctx.rect(this.x0, this.y0, - this.xF - this.x0, this.yF - this.y0); - this.ctx.clip(); - this.ctx.fillStyle = bg; - this.ctx.strokeStyle = fg; - this.ctx.fillRect(this.x0, this.y0, - this.xF - this.x0, this.yF - this.y0); - this.ctx.lineWidth = 3; - this.ctx.strokeRect(this.x0, this.y0, - this.xF - this.x0, this.yF - this.y0); - var xPad = (this.xF - this.x0) / 5; - var yPad = (this.yF - this.y0) / 5; - this.ctx.fillStyle = fg; - this.ctx.strokeStyle = fg; - this.ctx.beginPath(); - this.ctx.strokeStyle = fg; - if (this.direction === "up") { - this.ctx.moveTo(Math.floor(this.x0 + ((this.xF - this.x0) / 2)), - this.y0 + yPad); - this.ctx.lineTo(this.xF - xPad, this.yF - yPad); - this.ctx.lineTo(this.x0 + xPad, this.yF - yPad); - } else if (this.direction === "down") { - this.ctx.moveTo(this.x0 + xPad, this.y0 + yPad); - this.ctx.lineTo(this.xF - xPad, this.y0 + yPad); - this.ctx.lineTo(Math.floor(this.x0 + ((this.xF - this.x0) / 2)), - this.yF - yPad); - } else { - console.log("TriangleButton: unknown direction " + this.direction); - } - this.ctx.closePath(); - this.ctx.fill(); - this.ctx.restore(); - }; - - this.inBoundingBox = function(x, y) { - return ((x >= this.x0) && (x <= this.xF) && (y >= this.y0) && (y <= this.yF)); - } - - this.handleMouseDown = function(x, y) { -// console.log("TriangleButton#handleMouseDown(x=" + x + ", y=" + y + -// ", x0=" + this.x0 + ", y0="+ this.y0 + -// ", xF=" + this.xF + ", yF=" + this.yF); - if (this.inBoundingBox(x,y)) { - this.selected = true; - return true; - } - return false; - } - - this.handleMouseUp = function(x, y) { - if (this.selected) { - console.log("executing callback"); - } - this.selected = false; - } - - this.handleMouseMove = function(x, y) { - var selected = this.inBoundingBox(x,y); - if (this.selected != selected) { - this.selected = selected; - return true; - } - return false; - } - - for (var k in params) { - this[k]=params[k]; - } - return this; -}; diff --git a/htrace-htraced/src/web/app/widget_manager.js b/htrace-htraced/src/web/app/widget_manager.js deleted file mode 100644 index 49202c5..0000000 --- a/htrace-htraced/src/web/app/widget_manager.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var htrace = htrace || {}; - -// Manages a set of widgets on the canvas. -// Buttons and sliders are both widgets. -htrace.WidgetManager = function(params) { - this.widgets = []; - this.focusedWidget = null; - - this.handleMouseDown = function(x, y) { - if (this.focusedWidget != null) { - this.focusedWidget = null; - } - var numWidgets = this.widgets.length; - console.log("WidgetManager looking through " + numWidgets + " widgets."); - for (var i = 0; i < numWidgets; i++) { - if (this.widgets[i].handleMouseDown(x, y)) { - this.focusedWidget = this.widgets[i]; - break; - } - } - return (this.focusedWidget != null); - }; - - this.handleMouseUp = function(x, y) { - if (this.focusedWidget != null) { - this.focusedWidget.handleMouseUp(x, y); - this.focusedWidget = null; - } - }; - - this.handleMouseMove = function(x, y) { - return this.focusedWidget != null ? - this.focusedWidget.handleMouseMove(x, y) : false; - }; - - this.draw = function() { - var numWidgets = this.widgets.length; - for (var i = 0; i < numWidgets; i++) { - this.widgets[i].draw(); - } - }; - - for (var k in params) { - this[k]=params[k]; - } - return this; -}; diff --git a/htrace-htraced/src/web/custom.css b/htrace-htraced/src/web/custom.css deleted file mode 100644 index 17945cb..0000000 --- a/htrace-htraced/src/web/custom.css +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -.navbar-default { - background-color: #001da9; - border-color: #b2b5db; -} -.navbar-default .navbar-brand { - color: #ecf0f1; -} -.navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { - color: #ffffff; -} -.navbar-default .navbar-text { - color: #ecf0f1; -} -.navbar-default .navbar-nav > li > a { - color: #ecf0f1; -} -.navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { - color: #ffffff; -} -.navbar-default .navbar-nav > li > .dropdown-menu { - background-color: #001da9; -} -.navbar-default .navbar-nav > li > .dropdown-menu > li > a { - color: #ecf0f1; -} -.navbar-default .navbar-nav > li > .dropdown-menu > li > a:hover, -.navbar-default .navbar-nav > li > .dropdown-menu > li > a:focus { - color: #ffffff; - background-color: #b2b5db; -} -.navbar-default .navbar-nav > li > .dropdown-menu > li > .divider { - background-color: #001da9; -} -.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { - color: #ffffff; - background-color: #b2b5db; -} -.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { - color: #ffffff; - background-color: #b2b5db; -} -.navbar-default .navbar-toggle { - border-color: #b2b5db; -} -.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { - background-color: #b2b5db; -} -.navbar-default .navbar-toggle .icon-bar { - background-color: #ecf0f1; -} -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #ecf0f1; -} -.navbar-default .navbar-link { - color: #ecf0f1; -} -.navbar-default .navbar-link:hover { - color: #ffffff; -} -.htrace-canvas-container { - overflow: hidden; - position: relative; -} -.htrace-canvas { - position: absolute; - top: 0px; - left: 0px; -} - -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #ecf0f1; - } - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #ffffff; - } - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #ffffff; - background-color: #b2b5db; - } -} diff --git a/htrace-htraced/src/web/index.html b/htrace-htraced/src/web/index.html deleted file mode 100644 index 66ef0dc..0000000 --- a/htrace-htraced/src/web/index.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - HTrace - - - - - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/htrace-htraced/src/web/lib/backbone-1.1.2.js b/htrace-htraced/src/web/lib/backbone-1.1.2.js deleted file mode 100644 index 5da4943..0000000 --- a/htrace-htraced/src/web/lib/backbone-1.1.2.js +++ /dev/null @@ -1,1608 +0,0 @@ -// Backbone.js 1.1.2 - -// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// http://backbonejs.org - -(function(root, factory) { - - // Set up Backbone appropriately for the environment. Start with AMD. - if (typeof define === 'function' && define.amd) { - define(['underscore', 'jquery', 'exports'], function(_, $, exports) { - // Export global even in AMD case in case this script is loaded with - // others that may still expect a global Backbone. - root.Backbone = factory(root, exports, _, $); - }); - - // Next for Node.js or CommonJS. jQuery may not be needed as a module. - } else if (typeof exports !== 'undefined') { - var _ = require('underscore'); - factory(root, exports, _); - - // Finally, as a browser global. - } else { - root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); - } - -}(this, function(root, Backbone, _, $) { - - // Initial Setup - // ------------- - - // Save the previous value of the `Backbone` variable, so that it can be - // restored later on, if `noConflict` is used. - var previousBackbone = root.Backbone; - - // Create local references to array methods we'll want to use later. - var array = []; - var push = array.push; - var slice = array.slice; - var splice = array.splice; - - // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '1.1.2'; - - // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns - // the `$` variable. - Backbone.$ = $; - - // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable - // to its previous owner. Returns a reference to this Backbone object. - Backbone.noConflict = function() { - root.Backbone = previousBackbone; - return this; - }; - - // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option - // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and - // set a `X-Http-Method-Override` header. - Backbone.emulateHTTP = false; - - // Turn on `emulateJSON` to support legacy servers that can't deal with direct - // `application/json` requests ... will encode the body as - // `application/x-www-form-urlencoded` instead and will send the model in a - // form param named `model`. - Backbone.emulateJSON = false; - - // Backbone.Events - // --------------- - - // A module that can be mixed in to *any object* in order to provide it with - // custom events. You may bind with `on` or remove with `off` callback - // functions to an event; `trigger`-ing an event fires all callbacks in - // succession. - // - // var object = {}; - // _.extend(object, Backbone.Events); - // object.on('expand', function(){ alert('expanded'); }); - // object.trigger('expand'); - // - var Events = Backbone.Events = { - - // Bind an event to a `callback` function. Passing `"all"` will bind - // the callback to all events fired. - on: function(name, callback, context) { - if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; - this._events || (this._events = {}); - var events = this._events[name] || (this._events[name] = []); - events.push({callback: callback, context: context, ctx: context || this}); - return this; - }, - - // Bind an event to only be triggered a single time. After the first time - // the callback is invoked, it will be removed. - once: function(name, callback, context) { - if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; - var self = this; - var once = _.once(function() { - self.off(name, once); - callback.apply(this, arguments); - }); - once._callback = callback; - return this.on(name, once, context); - }, - - // Remove one or many callbacks. If `context` is null, removes all - // callbacks with that function. If `callback` is null, removes all - // callbacks for the event. If `name` is null, removes all bound - // callbacks for all events. - off: function(name, callback, context) { - var retain, ev, events, names, i, l, j, k; - if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; - if (!name && !callback && !context) { - this._events = void 0; - return this; - } - names = name ? [name] : _.keys(this._events); - for (i = 0, l = names.length; i < l; i++) { - name = names[i]; - if (events = this._events[name]) { - this._events[name] = retain = []; - if (callback || context) { - for (j = 0, k = events.length; j < k; j++) { - ev = events[j]; - if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || - (context && context !== ev.context)) { - retain.push(ev); - } - } - } - if (!retain.length) delete this._events[name]; - } - } - - return this; - }, - - // Trigger one or many events, firing all bound callbacks. Callbacks are - // passed the same arguments as `trigger` is, apart from the event name - // (unless you're listening on `"all"`, which will cause your callback to - // receive the true name of the event as the first argument). - trigger: function(name) { - if (!this._events) return this; - var args = slice.call(arguments, 1); - if (!eventsApi(this, 'trigger', name, args)) return this; - var events = this._events[name]; - var allEvents = this._events.all; - if (events) triggerEvents(events, args); - if (allEvents) triggerEvents(allEvents, arguments); - return this; - }, - - // Tell this object to stop listening to either specific events ... or - // to every object it's currently listening to. - stopListening: function(obj, name, callback) { - var listeningTo = this._listeningTo; - if (!listeningTo) return this; - var remove = !name && !callback; - if (!callback && typeof name === 'object') callback = this; - if (obj) (listeningTo = {})[obj._listenId] = obj; - for (var id in listeningTo) { - obj = listeningTo[id]; - obj.off(name, callback, this); - if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id]; - } - return this; - } - - }; - - // Regular expression used to split event strings. - var eventSplitter = /\s+/; - - // Implement fancy features of the Events API such as multiple event - // names `"change blur"` and jQuery-style event maps `{change: action}` - // in terms of the existing API. - var eventsApi = function(obj, action, name, rest) { - if (!name) return true; - - // Handle event maps. - if (typeof name === 'object') { - for (var key in name) { - obj[action].apply(obj, [key, name[key]].concat(rest)); - } - return false; - } - - // Handle space separated event names. - if (eventSplitter.test(name)) { - var names = name.split(eventSplitter); - for (var i = 0, l = names.length; i < l; i++) { - obj[action].apply(obj, [names[i]].concat(rest)); - } - return false; - } - - return true; - }; - - // A difficult-to-believe, but optimized internal dispatch function for - // triggering events. Tries to keep the usual cases speedy (most internal - // Backbone events have 3 arguments). - var triggerEvents = function(events, args) { - var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; - switch (args.length) { - case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; - case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; - case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; - case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; - default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; - } - }; - - var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; - - // Inversion-of-control versions of `on` and `once`. Tell *this* object to - // listen to an event in another object ... keeping track of what it's - // listening to. - _.each(listenMethods, function(implementation, method) { - Events[method] = function(obj, name, callback) { - var listeningTo = this._listeningTo || (this._listeningTo = {}); - var id = obj._listenId || (obj._listenId = _.uniqueId('l')); - listeningTo[id] = obj; - if (!callback && typeof name === 'object') callback = this; - obj[implementation](name, callback, this); - return this; - }; - }); - - // Aliases for backwards compatibility. - Events.bind = Events.on; - Events.unbind = Events.off; - - // Allow the `Backbone` object to serve as a global event bus, for folks who - // want global "pubsub" in a convenient place. - _.extend(Backbone, Events); - - // Backbone.Model - // -------------- - - // Backbone **Models** are the basic data object in the framework -- - // frequently representing a row in a table in a database on your server. - // A discrete chunk of data and a bunch of useful, related methods for - // performing computations and transformations on that data. - - // Create a new model with the specified attributes. A client id (`cid`) - // is automatically generated and assigned for you. - var Model = Backbone.Model = function(attributes, options) { - var attrs = attributes || {}; - options || (options = {}); - this.cid = _.uniqueId('c'); - this.attributes = {}; - if (options.collection) this.collection = options.collection; - if (options.parse) attrs = this.parse(attrs, options) || {}; - attrs = _.defaults({}, attrs, _.result(this, 'defaults')); - this.set(attrs, options); - this.changed = {}; - this.initialize.apply(this, arguments); - }; - - // Attach all inheritable methods to the Model prototype. - _.extend(Model.prototype, Events, { - - // A hash of attributes whose current and previous value differ. - changed: null, - - // The value returned during the last failed validation. - validationError: null, - - // The default name for the JSON `id` attribute is `"id"`. MongoDB and - // CouchDB users may want to set this to `"_id"`. - idAttribute: 'id', - - // Initialize is an empty function by default. Override it with your own - // initialization logic. - initialize: function(){}, - - // Return a copy of the model's `attributes` object. - toJSON: function(options) { - return _.clone(this.attributes); - }, - - // Proxy `Backbone.sync` by default -- but override this if you need - // custom syncing semantics for *this* particular model. - sync: function() { - return Backbone.sync.apply(this, arguments); - }, - - // Get the value of an attribute. - get: function(attr) { - return this.attributes[attr]; - }, - - // Get the HTML-escaped value of an attribute. - escape: function(attr) { - return _.escape(this.get(attr)); - }, - - // Returns `true` if the attribute contains a value that is not null - // or undefined. - has: function(attr) { - return this.get(attr) != null; - }, - - // Set a hash of model attributes on the object, firing `"change"`. This is - // the core primitive operation of a model, updating the data and notifying - // anyone who needs to know about the change in state. The heart of the beast. - set: function(key, val, options) { - var attr, attrs, unset, changes, silent, changing, prev, current; - if (key == null) return this; - - // Handle both `"key", value` and `{key: value}` -style arguments. - if (typeof key === 'object') { - attrs = key; - options = val; - } else { - (attrs = {})[key] = val; - } - - options || (options = {}); - - // Run validation. - if (!this._validate(attrs, options)) return false; - - // Extract attributes and options. - unset = options.unset; - silent = options.silent; - changes = []; - changing = this._changing; - this._changing = true; - - if (!changing) { - this._previousAttributes = _.clone(this.attributes); - this.changed = {}; - } - current = this.attributes, prev = this._previousAttributes; - - // Check for changes of `id`. - if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; - - // For each `set` attribute, update or delete the current value. - for (attr in attrs) { - val = attrs[attr]; - if (!_.isEqual(current[attr], val)) changes.push(attr); - if (!_.isEqual(prev[attr], val)) { - this.changed[attr] = val; - } else { - delete this.changed[attr]; - } - unset ? delete current[attr] : current[attr] = val; - } - - // Trigger all relevant attribute changes. - if (!silent) { - if (changes.length) this._pending = options; - for (var i = 0, l = changes.length; i < l; i++) { - this.trigger('change:' + changes[i], this, current[changes[i]], options); - } - } - - // You might be wondering why there's a `while` loop here. Changes can - // be recursively nested within `"change"` events. - if (changing) return this; - if (!silent) { - while (this._pending) { - options = this._pending; - this._pending = false; - this.trigger('change', this, options); - } - } - this._pending = false; - this._changing = false; - return this; - }, - - // Remove an attribute from the model, firing `"change"`. `unset` is a noop - // if the attribute doesn't exist. - unset: function(attr, options) { - return this.set(attr, void 0, _.extend({}, options, {unset: true})); - }, - - // Clear all attributes on the model, firing `"change"`. - clear: function(options) { - var attrs = {}; - for (var key in this.attributes) attrs[key] = void 0; - return this.set(attrs, _.extend({}, options, {unset: true})); - }, - - // Determine if the model has changed since the last `"change"` event. - // If you specify an attribute name, determine if that attribute has changed. - hasChanged: function(attr) { - if (attr == null) return !_.isEmpty(this.changed); - return _.has(this.changed, attr); - }, - - // Return an object containing all the attributes that have changed, or - // false if there are no changed attributes. Useful for determining what - // parts of a view need to be updated and/or what attributes need to be - // persisted to the server. Unset attributes will be set to undefined. - // You can also pass an attributes object to diff against the model, - // determining if there *would be* a change. - changedAttributes: function(diff) { - if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; - var val, changed = false; - var old = this._changing ? this._previousAttributes : this.attributes; - for (var attr in diff) { - if (_.isEqual(old[attr], (val = diff[attr]))) continue; - (changed || (changed = {}))[attr] = val; - } - return changed; - }, - - // Get the previous value of an attribute, recorded at the time the last - // `"change"` event was fired. - previous: function(attr) { - if (attr == null || !this._previousAttributes) return null; - return this._previousAttributes[attr]; - }, - - // Get all of the attributes of the model at the time of the previous - // `"change"` event. - previousAttributes: function() { - return _.clone(this._previousAttributes); - }, - - // Fetch the model from the server. If the server's representation of the - // model differs from its current attributes, they will be overridden, - // triggering a `"change"` event. - fetch: function(options) { - options = options ? _.clone(options) : {}; - if (options.parse === void 0) options.parse = true; - var model = this; - var success = options.success; - options.success = function(resp) { - if (!model.set(model.parse(resp, options), options)) return false; - if (success) success(model, resp, options); - model.trigger('sync', model, resp, options); - }; - wrapError(this, options); - return this.sync('read', this, options); - }, - - // Set a hash of model attributes, and sync the model to the server. - // If the server returns an attributes hash that differs, the model's - // state will be `set` again. - save: function(key, val, options) { - var attrs, method, xhr, attributes = this.attributes; - - // Handle both `"key", value` and `{key: value}` -style arguments. - if (key == null || typeof key === 'object') { - attrs = key; - options = val; - } else { - (attrs = {})[key] = val; - } - - options = _.extend({validate: true}, options); - - // If we're not waiting and attributes exist, save acts as - // `set(attr).save(null, opts)` with validation. Otherwise, check if - // the model will be valid when the attributes, if any, are set. - if (attrs && !options.wait) { - if (!this.set(attrs, options)) return false; - } else { - if (!this._validate(attrs, options)) return false; - } - - // Set temporary attributes if `{wait: true}`. - if (attrs && options.wait) { - this.attributes = _.extend({}, attributes, attrs); - } - - // After a successful server-side save, the client is (optionally) - // updated with the server-side state. - if (options.parse === void 0) options.parse = true; - var model = this; - var success = options.success; - options.success = function(resp) { - // Ensure attributes are restored during synchronous saves. - model.attributes = attributes; - var serverAttrs = model.parse(resp, options); - if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); - if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { - return false; - } - if (success) success(model, resp, options); - model.trigger('sync', model, resp, options); - }; - wrapError(this, options); - - method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); - if (method === 'patch') options.attrs = attrs; - xhr = this.sync(method, this, options); - - // Restore attributes. - if (attrs && options.wait) this.attributes = attributes; - - return xhr; - }, - - // Destroy this model on the server if it was already persisted. - // Optimistically removes the model from its collection, if it has one. - // If `wait: true` is passed, waits for the server to respond before removal. - destroy: function(options) { - options = options ? _.clone(options) : {}; - var model = this; - var success = options.success; - - var destroy = function() { - model.trigger('destroy', model, model.collection, options); - }; - - options.success = function(resp) { - if (options.wait || model.isNew()) destroy(); - if (success) success(model, resp, options); - if (!model.isNew()) model.trigger('sync', model, resp, options); - }; - - if (this.isNew()) { - options.success(); - return false; - } - wrapError(this, options); - - var xhr = this.sync('delete', this, options); - if (!options.wait) destroy(); - return xhr; - }, - - // Default URL for the model's representation on the server -- if you're - // using Backbone's restful methods, override this to change the endpoint - // that will be called. - url: function() { - var base = - _.result(this, 'urlRoot') || - _.result(this.collection, 'url') || - urlError(); - if (this.isNew()) return base; - return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id); - }, - - // **parse** converts a response into the hash of attributes to be `set` on - // the model. The default implementation is just to pass the response along. - parse: function(resp, options) { - return resp; - }, - - // Create a new model with identical attributes to this one. - clone: function() { - return new this.constructor(this.attributes); - }, - - // A model is new if it has never been saved to the server, and lacks an id. - isNew: function() { - return !this.has(this.idAttribute); - }, - - // Check if the model is currently in a valid state. - isValid: function(options) { - return this._validate({}, _.extend(options || {}, { validate: true })); - }, - - // Run validation against the next complete set of model attributes, - // returning `true` if all is well. Otherwise, fire an `"invalid"` event. - _validate: function(attrs, options) { - if (!options.validate || !this.validate) return true; - attrs = _.extend({}, this.attributes, attrs); - var error = this.validationError = this.validate(attrs, options) || null; - if (!error) return true; - this.trigger('invalid', this, error, _.extend(options, {validationError: error})); - return false; - } - - }); - - // Underscore methods that we want to implement on the Model. - var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; - - // Mix in each Underscore method as a proxy to `Model#attributes`. - _.each(modelMethods, function(method) { - Model.prototype[method] = function() { - var args = slice.call(arguments); - args.unshift(this.attributes); - return _[method].apply(_, args); - }; - }); - - // Backbone.Collection - // ------------------- - - // If models tend to represent a single row of data, a Backbone Collection is - // more analagous to a table full of data ... or a small slice or page of that - // table, or a collection of rows that belong together for a particular reason - // -- all of the messages in this particular folder, all of the documents - // belonging to this particular author, and so on. Collections maintain - // indexes of their models, both in order, and for lookup by `id`. - - // Create a new **Collection**, perhaps to contain a specific type of `model`. - // If a `comparator` is specified, the Collection will maintain - // its models in sort order, as they're added and removed. - var Collection = Backbone.Collection = function(models, options) { - options || (options = {}); - if (options.model) this.model = options.model; - if (options.comparator !== void 0) this.comparator = options.comparator; - this._reset(); - this.initialize.apply(this, arguments); - if (models) this.reset(models, _.extend({silent: true}, options)); - }; - - // Default options for `Collection#set`. - var setOptions = {add: true, remove: true, merge: true}; - var addOptions = {add: true, remove: false}; - - // Define the Collection's inheritable methods. - _.extend(Collection.prototype, Events, { - - // The default model for a collection is just a **Backbone.Model**. - // This should be overridden in most cases. - model: Model, - - // Initialize is an empty function by default. Override it with your own - // initialization logic. - initialize: function(){}, - - // The JSON representation of a Collection is an array of the - // models' attributes. - toJSON: function(options) { - return this.map(function(model){ return model.toJSON(options); }); - }, - - // Proxy `Backbone.sync` by default. - sync: function() { - return Backbone.sync.apply(this, arguments); - }, - - // Add a model, or list of models to the set. - add: function(models, options) { - return this.set(models, _.extend({merge: false}, options, addOptions)); - }, - - // Remove a model, or a list of models from the set. - remove: function(models, options) { - var singular = !_.isArray(models); - models = singular ? [models] : _.clone(models); - options || (options = {}); - var i, l, index, model; - for (i = 0, l = models.length; i < l; i++) { - model = models[i] = this.get(models[i]); - if (!model) continue; - delete this._byId[model.id]; - delete this._byId[model.cid]; - index = this.indexOf(model); - this.models.splice(index, 1); - this.length--; - if (!options.silent) { - options.index = index; - model.trigger('remove', model, this, options); - } - this._removeReference(model, options); - } - return singular ? models[0] : models; - }, - - // Update a collection by `set`-ing a new list of models, adding new ones, - // removing models that are no longer present, and merging models that - // already exist in the collection, as necessary. Similar to **Model#set**, - // the core operation for updating the data contained by the collection. - set: function(models, options) { - options = _.defaults({}, options, setOptions); - if (options.parse) models = this.parse(models, options); - var singular = !_.isArray(models); - models = singular ? (models ? [models] : []) : _.clone(models); - var i, l, id, model, attrs, existing, sort; - var at = options.at; - var targetModel = this.model; - var sortable = this.comparator && (at == null) && options.sort !== false; - var sortAttr = _.isString(this.comparator) ? this.comparator : null; - var toAdd = [], toRemove = [], modelMap = {}; - var add = options.add, merge = options.merge, remove = options.remove; - var order = !sortable && add && remove ? [] : false; - - // Turn bare objects into model references, and prevent invalid models - // from being added. - for (i = 0, l = models.length; i < l; i++) { - attrs = models[i] || {}; - if (attrs instanceof Model) { - id = model = attrs; - } else { - id = attrs[targetModel.prototype.idAttribute || 'id']; - } - - // If a duplicate is found, prevent it from being added and - // optionally merge it into the existing model. - if (existing = this.get(id)) { - if (remove) modelMap[existing.cid] = true; - if (merge) { - attrs = attrs === model ? model.attributes : attrs; - if (options.parse) attrs = existing.parse(attrs, options); - existing.set(attrs, options); - if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; - } - models[i] = existing; - - // If this is a new, valid model, push it to the `toAdd` list. - } else if (add) { - model = models[i] = this._prepareModel(attrs, options); - if (!model) continue; - toAdd.push(model); - this._addReference(model, options); - } - - // Do not add multiple models with the same `id`. - model = existing || model; - if (order && (model.isNew() || !modelMap[model.id])) order.push(model); - modelMap[model.id] = true; - } - - // Remove nonexistent models if appropriate. - if (remove) { - for (i = 0, l = this.length; i < l; ++i) { - if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); - } - if (toRemove.length) this.remove(toRemove, options); - } - - // See if sorting is needed, update `length` and splice in new models. - if (toAdd.length || (order && order.length)) { - if (sortable) sort = true; - this.length += toAdd.length; - if (at != null) { - for (i = 0, l = toAdd.length; i < l; i++) { - this.models.splice(at + i, 0, toAdd[i]); - } - } else { - if (order) this.models.length = 0; - var orderedModels = order || toAdd; - for (i = 0, l = orderedModels.length; i < l; i++) { - this.models.push(orderedModels[i]); - } - } - } - - // Silently sort the collection if appropriate. - if (sort) this.sort({silent: true}); - - // Unless silenced, it's time to fire all appropriate add/sort events. - if (!options.silent) { - for (i = 0, l = toAdd.length; i < l; i++) { - (model = toAdd[i]).trigger('add', model, this, options); - } - if (sort || (order && order.length)) this.trigger('sort', this, options); - } - - // Return the added (or merged) model (or models). - return singular ? models[0] : models; - }, - - // When you have more items than you want to add or remove individually, - // you can reset the entire set with a new list of models, without firing - // any granular `add` or `remove` events. Fires `reset` when finished. - // Useful for bulk operations and optimizations. - reset: function(models, options) { - options || (options = {}); - for (var i = 0, l = this.models.length; i < l; i++) { - this._removeReference(this.models[i], options); - } - options.previousModels = this.models; - this._reset(); - models = this.add(models, _.extend({silent: true}, options)); - if (!options.silent) this.trigger('reset', this, options); - return models; - }, - - // Add a model to the end of the collection. - push: function(model, options) { - return this.add(model, _.extend({at: this.length}, options)); - }, - - // Remove a model from the end of the collection. - pop: function(options) { - var model = this.at(this.length - 1); - this.remove(model, options); - return model; - }, - - // Add a model to the beginning of the collection. - unshift: function(model, options) { - return this.add(model, _.extend({at: 0}, options)); - }, - - // Remove a model from the beginning of the collection. - shift: function(options) { - var model = this.at(0); - this.remove(model, options); - return model; - }, - - // Slice out a sub-array of models from the collection. - slice: function() { - return slice.apply(this.models, arguments); - }, - - // Get a model from the set by id. - get: function(obj) { - if (obj == null) return void 0; - return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid]; - }, - - // Get the model at the given index. - at: function(index) { - return this.models[index]; - }, - - // Return models with matching attributes. Useful for simple cases of - // `filter`. - where: function(attrs, first) { - if (_.isEmpty(attrs)) return first ? void 0 : []; - return this[first ? 'find' : 'filter'](function(model) { - for (var key in attrs) { - if (attrs[key] !== model.get(key)) return false; - } - return true; - }); - }, - - // Return the first model with matching attributes. Useful for simple cases - // of `find`. - findWhere: function(attrs) { - return this.where(attrs, true); - }, - - // Force the collection to re-sort itself. You don't need to call this under - // normal circumstances, as the set will maintain sort order as each item - // is added. - sort: function(options) { - if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); - options || (options = {}); - - // Run sort based on type of `comparator`. - if (_.isString(this.comparator) || this.comparator.length === 1) { - this.models = this.sortBy(this.comparator, this); - } else { - this.models.sort(_.bind(this.comparator, this)); - } - - if (!options.silent) this.trigger('sort', this, options); - return this; - }, - - // Pluck an attribute from each model in the collection. - pluck: function(attr) { - return _.invoke(this.models, 'get', attr); - }, - - // Fetch the default set of models for this collection, resetting the - // collection when they arrive. If `reset: true` is passed, the response - // data will be passed through the `reset` method instead of `set`. - fetch: function(options) { - options = options ? _.clone(options) : {}; - if (options.parse === void 0) options.parse = true; - var success = options.success; - var collection = this; - options.success = function(resp) { - var method = options.reset ? 'reset' : 'set'; - collection[method](resp, options); - if (success) success(collection, resp, options); - collection.trigger('sync', collection, resp, options); - }; - wrapError(this, options); - return this.sync('read', this, options); - }, - - // Create a new instance of a model in this collection. Add the model to the - // collection immediately, unless `wait: true` is passed, in which case we - // wait for the server to agree. - create: function(model, options) { - options = options ? _.clone(options) : {}; - if (!(model = this._prepareModel(model, options))) return false; - if (!options.wait) this.add(model, options); - var collection = this; - var success = options.success; - options.success = function(model, resp) { - if (options.wait) collection.add(model, options); - if (success) success(model, resp, options); - }; - model.save(null, options); - return model; - }, - - // **parse** converts a response into a list of models to be added to the - // collection. The default implementation is just to pass it through. - parse: function(resp, options) { - return resp; - }, - - // Create a new collection with an identical list of models as this one. - clone: function() { - return new this.constructor(this.models); - }, - - // Private method to reset all internal state. Called when the collection - // is first initialized or reset. - _reset: function() { - this.length = 0; - this.models = []; - this._byId = {}; - }, - - // Prepare a hash of attributes (or other model) to be added to this - // collection. - _prepareModel: function(attrs, options) { - if (attrs instanceof Model) return attrs; - options = options ? _.clone(options) : {}; - options.collection = this; - var model = new this.model(attrs, options); - if (!model.validationError) return model; - this.trigger('invalid', this, model.validationError, options); - return false; - }, - - // Internal method to create a model's ties to a collection. - _addReference: function(model, options) { - this._byId[model.cid] = model; - if (model.id != null) this._byId[model.id] = model; - if (!model.collection) model.collection = this; - model.on('all', this._onModelEvent, this); - }, - - // Internal method to sever a model's ties to a collection. - _removeReference: function(model, options) { - if (this === model.collection) delete model.collection; - model.off('all', this._onModelEvent, this); - }, - - // Internal method called every time a model in the set fires an event. - // Sets need to update their indexes when models change ids. All other - // events simply proxy through. "add" and "remove" events that originate - // in other collections are ignored. - _onModelEvent: function(event, model, collection, options) { - if ((event === 'add' || event === 'remove') && collection !== this) return; - if (event === 'destroy') this.remove(model, options); - if (model && event === 'change:' + model.idAttribute) { - delete this._byId[model.previous(model.idAttribute)]; - if (model.id != null) this._byId[model.id] = model; - } - this.trigger.apply(this, arguments); - } - - }); - - // Underscore methods that we want to implement on the Collection. - // 90% of the core usefulness of Backbone Collections is actually implemented - // right here: - var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', - 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', - 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', - 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', - 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', - 'lastIndexOf', 'isEmpty', 'chain', 'sample']; - - // Mix in each Underscore method as a proxy to `Collection#models`. - _.each(methods, function(method) { - Collection.prototype[method] = function() { - var args = slice.call(arguments); - args.unshift(this.models); - return _[method].apply(_, args); - }; - }); - - // Underscore methods that take a property name as an argument. - var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; - - // Use attributes instead of properties. - _.each(attributeMethods, function(method) { - Collection.prototype[method] = function(value, context) { - var iterator = _.isFunction(value) ? value : function(model) { - return model.get(value); - }; - return _[method](this.models, iterator, context); - }; - }); - - // Backbone.View - // ------------- - - // Backbone Views are almost more convention than they are actual code. A View - // is simply a JavaScript object that represents a logical chunk of UI in the - // DOM. This might be a single item, an entire list, a sidebar or panel, or - // even the surrounding frame which wraps your whole app. Defining a chunk of - // UI as a **View** allows you to define your DOM events declaratively, without - // having to worry about render order ... and makes it easy for the view to - // react to specific changes in the state of your models. - - // Creating a Backbone.View creates its initial element outside of the DOM, - // if an existing element is not provided... - var View = Backbone.View = function(options) { - this.cid = _.uniqueId('view'); - options || (options = {}); - _.extend(this, _.pick(options, viewOptions)); - this._ensureElement(); - this.initialize.apply(this, arguments); - this.delegateEvents(); - }; - - // Cached regex to split keys for `delegate`. - var delegateEventSplitter = /^(\S+)\s*(.*)$/; - - // List of view options to be merged as properties. - var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; - - // Set up all inheritable **Backbone.View** properties and methods. - _.extend(View.prototype, Events, { - - // The default `tagName` of a View's element is `"div"`. - tagName: 'div', - - // jQuery delegate for element lookup, scoped to DOM elements within the - // current view. This should be preferred to global lookups where possible. - $: function(selector) { - return this.$el.find(selector); - }, - - // Initialize is an empty function by default. Override it with your own - // initialization logic. - initialize: function(){}, - - // **render** is the core function that your view should override, in order - // to populate its element (`this.el`), with the appropriate HTML. The - // convention is for **render** to always return `this`. - render: function() { - return this; - }, - - // Remove this view by taking the element out of the DOM, and removing any - // applicable Backbone.Events listeners. - remove: function() { - this.$el.remove(); - this.stopListening(); - return this; - }, - - // Change the view's element (`this.el` property), including event - // re-delegation. - setElement: function(element, delegate) { - if (this.$el) this.undelegateEvents(); - this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); - this.el = this.$el[0]; - if (delegate !== false) this.delegateEvents(); - return this; - }, - - // Set callbacks, where `this.events` is a hash of - // - // *{"event selector": "callback"}* - // - // { - // 'mousedown .title': 'edit', - // 'click .button': 'save', - // 'click .open': function(e) { ... } - // } - // - // pairs. Callbacks will be bound to the view, with `this` set properly. - // Uses event delegation for efficiency. - // Omitting the selector binds the event to `this.el`. - // This only works for delegate-able events: not `focus`, `blur`, and - // not `change`, `submit`, and `reset` in Internet Explorer. - delegateEvents: function(events) { - if (!(events || (events = _.result(this, 'events')))) return this; - this.undelegateEvents(); - for (var key in events) { - var method = events[key]; - if (!_.isFunction(method)) method = this[events[key]]; - if (!method) continue; - - var match = key.match(delegateEventSplitter); - var eventName = match[1], selector = match[2]; - method = _.bind(method, this); - eventName += '.delegateEvents' + this.cid; - if (selector === '') { - this.$el.on(eventName, method); - } else { - this.$el.on(eventName, selector, method); - } - } - return this; - }, - - // Clears all callbacks previously bound to the view with `delegateEvents`. - // You usually don't need to use this, but may wish to if you have multiple - // Backbone views attached to the same DOM element. - undelegateEvents: function() { - this.$el.off('.delegateEvents' + this.cid); - return this; - }, - - // Ensure that the View has a DOM element to render into. - // If `this.el` is a string, pass it through `$()`, take the first - // matching element, and re-assign it to `el`. Otherwise, create - // an element from the `id`, `className` and `tagName` properties. - _ensureElement: function() { - if (!this.el) { - var attrs = _.extend({}, _.result(this, 'attributes')); - if (this.id) attrs.id = _.result(this, 'id'); - if (this.className) attrs['class'] = _.result(this, 'className'); - var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); - this.setElement($el, false); - } else { - this.setElement(_.result(this, 'el'), false); - } - } - - }); - - // Backbone.sync - // ------------- - - // Override this function to change the manner in which Backbone persists - // models to the server. You will be passed the type of request, and the - // model in question. By default, makes a RESTful Ajax request - // to the model's `url()`. Some possible customizations could be: - // - // * Use `setTimeout` to batch rapid-fire updates into a single request. - // * Send up the models as XML instead of JSON. - // * Persist models via WebSockets instead of Ajax. - // - // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests - // as `POST`, with a `_method` parameter containing the true HTTP method, - // as well as all requests with the body as `application/x-www-form-urlencoded` - // instead of `application/json` with the model in a param named `model`. - // Useful when interfacing with server-side languages like **PHP** that make - // it difficult to read the body of `PUT` requests. - Backbone.sync = function(method, model, options) { - var type = methodMap[method]; - - // Default options, unless specified. - _.defaults(options || (options = {}), { - emulateHTTP: Backbone.emulateHTTP, - emulateJSON: Backbone.emulateJSON - }); - - // Default JSON-request options. - var params = {type: type, dataType: 'json'}; - - // Ensure that we have a URL. - if (!options.url) { - params.url = _.result(model, 'url') || urlError(); - } - - // Ensure that we have the appropriate request data. - if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { - params.contentType = 'application/json'; - params.data = JSON.stringify(options.attrs || model.toJSON(options)); - } - - // For older servers, emulate JSON by encoding the request into an HTML-form. - if (options.emulateJSON) { - params.contentType = 'application/x-www-form-urlencoded'; - params.data = params.data ? {model: params.data} : {}; - } - - // For older servers, emulate HTTP by mimicking the HTTP method with `_method` - // And an `X-HTTP-Method-Override` header. - if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { - params.type = 'POST'; - if (options.emulateJSON) params.data._method = type; - var beforeSend = options.beforeSend; - options.beforeSend = function(xhr) { - xhr.setRequestHeader('X-HTTP-Method-Override', type); - if (beforeSend) return beforeSend.apply(this, arguments); - }; - } - - // Don't process data on a non-GET request. - if (params.type !== 'GET' && !options.emulateJSON) { - params.processData = false; - } - - // If we're sending a `PATCH` request, and we're in an old Internet Explorer - // that still has ActiveX enabled by default, override jQuery to use that - // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. - if (params.type === 'PATCH' && noXhrPatch) { - params.xhr = function() { - return new ActiveXObject("Microsoft.XMLHTTP"); - }; - } - - // Make the request, allowing the user to override any Ajax options. - var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); - model.trigger('request', model, xhr, options); - return xhr; - }; - - var noXhrPatch = - typeof window !== 'undefined' && !!window.ActiveXObject && - !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent); - - // Map from CRUD to HTTP for our default `Backbone.sync` implementation. - var methodMap = { - 'create': 'POST', - 'update': 'PUT', - 'patch': 'PATCH', - 'delete': 'DELETE', - 'read': 'GET' - }; - - // Set the default implementation of `Backbone.ajax` to proxy through to `$`. - // Override this if you'd like to use a different library. - Backbone.ajax = function() { - return Backbone.$.ajax.apply(Backbone.$, arguments); - }; - - // Backbone.Router - // --------------- - - // Routers map faux-URLs to actions, and fire events when routes are - // matched. Creating a new one sets its `routes` hash, if not set statically. - var Router = Backbone.Router = function(options) { - options || (options = {}); - if (options.routes) this.routes = options.routes; - this._bindRoutes(); - this.initialize.apply(this, arguments); - }; - - // Cached regular expressions for matching named param parts and splatted - // parts of route strings. - var optionalParam = /\((.*?)\)/g; - var namedParam = /(\(\?)?:\w+/g; - var splatParam = /\*\w+/g; - var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; - - // Set up all inheritable **Backbone.Router** properties and methods. - _.extend(Router.prototype, Events, { - - // Initialize is an empty function by default. Override it with your own - // initialization logic. - initialize: function(){}, - - // Manually bind a single named route to a callback. For example: - // - // this.route('search/:query/p:num', 'search', function(query, num) { - // ... - // }); - // - route: function(route, name, callback) { - if (!_.isRegExp(route)) route = this._routeToRegExp(route); - if (_.isFunction(name)) { - callback = name; - name = ''; - } - if (!callback) callback = this[name]; - var router = this; - Backbone.history.route(route, function(fragment) { - var args = router._extractParameters(route, fragment); - router.execute(callback, args); - router.trigger.apply(router, ['route:' + name].concat(args)); - router.trigger('route', name, args); - Backbone.history.trigger('route', router, name, args); - }); - return this; - }, - - // Execute a route handler with the provided parameters. This is an - // excellent place to do pre-route setup or post-route cleanup. - execute: function(callback, args) { - if (callback) callback.apply(this, args); - }, - - // Simple proxy to `Backbone.history` to save a fragment into the history. - navigate: function(fragment, options) { - Backbone.history.navigate(fragment, options); - return this; - }, - - // Bind all defined routes to `Backbone.history`. We have to reverse the - // order of the routes here to support behavior where the most general - // routes can be defined at the bottom of the route map. - _bindRoutes: function() { - if (!this.routes) return; - this.routes = _.result(this, 'routes'); - var route, routes = _.keys(this.routes); - while ((route = routes.pop()) != null) { - this.route(route, this.routes[route]); - } - }, - - // Convert a route string into a regular expression, suitable for matching - // against the current location hash. - _routeToRegExp: function(route) { - route = route.replace(escapeRegExp, '\\$&') - .replace(optionalParam, '(?:$1)?') - .replace(namedParam, function(match, optional) { - return optional ? match : '([^/?]+)'; - }) - .replace(splatParam, '([^?]*?)'); - return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); - }, - - // Given a route, and a URL fragment that it matches, return the array of - // extracted decoded parameters. Empty or unmatched parameters will be - // treated as `null` to normalize cross-browser behavior. - _extractParameters: function(route, fragment) { - var params = route.exec(fragment).slice(1); - return _.map(params, function(param, i) { - // Don't decode the search params. - if (i === params.length - 1) return param || null; - return param ? decodeURIComponent(param) : null; - }); - } - - }); - - // Backbone.History - // ---------------- - - // Handles cross-browser history management, based on either - // [pushState](http://diveintohtml5.info/history.html) and real URLs, or - // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) - // and URL fragments. If the browser supports neither (old IE, natch), - // falls back to polling. - var History = Backbone.History = function() { - this.handlers = []; - _.bindAll(this, 'checkUrl'); - - // Ensure that `History` can be used outside of the browser. - if (typeof window !== 'undefined') { - this.location = window.location; - this.history = window.history; - } - }; - - // Cached regex for stripping a leading hash/slash and trailing space. - var routeStripper = /^[#\/]|\s+$/g; - - // Cached regex for stripping leading and trailing slashes. - var rootStripper = /^\/+|\/+$/g; - - // Cached regex for detecting MSIE. - var isExplorer = /msie [\w.]+/; - - // Cached regex for removing a trailing slash. - var trailingSlash = /\/$/; - - // Cached regex for stripping urls of hash. - var pathStripper = /#.*$/; - - // Has the history handling already been started? - History.started = false; - - // Set up all inheritable **Backbone.History** properties and methods. - _.extend(History.prototype, Events, { - - // The default interval to poll for hash changes, if necessary, is - // twenty times a second. - interval: 50, - - // Are we at the app root? - atRoot: function() { - return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root; - }, - - // Gets the true hash value. Cannot use location.hash directly due to bug - // in Firefox where location.hash will always be decoded. - getHash: function(window) { - var match = (window || this).location.href.match(/#(.*)$/); - return match ? match[1] : ''; - }, - - // Get the cross-browser normalized URL fragment, either from the URL, - // the hash, or the override. - getFragment: function(fragment, forcePushState) { - if (fragment == null) { - if (this._hasPushState || !this._wantsHashChange || forcePushState) { - fragment = decodeURI(this.location.pathname + this.location.search); - var root = this.root.replace(trailingSlash, ''); - if (!fragment.indexOf(root)) fragment = fragment.slice(root.length); - } else { - fragment = this.getHash(); - } - } - return fragment.replace(routeStripper, ''); - }, - - // Start the hash change handling, returning `true` if the current URL matches - // an existing route, and `false` otherwise. - start: function(options) { - if (History.started) throw new Error("Backbone.history has already been started"); - History.started = true; - - // Figure out the initial configuration. Do we need an iframe? - // Is pushState desired ... is it available? - this.options = _.extend({root: '/'}, this.options, options); - this.root = this.options.root; - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - var fragment = this.getFragment(); - var docMode = document.documentMode; - var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); - - // Normalize root to always include a leading and trailing slash. - this.root = ('/' + this.root + '/').replace(rootStripper, '/'); - - if (oldIE && this._wantsHashChange) { - var frame = Backbone.$('