GUI Extension - TabPage not loading JS - tridion

I am adding a new Tab to the Component Edit screen. My tab is showing fine but the JavaScript is not loading. I would like to write out the URI of the current Component in the tab. I have a feeling the naming of my JavaScript methods is not correct or matching with the config, but really don't know how to say 'hello' from the JavaScript side.
I used the InfoTab View from the Tridion CME (code sample below) and also the PowerTools ItemXml for inspiration - but no luck.
What is the minimum set of methods in the JS to get hello world working?
Solution
Added the dependencies node from Rob's suggestion. The log then showed the js file as loading.
HelloTab.js
Type.registerNamespace("RC");
RC.HelloTab = function RC$HelloTab$HelloTab(element) {
console.log('Constructor');
Tridion.OO.enableInterface(this, "RC.HelloTab");
this.addInterface("Tridion.Controls.DeckPage", [element]); //My extension is like this
};
RC.HelloTab.prototype.initialize = function HelloTab$initialize()
{
console.log('init');
$log.debug('init');
this.callBase("Tridion.Controls.DeckPage", "initialize");
$evt.addEventHandler($display.getItem(), "load", this.getDelegate(this.updateView));
};
RC.HelloTab.prototype.select = function HelloTab$select()
{
console.log('select');
this.callBase("Tridion.Controls.DeckPage", "select");
this.updateView();
};
RC.HelloTab.prototype.updateView = function HelloTab$updateView()
{
console.log('update');
if (this.isSelected())
{
console.log('selected')
}
};
Tridion.Controls.Deck.registerPageType(RC.HelloTab, "RC.HelloTab");
HelloTab.config
<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge"
xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration"
xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions"
xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
<resources cache="true">
<cfg:filters />
<cfg:groups>
<cfg:group name="RC.HelloTab" merge="always">
<cfg:fileset>
<cfg:file type="script">/HelloTab.js</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
</cfg:dependencies>
</cfg:group>
</cfg:groups>
</resources>
<definitionfiles />
<extensions>
<ext:dataextenders/>
<ext:editorextensions>
<ext:editorextension target="CME">
<ext:editurls/>
<ext:listdefinitions/>
<ext:taskbars/>
<ext:commands/>
<ext:commandextensions/>
<ext:contextmenus/>
<ext:lists />
<ext:tabpages>
<ext:add>
<ext:extension assignid="HelloTab" name="Hi There!" insertbefore="InfoTab">
<ext:control>~/HelloTab.ascx</ext:control>
<ext:pagetype>RC.HelloTab</ext:pagetype>
<ext:dependencies>
<cfg:dependency>RC.HelloTab</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="ComponentView">
<ext:control id="MasterTabControl"/>
</ext:view>
</ext:apply>
</ext:extension>
</ext:add>
</ext:tabpages>
<ext:toolbars/>
<ext:ribbontoolbars/>
</ext:editorextension>
</ext:editorextensions>
</extensions>
<commands/>
<contextmenus />
<localization />
<settings>
<defaultpage/><!-- /Views/Default.aspx</defaultpage> -->
<navigatorurl/><!-- /Views/Default.aspx</navigatorurl> -->
<editurls/>
<listdefinitions />
<itemicons/>
<theme>
<path>theme/</path>
</theme>
<customconfiguration />
</settings>
</Configuration>
Original:
HelloTab.config
<resources cache="true">
<cfg:filters />
<cfg:groups>
<cfg:group name="RC.HelloTab" merge="always">
<cfg:fileset>
<cfg:file type="style">{ThemePath}/HelloTab.css</cfg:file>
<cfg:file type="script">/HelloTab/HelloTab.js</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
</cfg:dependencies>
</cfg:group>
</cfg:groups>
</resources>
<definitionfiles />
<extensions>
<ext:dataextenders/>
<ext:editorextensions>
<ext:editorextension target="CME">
<ext:editurls/>
<ext:listdefinitions/>
<ext:taskbars/>
<ext:commands/>
<ext:commandextensions/>
<ext:contextmenus/>
<ext:lists />
<ext:tabpages>
<ext:add>
<ext:extension assignid="HelloTab" name="Hi There!" insertbefore="InfoTab">
<ext:control>~/HelloTab.ascx</ext:control>
<ext:pagetype>HelloTab</ext:pagetype>
<ext:apply>
<ext:view name="ComponentView">
<ext:control id="MasterTabControl"/>
</ext:view>
</ext:apply>
</ext:extension>
</ext:add>
</ext:tabpages>
<ext:toolbars/>
<ext:ribbontoolbars/>
</ext:editorextension>
</ext:editorextensions>
</extensions>
<commands/>
<contextmenus />
<localization />
<settings>
<defaultpage/><!-- /Views/Default.aspx</defaultpage> -->
<navigatorurl/><!-- /Views/Default.aspx</navigatorurl> -->
<editurls/>
<listdefinitions />
<itemicons/>
<theme>
<path>theme/</path>
</theme>
<customconfiguration />
</settings>
JavaScript:
Type.registerNamespace("RC.HelloTab");
RC.HelloTab = function HelloTab(element)
{
Tridion.OO.enableInterface(this, "RC.HelloTab");
this.addInterface("Tridion.Controls.DeckPage", [element]);
};
RC.HelloTab.prototype.initialize = function HelloTab$initialize()
{
$log.event("RC.HelloTab", "RC.HelloTab init");
this.callBase("Tridion.Controls.DeckPage", "initialize");
document.write("something else");
var item = $display.getItem();
if (item)
{
if (item.isLoaded())
{
this._showInfo();
}
else
{
item.load();
}
}
};
RC.HelloTab.prototype.select = function HelloTab$select()
{
this.callBase("Tridion.Controls.DeckPage", "select");
if (this.properties.itemChanged)
{
this._showInfo();
this.properties.itemChanged = false;
}
};
RC.HelloTab.prototype._showInfo = function HelloTab$_showInfo()
{
var item = $display.getItem();
var html = "<h1>title</h1>";
$dom.setOuterHTML($("#title"), html);
document.write('another uri=' + item.ID);
};
RC.HelloTab.prototype._onItemChanged = function HelloTab$_onItemChanged()
{
if (this.isSelected())
{
this._showInfo();
}
};
Tridion.Controls.Deck.registerPageType(RC.HelloTab, "HelloTab");
Tridion.Web.Trace:
w3wp.exe Information: 0 : (634734775171817068) CachedJssControlResources: LastModifiedTime for type HelloTab is 5/24/2012 5:22:26 PM
w3wp.exe Information: 0 : (634734770029374585) CachedJssControlResources: LastModifiedTime for type editors_hellotab_hellotab_ascx is 5/24/2012 5:22:26 PM

I think your problem is you're missing the dependency elements in the ext:extension element.
<ext:dependencies>
<cfg:dependency></cfg:dependency>
</ext:dependencies>
Add like so:
<ext:extension assignid="HelloTab" name="Hi There!" insertbefore="InfoTab">
<ext:control>~/HelloTab.ascx</ext:control>
<ext:pagetype>HelloTab</ext:pagetype>
<ext:dependencies>
<cfg:dependency>RC.HelloTab</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="ComponentView">
<ext:control id="MasterTabControl"/>
</ext:view>
</ext:apply>
</ext:extension>
If you're using Chrome you could put in some console.log statements (alerts if IE) into each of the methods you have implemented from the interface as a test and at the top of the file. E.g.:
console.log('Hello: File loaded');
One difference between your code and a GUI extension I've written recently is your constructor function name does not include the namespace.
I would expect it to be called:
RC.HelloTab = function RC$HelloTab$HelloTab(element)
{
console.log('Constructor');
Tridion.OO.enableInterface(this, "RC.HelloTab.HelloTab"); //Also was missing NS here
this.addInterface("Tridion.Controls.DeckPage", [element]); //My extension is like this
};
Another way to check is to use Chrome developer tools on the page you expect your Javascript to appear, go to the scripts tab and use the search box within the developer tools to search for your namespace.
Hope this helps.

Indeed, the dependency on the group with resource files is missing. That's why js was not present on the page. Btw, there are two ways to make a dependency:
Make a dependency in the ext:extension node (as Rob proposed)
If you have code behind for your HelloTab.ascx, you could use ControlResources attribute for the class definition
[ControlResources("RC.HelloTab")]public class HelloTab{}
Some additional comments:
The recommended way to use registerNamespace is to define only namespace object, but not the class itself. So in the provided code, there should be only Type.registerNamespace("RC");
Tridion.OO.enableInterface used to define the unique interface name for the class and set the API functions to use OO possibilities on the class. To this call actually makes possible to call addInterface method. Interface name could be any string, which should be unique. Usually, we use the name of the class with full namespace. So in this case original version was correct - Tridion.OO.enableInterface(this, "RC.HelloTab");.

Related

Debugging of the UI5 Expression Bindings

Is there any way to debug UI5 Expression Bindings (without Formatters), embedded into XML with DevTools/IDE, but not in SAP WebIDE?
Debug "parse" method of ExpressionParser class. You can test that by creating a very simple app using the following code.
View
<App id="app">
<pages>
<Page id="page" title="{i18n>title}">
<content>
<Text text="{= ${/a} + ${/b} }" />
</content>
</Page>
</pages>
</App>
Controller
onInit: function () {
var oModel = new JSONModel({
a: 2,
b: 3
});
this.getView().setModel(oModel);
}
In DevTools, you can use Ctrl+P to open a file by searching for its name — ExpressionParser. If not, you can find this file in the following path (see image below): /resources/sap/ui/base/ExpressionParser-dbg.js

attachments with Plugin.Messaging.EmailMessageBuilder.withattachemnt throw exception error [duplicate]

I'm trying to take a picture with camera, but I'm getting the following error:
FATAL EXCEPTION: main
Process: com.example.marek.myapplication, PID: 6747
java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.example.marek.myapplication/files/Pictures/JPEG_20170228_175633_470124220.jpg
at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:711)
at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:400)
at com.example.marek.myapplication.MainActivity.dispatchTakePictureIntent(MainActivity.java:56)
at com.example.marek.myapplication.MainActivity.access$100(MainActivity.java:22)
at com.example.marek.myapplication.MainActivity$1.onClick(MainActivity.java:35)
AndroidManifest.xml:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.marek.myapplication.fileprovider"
android:enabled="true"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Java:
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Toast.makeText(getApplicationContext(), "Error while saving picture.", Toast.LENGTH_LONG).show();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.marek.myapplication.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="my_images" path="images/"/>
</paths>
I was searching whole day about this error and trying to understand FileProvider, but I have no idea what this error message tries to tell me. If you want more info/code, write me in the comment.
Your file is stored under getExternalFilesDir(). That maps to <external-files-path>, not <files-path>. Also, your file path does not contain images/ in it, so the path attribute in your XML is invalid.
Replace res/xml/file_paths.xml with:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="my_images" path="/" />
</paths>
UPDATE 2020 MAR 13
Provider path for a specific path as followings:
<files-path/> --> Context.getFilesDir()
<cache-path/> --> Context.getCacheDir()
<external-path/> --> Environment.getExternalStorageDirectory()
<external-files-path/> --> Context.getExternalFilesDir(String)
<external-cache-path/> --> Context.getExternalCacheDir()
<external-media-path/> --> Context.getExternalMediaDirs()
Ref: https://developer.android.com/reference/androidx/core/content/FileProvider
This may resolve everyones problem:
All tags are added so you don't need to worry about folders path.
Replace res/xml/file_paths.xml with:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
EDIT: 1st June 2021
We should use only specific path which we need.
Try alternate path on your own and use which you needed.
See Accepted answer for more information: https://stackoverflow.com/a/42516202/4498813
Got similar problem after enabled flavors (dev, stage).
Before flavors my path resource looked like this:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="my_images"
path="Android/data/pl.myapp/files/Pictures" />
</paths>
After added android:authorities="${applicationId}.fileprovider"
in Manifest appId was pl.myapp.dev or pl.myapp.stage depends on flavor and app started crashing.
I removed full path and replaced it with dot and everything started working.
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="my_images"
path="." />
</paths>
I am sure I am late to the party but below worked for me.
<paths>
<root-path name="root" path="." />
</paths>
If you are using internal cache then use.
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="/" />
</paths>
Lost 2 weeks trying to find some solution... If you arrive here after try everthing above:
1 - Verify if your tag provider are inside tag application
<application>
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.companyname.Pocidadao.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/file_paths"></meta-data>
</provider>
</application>
2 - If you try a lot paths way without success, then test with this:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="." />
<external-path name="external" path="." />
<root-path name="root" path="." />
<files-path name="my_images" path="/" />
<files-path name="my_images" path="myfile/"/>
<files-path name="files" path="." />
<external-path name="external_files" path="." />
<external-path name="images" path="Pictures" />
<external-path name="my_images" path="." />
<external-path name="my_images" path="Android/data/com.companyname.yourproject/files/Pictures" />
<external-path name="my_images" path="Android/data/com.companyname.yourproject/files/Pictures/" />
<external-files-path name="images" path="Pictures"/>
<external-files-path name="camera_image" path="Pictures/"/>
<external-files-path name="external_files" path="." />
<external-files-path name="my_images" path="my_images" />
<external-cache-path name="external_cache" path="." />
</paths>
Test this, if camera works, then start eliminate some lines and continue testing...
3 - No forget verify if camera are actived in your emulator.
Be aware that external-path is not pointing to your secondary storage, aka "removable storage" (despite the name "external"). If you're getting "Failed to find configured root" you may add this line to your XML file.
<root-path name="root" path="." />
See more details here FileProvider and secondary external storage
This confusing me a bit too.
The problem is on "path" attribute in your xml file.
From this document FileProvider
'path' is a subdirectory,
but in another document (camera/photobasics) shown
'path' is full path.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>
I just change this 'path' to full path and it just work.
I would be late but I found a solution for it.Working fine for me, I just changed the paths XML file to:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="root" path="." />
</paths>
Check how many storages your device offers - sharing files from secondary storage is not supported. Look at FileProvider.java source (from support-core-utils 25.3.1):
} else if (TAG_EXTERNAL_FILES.equals(tag)) {
File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
if (externalFilesDirs.length > 0) {
target = externalFilesDirs[0];
}
} else if (TAG_EXTERNAL_CACHE.equals(tag)) {
File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context);
if (externalCacheDirs.length > 0) {
target = externalCacheDirs[0];
}
}
So, they take only the first storage.
Also, you can see that getExternalCacheDirs() is used to obtain list of storages through the ContextCompat interface. See documentation for its limits (it's told to not recognize USB Flashes for example). Best is to make some debug output of list of storages from this API on your own, so that you can check that path to storage matches the path passed to getUriForFile().
There's already a ticket assigned (as for 06-2017) in Google's issue tracker, asking to support more than one storage. Eventually, I found SO question on this as well.
I have spent 5 hours for this..
I have tried all the methods above but it depends on the what storeage your app currently using.
https://developer.android.com/reference/android/support/v4/content/FileProvider#GetUri
Check the documentation before trying the codes.
In my case
since files-path sub directory will be Context.getFilesDir().
The funky thing is it Context.getFilesDir() notes one another subdirectory.
what I am looking for is
data/user/0/com.psh.mTest/app_imageDir/20181202101432629.png
Context.getFilesDir()
returns
/data/user/0/com.psh.mTest/files
so the tag should be
....files-path name="app_imageDir" path="../app_imageDir/" ......
Then it works!!
None of this worked for me. The only approach that works is not to declare an explicit path in xml. So do this and be happy:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
</paths>
Here too has a excelent tutorial about this question:
https://www.youtube.com/watch?v=9ZxRTKvtfnY&t=613s
My issue was that I had overlapping names in the file paths for different types, like this:
<cache-path
name="cached_files"
path="." />
<external-cache-path
name="cached_files"
path="." />
After I changed the names ("cached_files") to be unique, I got rid of the error. My guess is that those paths are stored in some HashMap or something which does not allow duplicates.
This Worked for me as well.Instead of giving full path i gave path="Pictures" and it worked fine.
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="images"
path="Pictures">
</external-files-path>
</paths>
What I did to solve this -
AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths"/>
</provider>
filepaths.xml (Allowing the FileProvider to share all the files that are inside the app's external files directory)
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="files"
path="/" />
</paths>
and in java class -
Uri fileProvider = FileProvider.getUriForFile(getContext(),"com.mydomain.fileprovider",newFile);
It depends what kind of storage you want to do, INTERNAL or EXTERNAL
for EXTERNAL STORAGE
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="my_images" path="my_images" />
</paths>
but for INTERNAL STORAGE, be careful with the path, because it uses getFilesDir() method
which mean that your file will be located in the root directory for the app ("/")
File storeDir = getFilesDir(); // path "/"
So your provider file must be like this:
<paths>
<files-path name="my_images" path="/" />
</paths>
Change your main/res/xml/provider_paths.xml
From
<paths>
<files-path path="images/" name="myimages" />
<external-path name="download" path="download/"/>
</paths>
To
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
I had the same problem, I tried the below code for its working.
1.Create Xmlfile : provider_paths
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="my_images" path="myfile/"/>
</paths>
2. Mainfest file
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.ril.learnet.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
3.In your Java file.
File file = new File(getActivity().getFilesDir(), "myfile");
if (!file.exists()) {
file.mkdirs();
}
String destPath = file.getPath() + File.separator + attachmentsListBean.getFileName();
file mfile = new File(destPath);
Uri path;
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
path = FileProvider.getUriForFile(AppController.getInstance().getApplicationContext(), AppController.getInstance().getApplicationContext().getPackageName() + ".provider", mfile );
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
path = Uri.fromFile(mfile);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(path, "image/*");
getActivity().startActivity(intent);
none of the above worked for me,
after a few hours debugging I found out that the problem is in createImageFile(), specifically absolute path vs relative path
I assume that you guys are using the official Android guide for taking photo. https://developer.android.com/training/camera/photobasics
private static File createImageFile(Context context) throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
Take note of the storageDir, this is the location where the file will be created. So in order to get the absolute path of this file, I simply use image.getAbsolutePath(), this path will be used in onActivityResult if you need the Bitmap image after taking photo
below is the file_path.xml, simply use . so that it uses the absolute path
<paths>
<external-path
name="my_images"
path="." />
</paths>
and if you need the bitmap after taking photo
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Bitmap bmp = null;
try {
bmp = BitmapFactory.decodeFile(mCurrentPhotoPath);
} catch (Exception e) {
e.printStackTrace();
}
}
If nothing helps and you are getting the error
failed to find configured root that contains /data/data/...
then try changing some line like:
File directory = thisActivity.getDir("images", Context.MODE_PRIVATE);
to:
File directory = new File(thisActivity.getFilesDir(), "images");
and in the xml file:
<files-path name="files" path="." />
which is weird, since the folder I access is /images.
The problem might not just be the path xml.
Following is my fix:
Looking into the root course in android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile():
public File getFileForUri(Uri uri) {
String path = uri.getEncodedPath();
final int splitIndex = path.indexOf('/', 1);
final String tag = Uri.decode(path.substring(1, splitIndex));
path = Uri.decode(path.substring(splitIndex + 1));
final File root = mRoots.get(tag); // mRoots is parsed from path xml
if (root == null) {
throw new IllegalArgumentException("Unable to find configured root for " + uri);
}
// ...
}
This means mRoots should contains the tag of requested uri. So I write some code to print mRoots and tag of uri, and then easily find the tags do not match.
It comes out that setting provider authority as ${applicationID}.provider is a stupid idea! This authority is so common that might be used by other providers, which will mess up the path config!
I was getting this error Failed to find configured root that contains...
The following work around resolves my issue
res/xml/file_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="media" path="." />
</paths>
AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="[PACKAGE_NAME]"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
ActivityClass.java
void shareImage() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(this,"com.slappstudio.pencilsketchphotomaker", selectedFilePath));
startActivity(Intent.createChooser(intent,getString(R.string.string_share_with)));
}
Android official document says file_paths.xml should have:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images"
path="Android/data/com.example.package.name/files/Pictures" />
</paths>
But to make it work in the latest android there should be a "/" at the end of the path, like this:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images"
path="Android/data/com.example.package.name/files/Pictures/" />
</paths>
The following change in xml file_paths file worked for me.
<external-files-path name="my_images" path="Pictures"/>
<external-files-path name="my_movies" path="Movies"/>
My Problem Was Wrong File Name:
I Create file_paths.xml under res/xml while resource was set to provider_paths.xml in Manifest:
<provider
android:authorities="ir.aghigh.radio.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
I changed provider_paths to file_paths and problem Solved.
If your file path is like /storage/emulated/0/"yourfile"
you just need modify your FileProvider xml
<paths>
<external-path name="external" path="." />
</paths>
then call this function when you need share file
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
Uri fileUri = FileProvider.getUriForFile(getContext(),
"com.example.myapp.fileprovider",
file);
sharingIntent.setType("*/*"); // any flie type
sharingIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
startActivity(Intent.createChooser(sharingIntent, "Share file"));
it works on android M ~ Pie
The Answer from #CommonsWare is great.
But in my case, I had to add multiple paths.
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<files-path name="external_files" path="." />
</paths>
I see that at least you're not providing the same path as others in file_paths.xml.
So please make sure you provide the exact the same package name or path in 3 places including:
android:authorities attribute in manifest
path attribute in file_paths.xml
authority argument when calling FileProvider.getUriForFile().
For Xamarin.Android users
This could also be the result of not updating your support packages when targeting Android 7.1, 8.0+. Update them to v25.4.0.2+ and this particular error might go away(giving you´ve configured your file_path file correctly as others stated).
Giving context: I switched to targeting Oreo from Nougat in a
Xamarin.Forms app and taking a picture with the Xam.Plugin.Media
started failing with the above error message, so updating the packages
did the trick for me ok.
I noticed that the policy or behaviour regarding the path.xml file changed between Support Library 26 and 27. For capturing a picture from the camera, I saw the following changes:
With 26, I had to use <external-path> and the full path given in the path argument.
With 27, I had to use <external-files-path> and only the subfolder in the path argument.
So what specifically worked for me with Support Library 27 as the path.xml file was
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="camera_image" path="Pictures/"/>
</paths>

How do you configure an SDL Tridion CME extension for a subset of views?

I have created a new editor for SDL Tridion which adds some new functionality to the ribbon bar. This is enabled by adding the following snippet to the editor.config
<!-- ItemCommenting PowerTool -->
<ext:extension assignid="ItemCommenting" name="Save and<br/>Comment" pageid="HomePage" groupid="ManageGroup" insertbefore="SaveCloseBtn">
<ext:command>PT_ItemCommenting</ext:command>
<ext:title>Save and Comment</ext:title>
<ext:issmallbutton>false</ext:issmallbutton>
<ext:dependencies>
<cfg:dependency>PowerTools.Commands</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="*" />
</ext:apply>
</ext:extension>
This is applied to all views by using a wildcard value in the node. This has results in my new button being added to the ribbon of every view, including the main dashboard. Is there a way to add this to all views except for the dashboard? Or do I have to create something like this?
<ext:apply>
<ext:view name="PageView" />
<ext:view name="ComponentView" />
<ext:view name="SchemaView" />
</ext:apply>
If this is the only way to achieve the result I need, is there a list of all the view names somewhere?
Workaround provided by Jaime will not gonna work, because:
Ribbon Toolbar will hide buttons only on the Create tab if isAvailable
method of the corresponding command will return false.
Most of the buttons in RibbonToolbar implements specific Tridion.Controls.RibbonButton interface. This means, when you'll try
to get Tridion.Controls.Button control for the same element - you'll
get totally different control, based on the same html element. So
RibbonToolbar will not know about it and it will work incorrectly.
If you want to hide button in RibbonToolbar, you should use public methods on RibbonToolbar and RibbonPage instead. Thus it will be correctly handled by RibbonToolbar. Example:
var toolbar = $controls.getControl($("#ItemToolbar"), "Tridion.Controls.RibbonToolbar");
var page = toolbar.getPageById("HomePage");
page.hideItem(buttonId);
page.showItem(buttonId);
As for the original question, here is pretty simple and easiest solution:
<ext:add>
<ext:extension assignid="ItemCommenting" name="Save and<br/>Comment" pageid="HomePage" groupid="ManageGroup" insertbefore="SaveCloseBtn">
<ext:command>PT_ItemCommenting</ext:command>
<ext:title>Save and Comment</ext:title>
<ext:issmallbutton>false</ext:issmallbutton>
<ext:dependencies>
<cfg:dependency>PowerTools.Commands</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="*" />
</ext:apply>
</ext:extension>
</ext:add>
<ext:remove>
<ext:extension id="ItemCommenting">
<ext:apply>
<ext:view name="DashboardView" />
</ext:apply>
</ext:extension>
</ext:remove>
As far as I know you need to specify all the views or use the wildcard. It will be nice that the isAvailable functionality would work for the Ribbon Tool bar buttons, right? Meaning that if the command returns false in the _isAvailable method, the button will not show...
Well, I found a work around. You can do something like this within your isAvailable method in your command:
Your.Namespace.PT_ItemCommenting.prototype._isAvailable = function PT_ItemCommenting$_isAvailable(selection) {
var isAvailable = $display.getView().getId()!='DashboardView';
if(isAvailable){
return true;
}
var button = $controls.getControl($("#ItemCommenting"), "Tridion.Controls.Button");
button.hide();
return false;
};
I think this is actually a good practice, since it will "hide" the commands if they shouldn't be available, right?
Let me know how it works out.

How to redirect to the different pages in Tridion using Ribbon Toolbar button?

Am implementing the Ribbon toolbar button. On button click depending upon the schema name, I need to create the popup with corresponding url (Aspx page). Previously I worked with only one aspx page and I am succeeded in the same, I created a popup java script file with the same name as aspx page and configured it in configuration file. But in case of multiple aspx pages even if I create multiple popup javascript files.It is not calling the respected javascript file.
How to map the popup java script files to aspx pages in case of multiple aspx pages?
PFB the code samples.
Button java script file code fragment:
if (some condition) {
//Creating the url
var url = "Editors/RTFExtension/Popups/ButtonReferencePopup_2.aspx?schemaId='" + schemaId + "'";
var popup = $popup.create(url, "toolbar=no,width=500,height=200,resizable=yes,scrollbars=yes", null);
}
else{
//Creating the url
var url = "Editors/RTFExtension/Popups/ButtonReferencePopup.aspx?schemaId='" + schemaId + "'";
var popup = $popup.create(url, "toolbar=no,width=500,height=200,resizable=yes,scrollbars=yes", null);
}
Config file code fragment:
<cfg:group name="RTFExtension.ButtonReference">
<cfg:fileset>
<cfg:file type="script">/Popups/ButtonReferencePopup.js</cfg:file>
<cfg:file type="script">/Popups/ButtonReferencePopup_2.js</cfg:file>
<cfg:file type="style">/Themes/ButtonReference.css</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Controls</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
</cfg:dependencies>
</cfg:group>
Please help me out in this issue. Thanks in advance. Early response is appreciated.
I believe you need to configure different groups for each config - something like this:
<cfg:group name="RTFExtension.ButtonReference.Popup1">
<cfg:fileset>
<cfg:file type="script">/Popups/ButtonReferencePopup.js</cfg:file>
<cfg:file type="style">/Themes/ButtonReference.css</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Controls</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
</cfg:dependencies>
</cfg:group>
<cfg:group name="RTFExtension.ButtonReference.Popup2">
<cfg:fileset>
<cfg:file type="script">/Popups/ButtonReferencePopup_2.js</cfg:file>
<cfg:file type="style">/Themes/ButtonReference.css</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Controls</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
</cfg:dependencies>
</cfg:group>
In your popup code-behind you then need to reference the relevant group:
namespace Button.Reference.Popups
{
[ControlResourcesDependency(new Type[] { typeof(Popup), typeof(Tridion.Web.UI.Controls.Button), typeof(Stack), typeof(Dropdown), typeof(List) })]
[ControlResources("RTFExtensions.ButtonReference.Popup1")]
public partial class PopupReference1 : TridionPage
Or:
namespace Button.Reference.Popups
{
[ControlResourcesDependency(new Type[] { typeof(Popup), typeof(Tridion.Web.UI.Controls.Button), typeof(Stack), typeof(Dropdown), typeof(List) })]
[ControlResources("RTFExtensions.ButtonReference.Popup2")]
public partial class PopupReference2 : TridionPage

GUI Extensions - Resources failing to load

I'm kinda at a loss as to what exactly I'm doing wrong, so hopefully by throwing this out there someone should be able to point something hopefully obvious out to me.
A new GUI extension is being created that will sit as a button on a new Events tab of the Tridion ribbon bar. I can get the button to appear, however no icon appears for the button and is always disabled, which leads me to believe the stylesheet and javascript resources for the extension are not loading :S
My editor config is as follows:
<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge" xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration" xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions" xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
<resources>
<cfg:filters/>
<cfg:groups>
<cfg:group name="Events.Cvent">
<cfg:fileset>
<cfg:file type="style">/Theme/cvent.css</cfg:file>
<cfg:file type="reference">Events.Commands.Cvent</cfg:file>
</cfg:fileset>
</cfg:group>
<cfg:group name="Events.Cvent.Commandset">
<cfg:fileset>
<cfg:file type="script">/Scripts/cvent.js</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
<cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
</cfg:dependencies>
</cfg:group>
</cfg:groups>
</resources>
<definitionfiles/>
<extensions>
<ext:dataextenders/>
<ext:editorextensions>
<ext:editorextension target="CME">
<ext:editurls/>
<ext:listdefinitions/>
<ext:taskbars/>
<ext:commands/>
<ext:commandextensions/>
<ext:contextmenus/>
<ext:lists/>
<ext:tabpages/>
<ext:toolbars/>
<ext:ribbontoolbars>
<ext:add>
<ext:extension assignid="EventsPage" name="Events">
<ext:control/>
<ext:pagetype/>
<ext:apply>
<ext:view name="DashboardView">
<ext:control id="DashboardToolbar"/>
</ext:view>
</ext:apply>
</ext:extension>
<ext:extension assignid="EventsAdministrationGroup" pageid="EventsPage" name="Administration">
<ext:group/>
<ext:apply>
<ext:view name="DashboardView">
<ext:control id="DashboardToolbar"/>
</ext:view>
</ext:apply>
</ext:extension>
<ext:extension assignid="CventBtn" groupid="EventsAdministrationGroup" name="Import Cvent Events" pageid="EventsPage">
<ext:command>Cvent</ext:command>
<ext:title>Import Cvent Events</ext:title>
<ext:dependencies>
<cfg:dependency>Events.Cvent</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="DashboardView">
<ext:control id="DashboardToolbar"/>
</ext:view>
</ext:apply>
</ext:extension>
</ext:add>
</ext:ribbontoolbars>
</ext:editorextension>
</ext:editorextensions>
<ext:modelextensions/>
</extensions>
<commands>
<cfg:commandset id="Events.Commands.Cvent">
<cfg:command name="Cvent" implementation="Events.Commands.OpenCvent"/>
<cfg:dependencies>
<cfg:dependency>Events.Cvent.Commandset</cfg:dependency>
</cfg:dependencies>
</cfg:commandset>
</commands>
<contextmenus/>
<localization/>
<settings>
<defaultpage/>
<editurls/>
<listdefinitions/>
<theme>
<path>/Theme/</path>
</theme>
<customconfiguration>
<clientconfiguration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge">
<Cventurl xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge">/Cvent/Cvent.aspx</Cventurl>
</clientconfiguration>
</customconfiguration>
</settings>
</Configuration>
All the resource files (javascript and css) I need are there in the directories as according to the config. My JS for GUI looks like the following:
Type.registerNamespace("Events.Commands");
Events.Commands.OpenCvent = function Commands$OpenCvent(name)
{
Type.enableInterface(this, "Events.Commands.OpenCvent");
this.addInterface("Tridion.Cme.Command", ["Cvent"]);
this.properties.url;
};
Events.Commands.OpenCvent.prototype._isAvailable = function OpenCvent$_isAvailable(selection, pipeline)
{
return true;
};
Events.Commands.OpenCvent.prototype._isEnabled = function OpenCvent$_isEnabled(selection, pipeline)
{
return true;
};
Events.Commands.OpenCvent.prototype._execute = function OpenCvent$_execute(selection, pipeline)
{
window.open('www.google.com');
};
Restarted Tridion and still nothing, what am I doing wrong?
You can check whether or not your files are included by loading the CME with the ?mode=js and ?mode=css parameters.
Don't forget that those files are heavily cached - and just changing the configuration does not invalidate the cache. You need to either increase the #modification attribute in System.config (to invalidate the cache of all clients) - or simply clear your browser cache manually (easiest while developing).
If your changes are not in those files, it's likely a problem with your editor config. As Chris pointed out, files are only included if something else is included which has a dependency on it. If you enable tracing, you can see why your files are not being included in the resulting log file (Tridion.Web.trace).
Check out section 6 of this article for more information on how to do that: http://www.sdltridionworld.com/articles/sdltridion2011/tutorials/debugging_the_tridion_2011_cme.aspx
I can't tell you what exactly is wrong with your extension, but maybe you can have a look at an existing GUI Extension (in fact several extensions) and maybe you can compare what is wrong with yours. Have a look at the PowerTools http://code.google.com/p/tridion-2011-power-tools/
Also some good examples on http://www.sdltridionworld.com, e.g. http://www.sdltridionworld.com/articles/sdltridion2011/tutorials/GUIextensionIn8steps.aspx
I believe the dependencies will not actually get loaded unless they are used and referenced from within the comandset nodes of the config. Could you include your complete editor.config rather than just the extract?

Resources