Recently i've been working on a rather modest application, which lists contacts. When a detail-link was clicked, a popup would come up with more details and an image of that contact.
The table this was based on contained a column photo_reference, which held a path to a folder under /i/. With this setup, it was easy to get the images working for each contact.
The popup i showed was a bunch of htmlcode i put in the region footer, and hid.
<div id="contact_popup" class="contact_pop">
<div class="contact_pop_imgcontainer">
<img id="id_photo" class="contact_pop" />
</div>
</div>
When the detail was clicked, i retrieved data via an application process, and showed a modal dialog of this container.
Now the scope has changed: users need to be able to upload their own images. So, i went to work and made it so the images get uploaded into wwv_flow_files, and then i move them to a new contact_image table. So far so good. But now i want to display these pictures, and here i'm kind of stuck.
* I could include a column in my IR, and put a blob format mask on it with image. I've got this working, but annoyingly. My image table has as primary key the field 'ID'. My contacts table also has 'ID' as PK.
Since IMAGE:APXT_CONTACTS_IMG:SMALL_PHOTO:ID::::::inline: specifies ID as the PK, it uses ID from my contacts table.
My link between tables is APXT_CONTACTS.CONTACT_IMAGE_ID -> APXT_CONTACTS_IMG.ID.
The way i might make it work is to rename my id field in the image table to contact_image_id. (why couldnt the format mask just take the column it's based on as the value :().
I then could hide the column, and take the image with javascript to show in my popup. However, this preloads all images for the amount of selected rows, which isn't entirely bad, but i'm looking for an alternative first.
get_blob_file_src also seems no use to me because of the limited use of the parameters (file browse item, id field), and even then, could i use this through an ajax callback?
I'd much rather be able to get the image blob via ajax to then display in my popup, but i have no clue as to how to do this. I've made a stored procedure which gets the file to download and call this from an application process (ajax callback), and with firebug i can see the post/response, but i wouldn't know how to get this displayed as an image.
Javascript:
function cimg(){
var get = new htmldb_Get(null, &APP_ID., "APPLICATION_PROCESS=get_img", &APP_PAGE_ID.);
get.addParam('x01', 25);
var greturn = get.get();
alert(greturn);
var pic = new Image();
pic.onload = function(){
document.body.appendChild(pic);
};
pic.src = greturn;
};
Page process:
begin
show_small_photo(apex_application.g_x01);
end;
Stored Procedure:
create or replace procedure "SHOW_SMALL_PHOTO"
(p_contact_image_id IN NUMBER)
is
v_mime apxt_contacts_img.mime_type%type;
v_length NUMBER;
v_filename apxt_contacts_img.filename%type;
v_lob BLOB;
begin
SELECT mime_type, lengthb(small_photo), filename, small_photo
INTO v_mime, v_length, v_filename, v_lob
FROM apxt_contacts_img
WHERE id = p_contact_image_id;
OWA_UTIL.mime_header (NVL (v_mime, 'application/octet'), FALSE);
HTP.p ('Content-length: ' || v_length);
-- needed?
--HTP.p ('Content-Disposition: attachment; filename="'||v_filename||'"');
OWA_UTIL.http_header_close;
wpg_docload.download_file (v_lob);
end;
(I'd rather not have to grant execute to public and call the stored procedure via a link.)
This way i'd only have to load the pictures of links clicked. Though this code doesn't work. I get the alert and see a bunch of those weird chars, so i presume that is the blob talking. But i'm not getting an image anywhere.
Am i missing something? Doing something wrong? I'm totally out of clues.
Instead of my existing code, i might make this work through creating another page, and juggle page items and their values around to try and get the same layout going, and then get that page through ajax?
I'm mainly trying to minimize impact (=work) on the existing app, so i wouldn't have to yet again change pages.
TL;DR: is it possible to retrieve and display an image blob from a table not in the report query, preferably through ajax?
This is Apex 4.02 on an 11g db
Thanks,
Tom
To whom it may concern, here is how i solved/worked around it:
I went with the fetch-another-page route. Maybe i was too technical or too difficult about it in my op, but in hindsight i think i could've boiled it down to:
"retrieve an image blob via ajax and display it".
So if that rings any bells for anyone, let me know.
Some rectifying too:
my images table is a child of the contacts table, as such my FK between those is images.contact_id -> contact.id
I've created a new page in my application, and put a form in based on my images table. I only display 2 items of the type 'display image' (a thumbnail and original size), and 2 hidden items, 'ID' and 'CONTACT_ID'. I've removed all that was not necessary: no buttons, no DML-process, no labels, no templates.
The automated row fetch process takes as 'primary key' my contact id, so i can call the page with contact_id in the url (so that i won't have to do an extra select to retrieve the correct id in the image table).
On the page i need the pictures, i then get the page with the photos on it when required (ie: when the detail icon is clicked and i show my popup).
Following piece of code is called within this function
(only code 'missing' here is where i call another page process to fetch me contact details in a json format)
//page 120 only holds the 2 images for a contact. Due to images being in
//blobs and no easy way to dynamically fetch them, i made a small
//form with the 2 photos on, and id + contact_id
//(contact_id made PK for the ARF-process, since this is what is worked with)
//The page is pulled in, and the photos retrieved
//htmldb_get does not do the job! It only pulls the page from cache, which isnt what i want
// arrays with the arguments and their values for the post
var vArgs = new Array();
var vVals = new Array();
vArgs.push("P120_CONTACT_ID");
vVals.push(cid);
$.post('wwv_flow.show',
{"p_request" : "NULL",
"p_flow_id" : $v('pFlowId'),
"p_flow_step_id" : "120",
"p_instance" : $v('pInstance'),
"p_arg_names" : vArgs,
"p_arg_values" : vVals},
function(data){
var oContainerSmall = $("#id_photo_container").empty(),
oSmallPhoto = $(data).find("#P120_SMALL_PHOTO").attr({"id":"id_photo",
"alt":oContact.lastname + " " + oContact.firstname}),
oLargePhoto = $(data).find("#P120_LARGE_PHOTO").attr("alt",oContact.lastname + " " + oContact.firstname);
$("#large_photo_container").empty().append(oLargePhoto);
$("#large_photo_container").css({"display": "block", "visibility": "hidden"});
//Why remove and remake the container?
//for some reason i couldn't find out, the image width and height get reset to 0 after
// an initial display of the enlarged picture and closing its dialogbox.
//I assume it is an apex+css+javascript issue.
//The only fix i was able to find was to remove the div and remake a new one.
// There have to be some properties set on the div and/or its children through
// the closing of the popup dialog (for the larger picture),
// which then prevents subsequent calls to get the correct values.
$("#large_photo_container").remove();
var oContainerLarge = $("<div></div>").attr("id","large_photo_container").css({"display":"block", "visibility":"hidden"});
oContainerLarge.append(oLargePhoto);
$("body").append(oContainerLarge);
//Hardcoded value here! Adapt when necessary
if($("#id_contact_type").val() == "Conference Room"){
var oLink = $("<a/>").attr("href", "#")
.click(function(event){
explode_headshot(event, this);
});
oContainerSmall.append(oLink.append(oSmallPhoto));
} else {
oContainerSmall.append(oSmallPhoto);
};
$("#contact_popup").dialog({modal: true,
width: 750,
resizable: false,
title: oContact.lastname + " " + oContact.firstname,
close: function(event, ui){
$("#id_photo").attr({"src": "", "alt": ""});
}});
});
I wouldn't call it ideal, but it works, and that's all i need at this time :)
I made a very similar application for showing contact details including photos a few months ago in apex.
I found this web page very useful:
http://blog.hilandco.com/2010/05/how-to-show-blob-type-column-as-image.html
Displaying an image can be as easy as creating an item of type: "display Image". Settings: " Blob column returned by sql statement".
And then an sql statement selecting the correct row:
select blob_content
from my_bitmap_table
where ID = ...
Related
I'm very new to writing script for Google Sheets, and I'm attempting to create a spreadsheet that will only display a dropdown in a column ("Provisional Notes") if the value in a column ("Certified or Provisional" is "Provisional." If it is "Certified," the user is be able to enter data freely. I'm also wanting to remove the validation if the value changes from "Provisional". I also need the solution to run on the Google Sheets App, as this spreadsheet will be run on an iPad and/or a smartphone.
I've done quite a bit of searching, and only seem to find dropdowns that depend on other dropdowns, rather than leaving a cell blank if the initial dropdown is a certain value.
What I have come up with so far only runs once (even though I've attempted to have it occur on every edit?) Also, if the value changes from Provisional to Certified, the validation does not get removed.
For my practice, I've applied it to only 2 specific cells, rather than the entire 2 columns.
function onEdit2(e){
var dropdownCell = SpreadsheetApp.getActive().getRange('P2');
var certCell = SpreadsheetApp.getActive().getRange('J2');
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['Test 1', 'Test 2'], true).build();
function insertDropdown(){
if(e.certCell.getValue() === "P"){
dropdownCell.setDataValidation(rule);
}
}
}
I greatly appreciate all suggestions!
To delete a data validation you just need to use the method setDataValidation() and set its value to null to delete the data validation present on that cell.
In the following code example, depending on the value inserted I am adding or deleting the data validation. This code example has self explanatory comments:
function onEdit(e) {
// get sheet
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
// if modifications happen in A
if(e.range.getColumn()==1){
// check inserted value
if(e.range.getValue()=='Provisional'){
// create rule from a list range and set it to the same row in column B
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['A','B']).setAllowInvalid(false).build();
sheet.getRange(e.range.getRow(),2).setDataValidation(rule);
}
// if it is set to certified
if(e.range.getValue()=='Certified'){
// delete any data validation that may have been in its adjacent cell
sheet.getRange(e.range.getRow(),2).setDataValidation(null);
}
}
}
I've got an Array with 17 web links of images
var products:Array;
trace(products)
// Ouput :
"http://www.myWebsite.com/zootopia.jpg"
"http://www.myWebsite.com/james.jpg"
"http://www.myWebsite.com/tom.jpg"
..etc
If I do products[10].movieimage; the output will be the 9th link (something like : "http://www.myWebsite.com/lalaland.jpg")
I'm looking for downloading every images without a dialog box.
I manage to do so for 1 image with the specific link, like that :
function saveImage (event:Event):void {
var stream:URLStream = new URLStream();
var image1:File = File.documentsDirectory.resolvePath("test.jpg");
var fileStream:FileStream = new FileStream();
stream.load(new URLRequest("http://www.myWebsite.com/lalaland.jpg"));
stream.addEventListener(Event.COMPLETE, writeComplete);
function writeComplete(evt:Event):void {
var fileData:ByteArray = new ByteArray();
stream.readBytes(fileData,0,stream.bytesAvailable);
fileStream.openAsync(image1, FileMode.UPDATE);
fileStream.writeBytes(fileData,0,fileData.length);
fileStream.close();
trace("writeComplete");
trace(image1.url);
}
}
Question : Is there a way to download all the images with the web links of my products array ? (and if images already exist, replace them. I could use if (image1.exists){ if (image2.exists){ ..etc for each image. But maybe there is a simplier solution)
If you could show me how, with a bit of code, I could that.
Also, note that I'd like to load the images then in Uiloader, like that :
function loadImages():void {
uiloader1.source = image1.url;
uiloader2.source = image2.url;
etc...
}
Don't over think it. You have your array of images. You have your tested routine for saving one image. Now put it together:
Some function initializes things and kicks it off.
Either splice out (or pop out) an item on the array – OR use a index var to access an item in the array
Pass that to your download function.
When the download completes either pop another item off the array OR increment your index. But first you would test if array.length== 0 OR `index > array.length. If either is true (depending on which method you use), then you are done.
If you want to get fancy you can show a progress bar and update it each time your download completes.
I am using data validation rules on a Google Spreadsheet.
In my scenario, I need users to entry only valid values. I use the 'Reject input' to force them to write only validated content.
However, the 'Reject input' option works for manually entried data only, but it does not work if the user pastes content into the cell from a different source (e.g. a MS Excel document). In that case, a warning is still shown but the invalid value is written.
In other words, I need the 'Reject input' option to work also with pasted content.
OR... another approach would be to programmatically check the validity of the value according the Datavalidation rule for that cell.
Any ideas?
Thank you in advance.
I had a little play with this.
I had inconsistent behavior from google.
On occasion when I ctrl-c and ctrl-p, the target cell lost its data validation!
To do this programmatically
Write myfunction(e)
Set it to run when the spreadsheet is edited, do this by Resources>Current Project's Triggers
Query e to see what has happened.
Use the following to gather parameters
var sheet = e.source.getActiveSheet();
var sheetname = sheet.getSheetName();
var a_range = sheet.getActiveRange();
var activecell = e.source.getActiveCell();
var col = activecell.getColumn();
var row = activecell.getRow();
You may wish to check a_range to make sure they have not copied and pasted multiple cells.
Find out if the edit happened in an area that you have data validated;
if (sheetname == "mySheet") {
// checking they have not just cleared the cell
if (col == # && activecell.getValue() != "") {
THIS IS WHERE YOU CHECK THE activecell.getValue() AGAINST YOUR DATA VALIDATION
AND USE
activecell.setValue("") ;
to clear the cell if you want to reject the value
}
}
The obvious problem with this is that it is essentially repeating programmatically what the data validation should already be doing.
So you have to keep two sets of validation rules synchronized. You could just delete the in sheet data validation but I find that useful for providing the user feedback. Also is the data validation you are using provides content it is practical to leave it in place.
It would be great if there was a way of detecting that ctrl-p had been used or one of the paste-special options and only run the script in those cases. I would really like to know the answer to that. Can't find anything to help you there.
Note also that if someone inserts a row, this will not trigger any data validation and the onEdit() trigger will not detect it. It only works when the sheet is edited and by this I think it means there is a content change.
onChange() should detect insertion, it is described as;
Specifies a trigger that will fire when the spreadsheet's content or
structure is changed.
I am posting another answer because this is a programmatic solution.
It has a lot of problems and is pretty slow but I am demonstrating the process not the efficiency of the code.
It is slow. It will be possible to make this run faster.
It assumes that a single cell is pasted.
It does not cater for inserting of rows or columns.
This is what I noticed
The onEdit(event) has certain properties that are accessible. I could not be sure I got a full listing and one would be appreciated. Look at the Spreadsheet Edit Events section here.
The property of interest is "e.value".
I noticed that if you typed into a cell e.value = "value types" but if you pasted or Paste->special then e.value = undefined. This is also true for if you delete a cell content, I am not sure of other cases.
This is a solution
Here is a spreadsheet and script that detects if the user has typed, pasted or deleted into a specific cell. It also detects a user select from Data validation.
Type, paste or delete into the gold cell C3 or select the dropdown green cell C4.
You will need to request access, if you can't wait just copy & paste the code, set a trigger and play with it.
Example
Code
Set the trigger onEdit() to call this or rename it to onEdit(event)
You can attach it to a blank sheet and it will write to cells(5,3) and (6,3).
function detectPaste(event) {
var sheet = event.source.getActiveSheet();
var input_type =" ";
if (event.value == undefined) { // paste has occured
var activecell = event.source.getActiveCell();
if (activecell.getValue() == "") { // either delete or paste of empty cell
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "delete"
}
else {
activecell.setValue("");
sheet.getRange(5,3).setValue("You pasted so I removed it");
sheet.getRange(6,3).setValue(event.value);
input_type = "paste";
}
}
else { // valid input
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "type or select";
}
SpreadsheetApp.getActiveSpreadsheet().toast('Input type = ' + input_type, 'User Input detected ', 3);
}
I have a view panel in xpages and a computed field to show the row-count. I have 2 problems:
1st: how can i count only the rows of the current page of the pager?it seems that viewPanel.getRowCount() gets all rows from the beginning until the current page, so if i have 30 entries per page and i am in page 2, it shows 60 instead of 30.
2nd: even if i achieve the above, the pager only refreshes the view and i can't refresh the computed field or another panel. Can i put the computed field inside the view panel or make it somehow to be refreshed in each page change? i would prefer not to do it with full page refresh if possible...
Given the computed field refresh issue, even if you managed to get a count of rows (eg on a page-scope SSJS variable), it woudln't update the computed field.
It might be easier to to count to rows in the HTML table using clientside javascript ?
something like ..
page onload event :
get the HTML table
get the table rows property (array of rows in the table)
number or rows = that value (1st row = TH (header) row if there is one = row 0)
use javsscript to plug that value into a known DIV
You could also hijack the partial refresh issued from the pager and check for the ID that has been refreshed. If the ID match your view panel's ID, you could do another partial update in order to update another panel.
Example code for hijacking the partial refresh:
var sysHijackPartialRefresh = function(){
XSP._inheritedPartialRefresh = XSP._partialRefresh;
XSP._partialRefresh = function( method, form, refreshId,options) {
if (options){
if (!options.onComplete) {
options.onStart = function(){
};
options.onComplete = function(){
if (refreshId == "<client id of view panel>") {
// issue another partial refresh
XSP.partialRefreshGet(<client id of the other panel>);
}
};
}
}
this._inheritedPartialRefresh(method, form, refreshId, options); }
}
XSP.addOnLoad(sysHijackPartialRefresh);
See http://xpageswiki.com/web/youatnotes/wiki-xpages.nsf/dx/Work_with_events_and_partial_or_full_refresh for more information about partial updates.
The pager should have a "refreshID" property in which you can put the client ID (!) of the panel you want to refresh. Use getClientId() to compute the client ID of the panel.
For the row count: use the XPages debug toolbar (from openNTF) to check for a property of the view panel or the pager which gives you the current page number. If you got that, you can simply compute by using viewPanel.getRowCount() - (page number * number of rows per page).
I need to select a specific row in kendoGrid but NOT by data-uid (as data-uid is changed when the grid dataSource is loaded again) but by the row itemID. I saw posts but they only select the row by uid which is not what I needed, I actually need to restart the HTML5 application and when grid is loaded, a specific item should be selected. This is what I've been seeing
Demo: jsfiddle.net/rusev/qvKRk/3/
e.g. the object has OrderID as ID, and every time the grid is loaded, it will be the same, unlike uid, I want to know how will I be able to select a row with OrderID, instead of uid.
You cam mix row itemID and data.uid, I guess.
var grid = $("#Grid").data("kendoGrid");
var dataItem = $("#Grid").data("kendoGrid").dataSource.get(itemID);
var row = $("#Grid").data("kendoGrid").tbody.find("tr[data-uid='" + dataItem.uid + "']");
Going along with what umais has mentioned, the better approach, since there is no built in functionality for this as of yet, would be to iterate through all the records to find the one you need. The function that I built will work even if there are pages of data. The only other way that I can think of doing this would be do do a secondary ajax call; But this works well. Note that i haven't tested it with more than 2000 records.
var dataGrid = $("#GATIPS").data("kendoGrid").dataSource;
var numOfRows = dataGrid.total();
var currentPageSize = dataGrid.pageSize();
dataGrid.pageSize(numOfRows);
var dataGridData = dataGrid.data();
for (var i = 0; i < numOfRows; i++) {
if (dataGridData[i].uid == e)
return dataGridData[i];
}
dataGrid.pageSize(currentPageSize); // reset the view
e is the UID. However this can be substituted for what ever variable you need just replace the check.
a work around that I managed to have, was to go through all rows and check which row model has that ID equal to the parameter, and then get that row data-uid and select the item through data-uid. It's working fine for me, since there were no suggestion, it's the better answer for now.
Well, accordingly to what I have done (and worked for me), and even though the work around isn't the prettiest, set one more Column, with your model id and with ClientTemplate then create any html object (div in my case) inside it give it a html id of your id, so when ever you need it, you just have to go and look with something like:
grid.dataItem($("td div#id").closest("tr"));
Because remember that the dataItem method is waiting for a selector then you get your selectedItem as regular one.
EDIT:
I forgot to say, that you should (or could) use the style property
display:none
If you don't want to display that col.