Display image in html (web version) from attribute value in Quest.

I added an attribute (pic) to certain objects in Quest. This attribute is a String and simply the name of the image file (image_1.png). I want to use that attribute field to show the image to the player.

I'm sure I will need a function for this, and I tried to copy the GetDisplayName function in order to alter it for my needs, but that did not work. Here is a line of code from function updateList in game.js:

<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "</li>

I want to insert the pic attribute inside the list tag, like this:

<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "<img src='../images/" + objectPic + " /></li>"

This would then display the image of the object in the Places and Objects pane. Since my javascript is not very strong, here I am, with hat in hand...

It seems that no matter what I write for a function, object is not defined, pic is not defined, defined is not defined (the last one didn't happen, but it felt like that's all that was happening...just everything "not defined").

I tried setting the var for pic:
var objectPic = GetDisplayPic();

Here are the functions for GetDisplayPic and GetListDisplayPic:

function GetListDisplayPic(obj)
{
if (HasString(obj, "pic")) {
var result = obj.pic;
}
else {
var result = GetDisplayPic(obj);
}
return (result);
}
function GetDisplayPic(obj)
{
if (HasString(obj, "pic")) {
var result = obj.pic;
}
else {
var result = msg("No image available");
}
return (result);
}

I know that Quest can do a lot IN the GUI, but I didn't want to bloat my game file with a bunch of images. I figured just pulling the filename would be enough. I'm guessing KV or Pixie will be able to answer this, but in case anyone else figured out a better "hack", let me know please. Thanks everyone!


You have a couple of lines like this:

var result = obj.pic;

That will make Quest set "var result" equal to the "pic" attribute (it is not JavaScript!). And then you return the value of hge "result" attribute.

I think you will then hit another issue or two, relating to then finding the image. You may find this page helpful:
http://docs.textadventures.co.uk/quest/images.html


The only place to edit the Panes is in the Javascript...?

That means I would have to have custom: inventory, places and objects, and exits panes in the GUI, in order to add an image along with the text?

The functions above are just copies of existing functions for GetDisplayName/GetDisplayListName, just altered for the pic field. This is the section of code I'm looking at in game.js:

if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
            listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $(listElement).append(
                "<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "</li>"
            );
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }

If you're writing javascript, you can't call HasString(). Javascript code cannot access Quest objects or their attributes.


This code is from the JS compiler that KV forked:

function updateList(listName, listData) {
    var listElement = "";
    var emptyListLabel = "";

    if (listName == "inventory") {
        listElement = "#inventoryList";
        emptyListLabel = "#inventoryEmpty";
    }

    if (listName == "placesobjects") {
        listElement = "#objectsList";
        emptyListLabel = "#placesObjectsEmpty";
    }

    $(listElement).empty();
    $(listElement).show();
    var listcount = 0;
    var anyItem = false;

    $.each(listData, function (key, value) {
        var splitString = value.split(":");
        var objectDisplayName = splitString[0];
        var objectVerbs = splitString[1];

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
            listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $(listElement).append(
                "<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "</li>"
            );
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }
    });
    $(listElement + " li:last-child").addClass('last-child')
    if (listcount == 0) $(listElement).hide();
    if (anyItem) {
        $(emptyListLabel).hide();
    }
    else {
        $(emptyListLabel).show();
    }
}

All of that works, as the objectlinks are displayed in the list and when clicked, display the appropriate verbs associated with that object. I was just hoping to display an image (for each specific object) next to the text of the name.


Hmm ... my thoughts on this would be to output the values you need at the start.
In your UI initialisation script:

JS.eval("panedisplaypics = {};")
foreach (obj, AllObjects()) {
  if (HasString(obj, pic)) {
    JS.eval(ProcessText("panedisplaypics['{obj.name}'] = '{obj.pic}';"))
  }
}

Then in javascript, you can just access panedisplaypics[objectname].

If I was doing something like this, then I might even have Quest output a load of <img id="inventorypic-{obj.name}" src="{obj.pic}" style="display: none;" /> lines; or override GetListDisplayAlias so that it outputs an image the first time it's called for a given object. Then your JS can just move the image to the right place and un-hide it as needed.


Ah, I've not tried playing with QuestJS, sorry.


Sorry, it's probably no help if you're not using Quest, but I think I worked out how I'd do images in the inventory list. Something like this might work.

In UI initialisation script:

game.sentListImages = NewStringList()

Override one core function:

  <function name="GetListDisplayAlias" type="string" parameters="obj">
    <![CDATA[
    if (HasString(obj, "listimage")) {
      if (not ListContains(game.sentListImages, obj.name)) {
        msg ("<img id=\"inventorypic-{obj.name}\" src=\"{obj.listimage}\" style=\"display: none;\" class=\"itemlistimage\" />")
        list add (game.sentListImages, obj.name)
      }
    }
    if (HasString(obj, "listalias")) {
      result = obj.listalias
    }
    else {
      result = GetDisplayAlias(obj)
    }
    return (result)
  ]]>
  </function>

And override one javascript function:

function updateList(listName, listData) {
    var listElement = "";
    var buttonPrefix = "";

    if (listName == "inventory") {
        listElement = "#lstInventory";
        inventoryVerbs = new Array();
        buttonPrefix = "cmdInventory";
    }

    if (listName == "placesobjects") {
        listElement = "#lstPlacesObjects";
        placesObjectsVerbs = new Array();
        buttonPrefix = "cmdPlacesObjects";
    }

    var previousSelectionText = "";
    var previousSelectionKey = "";
    var foundPreviousSelection = false;

    var $selected = $(listElement + " .ui-selected");
    if ($selected.length > 0) {
        previousSelectionText = $selected.first().text();
        previousSelectionKey = $selected.first().data("key");
    }

    $(listElement+" .itemlistimage").hide().insertAfter("#divOutput");
    $(listElement).empty();
    var count = 0;
    $.each(listData, function (key, value) {
        var data = JSON.parse(value);
        var objectDisplayName = data["Text"];
        var verbsArray, idPrefix;

        if (listName == "inventory") {
            verbsArray = inventoryVerbs;
            idPrefix = "cmdInventory";
        } else {
            verbsArray = placesObjectsVerbs;
            idPrefix = "cmdPlacesObjects";
        }

        verbsArray.push(data);

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
            var $newItem = $("<li/>").data("key", key).data("elementid", data["ElementId"]).data("elementname", data["ElementName"]).data("index", count).html(objectDisplayName);
            $("#inventorypic-"+key).appendTo($newItem).show();
            if (objectDisplayName == previousSelectionText && key == previousSelectionKey) {
                $newItem.addClass("ui-selected");
                foundPreviousSelection = true;
                updateVerbButtons($newItem, verbsArray, idPrefix);
            }
            $(listElement).append($newItem);
            count++;
        }
    });

    var selectSize = count;
    if (selectSize < 3) selectSize = 3;
    if (selectSize > 12) selectSize = 12;
    $(listElement).attr("size", selectSize);
    
    if (!foundPreviousSelection) {
        for (var i = 1; i <= verbButtonCount; i++) {
            var target = $("#" + buttonPrefix + i);
            target.hide();
        }
    }
}

Is it an online image?

    <object name="Flux Capacitor">
      <inherit name="editor_object" />
      <take />
      <look>{Flux Capacitor.img_url}</look>
      <listalias><![CDATA[<hr/><img width='100%'  src='https://vignette.wikia.nocookie.net/bttf/images/b/b6/BTTF-game-SS-09.jpg/revision/latest/scale-to-width-down/300?cb=20101214205519'/>Flux Capacitor<hr/>]]></listalias>
      <img_url type="string"><![CDATA[<img width='100%'  src='https://vignette.wikia.nocookie.net/bttf/images/b/b6/BTTF-game-SS-09.jpg/revision/latest/scale-to-width-down/300?cb=20101214205519'/>]]></img_url>
    </object>

Or is it an offline image?

(The image will not display as the list alias when doing it this way (as far as I can tell). I think this is because Quest reads the listalias string attribute as text, not as an expression. (I'm about to play around with mrangel's last code.))

    <object name="Flux Capacitor">
      <inherit name="editor_object" />
      <take />
      <look>{Flux Capacitor.img_url}</look>
      <img_url type="string"><![CDATA[<img width='100%'  src='{=GetFileURL("flux-capacitor.png")}'/>]]></img_url>
    </object>

Okay, I just changed this (and only this; no change to any JS):

  <function name="GetListDisplayAlias" parameters="obj" type="string">
    if (HasString(obj, "listalias")) {
      result = ProcessText(obj.listalias)
    }
    else {
      result = GetDisplayAlias(obj)
    }
    return (result)
  </function>

That works for online URLs or GetFileURL("file-in-game-folder.png").


Example with file in the game's directory (on the hard drive):

    <object name="Flux Capacitor">
      <inherit name="editor_object" />
      <take />
      <look>{Flux Capacitor.img_url}</look>
      <listalias><![CDATA[<hr/>{Flux Capacitor.img_url}Flux Capacitor<hr/>]]></listalias>
      <attr name="img_url"><![CDATA[<img width='100%'  src='{=GetFileURL("flux-capacitor.png")}'/>]]></attr>
    </object>

Example using an online image:

    <object name="Flux Capacitor">
      <inherit name="editor_object" />
      <take />
      <look>{Flux Capacitor.img_url}</look>
      <listalias><![CDATA[<hr/>{Flux Capacitor.img_url}Flux Capacitor<hr/>]]></listalias>
      <attr name="img_url"><![CDATA[<img width='100%'  src='https://vignette.wikia.nocookie.net/bttf/images/b/b6/BTTF-game-SS-09.jpg/revision/latest/scale-to-width-down/300?cb=20101214205519'/>]]></attr>
    </object>

image


Oh man, you all are wonderful, seriously! I feel like Doctor Frankenstein...it's almost...alive.

Allow me to cover a few things for those that decide to tackle this issue:

  1. mrangel's original function override
if (HasString(obj, "listimage")) {
      if (not ListContains(game.sentListImages, obj.name)) {
        msg ("<img id=\"inventorypic-{obj.name}\" src=\"{obj.listimage}\" style=\"display: none;\" class=\"itemlistimage\" />")
        list add (game.sentListImages, obj.name)
      }
    }
    if (HasString(obj, "listalias")) {
      result = obj.listalias
    }
    else {
      result = GetDisplayAlias(obj)
    }
    return (result)

and then the javascript:

JS.eval("panedisplaypics = {};")
foreach (obj, AllObjects()) {
  if (HasString(obj, pic)) {
    JS.eval(ProcessText("panedisplaypics['{obj.name}'] = '{obj.pic}';"))
  }
}

I added this to game.js function updateList:

var objectDisplayPic = panedisplaypics[objectDisplayName];

(Note: originally, mrangel had panedisplaypics[objectname], but objectname threw a fit about objectname not being defined.)

This resulted in the game executing without any errors, however the objectName text would display, with a broken-link image box. Firefox Inspector shows that the code works, but the URL shows as "undefined".

I felt like that was so very, very close to the result I was hoping for.

Now, moving on to where KV is heading:
It's an online file, so the relative directory will always be : ../images/
I figured even if I could rebuild the filename in game.js, the path would work. It looks like what you are thinking KV, is that I would be altering the objectListDisplayAlias for only the image, but the image is in addition to the text for the name.
I added an attribute (pic) to each object in the game, this contains part of the filename, without the extension, so vamp_1, instead of vamp_1.png. Then in game.js the <li> would read:
`<li id="" + paneLinkId + "" href="#">" + objectDisplayName + "
<img src="../images/" + objectDisplayPic + ".png" />"

Code Inspector:
<img src="../images/undefined.png" />

The image above is part of the game window. The spellbook text is the PlacesObjects pane. I want to keep the text and add an image (underneath it, above it, next to it, etc which I can handle with external CSS).

Seriously...like >this< close, thank you!


It looks like what you are thinking KV, is that I would be altering the objectListDisplayAlias for only the image, but the image is in addition to the text for the name.

Negative.

I have text underneath my image, and it is all nested between hard breaks. (Check out my Places and Objects pane in the picture I posted last.)

Is that what you're trying to accomplish?


While testing mrangel's code, I couldn't get anything added to the JS array.

So, I tested other methods, and this works:

JS.eval ("gameDisplayAliases = {};")
foreach (obj, AllObjects()) {
  JS.eval ("gameDisplayAliases['"+obj.name+"'] = '"+GetDisplayName(obj)+"';addTextAndScroll(gameDisplayAliases."+obj.name+"+'<br/><br/>');")
}

a room

a me

a thing

a crate


So try this (revised):

JS.eval("panedisplaypics = {};")
foreach (obj, AllObjects()) {
  if (HasString(obj, "pic")) {
    JS.eval("panedisplaypics['"+obj.name+"'] = '"+obj.pic+"';")
  }
}

I posted 2 different methods. One with a JS object acting as an array, and one with hidden images. Those two were not intended to work together.


KV, your code did work, once I actually looked at it closer! I missed that you included the img_url into the listalias field. That does the trick though, just:

<function name="GetListDisplayAlias" parameters="obj" type="string">
    if (HasString(obj, "listalias")) {
      result = ProcessText(obj.listalias)
    }
    else {
      result = GetDisplayAlias(obj)
    }
    return (result)
  </function>

Thanks again for all your help everyone!

EDIT: The code works locally, but not through a website. So this doesn't carry over through the compiler.


Game.js has the function updateList, which builds and refreshes the inventory, places and objects and exits panes. This code here is how each element is generated for the list:

$.each(listData, function (key, value) {
        var splitString = value.split(":");
        var objectDisplayName = splitString[0];
        var objectVerbs = splitString[1];

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
            listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $(listElement).append(
                "<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "</li>"
            );
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }
    });

The coding offered by KV, is handy for using within Quest! It works, within Quest. Since my project requires me to export using QuestJS, I needed another solution that would work for a website. After banging my head on my laptop for a few hours and then asking these folks here to join me in banging our heads against our computers...I discovered a simple solution.

If you add one segment of code to the game.js function updateList, you can easily control list elements with CSS!!

$.each(listData, function (key, value) {
        var splitString = value.split(":");
        var objectDisplayName = splitString[0];
        var objectVerbs = splitString[1];

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
            listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $(listElement).append(
                "<li id=\"" + paneLinkId + "\" href=\"# " + objectDisplayName + "\">" + objectDisplayName + "</li>"
            );
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }
    });

Notice the difference between the first piece of code and the second? All I did was copy objectDisplayName from the end of the li element, and paste it after adding a space in the href. This step actually gives EACH list element a unique href that can be referenced in CSS!

The original game.js code produces this HTML result:

<div id="gamePanes" style="">
     <div id="gamePanesRunning">
          <div id="inv">
               <p id="inventoryEmpty" class="emptyListLabel" style="display: none;">Empty</p>
                    <ul class="elementList" id="inventoryList"><li id="paneLink2" href="#" class="last-child">spellbook</li></ul>
...

Adding the extra objectDisplayName onto the href, you see this result:

<div id="gamePanes" style="">
     <div id="gamePanesRunning">
          <div id="inv">
               <p id="inventoryEmpty" class="emptyListLabel" style="display: none;">Empty</p>
                    <ul class="elementList" id="inventoryList"><li id="paneLink2" href="# spellbook" class="last-child">spellbook</li></ul>
...

The li element href now has a specific name # spellbook and you can use CSS like this:

ul.elementList li[href*=spellbook] {
color: transparent !important;
background-image: url('../images/sp_book1.png');
background-position: center;
}

Obviously if you decide to use this and you have lots of items, you will be adding a lot of CSS code, but as of right now, this is the best way to achieve images in any of the panes with minimal core editing and no additional changes inside Quest are needed (not even to combine the URL with the listalias, which is genius by the way...text processor for the win!). Yes, I also added objectDisplayName to other locations within the li element and discovered that in every other location, except as attached to the href, the menu is then broken (code is looking for specific elements, i.e. panelink#, but not panelink objectDisplayName).

Thanks again for your help everyone! If not for all of your efforts, we would all still be trying to do this stuff on paper! (Wait, did I just reveal my age?)


Not sure why I didn't think of putting the <img> tag inside the object's listalias... I've done that before, in the first game I started building. (Note: if using data: URIs this causes massive slowdown, because the entire inventory is sent from Quest to the browser every turn. You don't want to be sending more than 1MB every turn)

The li element href now has a specific name # spellbook and you can use CSS like this:

Using href like that... I guess it works. I would suggest using 'class', because that's kind of what the class attribute is for, but that would require a name without spaces in.

If you're including all the image names in CSS anyway, it probably makes more sense (and takes less space) to include a literal JS array of object names and then look them up within updateList.

Somewhere at the start of your JS:

imagesForInventoryPane = {
  'spellbook': '../images/sp_book1.png',
  'sword': '../images/sword1.png',
  'penguin': '../images/penguin1.png'
};

And modify updateList:

      $.each(listData, function (key, value) {
        var splitString = value.split(":");
        var objectDisplayName = splitString[0];
        var objectVerbs = splitString[1];

        if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
            listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $newElement = $("<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "</li>");
            if(imagesForInventoryPane[objectDisplayName]) {
              $newElement.css({
                'color': 'transparent',
                'background-image': "url('"+imagesForInventoryPane[objectDisplayName]+"')",
                'background-position': 'center'
              });
            }
            $(listElement).append($newElement);
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }
    });

This way, you don't need extra CSS for every item; just one more line in the array.


Though actually, "<li id=\"" + paneLinkId + "\" href=\"#\">" + objectDisplayName + "</li>" is ugly enough anyway.

Is there some reason game.js doesn't use $("<li/>", {id: paneLinkId, href: "#"}).text(objectDisplayName)?


Nice idea, mrangel! I may look more into that direction if I start having more than a dozen items in the various panes.

Using class - Yeah, I tried that at first, however, if you look at the resulting HTML output, the class has already been assigned and any changes to it, break the functionality of the QuestJS compiler (showing menus).


It's just the same code I gave you earlier (my first suggestion), except with the static array of URLs in the javascript, rather than relying on Quest to extract an attribute from the object.


I have no idea...KV is [the one] developing the QuestJS fork I am using. I am just good at finding stuff like this, banging my head on my keyboard/laptop for a bit and eventually figuring out a different way to "skin the cat".


Building off of both of your suggestions:

I changed it to this:

EDITED

function updateList(listName, listData) {
    var listElement = "";
    var emptyListLabel = "";

    if (listName == "inventory") {
        listElement = "#inventoryList";
        emptyListLabel = "#inventoryEmpty";
    }

    if (listName == "placesobjects") {
		$('#gameObjects').show();
        listElement = "#objectsList";
        emptyListLabel = "#placesObjectsEmpty";
    }

    $(listElement).empty();
    $(listElement).show();
    var listcount = 0;
    var anyItem = false;

    $.each(listData, function (key, value) {
        var splitString = value.split(":");
        var objectDisplayName = splitString[0];
        var objectVerbs = splitString[1];
		var hasListAlias = false;
        var thisObj = GetObject(objectDisplayName);
		var objNameToClass = objectDisplayName.replace(/ /g,'-');
		var objectListAlias = objectDisplayName;
		
		if (typeof(thisObj['listalias']) === "string" && thisObj['listalias'] !== ""){
			hasListAlias = true;
			objectListAlias = thisObj['listalias'];
		}
		
		if (listName == "inventory" || $.inArray(objectDisplayName, _compassDirs) == -1) {
	        listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $(listElement).append(
                "<li id=\"" + paneLinkId + "\" class=\"" + objNameToClass + "\" href=\"#\">" + objectListAlias + "</li>"
            );
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }
		else if (listName == "placesobjects" || $.inArray(objectDisplayName, _compassDirs) == -1) {
			
            listcount++;
            lastPaneLinkId++;
            var paneLinkId = "paneLink" + lastPaneLinkId;
            $(listElement).append(
				"<li id=\"" + paneLinkId + "\" class=\"" + objNameToClass + "\" href=\"#\">" + objectListAlias + "</li>"
            );
            bindMenu(paneLinkId, objectVerbs, objectDisplayName, false);
            anyItem = true;
        }
    });
    $(listElement + " li:last-child").addClass('last-child')
    if (listcount == 0) $(listElement).hide();
    if (anyItem) {
        $(emptyListLabel).hide();
    }
    else {
        $(emptyListLabel).show();
    }
}

Now, it shows the listalias value in the panes, if it exists.

It still adds the other classes, too.


It replaces any spaces in the object's display name to -.

So, I have "Flux Capacitor".

It sets the class "Flux-Capacitor". Then, I can do whatever with $('.Flux-Capacitor').


QuestJS has it's own functions in game.js. The normal Quest functions do not copy over to anything that can be interpreted by a browser. That's one of the reasons QuestJS doesn't compile sometimes. If a new function is added to Quest which doesn't have a JS version in game.js, the game will be faulty (if it even compiles).


This works:

$(".Flux-Capacitor").css('background-image','url("https://vignette.wikia.nocookie.net/bttf/images/b/b6/BTTF-game-SS-09.jpg/revision/latest/scale-to-width-down/300?cb=20101214205519")')

This works, but it doesn't process the text when using text-processor code:

$(".Flux-Capacitor").html(GetObject("Flux Capacitor")['listalias'])

If QuestJS had the text-processor functions, that would work.

...but:

image


Nice KV! Did you add that to the QuestJS fork too? I want to make sure I don't overwrite the code if I do an update in the wee hours of the morning.

That was similar to my suggestion on Github (RE: adding more specific naming information to HTML elements during the compile process with QuestJS for this very reason).


NOTE: I will add text-processor functions, too.


ALSO NOTE: If you have a text-processor function in your listalias, replace it with actual HTML.

Maybe:

$(".Flux-Capacitor").html("<img src='whatever'/>Flux Capacitor");

Yep:

BEFORE
image


AFTER
image


Did you add that to the QuestJS fork too?

Negative, but I will soon.

I want to make sure I don't overwrite the code if I do an update in the wee hours of the morning.

I hate it when that happens!

That was similar to my suggestion on Github (RE: adding more specific naming information to HTML elements during the compile process with QuestJS for this very reason).

I have learned much from mrangel and Pixie since then.

I didn't know exactly what you meant, either.

...but now I know.

..and knowing is half the battle!


#GoJ.O.E.!


<a href='../ycnqtuiyckeuqwbz3nfp-q/quest-js-another-sort-of-update'>Updated it!</a>

Updated it!


The object links work now! (Except for the first click, but I bet that can be fixed with a bind when the game loads, but I don't know how to do that.)


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums