-
+
- Home +
- Download / Install +
- Tutorials +
- Live examples +
- Documentation +
- Forum +
- Source +
diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..913fff06b --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.suo +*.swp +*.csproj.user +bin +obj +*.pdb +_ReSharper* +*.ReSharper.user +*.ReSharper +desktop.ini +.eprj +_site + +.DS_Store +build \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..2fe883a5e --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +knockoutjs.com diff --git a/README b/README deleted file mode 100644 index e69de29bb..000000000 diff --git a/_includes/documentation-menu.html b/_includes/documentation-menu.html new file mode 100644 index 000000000..6110a522d --- /dev/null +++ b/_includes/documentation-menu.html @@ -0,0 +1,90 @@ +
visible bindingtext bindinghtml bindingcss bindingstyle bindingattr bindingclick bindingevent bindingsubmit bindingenable bindingdisable bindingvalue bindinghasFocus bindingchecked bindingoptions bindingselectedOptions bindinguniqueName bindingthrottle extenderfn to add custom functionsko.observable & ko.computed
+ ko.observableArray
+ + Choose a ticket class: + + + +
++ You have chosen + ($) +
+ + +
\ No newline at end of file
diff --git a/_includes/live-example-minimal.html b/_includes/live-example-minimal.html
new file mode 100644
index 000000000..e7680178d
--- /dev/null
+++ b/_includes/live-example-minimal.html
@@ -0,0 +1,27 @@
+
+{% if live_example_view %}
+{{ live_example_view | replace:'/**/','' | escape }}
+{{ live_example_viewmodel | replace:'/**/','' | escape }}
+
+{% endif %}
\ No newline at end of file
diff --git a/_includes/live-example-tabs.html b/_includes/live-example-tabs.html
new file mode 100644
index 000000000..6f6420fe3
--- /dev/null
+++ b/_includes/live-example-tabs.html
@@ -0,0 +1,16 @@
+
+{% if live_example_view %}
+{{ live_example_view | replace:'/**/','' | escape }}
+{{ live_example_viewmodel | replace:'/**/','' | escape }}
+
+{% endif %}
\ No newline at end of file
diff --git a/_includes/main-menu.html b/_includes/main-menu.html
new file mode 100644
index 000000000..47049d8d6
--- /dev/null
+++ b/_includes/main-menu.html
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/_includes/plugin-download-link.html b/_includes/plugin-download-link.html
new file mode 100644
index 000000000..d6a3d94c6
--- /dev/null
+++ b/_includes/plugin-download-link.html
@@ -0,0 +1,7 @@
+
+{% if plugin_download_link %}
+
+### Download
+{{ plugin_download_link }}
+
+{% endif %}
\ No newline at end of file
diff --git a/_layouts/default.html b/_layouts/default.html
new file mode 100644
index 000000000..5dfe9e4f0
--- /dev/null
+++ b/_layouts/default.html
@@ -0,0 +1,40 @@
+
+
+
+
+ First name:
+First name capitalized:
+ + + +scripts/init.js + + require(['knockout-x.y.z', 'appViewModel', 'domReady!'], function(ko, appViewModel) { + ko.applyBindings(new appViewModel()); + }); + +scripts/appViewModel.js + + // Main viewmodel class + define(['knockout-x.y.z'], function(ko) { + return function appViewModel() { + this.firstName = ko.observable('Bert'); + this.firstNameCaps = ko.computed(function() { + return this.firstName().toUpperCase(); + }, this); + }; + }); + +Of course, `x.y.z` should be replaced with the version number of the Knockout script you are loading (e.g., `knockout-2.3.0`). + +### Loading Knockout.js, a Binding Handler, and a ViewModel class via RequireJs + +Documentation on Binding Handlers in general can be found [here](http://knockoutjs.com/documentation/custom-bindings.html). This section is meant to demonstrate the power that AMD modules provide in maintaining your custom handlers. We will take the example of the `ko.bindingHandlers.hasFocus` example from the binding handlers documentation. By wrapping that handler in it's own module you can restrict it's use only to the pages that need it. The wrapped module becomes: + + define(['knockout-x.y.z'], function(ko){ + ko.bindingHandlers.hasFocus = { + init: function(element, valueAccessor) { ... }, + update: function(element, valueAccessor) { ... } + } + }); + +After you have defined the module update the input element from the HTML example above to be: + +First name: You're editing the name!
+ +Include the module in the list of dependencies for your view model: + + define(['knockout-x.y.z', 'customBindingHandlers/hasFocus'], function(ko) { + return function appViewModel(){ + ... + // Add an editingName observable + this.editingName = ko.observable(); + }; + }); + +Note that the custom binding handler module does not inject anything into our ViewModel module, that is because it does not return anything. It just appends additional behavior to the knockout module. + +### RequireJs Download + +RequireJs can be downloaded from [http://requirejs.org/docs/download.html](http://requirejs.org/docs/download.html). diff --git a/documentation/attr-binding.md b/documentation/attr-binding.md new file mode 100644 index 000000000..a4d64dfe3 --- /dev/null +++ b/documentation/attr-binding.md @@ -0,0 +1,47 @@ +--- +layout: documentation +title: The "attr" binding +--- + +### Purpose +The `attr` binding provides a generic way to set the value of any attribute for the associated DOM element. This is useful, for example, when you need to set the `title` attribute of an element, the `src` of an `img` tag, or the `href` of a link based on values in your view model, with the attribute value being updated automatically whenever the corresponding model property changes. + +### Example + + Report + + + + +This will set the element's `href` attribute to `year-end.html` and the element's `title` attribute to `Report including final year-end statistics`. + +### Parameters + + * Main parameter + + You should pass a JavaScript object in which the property names correspond to attribute names, and the values correspond to the attribute values you wish to apply. + + If your parameter references an observable value, the binding will update the attribute whenever the observable value changes. If the parameter doesn't reference an observable value, it will only set the attribute once and will not update it later. + + * Additional parameters + + * None + +### Note: Applying attributes whose names aren't legal JavaScript variable names + +If you want to apply the attribute `data-something`, you *can't* write this: + +Send me spam:
+ + + +### Example adding checkboxes bound to an array +Send me spam:
+Send me spam:
+First name:
+Last name:
+Enter bid price:
+ +Now, whenever the user enters a new price, the text box immediately updates to show it formatted with the currency symbol and two decimal places, no matter what format they entered the value in. This gives a great user experience, because the user sees how the software has understood their data entry as a price. They know they can't enter more than two decimal places, because if they try to, the additional decimal places are immediately removed. Similarly, they can't enter negative values, because the `write` callback strips off any minus sign. + +### Example 3: Filtering and validating user input + +Example 1 showed how a writeable computed observable can effectively *filter* its incoming data by choosing not to write certain values back to the underlying observables if they don't meet some criteria. It ignored full name values that didn't include a space. + +Taking this a step further, you could also toggle an `isValid` flag depending on whether the latest input was satisfactory, and display a message in the UI accordingly. There's an easier way of doing validation (explained below), but first consider the following view model, which demonstrates the mechanism: + + function MyViewModel() { + this.acceptedNumericValue = ko.observable(123); + this.lastInputWasValid = ko.observable(true); + + this.attemptedValue = ko.computed({ + read: this.acceptedNumericValue, + write: function (value) { + if (isNaN(value)) + this.lastInputWasValid(false); + else { + this.lastInputWasValid(true); + this.acceptedNumericValue(value); // Write to underlying storage + } + }, + owner: this + }); + } + + ko.applyBindings(new MyViewModel()); + +... with the following DOM elements: + +Enter a numeric value:
+Name:
+ + ++ + I have a cellphone +
++ Your cellphone number: + +
+ + + +In this example, the "Your cellphone number" text box will initially be disabled. It will be enabled only when the user checks the box labelled "I have a cellphone". + +### Parameters + + * Main parameter + + A value that controls whether or not the associated DOM element should be enabled. + + Non-boolean values are interpreted loosely as boolean. For example, `0` and `null` are treated as `false`, whereas `21` and non-`null` objects are treated as `true`. + + If your parameter references an observable value, the binding will update the enabled/disabled state whenever the observable value changes. If the parameter doesn't reference an observable value, it will only set the state once and will not do so again later. + + * Additional parameters + + * None + +### Note: Using arbitrary JavaScript expressions + +You're not limited to referencing variables - you can reference arbitrary expressions to control an element's enabledness. For example, + + + +### Dependencies + +None, other than the core Knockout library. \ No newline at end of file diff --git a/documentation/event-binding.md b/documentation/event-binding.md new file mode 100644 index 000000000..905cfc6f0 --- /dev/null +++ b/documentation/event-binding.md @@ -0,0 +1,133 @@ +--- +layout: documentation +title: The "event" binding +--- + +### Purpose +The `event` binding allows you to add an event handler for a specified event so that your chosen JavaScript function will be invoked when that event is triggered for the associated DOM element. This can be used to bind to any event, such as `keypress`, `mouseover` or `mouseout`. + +### Example +You seem to be interested in:
+ + + +Two points to note about this example: + + * If you're inside a nested [binding context](binding-context.html), for example if you're inside a `foreach` or a `with` block, but your handler function + is on the root viewmodel or some other parent context, you'll need to use a prefix such as `$parent` or `$root` to locate the + handler function. + * In your viewmodel, it's often useful to declare `self` (or some other variable) as an alias for `this`. Doing so avoids any problems + with `this` being redefined to mean something else in event handlers or Ajax request callbacks. + +### Note 2: Accessing the event object, or passing more parameters + +In some scenarios, you may need to access the DOM event object associated with your event. Knockout will pass the event as the second parameter to your function, as in this example: + +(round to whole number)
+(round to two decimals)
+{% endcapture %} + +{% capture live_example_viewmodel %} +ko.extenders.numeric = function(target, precision) { + //create a writeable computed observable to intercept writes to our observable + var result = ko.computed({ + read: target, //always return the original observables value + write: function(newValue) { + var current = target(), + roundingMultiplier = Math.pow(10, precision), + newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue), + valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier; + + //only write if it changed + if (valueToWrite !== current) { + target(valueToWrite); + } else { + //if the rounded value is the same, but a different value was written, force a notification for the current field + if (newValue !== current) { + target.notifySubscribers(valueToWrite); + } + } + } + }).extend({ notify: 'always' }); + + //initialize with current value to make sure it is rounded appropriately + result(target()); + + //return the new computed observable + return result; +}; + +function AppViewModel(one, two) { + this.myNumberOne = ko.observable(one).extend({ numeric: 0 }); + this.myNumberTwo = ko.observable(two).extend({ numeric: 2 }); +} + +ko.applyBindings(new AppViewModel(221.2234, 123.4525)); +{% endcapture %} + +{% include live-example-minimal.html %} + +Note that for this to automatically erase rejected values from the UI, it's necessary to use `.extend({ notify: 'always' })` on the computed observable. Without this, it's possible for the user to enter an invalid `newValue` that when rounded gives an unchanged `valueToWrite`. Then, since the model value would not be changing, there would be no notification to update the textbox in the UI. Using `{ notify: 'always' }` causes the textbox to refresh (erasing rejected values) even if the computed property has not changed value. + +### Live Example 2: Adding validation to an observable + +This example creates an extender that allows an observable to be marked as required. Instead of returning a new object, this extender simply adds additional sub-observables to the existing observable. Since observables are functions, they can actually have their own properties. However, when the view model is converted to JSON, the sub-observables will be dropped and we will simply be left with the value of our actual observable. This is a nice way to add additional functionality that is only relevant for the UI and does not need to be sent back to the server. + +{% capture live_example_id %}requiredFields{% endcapture %} +{% capture live_example_view %} ++ + +
++ + +
+{% endcapture %} + +{% capture live_example_viewmodel %} +ko.extenders.required = function(target, overrideMessage) { + //add some sub-observables to our observable + target.hasError = ko.observable(); + target.validationMessage = ko.observable(); + + //define a function to do validation + function validate(newValue) { + target.hasError(newValue ? false : true); + target.validationMessage(newValue ? "" : overrideMessage || "This field is required"); + } + + //initial validation + validate(target()); + + //validate whenever the value changes + target.subscribe(validate); + + //return the original observable + return target; +}; + +function AppViewModel(first, last) { + this.firstName = ko.observable(first).extend({ required: "Please enter a first name" }); + this.lastName = ko.observable(last).extend({ required: "" }); +} + +ko.applyBindings(new AppViewModel("Bob","Smith")); +{% endcapture %} + +{% include live-example-minimal.html %} + +### Applying multiple extenders + +More than one extender can be applied in a single call to the `.extend` method of an observable. + + this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" }); + +In this case, both the `required` and `logChange` extenders would be executed against our observable. \ No newline at end of file diff --git a/documentation/fn.md b/documentation/fn.md new file mode 100644 index 000000000..fa6e0ad19 --- /dev/null +++ b/documentation/fn.md @@ -0,0 +1,108 @@ +--- +layout: documentation +title: Adding custom functions using "fn" +--- + +Occasionally, you may find opportunities to streamline your code by attaching new functionality to Knockout's core value types. You can define custom functions on any of the following types: + + + +Because of inheritance, if you attach a function to `ko.subscribable`, it will be available on all the others too. If you attach a function to `ko.observable`, it will be inherited by `ko.observableArray` but not by `ko.computed`. + +To attach a custom function, add it to one of the following extensibility points: + + * `ko.subscribable.fn` + * `ko.observable.fn` + * `ko.observableArray.fn` + * `ko.computed.fn` + +Then, your custom function will become available on all values of that type created from that point onwards. + +***Note:*** It's best to use this extensibility point only for custom functions that are truly applicable in a wide range of scenarios. You don't need to add a custom function to these namespaces if you're only planning to use it once. + +### Example: A filtered view of an observable array + +Here's a way to define a `filterByProperty` function that will become available on all subsequently-created `ko.observableArray` instances: + + ko.observableArray.fn.filterByProperty = function(propName, matchValue) { + return ko.computed(function() { + var allItems = this(), matchingItems = []; + for (var i = 0; i < allItems.length; i++) { + var current = allItems[i]; + if (ko.unwrap(current[propName]) === matchValue) + matchingItems.push(current); + } + return matchingItems; + }, this); + } + +This returns a new `ko.computed` value that provides a filtered view of the array, while leaving the original array unchanged. Because the filtered array is a `ko.computed`, it will be re-evaluated automatically whenever the underlying array changes. + +The following live example shows how you could use this: + + + +{% capture live_example_view %} +| First name | Last name |
|---|---|
| + | + |
+ Name: + + +
+Click the name to edit it; click elsewhere to apply changes.
+{% endcapture %} + +{% capture live_example_viewmodel %} +function PersonViewModel(name) { + // Data + this.name = ko.observable(name); + this.editing = ko.observable(false); + + // Behaviors + this.edit = function() { this.editing(true) } +} + +ko.applyBindings(new PersonViewModel("Bert Bertington")); +{% endcapture %} + +{% include live-example-minimal.html %} + + +### Parameters + + * Main parameter + + Pass `true` (or some value that evaluates as true) to focus the associated element. Otherwise, the associated element will be unfocused. + + When the user manually focuses or unfocuses the element, your value will be set to `true` or `false` accordingly. + + If the value you supply is observable, the `hasFocus` binding will update the element's focus state whenever that observable value changes. + + * Additional parameters + + * None + +### Dependencies + +None, other than the core Knockout library. \ No newline at end of file diff --git a/documentation/html-binding.md b/documentation/html-binding.md new file mode 100644 index 000000000..907cad90b --- /dev/null +++ b/documentation/html-binding.md @@ -0,0 +1,41 @@ +--- +layout: documentation +title: The "html" binding +--- + +### Purpose +The `html` binding causes the associated DOM element to display the HTML specified by your parameter. + +Typically this is useful when values in your view model are actually strings of HTML markup that you want to render. + +### Example + + + + +### Parameters + + * Main parameter + + KO sets the element's `innerHTML` property to your parameter value. Any previous content will be overwritten. + + If this parameter is an observable value, the binding will update the element's content whenever the value changes. If the parameter isn't observable, it will only set the element's content once and will not update it again later. + + If you supply something other than a number or a string (e.g., you pass an object or an array), the `innerHTML` will be equivalent to `yourParameter.toString()` + + * Additional parameters + + * None + +### Note: About HTML encoding + +Since this binding sets your element's content using `innerHTML`, you should be careful not to use it with untrusted model values, because that might open the possibility of a script injection attack. If you cannot guarantee that the content is safe to display (for example, if it is based on a different user's input that was stored in your database), then you can use [the text binding](text-binding.html), which will set the element's text value using `innerText` or `textContent` instead. + +### Dependencies + +None, other than the core Knockout library. \ No newline at end of file diff --git a/documentation/if-binding.md b/documentation/if-binding.md new file mode 100644 index 000000000..e66ba3a17 --- /dev/null +++ b/documentation/if-binding.md @@ -0,0 +1,90 @@ +--- +layout: documentation +title: The "if" binding +--- + +### Purpose +The `if` binding causes a section of markup to appear in your document (and to have its `data-bind` attributes applied), only if a specified expression evaluates to `true` (or a `true`-ish value such as a non-`null` object or nonempty string). + +`if` plays a similar role to [the `visible` binding](visible-binding.html). The difference is that, with `visible`, the contained markup always remains in the DOM and always has its `data-bind` attributes applied - the `visible` binding just uses CSS to toggle the container element's visiblity. The `if` binding, however, physically adds or removes the contained markup in your DOM, and only applies bindings to descendants if the expression is `true`. + +### Example 1 + +This example shows that the `if` binding can dynamically add and remove sections of markup as observable values change. + +{% capture live_example_view %} + + +Choose some countries you'd like to visit:
+ + + +### Example 3: Drop-down list representing arbitrary JavaScript objects, not just strings ++ Your country: + +
+ +