From 706819e28847441a633fd3f3fbb67caac8dc1de5 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 26 Apr 2012 16:05:43 +0100 Subject: [PATCH 01/15] [test][xs]: remove element after test. --- test/view-graph.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/view-graph.test.js b/test/view-graph.test.js index 3f65611a..3b015f12 100644 --- a/test/view-graph.test.js +++ b/test/view-graph.test.js @@ -49,5 +49,5 @@ test('dates in graph view', function () { }); $('.fixtures').append(view.el); - // view.remove(); + view.remove(); }); From c9a3471b579ea5ec490b858115b55372fa660528 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 26 Apr 2012 17:22:03 +0100 Subject: [PATCH 02/15] [view/explorer][xs]: remove (already obsolete) router and routing code from explorer. --- src/view.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/view.js b/src/view.js index ee3ffe62..157576b4 100644 --- a/src/view.js +++ b/src/view.js @@ -225,9 +225,6 @@ my.DataExplorer = Backbone.View.extend({ this.updateNav(this.pageViews[0].id); } - this.router = new Backbone.Router(); - this.setupRouting(); - this.model.bind('query:start', function() { my.notify('Loading data', {loader: true}); }); @@ -239,7 +236,6 @@ my.DataExplorer = Backbone.View.extend({ var qs = my.parseHashQueryString(); qs.reclineQuery = JSON.stringify(self.model.queryState.toJSON()); var out = my.getNewHashForQueryString(qs); - // self.router.navigate(out); }); this.model.bind('query:fail', function(error) { my.clearNotifications(); @@ -299,21 +295,6 @@ my.DataExplorer = Backbone.View.extend({ this.el.find('.header').append(facetViewer.el); }, - setupRouting: function() { - var self = this; - // Default route -// this.router.route(/^(\?.*)?$/, this.pageViews[0].id, function(queryString) { -// self.updateNav(self.pageViews[0].id, queryString); -// }); -// $.each(this.pageViews, function(idx, view) { -// self.router.route(/^([^?]+)(\?.*)?/, 'view', function(viewId, queryString) { -// self.updateNav(viewId, queryString); -// }); -// }); - this.router.route(/.*/, 'view', function() { - }); - }, - updateNav: function(pageName) { this.el.find('.navigation li').removeClass('active'); this.el.find('.navigation li a').removeClass('disabled'); From 81dc7eab2b6660778e51fa8ebd66a44331894fad Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 26 Apr 2012 17:22:36 +0100 Subject: [PATCH 03/15] [app][m]: proper start page for app (welcome, etc) plus introduce backbone routing to support this. --- app/index.html | 50 ++++++++++++++++++++++++++++++++++++++++++++------ app/js/app.js | 46 +++++++++++++++++++++++++++++++--------------- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/app/index.html b/app/index.html index 28f148c3..757d3978 100644 --- a/app/index.html +++ b/app/index.html @@ -124,6 +124,50 @@ +
+
+
+
+

Welcome to the Recline Data Explorer

+

The Data Explorer is an application for exploring + and working with data built in pure javascript and html. In basic operation it's much like a spreadsheet - though it's + feature set is a little different. In particular, the Data + Explorer provides: +

    +
  • Data grid / spreadsheet
  • +
  • Data editing including programmatic data transformation in javascript
  • +
  • Visualizations includes graphs and maps
  • +
  • Import and export from a variety of sources including online sources such as online Excel and CSV files, Google docs and + the DataHub and offline sources like CSV files on your local machine.
  • +
  • Use online or offline - because the app is built in pure javascript and html you can use it anywhere there's a modern web browser. Using offline is as easy and downloading this web page to your local machine.
  • +
+
+
+

View the demo

+

Take a look at a local demo dataset.

+

View the demo dataset »

+
+
+

Read the tutorial

+

Take a look at the tutorial for using the data explorer:

+ Read the tutorial » +
+
+

Import some data

+

Starting working with some data straight away. You can import some data using the menu at the top right of this page.

+
+
+
+
+
+ +
+
+
+
+ + + - -
-
-
-
-
diff --git a/app/js/app.js b/app/js/app.js index e6c87123..342cfa8d 100755 --- a/app/js/app.js +++ b/app/js/app.js @@ -2,7 +2,6 @@ jQuery(function($) { var app = new ExplorerApp({ el: $('.recline-app') }) - Backbone.history.start(); }); var ExplorerApp = Backbone.View.extend({ @@ -15,6 +14,12 @@ var ExplorerApp = Backbone.View.extend({ this.el = $(this.el); this.explorer = null; this.explorerDiv = $('.data-explorer-here'); + _.bindAll(this, 'viewExplorer', 'viewHome'); + + this.router = new Backbone.Router(); + this.router.route('', 'home', this.viewHome); + this.router.route(/explorer/, 'explorer', this.viewExplorer); + Backbone.history.start(); var state = recline.View.parseQueryString(window.location.search); if (state) { @@ -30,18 +35,34 @@ var ExplorerApp = Backbone.View.extend({ } } var dataset = null; - if ( - (state.dataset || state.url) && - // special cases for demo / memory dataset - !(state.url === 'demo' || state.backend === 'memory')) - { - dataset = recline.Model.Dataset.restore(state); - } else { + // special cases for demo / memory dataset + if (state.url === 'demo' || state.backend === 'memory') { dataset = localDataset(); } - this.createExplorer(dataset, state); + else if (state.dataset || state.url) { + dataset = recline.Model.Dataset.restore(state); + } + if (dataset) { + this.createExplorer(dataset, state); + } }, + viewHome: function() { + this.switchView('home'); + }, + + viewExplorer: function() { + this.router.navigate('explorer'); + this.switchView('explorer'); + }, + + switchView: function(path) { + $('.backbone-page').hide(); + var cssClass = path.replace('/', '-'); + $('.page-' + cssClass).show(); + }, + + // make Explorer creation / initialization in a function so we can call it // again and again createExplorer: function(dataset, state) { @@ -63,12 +84,7 @@ var ExplorerApp = Backbone.View.extend({ this._setupPermaLink(this.dataExplorer); this._setupEmbed(this.dataExplorer); - // HACK (a bit). Issue is that Backbone will not trigger the route - // if you are already at that location so we have to make sure we genuinely switch - if (reload) { - // this.dataExplorer.router.navigate('graph'); - // this.dataExplorer.router.navigate('', true); - } + this.viewExplorer(); }, _setupPermaLink: function(explorer) { From 9c0c4dfae3618c999e2db5d3f8b600fbd47c2a41 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 26 Apr 2012 21:20:59 +0100 Subject: [PATCH 04/15] [#46,view-grid,view-transform-dialog][s]: switch ColumnTransform to work off bootstrap modal. --- css/grid.css | 4 ---- src/view-grid.js | 10 ++-------- src/view-transform-dialog.js | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/css/grid.css b/css/grid.css index aeb9984e..88f0b134 100644 --- a/css/grid.css +++ b/css/grid.css @@ -210,10 +210,6 @@ div.data-table-cell-content-numeric > a.data-table-cell-edit { * Transform Dialog *********************************************************/ -#expression-preview-tabs .ui-tabs-nav li a { - padding: 0.15em 1em; -} - textarea.expression-preview-code { font-family: monospace; height: 5em; diff --git a/src/view-grid.js b/src/view-grid.js index dc616713..bafc08ae 100644 --- a/src/view-grid.js +++ b/src/view-grid.js @@ -104,19 +104,13 @@ my.Grid = Backbone.View.extend({ }, showTransformColumnDialog: function() { - var $el = $('.dialog-content'); - util.show('dialog'); var view = new my.ColumnTransform({ model: this.model }); view.state = this.tempState; view.render(); - $el.empty(); - $el.append(view.el); - util.observeExit($el, function() { - util.hide('dialog'); - }); - $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); + this.el.append(view.el); + view.el.modal(); }, showTransformDialog: function() { diff --git a/src/view-transform-dialog.js b/src/view-transform-dialog.js index 12f83872..c3f62d35 100644 --- a/src/view-transform-dialog.js +++ b/src/view-transform-dialog.js @@ -76,12 +76,13 @@ my.DataTransform = Backbone.View.extend({ // View (Dialog) for doing data transformations (on columns of data). my.ColumnTransform = Backbone.View.extend({ - className: 'transform-column-view', + className: 'transform-column-view modal fade in', template: ' \ -
\ - Functional transform on column {{name}} \ + \ -
\ + \ - \
\ - \ - \
\ ', events: { diff --git a/vendor/jquery-ui-1.8.14.custom.min.js b/vendor/jquery-ui-1.8.14.custom.min.js deleted file mode 100755 index d1949182..00000000 --- a/vendor/jquery-ui-1.8.14.custom.min.js +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * jQuery UI 1.8.14 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI - */ -(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.14", -keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); -b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, -"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", -function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, -outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); -return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= -0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted= -false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); -;/* - * jQuery UI Draggable 1.8.14 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Draggables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== -"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= -this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;this.helper= -this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}); -this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);d.ui.ddmanager&&d.ui.ddmanager.dragStart(this,a);return true}, -_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b= -false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration, -10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});d.ui.ddmanager&&d.ui.ddmanager.dragStop(this,a);return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle|| -!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&& -a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent= -this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"), -10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"), -10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[a.containment=="document"?0:d(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a.containment=="document"?0:d(window).scrollTop()-this.offset.relative.top-this.offset.parent.top, -(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!= -"hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"), -10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+ -this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&& -!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.leftg[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=b.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1]:this.originalPageY;h=g?!(h-this.offset.click.topg[3])?h:!(h-this.offset.click.topg[2])?e:!(e-this.offset.click.left=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e Date: Thu, 26 Apr 2012 22:38:45 +0100 Subject: [PATCH 13/15] [addendum][xs]: remove reference to jquery-ui from app. --- app/built.html | 1 - app/index.html | 1 - 2 files changed, 2 deletions(-) diff --git a/app/built.html b/app/built.html index 334c47f8..6fb0ed16 100644 --- a/app/built.html +++ b/app/built.html @@ -32,7 +32,6 @@ - diff --git a/app/index.html b/app/index.html index 757d3978..42666af3 100644 --- a/app/index.html +++ b/app/index.html @@ -32,7 +32,6 @@ - From 9d084fc46ca401fe48f006040dc70f243548ca02 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 26 Apr 2012 22:40:34 +0100 Subject: [PATCH 14/15] [addendum][xs]: ... and remove jquery-ui ref from tests. --- test/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/test/index.html b/test/index.html index f9f8e783..8e25198c 100644 --- a/test/index.html +++ b/test/index.html @@ -10,7 +10,6 @@ - From e4aa4e546c8906b918faa98a093c8cda9976d449 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 26 Apr 2012 22:45:28 +0100 Subject: [PATCH 15/15] [#82,flash/notify,refactor][m]: convert notify / flash messages to use events rather than call to a central function. --- src/view-grid.js | 18 ++++-- src/view-map.js | 5 +- src/view-transform-dialog.js | 6 +- src/view.js | 104 +++++++++++++++++++---------------- 4 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/view-grid.js b/src/view-grid.js index 8660fb5a..30e81a33 100644 --- a/src/view-grid.js +++ b/src/view-grid.js @@ -74,6 +74,7 @@ my.Grid = Backbone.View.extend({ hideColumn: function() { self.hideColumn(); }, showColumn: function() { self.showColumn(e); }, deleteRow: function() { + var self = this; var doc = _.find(self.model.currentDocuments.models, function(doc) { // important this is == as the currentRow will be string (as comes // from DOM) while id may be int @@ -81,9 +82,9 @@ my.Grid = Backbone.View.extend({ }); doc.destroy().then(function() { self.model.currentDocuments.remove(doc); - my.notify("Row deleted successfully"); + self.trigger('recline:flash', {message: "Row deleted successfully"}); }).fail(function(err) { - my.notify("Errorz! " + err); + self.trigger('recline:flash', {message: "Errorz! " + err}); }); } }; @@ -91,9 +92,14 @@ my.Grid = Backbone.View.extend({ }, showTransformColumnDialog: function() { + var self = this; var view = new my.ColumnTransform({ model: this.model }); + // pass the flash message up the chain + view.bind('recline:flash', function(flash) { + self.trigger('recline:flash', flash); + }); view.state = this.tempState; view.render(); this.el.append(view.el); @@ -286,6 +292,7 @@ my.GridRow = Backbone.View.extend({ }, onEditorOK: function(e) { + var self = this; var cell = $(e.target); var rowId = cell.parents('tr').attr('data-id'); var field = cell.parents('td').attr('data-field'); @@ -293,12 +300,13 @@ my.GridRow = Backbone.View.extend({ var newData = {}; newData[field] = newValue; this.model.set(newData); - my.notify("Updating row...", {loader: true}); + this.trigger('recline:flash', {message: "Updating row...", loader: true}); this.model.save().then(function(response) { - my.notify("Row updated successfully", {category: 'success'}); + this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'}); }) .fail(function() { - my.notify('Error saving row', { + this.trigger('recline:flash', { + message: 'Error saving row', category: 'error', persist: true }); diff --git a/src/view-map.js b/src/view-map.js index 97d49499..5b2f3433 100644 --- a/src/view-map.js +++ b/src/view-map.js @@ -281,7 +281,6 @@ my.Map = Backbone.View.extend({ // Each feature will have a popup associated with all the document fields. // _add: function(docs){ - var self = this; if (!(docs instanceof Array)) docs = [docs]; @@ -316,13 +315,13 @@ my.Map = Backbone.View.extend({ var msg = 'Wrong geometry value'; if (except.message) msg += ' (' + except.message + ')'; if (wrongSoFar <= 10) { - my.notify(msg,{category:'error'}); + self.trigger('recline:flash', {message: msg, category:'error'}); } } } else { wrongSoFar += 1 if (wrongSoFar <= 10) { - my.notify('Wrong geometry value',{category:'error'}); + self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'}); } } return true; diff --git a/src/view-transform-dialog.js b/src/view-transform-dialog.js index 83ab53a8..6c69766b 100644 --- a/src/view-transform-dialog.js +++ b/src/view-transform-dialog.js @@ -93,11 +93,11 @@ my.ColumnTransform = Backbone.View.extend({ var funcText = this.el.find('.expression-preview-code').val(); var editFunc = costco.evalFunction(funcText); if (editFunc.errorMessage) { - my.notify("Error with function! " + editFunc.errorMessage); + this.trigger('recline:flash', {message: "Error with function! " + editFunc.errorMessage}); return; } this.el.modal('hide'); - my.notify("Updating all visible docs. This could take a while...", {persist: true, loader: true}); + this.trigger('recline:flash', {message: "Updating all visible docs. This could take a while...", persist: true, loader: true}); var docs = self.model.currentDocuments.map(function(doc) { return doc.toJSON(); }); @@ -107,7 +107,7 @@ my.ColumnTransform = Backbone.View.extend({ function onCompletedUpdate() { totalToUpdate += -1; if (totalToUpdate === 0) { - my.notify(toUpdate.length + " documents updated successfully"); + self.trigger('recline:flash', {message: toUpdate.length + " documents updated successfully"}); alert('WARNING: We have only updated the docs in this view. (Updating of all docs not yet implemented!)'); self.remove(); } diff --git a/src/view.js b/src/view.js index ee6a5135..ac4a2f28 100644 --- a/src/view.js +++ b/src/view.js @@ -209,6 +209,7 @@ my.DataExplorer = Backbone.View.extend({ // these must be called after pageViews are created this.render(); this._bindStateChanges(); + this._bindFlashNotifications(); // now do updates based on state (need to come after render) if (this.state.get('readOnly')) { this.setReadOnly(); @@ -220,15 +221,15 @@ my.DataExplorer = Backbone.View.extend({ } this.model.bind('query:start', function() { - my.notify('Loading data', {loader: true}); + self.notify({message: 'Loading data', loader: true}); }); this.model.bind('query:done', function() { - my.clearNotifications(); + self.clearNotifications(); self.el.find('.doc-count').text(self.model.docCount || 'Unknown'); - my.notify('Data loaded', {category: 'success'}); + self.notify({message: 'Data loaded', category: 'success'}); }); this.model.bind('query:fail', function(error) { - my.clearNotifications(); + self.clearNotifications(); var msg = ''; if (typeof(error) == 'string') { msg = error; @@ -242,7 +243,7 @@ my.DataExplorer = Backbone.View.extend({ } else { msg = 'There was an error querying the backend'; } - my.notify(msg, {category: 'error', persist: true}); + self.notify({message: msg, category: 'error', persist: true}); }); // retrieve basic data like fields etc @@ -252,7 +253,7 @@ my.DataExplorer = Backbone.View.extend({ self.model.query(self.state.get('query')); }) .fail(function(error) { - my.notify(error.message, {category: 'error', persist: true}); + self.notify({message: error.message, category: 'error', persist: true}); }); }, @@ -368,6 +369,57 @@ my.DataExplorer = Backbone.View.extend({ }); } }); + }, + + _bindFlashNotifications: function() { + var self = this; + _.each(this.pageViews, function(pageView) { + pageView.view.bind('recline:flash', function(flash) { + self.notify(flash); + }); + }); + }, + + // ### notify + // + // Create a notification (a div.alert in div.alert-messsages) using provided + // flash object. Flash attributes (all are optional): + // + // * message: message to show. + // * category: warning (default), success, error + // * persist: if true alert is persistent, o/w hidden after 3s (default = false) + // * loader: if true show loading spinner + notify: function(flash) { + var tmplData = _.extend({ + message: '', + category: 'warning' + }, + flash + ); + var _template = ' \ +
× \ + {{message}} \ + {{#loader}} \ +   \ + {{/loader}} \ +
'; + var _templated = $.mustache(_template, tmplData); + _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages')); + if (!flash.persist) { + setTimeout(function() { + $(_templated).fadeOut(1000, function() { + $(this).remove(); + }); + }, 1000); + } + }, + + // ### clearNotifications + // + // Clear all existing notifications + clearNotifications: function() { + var $notifications = $('.recline-data-explorer .alert-messages .alert'); + $notifications.remove(); } }); @@ -609,45 +661,5 @@ my.FacetViewer = Backbone.View.extend({ }); -// ## notify -// -// Create a notification (a div.alert in div.alert-messsages) using provide messages and options. Options are: -// -// * category: warning (default), success, error -// * persist: if true alert is persistent, o/w hidden after 3s (default = false) -// * loader: if true show loading spinner -my.notify = function(message, options) { - if (!options) options = {}; - var tmplData = _.extend({ - msg: message, - category: 'warning' - }, - options); - var _template = ' \ -
× \ - {{msg}} \ - {{#loader}} \ -   \ - {{/loader}} \ -
'; - var _templated = $.mustache(_template, tmplData); - _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages')); - if (!options.persist) { - setTimeout(function() { - $(_templated).fadeOut(1000, function() { - $(this).remove(); - }); - }, 1000); - } -}; - -// ## clearNotifications -// -// Clear all existing notifications -my.clearNotifications = function() { - var $notifications = $('.recline-data-explorer .alert-messages .alert'); - $notifications.remove(); -}; - })(jQuery, recline.View);