I'm trying to display a preview of some images when a user selects them before he uploads them, but I'm new to vue and I can't figure out how to apply the background image url style to each div (it must be with background image), here is my UploadComponent so far:
<template>
<div class="content">
<h1>{{ title }}</h1>
<div class="btn-container">
<label class="button" for="images">Select Images</label>
<input type="file" id="images" name="images[]" multiple="multiple" #change="selectFiles"/>
</div>
<br><br>
<div class="block">
<h3>Selected images</h3>
<div v-for="image in images" v-bind:key="image" class="image-result" v-bind:style="{ backgroundImage: 'url(' + image + ')' }">
</div>
</div>
<div class="btn-container">
<button type="button" class="submit-btn" v-on:click="uploadImages">Submit Images</button>
</div>
<br><br>
<div class="block">
<h3>Uploaded images</h3>
<div class="files-preview">
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'UploadComponent',
data: () => ({
title: 'Select your images first and then, submit them.',
images: []
}),
methods:{
selectFiles(event){
var selectedFiles = event.target.files;
var i = 0;
for (i = 0; i < selectedFiles.length; i++){
this.images.push(selectedFiles[i]);
}
for (i = 0; i < this.images.length; i++){
let reader = new FileReader(); //instantiate a new file reader
reader.addEventListener('load', function(){
console.log(this);
}.bind(this), false); //add event listener
reader.readAsDataURL(this.images[i]);
}
},
uploadImages(){
const data = new FormData();
data.append('photos', this.images);
axios.post('url')
.then(res => {
console.log(res);
});
}
}
}
</script>
The divs are being created but I'm missing the part where I assign the background image, how can I solve this?
You can't just put a File directly as the parameter for background-image. You started using FileReader but didn't do anything with the result.
Here's what I would do below, I converted your images array into a list of objects that have a file property and a preview property, just to keep the file and preview linked together.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: "#app",
data: () => {
return {
title: 'Select your images first and then, submit them.',
images: []
}
},
methods: {
selectFiles(event) {
var selectedFiles = event.target.files;
for (var i = 0; i < selectedFiles.length; i++) {
let img = {
file: selectedFiles[i],
preview: null
};
let reader = new FileReader();
reader.addEventListener('load', () => {
img.preview = reader.result;
this.images.push(img);
});
reader.readAsDataURL(selectedFiles[i]);
}
},
uploadImages() {
const data = new FormData();
data.append('photos', this.images.map(image => image.file));
axios.post('url')
.then(res => {
console.log(res);
});
}
}
});
.image-result {
width: 100px;
height: 100px;
background-size: contain;
background-repeat: no-repeat;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" class="content">
<h1>{{ title }}</h1>
<div class="btn-container">
<label class="button" for="images">Select Images</label>
<input type="file" id="images" name="images[]" multiple="multiple" #change="selectFiles" />
</div>
<br><br>
<div class="block">
<h3>Selected images</h3>
<div id="target-photos">
</div>
<div v-for="image in images" class="image-result" v-bind:style="{ backgroundImage: 'url(' + image.preview + ')' }">
</div>
</div>
<div class="btn-container">
<button type="button" class="submit-btn" v-on:click="uploadImages">Submit Images</button>
</div>
<br><br>
<div class="block">
<h3>Uploaded images</h3>
<div class="files-preview">
</div>
</div>
</div>
Related
I have created a partial view and I added the rendering in Index page. I have a button (See More) and every time I am calling controller with ajax and append the new data in the screen.
The problem is when i have no more results to show and a user keeps pressing the button I append multiple times "no results found".
Is there a way to prevent this?
My Index:
<div class="sectionMain" id="stopscroll">
#foreach (var item in Model)
{
if (item.restDetails != null && item.restDetails.Count() != 0)
{
#*<label class="sectionLabels" style="padding-left: 3%;">Restaurants</label>*#
#Html.Hidden("nextItems")
<div id="test" class="sectionSeeMore">
#Html.Partial("_RestaurantDetails", item.restDetails)
</div>
<div class="row">
<div class="col-5"></div>
<div class="col-2" style="text-align:center;">
<button id="btnsubmit" class="buttonCategory" onclick="callseemore(this)" style="font-family:'Fredoka-One';height: 36px;">See More</button>
</div>
<div class="col-5"></div>
</div>
}
break;
}
</div>
<script>
function callseemore(a){
var postcode = $("#inputPostCode").val();
var category = "";
var slides = document.getElementsByClassName("buttonCategoryMain");
for (var i = 0; i < slides.length; i++) {
if (slides.item(i).className == "buttonCategoryMain activebtnMain") {
category = slides.item(i).value;
}
}
var offerButton = document.getElementById("sidebarOfferBtn").classList.value;
var isofferparam = "False";
if (offerButton == "activebtn") {
isofferparam = "True";
}
var nextItems= $("#nextItems").val();
$.ajax({
url: "#Url.Action("RestaurantPaging", "Home")",
type: 'POST',
data: ({ categoryparam: category, postcodeparam: postcode, nextitemparam: nextItems, isOfferparam: isofferparam}),
cache: false,
success: function (result, status, xhr) {
debugger;
$('#tblSeeMore').append(result);
var asf = xhr.getResponseHeader("nextItems");
document.getElementById("nextItems").value =asf ;
}
})
}
</script>
PartialView:
#model IEnumerable<Restaurants.Models.RestaurantDetail>
#if (Model.Count() == 0)
{
<h3 style="text-align:center;font-family:'Fredoka-One'">No Results Found</h3>
}
else
{
<div style="display:flex;flex-flow:row wrap;" id="tblSeeMore">
#foreach (var itemRest in Model)
{
<div class="newRestaurantsSection" id="newRestaurantsSection">
<div class="newRestaurantBox">
#if (itemRest.RestaurantImgPath != null)
{
<img class="restImage" src="#Url.Content(itemRest.RestaurantImgPath)">
}
else
{
<img class="restImage" src="~/Content/Assets/Images/no_image.png" />
}
<div class="restMethods">
#if (itemRest.Pickup)
{
<span style="display: inline-block; border-right: 1px solid lightgrey;padding-right:3px;">
<img class="restMethodImg" src="~/Content/Assets/Images/takeaway.png" />
#*<i class="fas fa-box fa-2x"></i>*#
</span>
}
#if (itemRest.Delivery)
{
<span style="display: inline-block; border-right: 1px solid lightgrey;padding-right:3px;">
#*<i class="fas fa-motorcycle fa-2x"></i>*#
<img class="restMethodImg" src="~/Content/Assets/Images/delivery.png" />
</span>
}
#if (itemRest.OnlinePayments)
{
#*<i class="fas fa-credit-card fa-2x"></i>*#
<img class="restMethodImg" src="~/Content/Assets/Images/visa.png" />
}
</div>
<div class="row restLogoNameKitchenType">
<div class="col-xs-1" style="flex-grow: 0;">
#if (itemRest.Logo != null)
{
<img class="restLogo rounded-circle" src="#Url.Content(itemRest.Logo)">
}
else
{
<img class="restLogo rounded-circle" src="~/Content/Assets/Images/no_image.png" />
}
</div>
<div class="col-xs-9 restKitchenTypeContent">
<label class="restName">#itemRest.RestaurantName</label>
<br />
#{ string kitchenTypeFull = "";}
#foreach (var kitchenType in itemRest.RestaurantCategories)
{
kitchenTypeFull += "," + #kitchenType.Category.Description;
}
#if (kitchenTypeFull.Length > 0)
{
kitchenTypeFull = kitchenTypeFull.TrimStart(new Char[] { ',' });
<label>#kitchenTypeFull</label>
}
</div>
</div>
<div class="row restAddressPhoneOffer">
<div class="col-md-12">
<div class="row">
<div>
<i class="fas fa-map-marker-alt"></i>
</div>
<div class="col" style="margin-left:-20px">
#itemRest.Address1,<br /> #itemRest.Area, #itemRest.PostalCode<br /> #itemRest.City
</div>
</div>
<div class="row">
<div>
<i class="fas fa-phone-alt"></i>
</div>
<div class="col" style="margin-left:-20px">
#itemRest.PhoneNumber
</div>
<div>
#if (itemRest.Offer)
{
<img style="width:60px;" class="restOfferImg" src="~/Content/Assets/Images/gift.png" />
}
else
{
<img style="width:60px;height:60px;" src="~/Content/Assets/Images/no_image _blank.png" />
}
</div>
</div>
</div>
<div class="col-md-12" style="margin-top:10px;margin-bottom:10px;padding-left:0px;padding-right:10px;">
#if (itemRest.MenuLink != null && itemRest.TableLink != null)
{
<div class="rowMenuTableLink">
<button class="rowMenuTableLinkM">
#Html.Raw(itemRest.MenuLink)
</button>
<button class="rowMenuTableLinkT">
#Html.Raw(itemRest.TableLink)
</button>
</div>
}
else if (itemRest.MenuLink == null && itemRest.TableLink == null)
{
<div class="rowTableLink" style="height:44px">
</div>
}
else
{
if (itemRest.MenuLink != null)
{
<div class="col-md-12 rowMenuLink">
<button class="restMenuResrBtns">
#Html.Raw(itemRest.MenuLink)
</button>
</div>
}
if (itemRest.TableLink != null)
{
<div class="col-md-12 rowTableLink">
<button class="restMenuResrBtns">
#Html.Raw(itemRest.TableLink)
</button>
</div>
}
}
</div>
</div>
</div>
</div>
}
</div>
#*#Html.ActionLink("See More", "RestaurantPaging", "Home", new { JSONModel = Json.Encode(Model) }, null)*#
}
<script type="text/javascript">
$(document).ready(function () {
var el = document.getElementsByClassName("newRestaurantBox");
var sidebar = document.getElementById('scrollableSidebar').style.display;
var sidebarclasses = document.getElementById('scrollableSidebar').classList.value;
var newRestaurantsView = document.querySelectorAll("[id='newRestaurantsSection']");
for (var i = 0, ilen = el.length; i < ilen; i++) {
if (sidebarclasses != "sectionSidebar sectionSidebarMin" && sidebar != "none") {
$(newRestaurantsView[i]).addClass('newRestsSectionSidebar');
$(newRestaurantsView[i]).removeClass('newRestaurantsSection');
}
else {
$(newRestaurantsView[i]).addClass('newRestaurantsSection');
$(newRestaurantsView[i]).removeClass('newRestsSectionSidebar');
}
}
})
</script>
print screen example:
You just have to remove the previously appended "No Results Found" and show whatever your post method gives as a response. i.e
Create a separate div inside #tblSeeMore which will contain "No Results Found"
<div id="divNoResults">
</div>
and change your ajax post call to this:
$.ajax({
url: "#Url.Action("RestaurantPaging", "Home")",
type: 'POST',
data: ({ categoryparam: category, postcodeparam: postcode, nextitemparam: nextItems, isOfferparam: isofferparam}),
cache: false,
success: function (result, status, xhr) {
debugger;
if(result == "No Results Found"){
$('#divNoResults').empty();
$('#divNoResults').append(result);
}
else
$('#tblSeeMore').append(result);
//rest of the code
}
})
Or you can just change the display of #divNoResults based on the result from the post method
<div id="divNoResults" style="display:none">
<p>No Results Found</p>
</div>
$.ajax({
url: "#Url.Action("RestaurantPaging", "Home")",
type: 'POST',
data: ({ categoryparam: category, postcodeparam: postcode, nextitemparam: nextItems, isOfferparam: isofferparam}),
cache: false,
success: function (result, status, xhr) {
debugger;
if(result == "No Results Found"){
$('#divNoResults').show();
}
else
$('#tblSeeMore').append(result);
//rest of the code
}
})
So, I have been trying to use google-maps and fill my data with marker and show info-window.
For reference see img
But, my goal is to make the page responsive also, i have got many thing done but making infowindow resposnive is a prob.
My goal is to show info-window full screen upon click event. But currently its a stupid.
See img for reference
So, how to make my infowindow to show full screen upon click.
Thanks and appreciate your help.
EDIT:
my code
import React, { Component } from 'react';
import { Map, InfoWindow, Marker, GoogleApiWrapper } from 'google-maps-react';
import DRINKING_WATER_PUMP from './pins/strop3.png'
import ROOFTOP from './pins/strop2.png'
import IRRIGATION_PUMP from './pins/strop1.png'
import PATVAN from './pins/strop5patvan.png'
import MINIGRID from './pins/strop4.png'
import farmer from './pins/user.png'
import axios from 'axios'
import config from './config.js'
import Swal from 'sweetalert2'
const mapStyles = {
width: '100%',
height: '100%',
position:'relative',
display: 'flex',
flexFlow: 'row nowrap',
justifyContent: 'center',
};
class MapList extends Component{
constructor(props) {
super(props);
this.markersRendered = false;
}
shouldComponentUpdate(nextProps, nextState) {
if (JSON.stringify(this.props.places) === JSON.stringify(nextProps.places) && this.markersRendered) {
return false;
}
this.markersRendered = true;
return true;
}
render() {
return (
<span>
{
this.props.places.map((marker, index) => {
let assetType=marker.assetType
let icon={
url:'' ,
anchor: new this.props.google.maps.Point(12,23),
origin: new this.props.google.maps.Point(0,0),
scaledSize: new this.props.google.maps.Size(20,20)
}
switch(assetType){
case 'PATVAN':icon.url=PATVAN; break;
case 'MINIGRID':icon.url=MINIGRID; break;
case 'IRRIGATION_PUMP':icon.url=IRRIGATION_PUMP; break;
case 'DRINKING_WATER_PUMP':icon.url=DRINKING_WATER_PUMP; break;
case 'ROOFTOP':icon.url=ROOFTOP; break;
default: break;
}
return (
<Marker {...this.props} key={index} assetId={marker.assetId} assetType={assetType} icon={icon} position={{lat: parseFloat(marker.lat), lng: parseFloat(marker.lng)}} />
)
})
}
</span>
)
}
}
class MapContainer extends Component {
constructor(props){
super(props)
this.state = {
showingInfoWindow: false, //Hides or the shows the infoWindow
activeMarker: {}, //Shows the active marker upon click
selectedPlace: {'owner':{}} //Shows the infoWindow to the selected place upon a marker
};
this.onMarkerClick=this.onMarkerClick.bind(this)
this.onClose=this.onClose.bind(this)
}
onMarkerClick(props, marker, e){
let url;
switch(props.assetType){
case 'PATVAN':url=config.patvan; break;
case 'MINIGRID':url=config.minigrid; break;
case 'IRRIGATION_PUMP':url=config.irrigation; break;
case 'DRINKING_WATER_PUMP':url=config.drinkingwater; break;
case 'ROOFTOP':url=config.rooftop; break;
default: break;
}
axios({
url:url,
method:'POST',
data:{
assetId:props.assetId
},
headers:{
'Content-Type': 'application/json'
}
}).then((res)=>{
if(res.data.data!==null){
let data=res.data.data
data['assetType']=props.assetType
this.setState({
selectedPlace: data,
activeMarker: marker,
showingInfoWindow: true
});
}
else if(res.data.error!==undefined){
if(res.data.error.errorCode===153){
window.location.href='../login.html?redirect=dashboard';
}
else{
Swal({
type: 'error',
title: 'Oops...',
text: res.data.error.errorMsg,
})
}
}
})
}
onClose(props) {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
});
}
}
render() {
return (
<Map id="map"
mapTypeControl={false}
gestureHandling= {'greedy'}
zoomControl= {false}
streetViewControl={false}
fullscreenControl={false}
google={this.props.google}
zoom={5}
style={mapStyles}
initialCenter={{
lat: 21.5937,
lng: 78.9629
}}
>
<MapList google={this.props.google} places={this.props.datapins} onClick={this.onMarkerClick} />
<InfoWindow className="infoWindowCard"
pixelOffset={new this.props.google.maps.Size(185,310)}
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
// disableAutoPan={true}
>
<div style={{'overflow':'hidden'}}>
{this.state.selectedPlace.owner.image!=='NA'&&this.state.selectedPlace.owner.image!==null?(
<img src={this.state.selectedPlace.owner.image} alt='farmer' className="infoWindowImg"/>
):(
<img src={farmer} alt='farmer' className="infoWindowImg"/>
)}
<h4 className="infoWindowName" style={{'maxWidth': '262px'}}> {this.state.selectedPlace.owner.name} </h4>
<h6 className="infoWindowName"> Customer ID: {this.state.selectedPlace.id} </h6>
<div>
<ul className="infoWindowDetail">
<li>
<div className="row">
<div className="col-md-2">
<i className="fa fa-calendar" aria-hidden="true"></i>
</div>
<div className="col-md-10">
<span>Installed on {this.state.selectedPlace.dateOfInstallation}</span>
</div>
</div>
</li>
<li>
<div className="row">
<div className="col-md-2">
<i className="fa fa-map-marker" aria-hidden="true"></i>
</div>
<div className="col-md-10">
<span>{this.state.selectedPlace.owner.block}</span>
<p>{this.state.selectedPlace.owner.district}, {this.state.selectedPlace.owner.state} </p>
</div>
</div>
</li>
<li>
<div className="row">
<div className="col-md-2">
<i className="fa fa-tint" aria-hidden="true"></i>
</div>
<div className="col-md-10">
{ this.state.selectedPlace.assetType==='IRRIGATION_PUMP' &&
<div>
<span>Irrigation Pump <b>{this.state.selectedPlace.pumpCapacity} {this.state.selectedPlace.powerType} {this.state.selectedPlace.pumpType}</b></span>
<p>Solar Panel Capacity {this.state.selectedPlace.panelRating} </p>
</div>
}
{ this.state.selectedPlace.assetType==='MINIGRID' &&
<span>Minigrid <b>{this.state.selectedPlace.assetName} </b></span>
}
{ this.state.selectedPlace.assetType==='PATVAN' &&
<div>
<span>Patvan <b>{this.state.selectedPlace.pumpCapacity} {this.state.selectedPlace.powerType} {this.state.selectedPlace.pumpType}</b></span>
<p>Solar Panel Capacity {this.state.selectedPlace.panelRating} </p>
</div>
}
{ this.state.selectedPlace.assetType==='DRINKING_WATER_PUMP' &&
<div>
<span>Drinking Water Pump <b>{this.state.selectedPlace.pumpCapacity} {this.state.selectedPlace.powerType} {this.state.selectedPlace.pumpType}</b></span>
<p>Solar Panel Capacity {this.state.selectedPlace.panelRating} </p>
</div>
}
{ this.state.selectedPlace.assetType==='ROOFTOP' &&
<div>
<span>Rooftop</span>
<p>Solar Panel Capacity {this.state.selectedPlace.panelRating} </p>
</div>
}
</div>
</div>
</li>
</ul>
<div className="portal">
<span>Visit portal</span><a target="_blank" rel="noopener noreferrer" href={ "../all_rms/rmspage.html?ID=" + this.state.selectedPlace.id } ><i className="fa fa-external-link-square" aria-hidden="true"></i></a>
</div>
</div>
</div>
</InfoWindow>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: 'MYAPI'
})(MapContainer);
I have a testbed for CropperJS - I have included a working sample. It displays your camera, and when you click take a snapshot, it copied the current image to the canvas. However, I cannot get the following to work:
When the image is cropped, I need to put the cropped image in the canvas instead of the image control.
I need to move the upload an image from actually clicking on the image to clicking the upload button.
<link href="Styles/bootstrap/v4.1.2/bootstrap.min.css" rel="stylesheet" />
<link href="Styles/cropper.css" rel="stylesheet" />
<link href="Styles/Site.css" rel="stylesheet" />
<div class="row">
<div class="col">
<video playsinline autoplay></video>
<canvas id="imageCanvas"></canvas>
</div>
</div>
<div class="row">
<button>Take snapshot</button>
<button id="btnUploadImage">Upload Image</button>
</div>
<div class="row">
<label class="label" data-toggle="tooltip" title="Upload A New Image">
<img class="rounded" id="avatar" src="./Images/Cardholders_72x72.png" alt="avatar">
<input type="file" class="sr-only" id="input" name="image" accept="image/*">
</label>
<!-- Modal -->
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Crop your image.</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="image" src="./Images/Cardholders_72x72.png">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="crop">Crop</button>
</div>
</div>
</div>
</div>
</div>
<script src="Scripts/jquery-3.3.1.min.js"></script>
<script src="Scripts/bootstrap.bundle.min.js"></script>
<script src="Scripts/cropper.js"></script>
<!-- This Script is for the Cropper -->
<script>
window.addEventListener('DOMContentLoaded', function ()
{
var avatar = document.getElementById('avatar');
var image = document.getElementById('image');
var input = document.getElementById('input');
var $modal = $('#modal');
var cropper;
input.addEventListener('change', function (e)
{
var files = e.target.files;
var done = function (url) {
input.value = '';
image.src = url;
$modal.modal('show');
};
var reader;
var file;
var url;
if (files && files.length > 0)
{
file = files[0];
if (URL)
{
done(URL.createObjectURL(file));
}
else if (FileReader)
{
reader = new FileReader();
reader.onload = function (e)
{
done(reader.result);
};
reader.readAsDataURL(file);
}
}
});
$modal.on('shown.bs.modal', function ()
{
cropper = new Cropper(image,
{
aspectRatio: 1,
viewMode: 3
});
}).on('hidden.bs.modal', function ()
{
cropper.destroy();
cropper = null;
});
document.getElementById('crop').addEventListener('click', function () {
var initialAvatarURL;
var canvas;
$modal.modal('hide');
if (cropper) {
canvas = cropper.getCroppedCanvas({
width: 160,
height: 160
});
initialAvatarURL = avatar.src;
avatar.src = canvas.toDataURL();
canvas.toBlob(function (blob)
{
var formData = new FormData();
formData.append('avatar', blob, 'avatar.jpg');
});
}
});
});
</script>
<!-- This script is for the camera and snapshot -->
<script>
'use strict';
// Put variables in global scope to make them available to the browser console.
const video = document.querySelector('video');
const canvas = window.canvas = document.querySelector('canvas');
canvas.width = 480;
canvas.height = 360;
const button = document.querySelector('button');
button.onclick = function ()
{
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
};
var constraints = {
audio: false,
video: {
width: { min: 320, ideal: 320, max: 640 },
height: { min: 400, ideal: 400, max: 480 },
aspectRatio: { ideal: 1.25 }
}
};
function handleSuccess(stream) {
window.stream = stream; // make stream available to browser console
video.srcObject = stream;
}
function handleError(error) {
console.log('navigator.getUserMedia error: ', error);
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
</script>
So I am following this tutorial to extend the autodesk forge viewer. I have compelted all of the steps and no button is showing, I assume this is due to an error with the loading.
https://forge.autodesk.com/blog/extension-skeleton-toolbar-docking-panel
I have also tried this tutorial, with the same issue:
http://learnforge.autodesk.io/#/viewer/extensions/selection?id=conclusion
My issue is I am not getting an error, the extension just isn't showing... does anyone know why?
I'm assuming theres an error in either the viewer or the index.
Below is my code: (index & forge viewer)
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Autodesk Forge Tutorial</title>
<meta charset="utf-8" />
<!-- Common packages: jQuery, Bootstrap, jsTree -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<script src="/js/MyAwesomeExtension.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<!-- Autodesk Forge Viewer files -->
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/style.min.css?v=v6.0" type="text/css">
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/viewer3D.min.js?v=v6.0"></script>
<!-- this project files -->
<link href="css/main.css" rel="stylesheet" />
<script src="js/ForgeTree.js"></script>
<script src="js/ForgeViewer.js"></script>
</head>
<body>
<!-- Fixed navbar by Bootstrap: https://getbootstrap.com/examples/navbar-fixed-top/ -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<ul class="nav navbar-nav left">
<li>
<a href="http://developer.autodesk.com" target="_blank">
<img alt="Autodesk Forge" src="//developer.static.autodesk.com/images/logo_forge-2-line.png" height="20">
</a>
</li>
</ul>
</div>
</nav>
<!-- End of navbar -->
<div class="container-fluid fill">
<div class="row fill">
<div class="col-sm-4 fill">
<div class="panel panel-default fill">
<div class="panel-heading" data-toggle="tooltip">
Buckets & Objects
<span id="refreshBuckets" class="glyphicon glyphicon-refresh" style="cursor: pointer"></span>
<button class="btn btn-xs btn-info" style="float: right" id="showFormCreateBucket" data-toggle="modal" data-target="#createBucketModal">
<span class="glyphicon glyphicon-folder-close"></span> New bucket
</button>
</div>
<div id="appBuckets">
tree here
</div>
</div>
</div>
<div class="col-sm-8 fill">
<div id="forgeViewer"></div>
</div>
</div>
</div>
<form id="uploadFile" method='post' enctype="multipart/form-data">
<input id="hiddenUploadField" type="file" name="theFile" style="visibility:hidden" />
</form>
<!-- Modal Create Bucket -->
<div class="modal fade" id="createBucketModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Cancel">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">Create new bucket</h4>
</div>
<div class="modal-body">
<input type="text" id="newBucketKey" class="form-control"> For demonstration purposes, objects (files) are
NOT automatically translated. After you upload, right click on
the object and select "Translate".
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="createNewBucket">Go ahead, create the bucket</button>
</div>
</div>
</div>
</div>
</body>
</html>
ForgeViewer.js:
var viewerApp;
function launchViewer(urn) {
var options = {
env: 'AutodeskProduction',
getAccessToken: getForgeToken
};
var documentId = 'urn:' + urn;
Autodesk.Viewing.Initializer(options, function onInitialized() {
viewerApp = new Autodesk.Viewing.ViewingApplication('forgeViewer');
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, { extensions: ['MyAwesomeExtension'] });
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
// We could still make use of Document.getSubItemsWithProperties()
// However, when using a ViewingApplication, we have access to the **bubble** attribute,
// which references the root node of a graph that wraps each object from the Manifest JSON.
var viewables = viewerApp.bubble.search({ 'type': 'geometry' });
if (viewables.length === 0) {
console.error('Document contains no viewables.');
return;
}
// Choose any of the available viewables
viewerApp.selectItem(viewables[0].data, onItemLoadSuccess, onItemLoadFail);
}
function onDocumentLoadFailure(viewerErrorCode) {
console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}
function onItemLoadSuccess(viewer, item) {
// item loaded, any custom action?
}
function onItemLoadFail(errorCode) {
console.error('onItemLoadFail() - errorCode:' + errorCode);
}
function getForgeToken(callback) {
jQuery.ajax({
url: '/api/forge/oauth/token',
success: function (res) {
callback(res.access_token, res.expires_in)
}
});
}
MyAwesomeExtension.js:
// *******************************************
// My Awesome Extension
// *******************************************
function MyAwesomeExtension(viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
this.panel = null;
}
MyAwesomeExtension.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
MyAwesomeExtension.prototype.constructor = MyAwesomeExtension;
MyAwesomeExtension.prototype.load = function () {
if (this.viewer.toolbar) {
// Toolbar is already available, create the UI
this.createUI();
} else {
// Toolbar hasn't been created yet, wait until we get notification of its creation
this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
this.viewer.addEventListener(av.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
}
return true;
};
MyAwesomeExtension.prototype.onToolbarCreated = function () {
this.viewer.removeEventListener(av.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
this.onToolbarCreatedBinded = null;
this.createUI();
};
MyAwesomeExtension.prototype.createUI = function () {
var viewer = this.viewer;
var panel = this.panel;
// button to show the docking panel
var toolbarButtonShowDockingPanel = new Autodesk.Viewing.UI.Button('showMyAwesomePanel');
toolbarButtonShowDockingPanel.onClick = function (e) {
// if null, create it
if (panel == null) {
panel = new MyAwesomePanel(viewer, viewer.container,
'awesomeExtensionPanel', 'My Awesome Extension');
}
// show/hide docking panel
panel.setVisible(!panel.isVisible());
};
// myAwesomeToolbarButton CSS class should be defined on your .css file
// you may include icons, below is a sample class:
/*
.myAwesomeToolbarButton {
background-image: url(/img/myAwesomeIcon.png);
background-size: 24px;
background-repeat: no-repeat;
background-position: center;
}*/
toolbarButtonShowDockingPanel.addClass('myAwesomeToolbarButton');
toolbarButtonShowDockingPanel.setToolTip('My Awesome extension');
// SubToolbar
this.subToolbar = new Autodesk.Viewing.UI.ControlGroup('MyAwesomeAppToolbar');
this.subToolbar.addControl(toolbarButtonShowDockingPanel);
viewer.toolbar.addControl(this.subToolbar);
};
MyAwesomeExtension.prototype.unload = function () {
this.viewer.toolbar.removeControl(this.subToolbar);
return true;
};
Autodesk.Viewing.theExtensionManager.registerExtension('MyAwesomeExtension', MyAwesomeExtension);
MyAwesomePanel:
// *******************************************
// My Awesome (Docking) Panel
// *******************************************
function MyAwesomePanel(viewer, container, id, title, options) {
this.viewer = viewer;
Autodesk.Viewing.UI.DockingPanel.call(this, container, id, title, options);
// the style of the docking panel
// use this built-in style to support Themes on Viewer 4+
this.container.classList.add('docking-panel-container-solid-color-a');
this.container.style.top = "10px";
this.container.style.left = "10px";
this.container.style.width = "auto";
this.container.style.height = "auto";
this.container.style.resize = "auto";
// this is where we should place the content of our panel
var div = document.createElement('div');
div.style.margin = '20px';
div.innerText = "My content here";
this.container.appendChild(div);
// and may also append child elements...
}
MyAwesomePanel.prototype = Object.create(Autodesk.Viewing.UI.DockingPanel.prototype);
MyAwesomePanel.prototype.constructor = MyAwesomePanel;
Yes, you are missing the CSS for the Buttons and also the reference to the JS files pertaining to the extensions in your HTML file.
<script src="your_folder/MyExtensionFileName.js"></script>
http://learnforge.autodesk.io/#/viewer/extensions/selection?id=toolbar-css
Check this for the CSS of your extension buttons.
I am trying out w3.css for styling, along with knockout, and when I use a footer, it covers the content near the bottom of the page.
I have a button at the bottom of my content. When the page resizes or is small enough, the footer covers the button.
See codepen, or the code below
function setting(color) {
this.color = ko.observable(color);
this.colorClassName = ko.computed(function() {
return "w3-hover-" + this.color();
}, this);
}
function myInput() {
this.data = ko.observable("");
this.nameValid = ko.computed(function() {
return !(this.data() == null || this.data().length == 0);
}, this);
this.error = ko.computed(function() {
//if (this.data() == null || this.data().length == 0)
if (this.nameValid() == false) {
return "Enter name";
} else {
return "";
}
}, this);
this.display = ko.computed(function() {
if (this.nameValid() == false) {
return "block";
} else {
return "none";
}
}, this);
this.ageData = ko.observable();
this.ageValid = ko.computed(function() {
var age = this.ageData() + "";
var patt = new RegExp(/^[0-9]+$/g); /// ^-from start, $-to end, [0-9] - 0 to 9 numbers only
var res = patt.test(age);
if (isNaN(age) == true || res == false) {
return false;
} else {
return true;
}
}, this);
this.ageError = ko.computed(function() {
if (this.ageValid() == false) {
return "Enter a valid age";
} else {
return "";
}
}, this);
this.ageDisplay = ko.computed(function() {
if (this.ageValid() == true) {
return "none";
} else {
return "block";
}
}, this);
this.phone = ko.observable('http://digitalbush.com/projects/masked-input-plugin/');
this.allValid = ko.computed(function() {
return this.ageValid() && this.nameValid();
}, this);
}
function myModel() {
this.name = "Ice-Cream";
this.items = [{
name: "Chocolate",
price: 10
}, {
name: "Vanilla",
price: 12
}];
this.style = new setting('pale-green');
this.input = new myInput();
this.changeColor = function() {
if (this.style.color().indexOf('blue') == -1) {
this.style.color('pale-blue');
} else {
this.style.color('pale-green');
}
};
}
ko.applyBindings(new myModel());
<script type='text/javascript' src='http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js'></script>
<link href="http://www.w3schools.com/lib/w3.css" rel="stylesheet" type="text/css">
<body class="w3-content w3-pale-blue" style="max-width:100%">
<header class="w3-container w3-pale-green w3-border">
<h1>Hello</h1>
</header>
<div class="w3-container w3-pale-yellow w3-border w3-padding-hor-16 w3-content" style="max-width:100%">
W3.CSS
<p>
The item is <span data-bind="text: name"></span> today.
<br />Available flavours are:
</p>
<div class="w3-container">
<ul data-bind="foreach: items" class="w3-ul w3-left w3-border w3-border-red">
<li class="w3-ul w3-hoverable w3-border-red " data-bind="css: $parent.style.colorClassName()">
<span data-bind="text: name"></span> is $<span data-bind="text:price" />
</li>
</ul>
</div>
<label class="w3-label w3-text-blue w3-xlarge">Name</label>
<input class="w3-input w3-border" type="text" data-bind="textInput: input.data">
<label class="w3-label w3-text-red w3-large" data-bind="text: input.error(), style: { display: input.display()}"></label>
<br />
<label class="w3-label w3-text-blue w3-xlarge">Age</label>
<input class="w3-input w3-border" type="text" data-bind="textInput: input.ageData">
<label class="w3-label w3-text-red w3-large" data-bind="text: input.ageError(), style: { display: input.ageDisplay()}"></label>
<br />
<label class="w3-label w3-text-blue w3-xlarge">Phone</label>
<input class="w3-input w3-border" type="text" data-bind="textInput: input.phone">
<!--<label class="w3-label w3-text-red w3-large" data-bind="text: input.phoneError(), style: { display: input.phoneDisplay()}"></label>-->
<br />
<button class="w3-btn w3-border w3-border-teal w3-round w3-teal" data-bind="click: changeColor, enable: input.allValid()">Test</button>
</div>
<footer class="w3-light-grey w3-bottom">
<div class="w3-container">
<p>This is my footer</p>
</div>
</footer>
My solution was to add another div element with the same content as my footer, but make it invisible. This way it will fill the space behind the real footer.
In the code i above i will add the following
<div class="w3-container" style="opacity:0">
<p>This is my footer</p>
</div>
The updated codepen shows the solution.
The elements .w3-top, .w3-bottom have a position of :fixed, so they're always going to stick to the page.
Remove this from their stylesheet or add an alternative to your own.
E.g.
.w3-bottom {
position: static;
}
The class of your footer is w3-bottom, and by defaut, its position is fixed, so you need to change it to relative:
.w3-bottom {
position: relative !important;
}
Another way could be to place the "button" in its own "div" to better control its attributes, remove the "div" from the footer and the bottom class. Something like:
<div class="w3-container w3-padding-bottom-32">
<button class="w3-btn w3-border w3-border-teal w3-round w3-teal" data-bind="click: changeColor, enable: input.allValid()">Test</button>
</div>
<footer class="w3-container w3-light-grey">
<p>This is my footer</p>
</footer>
Please let me know if it works for you.
Kindly, Edwin
Try to use the styles from Sticky footer example from the bootstrap.
But this method have one disadvantage: footer have fixed height :(
Example:
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 80px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
overflow: hidden;
/* Set the fixed height of the footer here */
height: 80px;
background-color: #f5f5f5;
}
<!DOCTYPE html>
<html>
<title>W3.CSS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">
<body>
<div class="w3-container w3-teal">
<h1>Header</h1>
</div>
<div class="w3-container">
<p>The w3-container class can be used to display headers.</p>
</div>
<div class="footer w3-container w3-teal">
<h5>Footer</h5>
<p>Footer information goes here</p>
</div>
</body>
</html>