Creating Pinterest like layout in ASP.NET webform - asp.net-mvc-4

I am creating a Pinterest like layout in ASP.NET webform and I followed following two tutorials
Creating Pinetrest like Layout in ASP.NET
How to use Masonry
However, I made changes in the first tutorial based on second and I am getting below output
Clearly, this isn't what I was looking. The gap between two rows and columns is high.
Below is my code:
<
style type="text/css">
body
{
background-color:#EEEEEE;
}
#imgLoad
{
position:fixed;
bottom:0;
left:40%;
display:none;
}
.item {
width: 220px;
margin: 10px;
float: left;
background-color:honeydew;
}
</style>
<div id="container" class="transitions-enabled infinite-scroll clearfix">
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<div class="item">
<img src="<%# Eval("Url") %>" />
<p><%# Eval("Description") %></p>
</div>
</ItemTemplate>
</asp:Repeater>
</div>
How do I fix it?
I believe this is related to the height, may be the row height of repeater control takes the highest among the column.
I did tried to do it with ASp.NET MVC
Controller
IEnumerable<Product> model = ProductRepository.GetData(1, 25);
return View(model);
View
<!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
<style type="text/css">
.item {
width: 220px;
margin: 5px;
float: left;
background-color:honeydew;
}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script>
<script src="Scripts/Mansory.js" type="text/javascript"></script>
<script type="text/javascript">
var $container = $('#container');
$container.imagesLoaded(function () {
$container.masonry({
itemSelector: '.item',
columnWidth: 100,
isAnimated: false
});
});
</script>
#foreach (var item in Model) {
<div class="item">
<img src="#(item.Url)" />
#Html.DisplayFor(modelItem => item.Description)
</div>
}
but same result
EDIT 1
I have changed my script to
<script type="text/javascript">
$(function () {
var $container = $('#container');
$container.masonry({
itemSelector: '.item',
columnWidth: 240,
isAnimated: false
});
});
</script>
and code to
#foreach (var item in Model) {
<div id="container">
<div class="item">
<img src="#(item.Url)" />
#Html.DisplayFor(modelItem => item.Description)
</div>
</div>
}
but same result

Ok, a few things:
You have spelled "Masonry" wrong when initializing the script
Put a div with Id "container" around your items list
In stead of using $container.imagesLoaded make the whole javascript section run when page is loaded
Like this:
$(function(){
var $container = $('#container');
$container.masonry({
itemSelector: '.item',
columnWidth: 240,
isAnimated: false
});
});
Then it should work.

This is small library which implements Pinterest layout. Filling the grid goes from left to right. Count of columns can be customized in config for each resolution. Columns adaptive changes on resize. Image can be at up or down of pin.
https://github.com/yury-egorenkov/pins-grid

Related

Google Script: Editors can't save data from sidebar in the sheet

The HTML sidebar get user input and save it in sheet:
with sheet's owner, it works fine (the data from sidebar are
correctly saved in the sheet)
with others sheet editors the data from sidebar is NOT been saved in the sheet.
Owner and users at same domain
Apps Script Dashboard shows NO error
I would be very grateful for any help on how I can fix this issue.
SIDEBAR SOURCE:
<!DOCTYPE html>
<html>
<!-- Style should be in the head -->
<style>
.info {
margin: 5px;
width: 95%;
padding: 2px;
font-size: 14px;
font-weight: bold;
}
.msg {
margin: 5px;
width: 95%;
padding: 2px;
font-size: 13px;
}
.container,
.buttons {
margin: 5px;
width: 95%;
padding: 2px;
font-size: 13px;
}
</style>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
</head>
<body>
<div class="msg">
<p align="justify">Marque todos os motivos que levaram ao indeferimento do requerimento</p>
</div>
<div class="info">
<p id="stdname"></p>
<p id="stamp"></p>
<p id="applied"></p>
<p id="eRange"></p>
</div>
<div class="container">
<?!=rngValid?>
</div>
<div class="buttons">
<p>
<button class="action" id="action" onclick="saveReasons();">Salvar motivos</button>
</p>
<br>
</div>
<!-- These should be in the head. They should be there prior to page loading-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/mdehoog/Semantic-UI/6e6d051d47b598ebab05857545f242caf2b4b48c/dist/semantic.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.1.8/semantic.min.js"></script>
<script>
var stamp = <?= stamp ?>;
var to = <?= to ?>;
var subject = <?= subject ?>;
var stdName = <?= stdName ?>;
var apply = <?= apply ?>;
var eRange = <?= eRange ?>;
document.getElementById("stdname").innerHTML = stdName;
document.getElementById("stamp").innerHTML = stamp;
document.getElementById("applied").innerHTML = apply;
document.getElementById("eRange").innerHTML = eRange;
function saveReasons() {
var selected = [];
//get all checkboxes
var checkboxes = document.getElementsByClassName("ui.checkbox");
console.log("checkboxes length: "+ checkboxes.length);
console.log("checkboxes: "+ checkboxes);
$("input:checkbox[name=sel]:checked").each(function() {
selected.push($(this).val());
$(this).prop( "checked", false );
})
console.log("selected: "+ selected);
console.log("eRange: "+ eRange);
google.script.run
.withFailureHandler(() => {console.log("Error running script")})
.process(selected,stdName, apply, eRange);
}
</script>
</body>
</html>
DEV TOOLS CONSOLE:
Net state changed from IDLE to BUSY 386795301-warden_bin_i18n_warden.js:67
Net state changed from BUSY to IDLE userCodeAppPanel:36
Error running script
On your client-side code there is the following
google.script.run
.withFailureHandler(() => {console.log("Error running script")})
.process(selected,stdName, apply, eRange);
The problem with the above code is that it give very low help to debug the main problem.
To get a meaningful error message replace
.withFailureHandler(() => {console.log("Error running script")})
to
.withFailureHandler((error) => {console.log('%s, %s',error.message, error.stack)})

Why won't my click event in Vue.js fire?

I'm trying to use Vue's on-click directive on a div but it does not fire as intended. When I click the .demo class nothing happens; I should get a 'test clicked' in the console. I don't see any errors in the console, so I don't know what am I doing wrong.
var app = new Vue({
el: '#app',
data: {
title: 'This is the first vue app',
name: 'ALRAZY',
link: 'http://google.com',
attachRed: false
},
methods: {
changeLink: function() {
this.link = 'http://apple.com'
}
}
})
body {
font-family: 'Trykker', 'Spectral SC', serif;
}
h1,h2,h3,h4,h5,h6 {
font-family: 'Spectral SC', serif;
}
p {
font-family: 'Trykker', serif;
}
.demo {
width: 100px;
height: 100px;
background-color: grey;
display: inline-block;
margin: 10px;
}
.red {
background: red;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css?family=Spectral+SC:400,400i,500,500i,600|Trykker" rel="stylesheet" >
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div class="container">
<div class="row">
<div id="app">
<h1>{{ title }} </h1>
<input type="text" v-model="name" name="">
<p> {{ name }}</p>
<button onclick="changeLink" class="btn btn-danger btn-lg">click to change</button>
<a :href="link" target="_blank">Link</a>
<!-- style-vue -->
<div class="demo" onclick="attachRed = !attachRed" :class="{red: attachRed}"></div>
<p v-if="show">You can see me!!!</p>
<p v-else>Do You see me??</p>
</div>
</div>
</div>
You're using the standard onclick attribute which is not going to trigger vue methods. Try using v-on:click="changeLink()" or #click="changeLink()" (I'm not entirely sure if the second is the correct syntax). That should resolve your problem.
Similarly, you can do #click="attachRed = !attachRed".

polymer 2 dom-repeat does not reflect when item is removed

I have a very simple application in Polymer 2. I have a static list of items which is displayed with dom-repeat.
I have a button for delete, on click i could see the item is removed from the list. But the dom-repeat is not refreshed. Its still the old code.
Can someone help how to reflect the changes on the screen:
code snippet is below:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/iron-list/iron-list.html">
<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">
<link rel="import" href="../../bower_components/app-storage/app-localstorage/app-localstorage-document.html">
<link rel="import" href="../../bower_components/polymer/lib/elements/dom-repeat.html">
<dom-module id="todo-list">
<template>
<style>
:host {
display: block;
}
.ibtn {
float: right;
}
.item {
width: 500px;
}
paper-icon-button {
color: grey;
}
.list-todo {
height: 700px;
text-align: left;
-webkit-text-fill-color: brown;
}
.list-todo ul li {
width: 500px;
font-family: sans-serif;
font-size: 20px;
}
.list-todo .btn-set {
float: right;
}
</style>
<!-- <app-localstorage-document key="app-store" data="{{todos}}"></app-localstorage-document> -->
<div class="list-todo">
<template is="dom-repeat" items="{{todos}}">
<ul>
<li>
<iron-icon icon="icons:arrow-forward"></iron-icon>
{{item}}
<span class="btn-set">
<paper-icon-button icon="create"></paper-icon-button>
<paper-icon-button icon="delete" on-tap="deleteTodo"></paper-icon-button>
<paper-icon-button icon="star"></paper-icon-button>
<span>
</li>
</ul>
</template>
</div>
</template>
<script>
/**
* #customElement
* #polymer
*/
class TodoList extends Polymer.Element {
static get is() { return 'todo-list'; }
static get properties() {
return {
todos: {
type: Object,
value: ["Build the code", "Check Sonar Issues", "Release the APP", "Deploy in Production", "Get Feedback"],
notify: true
}
};
}
populateTodoList() {
return this.todos;
}
deleteTodo(item) {
this.todos.splice(item.model.index, 1);
this.todos.flush;
}
}
window.customElements.define(TodoList.is, TodoList);
</script>
</dom-module>
Javascript splice removes the item from the array, but it does not mean the change is reflected in polymer shadowRoot.
To do this you have to use polymer array mutation methods. See this.
In your case will be:
this.splice('todos', item.model.index, 1);
Also, this.todos.flush; is not needed.
Another thing I detected (not related with this), are you sure you want to have <ul> inside the repeat? This created a bunch of list with just one item inside each list.
I suggest you to do this
<div class="list-todo">
<ul>
<template is="dom-repeat" items="{{todos}}">
<li>
<iron-icon icon="icons:arrow-forward"></iron-icon>
{{item}}
<span class="btn-set">
<paper-icon-button icon="create">create</paper-icon-button>
<paper-icon-button icon="crystal-icons:lens" on-tap="deleteTodo">delete</paper-icon-button>
<paper-icon-button icon="star">star</paper-icon-button>
<span>
</li>
</template>
</ul>
</div>
This way you just create one list.

dojo borderlayout show all the content , flicker then redraw correctly

I copied an example from the dojo site using Borderlayout. However, when I load in the browser , the entire data is shown for all the section . Then after a few second the content is refersh and the data is displayed correctly.
here is code that i copied . Thanks for your help
<head>
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.3/dijit/themes/tundra/tundra.css">
<style type="text/css">
body, html { font-family:helvetica,arial,sans-serif; font-size:90%; }
</style>
<style type="text/css">
html, body { width: 100%; height: 100%; margin: 0; } #borderContainer
{ width: 100%; height: 100%; }
</style>
</head>
<body class="tundra ">
<div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="true"
liveSplitters="true" id="borderContainer">
<div dojoType="dijit.layout.ContentPane" splitter="true" region="leading"
style="width: 100px;">
Hi
</div>
<div dojoType="dijit.layout.ContentPane" splitter="true" region="center">
Hi, I'm center
</div>
</div>
</body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.3/dojo/dojo.xd.js"
djConfig="parseOnLoad: true">
</script>
<script type="text/javascript">
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.layout.BorderContainer");
</script>
<!-- NOTE: the following script tag is not intended for usage in real
world!! it is part of the CodeGlass and you should just remove it when
you use the code -->
<script type="text/javascript">
dojo.addOnLoad(function() {
if (window.pub) {
window.pub();
}
});
</script>
This looks a bit upside down : you should put your javascripts in the head section and load the dojo libraries in first place. That's not your problem though.
What happens is that when the page loads, dojo loads all the modules that you "dojo.require", then parses all your tags containing the attribute "dojoType" and processes them for rendering, and this takes time.
So the flickering that you're seeing is the difference between the page before and after the widgets are parsed.
You should add a preloader div and hide it once the page is parsed (see this example).
This is what it would look like for your example :
<html>
<head>
<title>Preloader example</title>
<!– every Dijit component needs a theme –>
<link rel="stylesheet"
href="http://o.aolcdn.com/dojo/1.4/dijit/themes/soria/soria.css">
<style type="text/css">
#preloader,
body, html {
width:100%; height:100%; margin:0; padding:0;
}
#preloader {
width:100%; height:100%; margin:0; padding:0;
background:#fff
url(’http://search.nj.com/media/images/loading.gif’)
no-repeat center center;
position:absolute;
z-index:999;
}
#borderContainer {
width:100%; height:100%;
}
</style>
<!– load Dojo, and all the required modules –>
<script src="http://o.aolcdn.com/dojo/1.4/dojo/dojo.xd.js"></script>
<script type="text/javascript">
var hideLoader = function(){
dojo.fadeOut({
node:"preloader",
onEnd: function(){
dojo.style("preloader", "display", "none");
}
}).play();
}
dojo.addOnLoad(function(){
// after page load, load more stuff (spinner is already spinning)
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dojo.parser");
// notice the second onLoad here:
dojo.addOnLoad(function(){
dojo.parser.parse();
hideLoader();
});
});
</script>
</head>
<body class="soria">
<div id="preloader"></div>
<div dojoType="dijit.layout.BorderContainer" id="borderContainer" design="sidebar" gutters="true" liveSplitters="true">
<div dojoType="dijit.layout.ContentPane" splitter="true" region="leading" style="width: 100px;">Hi</div>
<div dojoType="dijit.layout.ContentPane" splitter="true" region="center">I'm Center</div>
</div>
</body>
</html>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.3/dojo/dojo.xd.js"></script>
if(dojo.isIE){
addEvent(window, 'load', function(event) {
dojo.parser.parse();
});
}else{
dojo.addOnLoad(function(){
dojo.addOnLoad(function(){
dojo.parser.parse();
});
});
}
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
disable parseOnLoad and manually add event to parse widgets for ie.

Dojo Tabs as Custom Widgets

I've created a number of widgets that are loaded as tab panes in a tabcontainer. Everything seems to work well, except I'm getting different results when using my custom widgets vs. creating everything in markup (tab content isn't taking up the whole page, 3rd party widgets aren't behaving properly, etc). This all leads me to think that I might not be creating my widgets correctly. The trick seems to be that they are not interpreted as contentpanes.
I am using dojo 1.3.2.
The following markup, for example, works great:
<body class="soria" style="overflow-y: hidden;">
<!-- Container for whole page -->
<div dojoType="dijit.layout.BorderContainer" design="headline" id="borderContainer" style="height:100%;width:100%;">
<!-- Header container -->
<div dojoType="dijit.layout.ContentPane" region="top">
Title
</div>
<!-- Left side -->
<!--<div dojoType="dijit.layout.ContentPane" region="left" id="addressContainer" splitter="true" style="width:100%; background-color: #F0F8FF;">-->
<div dojoType="dijit.layout.ContentPane" region="left" id="addressContainer" splitter="true">
<div style="text-align: center;">
<!-- address -->
<span id="addressInstructions">Address Contents</span>
</div>
</div>
<!-- Tabs -->
<div dojoType="dijit.layout.TabContainer" id="tabs" region="center" tabPosition="top">
<div dojoType="dijit.layout.ContentPane" id="tab1" title="map">
<p>Change map to:</p>
<div id="divMapList"></div>
<div style="padding: 10px 10px 10px 10px;">
<div id="divMap" dojoAttachPoint="divMap" style="width:300px;height:300px;border:1px solid #000;display:block;margin-left:auto;margin-right:auto;"></div>
</div>
</div>
<div dojoType="dijit.layout.ContentPane" id="tab2" title="other">
Some text in here.
</div>
</div>
</div>
</body>
Looking at the resulting HTML code, I can see that the tab itself has the following class: "dijitContentPane dijitTabPane dijitTabContainerTop-child dijitTabContainerTop-dijitContentPane dijitVisible".
Below is the HTML of one of my custom widgets:
<div>
<div style="text-align: center; padding: 5px 5px 5px 5px;">
<div>${i18nStrings.mapChangeMap}:</div>
<div dojoAttachPoint="divMapSelection"></div>
<div style="padding: 10px 10px 10px 10px;">
<div id="divMap" dojoAttachPoint="divMap" style="width:300px;height:300px;border:1px solid #000;display:block;margin-left:auto;margin-right:auto;"></div>
</div>
</div>
And here is part of the custom widget JS (I can provide more code if necessary):
dojo.declare("xxxx.Widget.Map", [dijit._Widget, dijit._Templated],
{
//Specify the path of the HTML file that sets the look of the widget
templatePath: dojo.moduleUrl("xxxx", "widget/templates/Map.html"),
//The strings used to populate standard text. Populated in postMixInProperties
i18nStrings: null,
//The widget's root div identifier. Value overwritten in postMixInProperties
id:"tabMap",
//The tab's title. Value overwritten in postMixInProperties
title: "",
//True to activate the tab. False otherwise.
activate: false,
//The map & current layer
map: null,
currentLayer: null,
//Sets up some basic properties
postMixInProperties: function() {
this.inherited(arguments);
var _1 = dojo.i18n.getLocalization("dijit", "loading", this.lang);
this.loadingMessage = dojo.string.substitute(this.loadingMessage, _1);
this.errorMessage = dojo.string.substitute(this.errorMessage, _1);
if (!this.href && this.srcNodeRef && this.srcNodeRef.innerHTML) {
this.isLoaded = true;
}
this.i18nStrings = dojo.i18n.getLocalization("IndyVIP","applicationStrings");
this.title = this.i18nStrings.mapTabTitle;
},
</div>
I am adding the widget to the container by creating the widget, then adding it as a child to the tabcontainer.
This results in the following class assigned to my tab: "dijitTabPane dijitTabContainerTop-child dijitVisible".
Does anyone know what I am doing wrong?
Thank you!
Emmster
UPDATE:
I have decided to try inheriting from dijit.layout.ContentPane & dijit._Templated. I am still having problems with that though, the main one being that if I add my widget to a TabContainer, I get the error "node is null" on line 4467 of dojo.js.uncompressed.js, but the tab seems to work just fine IF it's the active tab starting out. If I add the widget to a page that only contains the widget, it works fine without errors.
See the code below that gives me the expected results, but causes that one "node is null" error.
HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Test</title>
<script type="text/javascript">
var djConfig = {
parseOnLoad: true,
useXDomain: true,
debugAtAllCosts: true,
baseUrl: './',
modulePaths: {IndyVIP: 'js'}
};
</script>
<link rel="Stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/1.5/js/dojo/dijit/themes/soria/soria.css" />
<script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=1.5"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.layout.TabContainer");
dojo.require("IndyVIP.Widget.Map");
dojo.addOnLoad(init);
function init() {
var tab = new IndyVIP.Widget.Map({title: 'Map'});
var tabContainer = dijit.byId('tabs');
tabContainer.addChild(tab);
}
</script>
</head>
<body class="soria" style="overflow-y: hidden;">
<!-- Container for whole page -->
<div dojoType="dijit.layout.BorderContainer" design="headline" id="borderContainer" style="height:100%;width:100%;">
<!-- Header container -->
<div dojoType="dijit.layout.ContentPane" region="top">
Title
</div>
<!-- Left side -->
<div dojoType="dijit.layout.ContentPane" region="left" id="addressContainer" splitter="true">
<div style="text-align: center;">
<!-- address -->
<span id="addressInstructions">Address Contents</span>
</div>
</div>
<!-- Tabs-->
<div dojoType="dijit.layout.TabContainer" id="tabs" region="center" tabPosition="top">
<!-- uncommenting the following results in map not acting the way it should -->
<!--
<div dojoType="dijit.layout.ContentPane" id="tab2" title="other">
Some text in here.
</div>-->
</div>
</div>
</body>
</html>
Map.js:
dojo.provide("IndyVIP.Widget.Map");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit._Templated");
dojo.require("dijit._Widget");
dojo.declare("IndyVIP.Widget.Map", [dijit.layout.ContentPane, dijit._Templated],
{
map: null,
//Specify the path of the HTML file that sets the look of the widget
templatePath: dojo.moduleUrl("IndyVIP", "widget/templates/Map.html"),
//The tab's title. Value overwritten in postMixInProperties
title: "",
widgetsInTemplate: true,
startup: function() {
this.map = new esri.Map('divMap');
dojo.connect(this.map, 'onLayerAdd', this, this.onLayerAdd);
var layer = new esri.layers.ArcGISTiledMapServiceLayer('http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer');
this.map.addLayer(layer);
},
onLayerAdd: function() {
//Create symbol
var lineColor = new dojo.Color('black');
var lineSymbol = new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID,lineColor,1);
var pointColor = new dojo.Color('red');
var pointSymbol = new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE,12,lineSymbol,pointColor);
//Create point
var x = 4.92;
var y = 50.625;
var point = new esri.geometry.Point(x,y,this.map.spatialReference);
//Create template
var template = new esri.InfoTemplate('A title','Some content');
//Create graphic
var graphic = new esri.Graphic(point,pointSymbol,null,template);
//Add graphic to map
this.map.graphics.clear();
this.map.graphics.add(graphic);
}
});
And finally, here's the widget's HTML template:
<div>
<div id="divMap" dojoAttachPoint="divMap" style="width:300px;height:300px;border:1px solid #000;display:block;margin-left:auto;margin-right:auto;"></div>
</div>
I very much appreciate all of your suggestions so far. I feel like I am missing something elementary, but I can't quite figure it out.
Emmster
The issue I was having when inheriting from dijit.layout.ContentPane was that my top div in my widget template did not specify dojoAttachPoint="containerNode". Doing this took care of the "node is null" error I was getting.
I am still having issues with the map not behaving properly, but this seems related to the tab not being visible initially as dante hinted.
UPDATE:
The other error I was having (the map not acting as it should) was due to the widget's template HTML having items above the map in the widget HTML as in my first example above (map.html). In the second example I provided, I took that extra stuff out, and that fixes the problem.
Inheriting from the ContentPane and having dojoAttachPoint="containerNode" on the root node seems to be mandatory for the tabs. This helped me to resolve a similar issue.