casbah
Version:
Contract Administration Site * Be Architecural Heroes *
668 lines (580 loc) • 21.9 kB
HTML
<style>
.picBox{ max-height:350px; max-width:100%;}
caption{ font-size:10pt; font-family:'segoe UI'; }
figure{ font-size:12pt; font-family:'segoe UI'; font-weight:bold; }
.border-not {border-color:black; border-style:solid; border-width:1px;}
.photo-frame-not{ padding-left:5px; padding-top:15px; border-color:black; border-style:solid; border-width:1px; }
.photo-frame{ padding-left:5px; padding-top:15px;}
.photo-text{ padding-left:5px; padding-top:15px;}
.photo-image {max-width:100%; padding-left:5px; float:left;}
.break-line {margins:auto;}
@media print {
.row {page-break-inside:avoid; !important;}
.control-joint {page-break-inside:auto; !important;}
}
</style>
<script type='text/javascript'>
///////////////////////
// Site visit report
// main object
var svr={};
// cache
svr.data={};
svr.change=function(field, valu, callback){
//console.log("SVR_ID", localStorage.getItem("svr_id"))
$.ajax({
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
data: $.param({
action:"SVR-CHANGE",
project_id:localStorage.getItem("project_id"),
svr_id:localStorage.getItem("svr_id"),
field:field,
valu:valu
}),
error: function(err){ console.log(err.message);},
success: function(result){ if (typeof callback =="function"){callback();}},
type:"POST",
url:"/uploads"
});
};
// text editor
svr.ed=new casbah.Editor();
svr.refresh=function(result, delta){
//console.log("SVR_ID", localStorage.getItem("svr_id"))
$.ajax({
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
data: $.param({
action:"SVR-SELECT",
project_id:localStorage.getItem("project_id"),
svr_id:localStorage.getItem("svr_id")
}),
error: function(err){ console.log(err.message);},
success: function(result){
console.log("SVR-SUCCESS");
svr.notes.render(result.svrs[0]);
svr.photos.render(result.svrs[0]);
},
type:"POST",
url:"/uploads"
});
};
svr.notes={};
svr.notes.insert=function(){
//this function is meant to be called from a context menu and without arguments
var caller=svr.notes.menu.menu("option","caller");
var sn=$(caller).attr("section_name");
var si=$(caller).attr("section_index");
var copy=$(caller).text();
console.log("INSERT:", sn);
//insert copy of text into section
svr.data[sn].splice(si, 0, copy);
svr.change(sn, svr.data[sn], svr.notes.render);
};
svr.notes.menu=$("#svr-notes-menu").menu();
svr.notes.menu.css("position","absolute", "width", "200px").hide();
svr.notes.edit=function(el){
svr.ed.text(el, function(){
var field=svr.ed.target_attr("section_name"); //eg. 'comments'
var index=svr.ed.target_attr("section_index");
var text=svr.ed.val();
svr.data[field][index]=text;
//svr.data[field][index]=; //eg. ['first comment','revised comment 2'...]
console.log("FIELD",field, " UPDATED TEXT:", text);
svr.change(field, svr.data[field], function(){
svr.ed.hide();
//refresh (server request and render) or just render cache for now...
svr.notes.render(svr.data);
});
});
};
svr.notes.ondragstart=function(el, ev){
//note that "Text" argument is required by iexplorer, "text/plain" won't work...
ev.dataTransfer.setData("Text",
"section_index:"+$(el).attr("section_index") + " " +
"section_name:"+$(el).attr("section_name")
);
};
svr.notes.ondrop=function(el, ev){
console.log("DROP...");
//var txt=ev.dataTransfer.getData("text/plain");
//note that "Text" argument is required by iexplorer...
var tx=ev.dataTransfer.getData("Text");
var a1=tx.indexOf("section_index:");
var a2=tx.indexOf("section_name:");
if ( a1 > -1 && a2 > -1){
//from row index
var fsi=tx.slice(a1+"section_index:".length).split(/\s+/)[0];
var fsn=tx.slice(a2+"section_name:".length).split(/\s+/)[0];
//to row index
var tsi=$(el).attr("section_index");
var tsn=$(el).attr("section_name");
//console.log("drag from:", fsi, fsn, " drop to:", tsi, tsn);
if (fsn == tsn){
//same section
casbah.array_fromindex_toindex(svr.data[tsn], fsi, tsi);
//save changes then callback render
svr.change(tsn, svr.data[tsn], svr.notes.render );
} else if (fsn != tsn) {
//different sections
//insert item at 'to' section
svr.data[tsn].splice(tsi, 0, svr.data[fsn][fsi]);
//remove item at 'from' section
svr.data[fsn].splice(fsi, 1);
//save change for 'to' sections, then save change for 'from' section then callback renderer
svr.change(tsn, svr.data[tsn], function(){ svr.change(fsn, svr.data[fsn], svr.notes.render);});
}
}
};
svr.notes.render=function(r){
//result from server or undefined
//r={generals:[...], comments:["comment 1", "comment 2",...],...}
// svr.notes.render called without argument means cache changed and server updated, no need to get result from server just use cache, otherwise update local cache with server results
if(typeof r == "undefined"){ r=svr.data;} else { svr.data=r;}
//reformat result to suit comments template
svr.data.rows=[].concat(
svr.notes.reformat(r.generals, "generals", 1, "General Notes"),
svr.notes.reformat(r.comments, "comments", 2, "Comments & Observations"),
svr.notes.reformat(r.issues_closed, "issues_closed", 3, "Closed Issues"),
svr.notes.reformat(r.issues, "issues", 4, "New and Ongoing Issues")
);
console.log("SVR data:", svr.data);
$("#svr-header-placeholder").html(svr.header.template({svr:r}));
$("#svr-titleblock-right-placeholder").html(svr.titleblock_right.template({svr:r}));
$("#svr-notes-placeholder").html(svr.notes.template({rows:svr.data.rows}));
}
svr.notes.reformat=function(section, section_name, section_num, section_title){
//console.log("reformat_notes:", section);
var rows=[{section_heading:true, section_name:section_name, txt:section_title, section_num:section_num, section_index:0}];
for (i in section){rows.push({
txt:section[i],
section_item:true,
section_name:section_name,
section_num:section_num,
section_index:Number(i)
});}
return rows;
};
svr.notes.remove=function(el){
//this function is meant to be called from a context menu and without arguments
var caller=svr.notes.menu.menu("option","caller");
var sn=$(caller).attr("section_name");
var si=$(caller).attr("section_index");
//delete note from section
svr.data[sn].splice(si, 1);
//save change for 'to' sections, then save change for 'from' section then callback renderer
svr.change(sn, svr.data[sn], svr.notes.render);
};
svr.notes.template=Handlebars.compile($("#svr-notes-template").html());
svr.notes.update=function(row, rowid){
console.log("comments update ROWID:\n", rowid, "ROW:\n",row)
};
/////////////////
// PHOTOS
svr.photos={}
svr.photos.ondrop=function(ev){
/***
https://msdn.microsoft.com/en-us/ie/ms536929(v=vs.94)
You must cancel the default action for ondragenter and ondragover in order for ondrop to fire. In the case of a div, the default action is not to drop. This can be contrasted with the case of an input type=text element, where the default action is to drop. In order to allow a drag-and-drop action on a div, you must cancel the default action by specifying window.event.returnValue=false in both the ondragenter and ondragover event handlers. Only then will ondrop fire.
https://stackoverflow.com/questions/2320069/jquery-ajax-file-upload
Re. processData:false solves 'append called on an object that does not implement interface FormData'
***/
//ev.preventDefault();
console.log("ONDROP...");
var fd=new FormData();
//load up form data with dropped files...
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
//console.log("name:", ev.dataTransfer.files[i].name);
//console.log("filetype:", ev.dataTransfer.files[i].type);
fd.append(ev.dataTransfer.files[i].name, ev.dataTransfer.files[i]);
}
fd.append("action","SVR-UPLOAD");
fd.append("project_id", localStorage.getItem("project_id"));
fd.append("svr_id",localStorage.getItem("svr_id"));
fd.append("upload_file",true);
$.ajax({
data:fd,
contentType:false,
error:function(err){console.log("Error uploading:",err);},
processData:false,
success:function(result){
console.log("Success uploading, refresh everything...");
svr.refresh();
},
type:"POST",
url:"/uploads"
});
};
svr.photos.formats={};
svr.photos.formats.filler=function(rx, i, row){
var keys=Object.keys(rx);
var vals=Object.values(rx);
var j=keys.indexOf(i)+1;
var len=keys.length;
var span;
//console.log("VALS:", vals);
while (j<len && svr.photos.cc<12){
//span=Number(vals[j].col.split("-")[2]); //col-xs-2 => 2
if(vals[j].available==true ){
if(vals[j].format=="landscape" && svr.photos.cc+5<=12) {
svr.photos.formats.landscape(rx, keys[j], row);
}
else if (vals[j].format=="portrait"&& svr.photos.cc+3<=12){
svr.photos.formats.portrait(rx, keys[j], row);}
}
j+=1;
}
svr.photos.cc=0; //assume row filled so reset column count
};
svr.photos.formats.landscape=function(rx, i, row){
row.push({
fig:true,
captions:rx[i].captions,
col:"col-xs-2",
index:svr.photos.index,
key:rx[i].key
});
row.push({img:rx[i].path, col:"col-xs-4", index:svr.photos.index});
svr.photos.cc+=6;
svr.photos.index+=1;
rx[i].available=false;
};
svr.photos.formats.portrait=function(rx, i, row){
row.push({
fig:true,
captions:rx[i].captions,
col:"col-xs-2",
index:svr.photos.index,
key:rx[i].key
})
row.push({img:rx[i].path, col:"col-xs-3", index:svr.photos.index});
svr.photos.cc+=5;
svr.photos.index+=1;
rx[i].available=false;
};
svr.photos.formats.wide=function(rx, i, row){
row.push({
fig:true,
captions:rx[i].captions,
col:"col-xs-2",
index:svr.photos.index,
key:rx[i].key
})
row.push({img:rx[i].path, col:"col-xs-10", index:svr.photos.index});
svr.photos.cc+=12;
svr.photos.index+=1;
rx[i].available=false;
};
svr.photos.layouts={};
svr.photos.layouts.azEasy=function(svrdata){
//reformat result for photos template...
//svrdata.xdata = {img01:{caption:"", date:"", format:"", path:"..."}, ...}
svr.photos.index=0;
svr.photos.cc=0; //column counter
var rx=svrdata.xdata;
var rows=[], row=[];
for (var i in rx){
if(rx[i].available==true){
if(rx[i].format=="landscape") {
if (svr.photos.cc > 6) {rows.push(row); row=[]; svr.photos.cc=0;}
svr.photos.formats.landscape(rx, i, row);
} else if (rx[i].format=="portrait") {
if (svr.photos.cc > 7) {rows.push(row); row=[]; svr.photos.cc=0;}
svr.photos.formats.portrait(rx, i, row);
} else if (rx[i].format=="wide") {
if (svr.photos.cc > 1) {rows.push(row); row=[]; svr.photos.cc=0;}
svr.photos.formats.wide(rx, i, row);
}
};
};
rows.push(row);
return rows;
}
svr.photos.layouts.azTight=function(svrdata){
//svrdata.xdata = {img01:{caption:"", date:"", format:"", path:"..."}, ...}
svr.photos.index=0; //image counter
svr.photos.cc=0; //column counter, 12 columns is 1 row in bootstrap
var rx=svrdata.xdata;
var rows=[],row=[];
for (var i in rx){
if(rx[i].available==true){
if(rx[i].format=="landscape") {
svr.photos.formats.landscape(rx, i, row);
svr.photos.formats.filler(rx, i, row);
rows.push(row); row=[];
} else if (rx[i].format=="portrait") {
svr.photos.formats.portrait(rx, i, row);
svr.photos.formats.filler(rx, i, row);
rows.push(row); row=[];
} else if (rx[i].format=="wide") {
svr.photos.formats.wide(rx, i, row);
svr.photos.formats.filler(rx, i, row);
rows.push(row); row=[];
}
};
};
return rows;
}
svr.photos.render=function(svrdata){
//layout photos in svrdata.xdata to photorows as required for handlebar template
//svr.data.photorows=svr.layouts.azEasy(svrdata)
svr.data.photorows=svr.photos.layouts.azTight(svrdata);
$("#svr-photos-placeholder").html(svr.photos.template({rows:svr.data.photorows}));
}
svr.photos.template=Handlebars.compile($("#svr-photos-template").html());
/////////////
// titleblocks
svr.titleblock_left={};
svr.titleblock_right={};
svr.titleblock_right.edit=function(el){
svr.ed.text(el, function(){
var field=$(el).attr("field"); //eg. 'date'
var text=svr.ed.val();
svr.data[field]=text;
//svr.data[field][index]=; //eg. ['first comment','revised comment 2'...]
console.log("FIELD",field, " UPDATED TEXT:", text);
svr.change(field, svr.data[field], function(){
svr.ed.hide();
//refresh (server request and render) or just render cache for now...
//svr.titleblock.render(svr.data);
$("#svr-titleblock-right-placeholder").html(svr.titleblock_right.template({svr:svr.data}));
});
});
};
svr.titleblock_left.refresh=function(){
//console.log("PROJECT SELECT");
//var svr_id=localStorage.getItem("svr_id");
$.ajax({
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
data: $.param({
action:"PROJECT-SELECT",
//select info for this project only...
project_id:localStorage.getItem("project_id")
}),
error: function(err){ console.log("Error", err);},
success: function(result){
$("#svr-titleblock-left-placeholder").html(svr.titleblock_left.template(result));
},
type:"POST",
url:"/uploads"
});
};
svr.titleblock_left.template=Handlebars.compile($("#svr-titleblock-left").html());
svr.titleblock_right.template=Handlebars.compile($("#svr-titleblock-right").html());
svr.header={};
svr.header.template=Handlebars.compile($("#svr-header").html());
svr.header.edit=function(el){
svr.ed.text(el, function(){
var field=$(el).attr("field"); //eg. 'title'
var text=svr.ed.val();
svr.data[field]=text;
//svr.data[field][index]=; //eg. ['first comment','revised comment 2'...]
console.log("FIELD",field, " UPDATED TEXT:", text);
svr.change(field, svr.data[field], function(){
svr.ed.hide();
//refresh (server request and render) or just render cache for now...
//svr.titleblock.render(svr.data);
$("#svr-header-placeholder").html(svr.header.template({svr:svr.data}));
});
});
};
/**
///////////////////////////////////////
// Render page header and titleblock
$.get("client/header.html", function(htm){
//console.log('tbh loaded:', htm);
if (typeof svr.header=="undefined"){svr.header={};}
svr.header.template=Handlebars.compile(htm);
$("#svr-header").html(svr.header.template({doc_type:"Site Visit Review"}));
});
*/
///////////////////////////////////////
// Render page
svr.titleblock_left.refresh(); //renders titleblock_left IE project info
svr.refresh(); //renders header, notes & titleblock_right IE report info
</script>
<!-- PAGE STARTS HERE -->
<div class="container">
<div id="svr-header-placeholder">placeholder</div>
<div class="row">
<div id="svr-titleblock-left-placeholder" class="col-xs-6">Project part of titleblock</div>
<div id="svr-titleblock-right-placeholder" class="col-xs-6">Report part of titleblock</div>
</div>
<p class="row row__" >This report is a general review of progress and construction activities on site. Architectural Work was visually reviewed on a random basis for general conformity with Architectural Contract Documents prepared by this firm. Refer also to Mechanical and Electrical field reports issued separately.
</p>
<br><br>
<div id="svr-notes-placeholder" class="marz">notes-placeholder</div>
<div id="svr-photos-placeholder"
class="marz">photos-placeholder</div>
<br><br>
</div>
<!-- TEMPLATES -->
<template id='svr-header' type="text/x-handlebars-template">
{{#with svr}}
<div id="backup-logo"></div>
<img style="width:100px; float:left;"
src="uploads/logo.png"
alt=""
onerror="$('#backup-logo').load('client/casbah_logo.html');">
<h2 style="float:right;"
field="title"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.header.edit(this)">{{title}}</h2>
<div style="clear:both"></div>
{{/with}}
</template>
<template id='svr-titleblock-left' type="text/x-handlebars-template">
{{#each projects}}
<div class="row row__">
<strong class="col-xs-4">Project Id:</strong>
<p class="col-xs-8">{{project_id}}</p>
</div>
<div class="row row">
<strong class="col-xs-4">Project Name:</strong>
<p class="col-xs-8">{{project_name}}</p>
</div>
<div class="row row">
<strong class="col-xs-4">Project Address:</strong>
<p class="col-xs-8">{{address}}</p>
</div>
<div class="row row">
<strong class="col-xs-4">Contractor:</strong>
<p class="col-xs-8">{{contractor}}</p>
</div>
<div class="row row">
<strong class="col-xs-4">Permit No:</strong>
<p id="svr-permit" class="col-xs-8">{{permit}}</p>
</div>
{{/each}}
</template>
<template id='svr-titleblock-right' type="text/x-handlebars-template">
{{#with svr}}
<div class="row row__">
<strong class="col-xs-4">Report Id:</strong>
<p class="col-xs-8">{{svr_id}}</p>
</div>
<div class="row row">
<strong id="svr-date" class="col-xs-4">Date of Visit:</strong>
<p class="col-xs-8"
title="click to edit, then double-click to save..."
field="date"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.titleblock_right.edit(this)">{{date}}</p>
</div>
<div class="row row">
<strong class="col-xs-4">Date Issued:</strong>
<p class="col-xs-8"
title="click to edit, then double-click to save..."
field="date_issued"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.titleblock_right.edit(this)">{{date_issued}}</p>
</div>
<div class="row row">
<strong class="col-xs-4">Reviewer:</strong>
<p class="col-xs-8"
title="click to edit, then double-click to save..."
field="author"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.titleblock_right.edit(this)">{{author}}</p>
</div>
<div class="row row">
<strong class="col-xs-4"
title="click to edit, then double-click to save..."
field="misc_key"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.titleblock_right.edit(this)">{{#if misc_key}}{{misc_key}}{{else}}--{{/if}}</strong>
<p class="col-xs-8"
title="click to edit, then double-click to save..."
field="misc_valu"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.titleblock_right.edit(this)">{{#if misc_valu}}{{misc_valu}}{{else}}--{{/if}}</p>
</div>
{{/with}}
</template>
<template id='svr-notes-template' type="text/x-handlebars-template">
{{#each rows as |row rowindex|}}
{{#if row.section_heading}}
<div class="row marz"
section_name="{{row.section_name}}"
section_num="{{row.section_num}}"
section_index="{{row.section_index}}"
ondrop="svr.notes.ondrop(this, event)">
<h3 class="col-xs-1 marz" >{{row.section_num}}</h3>
<h3 class="col-xs-10 border-left marz"
section_name="{{row.section_name}}"
section_index="{{row.section_index}}"
oncontextmenu="casbah.showMenu(svr.notes.menu, event)">{{row.txt}}</h3>
<h3 class="col-xs-1 marz" ></h3>
</div>
{{/if}}
{{#if row.section_item}}
<div class="row marz"
section_name="{{row.section_name}}"
section_num="{{row.section_num}}"
section_index="{{row.section_index}}"
draggable="true"
ondragstart="svr.notes.ondragstart(this, event)"
ondragover="event.preventDefault()"
ondrop="svr.notes.ondrop(this, event)">
<p class="col-xs-1 marz">{{row.section_num}}.{{plusOne row.section_index}}</p>
<p class="col-xs-10 border-left marz"
title="click to edit, then double-click to save"
section_name="{{row.section_name}}"
section_num="{{row.section_num}}"
section_index="{{row.section_index}}"
onmouseenter="$(this).addClass('highlite')"
onmouseleave="$(this).removeClass('highlite')"
onclick="svr.notes.edit(this)"
oncontextmenu="casbah.showMenu(svr.notes.menu, event)">{{row.txt}}</p>
<p class="col-xs-1 marz"></p>
</div>
{{/if}}
{{/each}}
</template>
<template id='svr-photos-template' type="text/x-handlebars-template">
<div class="row marz"
title="Drop photos here to add to report..."
ondragenter="event.preventDefault();$(this).addClass('highlite');"
ondragover="event.preventDefault();"
ondragleave="event.preventDefault();$(this).removeClass('highlite');"
ondrop="svr.photos.ondrop(event);"
oncontextmenuNOT="casbah.showMenu(svr.photo.menu, event)">
<h3 class="col-xs-1 marz" >5.0</h3>
<h3 class="col-xs-10 border-left marz">Photos</h3>
<h3 class="col-xs-1 marz" > </h3>
</div>
{{#each rows as |row ri|}}
<div class="row marz">
{{#each row as |item ii|}}
{{#if item.img}}
<div class="{{item.col}} photo-frame border" onclick="svr.photos.onclick(this)">
<img class="photo-image" src="{{item.img}}">
</div>
{{/if}}
{{#if item.fig}}
<div class="{{item.col}} photo-text border"
title="{{item.key}}">
<figure style="display:inline;">5.{{plusOne item.index}}</figure>
<caption>{{array item.captions}}</caption>
<p>{{item.dateTaken}}</p>
</div>
{{/if}}
{{/each}}
</div>
<div class="control-joint"></div>
{{/each}}
</template>
<!-- MENUS -->
<div id="svr-notes-menu" onmouseleave="svr.notes.menu.hide()" class="hide9999">
<p onclick="svr.notes.remove()">Delete</p>
<p onclick="svr.notes.edit(svr.notes.menu.menu('option', 'caller'))">Edit</p>
<p onclick="svr.notes.insert()">Insert</p>
<p onclick="svr.notes.menu.hide()">Exit</p>
<p onclick="svr.refresh();svr.notes.menu.hide()">Refresh</p>
</div>