Knockoutjs valueHasMutated无法正常工作

时间:2016-05-24 11:29:59

标签: javascript mvvm knockout.js data-binding custom-binding

希望对于淘汰赛大师来说这将是一个快速的......

我正在编写一些自定义绑定来帮助我在我正在处理的项目中使用自定义翻译引擎翻译UI。

一种是翻译文本,另一种是翻译HTML5输入元素上的'占位符'属性。

除了最后一个语句之外,两个绑定都是相同的,其中一个更新元素中的文本,另一个更新属性值。

文本一个完美,但占位符没有,我仍然坚持为什么答案。

绑定代码如下:

翻译文本绑定

ko.bindingHandlers.translatedText = {

    init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {

        // Get our custom binding values
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;

        // Set up an event handler that will respond to events telling it when our translations have finished loading
        // the custom binding will instantly update when a key matching it's translation ID is loaded into the
        // local session store
        window.addEventListener("TranslationsLoaded", (e) => {
            //associatedObservable(" "); // Force an update on our observable, so that the update routine below is triggered
            associatedObservable.valueHasMutated();
        }, false);

    },

    update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {

        // Get our custom binding values
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;

        // Ask local storage if we have a token by that name
        var translatedText = utilityLib.getTranslatedString(translationToken);

        // Check if our translated text is defined, if it's not then substitute it for a fixed string that will
        // be seen in the UI (Whatever you put into the 'associatedObservable' at this point WILL appear in the element
        if (undefined === translatedText || translatedText === "" || translatedText === null) {
            if (sessionStorage["translations"] === undefined) {
                // No translations have loaded yet, so we blank the text
                translatedText = "";
            } else {
                // Translations have loaded, and the token is still not found
                translatedText = "No Translation ID";
            }
        }
        associatedObservable(translatedText);
        ko.utils.setTextContent(element, associatedObservable());
    }

} // End of translatedText binding

翻译的占位符绑定

ko.bindingHandlers.translatedPlaceholder = {

    // This one works pretty much the same way as the translated text binding, except for the final part where
    // the translated text is inserted into the element.

    init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;
        window.addEventListener("TranslationsLoaded", (e) => {
            debugger;
            associatedObservable.valueHasMutated();
        }, false);
    },

    update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;
        var translatedText = utilityLib.getTranslatedString(translationToken);
        debugger;
        if (undefined === translatedText || translatedText === "" || translatedText === null) {
            if (sessionStorage["translations"] === undefined) {
                translatedText = "";
            } else {
                translatedText = "No Translation ID";
            }
        }
        associatedObservable(translatedText);
        element.setAttribute("placeholder", translatedText);
    }

} // End of translatedPlaceholder binding

这个想法很简单,如果绑定运行并且转换已经存在于sessionStorage中,那么我们将获取已翻译的字符串并将其插入与该元素关联的observable。

如果翻译已加载,但未找到翻译,则“无翻译ID”会插入绑定到该元素的observable中。

如果翻译尚未加载,请将空字符串插入observable,然后等待事件'TranslationsLoaded'触发。引发此事件时,绑定到元素的observable会发生变化,从而导致更新发生,然后重新检查翻译,然后发现它已加载并因此起作用。

然而.....

无论我怎么努力,翻译的占位符绑定都不会激发它的更新。

我可以在调试器中清楚地看到在两个绑定上都收到了事件,并调用了mutate函数。

在翻译的文本绑定上,我得到以下序列......

'init' - > '更新' - > 'event' - > 'mutate' - > '更新'

这正是我所期望的,它发生在绑定到该绑定的每个元素+ observable上。

在翻译的占位符上我得到了

'init' - > '更新' - > 'event' - > '发生变异'

但最终的更新永远不会发生。

因此,占位符的翻译字符串永远不会正确查找,具有相同代码的文本可以完美地运行!!

对于那些要问的人,我正在使用这样的绑定:

<input type="text" class="form-control" data-bind="value: userName, translatedPlaceholder: { observable: namePlaceHolderText, translationToken: 'loginBoxNamePlaceholderText'}">

<span class="help-block" data-bind="translatedText: {observable: nameErrorText, translationToken: 'loginBoxUserNameEmptyValidationText'}"></span>

并且在视图模型中,'observable'参数只是包含字符串的普通ko.observable变量。

干杯 美女

2 个答案:

答案 0 :(得分:0)

我相信你会遇到事件冒泡问题......试着回复真实&#39;在您调用valueHasMutated之后:

init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
    var value = valueAccessor();
    var associatedObservable = value.observable;
    var translationToken = value.translationToken;
    window.addEventListener("TranslationsLoaded", (e) => {
        associatedObservable.valueHasMutated();
        return true; // allow event to bubble
    }, false);
},

话虽如此,我认为所有这些手动事件对于淘汰赛可以为你做什么都是反对的。你应该观察你的数据,绑定它,并让淘汰赛为你做所有的内部事件......这就是它的作用。

建立在user3297291小提琴上的示例:

ko.bindingHandlers.translatedPlaceholder = {
  init: function(element, valueAccessor) {
    var va = valueAccessor();
    var obs = ko.utils.unwrapObservable(va.obs);
    var placeholderStr = obs[va.key];
    console.log(placeholderStr);
    element.setAttribute("placeholder", placeholderStr);
  },
  update: function(element, valueAccessor) {
    var va = valueAccessor();
    var obs = ko.utils.unwrapObservable(va.obs);
        var placeholderStr = obs[va.key];
    console.log(placeholderStr);
    element.setAttribute("placeholder", placeholderStr);
  }
};

var vm = function() {
    var self = this;
    self.dictionary = ko.observable({
    "placeholder": "Initial State"
  });

  self.switchTranslations = function() {
    // Set the 'new' dictionary data:
    self.dictionary({
      "placeholder": "My Translated Placeholder"
    });
  };
}
ko.applyBindings(new vm());

小提琴:https://jsfiddle.net/brettwgreen/5pmmd0va/

答案 1 :(得分:0)

触发更新处理程序

来自Knockout documentation

  

当绑定应用于元素并且跟踪您访问的任何依赖项(observables / computeds)时,Knockout将最初调用update回调。当任何这些依赖关系发生变化时,将再次调用update回调。

关键是您必须访问update函数中的observable才能获得更新。在translatedText绑定中,您可以这样做:

ko.utils.setTextContent(element, associatedObservable());

associatedObservable update函数中translatedPlaceholder没有associatedObservable(translatedText); associatedObservable(); // get notified of updates to associatedObservable element.setAttribute("placeholder", translatedText); 的访问权限。你需要像这样添加它:

update

更好的方法

对于您的情况,确实不需要init处理程序,因为您不需要根据对viewmodel的更改来更新视图。相反,您的更新只来自事件,可以在ko.bindingHandlers.translatedPlaceholder = { init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { function loadTranslation() { var translationToken = valueAccessor(), translatedText = utilityLib.getTranslatedString(translationToken); element.setAttribute("placeholder", translatedText || "No Translation ID"); window.removeEventListener("TranslationsLoaded", loadTranslation); } if (sessionStorage["translations"] === undefined) window.addEventListener("TranslationsLoaded", loadTranslation, false); } else { loadTranslation(); } } } 处理程序中设置。

data-bind="value: userName, translatedPlaceholder: 'loginBoxNamePlaceholderText'"

用法:

import Firebase
import FirebaseInstanceID
import FirebaseMessaging
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    registerForPushNotifications(application)
    FIRApp.configure()

    // Add observer for InstanceID token refresh callback.
    NSNotificationCenter
     .defaultCenter()
     .addObserver(self, selector: #selector(AppDelegate.tokenRefreshNotificaiton),
                                                     name: kFIRInstanceIDTokenRefreshNotification, object: nil)

    // Override point for customization after application launch.
    return true
  }

func registerForPushNotifications(application: UIApplication) {
      let settings: UIUserNotificationSettings =
        UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
      application.registerUserNotificationSettings(settings)
      application.registerForRemoteNotifications()
  }


  func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
                   fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    print("===== didReceiveRemoteNotification ===== %@", userInfo)
  }


 func tokenRefreshNotificaiton(notification: NSNotification) {
    let refreshedToken = FIRInstanceID.instanceID().token()!
    print("InstanceID token: \(refreshedToken)")

    // Connect to FCM since connection may have failed when attempted before having a token.
     connectToFcm()
  }

  func connectToFcm() {
    FIRMessaging.messaging().connectWithCompletion { (error) in
      if (error != nil) {
        print("Unable to connect with FCM. \(error)")
      } else {
        print("Connected to FCM.")
      }
    }
  }