In WiX how can I select an IIS website by name? - iis

What I would like to do is show the installer user a list of the websites on their server and allow them to choose one (using the method described here: http://www.cmcrossroads.com/content/view/13160/120/, which now seems broken see here for the core code). The installer would then create a virtual directory in the chosen website.
However, my searching seems to have revealed that the only way to specify a website in WiX is by IP, Port, and Header. Asking for these is not very user friendly, so I'm left with the idea of writing a second custom action to get those details from a website name.
Is there a better way?
BTW This needs to work in both IIS6 and IIS7 in case that affects the answer.

OK it is possible (in IIS6 or IIS7 with Metabase compatibility), thanks to this post to the mailing list explaining the slightly bizarre way the iis:Website element works. The useful part is:
Using a fragment like this and test with v3.0.5120.0:*
<iis:WebSite Id="WebSite" Description="Default Web Site" SiteId="*">
<iis:WebAddress Id="TestWebSite" Port="1" />
</iis:WebSite>
The following work:
1. If WebSite/#SiteId="*" then a case sensitive match on WebSite/#Description happens.
2. If WebSite/#SiteId matches the site id than WebSite/#Description is ignored and a match on site id happens.
3. If WebSite/#SiteId has any value WebAddress/#Port is ignored (although the syntax requires it and it can't be 0).
4. If WebSite/#SiteId is missing WebAddress/#Port is used and WebSite/#Description is ignored.
5. Once a website is created and gets site id, you can rename it (therefore its site id is not the hash of its name), the WebSite/#SiteId="*" syntax will match on the WebSite/#Description.
So my WiX code ends up looking like:
<DirectoryRef Id="TARGETDIR">
<Component Id="IisSetup" Guid="YOUR-GUID-HERE">
<iis:WebVirtualDir Id="IisVirtualDir" Alias="[IIS_VIRTUALDIRNAME]" Directory="INSTALLLOCATION" WebSite="IisWebsite">
<iis:WebApplication Id="IisWebApplication" Name="[IIS_VIRTUALDIRNAME]" WebAppPool="IisAppPool" Isolation="high"/>
</iis:WebVirtualDir>
<iis:WebAppPool Id="IisAppPool" Name="[IIS_APPPOOLNAME]" Identity="networkService"/>
</Component>
</DirectoryRef>
<!-- Note that this entry should not be put under a component. If it is WiX
will update the website on install and remove it on uninstall -->
<iis:WebSite Id="IisWebsite" Description="[IIS_WEBSITENAME]" SiteId="*">
<iis:WebAddress Id="IisWebAddress" Port="80" />
</iis:WebSite>
The iis:WebAddress element should never be used but is necessary for the project to compile.

In my installer I didnt want to create a Web site. I wanted to allow the user to select an existing website. I did this with a custom action in Javascript, and one custom UI panel.
Custom Action code:
//
// CustomActions.js
//
// Custom Actions usable within WIX For IIS installations.
//
// EnumerateWebSites_CA():
// Adds new UI to the MSI at runtime to allow the user to select a
// website, to which an ISAPI filter will be added.
//
// UpdatePropsWithSelectedWebSite_CA():
// fills session with properties for the selected website.
//
// SetAuthProps_CA():
// sets properties for the needed user and group that needs authorization to the created dir.
//
//
// original idea from:
// http://blog.torresdal.net/2008/10/24/WiXAndDTFUsingACustomActionToListAvailableWebSitesOnIIS.aspx
//
// Mon, 23 Nov 2009 10:54
//
//
// ===================================================================
// http://msdn.microsoft.com/en-us/library/aa372516(VS.85).aspx
var MsiViewModify =
{
Refresh : 0,
Insert : 1,
Update : 2,
Assign : 3,
Replace : 4,
Merge : 5,
Delete : 6,
InsertTemporary : 7, // cannot permanently modify the MSI during install
Validate : 8,
ValidateNew : 9,
ValidateField : 10,
ValidateDelete : 11
};
// http://msdn.microsoft.com/en-us/library/sfw6660x(VS.85).aspx
var Buttons =
{
OkOnly : 0,
OkCancel : 1,
AbortRetryIgnore : 2,
YesNoCancel : 3
};
var Icons=
{
Critical : 16,
Question : 32,
Exclamation : 48,
Information : 64
}
var MsgKind =
{
Error : 0x01000000,
Warning : 0x02000000,
User : 0x03000000,
Log : 0x04000000
};
// http://msdn.microsoft.com/en-us/library/aa371254(VS.85).aspx
var MsiActionStatus =
{
None : 0,
Ok : 1, // success
Cancel : 2,
Abort : 3,
Retry : 4, // aka suspend?
Ignore : 5 // skip remaining actions; this is not an error.
};
//*****************************************************************************
// Purpose: Custom action that enumerates the local websites, and stores their
// properties in the ListBox and AvailableWebSites tables.
// Effects: Fills the ListBox table and creates and fills the AvailableWebSites
// tables.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
function EnumerateWebSites_CA()
{
try
{
LogMessage("function EnumerateWebSites_CA() ENTER");
var c = 1;
var serverBindings, aBindings;
var listboxesView = Session.Database.OpenView("SELECT * FROM ListBox");
listboxesView.Execute();
var record = Session.Installer.CreateRecord(4);
record.StringData(1) = "WEBSITE"; // Property
record.IntegerData(2) = c++; // display order
record.StringData(3) = "Server"; // returned bby the selection
record.StringData(4) = "Server-wide"; // displayed in the UI
listboxesView.Modify(MsiViewModify.InsertTemporary, record);
// Create this table dynamically. We could also create this
// custom table in the WiX .wxs file , but that's not necessary.
// old quote: ``````
// my quote: '''''
// var createCmd = Session.Database.OpenView("CREATE TABLE 'AvailableWebSites' ('WebSiteNo' INT NOT NULL, 'WebSiteDescription' CHAR(50), 'WebSitePort' CHAR(50) NOT NULL, 'WebSiteIP' CHAR(50), 'WebSiteHeader' CHAR(50) PRIMARY KEY 'WebSiteNo')")
var createCmd = Session.Database.OpenView("CREATE TABLE AvailableWebSites (Num INT NOT NULL, Name CHAR(64), Desc CHAR(64), Port CHAR(16) NOT NULL, IP CHAR(32), Hostname CHAR(80) PRIMARY KEY Num)")
createCmd.Execute();
createCmd.Close();
LogMessage("Table 'AvailableWebSites' has been created");
var websitesView = Session.Database.OpenView("SELECT * FROM AvailableWebSites");
websitesView.Execute();
LogMessage("Query from Table 'AvailableWebSites' has returned");
var iis = GetObject("winmgmts://localhost/root/MicrosoftIISv2");
// See the metabase hierarchy diagram here:
// http://msdn.microsoft.com/en-us/library/ms524661.aspx
// http://msdn.microsoft.com/en-us/library/ms525545.aspx
// list "virtual servers", which is the same as websites.
var query = "SELECT * FROM IIsWebServerSetting"
// get the list of virtual servers
var results = iis.ExecQuery(query);
LogMessage("WMI Query completed.");
LogMessage("WMI Query results : " + typeof results);
for(var e = new Enumerator(results); !e.atEnd(); e.moveNext())
{
var site = e.item();
// site.Name // W3SVC/1, W3SVC/12378398, etc
// site.Name.substr(6) // 1, 12378398, etc
// site.ServerComment) // "Default Web Site", "Site2", etc
// site.ServerBindings(0).Port // 80, 8080, etc
LogMessage("Web site " + site.Name);
LogMessage("listbox record");
record = Session.Installer.CreateRecord(4);
record.StringData(1) = "WEBSITE";
record.IntegerData(2) = c++;
record.StringData(3) = site.Name.substr(6); // site.Name;
record.StringData(4) = site.ServerComment + " (" + site.Name + ")";
listboxesView.Modify(MsiViewModify.InsertTemporary, record);
LogMessage("websites record");
LogMessage("website(" + site.Name + ") name(" + site.ServerComment + ") port(" + site.ServerBindings(0).Port + ")");
record = Session.Installer.CreateRecord(6);
record.IntegerData(1) = parseInt(site.Name.substr(6)); // WebSiteNo
record.StringData(2) = site.Name; // name, like W3SVC/1
record.StringData(3) = site.ServerComment; // WebSiteDescription
record.StringData(4) = site.ServerBindings(0).Port; // WebSitePort
record.StringData(5) = site.ServerBindings(0).Ip; // WebSiteIP; maybe empty
record.StringData(6) = site.ServerBindings(0).Hostname; // WebSiteHeader; maybe empty
websitesView.Modify(MsiViewModify.InsertTemporary, record);
}
listboxesView.Close();
websitesView.Close();
LogMessage("function EnumerateWebSites_CA() EXIT");
}
catch (exc1)
{
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException(exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}
//*****************************************************************************
// Purpose: Custom action that copies the selected website's properties from the
// AvailableWebSites table to properties.
// Effects: Fills the WEBSITE_DESCRIPTION, WEBSITE_PORT, WEBSITE_IP, WEBSITE_HEADER
// properties.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
function UpdatePropsWithSelectedWebSite_CA()
{
try
{
LogMessage("function UpdatePropsWithSelectedWebSite_CA() ENTER");
var selectedWebSiteId = Session.Property("WEBSITE");
LogMessage("selectedWebSiteId(" + selectedWebSiteId + ") type(" + typeof selectedWebSiteId + ")");
// check if the user selected anything
if (selectedWebSiteId == "")
{
LogMessage("function UpdatePropsWithSelectedWebSite_CA() EXIT (None)");
return MsiActionStatus.None;
}
if (selectedWebSiteId.toUpperCase() == "SERVER")
{
Session.Property("WEBSITE_NAME") = "W3SVC";
Session.Property("WEBSITE_DESCRIPTION") = "Server";
Session.Property("WEBSITE_PORT") = "";
Session.Property("WEBSITE_IP") = "";
Session.Property("WEBSITE_HEADER") = "";
LogMessage("function UpdatePropsWithSelectedWebSite_CA() EXIT (Ok)");
return MsiActionStatus.Ok;
}
var websitesView = Session.Database.OpenView("SELECT * FROM `AvailableWebSites` WHERE `Num`=" + selectedWebSiteId);
websitesView.Execute();
var record = websitesView.Fetch();
LogMessage("website Fetch() complete");
if (record.IntegerData(1) == parseInt(selectedWebSiteId))
{
Session.Property("WEBSITE_NAME") = record.StringData(2);
Session.Property("WEBSITE_DESCRIPTION") = record.StringData(3);
Session.Property("WEBSITE_PORT") = record.StringData(4);
Session.Property("WEBSITE_IP") = record.StringData(5);
Session.Property("WEBSITE_HOSTNAME") = record.StringData(6);
}
websitesView.Close();
LogMessage("function UpdatePropsWithSelectedWebSite_CA() EXIT (Ok)");
}
catch (exc1)
{
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException(exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}
// Pop a message box. also spool a message into the MSI log, if it is enabled.
function LogException(exc)
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "IisEnumSites: Exception: 0x" + decimalToHexString(exc.number) + " : " + exc.message;
Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}
// spool an informational message into the MSI log, if it is enabled.
function LogMessage(msg)
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "IisEnumSites: " + msg;
Session.Message(MsgKind.Log, record);
}
function decimalToHexString(number)
{
if (number < 0)
{
number = 0xFFFFFFFF + number + 1;
}
return number.toString(16).toUpperCase();
}
// Testing only
function Test1_CA()
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "Hello, this is an error message";
Session.Message(msgKindUser + iconInformation + btnOk, record);
return MsiActionStatus.Ok;
}
Register the Custom Actions like this:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Binary Id="IisScript_CA" SourceFile="CustomActions.js" />
<CustomAction Id="EnumerateWebSites"
BinaryKey="IisScript_CA"
JScriptCall="EnumerateWebSites_CA"
Execute="immediate"
Return="check" />
<CustomAction Id="UpdatePropsWithSelectedWebSite"
BinaryKey="IisScript_CA"
JScriptCall="UpdatePropsWithSelectedWebSite_CA"
Execute="immediate"
Return="check" />
</Fragment>
</Wix>
This is the .wxs for the UI Panel:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI>
<Dialog Id="SelectWebSiteDlg" Width="370" Height="270" Title="Select a Web Site">
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="Next" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="Back" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Please select which web site you want to install to." />
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="Select a Web Site" />
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="SelectWebSiteLabel" Type="Text" X="20" Y="60" Width="290" Height="14" NoPrefix="yes" Text="Select the web site for the filter:" />
<Control Id="SelectWebSiteCombo" Type="ListBox" X="20" Y="75" Width="200" Height="150" Property="WEBSITE" Sorted="yes" />
</Dialog>
</UI>
</Fragment>
</Wix>
The UI panel presents a listbox, which is automatically populated with elements from the ListBox table with the first field of WEBSITE. This table is populated at runtime by the custom action in Javascript.
To invoke the custom action at the right time, you need something like this in the main .wxs file:
<InstallUISequence>
<Custom Action="EnumerateWebSites" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
</InstallUISequence>

Whilst this question and answer are still valid, I think it's worth asking yourself whether you really want to use the website name. I you want to store it for use during uninstallation then saving the site ID is probably a better idea. In which case the website element becomes:
<iis:WebSite Id="IisWebsite" Description="Dummy" SiteId="[IIS_WEBSITEID]">
<iis:WebAddress Id="IisWebAddress" Port="80" />
</iis:WebSite>

Reply on For IisEnumSites:Exception: 0x80004005 : Modify, Mode, Record
I have similar experience and what I found so far is the site Id that extract from parseInt:
record = Session.Installer.CreateRecord(6);
record.IntegerData(1) = parseInt(site.Name.substr(6)); // WebSiteNo
I have a website with a name like W3SVC/1528550093 and I suspect 1528550093 is too big for the AvailableWebSites table.
Once I have the if statement to filter out these big number, and the script work fine.
Hope this help for other.

Based on Cheeso's answer and updated custom action to use C# with Microsoft.Web.Administration rather than Javascript with WMI. Tested against IIS 8.5.
CustomActions.cs:
public class IISCustomActions
{
//*****************************************************************************
// Purpose: Custom action that enumerates the local websites, and stores their
// properties in the ListBox and AvailableWebSites tables.
// Effects: Fills the ListBox table and sets WEBSITE.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
[CustomAction]
public static ActionResult GetWebsites(Session session)
{
ActionResult result = ActionResult.Success;
session.Log("Begin GetWebSites");
try
{
var c = 1;
var listboxesView = session.Database.OpenView("SELECT * FROM ListBox");
listboxesView.Execute();
var iisManager = new ServerManager();
SiteCollection sites = iisManager.Sites;
string firstWebSite = String.Empty;
foreach (Site site in sites)
{
session.Log("Web site " + site.Name);
string itemKey = site.Name;
// Set the default selection if one isn't passed in from the command line
if (("Default Web Site" == itemKey) && String.IsNullOrEmpty(session["WEBSITE"]))
{
session["WEBSITE"] = itemKey;
}
// If this is the first item, store it's name so we can select it as the default selection
// if Default Web Site doesn't exist
if (1 == c)
{
firstWebSite = itemKey;
}
Record listboxItem = new Record(4);
listboxItem.SetString(1, "WEBSITE"); // Property to update
listboxItem.SetInteger(2, c++); // Display order
listboxItem.SetString(3, itemKey); // Key returned by the selection
listboxItem.SetString(4, site.Name); // Displayed in the UI
listboxesView.Modify(ViewModifyMode.InsertTemporary, listboxItem);
session.Log("website record (" + site.Name + ") id(" + site.Id + ")");
}
// They musn't have Default Web Site in their list of sites
if (String.IsNullOrEmpty(session["WEBSITE"]))
{
session["WEBSITE"] = firstWebSite;
}
listboxesView.Close();
}
catch (Exception ex)
{
session.Log(ex.Message);
result = ActionResult.Failure;
}
return result;
}
//*****************************************************************************
// Purpose: Custom action that copies the selected website's properties from the
// AvailableWebSites table to properties.
// Effects: Fills the IISROOT and WEBSITE_PORT
// properties.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
[CustomAction]
public static ActionResult UpdatePropsWithSelectedWebSite(Session session)
{
session.Log("Begin UpdatePropsWithSelectedWebSite");
ActionResult result = ActionResult.Success;
try
{
var selectedWebSiteId = session["WEBSITE"];
session.Log("selectedWebSiteId(" + selectedWebSiteId + ")");
var iisManager = new ServerManager();
Site site = iisManager.Sites[selectedWebSiteId];
session["WEBSITE_PORT"] = site.Bindings[0].EndPoint.Port.ToString();
session["IISROOT"] = site.Applications["/"].VirtualDirectories["/"].PhysicalPath;
session.Log("End UpdatePropsWithSelectedWebSite EXIT (Ok)");
}
catch (Exception ex)
{
session.Log(ex.Message);
result = ActionResult.Failure;
}
return result;
}
}
Register custom action like this:
<Binary Id='WiXCustomActionsDLL' SourceFile='CustomActions.CA.dll' />
<CustomAction Id="GetWebsitesAction"
Return="check"
Execute="immediate"
BinaryKey="WiXCustomActionsDLL"
DllEntry="GetWebsites" />
<InstallUISequence>
<Custom Action='GetWebsitesAction' Before='AppSearch' />
</InstallUISequence>
<!-- Updating IISROOT in the UI does not update the value of it's sub-directory INSTALLLOCATION.
So we have this to force the update of INSTALLLOCATION with a custom action. -->
<CustomAction Id="ChangeDir" Directory="INSTALLLOCATION" Value="[IISROOT]ProjectWebSite" />
<InstallExecuteSequence>
<Custom Action='ChangeDir' After='CostFinalize'></Custom>
</InstallExecuteSequence>
<!-- This populates properties for IISROOT and WEBSITE_PORT after this before files are installed -->
<CustomAction Id="UpdatePropsWithSelectedWebSiteAction"
Return="check"
Execute="immediate"
BinaryKey="WiXCustomActionsDLL"
DllEntry="UpdatePropsWithSelectedWebSite" />
The dialog wxs looks like:
<UI>
<Dialog Id="IISDlg" Width="370" Height="270" Title="[ProductName] Setup" NoMinimize="yes">
<Control Id="SelectWebSiteLabel" Type="Text" X="20" Y="73" Width="100" Height="15" NoPrefix="yes" Text="Select web site:" />
<Control Id="SelectWebSiteCombo" Type="ListBox" X="20" Y="89" Width="200" Height="150" Property="WEBSITE" Sorted="yes" />
<Control Id="VirtualHostLabel" Type="Text" X="235" Y="73" Width="100" Height="15" TabSkip="no" Text="&Application Path Alias:" />
<Control Id="VirtualHostTextbox" Type="Edit" X="235" Y="89" Height="17" Width="120" Property="IIS_VIRTUAL_DIR" Indirect="no" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back">
<Publish Event="NewDialog" Value="MaintenanceTypeDlg" Order="1">Installed</Publish>
<Publish Event="NewDialog" Value="LicenseAgreementDlg" Order="2">NOT Installed</Publish>
</Control>
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
<Publish Event="NewDialog" Value="CMParametersDlg">1</Publish>
<Publish Event="DoAction" Value="UpdatePropsWithSelectedWebSiteAction">1</Publish>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
<Text>Configure settings for your Web Server</Text>
</Control>
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
<Text>{\WixUI_Font_Title}Settings</Text>
</Control>
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
</Dialog>
</UI>
Note the DoAction event in the Next button control. This runs the custom action to update properties using the selected website.
And then follow Dan's answer regarding use of SiteId="*' when applying changes.

Related

Capturing drawing from Nativescript Angular with nativescript-drawingpad

I'm attempting to capture drawings for an app using the native-script drawingpad plugin. I'm working (among other things) from the helpful tutorial provided here:
Capturing Signatures with NativeScript + Angular
My component includes the following:
<GridLayout rows="*,auto" columns="*" backgroundColor="#FFFFFF">
<StackLayout col="0" row="0" horizontalAlignment="center" verticalAlignment="center" backgroundColor="#FFFFFF">
<Label [visibility]="signatureImage ? 'visible' : 'collapsed'" text="Preview" textWrap="true" horizontalAlignment="center" verticalAlignment="center"></Label>
<Image [visibility]="signatureImage ? 'visible' : 'collapsed'" [src]="signatureImage" height="150" width="90%" borderColor="black" borderWidth="2"></Image>
<Label text="Sign/Update below" textWrap="true" horizontalAlignment="center" verticalAlignment="center" marginTop="20"></Label>
<DrawingPad #DrawingPad backgroundcolor="#FEFEFE" height="150" width="90%" id="drawingPad" penColor="#FF6B6B" penWidth="3" borderColor="black"
borderWidth="2">
</DrawingPad>
</StackLayout>
<GridLayout col="0" row="1" rows="auto,*" columns="*,*">
<Button col="0" row="0" colSpan="2" text="Clear Signature" (tap)="clearMyDrawing()"></Button>
<Button col="1" row="1" class="btn -primary" text="Save" (tap)="onDoneTap()"></Button>
</GridLayout>
</GridLayout>
In the TS I have the following:
onDoneTap(): void {
// get reference to the drawing pad
const pad = this.DrawingPad.nativeElement;
// then get the drawing (Bitmap on Android) of the drawingpad
let drawingImage;
pad.getDrawing().then(
(data) => {
console.log(data);
drawingImage = data;
this.signatureImageString = imageSourceModule.fromNativeSource(data).toBase64String("png");
//this.driverSvc.storeSignature(this.DRNO, this.signatureImageString,null,null,null);
},
(err) => {
console.log(err);
}
);
}
Running the application produces the following error at the console:
CONSOLE LOG file: src/app/deliveries/signatures/signatures.component.ts:43:22: <UIImage:0x280feb060 anonymous {729, 150}>
CONSOLE LOG file: node_modules/#nativescript/core/image-source/image-source.ios.js:344:0: fromNativeSource() is deprecated. Use ImageSource constructor instead.
Then it crashes.
Unfortunately the documents as far as I can see do not indicate that fromNativeSource() is deprecated. I need to be able to either 1) save the image as a file to subsequently upload, or 2) save the image as a base64 string for subsequent upload.
Doing it this way solved the problem:
onDoneTap(): void {
// get reference to the drawing pad
const pad = this.DrawingPad.nativeElement;
// then get the drawing (Bitmap on Android) of the drawingpad
pad.getDrawing().then(
( data) => {
const source = new imageSourceModule.ImageSource;
source.setNativeSource(data);
this.signatureImageString = source.toBase64String("png");
})
}

include Facebook Pixel Code js in orchard

I need to add Facebook Pixel Code script in head section of every page.
<!-- Facebook Pixel Code -->
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','//connect.facebook.net/en_US/fbevents.js');
fbq('init', 'xxx');
fbq('track', "PageView");</script>
<noscript><img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id=xxx&ev=PageView&noscript=1"
/></noscript>
<!-- End Facebook Pixel Code -->
How can get this?
I recently started working on orchard. I read about edit cshtml or edit the theme...
Thanks
There should be a Layout.cshtml file in your themes directory, where you can add the script like this:
#using ( Script.Head() )
{
<!-- Facebook Pixel Code -->
<script>
!function (f, b, e, v, n, t, s) {
if (f.fbq) return; n = f.fbq = function () {
n.callMethod ?
n.callMethod.apply(n, arguments) : n.queue.push(arguments)
}; if (!f._fbq) f._fbq = n;
n.push = n; n.loaded = !0; n.version = '2.0'; n.queue = []; t = b.createElement(e); t.async = !0;
t.src = v; s = b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t, s)
}(window,
document, 'script', '//connect.facebook.net/en_US/fbevents.js');
fbq('init', 'xxx');
fbq('track', "PageView");</script>
<noscript>
<img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id=xxx&ev=PageView&noscript=1" />
</noscript>
<!-- End Facebook Pixel Code -->
}
Alternatively, you could copy the Document.cshtml located in *Orchard.Web\Core\Shapes\Views* into your theme and insert the script directly in the head section.

Alloy require could not find module

Problem: Trying out a simple demo for a custom tabgroup in Titanium Alloy. However, the compiler keeps failing with the message Could not find module: common. What I thought would be a straightforward test is anything but.
Titanium SDK: 3.1.3.GA
OS: iOS 7.x
controllers/index.js
var common = require('common');
function tabGroupClicked(e) {
common.tabGroupClicked(e);
}
Alloy.Globals.parent = $;
Alloy.Globals.tabGroup = $.tabGroup;
Alloy.Globals.selectedTab = $.tab1;
$.index.open();
controllers/common.js
exports.tabGroupClicked = function(e){
if (Alloy.Globals.selectedTab !== e.source){
// reset the selected tab
Alloy.Globals.previousTab = Alloy.Globals.selectedTab;
Alloy.Globals.selectedTab = e.source;
// change the selected flag
Alloy.Globals.previousTab.selected = false;
Alloy.Globals.selectedTab.selected = true;
// change the background image
Alloy.Globals.previousTab.backgroundImage = Alloy.Globals.previousTab.disabledImage;
Alloy.Globals.selectedTab.backgroundImage = Alloy.Globals.selectedTab.selectedImage;
// swapping the zindexes of the childTabs
Alloy.Globals.parent.getView(Alloy.Globals.previousTab.childTab).getView().zIndex=2;
Alloy.Globals.parent.getView(Alloy.Globals.selectedTab.childTab).getView().zIndex=3;
}
};
index.xml
<Alloy>
<Window id="index" class="container">
<View id="tabGroupWindow" zIndex="0" class="container">
<Require src="tabThreeView" id="tabThreeView"/>
<Require src="tabTwoView" id="tabTwoView"/>
<Require src="tabOneView" id="tabOneView" />
</View>
<!-- Custom tab group -->
<View id="tabGroup">
<View id="tab1" onClick="tabGroupClicked"></View>
<View id="tab2" onClick="tabGroupClicked"></View>
<View id="tab3" onClick="tabGroupClicked"></View>
</View>
</Window>
</Alloy>
Can anyone see anything that I'm obviously overlooking? I've cleaned the project, restarted Studio, scoured forums for any reference to this issue. Not finding a reference usually means I forgot some basic detail.
Your help is appreciated.
To use the require function, you have to create a service.
So the common.js module as you nammed it, has to be under this folder : app/lib. If it's not in the lib folder, it will not be recognized, and it will not be required.
You can find more help in this page.

Is it possible to have a while loop connected to the mouseover state in an OpenLaszlo app?

Is it possible to do something like this
while (view.mouseover == true) {
preform action
}
I want to have an action repeat for as long as the mouse is over a specific view.
(asked on the laszlo-user mailing list)
Well, it looks like you answered your own question while I was testing my solution to make sure it worked correctly, but here is an alternative solution that works under OpenLaszlo 4.9.0 SWF10 and OpenLaszlo 4.9.0 DHTML run-times:
<canvas width="1000" height="665" debug="true">
<view id="v" bgcolor="0xcccccc" width="200" height="200">
<!--- #param boolean mouseisover: true when the mouse is over -->
<attribute name="mouseisover" type="boolean" value="false" />
<!--- #keywords private -->
<!--- #param lz.Delegate dlgt_repeat: stores the lz.Delegate object -->
<attribute name="dlgt_repeat" type="expression" />
<!--
Called when the 'onmouseover' event occurs
-->
<handler name="onmouseover">
// Step 1) unregister any existing delegate
// mark it for garbage collection
// and prevent its event from triggering:
if (this['dlgt_repeat'])
this.dlgt_repeat.unregisterAll();
// Step 2) update this.mouseisover flag:
if (!this.mouseisover)
this.setAttribute('mouseisover', true);
// Step 3) create an event Delegate and call it
// on the next application idle event:
var objDlgt = new lz.Delegate(this, 'doSomething');
this.setAttribute('dlgt_repeat', objDlgt);
lz.Idle.callOnIdle(this.dlgt_repeat);
</handler>
<!--
Called when the 'onmouseout' event occurs
-->
<handler name="onmouseout">
// Step 1) unregister any existing delegate
// mark it for garbage collection
// and prevent its event from triggering:
if (this['dlgt_repeat'])
this.dlgt_repeat.unregisterAll();
// Step 2) Update this.mouseisover flag:
if (this.mouseisover)
this.setAttribute('mouseisover', false);
</handler>
<!--- #keywords private -->
<!---
Called on application idle event by lz.Idle repeatedly
when the mouse is down.
#param ??? objDummy: required for SWF9+ run-times for methods
called by delegates due to AS3 (ActionScript3 compiler
requirements). Just set default to null to make compiler
happy and ignore...
-->
<method name="doSomething" args="objDummy=null">
<![CDATA[
// Note: CDATA allows '&&' to be used in script below,
// alternatively omit CDATA and use '&&' instead
// of '&&'
// Step 1) Execute your code you want to run here:
if ($debug) Debug.debug('Do something...');
// Step 2): If mouse is still over and the event
// delegate exists then schedule the event to be
// executed upon the next application idle state:
if (this.mouseisover && this['dlgt_repeat'] != null)
lz.Idle.callOnIdle(this.dlgt_repeat);
]]>
</method>
<text text="Move mouse over" />
</view>
</canvas>
Since both ActionScript and JavaScript are single threaded, it's not possible to have a while loop with pauses between each loop iteration. In the SWF10/11 runtime, you need to make sure that the code within each method or function can be executed within one frame (duration depends on the framerate of the SWF clip) of your application.
As a workaround you can use a timer, here is an example:
<canvas debug="true">
<class name="mouseoverview" extends="view"> <attribute name="timer" type="object" value="null" />
<!-- lz.Delegate instance used by the timer -->
<attribute name="timerdel" type="object" value="null" />
<attribute name="timeractive" type="boolean" value="false" />
<!-- milliseconds to pause before each call to doWhileMouseover method -->
<attribute name="tick" type="number" value="500" />
<handler name="onmouseover">
Debug.info('mouseover');
if (this.timeractive == false) {
this.setAttribute('timeractive', true);
this.timerdel = new lz.Delegate( this, "timerCallback" );
this.timer = lz.Timer.addTimer( this.timerdel, this.tick );
// When the timer is activated, do one call to the method
// containing the loop logic. The following calls will be
// handled by the timer and delegate.
this.doWhileMouseover();
}
</handler>
<handler name="onmouseout">
Debug.info('mouseout');
if (this.timeractive) {
this.setAttribute('timeractive', false);
lz.Timer.removeTimer(this.timerdel);
}
</handler>
<method name="timerCallback" args="millis">
if (this.timeractive) {
lz.Timer.resetTimer(this.timerdel, this.tick);
this.doWhileMouseover();
}
</method>
<method name="doWhileMouseover">
Debug.info("This is your virtual while loop for mouseover");
</method>
</class>
<mouseoverview x="100"
y="100"
width="400"
height="400"
bgcolor="#33aaff"
tick="250">
</mouseoverview>
</canvas>
When a mouseover occurs, a timer is started using the timerdel (an instance of lz.Delegate). Then the doWhileMouseover() method is called once directly, and then repeatedly using the timer, as long as no onmouseout event happened.

Displaying an Image/Icon inside an EXT.NET GridPanel Cell depinding on value

Greeting,
I need to display an image/icon AND a value in an EXT.NET cell (of a gridpanel). The value comes from the datatable. The value can either be a string ‘good’ or ‘bad’ and resides in the column ‘Status’.
For example: good accept.png or bad cancel.png.
Layout:
<ext:GridPanel ID="grid" runat="server">
<Store>
<ext:Store ID="Store1" runat="server">
<Reader>
<ext:ArrayReader>
<Fields>
<ext:RecordField Name="status" Mapping="Status" />
</Fields>
</ext:ArrayReader>
</Reader>
</ext:Store>
</Store>
<ColumnModel ID="ColumnModel1" runat="server">
<Columns>
<ext:Column DataIndex="status" Header="Status" Width="160">
</ext:Column>
</Columns>
</ColumnModel>
</ext:GridPanel>
Now I have seen some exaples but I can’t seem to get the picture, I think it has something to do with this:
<script type="text/javascript">
function imgRenderer(value, meta, record, rowIndex, colIndex, store) {
if(data == ‘good’)
{
return "<img src='accept.png'/>"
}
else (data == "bad")
{
return "<img src='cancel.png'/>"
}
}
</script>
More info:
http://miamicoder.com/2009/displaying-an-image-inside-an-ext-js-gridpanel-cell-part-2/
http://techmix.net/blog/2010/11/25/add-button-to-extjs-gridpanel-cell-using-renderer/
I forgot to return the value.
<ext:Column ColumnID="columnStatus" DataIndex="omschrijving" Header="Status" Width="150">
<Renderer Handler="return imgRenderer(value);" />
</ext:Column>
You have options, although I think there is just one minor syntax error that is causing the problem.
Option 1:
In your existing code, you should change data to value.
Example
// existing
if(data == ‘good’)
// revised
if(value == ‘good’)
Option 2:
Rename your images to same value as the value, although this would still require using the value attribute instead of data. Rename accept.png to good.png, and same renaming with the "bad" image. With this change you shouldn't require the if|else statement.
Example
// existing
if(data == ‘good’)
{
return "<img src='accept.png'/>"
}
else (data == "bad")
{
return "<img src='cancel.png'/>"
}
// revised
return '<img src="' + value + '.png"/>';
Hope this helps.

Resources