Показаны сообщения с ярлыком JavaScript. Показать все сообщения
Показаны сообщения с ярлыком JavaScript. Показать все сообщения

Динамическая загрузка скриптов

понедельник, 27 апреля 2009 г.,

Например, на веб-сайте в папке Script есть файл JScript.js:
function Test()
{
    alert("TEST");
}
В какой-то момент времени требуется из Silverlight вызвать загрузку .js-файла и выполнение функции Test. При этом загрузка файла должна происходить после каждого его изменения. Это можно сделать следующим образом в Silverlight:
ScriptHelper.Invoke("/Scripts/JScript.js?guid=" + Guid.NewGuid(), () =>
{
    HtmlPage.Window.Invoke("Test");
});

В метод Invoke передается путь к файлу. Guid используется для того, чтобы файл не кешировался браузером.
Принцип работы метода: создается тег SCRIPT; добавляется на веб-страницу в тег HEAD; происходит перехват события onreadystatechanged и в его обработчике проверяется состояние SCRIPT.readyState; если loaded, то вызвать метод в Silverlight.
Ниже код класса с extension method'ами:
public static class ScriptHelper
{
    public static HtmlElement FindByUri(string uri)
    {
        foreach (var script in HtmlPage.Document.GetElementsByTagName("script"))
        {
            if (string.Equals((string)script.GetProperty("src"), uri, StringComparison.OrdinalIgnoreCase))
                return (HtmlElement)script;
        }
        return null;
    }

    public static ScriptReadyState ReadyState(this HtmlElement scriptTag)
    {
        return (ScriptReadyState)Enum.Parse(typeof(ScriptReadyState), (string)scriptTag.GetProperty("readyState"), true);
    }

    public enum ScriptReadyState
    {
        Uninitialized = 0,
        Loading = 1,
        Loaded = 2,
        Interactive = 3,
        Complete = 4
    }

    public static void OnLoaded(this HtmlElement scriptTag, Action callback)
    {
        if ((int)scriptTag.ReadyState() > 1)
        {
            callback();
        }
        else
        {
            // обработчик событий
            EventHandler<HtmlEventArgs> eh = (s, e) =>
            {
                // проверить событие и состояние и вызвать handler
                if (e.EventType == "readystatechange" && (int)scriptTag.ReadyState() > 1)
                    callback();
            };

            // подключить обработчик
            scriptTag.AttachEvent("onreadystatechange", eh);
        }
    }

    // handler - в IE вызовется раньше, чем скрипт будет выполнен;
    public static void Invoke(string scriptUri, Action callback)
    {
        HtmlElement script = FindByUri(scriptUri);
        if (script == null)
        {
            // создать тег и добавить на страницу в тег HEAD;
            script = Create(scriptUri);
            var head = (HtmlElement)HtmlPage.Document.GetElementsByTagName("head").FirstOrDefault();
            head.AppendChild(script);
        }

        script.OnLoaded(callback);
    }

    public static HtmlElement Create(string scriptUri)
    {
        // создать тег скрипта
        var js = HtmlPage.Document.CreateElement("script");
        js.SetProperty("type", ScriptType);
        js.SetProperty("src", scriptUri);
        return js;
    }

    public static string ScriptType = "text/javascript";
}

Если js-функции не существует, то получим ошибку ... см. здесь.
Код работает в IE.

Как проверить existance JavaScript-функции

четверг, 23 апреля 2009 г.,

Из Silverlight-приложения можно проверить existance (существование) функции с помощью следующего метода:
bool res = HtmlPage.Window.IsFunctionExists("MyFunc");
Метод вернет true для функции, которая определена в теге SCRIPT на странице или определена в подгружаемом .js-файле (например: <script type="text/javascript" src="Silverlight.js"></script>).
Метод определен в следующем классе:
public static class FunctionHelper
{
    public static bool IsFunctionExists(this HtmlWindow wnd, string functionName)
    {
        return (bool)wnd.Eval("typeof " + functionName + " == \"function\"");
    }
}

Как получить текст JavaScript-функции


Если имя JavaScript-функции известно, и функция определена в одном из тегов SCRIPT на веб-странице, либо она находится в отдельном .js-файле, то ее текст можно получить в Silverlight-приложении следующим образом:
var tmp = HtmlPage.Window.Eval("'' + MyFunc;");
Если ''+ не указать, то в tmp окажется ссылка на System.Windows.Browser.ScriptObject;
Если функция не определена, то при вызове метода Eval в IE8 всплывет диалог Webpage Error с вопросом: Do you want to debug this webpage? This webpage contains errors that might prevent it from displaying or working correctyly. If you are not testing this webpage, click No.
После закрытия диалога в Silvelight-приложении получим System.InvalidOperationException.

Решение:
public static string GetFunctionCode(this HtmlWindow wnd, string fname)
{
    return (string)wnd.Eval("(typeof " + fname + " == 'function') ? ('' + " + fname + ") : null;");
}

Как получить текст script'ов в Silverlight


С помощью метода GetJavaScripts можно получить список скриптов, размещенных на веб-странице:
foreach (string script in HtmlPage.Document.GetJavaScripts())
{
    System.Diagnostics.Debug.WriteLine(script);
}
Метод находит все теги SCRIPT и отбирает только те, у которых есть атрибут type="text/javascript".
Для использования метода необходимо в Silverlight-приложение добавить следующий код:
public static class ScriptHelper
{
    public static IEnumerable<string> GetJavaScripts(this HtmlDocument doc)
    {
        return doc.GetScripts("text/javascript");
    }

    public static IEnumerable<string> GetScripts(this HtmlDocument doc, string type)
    {
        Regex reg = new Regex(@"<script.+?type=" + type + ".*?>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
        foreach (var scr in doc.GetElementsByTagName("script"))
        {
            string html = scr.GetProperty("outerHTML") as string;
            if (reg.IsMatch(html))
                yield return html;
        }
    }
}

Если скрипт находится в отдельном файле, то его код с помощью этого метода недоступен (в html получим, например: "\r\n<SCRIPT type=text/javascript src=\"Silverlight.js\"></SCRIPT>");

Вызов JavaScript функции из Silverlight


Представим себе, что требуется периодически обращаться к сервису, например, за какими-то зашифрованными данными, обрабатывать их, результат выводить на страницу.
Понятно, что JavaScript в такой ситуации не поможет, но можно использовать Silverlight. Для этого надо:
1) в .html- или .aspx-файл в тег body добавить "невидимый" Silverlight:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" style="width: 0px; height: 0px;">
      <param name="source" value="ClientBin/App.xap" /></object>
Ширина и высота равны 0. Если указать style="display: none;", то Silverlight не загрузится.
2) в тег head добавить функцию, которая вызывается из Silverlight:
<script type="text/javascript">
    function ShowResult1(val) {
    }
</script>
3) в Silverlight-приложение добавить, например, следующий код:
ThreadPool.QueueUserWorkItem((state) =>
{
    for (int i = 0; i < 1000; i++)
    {
        // имитация длительной обработки
        Thread.Sleep(1000);
        // передача данных в JavaScript функцию
        this.Dispatcher.BeginInvoke(() => HtmlPage.Window.Invoke("ShowResult1", DateTime.Now.Ticks));
    }
});
В результате открытия веб-страницы в браузере произойдет загрузка Silverlight-приложения, в котором будет запущен рабочий поток с циклом на 1000 итераций. При каждой итерации данные передаются в JavaScript-функцию на веб-странице.

Вызов DomainService с веб-страницы

четверг, 9 апреля 2009 г.,

Класс DomainService входит в состав .NET RIA Services и используется в качестве базового, для классов, обменивающихся данными с Silverlight-приложениями. Но данные можно передавать и получать также с помощью XMLHttpRequest.
  1. Создать проект: ASP.NET Web Application
  2. Добавить в References: System.Web.Ria и System.Web.DomainServices
  3. Добавить в проект файл Class1.cs (см. ниже)
  4. В Web.config в httpHandlers добавить:
    <add path="DataService.axd" verb="GET,POST" type="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
  5. В Default.aspx заменить содержимое (см. ниже)
  6. Запустить приложение. В IE откроется веб-страница с двумя кнопками Operation1 и Operation2. В результате нажатия на кнопку будет создан POST запрос, который попадет в соответствующий метод в классе Test.
    Результат запроса возвращается в JSON-формате:
    {"__type":"DataServiceResult:DomainServices", "IsDomainServiceException":false, "ReturnValue":"Hello 123"}

[Class1.cs]
using System.Web.DomainServices;
using System.Web.Ria;
using System;

namespace Cognitex.Web
{
    [EnableClientAccess]
    public class Test : DomainService
    {
        [ServiceOperation]
        public string Operation1()
        {
            return "Ticks: " + DateTime.Now.Ticks;
        }

        [ServiceOperation]
        public string Operation2(string value)
        {
            return "Hello " + value;
        }
    }
}

[Default.aspx]
<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>

    <script language="javascript" type="text/javascript">
        function Operation1() {
            var r = new XMLHttpRequest();
            r.open("POST", "/DataService.axd/Cognitex-Web-Test/Operation1", false);
            r.setRequestHeader("Content-Type", "text/json");
            r.send("[]");
            return r;
        }
        function Operation2(value) {
            var r = new XMLHttpRequest();
            r.open("POST", "/DataService.axd/Cognitex-Web-Test/Operation2", false);
            r.setRequestHeader("Content-Type", "text/json");
            r.send("[\"" + value + "\"]");
            return r;
        }
        function Trace(r) {
            var str = "";
            for (var o in r) str += o + ": " + r[o] + "<br/>";
            _Msg.innerHTML = str + "<hr/>" + eval("res=" + r.responseText).ReturnValue;
        }
    </script>

</head>
<body>
    <button onclick="Trace(Operation1())">Operation1</button>
    <br />
    <input id="_Data" value="123" /><button onclick="Trace(Operation2(_Data.value))">Operation2</button>
    <div id="_Msg" style="margin-top: 50px" />
</body>
</html>


Чтобы вызвать DomainService из Fiddler'а: 1) открыть закладку Request Builder 2) в выпадающем списке выбрать POST 3) указать адрес метода, например, http://ipv4.fiddler:5966/DataService.axd/Services-Test/Operation1 4) в поле Request Headers указать Content-Type: text/json 5) в поле Request Body указать [] 6) нажать Execute.
Для вызова метода Operation2 в поле Request Body надо указать, например, ["wow"]