3

I am trying to create an interactive tour using hopscotch.js. The standard data structure that the JavaScript library requires the tour to be in to work is as follows:

// Define the tour!
    var tour = {
      id: "hello-hopscotch",
      steps: [
        {
          title: "My Header",
          content: "This is the header of my page.",
          target: "header",
          placement: "right"
        },
        {
          title: "My content",
          content: "Here is where I put my content.",
          target: document.querySelector("#content p"),
          placement: "bottom"
        }
      ]
    };

Putting the tour steps directly into a JavaScript library works perfectly well but ideally I want to be able to hold all these details in a database and use AJAX to pass the data through to hopscotch.js. (This is so tours can be created dynamically and the content can be changed without a code release).

Everything is working fine apart from when my target is using the document.querySelector() element selector.

My basic C# Model for each tour step is as follows:

public class MockTourSteps
{
    public string Title { get; set; }
    public string Content { get; set; }
    public string Target { get; set; }
    public string Placement { get; set; }
}

As Target is a string, I have to pass the value in double-quotes.

The problem with this is that when it is serialized to JSON, the following error is being kicked-out when viewing the page with Chrome Developer Tools:

Syntax error, unrecognized expression: document.querySelector(".btn-primary")

Exploring the JSON data that has been returned to the page, I can see that the double-quotes around my document.querySelector() are causing the problem.

I need these double quotes to be removed from my target when I am specifying the target via document.querySelector() but the double-quotes are to remain when I am specifying a target based on a HTML tag such as header.

Any ideas how I can achieve this?

kapantzak
  • 11,610
  • 4
  • 39
  • 61
Stu1986C
  • 1,452
  • 1
  • 18
  • 34
  • have you tried using JSON.stringify to convert javascript value to a json string? https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify – man_luck May 05 '15 at 10:28
  • In that JavaScript above, you are actually passing the result of `document.querySelector()` as the value for target, is this really what you want? – DavidG May 05 '15 at 10:32
  • If possible, I'd like to deal with the double-quotes server-side with my C#. @DavidG - Yes, I want to be able to pass `document.querySelector()` as the value (with the element to be selected within the brackets of course :-) ). @collapsar - Unfortunately, within C# all strings need to be defined within double-quotes. The problem is that these C# double-quotes cause me problems when it reaches the client-side JavaScript. – Stu1986C May 05 '15 at 10:37
  • @Stu1986C I'm not sure you get my meaning exactly. Your Javascript passes an HTML element (in this case a `p` tag) and NOT the string `document.querySelector(...)`. Is that what you want? – DavidG May 05 '15 at 10:40
  • @Stu1986C: (deleted comment suggested to use single quotes in querySelector call). The argument to `querySelector` needs to be interpreted by JS only, to C# it will only be _part_ of a string; thus no problems with the server-side syntax. However, you would need to detect a stringified function call on the JS side ( easy ) and execute ( still easy, but this entails discouraged use of `eval` afaik) – collapsar May 05 '15 at 10:42
  • @collapsar I think I understand where you're coming from. (Apologies if I'm not). It is not just the argument that I pass to the `querySelector` that is a string...the `document.querySelector()` is also a string. This is because my `Target` property in my `MockTourSteps` model is of type string. I need this field to be kept flexible so that I can pass strings such as `footer` into the `target` field for hopscotch.js to consume. – Stu1986C May 05 '15 at 14:41
  • 1
    @Stu1986C I groked that and couldn't see any alternative to using a javascript `eval`. – collapsar May 05 '15 at 16:33
  • @collapsar Dammit, I've never used eval before but it sounds to be the root of all evil. Thanks for your help :) – Stu1986C May 05 '15 at 19:28

1 Answers1

1

The main problem that in provided example you are using javascript object but you will get JSON from your server. You will need to use some kind of JSON processing at client side.

I think you can add more info in your model:

public class MockTourStep
{
    public string Title { get; set; }
    public string Content { get; set; }
    public TargetC Target { get; set; }
    public string Placement { get; set; }

    public enum TargetType
    {
        ElementName,
        QuerySelector
    }

    public class TargetC
    {
        [JsonConverter(typeof(StringEnumConverter))]
        public TargetType Type { get; set; }

        public string Value { get; set; } 
    }
}

And sample response:

var tour = new
{
    Id = "hello-hopscotch",
    Steps = new List<MockTourStep>
    {
        new MockTourStep
        {
            Title = "My Header",
            Content = "This is the header of my page.",
            Target = new MockTourStep.TargetC
            {
                Type = MockTourStep.TargetType.ElementName,
                Value = "header"
            },
            Placement = "bottom"
        },
        new MockTourStep
        {
            Title = "My content",
            Content = "Here is where I put my content.",
            Target = new MockTourStep.TargetC
            {
                Type = MockTourStep.TargetType.QuerySelector,
                Value = "document.querySelector('#content p')"
            },
            Placement = "bottom"
        }
    }
};

Then, at client side you can check target type and set it to be a valid value:

$.getJSON("url", function (tour) {
    for (var i = 0; i < tour.steps.length; i++) {
        var step = tour.steps[i];

        switch (step.target.type) {
            case "ElementName":
                step.target = step.target.value;
                break;
            case "QuerySelector":
                step.target = eval(step.target.value);
                break;
            default:
                throw Error;
        }
    }

    hopscotch.startTour(tour);
});

Please note eval use for QuerySelector. Using of eval is dangerous, so you need to check what you are serving to client.

Aleksandr Ivanov
  • 2,778
  • 5
  • 27
  • 35
  • Thanks Aleksandr. The logic makes sense to me. I'll give it a try shortly and let you know if it worked for me :) – Stu1986C May 05 '15 at 15:38
  • That worked. Thanks Aleksandr! The only thing I needed to change on your code was the switch statement; replacing the strings with their respective enumeration ints. – Stu1986C May 06 '15 at 08:57
  • Please note that I use `JsonConverter(typeof(StringEnumConverter))` attribute. In this case the value will be serialized as enum string representation. – Aleksandr Ivanov May 06 '15 at 09:05
  • I have that in mine too but it hasn't sent the enum as a string. – Stu1986C May 06 '15 at 09:16
  • That is strange, because it worked in my test project. I think you should try manually serialize your model to test it. – Aleksandr Ivanov May 06 '15 at 09:28
  • You can also check most voted answer for this [question](http://stackoverflow.com/questions/2441290/json-serialization-of-enum-as-string). – Aleksandr Ivanov May 06 '15 at 09:30