I am currently trying to setup a project to implement localization on javascript files (as described here) but at the same time I'd like to bundle and minify the javascript in the project. I followed a tutorial on bundling and minification here
I have been able to get both working separately, but when I try to get them working together I cannot get the localisation working properly. I think this is because bundling creates it's own route handling for the bundled/minified javascript it generates, so the httpHandler I have defined in the webconfig gets ignored. I keep getting javascript errors saying "CustomTranslate is not defined".
I am trying to do this because we are building a number of controls using ExtJS, but we need to be able to apply localisation to those controls. Any help/ideas on how I can get them to work together would be appreciated.
I am not using MVC, but doing this in asp.net in Visual Studio 2012.
Here is my code:
BundleConfig.cs
namespace TranslationTest
{
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
//default bundles addeed here...
bundles.Add(new ScriptBundle("~/bundles/ExtJS.axd").Include("~/Scripts/ExtJS/ext-all.js", "~/Scripts/ExtJS/TestForm.js"));
}
}
}
web.config:
<globalization uiCulture="auto" />
<httpHandlers>
<add verb="*" path="/bundles/ExtJS.axd" type="TranslationTest.ScriptTranslator, TranslationTest" />
</httpHandlers>
Default.aspx
<%# Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TranslationTest._Default" %>
<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
<script src="/bundles/ExtJS.axd"></script>
</asp:Content>
TestForm.js:
Ext.require([
'Ext.form.*',
'Ext.layout.container.Column',
'Ext.tab.Panel'
]);
Ext.onReady(function () {
Ext.QuickTips.init();
var bd = Ext.getBody();
bd.createChild({ tag: 'h2', html: 'Form 1' });
var simple = Ext.create('Ext.form.Panel', {
url: 'save-form.php',
frame: true,
title: 'Simple Form',
bodyStyle: 'padding:5px 5px 0',
width: 350,
fieldDefaults: {
msgTarget: 'side',
labelWidth: 75
},
defaultType: 'textfield',
defaults: {
anchor: '100%'
},
items: [{
fieldLabel: CustomTranslate(FirstName),
name: 'first',
allowBlank: false
}, {
fieldLabel: CustomTranslate(LastName),
name: 'last'
}, {
fieldLabel: CustomTranslate(Company),
name: 'company'
}, {
fieldLabel: CustomTranslate(Email),
name: 'email',
vtype: 'email'
}, {
xtype: 'timefield',
fieldLabel: CustomTranslate(Time),
name: 'time',
minValue: '8:00am',
maxValue: '6:00pm'
}],
buttons: [{
text: CustomTranslate(Save)
}, {
text: CustomTranslate(Cancel)
}]
});
simple.render(document.body);
});
Currently the FirstName, LastName, etc are all stored in resource files, as in the linked example above.
ScriptTranslator.cs
namespace TranslationTest
{
public class ScriptTranslator : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
string relativePath = context.Request.AppRelativeCurrentExecutionFilePath.Replace(".axd", string.Empty);
string absolutePath = context.Server.MapPath(relativePath);
string script = ReadFile(absolutePath);
string translated = TranslateScript(script);
context.Response.Write(translated);
Compress(context);
SetHeadersAndCache(absolutePath, context);
}
#endregion
private void SetHeadersAndCache(string file, HttpContext context)
{
context.Response.AddFileDependency(file);
context.Response.Cache.VaryByHeaders["Accept-Language"] = true;
context.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
context.Response.Cache.SetLastModifiedFromFileDependencies();
context.Response.Cache.SetExpires(DateTime.Now.AddDays(7));
context.Response.Cache.SetValidUntilExpires(true);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
}
#region Localization
private static Regex REGEX = new Regex(#"CustomTranslate\(([^\))]*)\)", RegexOptions.Singleline | RegexOptions.Compiled);
private string TranslateScript(string text)
{
MatchCollection matches = REGEX.Matches(text);
ResourceManager manager = new ResourceManager(typeof(TranslationTest.App_GlobalResources.text));
foreach (Match match in matches)
{
object obj = manager.GetObject(match.Groups[1].Value);
if (obj != null)
{
text = text.Replace(match.Value, CleanText(obj.ToString()));
}
}
return text;
}
private static string CleanText(string text)
{
text = text.Replace("'", "\\'");
text = text.Replace("\\", "\\\\");
return text;
}
private static string ReadFile(string absolutePath)
{
if (File.Exists(absolutePath))
{
using (StreamReader reader = new StreamReader(absolutePath))
{
return reader.ReadToEnd();
}
}
return null;
}
#endregion
#region Compression
private const string GZIP = "gzip";
private const string DEFLATE = "deflate";
private static void Compress(HttpContext context)
{
if (IsEncodingAccepted(DEFLATE, context))
{
context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
SetEncoding(DEFLATE, context);
}
else if (IsEncodingAccepted(GZIP, context))
{
context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
SetEncoding(GZIP, context);
}
}
private static bool IsEncodingAccepted(string encoding, HttpContext context)
{
return context.Request.Headers["Accept-encoding"] != null && context.Request.Headers["Accept-encoding"].Contains(encoding);
}
private static void SetEncoding(string encoding, HttpContext context)
{
context.Response.AppendHeader("Content-encoding", encoding);
}
#endregion
}
}
global.asax.cs
namespace TranslationTest
{
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
Microsoft.Web.Optimization.BundleTable.Bundles.EnableDefaultBundles();
BundleConfig.RegisterBundles(System.Web.Optimization.BundleTable.Bundles);
AuthConfig.RegisterOpenAuth();
}
}
}
I hope I've covered everything, but please let me know if there's anything missing. Thanks in advance!!
Ok, I've set up everything in your example and I've got it to work but you need to use the IBundleTransform interface. The details of everything I did are posted below..
I had to create a class to handle the bundle transformation (i.e the translation) instead of allowing the default behaviour.
public class JsLocalizationTransform : IBundleTransform
{
public JsLocalizationTransform(){}
#region IBundleTransform Members
public void Process(BundleContext context, BundleResponse response)
{
string translated = TranslateScript(response.Content);
response.Content = translated;
}
#endregion
#region Localization
private static Regex REGEX = new Regex(#"CustomTranslate\(([^\))]*)\)", RegexOptions.Singleline | RegexOptions.Compiled);
private string TranslateScript(string text)
{
MatchCollection matches = REGEX.Matches(text);
ResourceManager manager = new ResourceManager(typeof(TranslationTest.App_GlobalResources.text));
foreach (Match match in matches)
{
object obj = manager.GetObject(match.Groups[1].Value);
if (obj != null)
{
text = text.Replace(match.Value, CleanText(obj.ToString()));
}
}
return text;
}
private static string CleanText(string text)
{
//text = text.Replace("'", "\\'");
text = text.Replace("\\", "\\\\");
return text;
}
#endregion
}
Then in BundleConfig.RegisterBundles method you need to create and add the bundle like this:
var extjsBundle = new Bundle("~/bundles/ExtJS").Include("~/Scripts/ExtJS/ext-all.js", "~/Scripts/ExtJS/TestForm.js");
extjsBundle.Transforms.Clear();
extjsBundle.Transforms.Add(new JsLocalizationTransform());
extjsBundle.Transforms.Add(new JsMinify());
bundles.Add(extjsBundle);
I could then remove the HttpHandler from web.config as that gets configured automatically through the bundler. I also had to make some changes to the Application_Start method in global.asax.cs
void Application_Start(object sender, EventArgs e)
{
//Microsoft.Web.Optimization.BundleTable.Bundles.EnableDefaultBundles();
BundleTable.EnableOptimizations = true; //Added this line..
BundleConfig.RegisterBundles(System.Web.Optimization.BundleTable.Bundles);
AuthConfig.RegisterOpenAuth();
}
Because the JSLocalisationTransform class is handling the bundle transformation and translation, I completely removed the ScriptTranslator class.
Hope that helps.
Related
I have added the Facebook js-sdk as a directive in an angular app, I have a button the switch the pages language my problem is that the sdk does not update the text for the Like button, even after I removed the sdk and add a new one for the selected language. I ran the FB.XFBML.parse() to update the txt but it will keep on using the first selected language only after a page refresh would changes take place
UPDATED CODE
Now Using a service with ngrx when language change it will replace the Facebook SDK for that language and dispatches an event for any the page directives to remove all the tags/classes Facebook added on the like button element and clear its children. Everything works the button show up but its still in the first language selected it does not change language
Code Service
#Injectable()
export class FacebookService {
constructor(private window: any, private document, private store: Store<fromRoot.State>) {
store.pipe(select(fromCore.getSelectedLang)).subscribe(lang => this.switchScript(lang));
if (!this.window.fbAsyncInit) {
this.window.fbAsyncInit = () => {
this.window.FB.init({
appId: environment.appId,
autoLogAppEvents: true,
cookie: true,
xfbml: true,
version: 'v4.0',
});
};
}
}
switchScript(lang: string) {
const langUrl = lang === 'ar' ? 'ar_AR' : 'en_US';
const newScript = document.createElement('script');
newScript.id = 'facebook-sdk';
newScript.async = true;
newScript.src = `https://connect.facebook.net/${langUrl}/sdk.js`;
const scriptTag = this.document.getElementById('facebook-sdk');
if (scriptTag) {
scriptTag.parentNode.replaceChild(newScript, scriptTag);
} else {
this.document.body.appendChild(newScript);
}
this.store.dispatch(SocialAction.UpdateTags({lang}));
}
}
Code Directive
#Directive({selector: '[lyxFacebook]'})
export class FacebookDirective implements AfterViewInit, OnDestroy {
tag$: Subscription;
constructor(private window: any,private document, private store: Store<fromRoot.State>) {
this.tag$ = store.pipe(select(Core.getTagLang)).subscribe(lang=> this.update(lang));
}
ngAfterViewInit(): void {
if (this.window.FB) {
this.window.FB.XFBML.parse();
}
}
update(lang: string) {
if (this.window.FB && lang) {
const tags = this.document.getElementsByClassName('fb-like');
if (tags && tags.length) {
for (let i = 0; i < tags.length; i++) {
tags[i].removeAttribute('fb-xfbml-state');
tags[i].removeAttribute('fb-iframe-plugin-query');
tags[i].classList.remove('fb_iframe_widget');
tags[i].innerHTML = '';
}
this.window.FB.XFBML.parse();
}
}
}
ngOnDestroy(): void {
if (this.tag$) {
this.tag$.unsubscribe();
}
}
}
Here is my model class
public class ProductModel
{
public Product {set;set;} // Product is one more class
}
I am using below javascript code to get partial view but 'model' is not getting deserialised in controller...What I am missing?
Storing data in a HTML attribute as shown below
JavaScriptSerializer serializer = new JavaScriptSerializer();
var jsonObject = serializer.Serialize(obj)
<span data-singleproduct="#jsonObject" id="#mprodid" class="ShowProductModal">Find out more..</span>
Used jQuery to call partial page and popup
$('.ShowProductModal').on('click', function () {
var model = $(this).data('singleproduct');
//I can see data of variable model here in developer tool
$("#ProductModal").dialog({
autoOpen: true,
position: { my: "center", at: "top+350", of: window },
width: 1000,
resizable: false,
title: '',
modal: true,
open: function () {
$(this).load('ShowProductModal', model );
},
buttons: {
}
});
return false;
});
Here is my controller code
public PartialViewResult ShowProductModal(ProductModel product)
{
return PartialView("ProductModal", product);
}
product always comes as null!!!
If I change ProductModel to Product , then it will work... ! CAN SOMEONE HELP ME?
public PartialViewResult ShowProductModal(Product product)
{
return PartialView("ProductModal", product);
}
You should try
$(this).load('ShowProductModal', { product: model });
And declare your method like this:
[HttpPost]
public PartialViewResult ShowProductModal([FromBody] JObject data)
{
var product = data["product"].ToObject<ProductModel>();
return PartialView("_SC5ProductModal", product);
}
I'm trying to build a class that will contain constant values to be used at different places.
The structure should be something like this:
class JavascriptEvents {
static change: string = "change";
static click: string = "click";
static blur: string = "blur";
static inputChange: string = "input propertychange paste";
static dblClick: string = "dblclick";
static bootstrap: Object = {
accordion: {
show: "show.bs.collapse",
shown: "shown.bs.collapse",
hide: "hide.bs.collapse",
hidden: "hidden.bs.collapse"
},
modal: {
shown: "shown.bs.modal",
show: "show.bs.modal",
hide: "hide.bs.modal",
hidden: "hidden.bs.modal",
loaded: "loaded.bs.modal"
}
}
}
Question: How should the bootstrap part be nested so I can reference an event like:
$("someElement").on(JavascriptEvents.bootstrap.modal.shown, function(){
// do whatever needed
});
I think the problem is the : Object declaration, which hid all the type information from the call site. If you simply remove it, the type checker should be happy again.
E.g.:
class JavascriptEvents {
static bootstrap: Object = {
modal: {
shown: "shown.bs.modal",
}
}
static bootstrap2 = {
modal: {
shown: "shown.bs.modal",
}
}
}
let jq: any;
jq.on(JavascriptEvents.bootstrap.modal.shown); // Error
jq.on(JavascriptEvents.bootstrap2.modal.shown); // Works
Playground
Now i am going on with to show bar chart in webview with help of below code showed the bar chart.
public class sample extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final WebView webview = (WebView)this.findViewById(R.id.webView);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setBuiltInZoomControls(true);
webview.requestFocusFromTouch();
webview.setWebChromeClient(new WebChromeClient());
webview.setWebViewClient(new WebViewClient()
{
public void onPageFinished(WebView view, String url)
{
String str = "john";
webview.loadUrl("javascript:callFromActivity('"+str+"')");
}
}
);
webview.loadUrl("file:///android_asset/mypage.html");
}
}
Actually i need to pass certain values from Android to javaScript
and show those value in chart (for eg: in bar chart i need to show the different user name in chart which i get those value from Android class to js).
follwed link to pass value from Android to js: http://android-er.blogspot.in/2011/10/call-javascript-inside-webview-from.html
js file:
<html>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script>
var name=null;
function callFromActivity(s) {
name = document.getElementById("mytext").innerHTML = s;
alert(name);
}
google.load('visualization', '1.1', {packages: ['corechart', 'bar']});
google.setOnLoadCallback(drawStacked);
function drawStacked() {
alert(name);
console.log(name);
var data = google.visualization.arrayToDataTable([
['USER', 'NAME', 'COUNT', 'POSITION'],
['USER1', name, 1, 1],
['USER2', name, 1, 2]
]);
var options = {
is3D: true, title: 'INVOICE',
width: 1200,
height: 240,
legend: { position: 'top'},
bar: { groupWidth: '50%' },
isStacked: true,
};
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
<body >
<div id="chart_div"></div>
<p id="mytext">Hello!</p>
</body>
</html>
My Problem:
callFromActivity function gets the value but if i pass it to drawStacked it shows as undefiened.
How to solve this problem if there any alternative please help me out to solve this prob.
You register a JS interface into you webview. An example of a JS interface is this
public class JsExampleInterface
{
private Context mContext;
public JsExampleInterface(Context context)
{
this.mContext = context;
}
/**
* Returns all the Device info
* #return DeviceInfo object
*/
#JavascriptInterface
public String getDeviceInfo()
{
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setSdk(Build.VERSION.SDK_INT);
deviceInfo.setDevice(Build.DEVICE);
deviceInfo.setModel(Build.MODEL);
deviceInfo.setProduct(Build.PRODUCT);
deviceInfo.setManufacturer(Build.MANUFACTURER);
return mGson.toJson(deviceInfo);
}
}
Then into your onCreate method you must register this interface like this
JsExampleInterface exampleInterface = new JsExampleInterface(this);
mWebView.addJavascriptInterface(exampleInterface, "ExampleInterface");
After that you will be able to use this js interface into the html like this:
// This returns a JSON object with all device info
ExampleInterface.getDeviceInfo()
I am using Kendo UI and trying to figure out how to get the TreeView to display a recursive hierarchy. Firstly, here is my model (I am retrieving a list of these objects by OData):
public class PageTreeItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool IsEnabled { get; set; }
public PageTreeItem[] SubPages { get; set; }
}
And here is my JavaScript:
var PagesDS = new kendo.data.HierarchicalDataSource({
type: "odata",
transport: {
read: {
url: "/odata/cms/PageTree",
dataType: "json"
}
},
schema: {
data: function (response) {
return response.value;
},
total: function (response) {
return response.value.length;
},
model: {
id: "Id",
children: "SubPages"
}
}
});
PagesDS.read();
$("#treeview").kendoTreeView({
template: kendo.template($("#treeview-template").html()),
dataSource: PagesDS,
dataTextField: ["Name"]
});
And finally, the markup:
<div id="treeview"></div>
<script id="treeview-template" type="text/kendo-ui-template">
#= item.Name #
</script>
I can only ever get the top-level items to display. Obviously, setting the "children" property in schema-> model is not working. How do I achieve what I want?
OK, ignore.. my problem was with the SubPages not actually being returned in the first place... oops! I simply assumed they were automatically serialized with everything else. I needed to provide the $expand option in my OData query, as follows:
read: {
url: "/odata/cms/PageTree?$expand=SubPages",
dataType: "json"
}