Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: sprintf like usage #2586

Open
dakhnod opened this issue Jan 30, 2022 · 3 comments
Open

Question: sprintf like usage #2586

dakhnod opened this issue Jan 30, 2022 · 3 comments

Comments

@dakhnod
Copy link

dakhnod commented Jan 30, 2022

I, as a Knockout beginner, am facing this challenge:
I have a data object

{
  "status": {
    "completion": 0.8,
    "percent": 80
  }
}

And want to give the user the ability to define his own template strings like
"{status.percent}%, {status.completion}".
After the conversion, this should result in 80%, 0.8.
The data object aswell as my template string are handled by javascript.
Ideal would be some function like replaceVariables(formatString, dataObject).

  • How can I offload as much replacement work as possible to Knockout?
  • What's the best format for the template string?
  • Does Knockout support this style of binding declaration without data-bind?
  • What would be some example code doing this?
  • Should I maybe Just grep {.*} from the template string myself, and write the object mapping logic myself?
    AFAIK Knockout uses data-bind attributes, which differ from my application a bit, since I don't want to make the user write HTML...

Thanks for the great work so far, and for the support.

@webketje
Copy link

webketje commented Jan 31, 2022

Use Handlebars for such a task, sth. like this (not tested)

// in HTML: <input type="text" data-bind="value: observableUserString">
var viewModel = {
  observableUserString: ko.observable("{{status.percent}}%, {{status.completion}}")
  status: ko.observable({ percent: 50, completion: 100 })
}

viewModel.compiled = ko.pureComputed(function() {
    try {
      return Handlebars.compile(viewModel.observableUserString()({ status: viewModel.status() })
    } catch (err) {
      return 'There was an error with your template string';
    }
  })

@dakhnod
Copy link
Author

dakhnod commented Feb 1, 2022

Thanks for the response, I will look in to that.
Still, the code for getting data out of an object using a string path must be somewhere in Knockout already, right?

@karimayachi
Copy link
Contributor

I don't think using a Handlebars to parse the literal answers the question as to how to tap into Knockout's internals to leverage it's native parsing and tracking and context-hierarchy. Also this only works under the assumption that you know what data is going to be used in the literal and pass it in.

The correct way to access all the Knockout "under the hood" magic is by creating custom bindings.

A working example of your case here:
https://jsfiddle.net/5wnLko0y/14/

The actual custom binding:

ko.bindingHandlers.literal = {
    init: function(elem, valueAccessor) {
        return { controlsDescendantBindings: true };
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    	const literal =  ko.unwrap(valueAccessor());
        const regex = /({([\w\.]*)})/gm;
	const subst = '<span data-bind="text: $2"></span>';
	const result = literal.replace(regex, subst);
      
    	ko.utils.setHtml(element, result);
        ko.applyBindingsToDescendants(bindingContext, element);
    }
};

Beware that this is a very quick and dirty example. You could use any form of parsing of your literal (including Handlebars) and create any type of binding. I chose to rewrite it as text-bindings, but that may not be the best solution. There are many ways to achieve the same effect. Maybe create a Computed and use the output of that as HTML, etc, etc.
Also note that this example only works because your model is not observable, if "status" and its properties "completion" and "percent" all were observable, you would have to accomodate in above example, because the actual binding should become status().completion in stead of status.completion.

You would use it as follows:

<div data-bind="literal: userTemplate"></div>

<input data-bind="value: userTemplate" />

Anyway, using custom bindings, you can use everything within the context (including $parent, etc) in your templates and all of Knockout's parsing and tracking (as long as you convert it correctly in your custom binding).

Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants