I have create custom checkout Step between shipping and payment step . Now i want to show all the order details and customer fillup details.
Below is the files that i have created for the Custom checkout step
Below is the file (checkout_index_index.xml) i have created for new step in the checkout :
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<!-- The new step you add -->
<item name="my-new-step" xsi:type="array">
<item name="component" xsi:type="string">Mycustom_Checkoutnew/js/view/my-step-view</item>
<!--To display step content before shipping step "sortOrder" value should be < 1-->
<!--To display step content between shipping step and payment step 1 < "sortOrder" < 2 -->
<!--To display step content after payment step "sortOrder" > 2 -->
<item name="sortOrder" xsi:type="string">1</item>
<item name="children" xsi:type="array">
<!--add here child component declaration for your step-->
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
In the second file (my-step-view.js) there will be js code as follow :
define([
'ko',
'uiComponent',
'underscore',
'Magento_Checkout/js/model/step-navigator'
], function (ko, Component, _, stepNavigator) {
'use strict';
/**
* mystep - is the name of the component's .html template,
* <Vendor>_<Module> - is the name of your module directory.
*/
return Component.extend({
defaults: {
template: 'Mycustom_Checkoutnew/mystep'
},
// add here your logic to display step,
isVisible: ko.observable(true),
/**
* #returns {*}
*/
initialize: function () {
this._super();
// register your step
stepNavigator.registerStep(
// step code will be used as step content id in the component template
'shippingaddress',
// step alias
null,
// step title value
'Shipping',
// observable property with logic when display step or hide step
this.isVisible,
_.bind(this.navigate, this),
/**
* sort order value
* 'sort order value' < 10: step displays before shipping step;
* 10 < 'sort order value' < 20 : step displays between shipping and payment step
* 'sort order value' > 20 : step displays after payment step
*/
15
);
return this;
},
/**
* The navigate() method is responsible for navigation between checkout steps
* during checkout. You can add custom logic, for example some conditions
* for switching to your custom step
* When the user navigates to the custom step via url anchor or back button we_must show step manually here
*/
navigate: function () {
this.isVisible(true);
},
/**
* #returns void
*/
navigateToNextStep: function () {
stepNavigator.next();
}
});
});
Below is the html file (mystep.html) where i want to display all the data of orders and the customer
<li id="shippingaddress" data-bind="fadeVisible: isVisible">
<div class="step-title" data-bind="i18n: 'Shipping'" data-role="title"></div>
<div id="checkout-step-title"
class="step-content"
data-role="content">
<form data-bind="submit: navigateToNextStep" novalidate="novalidate">
<div class="actions-toolbar">
<div class="primary">
<button data-role="opc-continue" type="submit" class="button action continue primary">
<span><!-- ko i18n: 'Next'--><!-- /ko --></span>
</button>
</div>
</div>
</form>
</div>
</li>
In the new custom page i want show following things
1)Email 2)Contact 3 ) Address that sellected in previous step
4)Shipping method
If anyone can help me in this then please let me know
In frontend i want to show data as per image attached. go through that image
Related
I am trying to add my new shipping method with a map (INPOST) but I have a problem. My map won't load. Then I try to use a case with no map, with points but still not working in the checkout. I tried to do this on the product page and it worked.
My custom shipping method working good, only problem with that map
Documentation: https://docs.inpost24.com/pages/viewpage.action?pageId=7798862
Magento version: 2.3
Bellow my codes:
app/code/Kitsune/Inpost/view/frontend/layout/checkout_index_index.xml
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<script src="https://geowidget.easypack24.net/js/sdk-for-javascript.js" src_type="url" async="async"/>
<css src="https://geowidget.easypack24.net/css/easypack.css" src_type="url"/>
</head>
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shipping-step" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shippingAddress" xsi:type="array">
<item name="config" xsi:type="array">
<item name="shippingMethodItemTemplate" xsi:type="string">Kitsune_Inpost/custom-method-item-template</item>
<item name="shippingMethodListTemplate" xsi:type="string">Kitsune_Inpost/custom-method-list-template</item> </item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
app/code/Kitsune/Inpost/view/frontend/web/js/inpost.js
jQuery( document ).ready(function() {
easyPack.init({
instance: 'pl',
mapType: 'osm',
searchType: 'osm',
points: {
types: ['parcel_locker'],
},
map: {
useGeolocation: true,
initialTypes: ['parcel_locker']
}
})
window.onload = function() {
easyPack.dropdownWidget('easypack-widget', function(point) {
console.log(point)
});
}
});
app/code/Kitsune/Inpost/view/frontend/web/template/custom-method-item-template.html
<!-- Initialize collapsible binding -->
<tbody collapsible="as: '$collapsible_' + method.method_code">
<tr class="row">
<td class="col col-method">
<input type="radio"
class="radio"
click="element.selectShippingMethod"
ifnot="method.error_message"
ko-checked="element.isSelected"
ko-value="method.carrier_code + '_' + method.method_code"
attr="'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code,
'checked': element.rates().length == 1 || element.isSelected" />
</td>
<td class="col col-price">
<each args="element.getRegion('price')" render="" />
</td>
<td class="col col-method"
attr="'id': 'label_method_' + method.method_code + '_' + method.carrier_code"
text="method.method_title" />
<td class="col col-carrier"
attr="'id': 'label_carrier_' + method.method_code + '_' + method.carrier_code"
text="method.carrier_title" />
<!-- Column with collapsible trigger -->
<td class="col">
<!-- ko if: method.carrier_code == 'kitsune_inpost' -->
<div data-bind="mageInit: {'inpost': {}}" id="easypack-widget"></div>
<h1>eloo</h1>
<!-- /ko -->
</td>
</tr>
<!-- Row for shipping method description -->
<tr class="row" visible="$context['$collapsible_' + method.method_code].opened">
<td class="col" colspan="5" i18n="'Some description.'"/>
</tr>
<tr class="row row-error"
if="method.error_message">
<td class="col col-error" colspan="5">
<div role="alert" class="message error">
<div text="method.error_message"></div>
</div>
<span class="no-display">
<input type="radio"
attr="'value' : method.method_code, 'id': 's_method_' + method.method_code" />
</span>
</td>
</tr>
</tbody>
app/code/Kitsune/Inpost/view/frontend/web/template/custom-method-list-template.html
<div id="checkout-shipping-method-load">
<table class="table-checkout-shipping-method" markdown="1"> <thead>
<tr class="row" markdown="1">
<th class="col col-method" translate="'Select Method'" />
<th class="col col-price" translate="'Price'" />
<th class="col col-method" translate="'Method Title'" />
<th class="col col-carrier" translate="'Carrier Title'" />
<!-- Column for triggers -->
<th class="col" />
</tr>
</thead> <!-- tbody was moved inside item template --> <!-- ko foreach: { data: rates(), as: 'method'} --> <!--ko template: { name: element.shippingMethodItemTemplate} --><!-- /ko --> <!-- /ko --> </table>
</div>
app/code/Kitsune/Inpost/view/frontend/requirejs-config.js
var config = {
map: {
'*': {
inpost: 'Kitsune_Inpost/js/inpost'
}
}
}
I wanted to add just comment instead of posting an answer (but I can't comment).
Wrap your js in define and check comments I added here:
define(['domReady!'], function() {
// check here via debugger if code is executed
// then you can investigate why rest isn't working
window.easyPack.init({
instance: 'pl',
mapType: 'osm',
searchType: 'osm',
points: {
types: ['parcel_locker'],
},
map: {
useGeolocation: true,
initialTypes: ['parcel_locker']
}
});
// this also won't work as you can miss onload event
window.onload = function() {
easyPack.dropdownWidget('easypack-widget', function(point)
{
console.log(point)
});
};
});
you need be sure that easyPack will be available. Do you load it synchronously? I see some async version in documentation so you need to think about that and implement it properly.
I'm currently making a custom UI-component for the adminhtml form which extends the default UI-select element. But the issue is that when I make a selection on this new UI-select all fields have their values cleared and if they are required the error appears.
I've been debugging for a while and have reached the conclusion that this issue appears down the line when the code gets to this.value(data) and also throws an exception.
Uncaught TypeError: Cannot read property 'click' of undefined
The element which is clicked in this case is the same element from the normal UI-select.
This is the component's XML through which it's added to the form.
And yes the options shouldn't be there but I couldn't figure out where to put it otherwise to make it work.
<field name="programs" class="Mirasvit\Affiliate\Component\Filters\Type\MultiplePrograms">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Programs</item>
<item name="componentType" xsi:type="string">field</item>
<item name="formElement" xsi:type="string">select</item>
<item name="elementTmpl" xsi:type="string">Mirasvit_Affiliate/form/elements/multiple-programs</item>
<item name="component" xsi:type="string">Mirasvit_Affiliate/js/component/multiple-programs</item>
<item name="filterOptions" xsi:type="boolean">true</item>
<item name="multiple" xsi:type="boolean">false</item>
<item name="options" xsi:type="object">Mirasvit\Affiliate\Ui\Account\Source\Program</item>
<item name="tableOptions" xsi:type="object">Mirasvit\Affiliate\Ui\Program\Source\ProgramsWithTiers</item>
<item name="tableValues" xsi:type="object">Mirasvit\Affiliate\Ui\Program\Source\AccountPrograms</item>
<item name="tableFields" xsi:type="array">
<item name="name" xsi:type="array">
<item name="label" xsi:type="string">Name</item>
<item name="type" xsi:type="string">text</item>
</item>
<item name="tier" xsi:type="array">
<item name="label" xsi:type="string">Tier</item>
<item name="type" xsi:type="string">select</item>
</item>
<item name="fixed" xsi:type="array">
<item name="label" xsi:type="string">Fixed tier</item>
<item name="type" xsi:type="string">checkbox</item>
</item>
</item>
</item>
</argument>
</field>
This is the component's js file
define([
'underscore',
'jquery',
'Magento_Ui/js/form/element/ui-select',
'uiRegistry',
], function (_, $, Abstract, registry) {
'use strict';
return Abstract.extend({
defaults: {
optionsCache: [],
options: [],
listVisible: false,
tableOptions: [],
tableValues: [],
tableFields: {},
currentSelected: [],
},
toggleOptionSelected: function (data) {
if (this.lastSelectable && data.hasOwnProperty(this.separator)) {
return this;
}
this.options(this.options.without(data));
/**
* THIS IS WHERE THE COMPONENT BREAKS
*/
this.value(data);
this.listVisible(false);
return this;
},
cleanHoveredElement: function () {
if (this.hoveredElement) {
$(this.hoveredElement)
.children(this.actionTargetSelector)
.removeClass(this.hoverClass);
this.hoveredElement = null;
}
return this;
},
});
});
And this is the component's php file
class MultiplePrograms extends Select
{
public function prepare() {
$config = $this->getData('config');
/** #var ArrayHelper $helper */
$helper = ObjectManager::getInstance()->get(ArrayHelper::class);
if (isset($config['tableOptions']) && $config['tableOptions'] instanceof ArrayInterface) {
$config['tableOptions'] = $config['tableOptions']->toOptionArray();
} else {
throw new \Exception('Missing tableOptions tag for multiple-options field');
}
if (isset($config['tableFields'])) {
$config['tableFields'] = $helper->nameToColumn($config['tableFields']);
} else {
throw new \Exception('Missing tableValues tag for multiple-options field');
}
if (isset($config['tableValues']) && $config['tableValues'] instanceof ArrayInterface) {
$config['tableValues'] = $config['tableValues']->toOptionArray();
}
if (isset($config['options']) && $config['options'] instanceof ArrayInterface) {
$config['options'] = $config['options']->toOptionArray();
}
$this->setData('config', (array)$config);
parent::prepare();
}
}
I want to be able to store information in the value of this component so that it can be passed to the controller later
I found out why this was happening.
The reason it was throwing that error is because this.value() contained all the form data and thus by setting this.value({anything}) to anything, would clear all of the fields stored and trigger the errors and the observers.
I have tried to create new payment method in Magento 2. When I tried to configure it, it is coming in the back end, but not in the front end. If anyone has fixed this issue, please let me know the fixes.
My block form for Quickpay.php
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Quickpay\Block\Form;
/**
* Block for Quickpay payment method form
*/
class Quickpay extends \Magento\Quickpay\Block\Form\AbstractInstruction
{
/**
* Cash on delivery template
*
* #var string
*/
protected $_template = 'form/quickpay.phtml';
}
AbstractInstuction.php
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Quickpay\Block\Form;
/**
* Abstract class for Quickpay payment method form
*/
abstract class AbstractInstruction extends \Magento\Payment\Block\Form
{
/**
* Instructions text
*
* #var string
*/
protected $_instructions;
/**
* Get instructions text from config
*
* #return null|string
*/
public function getInstructions()
{
if ($this->_instructions === null) {
/** #var \Magento\Payment\Model\Method\AbstractMethod $method */
$method = $this->getMethod();
$this->_instructions = $method->getConfigData('instructions');
}
return $this->_instructions;
}
}
Model Quickpay.php
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Quickpay\Model;
/**
* Quickpay payment method model
*
* #method \Magento\Quote\Api\Data\PaymentMethodExtensionInterface getExtensionAttributes()
*/
class Quickpay extends \Magento\Payment\Model\Method\AbstractMethod
{
const PAYMENT_METHOD_QUICKPAY_CODE = 'quickpay';
/**
* Payment method code
*
* #var string
*/
protected $_code = self::PAYMENT_METHOD_QUICKPAY_CODE;
/**
* Cash On Delivery payment block paths
*
* #var string
*/
protected $_formBlockType = 'Magento\Quickpay\Block\Form\Quickpay';
/**
* Info instructions block path
*
* #var string
*/
protected $_infoBlockType = 'Magento\Payment\Block\Info\Instructions';
/**
* Availability option
*
* #var bool
*/
protected $_isOffline = true;
/**
* Get instructions text from config
*
* #return string
*/
public function getInstructions()
{
return trim($this->getConfigData('instructions'));
}
}
view adminhtml/templates/form/quickpay.html
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
// #codingStandardsIgnoreFile
/**
* #var $block \Magento\Quickpay\Block\Form\Quickpay
*/
$instructions = $block->getInstructions();
?>
<?php if ($instructions): ?>
<?php $methodCode = $block->escapeHtml($block->getMethodCode());?>
<ul class="form-list checkout-agreements" id="payment_form_<?php /* #noEscape */ echo $methodCode ?>" style="display:none;">
<li>
<div class="<?php /* #noEscape */ echo $methodCode ?>-instructions-content checkout-agreement-item-content">
<?php /* #noEscape */ echo nl2br($block->escapeHtml($instructions)) ?>
</div>
</li>
</ul>
<?php endif; ?>
view/frontend/layout/checkout_index_index.xml
<?xml version="1.0"?>
<!--
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<item name="billing-step" xsi:type="array">
<item name="children" xsi:type="array">
<item name="payment" xsi:type="array">
<item name="children" xsi:type="array">
<item name="renders" xsi:type="array">
<!-- merge payment method renders here -->
<item name="children" xsi:type="array">
<item name="offline-payments" xsi:type="array">
<item name="component" xsi:type="string">Magento_OfflinePayments/js/view/payment/offline-payments</item>
<item name="methods" xsi:type="array">
<item name="quickpay" xsi:type="array">
<item name="isBillingAddressRequired" xsi:type="boolean">true</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
view/frontend/templates/form/quickpay.phtml
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
// #codingStandardsIgnoreFile
/**
* #var $block \Magento\Quickpay\Block\Form\Quickpay
*/
$instructions = $block->getInstructions();
?>
<?php if ($instructions): ?>
<?php $methodCode = $block->escapeHtml($block->getMethodCode());?>
<div class="items <?php /* #noEscape */ echo $methodCode ?> instructions agreement" id="payment_form_<?php /* #noEscape */ echo $methodCode ?>" style="display: none;">
<?php /* #noEscape */ echo nl2br($block->escapeHtml($instructions)) ?>
</div>
<?php endif; ?>
view/frontend/web/template/payment/quickpay.html
<!--
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">
<div class="payment-method-title field choice">
<input type="radio"
name="payment[method]"
class="radio"
data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()"/>
<label data-bind="attr: {'for': getCode()}" class="label"><span data-bind="text: getTitle()"></span></label>
</div>
<div class="payment-method-content">
<!-- ko foreach: getRegion('messages') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<div class="payment-method-billing-address">
<!-- ko foreach: $parent.getRegion(getBillingAddressFormName()) -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
</div>
<p data-bind="html: getInstructions()"></p>
<div class="checkout-agreements-block">
<!-- ko foreach: $parent.getRegion('before-place-order') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
</div>
<div class="actions-toolbar">
<div class="primary">
<button class="action primary checkout"
type="submit"
data-bind="
click: placeOrder,
attr: {title: $t('Place Order')},
enable: (getCode() == isChecked()),
css: {disabled: !isPlaceOrderActionAllowed()}
"
disabled>
<span data-bind="i18n: 'Place Order'"></span>
</button>
</div>
</div>
</div>
</div>
js
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
/*browser:true*/
/*global define*/
define(
[
'uiComponent',
'Magento_Checkout/js/model/payment/renderer-list'
],
function (
Component,
rendererList
) {
'use strict';
rendererList.push(
{
type: 'quickpay',
component: 'Magento_Quickpay/js/view/payment/method-renderer/quickpay-method'
}
);
/** Add view logic here if needed */
return Component.extend({});
}
);
I need to parse an XML file to Ruby objects.
Is there a tool to read attributes from XML like this
report.system_slots.items to return an array of item properties,
or report.system_slots.current_usage to return 'Available'?
Is it possible to do this with Nokogiri?
<page Title="System Slots" H1="Property" H2="Value" __type__="2">
<item Property="System Slot 1">
<item Property="Name" Value="PCI1"/>
<item Property="Type" Value="PCI"/>
<item Property="Data Bus Width" Value="32 bits"/>
<item Property="Current Usage" Value="Available"/>
<item Property="Characteristics">
<item Property="Vcc voltage supported" Value="3.3 V, 5.0 V"/>
<item Property="Shared" Value="No"/>
<item Property="PME Signal" Value="Yes"/>
<item Property="Support Hot Plug" Value="No"/>
<item Property="PCI slot supports SMBus signal" Value="Yes"/>
</item>
</item>
Look at Ox. It reads XML and returns a reasonable Ruby object facsimile of the XML.
require 'ox'
hash = {'foo' => { 'bar' => 'hello world'}}
puts Ox.dump(hash)
pp Ox.parse_obj(Ox.dump(hash))
Dumping that into IRB gives me:
require 'ox'
> hash = {'foo' => { 'bar' => 'hello world'}}
{
"foo" => {
"bar" => "hello world"
}
}
> puts Ox.dump(hash)
<h>
<s>foo</s>
<h>
<s>bar</s>
<s>hello world</s>
</h>
</h>
nil
> pp Ox.parse_obj(Ox.dump(hash))
{"foo"=>{"bar"=>"hello world"}}
{
"foo" => {
"bar" => "hello world"
}
}
That said, your XML sample is broken and won't work with OX. It WILL work with Nokogiri, though there are errors reported, which would hint that you wouldn't be able to parse the DOM correctly.
My question is, why do you want to convert the XML to an object? It is SO much easier to handle XML using a parser like Nokogiri. Using a fixed version of your XML:
require 'nokogiri'
xml = '
<xml>
<page Title="System Slots" H1="Property" H2="Value" __type__="2">
<item Property="System Slot 1"/>
<item Property="Name" Value="PCI1"/>
<item Property="Type" Value="PCI"/>
<item Property="Data Bus Width" Value="32 bits"/>
<item Property="Current Usage" Value="Available"/>
<item Property="Characteristics">
<item Property="Vcc voltage supported" Value="3.3 V, 5.0 V"/>
<item Property="Shared" Value="No"/>
<item Property="PME Signal" Value="Yes"/>
<item Property="Support Hot Plug" Value="No"/>
<item Property="PCI slot supports SMBus signal" Value="Yes"/>
</item>
</page>
</xml>'
doc = Nokogiri::XML(xml)
page = doc.at('page')
page['Title'] # => "System Slots"
page.at('item[#Property="Current Usage"]')['Value'] # => "Available"
item_properties = page.at('item[#Property="Characteristics"]')
item_properties.at('item[#Property="PCI slot supports SMBus signal"]')['Value'] # => "Yes"
Parsing a big XML document into memory can return a labyrinth of arrays and hashes that still have to be peeled apart to access the values you want. Using Nokogiri, you have CSS and XPath accessors which are easy to learn and read; I used CSS above but could easily have used XPath to accomplish the same things.
My XML:
<content>
<item id="1">A</item>
<item id="2">B</item>
<item id="4">D</item>
</content>
I have loaded this using XML similar to:
XDocument xDoc = new XDocument(data.Value);
var items = from i in xDoc.Element("content").Elements("item")
select i;
I want to insert another element, to end up with something like:
<content>
<item id="1">A</item>
<item id="2">B</item>
<item id="3">C</item>
<item id="4">D</item>
</content>
How do I do this using Linq2Xml?
Try this:
xDoc.Element("content")
.Elements("item")
.Where(item => item.Attribute("id").Value == "2").FirstOrDefault()
.AddAfterSelf(new XElement("item", "C", new XAttribute("id", "3")));
Or, if you like XPath like I do:
xDoc.XPathSelectElement("content/item[#id = '2']")
.AddAfterSelf(new XElement("item", "C", new XAttribute("id", "3")));